BREAKING CHANGE: Remove MCP server mode by sqrrrl · Pull Request #275 · googleworkspace/cli

4 min read Original article ↗

stefanhoth added a commit to stefanhoth/crusty-proxy that referenced this pull request

@stefanhoth @claude

)

## ⚠️ Breaking Change — major release

Existing `allowlist.json` files with `gws_*` sections **must be
updated** before deploying. See the migration guide below.

Semantic-release will cut a **major version** when this merges to `main`
(triggered by the `BREAKING CHANGE:` footer in the final commit).

---

## Background

`@googleworkspace/cli` v0.8.0 removed the `gws mcp` stdio server ([PR
#275](googleworkspace/cli#275)), leaving the
proxy pinned forever to 0.7.0. This PR replaces the stdio MCP upstream
pattern with a direct subprocess-per-call bridge, identical in spirit to
how `goplaces` works, and upgrades gws to the current latest (0.16.0).

## Architecture: `StdioUpstreamClient` → `GwsServiceBridge`

**Before:** a single persistent `gws mcp -s calendar,gmail,...` process
was spawned at startup and spoken to over stdin/stdout via the MCP
protocol. All 7 gws services were aggregated into one upstream client.

**After:** one `GwsServiceBridge` instance per enabled `gws_*` service.
Each tool call spawns a short-lived subprocess:

```
gws_calendar.events_list  { params: { calendarId: "primary", maxResults: 10 } }
  → gws calendar events list --calendar-id primary --max-results 10 --format json

gws_gmail.users_messages_send  { body: { raw: "..." } }
  → gws gmail users messages send --user-id me --json '{"raw":"..."}' --format json
```

The operation→CLI mapping is mechanical: split the operation name on
`_`, prepend the service name → CLI path segments. Parameters split into
`params` (path/query → `--kebab-case` flags) and `body` (request body →
`--json`).

## 🔧 Migration guide

Operation names inside each `gws_*` allowlist block must drop the
redundant service-name prefix. The service key already provides that
context.

| Service | Old operation name | New operation name |
|---|---|---|
| `gws_calendar` | `calendar_events_list` | `events_list` |
| `gws_calendar` | `calendar_events_insert` | `events_insert` |
| `gws_calendar` | `calendar_freebusy_query` | `freebusy_query` |
| `gws_gmail` | `gmail_users_messages_list` | `users_messages_list` |
| `gws_gmail` | `gmail_users_messages_send` | `users_messages_send` |
| `gws_gmail` | `gmail_users_drafts_create` | `users_drafts_create` |
| `gws_drive` | `drive_files_list` | `files_list` |
| `gws_drive` | `drive_files_create` | `files_create` |
| `gws_drive` | `drive_permissions_list` | `permissions_list` |
| `gws_sheets` | `sheets_spreadsheets_get` | `spreadsheets_get` |
| `gws_sheets` | `sheets_spreadsheets_values_get` |
`spreadsheets_values_get` |
| `gws_tasks` | `tasks_tasks_list` | `tasks_list` |
| `gws_tasks` | `tasks_tasklists_insert` | `tasklists_insert` |
| `gws_chat` | `chat_spaces_list` | `spaces_list` |
| `gws_chat` | `chat_spaces_messages_create` | `spaces_messages_create`
|
| `gws_docs` | `docs_documents_get` | `documents_get` |
| `gws_docs` | `docs_documents_batchUpdate` | `documents_batchUpdate` |

The rule: **remove the leading `<service>_` segment** from every
operation name in a `gws_*` block. The updated `config/allowlist.json`
in this repo is the complete reference.

The MCP tool names exposed to OpenClaw also change accordingly:

```
gws.calendar_events_list  →  gws_calendar.events_list
gws.gmail_users_messages_list  →  gws_gmail.users_messages_list
```

## Files changed

| File | Change |
|---|---|
| `src/services/gws.ts` | Full rewrite — `GwsServiceBridge` class |
| `src/upstream/stdio.ts` | **Deleted** — gws was its only consumer |
| `src/index.ts` | Per-service bridge map, updated tool registration and
dispatch, updated health endpoint |
| `src/types.ts` | Update comment on `GWS_SERVICE_KEYS` |
| `config/allowlist.json` | Strip redundant service-name prefix from all
`gws_*` operations |
| `tests/upstream.test.ts` | Replace `StdioUpstreamClient` tests with
`GwsServiceBridge` tests |
| `tests/server.test.ts` | Fix health endpoint shape assertion |
| `Dockerfile` | Unpin gws, upgrade to `@googleworkspace/cli@0.16.0` |
| `renovate.json` | Remove version-block rule, add regex manager to
track gws releases |

## Security properties preserved

- Credentials never in process args — gws reads
`GOOGLE_WORKSPACE_CLI_CREDENTIALS_FILE` from the environment
(bind-mounted read-only)
- Double allowlist enforcement: at tool registration time and at call
time in `handleToolCall()`
- Per-service enable/disable granularity unchanged
- Read-only container / no filesystem writes beyond `/tmp/gws` discovery
cache
- Each operation is an independent short-lived process — no persistent
privileged subprocess

## Upgrade path going forward

Renovate will now raise PRs for new `@googleworkspace/cli` releases (via
the new regex custom manager in `renovate.json`). Upgrading only
requires bumping the version in `Dockerfile` — no code changes needed
unless gws changes its CLI argument conventions.

## Testing

```
bun test      # 23 pass, 0 fail
bun tsc --noEmit  # clean
```

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>