diff --git a/.changeset/add-mcp-server.md b/.changeset/add-mcp-server.md
new file mode 100644
index 00000000..a686737a
--- /dev/null
+++ b/.changeset/add-mcp-server.md
@@ -0,0 +1,9 @@
+---
+"@googleworkspace/cli": minor
+---
+
+feat: add `gws mcp` Model Context Protocol server
+
+Adds a new `gws mcp` subcommand that starts an MCP server over stdio,
+exposing Google Workspace APIs as structured tools to any MCP-compatible
+client (Claude Desktop, Gemini CLI, VS Code, etc.).
diff --git a/README.md b/README.md
index f302075c..7f47a207 100644
--- a/README.md
+++ b/README.md
@@ -15,14 +15,12 @@ Drive, Gmail, Calendar, and every Workspace API. Zero boilerplate. Structured JS
-
```bash
npm install -g @googleworkspace/cli
```
`gws` doesn't ship a static list of commands. It reads Google's own [Discovery Service](https://developers.google.com/discovery) at runtime and builds its entire command surface dynamically. When Google Workspace adds an API endpoint or method, `gws` picks it up automatically.
-
> [!IMPORTANT]
> This project is under active development. Expect breaking changes as we march toward v1.0.
@@ -36,6 +34,7 @@ npm install -g @googleworkspace/cli
- [Why gws?](#why-gws)
- [Authentication](#authentication)
- [AI Agent Skills](#ai-agent-skills)
+- [MCP Server](#mcp-server)
- [Advanced Usage](#advanced-usage)
- [Architecture](#architecture)
- [Development](#development)
@@ -55,7 +54,6 @@ Or build from source:
cargo install --path .
```
-
## Why gws?
**For humans** — stop writing `curl` calls against REST docs. `gws` gives you tab‑completion, `--help` on every resource, `--dry-run` to preview requests, and auto‑pagination.
@@ -82,7 +80,6 @@ gws schema drive.files.list
gws drive files list --params '{"pageSize": 100}' --page-all | jq -r '.files[].name'
```
-
## Authentication
The CLI supports multiple auth workflows so it works on your laptop, in CI, and on a server.
@@ -167,16 +164,15 @@ export GOOGLE_WORKSPACE_CLI_TOKEN=$(gcloud auth print-access-token)
### Precedence
-| Priority | Source | Set via |
-|----------|--------|---------|
-| 1 | Access token | `GOOGLE_WORKSPACE_CLI_TOKEN` |
-| 2 | Credentials file | `GOOGLE_WORKSPACE_CLI_CREDENTIALS_FILE` |
-| 3 | Encrypted credentials (OS keyring) | `gws auth login` |
-| 4 | Plaintext credentials | `~/.config/gws/credentials.json` |
+| Priority | Source | Set via |
+| -------- | ---------------------------------- | --------------------------------------- |
+| 1 | Access token | `GOOGLE_WORKSPACE_CLI_TOKEN` |
+| 2 | Credentials file | `GOOGLE_WORKSPACE_CLI_CREDENTIALS_FILE` |
+| 3 | Encrypted credentials (OS keyring) | `gws auth login` |
+| 4 | Plaintext credentials | `~/.config/gws/credentials.json` |
Environment variables can also live in a `.env` file.
-
## AI Agent Skills
The repo ships 100+ Agent Skills (`SKILL.md` files) — one for every supported API, plus higher-level helpers for common workflows and 50 curated recipes for Gmail, Drive, Docs, Calendar, and Sheets. See the full [Skills Index](docs/skills.md) for the complete list.
@@ -205,10 +201,10 @@ The `gws-shared` skill includes an `install` block so OpenClaw auto-installs the
-
## Gemini CLI Extension
1. Authenticate the CLI first:
+
```bash
gws setup
```
@@ -220,6 +216,39 @@ The `gws-shared` skill includes an `install` block so OpenClaw auto-installs the
Installing this extension gives your Gemini CLI agent direct access to all `gws` commands and Google Workspace agent skills. Because `gws` handles its own authentication securely, you simply need to authenticate your terminal once prior to using the agent, and the extension will automatically inherit your credentials.
+## MCP Server
+
+`gws mcp` starts a [Model Context Protocol](https://modelcontextprotocol.io) server over stdio, exposing Google Workspace APIs as structured tools that any MCP-compatible client (Claude Desktop, Gemini CLI, VS Code, etc.) can call.
+
+```bash
+gws mcp -s drive # expose Drive tools
+gws mcp -s drive,gmail,calendar # expose multiple services
+gws mcp -s all # expose all services (many tools!)
+```
+
+Configure in your MCP client:
+
+```json
+{
+ "mcpServers": {
+ "gws": {
+ "command": "gws",
+ "args": ["mcp", "-s", "drive,gmail,calendar"]
+ }
+ }
+}
+```
+
+> [!TIP]
+> Each service adds roughly 10–80 tools. Keep the list to what you actually need
+> to stay under your client's tool limit (typically 50–100 tools).
+
+| Flag | Description |
+| ----------------------- | -------------------------------------------- |
+| `-s, --services ` | Comma-separated services to expose, or `all` |
+| `-w, --workflows` | Also expose workflow tools |
+| `-e, --helpers` | Also expose helper tools |
+
## Advanced Usage
### Multipart Uploads
@@ -230,11 +259,11 @@ gws drive files create --json '{"name": "report.pdf"}' --upload ./report.pdf
### Pagination
-| Flag | Description | Default |
-|------|-------------|---------|
-| `--page-all` | Auto-paginate, one JSON line per page (NDJSON) | off |
-| `--page-limit ` | Max pages to fetch | 10 |
-| `--page-delay ` | Delay between pages | 100 ms |
+| Flag | Description | Default |
+| ------------------- | ---------------------------------------------- | ------- |
+| `--page-all` | Auto-paginate, one JSON line per page (NDJSON) | off |
+| `--page-limit ` | Max pages to fetch | 10 |
+| `--page-delay ` | Delay between pages | 100 ms |
### Model Armor (Response Sanitization)
@@ -245,11 +274,10 @@ gws gmail users messages get --params '...' \
--sanitize "projects/P/locations/L/templates/T"
```
-| Variable | Description |
-|----------|-------------|
+| Variable | Description |
+| ---------------------------------------- | ---------------------------- |
| `GOOGLE_WORKSPACE_CLI_SANITIZE_TEMPLATE` | Default Model Armor template |
-| `GOOGLE_WORKSPACE_CLI_SANITIZE_MODE` | `warn` (default) or `block` |
-
+| `GOOGLE_WORKSPACE_CLI_SANITIZE_MODE` | `warn` (default) or `block` |
## Architecture
@@ -263,7 +291,6 @@ gws gmail users messages get --params '...' \
All output — success, errors, download metadata — is structured JSON.
-
## Troubleshooting
### API not enabled — `accessNotConfigured`
@@ -291,6 +318,7 @@ If a required Google API is not enabled for your GCP project, you will see a
```
**Steps to fix:**
+
1. Click the `enable_url` link (or copy it from the `enable_url` JSON field).
2. In the GCP Console, click **Enable**.
3. Wait ~10 seconds, then retry your `gws` command.
@@ -299,7 +327,6 @@ If a required Google API is not enabled for your GCP project, you will see a
> You can also run `gws setup` which walks you through enabling all required
> APIs for your project automatically.
-
## Development
```bash
@@ -309,7 +336,6 @@ cargo test # unit tests
./scripts/coverage.sh # HTML coverage report → target/llvm-cov/html/
```
-
## License
Apache-2.0
diff --git a/src/executor.rs b/src/executor.rs
index 73e531a7..5a900a08 100644
--- a/src/executor.rs
+++ b/src/executor.rs
@@ -199,6 +199,7 @@ async fn build_http_request(
/// Handle a JSON response: parse, sanitize via Model Armor, output, and check pagination.
/// Returns `Ok(true)` if the pagination loop should continue.
+#[allow(clippy::too_many_arguments)]
async fn handle_json_response(
body_text: &str,
pagination: &PaginationConfig,
@@ -207,6 +208,8 @@ async fn handle_json_response(
output_format: &crate::formatter::OutputFormat,
pages_fetched: &mut u32,
page_token: &mut Option,
+ capture_output: bool,
+ captured: &mut Vec,
) -> Result {
if let Ok(mut json_val) = serde_json::from_str::(body_text) {
*pages_fetched += 1;
@@ -249,7 +252,9 @@ async fn handle_json_response(
}
}
- if pagination.page_all {
+ if capture_output {
+ captured.push(json_val.clone());
+ } else if pagination.page_all {
let is_first_page = *pages_fetched == 1;
println!(
"{}",
@@ -279,7 +284,7 @@ async fn handle_json_response(
}
} else {
// Not valid JSON, output as-is
- if !body_text.is_empty() {
+ if !capture_output && !body_text.is_empty() {
println!("{body_text}");
}
}
@@ -293,7 +298,8 @@ async fn handle_binary_response(
content_type: &str,
output_path: Option<&str>,
output_format: &crate::formatter::OutputFormat,
-) -> Result<(), GwsError> {
+ capture_output: bool,
+) -> Result