stefanhoth added a commit to stefanhoth/crusty-proxy that referenced this pull request
) ##⚠️ 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>