mcp-google-multi 4.5.0-alpha.3 → 5.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,470 +1,129 @@
1
1
  # mcp-google-multi
2
2
 
3
- A local [MCP](https://modelcontextprotocol.io) server that gives Claude Code (and any MCP client) access to **Gmail**, **Google Drive**, **Google Calendar**, **Google Sheets**, **Google Docs**, **Google Contacts**, **Google Search Console**, **Google Tasks**, **Google Meet**, and optionally **Google Forms**, **Google Chat**, and **Google Workspace Admin** APIs across multiple Google accounts simultaneously.
3
+ A **local** [MCP](https://modelcontextprotocol.io) server that gives Claude Code (and any MCP client) access to your **Google Workspace** — Gmail, Drive, Calendar, Sheets, Docs, Contacts, Tasks, Meet, Search Console (plus optional Forms, Chat, and Workspace Admin) across **multiple Google accounts** at once.
4
4
 
5
5
  [![npm](https://img.shields.io/npm/v/mcp-google-multi?label=npm&color=cb3837)](https://www.npmjs.com/package/mcp-google-multi)
6
6
 
7
- ## Features
8
-
9
- - **Multi-account** — manage any number of Google accounts from a single server
10
- - **175 tools** across 13 Google services
11
- - **Tiered OAuth scopes** — base scopes always granted; Forms/Chat/Alert Center are opt-in bundles; Admin scopes are per-account opt-in with a separate safety flag for destructive writes
12
- - **Config-driven accounts** — defined in `.env`, no code changes needed
13
- - **Auto-refresh** — OAuth tokens refresh transparently and persist to disk
14
- - **Stdio transport** — runs as a local subprocess, no hosting needed
15
-
16
- ## Tools
17
-
18
- ### Gmail (21 tools)
19
-
20
- | Tool | Description |
21
- |------|-------------|
22
- | `gmail_search` | Search messages with Gmail query syntax |
23
- | `gmail_read` | Read a full message by ID |
24
- | `gmail_read_thread` | Read all messages in a thread |
25
- | `gmail_send` | Send an email (optional `htmlBody` for multipart/alternative) |
26
- | `gmail_download_attachment` | Download an email attachment to local disk |
27
- | `gmail_create_draft` | Create a draft (optional `htmlBody` for multipart/alternative) |
28
- | `gmail_modify_labels` | Add/remove labels on a message (star, archive, mark read, etc.) |
29
- | `gmail_trash` | Move a message to Trash (recoverable) |
30
- | `gmail_delete` | Permanently delete a message (irreversible) |
31
- | `gmail_batch_modify` | Bulk add/remove labels across up to 1000 messages |
32
- | `gmail_batch_delete` | Permanently delete multiple messages (irreversible) |
33
- | `gmail_list_drafts` | List all drafts |
34
- | `gmail_get_draft` | Read a specific draft |
35
- | `gmail_send_draft` | Send an existing draft |
36
- | `gmail_list_labels` | List all labels (system + custom) |
37
- | `gmail_create_label` | Create a custom label |
38
- | `gmail_delete_label` | Delete a label |
39
- | `gmail_get_profile` | Get account email, message count, history ID |
40
- | `gmail_list_history` | Get mailbox changes since a history ID |
41
- | `gmail_get_vacation` | Read vacation responder settings |
42
- | `gmail_set_vacation` | Enable/disable vacation responder |
43
-
44
- ### Google Drive (35 tools)
45
-
46
- | Tool | Description |
47
- |------|-------------|
48
- | `drive_search` | Search files with Drive query syntax (shared drives included) |
49
- | `drive_read` | Read file content (exports Workspace docs as text) |
50
- | `drive_list` | List files in a folder or root |
51
- | `drive_upload` | Upload a local file to Drive (optional `convertTo` imports it as a native Google Doc/Sheet/Slides) |
52
- | `drive_download` | Download a binary file to local disk |
53
- | `drive_export` | Export Google Docs/Sheets/Slides to PDF, DOCX, XLSX, Markdown, etc. |
54
- | `drive_create_folder` | Create a new folder |
55
- | `drive_update` | Rename, move, or replace file content |
56
- | `drive_delete` | Permanently delete a file (irreversible) |
57
- | `drive_trash` | Move a file to trash (recoverable) |
58
- | `drive_untrash` | Restore a trashed file |
59
- | `drive_empty_trash` | Permanently delete every file in trash |
60
- | `drive_copy` | Duplicate a file |
61
- | `drive_move` | Move a file between folders |
62
- | `drive_share` | Share with user/group/domain/anyone (with `transferOwnership` + `expirationTime`) |
63
- | `drive_list_permissions` | List who has access to a file |
64
- | `drive_permission_update` | Change a permission's role / expiration in place |
65
- | `drive_remove_permission` | Revoke access |
66
- | `drive_comment_create` / `_list` / `_get` / `_update` / `_delete` | Full comment CRUD with anchor support |
67
- | `drive_reply_create` / `_list` / `_update` / `_delete` | Reply CRUD; reply.create accepts `action: resolve | reopen` |
68
- | `drive_revision_list` / `_update` / `_delete` | Version history; `keepForever` pins against the 200-version cap |
69
- | `drive_access_proposal_list` / `_resolve` | Triage "Request access" submissions |
70
- | `drive_shared_drives_list` / `drive_shared_drive_get` | Shared drive discovery |
71
- | `drive_get_about` | Storage quota and account info |
72
-
73
- ### Google Calendar (11 tools)
74
-
75
- | Tool | Description |
76
- |------|-------------|
77
- | `calendar_list_calendars` | List all calendars |
78
- | `calendar_list_events` | List/search events with time range |
79
- | `calendar_get_event` | Get a single event by ID |
80
- | `calendar_create_event` | Create an event |
81
- | `calendar_update_event` | Update an event |
82
- | `calendar_delete_event` | Delete an event |
83
- | `calendar_quick_add` | Create event from natural language (e.g. "Lunch Thursday 1pm") |
84
- | `calendar_move_event` | Move an event between calendars |
85
- | `calendar_list_instances` | List occurrences of a recurring event |
86
- | `calendar_get_freebusy` | Check free/busy times for calendars |
87
- | `calendar_create_calendar` | Create a new calendar |
88
-
89
- ### Google Sheets (29 tools)
90
-
91
- | Tool | Description |
92
- |------|-------------|
93
- | `sheets_create` | Create a new spreadsheet |
94
- | `sheets_get` | Get spreadsheet metadata |
95
- | `sheets_read_range` / `sheets_batch_read` | Read values |
96
- | `sheets_write_range` / `sheets_batch_write` | Write values |
97
- | `sheets_append_rows` | Append after existing data |
98
- | `sheets_clear_range` / `sheets_batch_clear` | Clear values (preserves formatting) |
99
- | `sheets_add_sheet` / `sheets_delete_sheet` / `sheets_duplicate_sheet` | Tab management |
100
- | `sheets_update_sheet_properties` | Rename, freeze, color, hide, resize |
101
- | `sheets_format_cells` | Colors, fonts, alignment, number formats |
102
- | `sheets_update_borders` | Cell borders with style + color |
103
- | `sheets_merge_cells` / `sheets_unmerge_cells` | Cell merging |
104
- | `sheets_add_conditional_format_rule` | Boolean and gradient conditional formatting |
105
- | `sheets_sort_range` | Multi-column sort |
106
- | `sheets_set_basic_filter` / `sheets_clear_basic_filter` | Filter rows |
107
- | `sheets_find_replace` | Regex-capable, scope-aware find/replace |
108
- | `sheets_auto_resize_dimensions` | Fit rows/columns to content |
109
- | `sheets_set_data_validation` | Dropdowns, checkboxes, custom rules |
110
- | `sheets_add_named_range` / `sheets_delete_named_range` | Named ranges |
111
- | `sheets_insert_dimension` / `sheets_delete_dimension` | Insert/delete rows or columns |
112
- | `sheets_batch_update` | Generic batchUpdate escape hatch (any Request type) |
113
-
114
- ### Google Docs (27 tools)
115
-
116
- | Tool | Description |
117
- |------|-------------|
118
- | `docs_create` | Create a new document |
119
- | `docs_get` | Metadata + optional tab content / suggestionsViewMode |
120
- | `docs_read` | Read document content as plain text |
121
- | `docs_insert_text` | Insert text at position or end |
122
- | `docs_replace_text` | Find and replace |
123
- | `docs_delete_range` | Delete a character range |
124
- | `docs_update_style` | Inline text formatting (bold, italic, font, size) |
125
- | `docs_update_paragraph_style` | Alignment, heading, indents, spacing |
126
- | `docs_update_document_style` | Page size, margins, header/footer behavior |
127
- | `docs_insert_table` | Insert a table |
128
- | `docs_modify_table` | insertRow / insertColumn / deleteRow / deleteColumn / mergeCells / unmergeCells |
129
- | `docs_create_named_range` / `docs_delete_named_range` / `docs_replace_named_range_content` | Mail-merge primitive |
130
- | `docs_create_paragraph_bullets` / `docs_delete_paragraph_bullets` | Bulleted/numbered lists |
131
- | `docs_insert_inline_image` | PNG/JPEG/GIF image insertion |
132
- | `docs_insert_page_break` / `docs_insert_section_break` | Layout breaks |
133
- | `docs_create_header` / `docs_delete_header` / `docs_create_footer` / `docs_delete_footer` | Branded headers and footers |
134
- | `docs_add_tab` / `docs_delete_tab` / `docs_update_tab_properties` | Tabs CRUD |
135
- | `docs_batch_update` | Generic batchUpdate escape hatch (any Request type) |
136
-
137
- ### Google Contacts (9 tools)
138
-
139
- | Tool | Description |
140
- |------|-------------|
141
- | `contacts_search` | Search contacts |
142
- | `contacts_get` | Get one contact |
143
- | `contacts_list` | List contacts (paginated) |
144
- | `contacts_create` | Create a contact |
145
- | `contacts_update` | Update a contact |
146
- | `contacts_delete` | Delete a contact |
147
- | `contacts_groups_list` | List groups |
148
- | `contacts_group_members` | List members of a group |
149
- | `contacts_group_create` | Create a group |
150
-
151
- ### Google Search Console (10 tools)
152
-
153
- | Tool | Description |
154
- |------|-------------|
155
- | `searchconsole_sites_list` | List Search Console properties |
156
- | `searchconsole_sites_get` | Get a property |
157
- | `searchconsole_sites_add` | Add a property |
158
- | `searchconsole_sites_delete` | Remove a property |
159
- | `searchconsole_sitemaps_list` | List sitemaps |
160
- | `searchconsole_sitemaps_get` | Sitemap status |
161
- | `searchconsole_sitemaps_submit` | Submit a sitemap |
162
- | `searchconsole_sitemaps_delete` | Delete a sitemap |
163
- | `searchconsole_searchanalytics_query` | Query search analytics |
164
- | `searchconsole_url_inspect` | Inspect a URL |
165
-
166
- ### Google Tasks (12 tools) — _new in v4_
167
-
168
- | Tool | Description |
169
- |------|-------------|
170
- | `tasks_lists_list` / `tasks_list_get` / `tasks_list_insert` / `tasks_list_update` / `tasks_list_delete` | Tasklist management |
171
- | `tasks_list` | List tasks (filter by completion, due, updated time) |
172
- | `tasks_get` / `tasks_insert` / `tasks_update` / `tasks_delete` | Task CRUD |
173
- | `tasks_move` | Re-parent / re-position / move to another list |
174
- | `tasks_clear` | Delete every completed task |
175
-
176
- ### Google Meet (5 tools) — _new in v4_
177
-
178
- | Tool | Description |
179
- |------|-------------|
180
- | `meet_conference_records_list` | List past Meet sessions |
181
- | `meet_conference_record_get` | Get one record |
182
- | `meet_recordings_list` | Recordings on a record |
183
- | `meet_transcripts_list` | Transcripts on a record |
184
- | `meet_transcript_entries_list` | Per-speaker transcript text |
185
-
186
- ### Google Forms (4 tools, optional bundle) — _new in v4_
187
-
188
- Enable with `GOOGLE_OPTIONAL_SCOPES=forms` in `.env`.
189
-
190
- | Tool | Description |
191
- |------|-------------|
192
- | `forms_get` | Form definition |
193
- | `forms_responses_list` | List responses with filter |
194
- | `forms_response_get` | Single response |
195
- | `forms_watches_list` | List Pub/Sub watches |
196
-
197
- ### Google Chat (4 tools, optional bundle) — _new in v4_
198
-
199
- Enable with `GOOGLE_OPTIONAL_SCOPES=chat` in `.env` (combine: `GOOGLE_OPTIONAL_SCOPES=forms,chat`).
200
-
201
- | Tool | Description |
202
- |------|-------------|
203
- | `chat_spaces_list` | Spaces the user is in |
204
- | `chat_spaces_get` | One space |
205
- | `chat_messages_create` | Send text or Card v2 |
206
- | `chat_messages_list` | List messages with filter / orderBy |
207
-
208
- ### Google Workspace Admin (6 tools, admin bundle) — _new in v4_
209
-
210
- Enable per-account with `GOOGLE_ADMIN_ACCOUNTS=alias1,alias2` in `.env`. Requires the account to be a Workspace super-admin. Personal `@gmail.com` accounts will 403 — never list them here. Destructive writes additionally require `GOOGLE_ALLOW_ADMIN_WRITES=true`.
211
-
212
- | Tool | Description |
213
- |------|-------------|
214
- | `reports_activities_list` | Workspace audit log (login, drive, gmail, admin, token, etc.) |
215
- | `admin_users_list` / `admin_users_get` | User directory reads |
216
- | `admin_users_update` | User edits (gated, see env flag above) |
217
- | `admin_groups_list` / `admin_group_members_list` | Group + member reads |
218
-
219
- Every tool accepts an `account` parameter matching one of your configured aliases.
220
-
221
- ### Google Alert Center (2 tools, optional bundle) — _new in v4_
222
-
223
- Enable with `GOOGLE_OPTIONAL_SCOPES=alertcenter` in `.env`.
224
-
225
- | Tool | Description |
226
- |------|-------------|
227
- | `alertcenter_alerts_list` / `alertcenter_alert_get` | Security alerts (suspicious login, phishing, leaked password, Drive exfil) |
228
-
229
- > **⚠ Requires service-account domain-wide delegation.** Unlike every other bundle, the Alert Center `apps.alerts` scope **cannot** be granted through this server's interactive user-consent OAuth flow — Google rejects it with `Error 400: invalid_scope` / "Some requested scopes cannot be shown". Per [Google's docs](https://developers.google.com/workspace/admin/alertcenter/guides/auth), Alert Center requires a service account with domain-wide delegation. This server is user-OAuth only, so enabling this bundle declares the scope and registers the tools, but the API will reject the resulting tokens until service-account auth is added (see [#4](https://github.com/bakissation/mcp-google-multi/issues/4)). The bundle is kept separate precisely so this limitation never blocks the working admin tools above.
230
-
231
- ## Prerequisites
232
-
233
- - Node.js 18+
234
- - A Google Cloud project with the relevant APIs enabled (see step 1 below)
235
- - An OAuth 2.0 Client ID (Desktop app type)
7
+ > Open-source and **funded by [IdeaCrafters](https://github.com/IdeaCraftersHQ)** — the studio that pays for its development and upkeep.
236
8
 
237
- ## Setup
9
+ - 🔑 **Multi-account** — drive any number of your Google accounts from one server, each by a short alias.
10
+ - 🔒 **Secure by default** — refresh tokens **encrypted at rest** (AES-256-GCM); **writes are deny-by-default**; no telemetry — it talks only to Google.
11
+ - 📦 **npm-first** — install and run with `npx`; everything configured through env vars.
12
+ - 🧰 **~170 tools** across 12 services → full list in **[COVERAGE.md](./COVERAGE.md)**.
238
13
 
239
- ### 1. Google Cloud Credentials
240
-
241
- 1. Go to [Google Cloud Console](https://console.cloud.google.com).
242
- 2. Create or select a project.
243
- 3. Enable the APIs you intend to use:
244
- - **Always required:** [Gmail API](https://console.cloud.google.com/apis/library/gmail.googleapis.com), [Google Drive API](https://console.cloud.google.com/apis/library/drive.googleapis.com), [Google Calendar API](https://console.cloud.google.com/apis/library/calendar-json.googleapis.com), [Google Sheets API](https://console.cloud.google.com/apis/library/sheets.googleapis.com), [Google Docs API](https://console.cloud.google.com/apis/library/docs.googleapis.com), [People API](https://console.cloud.google.com/apis/library/people.googleapis.com), [Search Console API](https://console.cloud.google.com/apis/library/searchconsole.googleapis.com), [Google Tasks API](https://console.cloud.google.com/apis/library/tasks.googleapis.com), [Google Meet API](https://console.cloud.google.com/apis/library/meet.googleapis.com).
245
- - **Optional bundles (only if you enable them):** [Google Forms API](https://console.cloud.google.com/apis/library/forms.googleapis.com), [Google Chat API](https://console.cloud.google.com/apis/library/chat.googleapis.com), [Alert Center API](https://console.cloud.google.com/apis/library/alertcenter.googleapis.com) (the `alertcenter` bundle also needs service-account domain-wide delegation — see the Alert Center section above).
246
- - **Admin bundle (only if you set `GOOGLE_ADMIN_ACCOUNTS`):** [Admin SDK API](https://console.cloud.google.com/apis/library/admin.googleapis.com).
247
- 4. Go to **APIs & Services → Credentials → Create Credentials → OAuth 2.0 Client ID**.
248
- 5. Application type: **Desktop app**.
249
- 6. Add authorized redirect URI: `http://localhost:4242/oauth2callback`.
250
- 7. Copy the **Client ID** and **Client Secret**.
14
+ > v5 is **local + user-OAuth only**. Service accounts and hosting (and the APIs they unlock) are on the [roadmap](https://github.com/bakissation/mcp-google-multi/milestones). **Upgrading from v4?** Jump to [Upgrading](#upgrading-from-v4).
251
15
 
252
- ### 2. Install & Configure
16
+ ## Quick start
253
17
 
254
- **Option A from npm (no clone):**
18
+ You need **Node 20+**, a Google Cloud OAuth client (~2 min — [setup below](#google-cloud-setup)), and a random 32-byte key.
255
19
 
256
20
  ```bash
257
- npm install -g mcp-google-multi # installs the `mcp-google-multi` CLI
258
- # …or run on demand without installing: npx mcp-google-multi
259
- ```
260
-
261
- Published with channel dist-tags — `@latest` (stable), `@dev` (alpha), `@staging` (beta); e.g. `npm i -g mcp-google-multi@dev` for the latest alpha.
262
-
263
- **Option B — from source:**
264
-
265
- ```bash
266
- git clone https://github.com/bakissation/mcp-google-multi.git
267
- cd mcp-google-multi
268
- npm install && npm run build
269
- cp .env.example .env
270
- ```
271
-
272
- Create a `.env` (from source, `.env.example` is the template; from npm, make one in the directory you'll run the server from) with:
21
+ # 1) install
22
+ npm i -g mcp-google-multi
273
23
 
274
- ```env
275
- GOOGLE_CLIENT_ID=your_client_id_here
276
- GOOGLE_CLIENT_SECRET=your_client_secret_here
24
+ # 2) put your config + creds in the environment (see "Configuration").
25
+ # Easiest for a quick try — export them, or drop a .env in your working dir:
26
+ # GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GOOGLE_ACCOUNTS, MASTER_KEY, GOOGLE_PROFILE
277
27
 
278
- # Define your accounts as alias:email pairs (comma-separated)
279
- GOOGLE_ACCOUNTS=work:you@company.com,personal:you@gmail.com
28
+ # 3) authenticate each account (opens a browser; one-time per account)
29
+ mcp-google-multi auth --account work
30
+ mcp-google-multi auth --account personal
280
31
 
281
- # Optional (v4): enable Forms, Chat, and/or Alert Center tool bundles
282
- # (alertcenter additionally requires service-account domain-wide delegation)
283
- # GOOGLE_OPTIONAL_SCOPES=forms,chat
284
-
285
- # Optional (v4): grant Workspace admin scopes to specific accounts
286
- # GOOGLE_ADMIN_ACCOUNTS=work
287
-
288
- # Optional (v4): unlock destructive admin writes (admin_users_update)
289
- # GOOGLE_ALLOW_ADMIN_WRITES=true
32
+ # 4) register with Claude Code
33
+ claude mcp add google-multi -s user -- npx -y mcp-google-multi
290
34
  ```
291
35
 
292
- ### 3. Build
36
+ Restart your MCP client and the tools appear. Tokens are written **encrypted** to `~/.config/mcp-google-multi/tokens/` (override with `TOKEN_STORE_PATH`) — useless to anyone without your `MASTER_KEY`.
293
37
 
294
- ```bash
295
- npm run build
296
- ```
297
-
298
- ### 4. Authenticate Accounts
299
-
300
- ```bash
301
- npm run auth -- work # opens browser → log in with you@company.com
302
- npm run auth -- personal # opens browser → log in with you@gmail.com
303
- ```
38
+ Generate a key: `openssl rand -base64 32`.
304
39
 
305
- Each saves a token to `tokens/<alias>/token.json`. Tokens auto-refresh — you should only need to do this once per account.
40
+ ## Recommended: keep secrets in a vault (Infisical)
306
41
 
307
- ### 5. Register in Claude Code
42
+ A plaintext `.env` is fine to try it out, but for a daily driver, **don't leave `GOOGLE_CLIENT_SECRET` + `MASTER_KEY` on disk** — inject them at launch from a secrets manager. The server just reads `process.env` (it has no idea where the values come from), so wrap it with [Infisical](https://infisical.com):
308
43
 
309
44
  ```bash
310
- claude mcp add google-multi -s user -- node /absolute/path/to/mcp-google-multi/dist/index.js
311
- ```
312
-
313
- Or add it manually to your Claude Code MCP config:
314
-
315
- ```json
316
- {
317
- "mcpServers": {
318
- "google-multi": {
319
- "command": "node",
320
- "args": ["/absolute/path/to/mcp-google-multi/dist/index.js"]
321
- }
322
- }
323
- }
45
+ #!/usr/bin/env bash
46
+ # ~/.local/bin/mcp-google-multi-run — chmod +x, then register this as the MCP command
47
+ set -euo pipefail
48
+ export INFISICAL_TOKEN="$(infisical login --method=universal-auth \
49
+ --client-id "$YOUR_CLIENT_ID" --client-secret "$YOUR_CLIENT_SECRET" --plain --silent)"
50
+ exec infisical run --projectId <project> --env prod --path /mcp-google-multi \
51
+ -- npx -y mcp-google-multi
324
52
  ```
325
53
 
326
- If you installed from npm, point at the package instead and pass config via `env` (authenticate first with `mcp-google-multi auth --account <alias>`):
327
-
328
- ```json
329
- {
330
- "mcpServers": {
331
- "google-multi": {
332
- "command": "npx",
333
- "args": ["-y", "mcp-google-multi"],
334
- "env": {
335
- "GOOGLE_CLIENT_ID": "your_client_id_here",
336
- "GOOGLE_CLIENT_SECRET": "your_client_secret_here",
337
- "GOOGLE_ACCOUNTS": "work:you@company.com,personal:you@gmail.com"
338
- }
339
- }
340
- }
341
- }
342
- ```
343
-
344
- ## Migrating from v3
345
-
346
- v4.0.0 grows the base OAuth scope set (Tasks and Meet are now included by default). Existing v3 tokens lack those scopes and the corresponding tools will 403.
347
-
348
- 1. `git pull && npm install && npm run build`.
349
- 2. Re-authenticate every account: `npm run auth -- <alias>` for each alias.
350
- 3. If you want Forms or Chat tools: set `GOOGLE_OPTIONAL_SCOPES=forms,chat` in `.env` **before** re-authing.
351
- 4. If you're a Workspace admin: list relevant accounts in `GOOGLE_ADMIN_ACCOUNTS` **before** re-authing those accounts. Set `GOOGLE_ALLOW_ADMIN_WRITES=true` only if you need destructive admin operations.
352
- 5. In Google Cloud Console, enable any new APIs you'll use (Tasks, Meet, Forms, Chat, Admin SDK, Alert Center).
353
-
354
- ## Adding / Removing Accounts
355
-
356
- Edit the `GOOGLE_ACCOUNTS` variable in `.env`, rebuild, and authenticate the new account:
357
-
358
54
  ```bash
359
- # Add a new account
360
- # .env: GOOGLE_ACCOUNTS=work:you@company.com,personal:you@gmail.com,freelance:you@freelance.com
361
-
362
- npm run build
363
- npm run auth -- freelance
55
+ claude mcp add google-multi -s user -- ~/.local/bin/mcp-google-multi-run
364
56
  ```
365
57
 
366
- No code changes required.
58
+ Now the only thing on disk is the **encrypted** token store. Pass the token via the `INFISICAL_TOKEN` env var (as above), **not** a `--token` flag, so it never shows up in `ps`. (Any secrets manager works — Doppler, Vault, 1Password CLI, etc. — the pattern is the same.)
367
59
 
368
- ## OAuth Scopes
60
+ ## Configuration
369
61
 
370
- ### Base (always granted)
62
+ | Env var | Required | Description |
63
+ |---|---|---|
64
+ | `GOOGLE_CLIENT_ID` / `GOOGLE_CLIENT_SECRET` | ✓ | OAuth **Desktop** client from Google Cloud |
65
+ | `GOOGLE_ACCOUNTS` | ✓ | `alias:email,…` — e.g. `work:you@co.com,personal:you@gmail.com` |
66
+ | `MASTER_KEY` | ✓ | base64 32-byte key that encrypts the token store (`openssl rand -base64 32`) |
67
+ | `GOOGLE_PROFILE` | — | write policy: `read-only` (default) · `safe-writes` · `full-writes` |
68
+ | `GOOGLE_READ_ONLY` | — | `true` = hard kill-switch for all writes |
69
+ | `GOOGLE_WRITE_ALLOW` / `GOOGLE_WRITE_DENY` | — | glob overrides, e.g. `calendar:*`, `*:delete*` |
70
+ | `GOOGLE_OPTIONAL_SCOPES` | — | extra bundles: `forms`, `chat` |
71
+ | `GOOGLE_ADMIN_ACCOUNTS` | — | aliases granted Workspace-admin scopes (the account's own super-admin OAuth) |
72
+ | `TOKEN_STORE_PATH` | — | override the encrypted token dir (default: `~/.config/mcp-google-multi/tokens`) |
371
73
 
372
- | Scope | Access |
373
- |-------|--------|
374
- | `gmail.modify` | Read, label, trash, delete emails |
375
- | `gmail.send` | Send emails |
376
- | `drive` | Full Drive access (read, upload, share, comments, etc.) |
377
- | `calendar` | Full calendar access |
378
- | `spreadsheets` | Read/write Google Sheets |
379
- | `documents` | Read/write Google Docs |
380
- | `contacts` | Read/write Google Contacts |
381
- | `webmasters` | Google Search Console |
382
- | `tasks` | Read/write Google Tasks |
383
- | `meetings.space.readonly` | Read past Meet conference records and transcripts |
74
+ Inspect the resolved setup any time: `mcp-google-multi config check`.
384
75
 
385
- ### Optional (per-`GOOGLE_OPTIONAL_SCOPES` env var)
76
+ ## Write-control (deny-by-default)
386
77
 
387
- | Bundle key | Scopes |
388
- |------------|--------|
389
- | `forms` | `forms.body`, `forms.responses.readonly` |
390
- | `chat` | `chat.spaces`, `chat.messages`, `chat.messages.create` |
391
- | `alertcenter` | `apps.alerts` — ⚠ requires service-account domain-wide delegation; not grantable via user OAuth (see Alert Center section) |
78
+ Reads are never gated. **Every create/update/delete is off until you opt in** — pick a profile:
392
79
 
393
- ### Admin (per-`GOOGLE_ADMIN_ACCOUNTS` env var)
80
+ | `GOOGLE_PROFILE` | Allows |
81
+ |---|---|
82
+ | `read-only` (default) | reads only |
83
+ | `safe-writes` | create + update (deletes still blocked) |
84
+ | `full-writes` | everything |
394
85
 
395
- Only granted to accounts explicitly listed. Personal Gmail accounts will 403 on these scopes never list them.
86
+ `GOOGLE_READ_ONLY=true` overrides all. For fine control: `GOOGLE_WRITE_ALLOW="calendar:*, sheets:update*"` and `GOOGLE_WRITE_DENY="*:delete*"` (deny wins). `mcp-google-multi config check` prints the resolved policy and exactly which tools are enabled.
396
87
 
397
- | Scope | Access |
398
- |-------|--------|
399
- | `admin.reports.audit.readonly` | Workspace audit log |
400
- | `admin.directory.user` | Read/write Workspace users (writes also gated by `GOOGLE_ALLOW_ADMIN_WRITES`) |
401
- | `admin.directory.group.readonly` | Read groups |
402
- | `admin.directory.group.member.readonly` | Read group members |
88
+ ## What's covered
403
89
 
404
- ## Project Structure
90
+ ~170 tools across Gmail, Drive, Calendar, Sheets, Docs, Contacts, Search Console, Tasks, Meet, and (optional) Forms, Chat, Workspace Admin. **Full per-tool list → [COVERAGE.md](./COVERAGE.md).** Every tool takes an `account` argument matching one of your aliases.
405
91
 
406
- ```
407
- mcp-google-multi/
408
- ├── src/
409
- │ ├── index.ts # Entry point: MCP server or auth CLI
410
- │ ├── accounts.ts # Account config parser (reads from .env)
411
- │ ├── auth.ts # Scope tier resolution + OAuth flow
412
- │ ├── client.ts # OAuth2Client factory with auto-refresh
413
- │ ├── types.ts # Shared TypeScript types
414
- │ └── tools/
415
- │ ├── _errors.ts # Shared googleapis error → MCP response mapper
416
- │ ├── gmail.ts # Gmail (21)
417
- │ ├── drive.ts # Drive (35)
418
- │ ├── calendar.ts # Calendar (11)
419
- │ ├── sheets.ts # Sheets (29)
420
- │ ├── docs.ts # Docs (27)
421
- │ ├── contacts.ts # Contacts (9)
422
- │ ├── searchconsole.ts # Search Console (10)
423
- │ ├── tasks.ts # Tasks (12) — v4
424
- │ ├── meet.ts # Meet (5) — v4
425
- │ ├── forms.ts # Forms (4, optional) — v4
426
- │ ├── chat.ts # Chat (4, optional) — v4
427
- │ └── admin.ts # Admin SDK (6, admin) + Alert Center (2, alertcenter bundle) — v4
428
- ├── tests/ # vitest unit tests (gmail-mime, path-safety, field-mask helpers)
429
- ├── tokens/ # OAuth tokens per account (gitignored)
430
- ├── dist/ # Compiled output (gitignored)
431
- ├── .env # Your credentials (gitignored)
432
- ├── .env.example # Template for .env
433
- ├── CHANGELOG.md
434
- ├── CLAUDE.md # Conventions for AI assistants working on this codebase
435
- ├── package.json
436
- ├── tsconfig.json
437
- └── LICENSE
438
- ```
92
+ ## Google Cloud setup
439
93
 
440
- ## Troubleshooting
94
+ 1. [Google Cloud Console](https://console.cloud.google.com) → create or select a project.
95
+ 2. Enable the APIs you'll use: Gmail, Drive, Calendar, Sheets, Docs, People, Search Console, Tasks, Meet (+ Forms / Chat / Admin SDK if you enable those bundles).
96
+ 3. **APIs & Services → Credentials → Create Credentials → OAuth client ID → Desktop app**.
97
+ 4. Add the redirect URI `http://localhost:4242/oauth2callback`.
98
+ 5. Copy the **Client ID** + **Client Secret** into your environment.
441
99
 
442
- **"GOOGLE_ACCOUNTS is not set"** — Make sure your `.env` file exists in the project root and has the `GOOGLE_ACCOUNTS` variable set.
100
+ ## Upgrading from v4
443
101
 
444
- **"No token file found for account X"** Run `npm run auth -- <alias>` to authenticate that account.
102
+ v5 is a breaking change, but the migration is a one-time, ~2-minute step:
445
103
 
446
- **"Port 4242 is already in use"** Another process is using port 4242. Close it and retry the auth flow.
104
+ 1. Update: `npm i -g mcp-google-multi@latest` (or update your client config).
105
+ 2. Add **`MASTER_KEY`** to your environment (`openssl rand -base64 32`) — now required.
106
+ 3. Encrypt existing tokens: `mcp-google-multi migrate-tokens` (reads your old `tokens/<alias>/token.json` and encrypts them) — or just re-auth each account.
107
+ 4. Writes are now **deny-by-default** — set `GOOGLE_PROFILE=safe-writes` (or `full-writes`) to keep writing. (`GOOGLE_ALLOW_ADMIN_WRITES` is gone — replaced by write-control profiles.)
447
108
 
448
- **Token refresh errors** — Delete the token file at `tokens/<alias>/token.json` and re-authenticate.
109
+ ## Security
449
110
 
450
- **Tasks / Meet tools return 403 after upgrading from v3** You haven't re-authed since the base scope set grew. Run `npm run auth -- <alias>` for every account.
111
+ Your OAuth, your machine. Refresh tokens are **AES-256-GCM encrypted at rest** (decryptable only with your `MASTER_KEY`), writes are deny-by-default, and the server has **no telemetry** — it connects only to Google's APIs. Found a vulnerability? Report it privately see [SECURITY.md](./SECURITY.md), never a public issue.
451
112
 
452
- **`forms_*` or `chat_*` tools missing from the tool list** — They're only registered when `GOOGLE_OPTIONAL_SCOPES` includes their bundle key. Set `GOOGLE_OPTIONAL_SCOPES=forms,chat` (or one of them) in `.env`, rebuild, and re-auth the accounts that need them.
113
+ ## Roadmap
453
114
 
454
- **Admin tools missing or returning 403** Admin tools only register if `GOOGLE_ADMIN_ACCOUNTS` is set. The listed accounts must be Workspace super-admins, and you must re-auth them after adding the env var so the new scopes are granted. `admin_users_update` additionally requires `GOOGLE_ALLOW_ADMIN_WRITES=true`.
115
+ Maintainer-led. Direction is tracked publicly as **[GitHub Milestones](https://github.com/bakissation/mcp-google-multi/milestones)** (discover-first tooling exhaustive API coverage service accounts + hosting in v6). Not accepting unsolicited feature PRs; bug reports are welcome.
455
116
 
456
117
  ## Contributing
457
118
 
458
- Contributions are welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md) first. In short: **open pull requests against the `dev` branch** changes are promoted `dev → staging → main`, and `main` is release-only. Run `typecheck`/`lint`/`test`/`build` before submitting, and use Conventional Commits.
459
-
460
- By participating you agree to the [Code of Conduct](CODE_OF_CONDUCT.md). For security issues, **do not open a public issue** — see [SECURITY.md](SECURITY.md).
119
+ See [CONTRIBUTING.md](./CONTRIBUTING.md) and the [Code of Conduct](./CODE_OF_CONDUCT.md). Security issues go to [SECURITY.md](./SECURITY.md), never a public issue.
461
120
 
462
- ## Author
121
+ ## Credits
463
122
 
464
- **Abdelbaki Berkati** — [berkati.xyz](https://berkati.xyz) · [@bakissation](https://github.com/bakissation)
123
+ Built and maintained by **Abdelbaki Berkati** — [berkati.xyz](https://berkati.xyz) · [@bakissation](https://github.com/bakissation). [Read the case study →](https://berkati.xyz/case-studies/mcp-google-multi/)
465
124
 
466
- [Read the case study ](https://berkati.xyz/case-studies/mcp-google-multi/)
125
+ Development is **funded by [IdeaCrafters](https://ideacrafters.com)** ([@IdeaCraftersHQ](https://github.com/IdeaCraftersHQ)) — the studio that pays for this OSS to exist.
467
126
 
468
127
  ## License
469
128
 
470
- [MIT](LICENSE)
129
+ [MIT](./LICENSE)
package/dist/accounts.js CHANGED
@@ -1,11 +1,14 @@
1
1
  import dotenv from 'dotenv';
2
2
  import { fileURLToPath } from 'node:url';
3
3
  import path from 'node:path';
4
+ import { homedir } from 'node:os';
4
5
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
6
+ dotenv.config();
5
7
  dotenv.config({ path: path.resolve(__dirname, '..', '.env') });
8
+ const defaultTokenDir = path.join(process.env.XDG_CONFIG_HOME || path.join(homedir(), '.config'), 'mcp-google-multi', 'tokens');
6
9
  const tokenDir = process.env.TOKEN_STORE_PATH
7
10
  ? path.resolve(process.env.TOKEN_STORE_PATH)
8
- : path.resolve(__dirname, '..', 'tokens');
11
+ : defaultTokenDir;
9
12
  /**
10
13
  * Parse accounts from the GOOGLE_ACCOUNTS env var.
11
14
  * Format: "alias1:email1,alias2:email2,..."
package/dist/auth.d.ts CHANGED
@@ -10,6 +10,4 @@ export declare function getAdminAccounts(): string[];
10
10
  * Resolves env flags: GOOGLE_OPTIONAL_SCOPES (global) and GOOGLE_ADMIN_ACCOUNTS (per-account allowlist).
11
11
  */
12
12
  export declare function resolveScopesForAccount(alias: string): string[];
13
- /** True if admin writes are explicitly enabled. Default refuses to prevent accidents on small orgs. */
14
- export declare function adminWritesEnabled(): boolean;
15
13
  export declare function runAuthFlow(args: string[]): Promise<void>;
package/dist/auth.js CHANGED
@@ -36,13 +36,6 @@ export const OPTIONAL_SCOPE_BUNDLES = {
36
36
  'https://www.googleapis.com/auth/chat.messages',
37
37
  'https://www.googleapis.com/auth/chat.messages.create',
38
38
  ],
39
- // Alert Center's apps.alerts scope is NOT grantable through the interactive
40
- // user-consent flow this server uses — it requires a service account with
41
- // domain-wide delegation. It lives in its own opt-in bundle so it never
42
- // blocks the working Admin SDK admin scopes. See README "Alert Center".
43
- alertcenter: [
44
- 'https://www.googleapis.com/auth/apps.alerts',
45
- ],
46
39
  };
47
40
  export const ADMIN_SCOPES = [
48
41
  'https://www.googleapis.com/auth/admin.reports.audit.readonly',
@@ -79,10 +72,6 @@ export function resolveScopesForAccount(alias) {
79
72
  }
80
73
  return Array.from(new Set(scopes));
81
74
  }
82
- /** True if admin writes are explicitly enabled. Default refuses to prevent accidents on small orgs. */
83
- export function adminWritesEnabled() {
84
- return process.env.GOOGLE_ALLOW_ADMIN_WRITES === 'true';
85
- }
86
75
  export async function runAuthFlow(args) {
87
76
  const accountIdx = args.indexOf('--account');
88
77
  if (accountIdx === -1 || !args[accountIdx + 1]) {
package/dist/index.js CHANGED
@@ -16,7 +16,7 @@ import { registerTasksTools } from './tools/tasks.js';
16
16
  import { registerMeetTools } from './tools/meet.js';
17
17
  import { registerFormsTools } from './tools/forms.js';
18
18
  import { registerChatTools } from './tools/chat.js';
19
- import { registerAdminTools, registerAlertCenterTools } from './tools/admin.js';
19
+ import { registerAdminTools } from './tools/admin.js';
20
20
  import { getOptionalBundles, getAdminAccounts } from './auth.js';
21
21
  import { ToolRegistry } from './registry.js';
22
22
  import { resolvePolicy, isAllowed, describePolicy } from './write-control.js';
@@ -38,8 +38,6 @@ function buildRegistry(server, policy) {
38
38
  registerFormsTools(registry);
39
39
  if (optional.has('chat'))
40
40
  registerChatTools(registry);
41
- if (optional.has('alertcenter'))
42
- registerAlertCenterTools(registry);
43
41
  if (getAdminAccounts().length > 0)
44
42
  registerAdminTools(registry);
45
43
  return registry;
@@ -3,15 +3,6 @@ import type { ToolRegistry } from '../registry.js';
3
3
  * Admin SDK tools require Workspace super-admin (or delegated admin) privileges on the target account.
4
4
  * Personal `@gmail.com` accounts will 403 on every endpoint.
5
5
  *
6
- * Writes are gated behind GOOGLE_ALLOW_ADMIN_WRITES=true to prevent accidents on small orgs
7
- * (a stray users.update on a 3-person Workspace is a bad day).
6
+ * Writes (e.g. admin_users_update) are gated by write-control like any CUD tool.
8
7
  */
9
8
  export declare function registerAdminTools(server: ToolRegistry): void;
10
- /**
11
- * Alert Center tools. Gated behind the `alertcenter` optional bundle, NOT the
12
- * admin bundle: the apps.alerts scope cannot be granted via this server's
13
- * interactive user-consent OAuth flow — it requires a service account with
14
- * domain-wide delegation. Registered separately so a missing/ungrantable
15
- * apps.alerts scope never blocks the working Admin SDK admin tools.
16
- */
17
- export declare function registerAlertCenterTools(server: ToolRegistry): void;
@@ -4,14 +4,12 @@ import { google } from 'googleapis';
4
4
  import { ACCOUNTS } from '../accounts.js';
5
5
  import { getClient } from '../client.js';
6
6
  import { handleGoogleApiError } from './_errors.js';
7
- import { adminWritesEnabled } from '../auth.js';
8
7
  const accountEnum = z.enum(ACCOUNTS);
9
8
  /**
10
9
  * Admin SDK tools require Workspace super-admin (or delegated admin) privileges on the target account.
11
10
  * Personal `@gmail.com` accounts will 403 on every endpoint.
12
11
  *
13
- * Writes are gated behind GOOGLE_ALLOW_ADMIN_WRITES=true to prevent accidents on small orgs
14
- * (a stray users.update on a 3-person Workspace is a bad day).
12
+ * Writes (e.g. admin_users_update) are gated by write-control like any CUD tool.
15
13
  */
16
14
  export function registerAdminTools(server) {
17
15
  // ─── Reports / audit log ───────────────────────────────────────────────
@@ -125,7 +123,7 @@ export function registerAdminTools(server) {
125
123
  }
126
124
  });
127
125
  server.registerTool('admin_users_update', {
128
- description: 'Update a Workspace user (PATCH semantics). GATED: requires GOOGLE_ALLOW_ADMIN_WRITES=true to prevent accidental destructive ops on small orgs.',
126
+ description: 'Update a Workspace user (PATCH semantics). Gated by write-control (a CUD tool).',
129
127
  inputSchema: {
130
128
  account: accountEnum.describe('Google account alias (must be a Workspace admin)'),
131
129
  userKey: z.string().describe('User email or ID'),
@@ -138,18 +136,6 @@ export function registerAdminTools(server) {
138
136
  },
139
137
  }, async ({ account, userKey, givenName, familyName, suspended, password, changePasswordAtNextLogin, orgUnitPath }) => {
140
138
  try {
141
- if (!adminWritesEnabled()) {
142
- return {
143
- content: [{
144
- type: 'text',
145
- text: JSON.stringify({
146
- error: 'admin_writes_disabled',
147
- hint: 'Admin write operations are disabled by default. Set GOOGLE_ALLOW_ADMIN_WRITES=true in .env and restart to enable.',
148
- }),
149
- }],
150
- isError: true,
151
- };
152
- }
153
139
  const auth = await getClient(account);
154
140
  const directory = google.admin({ version: 'directory_v1', auth });
155
141
  const requestBody = {};
@@ -242,67 +228,6 @@ export function registerAdminTools(server) {
242
228
  }
243
229
  });
244
230
  }
245
- /**
246
- * Alert Center tools. Gated behind the `alertcenter` optional bundle, NOT the
247
- * admin bundle: the apps.alerts scope cannot be granted via this server's
248
- * interactive user-consent OAuth flow — it requires a service account with
249
- * domain-wide delegation. Registered separately so a missing/ungrantable
250
- * apps.alerts scope never blocks the working Admin SDK admin tools.
251
- */
252
- export function registerAlertCenterTools(server) {
253
- server.registerTool('alertcenter_alerts_list', {
254
- description: 'List Workspace security alerts (suspicious login, phishing, leaked password, Drive exfil, etc.). Requires the alertcenter bundle + service-account domain-wide delegation.',
255
- inputSchema: {
256
- account: accountEnum.describe('Google account alias (must be a Workspace admin)'),
257
- pageSize: z.number().min(1).max(1000).optional(),
258
- pageToken: z.string().optional(),
259
- filter: z.string().optional().describe('Filter expression, e.g. "type = \\"Suspicious login\\""'),
260
- orderBy: z.string().optional(),
261
- customerId: z.string().optional(),
262
- },
263
- }, async ({ account, pageSize, pageToken, filter, orderBy, customerId }) => {
264
- try {
265
- const auth = await getClient(account);
266
- const alertcenter = google.alertcenter({ version: 'v1beta1', auth });
267
- const res = await alertcenter.alerts.list({
268
- pageSize: pageSize ?? 50,
269
- pageToken,
270
- filter,
271
- orderBy,
272
- customerId,
273
- });
274
- return {
275
- content: [{ type: 'text', text: JSON.stringify(res.data, null, 2) }],
276
- };
277
- }
278
- catch (error) {
279
- return handleAlertCenterError(error, account);
280
- }
281
- });
282
- server.registerTool('alertcenter_alert_get', {
283
- description: 'Get a single alert by ID. Requires the alertcenter bundle + service-account domain-wide delegation.',
284
- inputSchema: {
285
- account: accountEnum.describe('Google account alias (must be a Workspace admin)'),
286
- alertId: z.string().describe('Alert ID'),
287
- customerId: z.string().optional(),
288
- },
289
- }, async ({ account, alertId, customerId }) => {
290
- try {
291
- const auth = await getClient(account);
292
- const alertcenter = google.alertcenter({ version: 'v1beta1', auth });
293
- const res = await alertcenter.alerts.get({ alertId, customerId });
294
- return {
295
- content: [{ type: 'text', text: JSON.stringify(res.data, null, 2) }],
296
- };
297
- }
298
- catch (error) {
299
- return handleAlertCenterError(error, account);
300
- }
301
- });
302
- }
303
231
  function handleAdminError(error, account) {
304
232
  return handleGoogleApiError(error, account, "Admin tools require Workspace super-admin privileges AND the account must be listed in GOOGLE_ADMIN_ACCOUNTS (then re-authenticated). Personal Gmail accounts cannot use these endpoints.");
305
233
  }
306
- function handleAlertCenterError(error, account) {
307
- return handleGoogleApiError(error, account, "Alert Center (apps.alerts) is not grantable via this server's user-consent OAuth flow — it requires a service account with domain-wide delegation. Enabling GOOGLE_OPTIONAL_SCOPES=alertcenter only declares the scope; the API will reject user-OAuth tokens. See README \"Alert Center\".");
308
- }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mcp-google-multi",
3
- "version": "4.5.0-alpha.3",
4
- "description": "MCP server for Gmail, Google Drive, Calendar, Sheets, Docs, and Contacts with multi-account support",
3
+ "version": "5.0.0-beta.1",
4
+ "description": "Local MCP server for Google Workspace (Gmail, Drive, Calendar, Sheets, Docs, Contacts, Tasks, Meet, Search Console, +Forms/Chat/Admin) across multiple accounts — OAuth-only, encrypted token storage, deny-by-default writes.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -36,6 +36,12 @@
36
36
  "google-sheets",
37
37
  "google-docs",
38
38
  "google-contacts",
39
+ "google-workspace",
40
+ "google-tasks",
41
+ "google-meet",
42
+ "search-console",
43
+ "oauth",
44
+ "mcp-server",
39
45
  "claude",
40
46
  "claude-code"
41
47
  ],