mcp-gee-sweet 0.7.0.dev49__tar.gz → 0.7.0.dev53__tar.gz

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 (127) hide show
  1. mcp_gee_sweet-0.7.0.dev53/.pre-commit-config.yaml +15 -0
  2. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/CLAUDE.md +2 -0
  3. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/PKG-INFO +2 -2
  4. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/README.md +1 -1
  5. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/docs/configuration.md +4 -1
  6. mcp_gee_sweet-0.7.0.dev53/docs/decisions/decision-grid-data-size-cap.md +63 -0
  7. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/docs/decisions/index.md +1 -0
  8. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/docs/qa/tests/infra.md +1 -0
  9. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/docs/qa/tests/sheets_read.md +66 -2
  10. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/docs/roadmap.md +50 -4
  11. mcp_gee_sweet-0.7.0.dev53/docs/tools.md +168 -0
  12. mcp_gee_sweet-0.7.0.dev53/scripts/gen_tool_docs.py +349 -0
  13. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/src/mcp_gee_sweet/.env.template +5 -0
  14. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/src/mcp_gee_sweet/tools/sheets/data.py +106 -2
  15. mcp_gee_sweet-0.7.0.dev53/tests/sheets/test_data.py +371 -0
  16. mcp_gee_sweet-0.7.0.dev53/tests/test_gen_tool_docs.py +130 -0
  17. mcp_gee_sweet-0.7.0.dev49/.pre-commit-config.yaml +0 -7
  18. mcp_gee_sweet-0.7.0.dev49/docs/tools.md +0 -133
  19. mcp_gee_sweet-0.7.0.dev49/tests/sheets/test_data.py +0 -175
  20. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/.claude/commands/prep-for-pr.md +0 -0
  21. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/.dockerignore +0 -0
  22. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  23. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  24. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/.github/dependabot.yml +0 -0
  25. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/.github/workflows/ci.yml +0 -0
  26. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/.github/workflows/docs.yml +0 -0
  27. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/.github/workflows/publish-dev.yml +0 -0
  28. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/.github/workflows/release.yml +0 -0
  29. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/.gitignore +0 -0
  30. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/.python-version +0 -0
  31. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/CHANGELOG.md +0 -0
  32. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/CONTRIBUTING.md +0 -0
  33. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/Dockerfile +0 -0
  34. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/LICENSE +0 -0
  35. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/Makefile +0 -0
  36. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/SECURITY.md +0 -0
  37. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/TODO.md +0 -0
  38. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/docker-compose.yml +0 -0
  39. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/docs/auth.md +0 -0
  40. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/docs/client-setup.md +0 -0
  41. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/docs/decisions/decision-chart-theming.md +0 -0
  42. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/docs/decisions/decision-composite-tools.md +0 -0
  43. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/docs/decisions/decision-docs-formatting.md +0 -0
  44. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/docs/decisions/decision-fork.md +0 -0
  45. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/docs/decisions/decision-publishing.md +0 -0
  46. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/docs/decisions/decision-testing.md +0 -0
  47. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/docs/design/docs-ast-pipeline.md +0 -0
  48. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/docs/design/effectiveformat-spike.md +0 -0
  49. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/docs/design/index.md +0 -0
  50. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/docs/design/markdown-support.md +0 -0
  51. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/docs/design/phase3-theme-system.md +0 -0
  52. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/docs/design.md +0 -0
  53. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/docs/index.md +0 -0
  54. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/docs/known-limitations.md +0 -0
  55. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/docs/notes-read.md +0 -0
  56. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/docs/qa/README.md +0 -0
  57. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/docs/qa/fixtures/tc-d195-create-doc.md +0 -0
  58. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/docs/qa/fixtures/tc-d196-create-doc.html +0 -0
  59. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/docs/qa/fixtures/tc-d226-heading-table.md +0 -0
  60. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/docs/qa/fixtures.template.md +0 -0
  61. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/docs/qa/operations.yaml +0 -0
  62. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/docs/qa/playwright_oauth.md +0 -0
  63. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/docs/qa/results/.gitkeep +0 -0
  64. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/docs/qa/results/2026-06-02.md +0 -0
  65. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/docs/qa/results/2026-06-14.md +0 -0
  66. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/docs/qa/results/2026-06-17.md +0 -0
  67. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/docs/qa/results/2026-06-18.md +0 -0
  68. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/docs/qa/results/2026-06-21-drive-discovery.md +0 -0
  69. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/docs/qa/results/2026-06-21-sheets-delete-clear.md +0 -0
  70. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/docs/qa/results/2026-06-28.md +0 -0
  71. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/docs/qa/retro-v0.8.0.md +0 -0
  72. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/docs/qa/run.md +0 -0
  73. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/docs/qa/runs/README.md +0 -0
  74. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/docs/qa/runs/v0.8.0.md +0 -0
  75. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/docs/qa/setup.md +0 -0
  76. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/docs/qa/tests/calendar.md +0 -0
  77. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/docs/qa/tests/docs.md +0 -0
  78. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/docs/qa/tests/drive.md +0 -0
  79. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/docs/qa/tests/sheets_charts.md +0 -0
  80. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/docs/qa/tests/sheets_mgmt.md +0 -0
  81. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/docs/qa/tests/sheets_write.md +0 -0
  82. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/docs/qa-checklist.md +0 -0
  83. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/mkdocs.yml +0 -0
  84. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/pyproject.toml +0 -0
  85. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/scripts/oauth_setup.py +0 -0
  86. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/src/mcp_gee_sweet/__init__.py +0 -0
  87. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/src/mcp_gee_sweet/auth.py +0 -0
  88. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/src/mcp_gee_sweet/cache.py +0 -0
  89. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/src/mcp_gee_sweet/server.py +0 -0
  90. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/src/mcp_gee_sweet/tools/__init__.py +0 -0
  91. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/src/mcp_gee_sweet/tools/cache.py +0 -0
  92. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/src/mcp_gee_sweet/tools/calendar.py +0 -0
  93. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/src/mcp_gee_sweet/tools/docs/__init__.py +0 -0
  94. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/src/mcp_gee_sweet/tools/docs/ast.py +0 -0
  95. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/src/mcp_gee_sweet/tools/docs/content.py +0 -0
  96. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/src/mcp_gee_sweet/tools/docs/emitter.py +0 -0
  97. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/src/mcp_gee_sweet/tools/docs/html_parser.py +0 -0
  98. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/src/mcp_gee_sweet/tools/docs/layout.py +0 -0
  99. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/src/mcp_gee_sweet/tools/docs/style.py +0 -0
  100. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/src/mcp_gee_sweet/tools/docs/tables.py +0 -0
  101. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/src/mcp_gee_sweet/tools/drive/__init__.py +0 -0
  102. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/src/mcp_gee_sweet/tools/drive/activity.py +0 -0
  103. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/src/mcp_gee_sweet/tools/drive/files.py +0 -0
  104. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/src/mcp_gee_sweet/tools/drive/sharing.py +0 -0
  105. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/src/mcp_gee_sweet/tools/drive/transfer.py +0 -0
  106. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/src/mcp_gee_sweet/tools/sheets/__init__.py +0 -0
  107. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/src/mcp_gee_sweet/tools/sheets/helpers.py +0 -0
  108. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/src/mcp_gee_sweet/tools/sheets/structure.py +0 -0
  109. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/tests/__init__.py +0 -0
  110. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/tests/drive/__init__.py +0 -0
  111. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/tests/drive/test_activity.py +0 -0
  112. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/tests/drive/test_files.py +0 -0
  113. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/tests/drive/test_sharing.py +0 -0
  114. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/tests/drive/test_transfer.py +0 -0
  115. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/tests/sheets/__init__.py +0 -0
  116. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/tests/sheets/test_helpers.py +0 -0
  117. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/tests/sheets/test_structure.py +0 -0
  118. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/tests/test_auth.py +0 -0
  119. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/tests/test_cache.py +0 -0
  120. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/tests/test_calendar.py +0 -0
  121. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/tests/test_docs_content.py +0 -0
  122. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/tests/test_docs_core.py +0 -0
  123. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/tests/test_docs_layout.py +0 -0
  124. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/tests/test_docs_style.py +0 -0
  125. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/tests/test_docs_tables.py +0 -0
  126. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/tests/test_server.py +0 -0
  127. {mcp_gee_sweet-0.7.0.dev49 → mcp_gee_sweet-0.7.0.dev53}/uv.lock +0 -0
@@ -0,0 +1,15 @@
1
+ repos:
2
+ - repo: https://github.com/astral-sh/ruff-pre-commit
3
+ rev: v0.11.9
4
+ hooks:
5
+ - id: ruff
6
+ args: [--fix]
7
+ - id: ruff-format
8
+ - repo: local
9
+ hooks:
10
+ - id: gen-tool-docs
11
+ name: Regenerate tool docs
12
+ entry: uv run python scripts/gen_tool_docs.py
13
+ language: system
14
+ pass_filenames: false
15
+ files: ^(src/mcp_gee_sweet/tools/.*\.py|docs/tools\.md|docs/configuration\.md)$
@@ -81,3 +81,5 @@ Set `AUTH_METHOD` to pin a specific method with no fallback — removes ambiguit
81
81
 
82
82
  **MCP restart**: After `docker compose restart mcp-gee-sweet`, Claude Code does not automatically reconnect to the SSE server — you must restart Claude Code too to re-establish the connection.
83
83
 
84
+ **Generated tool docs**: `docs/tools.md` and the "Tool filtering" section of `docs/configuration.md` are generated by `scripts/gen_tool_docs.py` from the tool source (names, signatures, docstrings) — do not hand-edit the tool tables in either file. The script is wired into `.pre-commit-config.yaml` and runs whenever a file under `src/mcp_gee_sweet/tools/` or either generated doc changes; it fails the commit if a tool has no docstring, if a tool doesn't match any section in the script's `SECTIONS` config, or if a `SUBSETS` entry references a renamed/removed tool name. Run it manually with `uv run python scripts/gen_tool_docs.py`.
85
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcp-gee-sweet
3
- Version: 0.7.0.dev49
3
+ Version: 0.7.0.dev53
4
4
  Summary: MCP server for Google Workspace — Sheets, Drive, Docs, and Calendar. 60+ tools for AI clients.
5
5
  Project-URL: Homepage, https://github.com/khuisman/mcp-gee-sweet
6
6
  Project-URL: Bug Tracker, https://github.com/khuisman/mcp-gee-sweet/issues
@@ -27,7 +27,7 @@ Description-Content-Type: text/markdown
27
27
  ![GitHub License](https://img.shields.io/github/license/khuisman/mcp-gee-sweet)
28
28
  </div>
29
29
 
30
- An MCP server that gives AI clients reliable, direct access to Google Workspace — Sheets, Drive, Docs, and Calendar. 75 tools across four domains. Forked from [xing5/mcp-google-sheets](https://github.com/xing5/mcp-google-sheets) and expanded substantially.
30
+ An MCP server that gives AI clients reliable, direct access to Google Workspace — Sheets, Drive, Docs, and Calendar. 84 tools across four domains. Forked from [xing5/mcp-google-sheets](https://github.com/xing5/mcp-google-sheets) and expanded substantially.
31
31
 
32
32
  **Install (stable):**
33
33
  ```bash
@@ -5,7 +5,7 @@
5
5
  ![GitHub License](https://img.shields.io/github/license/khuisman/mcp-gee-sweet)
6
6
  </div>
7
7
 
8
- An MCP server that gives AI clients reliable, direct access to Google Workspace — Sheets, Drive, Docs, and Calendar. 75 tools across four domains. Forked from [xing5/mcp-google-sheets](https://github.com/xing5/mcp-google-sheets) and expanded substantially.
8
+ An MCP server that gives AI clients reliable, direct access to Google Workspace — Sheets, Drive, Docs, and Calendar. 84 tools across four domains. Forked from [xing5/mcp-google-sheets](https://github.com/xing5/mcp-google-sheets) and expanded substantially.
9
9
 
10
10
  **Install (stable):**
11
11
  ```bash
@@ -14,6 +14,7 @@
14
14
  | `ENABLED_TOOLS` | Comma-separated list of tool names to register | all tools |
15
15
  | `CACHE_DB_PATH` | Path to the SQLite cache database | `/tmp/mcp_gee_sweet.db` |
16
16
  | `CACHE_TTL` | Cache time-to-live in seconds | `1800` (30 min) |
17
+ | `MAX_GRID_DATA_RESPONSE_CHARS` | Safety cap on `get_sheet_data(include_grid_data=True)` response size, in characters. Raise to match a raised `MAX_MCP_OUTPUT_TOKENS` in your MCP client (roughly 2 characters per token observed for this densely-formatted JSON) | `40000` |
17
18
  | `HOST` | Bind address for SSE transport | `0.0.0.0` |
18
19
  | `PORT` | Port for SSE transport | `8000` |
19
20
  | `DEBUG_LEVEL` | Logging level: `DEBUG`, `INFO`, `WARNING`, `ERROR` — controls package and access logs | — |
@@ -66,7 +67,9 @@ Access logs are emitted at `INFO` level — they appear when `DEBUG_LEVEL` is `D
66
67
 
67
68
  ## Tool filtering
68
69
 
69
- By default all 60 tools are registered. Use `ENABLED_TOOLS` (or `--include-tools` on the CLI) to restrict the server to exactly the tools you need. This reduces the AI's context window cost — each registered tool is a name the model must reason about on every call.
70
+ <!-- Auto-generated by `scripts/gen_tool_docs.py` do not edit by hand, your changes will be overwritten on the next commit. -->
71
+
72
+ By default all 84 tools are registered. Use `ENABLED_TOOLS` (or `--include-tools` on the CLI) to restrict the server to exactly the tools you need. This reduces the AI's context window cost — each registered tool is a name the model must reason about on every call.
70
73
 
71
74
  ```bash
72
75
  # Environment variable
@@ -0,0 +1,63 @@
1
+ # Decision: Response-Size Safety Net for get_sheet_data(include_grid_data=True)
2
+
3
+ **Date:** 2026-07-03
4
+ **Snapshot commit:** branch `feat/issue-235-grid-data-range` — see `src/mcp_gee_sweet/tools/sheets/data.py`
5
+
6
+ ## Background
7
+
8
+ Issue #235: `get_sheet_data(include_grid_data=True)` without a `range` fetches the sheet's full padded grid (often 1000x26 by default, regardless of actual content). In the reported case, the server-side Sheets API call succeeded (`200` in under a second), but the MCP client reported "Connection closed" — the failure was downstream of this server, in the client/transport layer, with no exception for the server to catch.
9
+
10
+ ## Options Considered
11
+
12
+ ### Option A: Require an explicit range
13
+
14
+ Raise a `ValueError` if `include_grid_data=True` and `range` is omitted, forcing the caller to supply a bounded range up front.
15
+
16
+ **Pros:** simplest possible fix; turns a silent transport failure into an immediate, actionable error.
17
+
18
+ **Cons:** pushes the burden onto the caller (typically an LLM) to already know the sheet's used extent before it can safely ask for formatting — usually means an extra round-trip (a values-only call first) just to learn the range, spending tokens on something the server can trivially compute itself. Rejected as poor UX once raised in review.
19
+
20
+ ### Option B: Auto-detect used range + pre-fetch cell-count cap
21
+
22
+ Auto-detect the used range from a values-only probe when `range` is omitted (removes the guesswork), and reject the request before fetching if the resolved range's cell count exceeds a fixed threshold (initially 2000 cells), estimated from the issue's own numbers (150 cells ≈ 107KB, ~713 bytes/cell).
23
+
24
+ **Cons — falsified by live testing:** a live test against a real spreadsheet found cell count doesn't predict response size at all. A 26,000-cell **blank** range (a fresh sheet's default padding) returned almost nothing — Sheets omits `rowData` entirely for cells with no explicit formatting. A 1,300-cell range with real formatting applied (bold, background, number format) returned 983,982 characters and broke the session's own client. A pre-fetch estimate based on range shape can't distinguish these two cases; cutting the cap at any fixed cell count either blocks working requests (blank/lightly-formatted large ranges) or lets truly dangerous ones (small but densely-formatted ranges) through.
25
+
26
+ ### Option C: Auto-detect used range + post-fetch response-size check (chosen)
27
+
28
+ Keep auto-detection (still correct and valuable — it prevents ever requesting formatting for irrelevant padding). Replace the pre-fetch cell-count estimate with a check on the **actual serialized response**, made after the Sheets API call completes (which is fast either way, per the issue's own account) but before returning it through the MCP tool response.
29
+
30
+ A second live-testing round, this time applying real formatting and binary-searching range size against the session's own MCP client, found the actual failure point much lower than the cell-count-based guess would have suggested:
31
+
32
+ | Cells | Characters | Result |
33
+ |---|---|---|
34
+ | 60 | (not captured — succeeded) | ✅ |
35
+ | 65 | 51,208 | ❌ |
36
+ | 75 | 58,784 | ❌ |
37
+ | 100 | 77,694 | ❌ |
38
+ | 500 | 380,494 | ❌ |
39
+ | 1,300 | 983,982 | ❌ |
40
+
41
+ This placed the real limit around 48,000–51,000 characters for that session — which matches Claude Code's documented default of 25,000 tokens per MCP tool response (`MAX_MCP_OUTPUT_TOKENS`), given this densely-nested, repeated-key JSON (`userEnteredFormat`, `backgroundColorStyle`, `rgbColor` on every cell) tokenizes at roughly 2 characters/token rather than the ~4 chars/token typical of prose.
42
+
43
+ Two consequences follow directly from this finding:
44
+
45
+ 1. **The cap has to be measured, not estimated.** There's no way to know from range shape alone how formatted a range's cells are, so the check runs on `len(json.dumps(result))` after the fetch.
46
+ 2. **The cap has to be configurable, not fixed.** `MAX_MCP_OUTPUT_TOKENS` is a client-side setting the server has no visibility into — a different MCP client, or the same client with a raised limit, has a different real threshold. `MAX_GRID_DATA_RESPONSE_CHARS` (default `40000`, chosen with margin below the ~48-51K observed here) lets an operator match their own client's actual configuration.
47
+
48
+ `local_path` (optional file/directory path) was added as an escape hatch that bypasses the cap entirely by writing the result to disk instead of returning it inline — this avoids the client output-token limit altogether, at the cost of the same server-filesystem-locality caveat `download_file`/`download_folder` already carry (meaningful for stdio and shared-volume SSE deployments; not useful for a remote SSE client with no shared filesystem).
49
+
50
+ ## Decision
51
+
52
+ **Use Option C.** Auto-detect the used range when omitted; check the actual serialized response size after fetching, not an estimate beforehand; make the cap configurable via `MAX_GRID_DATA_RESPONSE_CHARS`; offer `local_path` as an unconditional bypass.
53
+
54
+ The chosen default (`40000` characters) is explicitly documented in code as *one environment's observed limit, not a universal constant* — it should not be read as an authoritative number for all MCP clients.
55
+
56
+ ## Follow-up
57
+
58
+ Issue #242 tracks generalizing this pattern (post-fetch size check + configurable cap + `local_path` bypass) to other tools that can return large responses (`export_file`, `get_doc_content`, `get_multiple_sheet_data`, etc.) — out of scope for #235's fix, which is deliberately scoped to the one tool named in the bug report. That follow-up also needs to address that `MAX_GRID_DATA_RESPONSE_CHARS` was named for its one-tool scope and won't generalize as-is.
59
+
60
+ ## When to Re-evaluate
61
+
62
+ - If #242 generalizes this pattern, this decision's env var naming and per-tool scoping should be revisited together, not left as a precedent copied ad hoc.
63
+ - If Claude Code's `MAX_MCP_OUTPUT_TOKENS` default changes, or another MCP client with a materially different limit becomes a primary consumer of this server, the default cap value should be re-measured rather than assumed to still be well-calibrated.
@@ -10,3 +10,4 @@ Point-in-time records of specific decisions — context, alternatives, and reaso
10
10
  | [Publishing Strategy](decision-publishing.md) | 2026-05-23 | draft | Two-track PyPI release (stable + edge); version injection open |
11
11
  | [Docs Formatting Architecture](decision-docs-formatting.md) | 2026-06-17 | decided | Two-layer approach (direct API tools + HTML abstraction via AST); phase plan for #41, #65–#70, #79–#82 |
12
12
  | [Chart Theming and Phase 3 Integration](decision-chart-theming.md) | 2026-06-17 | decided | No parsing layer for charts; no Vega-Lite adoption; unified brand config (docs + charts sections) for Phase 3 theme system |
13
+ | [Grid Data Size Cap](decision-grid-data-size-cap.md) | 2026-07-03 | decided | Post-fetch response-size check (not pre-fetch cell-count estimate) for `get_sheet_data(include_grid_data=True)`; configurable cap + `local_path` bypass; empirically falsified two earlier approaches |
@@ -19,6 +19,7 @@ Most infrastructure behaviours are verified by unit tests rather than live QA pr
19
19
  | TC-I15 (hot reload) | Manual / live QA only — known uvicorn + SSE limitation, observe and note |
20
20
  | TC-I16–I20 (logging) | ✅ Already live-tested and passed — see Result entries below |
21
21
  | DB recovery (issue #212) | Unit-tested in `tests/test_cache.py` `TestOpenFallback` — read-only file, read-only dir, and `:memory:` fallback all covered |
22
+ | Tool doc generation (issue #94) | `scripts/gen_tool_docs.py` is a build-time/pre-commit script, not an MCP tool — no live prompt applies. Unit-tested in `tests/test_gen_tool_docs.py`: every registered tool is covered by a section and has a docstring, subset validation catches unknown tool names, and `main()` is idempotent on a second run |
22
23
 
23
24
  ---
24
25
 
@@ -34,14 +34,78 @@ Fixtures: see [`docs/qa/setup.md`](../setup.md). Substitute your `{SPREADSHEET_I
34
34
 
35
35
  ---
36
36
 
37
- ### TC-R03: Grid data with formatting
37
+ ### TC-R03: Grid data with an explicit range
38
38
 
39
39
  **Prompt**
40
- > "Get the Sales sheet from {SPREADSHEET_ID} with full grid data including formatting"
40
+ > "Get A1:D6 from the Sales sheet of {SPREADSHEET_ID} with full grid data including formatting"
41
41
 
42
42
  **Checks**
43
43
  - Response includes `rowData` field
44
44
  - `include_grid_data=True` was passed to the API (visible in raw response structure)
45
+ - Call includes `range="A1:D6"` — no auto-detection probe request happens when a range is given
46
+
47
+ ---
48
+
49
+ ### TC-R03b: Grid data without a range auto-detects the used range (issue #235)
50
+
51
+ **Setup**
52
+ Call `get_sheet_data(spreadsheet_id={SPREADSHEET_ID}, sheet="Sales", include_grid_data=True)` — no `range` argument. The Sales sheet has 6 rows x 4 columns of actual content but Sheets' default padded grid is 1000x26.
53
+
54
+ **Checks**
55
+ - No error — the call succeeds
56
+ - Server makes a values-only probe request first, then a grid-data request scoped to `A1:D6` — not the full padded grid
57
+ - Response includes `rowData` only for the 6x4 used range, not ~26,000 padded cells
58
+ - No "Connection closed" / oversized-response symptom (the original failure mode)
59
+
60
+ **Result (2026-07-02) ✅ PASS**
61
+ Called live against the actual Sales fixture (`gridProperties: rowCount=3016, columnCount=33`). Response's `rowData` covered exactly the 6x4 used range (header row + Widget/Gadget/Donut/Gizmo + Totals row, matching TC-R01's known fixture content) — confirmed scoped down from the sheet's real 3016x33 padding, not just the smaller 1000x26 default. No error, no truncation.
62
+
63
+ ---
64
+
65
+ ### TC-R03c: Densely formatted range over the safety cap raises a clear error (issue #235)
66
+
67
+ **Setup**
68
+ Apply formatting across a range with `format_cells(spreadsheet_id={SPREADSHEET_ID}, sheet="Sales", range="A1:Z200", bold=True, background_color={"red": 0.9, "green": 0.95, "blue": 1}, number_format_type="NUMBER")`, then call `get_sheet_data(spreadsheet_id={SPREADSHEET_ID}, sheet="Sales", range="A1:Z200", include_grid_data=True)`.
69
+
70
+ Note: cell *count* alone doesn't predict this — a live test found a 26,000-cell blank range costs almost nothing (Sheets omits `rowData` for untouched cells), while densely-formatted ranges cost ~700-780 bytes/cell. The cap check runs on the actual serialized response, after the fetch, not an estimate from range size. A live binary search against Claude Code's default MCP client found the real failure point around 48,000-51,000 characters — consistent with Claude Code's documented 25,000-token default per-tool-response cap (`MAX_MCP_OUTPUT_TOKENS`) at ~2 chars/token for this kind of repeated-key JSON. The server-side default (`MAX_GRID_DATA_RESPONSE_CHARS=40000`) sits below that with margin.
71
+
72
+ **Checks**
73
+ - The Sheets API fetch itself succeeds (it's fast either way) — the error comes from the size check on the result, not a failed API call
74
+ - Error message states the actual response size in characters, the cap, and mentions narrowing the range, `local_path`, or `MAX_GRID_DATA_RESPONSE_CHARS` as options
75
+ - Clean up: remove the formatting from A1:Z200 afterward so it doesn't affect other test cases
76
+
77
+ **Result (2026-07-02) ✅ PASS**
78
+ Tested against a scratch sheet (temp tab, deleted after) rather than formatting the shared Sales fixture directly, to avoid leaving unwanted formatting on it — same effective setup (`format_cells(range="A1:Z1000", bold=True, background_color=..., number_format_type="NUMBER")`, then `get_sheet_data(range="A1:Z200", include_grid_data=True)`). Raised:
79
+ `get_sheet_data(include_grid_data=True): the response is 4325100 characters, over the 40000-character safety cap. ... Narrow the range; pass local_path to write the result to disk instead of returning it inline (bypasses this cap); or set MAX_GRID_DATA_RESPONSE_CHARS if your MCP client can handle larger responses (e.g. a raised MAX_MCP_OUTPUT_TOKENS).`
80
+ Exact size (4,325,100 chars) confirms the fetch completed before the check ran, as designed.
81
+
82
+ ---
83
+
84
+ ### TC-R03d: Grid data with local_path writes to disk instead of returning inline (issue #235)
85
+
86
+ **Setup**
87
+ Same formatted range as TC-R03c, but call `get_sheet_data(spreadsheet_id={SPREADSHEET_ID}, sheet="Sales", range="A1:Z200", include_grid_data=True, local_path="/tmp/qa_grid_data.json")`.
88
+
89
+ **Checks**
90
+ - Call succeeds (no cap error) despite the response exceeding the cap
91
+ - Response is a small dict — `local_path`, `spreadsheet_id`, `sheet`, `range`, `bytes_written` — not the grid data itself
92
+ - `/tmp/qa_grid_data.json` exists and contains the full grid-data JSON (`rowData` present, formatting visible)
93
+
94
+ **Result (2026-07-02) ✅ PASS**
95
+ Same scratch-sheet setup as TC-R03c. Call returned `{"local_path": "/tmp/qa_grid_data_235.json", "spreadsheet_id": "...", "sheet": "SizeTest235d", "range": "SizeTest235d!A1:Z200", "bytes_written": 4325100}` — no error despite exceeding the cap. Verified on disk: file exists, `wc -c` matches `bytes_written` exactly (4,325,100), and contains real `rowData` with formatting. Scratch sheet and temp file both cleaned up afterward.
96
+
97
+ ---
98
+
99
+ ### TC-R03e: MAX_GRID_DATA_RESPONSE_CHARS raises the cap (issue #235)
100
+
101
+ **Setup**
102
+ Set `MAX_GRID_DATA_RESPONSE_CHARS=200000` in server config and restart the server (e.g. to match a `MAX_MCP_OUTPUT_TOKENS` raised in your MCP client config). Repeat the same call as TC-R03c: a formatted range whose response is over the *default* 40,000-character cap but under 200,000.
103
+
104
+ **Checks**
105
+ - Call now succeeds instead of raising — confirms the cap is actually read from the env var, not hardcoded
106
+ - Restore `MAX_GRID_DATA_RESPONSE_CHARS` (unset it) after this test
107
+
108
+ **Result:** ⏳ Pending — not yet live-tested. Unlike TC-R03b-d, this requires a server restart with a changed env var (not just an MCP reconnect), which wasn't done this pass. Covered by unit tests (`test_cap_is_configurable`, `test_env_var_sets_cap_at_import_time` in `tests/sheets/test_data.py`) in the meantime.
45
109
 
46
110
  ---
47
111
 
@@ -32,8 +32,9 @@ Features are ordered by practical priority within each tier, cross-domain. Items
32
32
  | Version | Scope | Signal |
33
33
  |---------|-------|--------|
34
34
  | **v0.7.0** | ✅ First stable PyPI release — 63 tools across Sheets, Drive, Docs, Calendar | Published 2026-06-21 |
35
- | **v0.8.0** | Tier 1 complete — all "frequently needed" items across all domains (~14 tools) | "Complete for everyday use" |
36
- | **v0.9.0** | Tier 2 completepower-user and structured-work layer (~20 tools) | Covers most real workflows |
35
+ | **v0.8.0** | Tier 1 complete — all "frequently needed" items across all domains (84 tools) | Published 2026-06-29 |
36
+ | **v0.8.1** | Defect & documentation cleanup no new tools, ships the QA/refactor work already on `develop` plus fixes for #235, #213, #239, #236 | Stabilizes before Tier 2 feature work begins |
37
+ | **v0.9.0** | Tier 2 complete — power-user and structured-work layer, features only (~20 tools) | Covers most real workflows |
37
38
  | **v1.0.0** | API stability declaration — Tier 3 items that make the cut + any breaking cleanups from v0.8–0.9 | Backwards-compatibility commitment |
38
39
  | **v1.1.0+** | Future domains — Tasks, Gmail (separate minor releases, each needs a new API client) | Expanded scope |
39
40
 
@@ -70,7 +71,26 @@ Tier 4 items remain backlog with no assigned version.
70
71
  - [x] `list_recent_files` — files recently accessed or modified ([#136](https://github.com/khuisman/mcp-gee-sweet/issues/136))
71
72
  - [x] `get_storage_quota` — Drive storage usage and limits ([#137](https://github.com/khuisman/mcp-gee-sweet/issues/137))
72
73
 
73
- ### Tier 2 Useful for structured work _(target: v0.9.0)_
74
+ ### v0.8.1Defect & documentation cleanup _(target: v0.8.1, before Tier 2 begins)_
75
+
76
+ No new tools. Stabilize on what Tier 1 shipped before starting Tier 2 feature work.
77
+
78
+ **Already on `develop`, awaiting release**
79
+ - [x] Playwright per-test tagging policy, conductor prompt improvements (PR #228)
80
+ - [x] QA test case fixes — `share_file` fixtures, TC-D148 setup, Playwright tags (PR #229)
81
+ - [x] SQLite cache defensive recovery from read-only/corrupted DB files at connect time (PR #230)
82
+ - [x] `tools/docs/__init__.py` split into `content.py`/`tables.py`/`style.py`/`layout.py` (PR #232)
83
+
84
+ **Remaining defects to fix before cutting the release**
85
+ - [ ] `get_sheet_data(include_grid_data=True)` without a range fetches the full padded grid instead of the used range ([#235](https://github.com/khuisman/mcp-gee-sweet/issues/235))
86
+ - [ ] `create_doc_from_file` markdown `$` escape renders as literal backslash+dollar in the doc ([#213](https://github.com/khuisman/mcp-gee-sweet/issues/213))
87
+ - [ ] Unknown tool parameters are silently dropped instead of raising a validation error — FastMCP's generated pydantic arg models default to ignoring extra fields, so a typo'd kwarg (e.g. `parent_id` instead of `parent_folder_id`) silently falls back to default behavior instead of erroring ([#239](https://github.com/khuisman/mcp-gee-sweet/issues/239))
88
+ - [x] Auto-generate `docs/tools.md` from tool source as a pre-commit hook — makes the manual tools.md backfill in #236 unnecessary (PR #240) ([#94](https://github.com/khuisman/mcp-gee-sweet/issues/94))
89
+ - [ ] Correct `docs/roadmap.md`'s own tool catalog counts, `known-limitations.md`'s `list_all_events` entry, and rework README's/`docs/client-setup.md`'s Configuration examples to lead with OAuth (not service account) for local/dev quick-start — currently mismatches the project's own auth priority order and PyPI's page inherits it verbatim ([#236](https://github.com/khuisman/mcp-gee-sweet/issues/236))
90
+ - [ ] Rewrite `CONTRIBUTING.md` — fix broken links to removed README anchors, fix stale "Available Tool Names" and `bug`-label references, add better local-dev setup examples including observability (`DEBUG_LEVEL`/`LOG_FILE`/`ACCESS_LOG_FILE`) ([#95](https://github.com/khuisman/mcp-gee-sweet/issues/95))
91
+ - [ ] Define and document community PR expectations — template, testing bar for non-tool PRs, scope/size convention, CLA/DCO/code-of-conduct decision, review turnaround ([#237](https://github.com/khuisman/mcp-gee-sweet/issues/237))
92
+
93
+ ### Tier 2 — Useful for structured work, features only _(target: v0.9.0)_
74
94
 
75
95
  **Sheets**
76
96
  - [ ] `update_borders` — border style, width, color on a range ([#122](https://github.com/khuisman/mcp-gee-sweet/issues/122)) _(freema/mcp-gsheets)_
@@ -79,18 +99,21 @@ Tier 4 items remain backlog with no assigned version.
79
99
  - [ ] `add_data_validation` / `get_data_validation` — dropdowns, checkboxes, value constraints ([#125](https://github.com/khuisman/mcp-gee-sweet/issues/125)) _(freema/mcp-gsheets)_
80
100
  - [ ] `update_sheet_properties` — tab color, hide/show gridlines ([#126](https://github.com/khuisman/mcp-gee-sweet/issues/126)) _(freema/mcp-gsheets)_
81
101
  - [ ] `duplicate_sheet` — copy a sheet within the same spreadsheet ([#127](https://github.com/khuisman/mcp-gee-sweet/issues/127))
102
+ - [ ] Partial (rich-text) hyperlinks in `update_cells` ([#89](https://github.com/khuisman/mcp-gee-sweet/issues/89))
103
+ - [ ] `import_csv_to_sheet` — populate a spreadsheet from a local CSV file ([#187](https://github.com/khuisman/mcp-gee-sweet/issues/187))
82
104
 
83
105
  **Calendar**
84
106
  - [ ] `create_calendar` / `update_calendar` / `delete_calendar` — calendar lifecycle ([#156](https://github.com/khuisman/mcp-gee-sweet/issues/156))
85
107
  - [ ] `add_calendar_to_list` / `remove_calendar_from_list` — subscribe/unsubscribe ([#157](https://github.com/khuisman/mcp-gee-sweet/issues/157))
86
108
  - [ ] Calendar ACL — share a calendar with users or groups ([#158](https://github.com/khuisman/mcp-gee-sweet/issues/158))
109
+ - [ ] `list_all_events` — query all subscribed calendars in parallel _(decision needed — also tracked in Decisions Needed)_ ([#194](https://github.com/khuisman/mcp-gee-sweet/issues/194))
87
110
 
88
111
  **Docs**
89
112
  - [ ] `insert_page_break` — explicit page break at an index ([#148](https://github.com/khuisman/mcp-gee-sweet/issues/148))
90
113
  - [ ] `merge_table_cells` — merge cells in an existing table (distinct from write-time colspan) ([#150](https://github.com/khuisman/mcp-gee-sweet/issues/150))
91
114
  - [ ] Comments API — list, add, resolve doc comments ([#151](https://github.com/khuisman/mcp-gee-sweet/issues/151))
92
115
  - [ ] `create_named_range` / `create_bookmark` — anchor points for internal links ([#152](https://github.com/khuisman/mcp-gee-sweet/issues/152))
93
- - [ ] Rowspan emitter — AST `Cell.rowspan` field exists but emitter does not yet merge rows
116
+ - [ ] Rowspan emitter — AST `Cell.rowspan` field exists but emitter does not yet merge rows ([#195](https://github.com/khuisman/mcp-gee-sweet/issues/195))
94
117
  - [ ] Warn/error on mixed text + nested table in same cell ([#108](https://github.com/khuisman/mcp-gee-sweet/issues/108))
95
118
  - [ ] colspan/rowspan inside nested tables ([#109](https://github.com/khuisman/mcp-gee-sweet/issues/109))
96
119
 
@@ -99,8 +122,13 @@ Tier 4 items remain backlog with no assigned version.
99
122
  - [ ] `star_file` / `unstar_file` — mark files with a star for easy retrieval ([#139](https://github.com/khuisman/mcp-gee-sweet/issues/139))
100
123
  - [ ] `transfer_ownership` — transfer a file to another user ([#140](https://github.com/khuisman/mcp-gee-sweet/issues/140))
101
124
  - [ ] `create_shortcut` — create a Drive shortcut to a file ([#141](https://github.com/khuisman/mcp-gee-sweet/issues/141))
125
+ - [ ] `sync_folder` — option to convert markdown files to Google Docs on upload ([#211](https://github.com/khuisman/mcp-gee-sweet/issues/211))
126
+ - [ ] `upload_local_file` with Drive format conversion (CSV→Sheets, MD→Docs) ([#188](https://github.com/khuisman/mcp-gee-sweet/issues/188))
102
127
  - [x] Drive Activity API — file change history ([#72](https://github.com/khuisman/mcp-gee-sweet/issues/72))
103
128
 
129
+ **Infrastructure**
130
+ - [ ] Cache reliability & configurability — runtime-configurable TTL, smarter invalidation for shared files ([#99](https://github.com/khuisman/mcp-gee-sweet/issues/99)); harden concurrent-session access — fail-open on cache read/write errors, `busy_timeout` ([#234](https://github.com/khuisman/mcp-gee-sweet/issues/234))
131
+
104
132
  ### Tier 3 — Advanced / occasionally needed _(target: v1.0.0)_
105
133
 
106
134
  **Sheets**
@@ -138,6 +166,23 @@ Tier 4 items remain backlog with no assigned version.
138
166
  - [ ] Working location / OOO / Focus time events ([#164](https://github.com/khuisman/mcp-gee-sweet/issues/164))
139
167
  - [ ] Watch notifications — webhook push on calendar or event changes ([#165](https://github.com/khuisman/mcp-gee-sweet/issues/165))
140
168
 
169
+ ### Decisions Needed — blocking backlog _(no assigned version)_
170
+
171
+ Design questions raised during Phase 1 QA that need a deliberate product/API decision rather than just code. Each blocks whichever tool it names until resolved — pick these up opportunistically alongside the tool they affect, or dedicate a pass to clear the backlog.
172
+
173
+ - [ ] `get_sheet_data` on an unknown sheet — exception vs structured error ([#31](https://github.com/khuisman/mcp-gee-sweet/issues/31))
174
+ - [ ] `batch_update_cells` empty input — validation error vs silent no-op ([#32](https://github.com/khuisman/mcp-gee-sweet/issues/32))
175
+ - [ ] `add_rows` count cap — accept unlimited or enforce a maximum ([#33](https://github.com/khuisman/mcp-gee-sweet/issues/33))
176
+ - [ ] `rename_sheet` same-name — skip the API call or always round-trip ([#34](https://github.com/khuisman/mcp-gee-sweet/issues/34))
177
+ - [ ] `create_sheet` duplicate title — API error or auto-suffix ([#35](https://github.com/khuisman/mcp-gee-sweet/issues/35))
178
+ - [ ] Hide Drive-create tools from the toolset when service account auth is detected ([#36](https://github.com/khuisman/mcp-gee-sweet/issues/36))
179
+ - [ ] `list_spreadsheets` with no folder — all accessible or Drive root ([#37](https://github.com/khuisman/mcp-gee-sweet/issues/37))
180
+ - [ ] Validate ownership before `share_spreadsheet` / `share_file` ([#38](https://github.com/khuisman/mcp-gee-sweet/issues/38))
181
+ - [ ] `search_spreadsheets` empty query — return all or require non-empty input ([#39](https://github.com/khuisman/mcp-gee-sweet/issues/39))
182
+ - [ ] `get_file_metadata` size field for Workspace files — normalize or document ([#40](https://github.com/khuisman/mcp-gee-sweet/issues/40))
183
+ - [ ] `rows_to_fetch=0` — clamp to 1 or return all rows ([#78](https://github.com/khuisman/mcp-gee-sweet/issues/78))
184
+ - [ ] `list_all_events` — how to handle per-calendar failures when querying in parallel (partial results vs fail-fast) ([#194](https://github.com/khuisman/mcp-gee-sweet/issues/194))
185
+
141
186
  ---
142
187
 
143
188
  ## Bugs found in Phase 1 QA run (2026-06-02)
@@ -183,6 +228,7 @@ The test expected the API to silently truncate a 2D array that's wider than the
183
228
  - [x] Google Docs tools (all 13) — `docs.md`
184
229
  - [x] Calendar tools — `calendar.md`
185
230
  - [x] Infrastructure — `infra.md`
231
+ - [ ] QA gaps targeted for v0.9 — second Drive folder fixture ([#44](https://github.com/khuisman/mcp-gee-sweet/issues/44)), shared drive coverage ([#48](https://github.com/khuisman/mcp-gee-sweet/issues/48)), domain/public sharing coverage ([#49](https://github.com/khuisman/mcp-gee-sweet/issues/49)), local-filesystem coverage ([#50](https://github.com/khuisman/mcp-gee-sweet/issues/50)), environment-constraint coverage ([#53](https://github.com/khuisman/mcp-gee-sweet/issues/53)), image fixtures for `insert_inline_image` ([#224](https://github.com/khuisman/mcp-gee-sweet/issues/224)), dedicated QA Google account ([#225](https://github.com/khuisman/mcp-gee-sweet/issues/225)), OAuth all-scopes token for faster setup ([#226](https://github.com/khuisman/mcp-gee-sweet/issues/226)), disposition of v0.8.0 SKIP entries ([#227](https://github.com/khuisman/mcp-gee-sweet/issues/227))
186
232
  - See [GitHub Issues (label: qa)](https://github.com/khuisman/mcp-gee-sweet/issues?q=label%3Aqa) for open QA gaps and fixture issues
187
233
 
188
234
  ---
@@ -0,0 +1,168 @@
1
+ # Tool Reference
2
+
3
+ <!-- Auto-generated by `scripts/gen_tool_docs.py` — do not edit by hand, your changes will be overwritten on the next commit. -->
4
+
5
+ 84 tools. All tool names are lowercase with underscores — use these exact strings with `ENABLED_TOOLS` or `--include-tools`.
6
+
7
+ ## Sheets — data
8
+
9
+ | Tool | Description | Key parameters |
10
+ |---|---|---|
11
+ | `get_sheet_data` | Get data from a specific sheet in a Google Spreadsheet. | `spreadsheet_id`, `sheet`, `range?`, `include_grid_data?`, `local_path?` |
12
+ | `get_sheet_formulas` | Get formulas from a specific sheet in a Google Spreadsheet. | `spreadsheet_id`, `sheet`, `range?` |
13
+ | `get_multiple_sheet_data` | Get data from multiple specific ranges in Google Spreadsheets. | `queries` |
14
+ | `get_multiple_spreadsheet_summary` | Get a summary of multiple Google Spreadsheets, including sheet names, headers, and the first few rows of data for each sheet. | `spreadsheet_ids`, `rows_to_fetch?` |
15
+ | `find_in_spreadsheet` | Find cells containing a specific value in a Google Spreadsheet. | `spreadsheet_id`, `query`, `sheet?`, `case_sensitive?`, `max_results?` |
16
+ | `clear_values` | Clear cell content in a range without touching formatting. | `spreadsheet_id`, `sheet`, `range?` |
17
+ | `update_cells` | Update cells in a Google Spreadsheet. | `spreadsheet_id`, `sheet`, `range`, `data` |
18
+ | `batch_update_cells` | Batch update multiple ranges in a Google Spreadsheet. | `spreadsheet_id`, `sheet`, `ranges` |
19
+ | `batch_update` | Execute a batch update on a Google Spreadsheet using the full batchUpdate endpoint. This provides access to all batchUpdate operations including adding sheets, updating properties, inserting/deleting dimensions, formatting, and more. | `spreadsheet_id`, `requests` |
20
+
21
+ `batch_update` is the escape hatch for anything not covered by a named tool (formatting, conditional formatting, dimension properties, etc.).
22
+
23
+ ## Sheets — structure
24
+
25
+ | Tool | Description | Key parameters |
26
+ |---|---|---|
27
+ | `list_sheets` | List all sheets in a Google Spreadsheet. | `spreadsheet_id` |
28
+ | `copy_sheet` | Copy a sheet from one spreadsheet to another. | `src_spreadsheet`, `src_sheet`, `dst_spreadsheet`, `dst_sheet` |
29
+ | `rename_sheet` | Rename a sheet in a Google Spreadsheet. | `spreadsheet`, `sheet`, `new_name` |
30
+ | `create_sheet` | Create a new sheet tab in an existing Google Spreadsheet. | `spreadsheet_id`, `title` |
31
+ | `add_rows` | Add rows to a sheet in a Google Spreadsheet. | `spreadsheet_id`, `sheet`, `count`, `start_row?` |
32
+ | `add_columns` | Add columns to a sheet in a Google Spreadsheet. | `spreadsheet_id`, `sheet`, `count`, `start_column?` |
33
+ | `delete_sheet` | Delete a sheet tab from a Google Spreadsheet. | `spreadsheet_id`, `sheet` |
34
+ | `delete_rows` | Delete rows from a sheet. | `spreadsheet_id`, `sheet`, `start_row`, `end_row?` |
35
+ | `delete_columns` | Delete columns from a sheet. | `spreadsheet_id`, `sheet`, `start_column`, `end_column?` |
36
+ | `format_cells` | Apply formatting to a cell range (background color, font, alignment, number format). | `spreadsheet_id`, `sheet`, `range`, `bold?`, `italic?`, `strikethrough?`, `font_size?`, `font_color?`, `background_color?`, `horizontal_alignment?`, `vertical_alignment?`, `number_format_type?`, `number_format_pattern?` |
37
+ | `merge_cells` | Merge a range of cells into one cell. | `spreadsheet_id`, `sheet`, `range`, `merge_type?` |
38
+ | `unmerge_cells` | Unmerge all merged cells in a range. | `spreadsheet_id`, `sheet`, `range` |
39
+ | `freeze` | Freeze rows and/or columns in a sheet. | `spreadsheet_id`, `sheet`, `rows?`, `columns?` |
40
+ | `sort_range` | Sort a range by one or more columns. | `spreadsheet_id`, `sheet`, `range`, `sort_order?` |
41
+ | `add_chart` | Add a chart to a Google Spreadsheet. | `spreadsheet_id`, `sheet`, `chart_type`, `data_range`, `title?`, `x_axis_label?`, `y_axis_label?`, `position_x?`, `position_y?`, `width?`, `height?` |
42
+
43
+ `add_chart` supports types: `COLUMN`, `BAR`, `LINE`, `AREA`, `PIE`, `SCATTER`, `COMBO`, `HISTOGRAM`.
44
+
45
+ ## Spreadsheets (Drive-level)
46
+
47
+ | Tool | Description | Key parameters |
48
+ |---|---|---|
49
+ | `create_spreadsheet` | Create a new Google Spreadsheet. | `title`, `folder_id?` |
50
+ | `list_spreadsheets` | List all spreadsheets in the specified Google Drive folder. If no folder is specified, uses the configured default folder or lists from 'My Drive'. | `folder_id?` |
51
+ | `search_spreadsheets` | Search for spreadsheets in Google Drive by name or content. | `query`, `max_results?` |
52
+
53
+ `create_spreadsheet` cannot create files in a personal Drive when using service account auth — use OAuth or a Shared Drive.
54
+
55
+ ## Drive — files
56
+
57
+ | Tool | Description | Key parameters |
58
+ |---|---|---|
59
+ | `list_folders` | List all folders in the specified Google Drive folder. If no parent folder is specified, lists folders from 'My Drive' root. | `parent_folder_id?` |
60
+ | `list_drives` | List shared (Team) Drives accessible to the authenticated account. | `query?`, `max_results?` |
61
+ | `list_files` | List files in a Google Drive folder, optionally filtered by MIME type. | `folder_id`, `mime_type?`, `max_results?` |
62
+ | `search_files` | Search for files in Google Drive by name or content. | `query`, `mime_type?`, `folder_id?`, `max_results?` |
63
+ | `get_file_metadata` | Get metadata for any file or folder in Google Drive. | `file_id` |
64
+ | `create_folder` | Create a new folder in Google Drive. | `name`, `parent_folder_id?` |
65
+ | `copy_file` | Copy a file in Google Drive. | `file_id`, `new_name?`, `folder_id?` |
66
+ | `move_file` | Move a file or folder to a different folder in Google Drive. | `file_id`, `destination_folder_id` |
67
+ | `rename_file` | Rename a file or folder in Google Drive. | `file_id`, `new_name` |
68
+ | `list_shared_with_me` | List files explicitly shared with the authenticated user. | `mime_type?`, `max_results?` |
69
+ | `list_recent_files` | List recently modified files in Drive, newest first. | `max_results?`, `days?`, `mime_type?` |
70
+ | `get_storage_quota` | Get Drive storage usage and limits for the authenticated account. | — |
71
+ | `delete_file` | Move a file to the trash or permanently delete it. | `file_id`, `permanent?` |
72
+
73
+ ## Drive — transfer
74
+
75
+ | Tool | Description | Key parameters |
76
+ |---|---|---|
77
+ | `export_file` | Export or download a file from Google Drive. | `file_id`, `export_format` |
78
+ | `list_revisions` | List available revisions for a Google Drive file (Sheets, Docs, or any file). | `file_id` |
79
+ | `export_revision` | Export a historical revision of a Google Sheets file and return its cell data. | `file_id`, `revision_id`, `range?`, `sheet?` |
80
+ | `upload_file` | Upload a text file to Google Drive, optionally converting it to a Google Doc. | `name`, `content`, `source_format?`, `folder_id?`, `convert_to_doc?` |
81
+ | `upload_local_file` | Upload a file from the local filesystem to a Google Drive folder. Handles binary and text files (images, PDFs, DOCX, XLSX, scripts, etc.). | `local_path`, `parent_folder_id`, `name?`, `skip_if_exists?` |
82
+ | `upload_local_folder` | Upload all files in a local directory (non-recursive) to a Google Drive folder. | `local_path`, `parent_folder_id`, `skip_if_exists?`, `skip_system_files?` |
83
+ | `download_file` | Download a file from Google Drive to the local filesystem. | `file_id`, `local_path`, `export_format?` |
84
+ | `download_folder` | Download all files in a Google Drive folder (non-recursive) to a local directory. | `folder_id`, `local_path`, `export_format?`, `mime_type_filter?`, `skip_if_exists?` |
85
+ | `sync_folder` | Sync files between a Google Drive folder and a local directory. | `folder_id`, `local_path`, `direction?`, `export_format?`, `skip_system_files?`, `dry_run?` |
86
+
87
+ ## Drive — sharing
88
+
89
+ | Tool | Description | Key parameters |
90
+ |---|---|---|
91
+ | `share_spreadsheet` | Share a Google Spreadsheet with multiple users via email, assigning specific roles. | `spreadsheet_id`, `recipients`, `send_notification?` |
92
+ | `list_permissions` | List all permissions on a file or folder in Google Drive. | `file_id` |
93
+ | `update_permission` | Change the role on an existing permission for a file or folder. | `file_id`, `permission_id`, `role` |
94
+ | `remove_permission` | Revoke a permission on a file or folder. | `file_id`, `permission_id` |
95
+ | `share_file` | Share any file or folder with one or more principals. | `file_id`, `permissions`, `send_notification?` |
96
+
97
+ `share_spreadsheet`'s `recipients` is a list of `{email_address, role}` objects. Valid roles: `reader`, `commenter`, `writer`, `owner`.
98
+
99
+ `share_file`'s `permissions` is a list of `{type, role, ...}` objects — `type` is `user`, `group`, `domain`, or `anyone`; `user`/`group` also need `email_address`, `domain` needs `domain`. Valid roles: `reader`, `commenter`, `writer`.
100
+
101
+ ## Drive — activity
102
+
103
+ | Tool | Description | Key parameters |
104
+ |---|---|---|
105
+ | `list_file_activity` | Return the activity timeline for a Google Drive file. | `file_id`, `page_size?`, `page_token?` |
106
+
107
+ Requires the `drive.activity.readonly` scope; uses the Drive Activity API v2.
108
+
109
+ ## Docs
110
+
111
+ | Tool | Description | Key parameters |
112
+ |---|---|---|
113
+ | `create_doc` | Create a new Google Doc, optionally with initial content. | `title`, `content?`, `folder_id?`, `content_format?` |
114
+ | `create_doc_from_file` | Create a Google Doc from a local .md or .html file. | `local_path`, `title?`, `folder_id?` |
115
+ | `get_doc_content` | Get the plain text content of a Google Doc. | `file_id` |
116
+ | `write_doc_content` | Replace the full content of an existing Google Doc. | `doc_id`, `content`, `content_format?` |
117
+ | `get_doc_structure` | Return the full structural map of a Google Doc body with element indices. | `doc_id` |
118
+ | `insert_doc_text` | Insert text at one or more positions in a Google Doc. | `doc_id`, `insertions` |
119
+ | `delete_doc_range` | Delete one or more content ranges from a Google Doc. | `doc_id`, `deletions` |
120
+ | `insert_inline_image` | Insert an inline image at a specific index in a Google Doc. | `doc_id`, `index`, `uri?`, `drive_file_id?`, `width?`, `height?` |
121
+ | `insert_doc_table` | Insert an empty table at a specific position in a Google Doc. | `doc_id`, `index`, `rows`, `columns` |
122
+ | `insert_table_row` | Insert a row into an existing table in a Google Doc. | `doc_id`, `table_start_index`, `row_index`, `insert_below?` |
123
+ | `delete_table_row` | Delete a row from an existing table in a Google Doc. | `doc_id`, `table_start_index`, `row_index` |
124
+ | `insert_table_column` | Insert a column into an existing table in a Google Doc. | `doc_id`, `table_start_index`, `column_index`, `insert_right?` |
125
+ | `delete_table_column` | Delete a column from an existing table in a Google Doc. | `doc_id`, `table_start_index`, `column_index` |
126
+ | `style_doc_range` | Apply paragraph and/or text styles to one or more index ranges in a Google Doc. | `doc_id`, `ranges` |
127
+ | `style_doc_table_cells` | Apply border, padding, and background styles to table cells in a Google Doc. | `doc_id`, `table_start_index`, `cells` |
128
+ | `get_doc_theme` | Derive a theme dict from a Google Doc's actual paragraph styles. | `doc_id` |
129
+ | `get_doc_named_styles` | Read a Google Doc's named style defaults and return them as a theme dict. | `doc_id` |
130
+ | `apply_theme` | Apply a theme dict to a Google Doc by updating its named style definitions. | `doc_id`, `theme`, `overwrite?` |
131
+ | `create_header` | Add a page header to a Google Doc. | `doc_id`, `header_type?`, `content?` |
132
+ | `create_footer` | Add a page footer to a Google Doc. | `doc_id`, `footer_type?`, `content?` |
133
+
134
+ `write_doc_content` accepts HTML and converts it to Docs API requests via the HTML→AST→emitter pipeline. See [Docs AST Pipeline](design/docs-ast-pipeline.md) for the design.
135
+
136
+ `create_doc` cannot create files in a personal Drive when using service account auth — see [Authentication](auth.md#method-a-service-account-recommended-for-servers).
137
+
138
+ ## Calendar
139
+
140
+ | Tool | Description | Key parameters |
141
+ |---|---|---|
142
+ | `list_calendars` | List all calendars accessible to the authenticated user. | — |
143
+ | `get_calendar` | Fetch metadata for a single calendar. | `calendar_id` |
144
+ | `list_events` | List events in a calendar. | `calendar_id`, `time_min?`, `time_max?`, `query?`, `max_results?`, `expand_recurring?` |
145
+ | `get_event` | Fetch a single event by calendar ID and event ID. | `calendar_id`, `event_id` |
146
+ | `create_event` | Create a new event in a calendar. | `calendar_id`, `summary`, `start`, `end`, `description?`, `location?`, `attendees?`, `timezone?`, `recurrence?` |
147
+ | `update_event` | Update fields on an existing event using a partial update (patch semantics). Only the fields you provide are changed; omitted fields are left as-is. | `calendar_id`, `event_id`, `summary?`, `start?`, `end?`, `description?`, `location?`, `attendees?`, `timezone?`, `recurrence?` |
148
+ | `delete_event` | Delete or cancel an event. | `calendar_id`, `event_id` |
149
+ | `find_free_slots` | Query busy times for a list of calendars and return free slots within the window. | `calendar_ids`, `time_min`, `time_max`, `timezone?` |
150
+
151
+ **Note:** when using service account auth, calendars must be explicitly shared with the service account. The service account must also subscribe to shared calendars via `calendarList().insert()` before they appear in `list_calendars`.
152
+
153
+ ## Cache
154
+
155
+ | Tool | Description | Key parameters |
156
+ |---|---|---|
157
+ | `refresh_cache` | Invalidate caches, forcing a fresh fetch on next use. | `spreadsheet_id?`, `doc_id?`, `folder_id?`, `calendar_id?` |
158
+
159
+ Omit all parameters to flush the entire cache. See [Configuration](configuration.md#caching) for TTL and path settings.
160
+
161
+ ## MCP resources
162
+
163
+ Two resources are available (read-only, not tools):
164
+
165
+ | Resource | Description |
166
+ |---|---|
167
+ | `server://auth-status` | Active auth method and Drive capability limits |
168
+ | `spreadsheet://{spreadsheet_id}/info` | Sheet list and grid properties for a spreadsheet |