mcp-google-gdrive 0.1.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.
Files changed (5) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +105 -0
  3. package/SPEC.md +917 -0
  4. package/package.json +35 -0
  5. package/src/index.js +487 -0
package/SPEC.md ADDED
@@ -0,0 +1,917 @@
1
+ # mcp-google-gdrive — Development Specification
2
+
3
+ ## 1. Project Overview
4
+
5
+ MCP server providing Claude Code with full read/write access to a personal Google Drive account. Implements Google Drive API v3 operations as MCP tools with automatic Google Workspace format conversion.
6
+
7
+ ### Key Facts
8
+
9
+ | Item | Value |
10
+ |------|-------|
11
+ | Package name | mcp-google-gdrive |
12
+ | Version | 0.1.0 |
13
+ | Runtime | Node.js 18+ (ESM), verified 22.22.0 in WSL |
14
+ | MCP SDK | @modelcontextprotocol/sdk ^1.26.0 |
15
+ | Validation | zod ^3.25.0 |
16
+ | Auth | Google OAuth2 (desktop app flow) |
17
+ | API | Google Drive API v3 |
18
+ | Extra deps | googleapis ^144.0.0, turndown ^7.2.0, marked ^15.0.0 |
19
+ | Location | WSL ~/projects/mcp-servers/mcp-google-gdrive/ |
20
+ | Entry point | src/index.js |
21
+ | Transport | stdio (JSON-RPC) |
22
+ | License | MIT |
23
+
24
+ ---
25
+
26
+ ## 2. Prerequisites and Setup
27
+
28
+ ### 2.1 System Requirements
29
+
30
+ - Node.js 18+ (verified: v22.22.0 in WSL Fedora 43)
31
+ - npm 10+ (verified: 10.9.4)
32
+ - Google account with Drive access
33
+ - Web browser accessible from WSL (for one-time OAuth2 consent)
34
+
35
+ ### 2.2 Google Cloud Console Setup
36
+
37
+ 1. Go to https://console.cloud.google.com/
38
+ 2. Create a new project (e.g., "PAI MCP Servers")
39
+ 3. Enable the Google Drive API:
40
+ - APIs & Services → Library → search "Google Drive API" → Enable
41
+ 4. Configure OAuth consent screen:
42
+ - APIs & Services → OAuth consent screen
43
+ - User type: External (or Internal if using Google Workspace)
44
+ - App name: "PAI Google Drive MCP"
45
+ - Scopes: Add `https://www.googleapis.com/auth/drive` (full Drive access)
46
+ - Test users: Add your Google email
47
+ 5. Create OAuth2 credentials:
48
+ - APIs & Services → Credentials → Create Credentials → OAuth client ID
49
+ - Application type: Desktop app
50
+ - Name: "mcp-google-gdrive"
51
+ - Download the JSON file → save as `credentials.json`
52
+
53
+ ### 2.3 Required OAuth2 Scopes
54
+
55
+ | Scope | Purpose |
56
+ |-------|---------|
57
+ | `https://www.googleapis.com/auth/drive` | Full read/write access to all Drive files |
58
+ | `https://www.googleapis.com/auth/drive.file` | Access to files created/opened by this app only (alternative, more restrictive) |
59
+
60
+ Recommendation: Use `drive` scope for full access. This is a personal tool — scope restriction adds friction without meaningful security benefit for a single-user local tool.
61
+
62
+ ### 2.4 Token Storage
63
+
64
+ - Credentials file: `~/.config/mcp-google-gdrive/credentials.json`
65
+ - Token file: `~/.config/mcp-google-gdrive/token.json`
66
+ - Token contains: access_token, refresh_token, expiry_date
67
+ - Refresh tokens do not expire unless revoked or unused for 6 months
68
+ - Access tokens expire after 1 hour; auto-refresh via googleapis client
69
+
70
+ ### 2.5 First-Run OAuth2 Flow (WSL → Windows Browser)
71
+
72
+ Since WSL doesn't have a native browser, the first-run consent flow requires:
73
+
74
+ 1. Server starts, detects no token.json
75
+ 2. Generates authorization URL and prints it to stderr
76
+ 3. User copies URL to Windows browser, completes consent
77
+ 4. Google redirects to `http://localhost:PORT/callback` with auth code
78
+ 5. Server exchanges auth code for tokens, saves to token.json
79
+ 6. Subsequent runs use refresh token automatically
80
+
81
+ Alternative approach if localhost redirect doesn't work in WSL:
82
+ - Use `redirect_uri: urn:ietf:wg:oauth:2.0:oob` (manual copy-paste of auth code)
83
+ - Server prompts "Enter the authorization code:" on stderr
84
+ - User pastes code from browser
85
+
86
+ ### 2.6 Project Scaffolding
87
+
88
+ ```
89
+ mcp-google-gdrive/
90
+ ├── .gitignore
91
+ ├── LICENSE (MIT)
92
+ ├── README.md
93
+ ├── package.json
94
+ └── src/
95
+ └── index.js
96
+ ```
97
+
98
+ ### 2.7 package.json
99
+
100
+ ```json
101
+ {
102
+ "name": "mcp-google-gdrive",
103
+ "version": "0.1.0",
104
+ "description": "MCP server for Google Drive API with full file management and Workspace format conversion",
105
+ "type": "module",
106
+ "bin": {
107
+ "mcp-google-gdrive": "./src/index.js"
108
+ },
109
+ "main": "./src/index.js",
110
+ "scripts": {
111
+ "start": "node src/index.js",
112
+ "auth": "node src/index.js --auth"
113
+ },
114
+ "keywords": [
115
+ "mcp",
116
+ "google-drive",
117
+ "google-workspace",
118
+ "file-management"
119
+ ],
120
+ "license": "MIT",
121
+ "repository": {
122
+ "type": "git",
123
+ "url": "git+https://github.com/sleepytimeshon/mcp-google-gdrive.git"
124
+ },
125
+ "homepage": "https://github.com/sleepytimeshon/mcp-google-gdrive#readme",
126
+ "bugs": {
127
+ "url": "https://github.com/sleepytimeshon/mcp-google-gdrive/issues"
128
+ },
129
+ "dependencies": {
130
+ "@modelcontextprotocol/sdk": "^1.26.0",
131
+ "googleapis": "^144.0.0",
132
+ "marked": "^15.0.0",
133
+ "turndown": "^7.2.0",
134
+ "zod": "^3.25.0"
135
+ }
136
+ }
137
+ ```
138
+
139
+ ### 2.8 .gitignore
140
+
141
+ ```
142
+ node_modules/
143
+ credentials.json
144
+ token.json
145
+ ```
146
+
147
+ ---
148
+
149
+ ## 3. Architecture
150
+
151
+ See section 11 for full C4 model diagrams (Context, Container, Deployment) and security posture.
152
+
153
+ ### 3.1 Internal Components
154
+
155
+ | Component | Responsibility |
156
+ |-----------|---------------|
157
+ | MCP Protocol Handler | McpServer + StdioServerTransport, receives tool calls |
158
+ | Tool Registry | 14 registerTool calls with Zod schemas and annotations |
159
+ | Auth Manager | OAuth2 token load/refresh/save using googleapis auth client |
160
+ | Drive API Client | Thin wrapper around googleapis drive.files.* methods |
161
+ | Format Converter | HTML→Markdown (turndown), MD→HTML (marked), Sheets→CSV/JSON parsing |
162
+
163
+ ### 3.2 Data Flow
164
+
165
+ ```
166
+ Claude Code (Windows)
167
+ │ stdio (JSON-RPC)
168
+
169
+ MCP Server (WSL Node.js)
170
+
171
+ ├── Auth Manager → token.json (local file)
172
+
173
+ ├── Drive API Client → Google Drive API v3 (HTTPS)
174
+ │ │
175
+ │ └── Response (file content, metadata, etc.)
176
+
177
+ └── Format Converter (if Google Workspace type)
178
+
179
+ └── Converted content (Markdown, CSV, JSON, text)
180
+
181
+
182
+ MCP Response → Claude Code
183
+ ```
184
+
185
+ ---
186
+
187
+ ## 4. Tool Specifications
188
+
189
+ ### Naming Convention
190
+
191
+ Tools use snake_case prefixed with `gdrive_` to namespace and avoid collisions:
192
+
193
+ | Tool Name | Operation | Read/Write |
194
+ |-----------|-----------|------------|
195
+ | gdrive_list_files | List/search files | Read |
196
+ | gdrive_get_metadata | Get file metadata | Read |
197
+ | gdrive_read_file | Read file content | Read |
198
+ | gdrive_create_file | Create/upload file | Write |
199
+ | gdrive_update_file | Update file content | Write |
200
+ | gdrive_delete_file | Trash or delete file | Write (destructive) |
201
+ | gdrive_move_file | Move file between folders | Write |
202
+ | gdrive_copy_file | Copy a file | Write |
203
+ | gdrive_create_folder | Create a folder | Write |
204
+ | gdrive_export_doc | Export Google Doc | Read |
205
+ | gdrive_export_sheet | Export Google Sheet | Read |
206
+ | gdrive_export_slides | Export Google Slides | Read |
207
+ | gdrive_create_doc | Create Google Doc from Markdown | Write |
208
+ | gdrive_update_doc | Update Google Doc content | Write |
209
+
210
+ ### 4.1 gdrive_list_files
211
+
212
+ List files in Google Drive with optional search query and folder filtering.
213
+
214
+ | Field | Value |
215
+ |-------|-------|
216
+ | Drive API | files.list |
217
+ | Annotations | readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true |
218
+
219
+ Parameters:
220
+
221
+ | Param | Type | Required | Default | Description |
222
+ |-------|------|----------|---------|-------------|
223
+ | query | string | no | — | Drive search query (e.g., "name contains 'report'") |
224
+ | folderId | string | no | — | List files in this folder only |
225
+ | pageSize | number | no | 20 | Results per page (max 100) |
226
+ | pageToken | string | no | — | Token for next page of results |
227
+ | orderBy | string | no | "modifiedTime desc" | Sort order |
228
+
229
+ Response: JSON array of file objects with id, name, mimeType, modifiedTime, size, parents.
230
+
231
+ ### 4.2 gdrive_get_metadata
232
+
233
+ Get detailed metadata for a specific file.
234
+
235
+ | Field | Value |
236
+ |-------|-------|
237
+ | Drive API | files.get (fields parameter for metadata) |
238
+ | Annotations | readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true |
239
+
240
+ Parameters:
241
+
242
+ | Param | Type | Required | Default | Description |
243
+ |-------|------|----------|---------|-------------|
244
+ | fileId | string | yes | — | Google Drive file ID |
245
+
246
+ Response: Full file metadata — id, name, mimeType, size, createdTime, modifiedTime, parents, webViewLink, owners, shared, description.
247
+
248
+ ### 4.3 gdrive_read_file
249
+
250
+ Read file content with automatic format detection and conversion.
251
+
252
+ | Field | Value |
253
+ |-------|-------|
254
+ | Drive API | files.get (alt=media) for binary; files.export for Workspace types |
255
+ | Annotations | readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true |
256
+
257
+ Parameters:
258
+
259
+ | Param | Type | Required | Default | Description |
260
+ |-------|------|----------|---------|-------------|
261
+ | fileId | string | yes | — | Google Drive file ID |
262
+
263
+ Auto-detection logic:
264
+ - `application/vnd.google-apps.document` → export as HTML → convert to Markdown via turndown
265
+ - `application/vnd.google-apps.spreadsheet` → export as CSV (first sheet)
266
+ - `application/vnd.google-apps.presentation` → export as plain text
267
+ - `application/vnd.google-apps.drawing` → export as PNG (base64)
268
+ - `text/*` → download raw content
269
+ - Other binary → download up to 10MB, return metadata if larger
270
+
271
+ Response: File content as text, or metadata + size warning for oversized binary files.
272
+
273
+ ### 4.4 gdrive_create_file
274
+
275
+ Create a new file in Google Drive.
276
+
277
+ | Field | Value |
278
+ |-------|-------|
279
+ | Drive API | files.create (with media upload) |
280
+ | Annotations | readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true |
281
+
282
+ Parameters:
283
+
284
+ | Param | Type | Required | Default | Description |
285
+ |-------|------|----------|---------|-------------|
286
+ | name | string | yes | — | File name including extension |
287
+ | content | string | yes | — | File content (text) |
288
+ | mimeType | string | no | auto-detect | MIME type of content |
289
+ | parentFolderId | string | no | root | Parent folder ID |
290
+
291
+ Response: Created file metadata (id, name, webViewLink).
292
+
293
+ ### 4.5 gdrive_update_file
294
+
295
+ Update content of an existing file.
296
+
297
+ | Field | Value |
298
+ |-------|-------|
299
+ | Drive API | files.update (with media upload) |
300
+ | Annotations | readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true |
301
+
302
+ Parameters:
303
+
304
+ | Param | Type | Required | Default | Description |
305
+ |-------|------|----------|---------|-------------|
306
+ | fileId | string | yes | — | File ID to update |
307
+ | content | string | yes | — | New file content |
308
+ | mimeType | string | no | preserve original | MIME type of new content |
309
+
310
+ Response: Updated file metadata (id, name, modifiedTime).
311
+
312
+ ### 4.6 gdrive_delete_file
313
+
314
+ Delete or trash a file.
315
+
316
+ | Field | Value |
317
+ |-------|-------|
318
+ | Drive API | files.update (trashed: true) or files.delete |
319
+ | Annotations | readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true |
320
+
321
+ Parameters:
322
+
323
+ | Param | Type | Required | Default | Description |
324
+ |-------|------|----------|---------|-------------|
325
+ | fileId | string | yes | — | File ID to delete |
326
+ | permanent | boolean | no | false | true = permanent delete, false = move to trash |
327
+
328
+ Response: Confirmation message with file name and action taken.
329
+
330
+ ### 4.7 gdrive_move_file
331
+
332
+ Move a file to a different folder.
333
+
334
+ | Field | Value |
335
+ |-------|-------|
336
+ | Drive API | files.update (addParents + removeParents) |
337
+ | Annotations | readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true |
338
+
339
+ Parameters:
340
+
341
+ | Param | Type | Required | Default | Description |
342
+ |-------|------|----------|---------|-------------|
343
+ | fileId | string | yes | — | File ID to move |
344
+ | destinationFolderId | string | yes | — | Target folder ID |
345
+
346
+ Response: Updated file metadata with new parent.
347
+
348
+ ### 4.8 gdrive_copy_file
349
+
350
+ Copy a file, optionally to a different folder with a new name.
351
+
352
+ | Field | Value |
353
+ |-------|-------|
354
+ | Drive API | files.copy |
355
+ | Annotations | readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true |
356
+
357
+ Parameters:
358
+
359
+ | Param | Type | Required | Default | Description |
360
+ |-------|------|----------|---------|-------------|
361
+ | fileId | string | yes | — | Source file ID |
362
+ | name | string | no | "Copy of {original}" | Name for the copy |
363
+ | parentFolderId | string | no | same as original | Destination folder ID |
364
+
365
+ Response: New file metadata (id, name, webViewLink).
366
+
367
+ ### 4.9 gdrive_create_folder
368
+
369
+ Create a new folder.
370
+
371
+ | Field | Value |
372
+ |-------|-------|
373
+ | Drive API | files.create (mimeType: application/vnd.google-apps.folder) |
374
+ | Annotations | readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true |
375
+
376
+ Parameters:
377
+
378
+ | Param | Type | Required | Default | Description |
379
+ |-------|------|----------|---------|-------------|
380
+ | name | string | yes | — | Folder name |
381
+ | parentFolderId | string | no | root | Parent folder ID |
382
+
383
+ Response: Created folder metadata (id, name, webViewLink).
384
+
385
+ ### 4.10 gdrive_export_doc
386
+
387
+ Export a Google Doc to a specified format. Default: Markdown.
388
+
389
+ Note: For the default Markdown export, this tool produces the same result as gdrive_read_file on a Google Doc. The distinction: read_file always returns Markdown (optimized for Claude's consumption), while export_doc lets you choose the output format (HTML, plain text, DOCX, PDF).
390
+
391
+ | Field | Value |
392
+ |-------|-------|
393
+ | Drive API | files.export |
394
+ | Annotations | readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true |
395
+
396
+ Parameters:
397
+
398
+ | Param | Type | Required | Default | Description |
399
+ |-------|------|----------|---------|-------------|
400
+ | fileId | string | yes | — | Google Doc file ID |
401
+ | format | enum | no | "markdown" | "markdown", "html", "plain_text", "docx", "pdf" |
402
+
403
+ Format mapping:
404
+ - "markdown" → export as text/html → convert via turndown → return Markdown
405
+ - "html" → export as text/html → return raw HTML
406
+ - "plain_text" → export as text/plain
407
+ - "docx" → export as application/vnd.openxmlformats-officedocument.wordprocessingml.document (base64)
408
+ - "pdf" → export as application/pdf (base64)
409
+
410
+ Response: Exported content in requested format.
411
+
412
+ ### 4.11 gdrive_export_sheet
413
+
414
+ Export a Google Sheet to CSV or JSON.
415
+
416
+ | Field | Value |
417
+ |-------|-------|
418
+ | Drive API | files.export |
419
+ | Annotations | readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true |
420
+
421
+ Parameters:
422
+
423
+ | Param | Type | Required | Default | Description |
424
+ |-------|------|----------|---------|-------------|
425
+ | fileId | string | yes | — | Google Sheet file ID |
426
+ | format | enum | no | "csv" | "csv" or "json" |
427
+ | sheetName | string | no | first sheet | Specific sheet/tab name (NOTE: requires Sheets API — see limitation below) |
428
+
429
+ Limitation: The Drive API `files.export` exports the first sheet only for CSV. Exporting a specific sheet by name requires the Google Sheets API (`spreadsheets.values.get`). If sheetName is specified, the implementation should fall back to the Sheets API. This may require adding the `https://www.googleapis.com/auth/spreadsheets.readonly` scope.
430
+
431
+ Format mapping:
432
+ - "csv" → export as text/csv
433
+ - "json" → export as text/csv → parse to JSON array of objects (headers as keys)
434
+
435
+ Response: Sheet data in requested format.
436
+
437
+ ### 4.12 gdrive_export_slides
438
+
439
+ Export Google Slides to text or PDF.
440
+
441
+ | Field | Value |
442
+ |-------|-------|
443
+ | Drive API | files.export |
444
+ | Annotations | readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true |
445
+
446
+ Parameters:
447
+
448
+ | Param | Type | Required | Default | Description |
449
+ |-------|------|----------|---------|-------------|
450
+ | fileId | string | yes | — | Google Slides file ID |
451
+ | format | enum | no | "text" | "text" or "pdf" |
452
+
453
+ Format mapping:
454
+ - "text" → export as text/plain
455
+ - "pdf" → export as application/pdf (base64)
456
+
457
+ Response: Slides content in requested format.
458
+
459
+ ### 4.13 gdrive_create_doc
460
+
461
+ Create a new Google Doc from Markdown content.
462
+
463
+ | Field | Value |
464
+ |-------|-------|
465
+ | Drive API | files.create (with conversion) |
466
+ | Annotations | readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true |
467
+
468
+ Parameters:
469
+
470
+ | Param | Type | Required | Default | Description |
471
+ |-------|------|----------|---------|-------------|
472
+ | name | string | yes | — | Document title |
473
+ | markdownContent | string | yes | — | Markdown content to convert |
474
+ | parentFolderId | string | no | root | Parent folder ID |
475
+
476
+ Implementation: Convert Markdown → HTML, upload as text/html with `mimeType: application/vnd.google-apps.document` to trigger Google's conversion.
477
+
478
+ Response: Created doc metadata (id, name, webViewLink).
479
+
480
+ ### 4.14 gdrive_update_doc
481
+
482
+ Update an existing Google Doc with new Markdown content.
483
+
484
+ | Field | Value |
485
+ |-------|-------|
486
+ | Drive API | files.update (with media upload and conversion) |
487
+ | Annotations | readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true |
488
+
489
+ Parameters:
490
+
491
+ | Param | Type | Required | Default | Description |
492
+ |-------|------|----------|---------|-------------|
493
+ | fileId | string | yes | — | Google Doc file ID |
494
+ | markdownContent | string | yes | — | New Markdown content (replaces entire doc) |
495
+
496
+ Implementation: Convert Markdown → HTML, upload as text/html to replace doc content.
497
+
498
+ Response: Updated doc metadata (id, name, modifiedTime).
499
+
500
+ ---
501
+
502
+ ## 5. Format Conversion Strategy
503
+
504
+ ### 5.1 Google Workspace MIME Types
505
+
506
+ | Google Type | MIME Type | Default Export |
507
+ |-------------|-----------|----------------|
508
+ | Document | application/vnd.google-apps.document | HTML → Markdown (turndown) |
509
+ | Spreadsheet | application/vnd.google-apps.spreadsheet | text/csv |
510
+ | Presentation | application/vnd.google-apps.presentation | text/plain |
511
+ | Drawing | application/vnd.google-apps.drawing | image/png |
512
+ | Form | application/vnd.google-apps.form | N/A (not supported) |
513
+
514
+ ### 5.2 Turndown Configuration
515
+
516
+ ```javascript
517
+ const turndownService = new TurndownService({
518
+ headingStyle: "atx", // # style headings
519
+ codeBlockStyle: "fenced", // ``` code blocks
520
+ bulletListMarker: "-",
521
+ emDelimiter: "_",
522
+ strongDelimiter: "**",
523
+ });
524
+ ```
525
+
526
+ ### 5.3 Markdown → HTML for Doc Creation
527
+
528
+ For creating/updating Google Docs from Markdown, a simple MD→HTML conversion is needed. Options:
529
+ - **marked** (npm) — lightweight, fast, well-maintained
530
+ - **showdown** (npm) — CommonMark compliant
531
+ - Recommendation: Use **marked** (~200KB, zero deps, CommonMark)
532
+
533
+ This adds one more dependency: `marked ^15.0.0`
534
+
535
+ Updated dependencies:
536
+ ```json
537
+ {
538
+ "googleapis": "^144.0.0",
539
+ "turndown": "^7.2.0",
540
+ "marked": "^15.0.0",
541
+ "@modelcontextprotocol/sdk": "^1.26.0",
542
+ "zod": "^3.25.0"
543
+ }
544
+ ```
545
+
546
+ ---
547
+
548
+ ## 6. Error Handling Strategy
549
+
550
+ ### 6.1 Error Categories
551
+
552
+ | Category | HTTP Code | MCP Response | User Message |
553
+ |----------|-----------|--------------|--------------|
554
+ | Auth expired | 401 | Error | "Authentication expired. Run `mcp-google-gdrive --auth` to re-authenticate." |
555
+ | Token missing | — | Error | "No token.json found. Run `mcp-google-gdrive --auth` for first-time setup." |
556
+ | File not found | 404 | Error | "File not found: {fileId}. Verify the file ID exists and you have access." |
557
+ | Permission denied | 403 | Error | "Permission denied for file {fileId}. Check sharing settings." |
558
+ | Rate limited | 429 | Error | "Rate limited by Google API. Wait and retry." (NOTE: write limit is 3 req/sec/account, hard cap) |
559
+ | Quota exceeded | 403 (reason: dailyLimitExceeded) | Error | "Daily API quota exceeded. Try again tomorrow." |
560
+ | File too large | — | Error | "File exceeds 10MB limit. File metadata: {name}, {size}." |
561
+ | Invalid file type | — | Error | "Cannot export: {mimeType} is not a Google Workspace file." |
562
+ | Network error | — | Error | "Network error connecting to Google APIs. Check internet connection." |
563
+
564
+ ### 6.2 Error Response Format
565
+
566
+ All errors return standard MCP error content:
567
+ ```javascript
568
+ return {
569
+ content: [{ type: "text", text: `Error: ${message}` }],
570
+ isError: true
571
+ };
572
+ ```
573
+
574
+ ### 6.3 Token Auto-Refresh
575
+
576
+ The googleapis client handles token refresh automatically:
577
+ - On 401 response, client uses refresh_token to get new access_token
578
+ - New access_token saved to token.json
579
+ - Original request retried with new token
580
+ - If refresh fails (revoked token), return auth expired error
581
+
582
+ ---
583
+
584
+ ## 7. File Size Handling
585
+
586
+ | Scenario | Behavior |
587
+ |----------|----------|
588
+ | Text file < 10MB | Return full content |
589
+ | Text file > 10MB | Return first 10MB + warning + total size |
590
+ | Binary file < 10MB | Return base64-encoded content |
591
+ | Binary file > 10MB | Return metadata only: name, size, mimeType, webViewLink |
592
+ | Google Doc/Sheet/Slides | Always export (size is post-conversion, usually small) |
593
+
594
+ ---
595
+
596
+ ## 8. Claude Code MCP Configuration
597
+
598
+ Add to `~/.claude/settings.json` under `mcpServers`:
599
+
600
+ ```json
601
+ {
602
+ "mcpServers": {
603
+ "google-drive": {
604
+ "command": "wsl",
605
+ "args": ["-d", "FedoraLinux-43", "--", "bash", "-c", "node ~/projects/mcp-servers/mcp-google-gdrive/src/index.js"],
606
+ "env": {}
607
+ }
608
+ }
609
+ }
610
+ ```
611
+
612
+ Alternative using npx (after npm link):
613
+ ```json
614
+ {
615
+ "mcpServers": {
616
+ "google-drive": {
617
+ "command": "wsl",
618
+ "args": ["-d", "FedoraLinux-43", "--", "npx", "mcp-google-gdrive"],
619
+ "env": {}
620
+ }
621
+ }
622
+ }
623
+ ```
624
+
625
+ ---
626
+
627
+ ## 9. Build Phases
628
+
629
+ ### Phase 1: Core (MVP) — Priority: Immediate
630
+
631
+ 1. Project scaffolding (package.json, .gitignore, LICENSE, README)
632
+ 2. OAuth2 auth flow (credentials.json → token.json, auto-refresh)
633
+ 3. gdrive_list_files (search and browse)
634
+ 4. gdrive_read_file (with auto-detection for Docs/Sheets/Slides)
635
+ 5. gdrive_get_metadata
636
+ 6. gdrive_create_file
637
+ 7. gdrive_create_folder
638
+ 8. Claude Code MCP configuration and end-to-end test
639
+
640
+ Rationale: These 5 tools cover the primary use case (browse, read, create). Auth must work first. End-to-end test validates the WSL↔Windows↔Google pipeline.
641
+
642
+ ### Phase 2: Full CRUD — Priority: Next
643
+
644
+ 9. gdrive_update_file
645
+ 10. gdrive_delete_file
646
+ 11. gdrive_move_file
647
+ 12. gdrive_copy_file
648
+
649
+ Rationale: Complete file management operations. Each builds on the Phase 1 foundation.
650
+
651
+ ### Phase 3: Workspace — Priority: After CRUD
652
+
653
+ 13. gdrive_export_doc (with turndown HTML→MD)
654
+ 14. gdrive_export_sheet (CSV + JSON)
655
+ 15. gdrive_export_slides
656
+ 16. gdrive_create_doc (MD→HTML→Google Doc)
657
+ 17. gdrive_update_doc
658
+
659
+ Rationale: Format conversion is valuable but builds on top of working read/write. Turndown and marked dependencies only needed for this phase.
660
+
661
+ ### Phase 4: Polish — Priority: As needed
662
+
663
+ 18. README with full documentation
664
+ 19. Error handling hardening
665
+ 20. Edge case coverage (shared drives, shortcuts, trashed files)
666
+ 21. npm publish (if desired)
667
+
668
+ ---
669
+
670
+ ## 10. BDD Acceptance Criteria
671
+
672
+ ### gdrive_list_files
673
+
674
+ **Story:** As a Claude Code user, I want to list and search files in my Drive so that I can find files to work with.
675
+
676
+ - Given a Drive with files, when I call gdrive_list_files with no params, then I receive up to 20 files sorted by modifiedTime desc with id, name, mimeType, modifiedTime, size, parents.
677
+ - Given a folder with ID "abc123", when I call gdrive_list_files with folderId "abc123", then I receive only files whose parents include "abc123".
678
+ - Given a query "name contains 'report'", when I call gdrive_list_files with that query, then I receive only files matching the search.
679
+ - Given more than 20 files match, when I call gdrive_list_files with pageSize 20, then the response includes a nextPageToken for pagination.
680
+ - Given an invalid folderId, when I call gdrive_list_files, then I receive an error with "File not found" message.
681
+
682
+ ### gdrive_get_metadata
683
+
684
+ **Story:** As a Claude Code user, I want to get detailed file metadata so that I can make informed decisions about file operations.
685
+
686
+ - Given a valid fileId, when I call gdrive_get_metadata, then I receive id, name, mimeType, size, createdTime, modifiedTime, parents, webViewLink, owners, shared, description.
687
+ - Given an invalid fileId, when I call gdrive_get_metadata, then I receive a 404 error with descriptive message.
688
+
689
+ ### gdrive_read_file
690
+
691
+ **Story:** As a Claude Code user, I want to read file contents so that I can analyze and work with Drive data.
692
+
693
+ - Given a text file (text/plain), when I call gdrive_read_file, then I receive the raw text content.
694
+ - Given a Google Doc, when I call gdrive_read_file, then the Doc is exported as HTML and converted to Markdown via turndown.
695
+ - Given a Google Sheet, when I call gdrive_read_file, then the first sheet is exported as CSV.
696
+ - Given a Google Slides presentation, when I call gdrive_read_file, then slides are exported as plain text.
697
+ - Given a binary file under 10MB, when I call gdrive_read_file, then I receive base64-encoded content.
698
+ - Given a binary file over 10MB, when I call gdrive_read_file, then I receive metadata (name, size, mimeType, webViewLink) with a size warning.
699
+
700
+ ### gdrive_create_file
701
+
702
+ **Story:** As a Claude Code user, I want to create files in Drive so that I can store new content.
703
+
704
+ - Given a name "notes.txt" and content "hello world", when I call gdrive_create_file, then a new file is created in root and I receive id, name, webViewLink.
705
+ - Given a parentFolderId, when I call gdrive_create_file with that param, then the file is created inside that folder.
706
+ - Given an invalid parentFolderId, when I call gdrive_create_file, then I receive a 404 error.
707
+
708
+ ### gdrive_update_file
709
+
710
+ **Story:** As a Claude Code user, I want to update file contents so that I can modify existing Drive files.
711
+
712
+ - Given a valid fileId and new content, when I call gdrive_update_file, then the file content is replaced and I receive id, name, modifiedTime.
713
+ - Given an invalid fileId, when I call gdrive_update_file, then I receive a 404 error.
714
+ - Given a file I don't own, when I call gdrive_update_file, then I receive a 403 permission denied error.
715
+
716
+ ### gdrive_delete_file
717
+
718
+ **Story:** As a Claude Code user, I want to delete files so that I can manage Drive contents.
719
+
720
+ - Given a valid fileId with permanent=false (default), when I call gdrive_delete_file, then the file is moved to trash and I receive confirmation.
721
+ - Given a valid fileId with permanent=true, when I call gdrive_delete_file, then the file is permanently deleted and I receive confirmation.
722
+ - Given an invalid fileId, when I call gdrive_delete_file, then I receive a 404 error.
723
+
724
+ ### gdrive_move_file
725
+
726
+ **Story:** As a Claude Code user, I want to move files between folders so that I can organize my Drive.
727
+
728
+ - Given a file in folder A and destination folder B, when I call gdrive_move_file, then the file's parents change from A to B.
729
+ - Given an invalid destinationFolderId, when I call gdrive_move_file, then I receive a 404 error.
730
+
731
+ ### gdrive_copy_file
732
+
733
+ **Story:** As a Claude Code user, I want to copy files so that I can duplicate content.
734
+
735
+ - Given a valid fileId, when I call gdrive_copy_file with no name, then a copy is created named "Copy of {original}" in the same folder.
736
+ - Given a valid fileId and name "backup.txt", when I call gdrive_copy_file, then the copy is created with the specified name.
737
+ - Given a parentFolderId, when I call gdrive_copy_file with that param, then the copy is placed in the specified folder.
738
+
739
+ ### gdrive_create_folder
740
+
741
+ **Story:** As a Claude Code user, I want to create folders so that I can organize Drive files.
742
+
743
+ - Given a name "Projects", when I call gdrive_create_folder, then a folder is created in root with mimeType application/vnd.google-apps.folder and I receive id, name, webViewLink.
744
+ - Given a parentFolderId, when I call gdrive_create_folder with that param, then the folder is created as a subfolder.
745
+
746
+ ### gdrive_export_doc
747
+
748
+ **Story:** As a Claude Code user, I want to export Google Docs in various formats so that I can work with their content.
749
+
750
+ - Given a Google Doc fileId with format="markdown" (default), when I call gdrive_export_doc, then the doc is exported as HTML and converted to Markdown via turndown.
751
+ - Given format="html", when I call gdrive_export_doc, then I receive raw HTML content.
752
+ - Given format="plain_text", when I call gdrive_export_doc, then I receive plain text content.
753
+ - Given a non-Doc fileId, when I call gdrive_export_doc, then I receive an error "Cannot export: {mimeType} is not a Google Workspace file."
754
+
755
+ ### gdrive_export_sheet
756
+
757
+ **Story:** As a Claude Code user, I want to export Google Sheets so that I can analyze spreadsheet data.
758
+
759
+ - Given a Google Sheet fileId with format="csv" (default), when I call gdrive_export_sheet, then I receive CSV content of the first sheet.
760
+ - Given format="json", when I call gdrive_export_sheet, then I receive a JSON array of objects with column headers as keys.
761
+ - Given a sheetName param, when I call gdrive_export_sheet, then only that specific sheet/tab is exported.
762
+ - Given a non-Sheet fileId, when I call gdrive_export_sheet, then I receive an error.
763
+
764
+ ### gdrive_export_slides
765
+
766
+ **Story:** As a Claude Code user, I want to export Google Slides so that I can work with presentation content.
767
+
768
+ - Given a Google Slides fileId with format="text" (default), when I call gdrive_export_slides, then I receive plain text content of all slides.
769
+ - Given format="pdf", when I call gdrive_export_slides, then I receive base64-encoded PDF content.
770
+
771
+ ### gdrive_create_doc
772
+
773
+ **Story:** As a Claude Code user, I want to create Google Docs from Markdown so that I can produce formatted documents.
774
+
775
+ - Given a name and markdownContent, when I call gdrive_create_doc, then a new Google Doc is created with the Markdown converted to HTML and then to Google Doc format, and I receive id, name, webViewLink.
776
+ - Given a parentFolderId, when I call gdrive_create_doc with that param, then the doc is created in that folder.
777
+
778
+ ### gdrive_update_doc
779
+
780
+ **Story:** As a Claude Code user, I want to update Google Docs with new Markdown so that I can revise formatted documents.
781
+
782
+ - Given a valid Google Doc fileId and markdownContent, when I call gdrive_update_doc, then the entire doc content is replaced with the new Markdown (converted via MD→HTML→Doc), and I receive id, name, modifiedTime.
783
+ - Given a non-Doc fileId, when I call gdrive_update_doc, then I receive an error.
784
+
785
+ ---
786
+
787
+ ## 11. Design Document (C4 Architecture)
788
+
789
+ ### 11.1 Business Posture
790
+
791
+ | Priority | Goal | Description |
792
+ |----------|------|-------------|
793
+ | P0 | Full Drive access for AI assistant | Give Claude Code (Pallas) read/write access to personal Google Drive, closing the last major Google integration gap in PAI |
794
+ | P1 | Workspace format interop | Convert Google Docs, Sheets, Slides into AI-consumable formats (Markdown, CSV, JSON, plain text) |
795
+ | P2 | File lifecycle management | Create, read, update, delete, search, move, copy operations on Drive files and folders |
796
+ | P3 | Local-only deployment | Run entirely on local hardware (WSL2) with no cloud hosting, no exposed endpoints |
797
+
798
+ ### 11.2 Security Posture
799
+
800
+ Existing Controls:
801
+ - SC-1: OAuth2 desktop app flow (Google-recommended, PKCE)
802
+ - SC-2: Token stored at ~/.config/mcp-google-gdrive/token.json with 0600 permissions
803
+ - SC-3: Single scope (drive), no Gmail or Calendar scopes
804
+ - SC-4: stdio transport only — no network listeners, no HTTP endpoints
805
+ - SC-5: Single-user model, runs as local user
806
+ - SC-6: WSL2 VM isolation from Windows host
807
+ - SC-7: Stateless between invocations (no database, no cache)
808
+
809
+ Accepted Risks:
810
+ - AR-1: Full Drive scope (required for Workspace export/conversion)
811
+ - AR-2: Token on local filesystem (single-user machine, encrypted disk + file permissions sufficient)
812
+ - AR-3: No audit logging (personal tool; Google Admin audit log available if needed)
813
+ - AR-4: AI can invoke destructive operations (Drive trash provides recovery; user approves tool calls)
814
+
815
+ ### 11.3 C4 Context Diagram
816
+
817
+ ```mermaid
818
+ C4Context
819
+ title System Context - Google Drive MCP Server
820
+
821
+ Person(user, "Shon", "PAI user interacting via Claude Code")
822
+ System(claudeCode, "Claude Code", "AI coding assistant running on Windows 11")
823
+ System(mcpServer, "mcp-google-gdrive", "MCP server in WSL providing Drive tools")
824
+ System_Ext(googleApi, "Google Drive API v3", "Google cloud service for file storage")
825
+ System_Ext(googleDrive, "Google Drive", "Personal cloud storage with files and Workspace docs")
826
+
827
+ Rel(user, claudeCode, "Sends requests via CLI/IDE")
828
+ Rel(claudeCode, mcpServer, "JSON-RPC over stdio", "MCP protocol")
829
+ Rel(mcpServer, googleApi, "HTTPS REST calls", "OAuth2 bearer token")
830
+ Rel(googleApi, googleDrive, "Internal Google infrastructure")
831
+ ```
832
+
833
+ | Element | Type | Responsibilities |
834
+ |---------|------|-----------------|
835
+ | Shon | Person | Issues file management commands through Claude Code |
836
+ | Claude Code | System | Discovers MCP tools, selects appropriate tool for user request, displays results |
837
+ | mcp-google-gdrive | System | Authenticates to Google, translates MCP tool calls to Drive API requests, converts formats |
838
+ | Google Drive API v3 | External System | Provides REST endpoints for file CRUD, export, metadata |
839
+ | Google Drive | External System | Stores files, Google Workspace documents, folder structure |
840
+
841
+ ### 11.4 C4 Container Diagram
842
+
843
+ ```mermaid
844
+ C4Container
845
+ title Container Diagram - mcp-google-gdrive
846
+
847
+ Person(user, "Claude Code", "MCP client")
848
+
849
+ Container_Boundary(server, "mcp-google-gdrive (Node.js ESM)") {
850
+ Container(protocol, "MCP Protocol Handler", "McpServer + StdioServerTransport", "Receives JSON-RPC tool calls, routes to handlers")
851
+ Container(registry, "Tool Registry", "14 registerTool calls + Zod schemas", "Validates inputs, dispatches to API client")
852
+ Container(auth, "Auth Manager", "googleapis OAuth2Client", "Loads/refreshes/saves OAuth2 tokens")
853
+ Container(client, "Drive API Client", "googleapis drive.files.*", "Executes Drive API v3 HTTP requests")
854
+ Container(converter, "Format Converter", "turndown + marked", "HTML→MD, MD→HTML, CSV→JSON")
855
+ }
856
+
857
+ System_Ext(google, "Google Drive API v3", "REST API")
858
+ System_Ext(tokenFile, "~/.config/mcp-google-gdrive/token.json", "Local file")
859
+
860
+ Rel(user, protocol, "stdio JSON-RPC")
861
+ Rel(protocol, registry, "Tool call dispatch")
862
+ Rel(registry, client, "API request")
863
+ Rel(registry, converter, "Format conversion")
864
+ Rel(client, auth, "Get access token")
865
+ Rel(auth, tokenFile, "Read/write tokens")
866
+ Rel(client, google, "HTTPS REST")
867
+ ```
868
+
869
+ | Container | Type | Responsibilities | Tech |
870
+ |-----------|------|-----------------|------|
871
+ | MCP Protocol Handler | Process | Accept stdio input, parse JSON-RPC, return responses | @modelcontextprotocol/sdk |
872
+ | Tool Registry | Module | Define 14 tools with Zod schemas and annotations | zod |
873
+ | Auth Manager | Module | OAuth2 token lifecycle (load, refresh, save) | googleapis |
874
+ | Drive API Client | Module | HTTP calls to Drive API endpoints | googleapis |
875
+ | Format Converter | Module | Bidirectional format conversion for Workspace files | turndown, marked |
876
+
877
+ ### 11.5 C4 Deployment Diagram
878
+
879
+ ```mermaid
880
+ C4Deployment
881
+ title Deployment Diagram - mcp-google-gdrive
882
+
883
+ Deployment_Node(win, "Windows 11 Pro", "Host OS") {
884
+ Deployment_Node(vscode, "VSCode + Claude Code") {
885
+ Container(cc, "Claude Code Extension", "MCP Client")
886
+ }
887
+ Deployment_Node(wsl, "WSL2 - Fedora 43", "Linux VM") {
888
+ Deployment_Node(node, "Node.js v22", "JavaScript Runtime") {
889
+ Container(mcp, "mcp-google-gdrive", "MCP Server Process")
890
+ }
891
+ Deployment_Node(config, "~/.config/mcp-google-gdrive/") {
892
+ Container(creds, "credentials.json", "OAuth2 client credentials")
893
+ Container(token, "token.json", "OAuth2 access + refresh tokens")
894
+ }
895
+ }
896
+ }
897
+
898
+ Deployment_Node(gcloud, "Google Cloud", "External") {
899
+ Container(driveApi, "Drive API v3", "REST Service")
900
+ }
901
+
902
+ Rel(cc, mcp, "stdio (JSON-RPC)")
903
+ Rel(mcp, creds, "Read on startup")
904
+ Rel(mcp, token, "Read/write for auth")
905
+ Rel(mcp, driveApi, "HTTPS REST calls")
906
+ ```
907
+
908
+ ### 11.6 Risk Assessment
909
+
910
+ Critical process: OAuth2 token management. If tokens are compromised, attacker has full Drive access.
911
+
912
+ Data sensitivity:
913
+ - credentials.json: MEDIUM (contains client_id and client_secret — enables auth flow but not data access alone)
914
+ - token.json: HIGH (contains refresh_token — grants persistent Drive access)
915
+ - File content in transit: MEDIUM (encrypted via HTTPS to Google, plaintext via stdio locally)
916
+
917
+ Mitigations: File permissions (0600), .gitignore exclusion, WSL2 VM boundary, no network exposure.