mcp-google-multi 4.2.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Abdelbaki Berkati
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,439 @@
1
+ # mcp-google-multi
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.
4
+
5
+ ## Features
6
+
7
+ - **Multi-account** — manage any number of Google accounts from a single server
8
+ - **175 tools** across 13 Google services
9
+ - **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
10
+ - **Config-driven accounts** — defined in `.env`, no code changes needed
11
+ - **Auto-refresh** — OAuth tokens refresh transparently and persist to disk
12
+ - **Stdio transport** — runs as a local subprocess, no hosting needed
13
+
14
+ ## Tools
15
+
16
+ ### Gmail (21 tools)
17
+
18
+ | Tool | Description |
19
+ |------|-------------|
20
+ | `gmail_search` | Search messages with Gmail query syntax |
21
+ | `gmail_read` | Read a full message by ID |
22
+ | `gmail_read_thread` | Read all messages in a thread |
23
+ | `gmail_send` | Send an email |
24
+ | `gmail_download_attachment` | Download an email attachment to local disk |
25
+ | `gmail_create_draft` | Create a draft |
26
+ | `gmail_modify_labels` | Add/remove labels on a message (star, archive, mark read, etc.) |
27
+ | `gmail_trash` | Move a message to Trash (recoverable) |
28
+ | `gmail_delete` | Permanently delete a message (irreversible) |
29
+ | `gmail_batch_modify` | Bulk add/remove labels across up to 1000 messages |
30
+ | `gmail_batch_delete` | Permanently delete multiple messages (irreversible) |
31
+ | `gmail_list_drafts` | List all drafts |
32
+ | `gmail_get_draft` | Read a specific draft |
33
+ | `gmail_send_draft` | Send an existing draft |
34
+ | `gmail_list_labels` | List all labels (system + custom) |
35
+ | `gmail_create_label` | Create a custom label |
36
+ | `gmail_delete_label` | Delete a label |
37
+ | `gmail_get_profile` | Get account email, message count, history ID |
38
+ | `gmail_list_history` | Get mailbox changes since a history ID |
39
+ | `gmail_get_vacation` | Read vacation responder settings |
40
+ | `gmail_set_vacation` | Enable/disable vacation responder |
41
+
42
+ ### Google Drive (35 tools)
43
+
44
+ | Tool | Description |
45
+ |------|-------------|
46
+ | `drive_search` | Search files with Drive query syntax (shared drives included) |
47
+ | `drive_read` | Read file content (exports Workspace docs as text) |
48
+ | `drive_list` | List files in a folder or root |
49
+ | `drive_upload` | Upload a local file to Drive (optional `convertTo` imports it as a native Google Doc/Sheet/Slides) |
50
+ | `drive_download` | Download a binary file to local disk |
51
+ | `drive_export` | Export Google Docs/Sheets/Slides to PDF, DOCX, XLSX, Markdown, etc. |
52
+ | `drive_create_folder` | Create a new folder |
53
+ | `drive_update` | Rename, move, or replace file content |
54
+ | `drive_delete` | Permanently delete a file (irreversible) |
55
+ | `drive_trash` | Move a file to trash (recoverable) |
56
+ | `drive_untrash` | Restore a trashed file |
57
+ | `drive_empty_trash` | Permanently delete every file in trash |
58
+ | `drive_copy` | Duplicate a file |
59
+ | `drive_move` | Move a file between folders |
60
+ | `drive_share` | Share with user/group/domain/anyone (with `transferOwnership` + `expirationTime`) |
61
+ | `drive_list_permissions` | List who has access to a file |
62
+ | `drive_permission_update` | Change a permission's role / expiration in place |
63
+ | `drive_remove_permission` | Revoke access |
64
+ | `drive_comment_create` / `_list` / `_get` / `_update` / `_delete` | Full comment CRUD with anchor support |
65
+ | `drive_reply_create` / `_list` / `_update` / `_delete` | Reply CRUD; reply.create accepts `action: resolve | reopen` |
66
+ | `drive_revision_list` / `_update` / `_delete` | Version history; `keepForever` pins against the 200-version cap |
67
+ | `drive_access_proposal_list` / `_resolve` | Triage "Request access" submissions |
68
+ | `drive_shared_drives_list` / `drive_shared_drive_get` | Shared drive discovery |
69
+ | `drive_get_about` | Storage quota and account info |
70
+
71
+ ### Google Calendar (11 tools)
72
+
73
+ | Tool | Description |
74
+ |------|-------------|
75
+ | `calendar_list_calendars` | List all calendars |
76
+ | `calendar_list_events` | List/search events with time range |
77
+ | `calendar_get_event` | Get a single event by ID |
78
+ | `calendar_create_event` | Create an event |
79
+ | `calendar_update_event` | Update an event |
80
+ | `calendar_delete_event` | Delete an event |
81
+ | `calendar_quick_add` | Create event from natural language (e.g. "Lunch Thursday 1pm") |
82
+ | `calendar_move_event` | Move an event between calendars |
83
+ | `calendar_list_instances` | List occurrences of a recurring event |
84
+ | `calendar_get_freebusy` | Check free/busy times for calendars |
85
+ | `calendar_create_calendar` | Create a new calendar |
86
+
87
+ ### Google Sheets (29 tools)
88
+
89
+ | Tool | Description |
90
+ |------|-------------|
91
+ | `sheets_create` | Create a new spreadsheet |
92
+ | `sheets_get` | Get spreadsheet metadata |
93
+ | `sheets_read_range` / `sheets_batch_read` | Read values |
94
+ | `sheets_write_range` / `sheets_batch_write` | Write values |
95
+ | `sheets_append_rows` | Append after existing data |
96
+ | `sheets_clear_range` / `sheets_batch_clear` | Clear values (preserves formatting) |
97
+ | `sheets_add_sheet` / `sheets_delete_sheet` / `sheets_duplicate_sheet` | Tab management |
98
+ | `sheets_update_sheet_properties` | Rename, freeze, color, hide, resize |
99
+ | `sheets_format_cells` | Colors, fonts, alignment, number formats |
100
+ | `sheets_update_borders` | Cell borders with style + color |
101
+ | `sheets_merge_cells` / `sheets_unmerge_cells` | Cell merging |
102
+ | `sheets_add_conditional_format_rule` | Boolean and gradient conditional formatting |
103
+ | `sheets_sort_range` | Multi-column sort |
104
+ | `sheets_set_basic_filter` / `sheets_clear_basic_filter` | Filter rows |
105
+ | `sheets_find_replace` | Regex-capable, scope-aware find/replace |
106
+ | `sheets_auto_resize_dimensions` | Fit rows/columns to content |
107
+ | `sheets_set_data_validation` | Dropdowns, checkboxes, custom rules |
108
+ | `sheets_add_named_range` / `sheets_delete_named_range` | Named ranges |
109
+ | `sheets_insert_dimension` / `sheets_delete_dimension` | Insert/delete rows or columns |
110
+ | `sheets_batch_update` | Generic batchUpdate escape hatch (any Request type) |
111
+
112
+ ### Google Docs (27 tools)
113
+
114
+ | Tool | Description |
115
+ |------|-------------|
116
+ | `docs_create` | Create a new document |
117
+ | `docs_get` | Metadata + optional tab content / suggestionsViewMode |
118
+ | `docs_read` | Read document content as plain text |
119
+ | `docs_insert_text` | Insert text at position or end |
120
+ | `docs_replace_text` | Find and replace |
121
+ | `docs_delete_range` | Delete a character range |
122
+ | `docs_update_style` | Inline text formatting (bold, italic, font, size) |
123
+ | `docs_update_paragraph_style` | Alignment, heading, indents, spacing |
124
+ | `docs_update_document_style` | Page size, margins, header/footer behavior |
125
+ | `docs_insert_table` | Insert a table |
126
+ | `docs_modify_table` | insertRow / insertColumn / deleteRow / deleteColumn / mergeCells / unmergeCells |
127
+ | `docs_create_named_range` / `docs_delete_named_range` / `docs_replace_named_range_content` | Mail-merge primitive |
128
+ | `docs_create_paragraph_bullets` / `docs_delete_paragraph_bullets` | Bulleted/numbered lists |
129
+ | `docs_insert_inline_image` | PNG/JPEG/GIF image insertion |
130
+ | `docs_insert_page_break` / `docs_insert_section_break` | Layout breaks |
131
+ | `docs_create_header` / `docs_delete_header` / `docs_create_footer` / `docs_delete_footer` | Branded headers and footers |
132
+ | `docs_add_tab` / `docs_delete_tab` / `docs_update_tab_properties` | Tabs CRUD |
133
+ | `docs_batch_update` | Generic batchUpdate escape hatch (any Request type) |
134
+
135
+ ### Google Contacts (9 tools)
136
+
137
+ | Tool | Description |
138
+ |------|-------------|
139
+ | `contacts_search` | Search contacts |
140
+ | `contacts_get` | Get one contact |
141
+ | `contacts_list` | List contacts (paginated) |
142
+ | `contacts_create` | Create a contact |
143
+ | `contacts_update` | Update a contact |
144
+ | `contacts_delete` | Delete a contact |
145
+ | `contacts_groups_list` | List groups |
146
+ | `contacts_group_members` | List members of a group |
147
+ | `contacts_group_create` | Create a group |
148
+
149
+ ### Google Search Console (10 tools)
150
+
151
+ | Tool | Description |
152
+ |------|-------------|
153
+ | `searchconsole_sites_list` | List Search Console properties |
154
+ | `searchconsole_sites_get` | Get a property |
155
+ | `searchconsole_sites_add` | Add a property |
156
+ | `searchconsole_sites_delete` | Remove a property |
157
+ | `searchconsole_sitemaps_list` | List sitemaps |
158
+ | `searchconsole_sitemaps_get` | Sitemap status |
159
+ | `searchconsole_sitemaps_submit` | Submit a sitemap |
160
+ | `searchconsole_sitemaps_delete` | Delete a sitemap |
161
+ | `searchconsole_searchanalytics_query` | Query search analytics |
162
+ | `searchconsole_url_inspect` | Inspect a URL |
163
+
164
+ ### Google Tasks (12 tools) — _new in v4_
165
+
166
+ | Tool | Description |
167
+ |------|-------------|
168
+ | `tasks_lists_list` / `tasks_list_get` / `tasks_list_insert` / `tasks_list_update` / `tasks_list_delete` | Tasklist management |
169
+ | `tasks_list` | List tasks (filter by completion, due, updated time) |
170
+ | `tasks_get` / `tasks_insert` / `tasks_update` / `tasks_delete` | Task CRUD |
171
+ | `tasks_move` | Re-parent / re-position / move to another list |
172
+ | `tasks_clear` | Delete every completed task |
173
+
174
+ ### Google Meet (5 tools) — _new in v4_
175
+
176
+ | Tool | Description |
177
+ |------|-------------|
178
+ | `meet_conference_records_list` | List past Meet sessions |
179
+ | `meet_conference_record_get` | Get one record |
180
+ | `meet_recordings_list` | Recordings on a record |
181
+ | `meet_transcripts_list` | Transcripts on a record |
182
+ | `meet_transcript_entries_list` | Per-speaker transcript text |
183
+
184
+ ### Google Forms (4 tools, optional bundle) — _new in v4_
185
+
186
+ Enable with `GOOGLE_OPTIONAL_SCOPES=forms` in `.env`.
187
+
188
+ | Tool | Description |
189
+ |------|-------------|
190
+ | `forms_get` | Form definition |
191
+ | `forms_responses_list` | List responses with filter |
192
+ | `forms_response_get` | Single response |
193
+ | `forms_watches_list` | List Pub/Sub watches |
194
+
195
+ ### Google Chat (4 tools, optional bundle) — _new in v4_
196
+
197
+ Enable with `GOOGLE_OPTIONAL_SCOPES=chat` in `.env` (combine: `GOOGLE_OPTIONAL_SCOPES=forms,chat`).
198
+
199
+ | Tool | Description |
200
+ |------|-------------|
201
+ | `chat_spaces_list` | Spaces the user is in |
202
+ | `chat_spaces_get` | One space |
203
+ | `chat_messages_create` | Send text or Card v2 |
204
+ | `chat_messages_list` | List messages with filter / orderBy |
205
+
206
+ ### Google Workspace Admin (6 tools, admin bundle) — _new in v4_
207
+
208
+ 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`.
209
+
210
+ | Tool | Description |
211
+ |------|-------------|
212
+ | `reports_activities_list` | Workspace audit log (login, drive, gmail, admin, token, etc.) |
213
+ | `admin_users_list` / `admin_users_get` | User directory reads |
214
+ | `admin_users_update` | User edits (gated, see env flag above) |
215
+ | `admin_groups_list` / `admin_group_members_list` | Group + member reads |
216
+
217
+ Every tool accepts an `account` parameter matching one of your configured aliases.
218
+
219
+ ### Google Alert Center (2 tools, optional bundle) — _new in v4_
220
+
221
+ Enable with `GOOGLE_OPTIONAL_SCOPES=alertcenter` in `.env`.
222
+
223
+ | Tool | Description |
224
+ |------|-------------|
225
+ | `alertcenter_alerts_list` / `alertcenter_alert_get` | Security alerts (suspicious login, phishing, leaked password, Drive exfil) |
226
+
227
+ > **⚠ 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.
228
+
229
+ ## Prerequisites
230
+
231
+ - Node.js 18+
232
+ - A Google Cloud project with the relevant APIs enabled (see step 1 below)
233
+ - An OAuth 2.0 Client ID (Desktop app type)
234
+
235
+ ## Setup
236
+
237
+ ### 1. Google Cloud Credentials
238
+
239
+ 1. Go to [Google Cloud Console](https://console.cloud.google.com).
240
+ 2. Create or select a project.
241
+ 3. Enable the APIs you intend to use:
242
+ - **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).
243
+ - **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).
244
+ - **Admin bundle (only if you set `GOOGLE_ADMIN_ACCOUNTS`):** [Admin SDK API](https://console.cloud.google.com/apis/library/admin.googleapis.com).
245
+ 4. Go to **APIs & Services → Credentials → Create Credentials → OAuth 2.0 Client ID**.
246
+ 5. Application type: **Desktop app**.
247
+ 6. Add authorized redirect URI: `http://localhost:4242/oauth2callback`.
248
+ 7. Copy the **Client ID** and **Client Secret**.
249
+
250
+ ### 2. Install & Configure
251
+
252
+ ```bash
253
+ git clone https://github.com/bakissation/mcp-google-multi.git
254
+ cd mcp-google-multi
255
+ npm install
256
+ cp .env.example .env
257
+ ```
258
+
259
+ Edit `.env`:
260
+
261
+ ```env
262
+ GOOGLE_CLIENT_ID=your_client_id_here
263
+ GOOGLE_CLIENT_SECRET=your_client_secret_here
264
+
265
+ # Define your accounts as alias:email pairs (comma-separated)
266
+ GOOGLE_ACCOUNTS=work:you@company.com,personal:you@gmail.com
267
+
268
+ # Optional (v4): enable Forms, Chat, and/or Alert Center tool bundles
269
+ # (alertcenter additionally requires service-account domain-wide delegation)
270
+ # GOOGLE_OPTIONAL_SCOPES=forms,chat
271
+
272
+ # Optional (v4): grant Workspace admin scopes to specific accounts
273
+ # GOOGLE_ADMIN_ACCOUNTS=work
274
+
275
+ # Optional (v4): unlock destructive admin writes (admin_users_update)
276
+ # GOOGLE_ALLOW_ADMIN_WRITES=true
277
+ ```
278
+
279
+ ### 3. Build
280
+
281
+ ```bash
282
+ npm run build
283
+ ```
284
+
285
+ ### 4. Authenticate Accounts
286
+
287
+ ```bash
288
+ npm run auth -- work # opens browser → log in with you@company.com
289
+ npm run auth -- personal # opens browser → log in with you@gmail.com
290
+ ```
291
+
292
+ Each saves a token to `tokens/<alias>/token.json`. Tokens auto-refresh — you should only need to do this once per account.
293
+
294
+ ### 5. Register in Claude Code
295
+
296
+ ```bash
297
+ claude mcp add google-multi -s user -- node /absolute/path/to/mcp-google-multi/dist/index.js
298
+ ```
299
+
300
+ Or add it manually to your Claude Code MCP config:
301
+
302
+ ```json
303
+ {
304
+ "mcpServers": {
305
+ "google-multi": {
306
+ "command": "node",
307
+ "args": ["/absolute/path/to/mcp-google-multi/dist/index.js"]
308
+ }
309
+ }
310
+ }
311
+ ```
312
+
313
+ ## Migrating from v3
314
+
315
+ 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.
316
+
317
+ 1. `git pull && npm install && npm run build`.
318
+ 2. Re-authenticate every account: `npm run auth -- <alias>` for each alias.
319
+ 3. If you want Forms or Chat tools: set `GOOGLE_OPTIONAL_SCOPES=forms,chat` in `.env` **before** re-authing.
320
+ 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.
321
+ 5. In Google Cloud Console, enable any new APIs you'll use (Tasks, Meet, Forms, Chat, Admin SDK, Alert Center).
322
+
323
+ ## Adding / Removing Accounts
324
+
325
+ Edit the `GOOGLE_ACCOUNTS` variable in `.env`, rebuild, and authenticate the new account:
326
+
327
+ ```bash
328
+ # Add a new account
329
+ # .env: GOOGLE_ACCOUNTS=work:you@company.com,personal:you@gmail.com,freelance:you@freelance.com
330
+
331
+ npm run build
332
+ npm run auth -- freelance
333
+ ```
334
+
335
+ No code changes required.
336
+
337
+ ## OAuth Scopes
338
+
339
+ ### Base (always granted)
340
+
341
+ | Scope | Access |
342
+ |-------|--------|
343
+ | `gmail.modify` | Read, label, trash, delete emails |
344
+ | `gmail.send` | Send emails |
345
+ | `drive` | Full Drive access (read, upload, share, comments, etc.) |
346
+ | `calendar` | Full calendar access |
347
+ | `spreadsheets` | Read/write Google Sheets |
348
+ | `documents` | Read/write Google Docs |
349
+ | `contacts` | Read/write Google Contacts |
350
+ | `webmasters` | Google Search Console |
351
+ | `tasks` | Read/write Google Tasks |
352
+ | `meetings.space.readonly` | Read past Meet conference records and transcripts |
353
+
354
+ ### Optional (per-`GOOGLE_OPTIONAL_SCOPES` env var)
355
+
356
+ | Bundle key | Scopes |
357
+ |------------|--------|
358
+ | `forms` | `forms.body`, `forms.responses.readonly` |
359
+ | `chat` | `chat.spaces`, `chat.messages`, `chat.messages.create` |
360
+ | `alertcenter` | `apps.alerts` — ⚠ requires service-account domain-wide delegation; not grantable via user OAuth (see Alert Center section) |
361
+
362
+ ### Admin (per-`GOOGLE_ADMIN_ACCOUNTS` env var)
363
+
364
+ Only granted to accounts explicitly listed. Personal Gmail accounts will 403 on these scopes — never list them.
365
+
366
+ | Scope | Access |
367
+ |-------|--------|
368
+ | `admin.reports.audit.readonly` | Workspace audit log |
369
+ | `admin.directory.user` | Read/write Workspace users (writes also gated by `GOOGLE_ALLOW_ADMIN_WRITES`) |
370
+ | `admin.directory.group.readonly` | Read groups |
371
+ | `admin.directory.group.member.readonly` | Read group members |
372
+
373
+ ## Project Structure
374
+
375
+ ```
376
+ mcp-google-multi/
377
+ ├── src/
378
+ │ ├── index.ts # Entry point: MCP server or auth CLI
379
+ │ ├── accounts.ts # Account config parser (reads from .env)
380
+ │ ├── auth.ts # Scope tier resolution + OAuth flow
381
+ │ ├── client.ts # OAuth2Client factory with auto-refresh
382
+ │ ├── types.ts # Shared TypeScript types
383
+ │ └── tools/
384
+ │ ├── _errors.ts # Shared googleapis error → MCP response mapper
385
+ │ ├── gmail.ts # Gmail (21)
386
+ │ ├── drive.ts # Drive (35)
387
+ │ ├── calendar.ts # Calendar (11)
388
+ │ ├── sheets.ts # Sheets (29)
389
+ │ ├── docs.ts # Docs (27)
390
+ │ ├── contacts.ts # Contacts (9)
391
+ │ ├── searchconsole.ts # Search Console (10)
392
+ │ ├── tasks.ts # Tasks (12) — v4
393
+ │ ├── meet.ts # Meet (5) — v4
394
+ │ ├── forms.ts # Forms (4, optional) — v4
395
+ │ ├── chat.ts # Chat (4, optional) — v4
396
+ │ └── admin.ts # Admin SDK (6, admin) + Alert Center (2, alertcenter bundle) — v4
397
+ ├── tests/ # vitest unit tests (gmail-mime, path-safety, field-mask helpers)
398
+ ├── tokens/ # OAuth tokens per account (gitignored)
399
+ ├── dist/ # Compiled output (gitignored)
400
+ ├── .env # Your credentials (gitignored)
401
+ ├── .env.example # Template for .env
402
+ ├── CHANGELOG.md
403
+ ├── CLAUDE.md # Conventions for AI assistants working on this codebase
404
+ ├── package.json
405
+ ├── tsconfig.json
406
+ └── LICENSE
407
+ ```
408
+
409
+ ## Troubleshooting
410
+
411
+ **"GOOGLE_ACCOUNTS is not set"** — Make sure your `.env` file exists in the project root and has the `GOOGLE_ACCOUNTS` variable set.
412
+
413
+ **"No token file found for account X"** — Run `npm run auth -- <alias>` to authenticate that account.
414
+
415
+ **"Port 4242 is already in use"** — Another process is using port 4242. Close it and retry the auth flow.
416
+
417
+ **Token refresh errors** — Delete the token file at `tokens/<alias>/token.json` and re-authenticate.
418
+
419
+ **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.
420
+
421
+ **`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.
422
+
423
+ **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`.
424
+
425
+ ## Contributing
426
+
427
+ 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.
428
+
429
+ 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).
430
+
431
+ ## Author
432
+
433
+ **Abdelbaki Berkati** — [berkati.xyz](https://berkati.xyz) · [@bakissation](https://github.com/bakissation)
434
+
435
+ [Read the case study →](https://berkati.xyz/case-studies/mcp-google-multi/)
436
+
437
+ ## License
438
+
439
+ [MIT](LICENSE)
@@ -0,0 +1,10 @@
1
+ export interface AccountConfig {
2
+ email: string;
3
+ tokenPath: string;
4
+ }
5
+ /** Tuple of account aliases (at least one) — usable with z.enum() */
6
+ export declare const ACCOUNTS: [string, ...string[]];
7
+ /** Map of alias → { email, tokenPath } */
8
+ export declare const ACCOUNT_CONFIG: Record<string, AccountConfig>;
9
+ /** Valid account alias (string union isn't static, so tools use z.enum(ACCOUNTS)) */
10
+ export type Account = string;
@@ -0,0 +1,53 @@
1
+ import dotenv from 'dotenv';
2
+ import { fileURLToPath } from 'node:url';
3
+ import path from 'node:path';
4
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
5
+ dotenv.config({ path: path.resolve(__dirname, '..', '.env') });
6
+ const tokenDir = path.resolve(__dirname, '..', 'tokens');
7
+ /**
8
+ * Parse accounts from the GOOGLE_ACCOUNTS env var.
9
+ * Format: "alias1:email1,alias2:email2,..."
10
+ * Example: "work:me@company.com,personal:me@gmail.com"
11
+ */
12
+ function parseAccounts() {
13
+ const raw = process.env.GOOGLE_ACCOUNTS;
14
+ if (!raw || raw.trim() === '') {
15
+ throw new Error('GOOGLE_ACCOUNTS is not set. Define it in .env like:\n' +
16
+ 'GOOGLE_ACCOUNTS=work:user@company.com,personal:user@gmail.com');
17
+ }
18
+ const configs = {};
19
+ const aliases = [];
20
+ for (const entry of raw.split(',')) {
21
+ const trimmed = entry.trim();
22
+ if (!trimmed)
23
+ continue;
24
+ const colonIdx = trimmed.indexOf(':');
25
+ if (colonIdx === -1) {
26
+ throw new Error(`Invalid account entry "${trimmed}". Expected format: alias:email`);
27
+ }
28
+ const alias = trimmed.slice(0, colonIdx).trim();
29
+ const email = trimmed.slice(colonIdx + 1).trim();
30
+ if (!alias || !email) {
31
+ throw new Error(`Invalid account entry "${trimmed}". Both alias and email are required.`);
32
+ }
33
+ // Restrict alias to a safe charset so it can't escape `tokenDir` via path traversal
34
+ // (e.g. "../../etc/passwd:foo@bar.com" in .env).
35
+ if (!/^[a-zA-Z0-9_-]+$/.test(alias)) {
36
+ throw new Error(`Invalid alias "${alias}". Allowed characters: letters, digits, underscore, hyphen.`);
37
+ }
38
+ aliases.push(alias);
39
+ configs[alias] = {
40
+ email,
41
+ tokenPath: path.join(tokenDir, alias, 'token.json'),
42
+ };
43
+ }
44
+ if (aliases.length === 0) {
45
+ throw new Error('GOOGLE_ACCOUNTS must define at least one account.');
46
+ }
47
+ return { aliases: aliases, configs };
48
+ }
49
+ const parsed = parseAccounts();
50
+ /** Tuple of account aliases (at least one) — usable with z.enum() */
51
+ export const ACCOUNTS = parsed.aliases;
52
+ /** Map of alias → { email, tokenPath } */
53
+ export const ACCOUNT_CONFIG = parsed.configs;
package/dist/auth.d.ts ADDED
@@ -0,0 +1,15 @@
1
+ export declare const BASE_SCOPES: string[];
2
+ export declare const OPTIONAL_SCOPE_BUNDLES: Record<string, string[]>;
3
+ export declare const ADMIN_SCOPES: string[];
4
+ /** Bundle keys enabled via GOOGLE_OPTIONAL_SCOPES (e.g. ["forms","chat"]). */
5
+ export declare function getOptionalBundles(): string[];
6
+ /** Account aliases granted ADMIN_SCOPES via GOOGLE_ADMIN_ACCOUNTS. */
7
+ export declare function getAdminAccounts(): string[];
8
+ /**
9
+ * Compose the scope list for a single account at consent time.
10
+ * Resolves env flags: GOOGLE_OPTIONAL_SCOPES (global) and GOOGLE_ADMIN_ACCOUNTS (per-account allowlist).
11
+ */
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
+ export declare function runAuthFlow(args: string[]): Promise<void>;