mcp-gee-sweet 0.7.0.dev64__tar.gz → 0.7.0.dev68__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.
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/.claude/commands/prep-for-pr.md +5 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/CHANGELOG.md +27 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/PKG-INFO +1 -1
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/docs/configuration.md +2 -2
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/docs/decisions/decision-testing.md +4 -0
- mcp_gee_sweet-0.7.0.dev68/docs/qa/results/2026-07-04-docs.md +153 -0
- mcp_gee_sweet-0.7.0.dev68/docs/qa/results/2026-07-04-drive-smoke.md +75 -0
- mcp_gee_sweet-0.7.0.dev68/docs/qa/results/2026-07-04-infra.md +61 -0
- mcp_gee_sweet-0.7.0.dev68/docs/qa/results/2026-07-04-sheets_read.md +94 -0
- mcp_gee_sweet-0.7.0.dev68/docs/qa/results/2026-07-04.md +119 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/run.md +14 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/runs/README.md +5 -2
- mcp_gee_sweet-0.7.0.dev68/docs/qa/runs/v0.8.1.md +133 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/tests/calendar.md +138 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/tests/docs.md +51 -2
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/tests/sheets_read.md +4 -2
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/docs/roadmap.md +12 -4
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/docs/tools.md +7 -4
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/scripts/gen_tool_docs.py +3 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/tools/calendar.py +134 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/tools/docs/content.py +83 -10
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/tests/test_calendar.py +186 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/tests/test_docs_content.py +71 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/.dockerignore +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/.github/dependabot.yml +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/.github/workflows/ci.yml +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/.github/workflows/docs.yml +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/.github/workflows/publish-dev.yml +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/.github/workflows/release.yml +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/.gitignore +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/.pre-commit-config.yaml +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/.python-version +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/CLAUDE.md +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/CODE_OF_CONDUCT.md +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/CONTRIBUTING.md +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/Dockerfile +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/LICENSE +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/Makefile +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/README.md +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/SECURITY.md +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/docker-compose.yml +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/docs/auth.md +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/docs/client-setup.md +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/docs/decisions/decision-chart-theming.md +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/docs/decisions/decision-composite-tools.md +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/docs/decisions/decision-docs-formatting.md +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/docs/decisions/decision-fork.md +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/docs/decisions/decision-grid-data-size-cap.md +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/docs/decisions/decision-publishing.md +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/docs/decisions/decision-response-size-cap-generalization.md +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/docs/decisions/index.md +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/docs/design/docs-ast-pipeline.md +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/docs/design/effectiveformat-spike.md +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/docs/design/index.md +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/docs/design/markdown-support.md +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/docs/design/phase3-theme-system.md +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/docs/design.md +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/docs/index.md +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/docs/known-limitations.md +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/docs/notes-read.md +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/README.md +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/fixtures/tc-d195-create-doc.md +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/fixtures/tc-d196-create-doc.html +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/fixtures/tc-d213-dollar-escape.md +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/fixtures/tc-d226-heading-table.md +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/fixtures.template.md +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/operations.yaml +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/playwright_oauth.md +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/results/.gitkeep +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/results/2026-06-02.md +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/results/2026-06-14.md +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/results/2026-06-17.md +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/results/2026-06-18.md +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/results/2026-06-21-drive-discovery.md +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/results/2026-06-21-sheets-delete-clear.md +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/results/2026-06-28.md +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/retro-v0.8.0.md +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/runs/v0.8.0.md +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/setup.md +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/tests/drive.md +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/tests/infra.md +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/tests/sheets_charts.md +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/tests/sheets_mgmt.md +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/tests/sheets_write.md +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/docs/qa-checklist.md +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/mkdocs.yml +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/pyproject.toml +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/scripts/oauth_setup.py +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/.env.template +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/__init__.py +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/auth.py +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/cache.py +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/server.py +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/tools/__init__.py +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/tools/cache.py +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/tools/docs/__init__.py +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/tools/docs/ast.py +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/tools/docs/emitter.py +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/tools/docs/html_parser.py +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/tools/docs/layout.py +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/tools/docs/style.py +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/tools/docs/tables.py +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/tools/drive/__init__.py +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/tools/drive/activity.py +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/tools/drive/files.py +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/tools/drive/sharing.py +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/tools/drive/transfer.py +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/tools/response_limits.py +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/tools/sheets/__init__.py +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/tools/sheets/data.py +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/tools/sheets/helpers.py +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/tools/sheets/structure.py +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/tests/__init__.py +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/tests/drive/__init__.py +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/tests/drive/test_activity.py +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/tests/drive/test_files.py +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/tests/drive/test_sharing.py +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/tests/drive/test_transfer.py +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/tests/sheets/__init__.py +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/tests/sheets/test_data.py +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/tests/sheets/test_helpers.py +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/tests/sheets/test_structure.py +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/tests/test_auth.py +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/tests/test_cache.py +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/tests/test_docs_core.py +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/tests/test_docs_layout.py +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/tests/test_docs_style.py +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/tests/test_docs_tables.py +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/tests/test_gen_tool_docs.py +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/tests/test_response_limits.py +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/tests/test_server.py +0 -0
- {mcp_gee_sweet-0.7.0.dev64 → mcp_gee_sweet-0.7.0.dev68}/uv.lock +0 -0
|
@@ -18,6 +18,11 @@ Review the current branch against this checklist and report the status of each i
|
|
|
18
18
|
- Tag IS NOT present when: the tool is auth-agnostic and only the test fixture happens to live in personal Drive (`write_doc_content`, `get_doc_structure`, `insert_doc_text`, `delete_doc_range`, `style_doc_range`, `style_doc_table_cells`, etc.)
|
|
19
19
|
- Tag IS NOT present on error-path tests that return before making any API call
|
|
20
20
|
|
|
21
|
+
- [ ] **`**Playwright: required**` accuracy** — scan all new and modified test cases (see `docs/qa/run.md` for the tag's runtime behavior):
|
|
22
|
+
- Tag IS present when: the check verifies a mutation with a visual signature the API-level response can't fully confirm (formatting, hyperlinks, images, charts, layout)
|
|
23
|
+
- Tag IS NOT present when: the test is read-only, an error path, or a count/pagination check with no visual component
|
|
24
|
+
- Note: this tier definition is a working rule, not yet formalized in `docs/qa/run.md` — see the open TODO in `docs/qa/retro-v0.8.0.md`. Flag inconsistency rather than silently picking a side.
|
|
25
|
+
|
|
21
26
|
## 3. Safety
|
|
22
27
|
|
|
23
28
|
- [ ] **No resource IDs committed** — no Google Drive/Docs/Sheets/Calendar IDs appear in committed files. IDs belong in `.env` or `fixtures.local.md` (both gitignored).
|
|
@@ -4,6 +4,33 @@ All notable changes to this project are documented in this file.
|
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
## [0.8.1] — 2026-07-05
|
|
8
|
+
|
|
9
|
+
Defects and documentation cleanup. No new tools.
|
|
10
|
+
|
|
11
|
+
### Bug fixes
|
|
12
|
+
|
|
13
|
+
- `get_sheet_data`: `include_grid_data=True` without a range now auto-detects the actual used range and enforces a response-size safety cap, instead of fetching/returning the sheet's full padded grid — #235
|
|
14
|
+
- Response-size safety net generalized to `export_file`, `get_doc_content`, `get_multiple_sheet_data`, `find_in_spreadsheet`, and `list_file_activity` — #242
|
|
15
|
+
- `create_doc_from_file`: `\$` markdown escape now renders as a literal `$` instead of passing through untouched — #213
|
|
16
|
+
- Unrecognized tool keyword arguments now raise a validation error instead of being silently dropped — #239
|
|
17
|
+
- Cache: graceful recovery from read-only or corrupted DB files instead of failing to start — #212
|
|
18
|
+
- `get_multiple_spreadsheet_summary`: `rows_to_fetch=0` now consistently returns no data rows regardless of prior cache state (previously returned different results on a cold vs. warm cache) — #254
|
|
19
|
+
- `write_doc_content`: fixed a bug where an explicit font size/family override left on a document's trailing paragraph mark by prior content (e.g. a table or fenced code block) could leak into newly-written plain-paragraph content — #255
|
|
20
|
+
|
|
21
|
+
### Documentation
|
|
22
|
+
|
|
23
|
+
- `docs/tools.md` and the tool-filtering section of `docs/configuration.md` are now regenerated from tool docstrings via a pre-commit hook (`scripts/gen_tool_docs.py`) — #94
|
|
24
|
+
- `CONTRIBUTING.md` rewritten for the full contributor experience: PR scope rules, best-effort review turnaround, Code of Conduct, and the current label table — #95, #237
|
|
25
|
+
- Fixed tool-count drift, known-limitations entries, and auth-method ordering across the docs; removed stale `TODO.md` — #236
|
|
26
|
+
- Release gate scoping policy: a stable release can substitute Smoke + targeted Domain QA runs for Full Regression when a source-diff audit shows the change is narrow — see `docs/decisions/decision-testing.md`
|
|
27
|
+
|
|
28
|
+
### QA
|
|
29
|
+
|
|
30
|
+
- Scoped release gate: 148 PASS / 2 FAIL / 16 SKIP across 166 test cases. Both FAILs were real bugs found during this pass (`rows_to_fetch=0` cache-clamp inconsistency, `write_doc_content` font contamination) — fixed and re-verified live before release, not deferred. See `docs/qa/runs/v0.8.1.md`.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
7
34
|
## [0.8.0] — 2026-06-29
|
|
8
35
|
|
|
9
36
|
### New tools
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mcp-gee-sweet
|
|
3
|
-
Version: 0.7.0.
|
|
3
|
+
Version: 0.7.0.dev68
|
|
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
|
|
@@ -69,7 +69,7 @@ Access logs are emitted at `INFO` level — they appear when `DEBUG_LEVEL` is `D
|
|
|
69
69
|
|
|
70
70
|
<!-- Auto-generated by `scripts/gen_tool_docs.py` — do not edit by hand, your changes will be overwritten on the next commit. -->
|
|
71
71
|
|
|
72
|
-
By default all
|
|
72
|
+
By default all 87 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.
|
|
73
73
|
|
|
74
74
|
```bash
|
|
75
75
|
# Environment variable
|
|
@@ -100,7 +100,7 @@ create_doc,get_doc_content,get_doc_structure,write_doc_content,insert_doc_text,i
|
|
|
100
100
|
|
|
101
101
|
**Calendar only:**
|
|
102
102
|
```
|
|
103
|
-
list_calendars,get_calendar,list_events,get_event,create_event,update_event,delete_event,find_free_slots
|
|
103
|
+
list_calendars,get_calendar,create_calendar,update_calendar,delete_calendar,list_events,get_event,create_event,update_event,delete_event,find_free_slots
|
|
104
104
|
```
|
|
105
105
|
|
|
106
106
|
See [Tools](tools.md) for the full list of tool names.
|
|
@@ -134,6 +134,10 @@ The fix isn't "skip QA for boring releases" — it's replacing a blanket rule wi
|
|
|
134
134
|
- **The audit has to happen and get written down** — which commits were reviewed, how each was classified, which domains were included or excluded and why — in the release's `docs/qa/runs/vX.Y.Z.md`. A scoped gate without a documented audit trail is just skipping the gate.
|
|
135
135
|
- **A pure refactor still needs a live Domain run if it's never had one.** Unit tests were updated in the `docs/__init__.py` split to assert the same things they always did, which proves the refactor didn't break what the tests check — it says nothing about behavior the tests don't cover, and nothing about the real Google API (unit tests mock it). "No intended behavior change" is a claim about intent, not evidence; a Domain run is the evidence.
|
|
136
136
|
|
|
137
|
+
**Addendum — structural vs. non-structural scoping within a domain.** The first pass at doing this audit for v0.8.1 assumed "Domain implicated → run its full test file" was the smallest safe unit. Counting actual test cases showed that assumption has a real cost: `drive.md` alone is 168 test cases across ~34 tool sections, but the only source changes in it since `v0.8.0` were two narrow, additive fixes to `export_file` and `list_file_activity` (11 test cases combined) — nothing else in that file had a line touched. Running all 168 to protect 11 isn't rigor, it's re-running an already-passing v0.8.0 result under a new name. Contrast with `docs.md`: also a large combined file, but it underwent the `docs/__init__.py` submodule split — a structural reorganization touching the whole module, which is exactly the case the "no intended behavior change is a claim, not evidence" rule above is for.
|
|
138
|
+
|
|
139
|
+
The distinction that separates these: did the change reorganize the file/module (structural), or touch one or a few isolated tool sections in place without reorganizing anything (non-structural)? Structural changes still get the full domain file, full stop. Non-structural changes can be scoped to just the changed tools' test sections, identified from the actual diff rather than from what the originating issue says it touched — a fix's real footprint can be larger or smaller than its issue description. See [`docs/qa/runs/README.md`](../qa/runs/README.md#release-gate) step 6 for the mechanical rule.
|
|
140
|
+
|
|
137
141
|
See [`docs/qa/runs/README.md`](../qa/runs/README.md#release-gate) for the mechanical process (the "how"); this section is the "why" it changed from the original all-or-nothing rule.
|
|
138
142
|
|
|
139
143
|
---
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# QA Run — 2026-07-04 (Docs domain — full live run)
|
|
2
|
+
|
|
3
|
+
**Auth:** OAuth
|
|
4
|
+
**Fixtures:** DOC_ID=`1-whiEVwvnSOABaK9qgpzdVaGUOMRvJdQhDmCURqx4fA` (mcp-gee-sweet-qa-fixtures-doc) · TEST_LARGE_DOC_ID=`10UAHiKxHAmjqZt4PCoB53afJZoPN7OTRf71EBsFC6cI`
|
|
5
|
+
**Start:** 2026-07-05 03:14 UTC · **End:** 2026-07-05 04:11 UTC (run completed across two sessions, separated by an OAuth token revocation and a later transient API outage) · **Duration:** ~00:14 (session 1) + ~00:11 (session 2)
|
|
6
|
+
|
|
7
|
+
**Scope:** Full live run of `docs/qa/tests/docs.md` (81 TCs), ordered by the run.md conductor pattern. This domain was targeted for a full run (not a scoped/spot-check run) because `docs/__init__.py` was structurally split into submodules for this release (issue #232) and had never had a live Domain run since that reorg.
|
|
8
|
+
|
|
9
|
+
**Run history:** Session 1 completed TC-DOC01–47 and TC-DOC78–79 (49 TCs) before halting on a server-wide OAuth refresh-token revocation (`invalid_grant: Token has been expired or revoked`). After re-authentication was confirmed live (`list_sheets` sanity check), session 2 restored the fixture doc to standard content and completed the remaining 32 TCs (DOC48–77, DOC80–81), with one further transient `[Errno 32] Broken pipe` blip (not auth-related, cleared on retry) during TC-DOC76/77 setup. All 81 TCs are now complete.
|
|
10
|
+
|
|
11
|
+
## Summary
|
|
12
|
+
|
|
13
|
+
| Category | PASS | FAIL | SKIP | Total |
|
|
14
|
+
|---|---|---|---|---|
|
|
15
|
+
| Docs Tools | 81 | 0 | 0 | 81 |
|
|
16
|
+
| **Total** | **81** | **0** | **0** | **81** |
|
|
17
|
+
|
|
18
|
+
## Failures
|
|
19
|
+
|
|
20
|
+
*(none — zero FAILs across all 81 TCs)*
|
|
21
|
+
|
|
22
|
+
## Findings worth flagging (not scored as TC failures — no existing check covers them)
|
|
23
|
+
|
|
24
|
+
**1. `write_doc_content`'s clear+reinsert path leaks a stale `font_size:1` override into freshly-written plain paragraphs — recommend filing a GitHub issue.**
|
|
25
|
+
At session-1 Step 0, `get_doc_structure` on the fixture doc showed every text run at `font_size: 1` even though `get_doc_named_styles` reported normal named-style defaults (NORMAL_TEXT 11pt). Root cause: `write_doc_content`'s "clear + reinsert" strategy (`deleteContentRange(1, end_index-1)` then insert at index 1) never touches the *final* paragraph mark, so a stale explicit `textStyle` on that trailing character survives every full-document rewrite. Newly inserted content at index 1 appears to inherit ambient style from that untouched trailing context in some cases (plain paragraphs) but not others (headings render correctly; table-cell content renders correctly).
|
|
26
|
+
|
|
27
|
+
This was reproduced **four separate times** across the full run — fixed manually via `style_doc_range` after TC-DOC42, after TC-DOC47, and again independently confirmed in session 2 (post-outage): after a plain `write_doc_content` restore, `get_doc_theme` reported `NORMAL_TEXT: {font_family: "Courier New", font_size: 1}` on a doc whose most recent explicit HTML content contained no code spans — the doc's ambient style was contaminated by leftover state from the *previous* content, not the new content being written. Session 2 also surfaced a compounding detail: **there is no exposed tool parameter to clear an inherited `font_family` override** — `style_doc_range` supports `font_size` but not `font_family`, so the only way to fully normalize a contaminated doc is `apply_theme(overwrite=True)`, which is a much heavier operation than the bug that necessitates it.
|
|
28
|
+
|
|
29
|
+
Net: **calling `write_doc_content` to replace content whose previous state had an explicit font/size override (e.g. from a table, or a fenced code block) can cause the new content to silently inherit that override**, rendering it in the wrong font or at unreadable 1pt size until manually restyled. No existing TC in `docs.md` checks post-write font styling on plain paragraphs, so this never fails a TC outcome, but it affects real usage (anyone iterating on a doc with `write_doc_content` after having had a table or code block in it) and is worth a ticket — likely fix is extending the clear+reinsert range in `write_doc_content` (or the blank-paragraph-before-table collapse logic) to also reset the trailing paragraph mark's `textStyle`, and/or adding `font_family` to `style_doc_range`'s supported fields as a mitigation.
|
|
30
|
+
|
|
31
|
+
**2. TC-DOC47 — possible over-broad `font_family` application (unconfirmed, still needs a follow-up check).**
|
|
32
|
+
After writing `` Use the `print()` function `` via markdown, the rendered line appeared **uniformly monospace** in the Google Docs editor (visual screenshot), and clicking to the very start of the line showed "Courier New" selected in the font toolbar — suggesting the Courier styling may have applied to the whole paragraph rather than just the `print()` code span. `html_parser.py`'s code-span handling (`font_family="Courier New" if (self._in_pre or self._code_depth > 0) else None`, lines ~74) looks correct at the AST level — only text nodes inside `<code>` should get the font override — so this may be a Google Docs toolbar/cursor-position display quirk rather than a real over-application, and may simply be an artifact of Finding 1's contamination rather than a distinct bug. **Could not conclusively verify** because `get_doc_structure` doesn't expose `font_family` per run (an acknowledged existing gap). Recommend a targeted follow-up: call the raw Docs API (or extend `get_doc_structure` to expose `font_family`) and check whether only the `print()` run carries `weightedFontFamily: Courier New`, or whether the whole paragraph does.
|
|
33
|
+
|
|
34
|
+
**3. TC-DOC57's prescribed test image URI (SVG) is rejected by the live Google Docs API — the test file's prompt needs updating.**
|
|
35
|
+
The test case's exact prompt URI (`https://www.gstatic.com/images/branding/googlelogo/svg/googlelogo_clr_74x24px.svg`) fails on every attempt with `HttpError 400: There was a problem retrieving the image. The provided image should be publicly accessible, within size limit, and in supported formats.` — this is Google's own API rejecting the SVG format, not a bug in `insert_inline_image`. Substituting a PNG at the same host (`https://www.gstatic.com/images/branding/googlelogo/1x/googlelogo_color_92x30dp.png`) succeeded immediately and rendered correctly. TC-DOC57 is recorded PASS on the basis of the PNG substitution (matching what appears to have happened in the historical result too, which mentions "PNG" despite the test file specifying an SVG URL). Recommend updating the test file's prompt to use a PNG/JPEG URL so future runs don't need to silently substitute.
|
|
36
|
+
|
|
37
|
+
## Tool coverage (Docs domain tools only)
|
|
38
|
+
|
|
39
|
+
| Tool | TC(s) run | Status |
|
|
40
|
+
|---|---|---|
|
|
41
|
+
| get_doc_structure | DOC01–05 + used as verification throughout | ✅ all pass |
|
|
42
|
+
| insert_doc_text | DOC06–08, 17, 22–24, 28–30, 74 + setup throughout | ✅ all pass |
|
|
43
|
+
| delete_doc_range | DOC09–11, 22 + cleanup throughout | ✅ all pass |
|
|
44
|
+
| style_doc_range | DOC12–15, 23–24, 28–30 | ✅ all pass |
|
|
45
|
+
| insert_doc_table | DOC16–17, 26–27, 48–50, 55, 61–66 + setup for table tests | ✅ all pass |
|
|
46
|
+
| style_doc_table_cells | DOC18–21, 25–26 | ✅ all pass |
|
|
47
|
+
| write_doc_content | DOC31–47, 78–79 + fixture restores throughout | ✅ all pass |
|
|
48
|
+
| create_doc_from_file | DOC44–46, 76–77, 81 | ✅ all pass |
|
|
49
|
+
| create_doc | (none — not directly exercised in this file) | ⚠️ no coverage |
|
|
50
|
+
| get_doc_content | DOC80 | ✅ pass |
|
|
51
|
+
| get_doc_theme | DOC52, 56 | ✅ pass |
|
|
52
|
+
| apply_theme | DOC53–56 | ✅ pass |
|
|
53
|
+
| get_doc_named_styles | DOC75 | ✅ pass |
|
|
54
|
+
| insert_inline_image | DOC57–60 | ✅ pass (see Finding 3 re: DOC57 SVG substitution) |
|
|
55
|
+
| insert_table_row | DOC61–62 | ✅ pass |
|
|
56
|
+
| delete_table_row | DOC63, 67 | ✅ pass |
|
|
57
|
+
| insert_table_column | DOC64–65 | ✅ pass |
|
|
58
|
+
| delete_table_column | DOC66 | ✅ pass |
|
|
59
|
+
| create_header | DOC68–69, 72, 74 | ✅ pass |
|
|
60
|
+
| create_footer | DOC70–71, 73 | ✅ pass |
|
|
61
|
+
|
|
62
|
+
## Full results
|
|
63
|
+
|
|
64
|
+
| TC | Title | Outcome | Notes |
|
|
65
|
+
|---|---|---|---|
|
|
66
|
+
| TC-DOC01 | Structure of a non-empty doc | PASS | docId/title/elements returned; sectionBreak at 0; last paragraph ends at doc length (89). |
|
|
67
|
+
| TC-DOC02 | Paragraph runs include style data | PASS | bold run `bold:true`; plain runs `bold:null`; link run `link_url` populated. |
|
|
68
|
+
| TC-DOC03 | Structure of a doc containing a table | PASS | 2×2 table, 4 cells, `paragraphStartIndex=startIndex+1`; non-empty cell text confirmed ("X"). |
|
|
69
|
+
| TC-DOC04 | Structure of an empty doc | PASS | sectionBreak + 1 empty paragraph, no error. |
|
|
70
|
+
| TC-DOC05 | Invalid doc ID returns error | PASS | `{"error": "<HttpError 404 ...>"}`, no exception. |
|
|
71
|
+
| TC-DOC06 | Insert a single paragraph | PASS | Inserted at 88; `insertions:1`; surrounding content shifted correctly. |
|
|
72
|
+
| TC-DOC07 | Insert at multiple indices — high→low ordering | PASS | N1=70, N2=79; BBB landed at N2+4=83; `insertions:2`. |
|
|
73
|
+
| TC-DOC08 | Empty insertions list returns error | PASS | `{"error": "insertions list is empty"}`. |
|
|
74
|
+
| TC-DOC09 | Delete a paragraph | PASS | Deleted [88,103]; doc restored to 89-char baseline; `deletions:1`. |
|
|
75
|
+
| TC-DOC10 | Cannot delete final segment newline | PASS | Delete [1,89] → 400 error re: newline at end of segment. |
|
|
76
|
+
| TC-DOC11 | Empty deletions list returns error | PASS | `{"error": "deletions list is empty"}`. |
|
|
77
|
+
| TC-DOC12 | Apply named style type | PASS | `namedStyleType:HEADING_2`, `requests:1`. Playwright: text confirmed present in rendered doc. |
|
|
78
|
+
| TC-DOC13 | Apply text styles (bold/italic/red) | PASS | `bold:true`,`italic:true`, `requests:1` (no paragraph style req). Playwright: text confirmed present. |
|
|
79
|
+
| TC-DOC14 | Apply both paragraph and text style | PASS | `requests:2` (updateParagraphStyle + updateTextStyle); HEADING_3 + bold confirmed. |
|
|
80
|
+
| TC-DOC15 | No recognised style fields returns error | PASS | `{"error": "no recognised style fields in any range"}`. |
|
|
81
|
+
| TC-DOC16 | Insert a 2×3 table | PASS | `precedingParagraphIndex=88=N`, `tableStartIndex=89=N+1`, 6 cells, paragraphStartIndex=startIndex+1. Playwright screenshot: empty 2×3 grid rendered. |
|
|
82
|
+
| TC-DOC17 | Cell indices usable for insert_doc_text | PASS | Inserted "Cell content" at paragraphStartIndex 92; cell [0,0] text confirmed. |
|
|
83
|
+
| TC-DOC18 | Grey header row background | PASS | `requests:1`. Playwright screenshot: row 0 visibly grey across both columns. |
|
|
84
|
+
| TC-DOC19 | Borders and padding on all cells | PASS | `requests:4` (one per cell). Playwright screenshot: visible black borders + padding. |
|
|
85
|
+
| TC-DOC20 | Empty cells list returns error | PASS | `{"error": "cells list is empty"}`. |
|
|
86
|
+
| TC-DOC21 | Cell with no style fields is skipped | PASS | `requests:1` — no-style cell silently skipped. |
|
|
87
|
+
| TC-DOC22 | Multi-delete high→low ordering | PASS | DEL-A [70,76], DEL-B [85,91] deleted in one call; Item two back at original startIndex 79; `deletions:2`. |
|
|
88
|
+
| TC-DOC23 | style_doc_range HEADING_1 round-trip | PASS | `requests:1`; `namedStyleType:HEADING_1`; text unchanged. |
|
|
89
|
+
| TC-DOC24 | style_doc_range bold/italic round-trip | PASS | `requests:1`; `bold:true,italic:true`; `namedStyleType` unchanged (NORMAL_TEXT). |
|
|
90
|
+
| TC-DOC25 | style_doc_table_cells post-fix verification | PASS | No 400 "oneof" error; `requests:1`. Playwright screenshot: cell [0,0] light blue. |
|
|
91
|
+
| TC-DOC26 | Full e2e: insert table → style cells | PASS | Table 2×3 inserted; grey header `requests:1`; borders `requests:6`. Playwright screenshot: grey header spanning 3 cols + visible borders. |
|
|
92
|
+
| TC-DOC27 | Insert text then table — index chaining | PASS | `precedingParagraphIndex=105=N+17`, `tableStartIndex=106=N+18`; both ops succeeded without re-fetching. |
|
|
93
|
+
| TC-DOC28 | Apply strikethrough | PASS | `requests:1`; `strikethrough:true`. |
|
|
94
|
+
| TC-DOC29 | Apply font_size | PASS | `requests:1` (18pt applied; not independently visually screenshotted — no Playwright tag on this TC). |
|
|
95
|
+
| TC-DOC30 | Apply link_url | PASS | `requests:1`; run split correctly; linked run `underline:true` (expected Docs API auto-behavior). |
|
|
96
|
+
| TC-DOC31 | `<h2>` maps to HEADING_2 | PASS | HEADING_1/2/3/4 all correct; old #41 bug (H2→H3 collapse) absent. |
|
|
97
|
+
| TC-DOC32 | `<th>` cells produce bold runs | PASS (partial) | Table + cell text confirmed; bold not verifiable via `get_doc_structure` (no cell-run exposure, known gap #54). |
|
|
98
|
+
| TC-DOC33 | Inline formatting inside `<td>` | PASS (partial) | Cell text "bold plain italic" confirmed; run-level bold/italic not exposed (same gap). |
|
|
99
|
+
| TC-DOC34 | `colspan` produces merged cells | PASS | 2 rows confirmed; row 0 merged cell "Wide cell", phantom [0,1] empty. Playwright screenshot: top row visibly spans both columns. |
|
|
100
|
+
| TC-DOC35 | Column widths from HTML | PASS | Call succeeded, no error. Playwright screenshot: first column visibly narrower than second (144pt vs 288pt). |
|
|
101
|
+
| TC-DOC36 | `rowspan` produces vertically merged cells | PASS | [0,0]"Tall", [0,1]"R0C1", [1,0]"" phantom, [1,1]"R1C1". Playwright screenshot: "Tall" visibly spans both rows. |
|
|
102
|
+
| TC-DOC37 | Combined rowspan + colspan | PASS | 2 rows, 3 cols; [0,0]"Big", phantoms [0,1]/[1,0]/[1,1] all empty; [0,2]"R0C2", [1,2]"R1C2". |
|
|
103
|
+
| TC-DOC38 | rowspan with header row | PASS | 3 rows, 3 cols; header bold row; "Alpha" rowspan; phantom [2,0] empty; "B" not shifted left. |
|
|
104
|
+
| TC-DOC39 | Markdown headings | PASS | HEADING_1/2/3 confirmed in order. |
|
|
105
|
+
| TC-DOC40 | Markdown bold/italic | PASS | run "bold" → `bold:true`; run "italic" → `italic:true`. |
|
|
106
|
+
| TC-DOC41 | Markdown task list | PASS | `☑ Done item`, `☐ Pending item`, `Plain item` (no glyph) confirmed. |
|
|
107
|
+
| TC-DOC42 | Markdown fenced code block | PASS | Text confirmed; Courier New font confirmed via Playwright toolbar + screenshot at readable size (after fixing fixture-contamination font_size:1, see Finding 1). |
|
|
108
|
+
| TC-DOC43 | Markdown table | PASS | 3 rows, 2 cols; Name/Value, Alpha/1, Beta/2 all correct. |
|
|
109
|
+
| TC-DOC44 | create_doc_from_file (.md) | PASS | docId/web_link returned; HEADING_1, bold/italic, task glyphs, table all confirmed via `get_doc_structure` and Playwright screenshot. Doc deleted after verification. |
|
|
110
|
+
| TC-DOC45 | create_doc_from_file (.html) | PASS | docId/web_link returned; HEADING_2 "From HTML file" + paragraph confirmed. Doc deleted after verification. |
|
|
111
|
+
| TC-DOC46 | create_doc_from_file file not found | PASS | `{"error": "File not found: ~/does-not-exist.md"}`, no exception. |
|
|
112
|
+
| TC-DOC47 | write_doc_content inline code monospace | PASS | "print()" renders monospace per Playwright screenshot (check satisfied). See Finding 2 for an unconfirmed observation re: scope of the Courier styling. |
|
|
113
|
+
| TC-DOC48 | Simple nested table | PASS | Outer 1×1 table inserted, cell span 4–17 consistent with inner 1×1 table. Playwright screenshot: nested table visibly rendered inside outer cell with "Inner" text. |
|
|
114
|
+
| TC-DOC49 | Nested table alongside regular cells | PASS | Outer 1×2 table; cell [0,0]="Label"; cell [0,1] empty text with span (11–31) consistent with a nested 1×2 table inside it. |
|
|
115
|
+
| TC-DOC50 | Nested table, multiple rows/cols | PASS | Outer 1×1 table; cell span (4–35) consistent with a nested 2×2 table inside it. |
|
|
116
|
+
| TC-DOC51 | Nested tables not supported in markdown | PASS | Documentation-only entry (per test file: "No test to run; this entry documents the known limitation"); no live call required. |
|
|
117
|
+
| TC-DOC52 | get_doc_theme scans body paragraph styles | PASS | No error key; returned `{"HEADING_1": {...}, "NORMAL_TEXT": {font_family, font_size, ...}}` — non-empty dict with named style keys, each carrying at least one of the expected fields. (The doc happened to have explicit contamination per Finding 1 at the time of this call, which is itself a valid "explicit paragraph styles" case per the tool's spec — confirms the tool correctly surfaces real explicit overrides.) |
|
|
118
|
+
| TC-DOC53 | apply_theme updates named style definitions | PASS | `requests:2` (one updateNamedStyle per named style type: HEADING_1, NORMAL_TEXT), no error. |
|
|
119
|
+
| TC-DOC54 | apply_theme overwrite=True patches paragraphs | PASS | `requests:5` (2 updateNamedStyle + 3 updateTextStyle for HEADING_1 paragraph, NORMAL_TEXT paragraph, trailing blank NORMAL_TEXT paragraph). Playwright screenshot: heading in Georgia 22pt, body text visibly different font/size (Verdana 11pt). |
|
|
120
|
+
| TC-DOC55 | apply_theme with table styling | PASS | `requests:2` (one updateTableCellStyle per row). Playwright screenshot: visible black borders, cell padding, and header row (row 0) grey background. |
|
|
121
|
+
| TC-DOC56 | get_doc_theme → apply_theme round-trip | PASS | `get_doc_theme` returned non-empty dict (`HEADING_1`/`NORMAL_TEXT` with font_family); feeding it straight into `apply_theme(overwrite=True)` succeeded with `requests:8`, no error. |
|
|
122
|
+
| TC-DOC57 | Insert image by public URI | PASS | See Finding 3 — the prescribed SVG URI is rejected by the live Google Docs API (`HttpError 400`, unsupported format); a PNG at the same host succeeded and rendered correctly (confirmed via Playwright screenshot). |
|
|
123
|
+
| TC-DOC58 | Insert image with explicit size | PASS | Inserted with width=100, height=50; Playwright screenshot: second logo visibly smaller than the default-sized one inserted for DOC57. |
|
|
124
|
+
| TC-DOC59 | No source provided returns error | PASS | `{"error": "Provide either uri or drive_file_id"}`. |
|
|
125
|
+
| TC-DOC60 | Both URI and drive_file_id returns error | PASS | `{"error": "Provide only one of uri or drive_file_id, not both"}`. |
|
|
126
|
+
| TC-DOC61 | Insert a row below | PASS | 2×2 table → insert below row 0 → 3 rows confirmed via `get_doc_structure`. |
|
|
127
|
+
| TC-DOC62 | Insert a row above | PASS | 2×2 table → insert above row 1 → 3 rows confirmed. Playwright screenshot: 3 empty rows visible. |
|
|
128
|
+
| TC-DOC63 | Delete a row | PASS | 3 rows → delete row 1 → 2 rows confirmed. Playwright screenshot: 2 rows visible. |
|
|
129
|
+
| TC-DOC64 | Insert a column to the right | PASS | 2 cols → insert right of col 0 → 3 cols confirmed. Playwright screenshot: 3 columns visible. |
|
|
130
|
+
| TC-DOC65 | Insert a column to the left | PASS | 3 cols → insert left of col 1 → 4 cols confirmed. Playwright screenshot: 4 columns visible. |
|
|
131
|
+
| TC-DOC66 | Delete a column | PASS | 3 cols → delete col 1 → 2 cols confirmed. Playwright screenshot: 2 columns visible. |
|
|
132
|
+
| TC-DOC67 | API error returned gracefully (out of bounds row) | PASS | `{"error": "<HttpError 400 ... row index 99 should be less than the total number of rows 2 ...>"}`, no exception. |
|
|
133
|
+
| TC-DOC68 | Create a default page header | PASS | `headerId` (non-empty string) returned, no error. Playwright screenshot: header text visible at top of rendered doc. |
|
|
134
|
+
| TC-DOC69 | Create a header with content | PASS | `headerId` returned, no error; content "Confidential — Internal Only" prepended. Playwright screenshot: new header text visible above prior header text. |
|
|
135
|
+
| TC-DOC70 | Create a default page footer | PASS | `footerId` (non-empty string) returned, no error. Confirmed via API response per known limitation (footer not reliably visible via scrollTo on a short doc in the rendered viewport). |
|
|
136
|
+
| TC-DOC71 | Create a footer with content | PASS | `footerId` returned, no error; content "Page 1" inserted. |
|
|
137
|
+
| TC-DOC72 | Invalid header_type returns error | PASS | `{"error": "Invalid header_type 'INVALID'. Use DEFAULT or FIRST_PAGE_HEADER"}`. |
|
|
138
|
+
| TC-DOC73 | Invalid footer_type returns error | PASS | `{"error": "Invalid footer_type 'INVALID'. Use DEFAULT or FIRST_PAGE_FOOTER"}`. |
|
|
139
|
+
| TC-DOC74 | insert_doc_text with segment_id writes into header | PASS | `insertions:1`, no error. Playwright screenshot: new header text visible, prepended correctly at index 0 of the header segment. |
|
|
140
|
+
| TC-DOC75 | get_doc_named_styles reads named style defaults | PASS | No error; returned a 9-key dict (NORMAL_TEXT, HEADING_1–6, TITLE, SUBTITLE) each with plausible font/spacing fields. |
|
|
141
|
+
| TC-DOC76 | Table immediately after heading renders at Normal Text size | PASS | Fresh doc created from fixture file; table 2×3 with all 6 cells confirmed correct (Finding/Severity/Ticket, Some finding/HIGH/KINDLY-123). Playwright screenshot: "HIGH" H2 heading (16pt) visibly larger than table cell text. Doc deleted after verification. |
|
|
142
|
+
| TC-DOC77 | No visible blank line between heading and table | PASS | Same fixture doc as DOC76. Playwright screenshot: no visible gap between "HIGH" heading and the table; blank collapsing paragraph confirmed present in structure (font_size:1 run, consistent with the intentional collapse behavior). Doc deleted after verification. |
|
|
143
|
+
| TC-DOC78 | `data-style="title"` → TITLE | PASS | First paragraph `namedStyleType:TITLE` text "My Document Title"; second `NORMAL_TEXT` "Body paragraph.". |
|
|
144
|
+
| TC-DOC79 | `data-style="subtitle"` → SUBTITLE | PASS | TITLE / SUBTITLE / NORMAL_TEXT all confirmed with correct text. |
|
|
145
|
+
| TC-DOC80 | get_doc_content response-size cap (cache-hit path re-check) | PASS | First call: `{"error": "...49700 characters, over the 40000-character safety cap..."}`. Second call (cache-hit path): identical error re-raised. Third call with `local_path`: returned `{"bytes_written": 49700, ...}`; verified on disk with `wc -c` — 49700 bytes, exact match. |
|
|
146
|
+
| TC-DOC81 | create_doc_from_file renders `\$` as literal `$` | PASS | docId/web_link returned, no error. `get_doc_content` confirmed literal `$6,000`, `$25`, `$1,200` in rendered text — no stray backslashes. Doc deleted after verification. |
|
|
147
|
+
|
|
148
|
+
## Notes for future runs
|
|
149
|
+
|
|
150
|
+
1. Fixture doc was restored to standard content (`<h1>Test Document</h1><p>This document is used for QA testing of mcp-gee-sweet.</p><ul><li>Item one</li><li>Item two</li></ul>`) at the start of session 2 and verified clean via `get_doc_structure` (title, HEADING_1 "Test Document", body paragraph, "Item one"/"Item two" bullets, no leftover `font_size` overrides). The doc's page header/footer now contain accumulated test content from DOC68–74/70–71 (headers/footers are additive by design in this tool and were left as-is, consistent with prior convention).
|
|
151
|
+
2. **File a GitHub issue for Finding 1** (`write_doc_content` clear+reinsert path leaking `font_size`/`font_family` contamination into new plain-paragraph content) — reproduced 4 times across this run, root-caused to the untouched trailing paragraph mark, and compounded by `style_doc_range` having no `font_family` parameter to clean it up after the fact.
|
|
152
|
+
3. Consider a targeted follow-up for Finding 2 (possible over-broad Courier New application in TC-DOC47) once `font_family` is exposed by `get_doc_structure` or via a raw API check — may turn out to be subsumed by Finding 1.
|
|
153
|
+
4. Update TC-DOC57's prescribed image URI in `docs/qa/tests/docs.md` from the SVG gstatic URL (rejected by the live API) to a PNG/JPEG URL (Finding 3).
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# QA Run — 2026-07-04 (Drive (scoped) + Smoke remainder)
|
|
2
|
+
|
|
3
|
+
**Auth:** OAuth
|
|
4
|
+
**Fixtures:** SPREADSHEET_ID=`15hOwO1Jay26PyxjjYtq9Pq-gEd8lDa81g-C13-GyvCA` · DOC_ID=`1-whiEVwvnSOABaK9qgpzdVaGUOMRvJdQhDmCURqx4fA` · FOLDER_ID=`1RCnhFTjh4aT8AJ_Rx5J9s8O2Kqta1vJE` · CALENDAR_ID=`kevin.huisman@gmail.com`
|
|
5
|
+
**Scope:** This is one of four parallel shards for the v0.8.1 live QA gate. This shard covers a narrow, precisely-scoped list of 20 TCs spanning `drive.md` (export_file / list_file_activity remainder), `sheets_write.md` (TC-W01, TC-W06 — rows 7–9, non-overlapping with the sheets_read shard's rows 1–6 / A1:B2 work), `sheets_mgmt.md` (TC-S01, TC-S15, TC-S20), `sheets_charts.md` (TC-C01), and `calendar.md` (TC-CAL01, TC-CAL09, TC-CAL20). No server restarts or env var/config changes were performed (would drop the shared MCP connection all four shards depend on).
|
|
6
|
+
|
|
7
|
+
**Playwright note:** TC-CAL20 is the only Playwright-tagged case in this shard's scope. Per the concurrent docs shard's heavy use of the shared Playwright browser tab, visual verification was skipped for TC-CAL20 — confirmation relied on the `create_event`/`get_event` API responses instead (the same fallback pattern `run.md` already prescribes for permission-change tests).
|
|
8
|
+
|
|
9
|
+
## Step 0 — Fixture verification
|
|
10
|
+
|
|
11
|
+
- `get_sheet_data(TEST_SPREADSHEET_ID, sheet="Sales", range="A1:D6")` → header row + Widget/Gadget/Donut/Gizmo/Totals, columns A–D, 6 rows total. Matches expected seed state exactly. No restore needed.
|
|
12
|
+
- Rows 7+ and extra tabs were intentionally not checked (owned by other shards / additive to this shard's own writes).
|
|
13
|
+
|
|
14
|
+
Fixture state: **correct**, no restore performed.
|
|
15
|
+
|
|
16
|
+
## Summary
|
|
17
|
+
|
|
18
|
+
| Category | PASS | FAIL | SKIP | Total |
|
|
19
|
+
|---|---|---|---|---|
|
|
20
|
+
| Drive (export_file / list_file_activity) | 9 | 0 | 1 | 10 |
|
|
21
|
+
| Sheets Write | 2 | 0 | 0 | 2 |
|
|
22
|
+
| Sheets Mgmt | 3 | 0 | 0 | 3 |
|
|
23
|
+
| Sheets Charts | 1 | 0 | 0 | 1 |
|
|
24
|
+
| Calendar | 3 | 0 | 0 | 3 |
|
|
25
|
+
| **Total** | **18** | **0** | **1** | **19*** |
|
|
26
|
+
|
|
27
|
+
\* 20 TCs assigned; TC-D166 is a unit-test-only case (see Full results) so effective live-executed total is 19 PASS + 1 documented SKIP = 20 rows below.
|
|
28
|
+
|
|
29
|
+
## Failures
|
|
30
|
+
|
|
31
|
+
*(none)*
|
|
32
|
+
|
|
33
|
+
## Tool coverage (this shard only)
|
|
34
|
+
|
|
35
|
+
| Tool | TC(s) | Status |
|
|
36
|
+
|---|---|---|
|
|
37
|
+
| export_file | TC-D83, TC-D84, TC-D85, TC-D86, TC-D87, TC-D167 | ✅ all pass |
|
|
38
|
+
| list_file_activity | TC-D163, TC-D164, TC-D165, TC-D168 | ✅ all pass |
|
|
39
|
+
| update_cells | TC-W01 | ✅ pass |
|
|
40
|
+
| batch_update_cells | TC-W06 | ✅ pass |
|
|
41
|
+
| list_sheets | TC-S01, TC-S20 (verification step) | ✅ all pass |
|
|
42
|
+
| create_sheet | TC-S15 | ✅ pass |
|
|
43
|
+
| refresh_cache | TC-S20 | ✅ pass |
|
|
44
|
+
| add_chart | TC-C01 | ✅ pass |
|
|
45
|
+
| list_calendars | TC-CAL01 | ✅ pass |
|
|
46
|
+
| list_events | TC-CAL09 | ✅ pass |
|
|
47
|
+
| create_event | TC-CAL20 | ✅ pass |
|
|
48
|
+
| get_event | TC-CAL20 (confirmation) | ✅ pass |
|
|
49
|
+
|
|
50
|
+
(Full tool coverage across all domains is aggregated by the conductor from all four shards' results files, not by this shard alone.)
|
|
51
|
+
|
|
52
|
+
## Full results
|
|
53
|
+
|
|
54
|
+
| TC | Title | Outcome | Notes |
|
|
55
|
+
|---|---|---|---|
|
|
56
|
+
| TC-D83 | Export Google Doc as plain text | **PASS** | `export_file(DOC_ID, "txt")` → `encoding: "utf-8"`, `format: "txt"`. Content included "Test Document", the body paragraph, and "Item one"/"Item two" bullet text. |
|
|
57
|
+
| TC-D84 | Export Google Doc as HTML | **PASS** | `export_file(DOC_ID, "html")` → `encoding: "utf-8"`. Content wrapped in `<html>...</html>`, heading rendered as `<h1>...Test Document...</h1>`, bullets rendered as a `<ul><li>` list. |
|
|
58
|
+
| TC-D85 | Export Google Doc as PDF (binary) | **PASS** | `export_file(DOC_ID, "pdf")` → `encoding: "base64"`. Base64 content decodes to a valid PDF (`JVBERi0...` = `%PDF-` header). Non-empty. |
|
|
59
|
+
| TC-D86 | Export Google Sheet as CSV | **PASS** | `export_file(SPREADSHEET_ID, "csv")` → `encoding: "utf-8"`. Content is comma-separated text exactly matching the Sales sheet's A1:D6 data. |
|
|
60
|
+
| TC-D87 | Unknown export format | **PASS** | `export_file(DOC_ID, "xyz")` raised: `Unknown export_format 'xyz'. Valid options: pdf, html, txt, docx, odt, rtf, epub, csv, xlsx, ods, pptx, raw`. Clean error, not a server crash. |
|
|
61
|
+
| TC-D163 | Basic activity fetch returns timeline entries | **PASS** | `list_file_activity(DOC_ID)` → 79 activities. All entries have `timestamp`, `action`, `actors`. Actions observed: `edit`, `rename`, `permission_change`, `create`. No `error` key. |
|
|
62
|
+
| TC-D164 | Known-user actor structure | **PASS** | Multiple `type: "user"` actors present with `person_name` (e.g. `people/101951097007377611160`) and boolean `is_current_user` (both `true` and `false` values observed across different actors). A `system` actor (`event: null`) appears on the `permission_change` entry. |
|
|
63
|
+
| TC-D165 | Pagination — next_page_token present when results exceed page_size | **PASS** | `list_file_activity(DOC_ID, page_size=1)` returned 2 activities (API groups related events rather than hard-clipping) and a non-null `next_page_token`. |
|
|
64
|
+
| TC-D166 | Invalid file ID returns error (unit test) | **SKIP** | Checks section is explicitly marked "(unit test)" in `drive.md` — no live-callable repro path (would require simulating a 403/404 from the Drive Activity API). Recorded as SKIP per the file's own stated scope rather than forcing a live call. |
|
|
65
|
+
| TC-D167 | export_file trips the response-size cap on base64-inflated content (issue #242) | **PASS** | `export_file(SPREADSHEET_ID, "xlsx")` raised `ValueError`: "export_file: the response is 116880 characters, over the 40000-character safety cap. Base64 encoding inflates raw file size by ~33%. Call download_file instead ... or set MAX_TOOL_RESPONSE_CHARS ...". Mentions the actual size, the 40,000 cap, base64 inflation, and `download_file` — does NOT mention `local_path`. Matches the documented behavior from the prior 2026-07-03 verification exactly. |
|
|
66
|
+
| TC-D168 | list_file_activity response-size cap — code-path only, no dedicated live fixture (issue #242) | **PASS (N/A by design)** | Per the file's own Background note, this tool intentionally has no dedicated live cap-triggering fixture (cap is defense-in-depth, verified by unit tests `test_oversized_result_raises` / `test_error_points_to_page_size_not_local_path`). Live sanity check performed instead: `list_file_activity(SPREADSHEET_ID, page_size=5)` returned a normal 10-entry activity list, no regression, no error. Cap-triggering behavior itself remains unit-test-only — a documented scoping decision, not an oversight. |
|
|
67
|
+
| TC-W01 | Write simple values | **PASS** | `update_cells(SPREADSHEET_ID, sheet="Sales", range="A7:D7", data=[["Test","A","B","C"]])` → `updatedRange: "Sales!A7:D7"`, 4 cells updated. Follow-up `get_sheet_data(range="A1:D9")` confirmed row 7 = `Test, A, B, C` and rows 1–6 unchanged (Widget/Gadget/Donut/Gizmo/Totals intact). No `error` field. |
|
|
68
|
+
| TC-W06 | Multiple ranges in one call | **PASS** | `batch_update_cells(SPREADSHEET_ID, sheet="Sales", ranges={"A8:A8": [["Batch1"]], "A9:A9": [["Batch2"]]})` → both ranges updated in one call (`totalUpdatedCells: 2`). Follow-up read confirmed A8="Batch1", A9="Batch2". |
|
|
69
|
+
| TC-S01 | Happy path | **PASS** | `list_sheets(SPREADSHEET_ID)` → `["Sales", "Empty", "Notes & Misc"]` (queried before TC-S15 added "BrandNew"). All 3 expected tabs present, no `error` field. |
|
|
70
|
+
| TC-S15 | Create a new tab | **PASS** | `create_sheet(SPREADSHEET_ID, title="BrandNew")` → `{"sheetId": 1823833581, "title": "BrandNew", "index": 3, "spreadsheetId": "..."}`. All expected fields present. |
|
|
71
|
+
| TC-S20 | Refresh by spreadsheet ID only | **PASS** | `refresh_cache(spreadsheet_id=SPREADSHEET_ID)` → `{"invalidated": ["spreadsheet:15hOwO1Jay26PyxjjYtq9Pq-gEd8lDa81g-C13-GyvCA"]}`. Follow-up `list_sheets` returned `["Sales", "Empty", "Notes & Misc", "BrandNew"]` — confirms the cache was actually invalidated and the next call re-fetched fresh data (BrandNew, created moments earlier by TC-S15, was visible). |
|
|
72
|
+
| TC-C01 | COLUMN chart | **PASS** | `add_chart(SPREADSHEET_ID, sheet="Sales", chart_type="COLUMN", data_range="A1:D5", title="Sales by Quarter")` → `chartId: 1019744218`, `success: true`, `basicChart.chartType: "COLUMN"` in the response spec. |
|
|
73
|
+
| TC-CAL01 | Returns subscribed calendars | **PASS** | `list_calendars()` → 11 calendars returned. Each has `id`, `summary`, `time_zone`, `access_role`, `primary`. Exactly one (`kevin.huisman@gmail.com`) has `primary: true`. |
|
|
74
|
+
| TC-CAL09 | No time filters — upcoming events | **PASS** | `list_events(calendar_id="kevin.huisman@gmail.com")` (no time filter) → events ordered by start time, earliest returned event starts 2026-07-07 (after "now" = 2026-07-04) — no past events present. Each entry has `id`, `summary`, `start`, `end`, `status`. |
|
|
75
|
+
| TC-CAL20 | Timed event ⚠️ destructive | **PASS** | `create_event(calendar_id="kevin.huisman@gmail.com", summary="QA-Timed-Test", start="2026-07-01T10:00:00-07:00", end="2026-07-01T11:00:00-07:00", timezone="America/Los_Angeles")` → `id: "7p8utbir5rmagpjl2n67rke8pg"`, `start`/`end` are RFC 3339 with `-07:00` offset, `html_link` present, `status: "confirmed"`. Confirmed via follow-up `get_event` (same calendar_id/event_id) — full details echoed back including `created`/`updated` timestamps. Playwright visual verification was **skipped** per the concurrent docs shard's heavy use of the shared browser tab; API-response confirmation (`create_event` + `get_event`) was used instead, consistent with `run.md`'s prescribed fallback pattern for cases where Playwright isn't practical. |
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# QA Run — 2026-07-04 (Infrastructure shard)
|
|
2
|
+
|
|
3
|
+
**Auth:** OAuth
|
|
4
|
+
**Fixtures:** SPREADSHEET_ID=`15hOwO1Jay26PyxjjYtq9Pq-gEd8lDa81g-C13-GyvCA` · DOC_ID=`1-whiEVwvnSOABaK9qgpzdVaGUOMRvJdQhDmCURqx4fA` · FOLDER_ID=`1RCnhFTjh4aT8AJ_Rx5J9s8O2Kqta1vJE`
|
|
5
|
+
**Scope:** This is one of four parallel shards for the v0.8.1 live QA gate. This shard covers ONLY `docs/qa/tests/infra.md`. No server restarts or env var/config changes were performed (would drop the shared MCP connection all four shards depend on).
|
|
6
|
+
|
|
7
|
+
## Step 0 — Fixture verification
|
|
8
|
+
|
|
9
|
+
- `list_sheets(TEST_SPREADSHEET_ID)` → `["Sales", "Empty", "Notes & Misc"]` — all three expected tabs present.
|
|
10
|
+
- `get_sheet_data(sheet="Sales", range="A1:D6")` → header row + Widget/Gadget/Donut/Gizmo/Totals, columns A–D, 6 rows total. Matches expected seed state. No restore needed.
|
|
11
|
+
- `get_sheet_data(sheet="Empty")` → confirmed empty before the TC-I02 write/read probe below.
|
|
12
|
+
|
|
13
|
+
Fixture state: **correct**, no restore performed. (Other shards may be mutating rows past row 6 or adding extra sheet tabs concurrently — out of scope for this check, not treated as corruption.)
|
|
14
|
+
|
|
15
|
+
## Summary
|
|
16
|
+
|
|
17
|
+
| Category | PASS | FAIL | SKIP | Total |
|
|
18
|
+
|---|---|---|---|---|
|
|
19
|
+
| Infrastructure | 7 | 0 | 14 | 21 |
|
|
20
|
+
|
|
21
|
+
Breakdown of the 7 PASS: 1 newly executed live (TC-I02) + 6 carried forward from existing dated Result entries in `infra.md` (TC-I16–I21), per the file's own Coverage Strategy table. This is **not** a fabricated full 21-PASS run — see "Full results" below for the honest per-TC breakdown.
|
|
22
|
+
|
|
23
|
+
## Failures
|
|
24
|
+
|
|
25
|
+
*(none)*
|
|
26
|
+
|
|
27
|
+
## Tool coverage (this shard only)
|
|
28
|
+
|
|
29
|
+
| Tool | TC(s) | Status |
|
|
30
|
+
|---|---|---|
|
|
31
|
+
| update_cells | TC-I02 | ✅ pass |
|
|
32
|
+
| get_sheet_data | TC-I02 | ✅ pass |
|
|
33
|
+
| list_sheets | TC-I21 (carried forward) | ✅ pass (per existing Result entry) |
|
|
34
|
+
|
|
35
|
+
(Full tool coverage across all domains is aggregated by the conductor from all four shards' results files, not by this shard alone.)
|
|
36
|
+
|
|
37
|
+
## Full results
|
|
38
|
+
|
|
39
|
+
| TC | Title | Outcome | Notes |
|
|
40
|
+
|---|---|---|---|
|
|
41
|
+
| TC-I01 | Structure cache TTL — stale entry causes re-fetch | SKIP | requires server restart/config change — out of scope for this scoped background shard; see file's Coverage Strategy table (`tests/test_cache.py`) for unit-test coverage |
|
|
42
|
+
| TC-I02 | SQLite WAL mode — concurrent reads during a write | **PASS** | Fired `update_cells(sheet="Empty", range="A1", data=[["TC-I02-WAL-test"]])` and `get_sheet_data(sheet="Sales", range="A1:D6")` as two parallel tool calls in the same turn. Both returned successfully: write confirmed `updatedRange: Empty!A1`, read returned the full expected 6-row Sales grid unchanged. No error, no blocking, no locking exception surfaced to the client on either call. (Server-side log inspection for a SQLite lock error was not performed — this shard has no log/file access — but no error propagated through the MCP layer, satisfying the two directly observable Checks: read does not block/error, both calls return valid responses.) Test marker cleared afterward via `clear_values(sheet="Empty", range="A1")` to restore fixture state. |
|
|
43
|
+
| TC-I03 | CACHE_DB_PATH env var respected | SKIP | requires server restart/config change — out of scope for this scoped background shard; see file's Coverage Strategy table for unit-test coverage or prior manual verification |
|
|
44
|
+
| TC-I04 | Cache persists across server restarts | SKIP | requires server restart/config change — out of scope for this scoped background shard; see file's Coverage Strategy table for unit-test coverage or prior manual verification |
|
|
45
|
+
| TC-I05 | CLI flag — only specified tools registered | SKIP | requires server restart/config change — out of scope for this scoped background shard; see file's Coverage Strategy table for unit-test coverage or prior manual verification |
|
|
46
|
+
| TC-I06 | ENABLED_TOOLS env var — same behavior as CLI flag | SKIP | requires server restart/config change — out of scope for this scoped background shard; see file's Coverage Strategy table for unit-test coverage or prior manual verification |
|
|
47
|
+
| TC-I07 | Unlisted tool called by name | SKIP | requires server restart/config change — out of scope for this scoped background shard; see file's Coverage Strategy table for unit-test coverage or prior manual verification |
|
|
48
|
+
| TC-I08 | CREDENTIALS_CONFIG (base64 service account) | SKIP | requires server restart/config change — out of scope for this scoped background shard; see file's Coverage Strategy table for unit-test coverage or prior manual verification |
|
|
49
|
+
| TC-I09 | SERVICE_ACCOUNT_PATH | SKIP | requires server restart/config change — out of scope for this scoped background shard; see file's Coverage Strategy table for unit-test coverage or prior manual verification |
|
|
50
|
+
| TC-I10 | OAuth flow (CREDENTIALS_PATH / TOKEN_PATH) | SKIP | requires server restart/config change — out of scope for this scoped background shard; see file's Coverage Strategy table for unit-test coverage or prior manual verification |
|
|
51
|
+
| TC-I11 | Application Default Credentials (ADC) | SKIP | requires server restart/config change — out of scope for this scoped background shard; see file's Coverage Strategy table for unit-test coverage or prior manual verification |
|
|
52
|
+
| TC-I12 | No credentials — server fails to start with clear error | SKIP | requires server restart/config change — out of scope for this scoped background shard; see file's Coverage Strategy table for unit-test coverage or prior manual verification |
|
|
53
|
+
| TC-I13 | stdio transport | SKIP | requires server restart/config change — out of scope for this scoped background shard; see file's Coverage Strategy table for unit-test coverage or prior manual verification |
|
|
54
|
+
| TC-I14 | SSE transport | SKIP | requires server restart/config change — out of scope for this scoped background shard; see file's Coverage Strategy table for unit-test coverage or prior manual verification |
|
|
55
|
+
| TC-I15 | Hot reload with SSE | SKIP | requires server restart/config change — out of scope for this scoped background shard; see file's Coverage Strategy table for unit-test coverage or prior manual verification |
|
|
56
|
+
| TC-I16 | DEBUG_LEVEL=DEBUG — debug and access logs appear | PASS (carried forward) | Already verified, see existing Result entry in `docs/qa/tests/infra.md` dated 2026-06-23, unchanged since (no logging/arg-validation code touched by the changes motivating this QA pass) |
|
|
57
|
+
| TC-I17 | DEBUG_LEVEL=INFO — access logs only, no debug lines | PASS (carried forward) | Already verified, see existing Result entry in `docs/qa/tests/infra.md` dated 2026-06-23, unchanged since (no logging/arg-validation code touched by the changes motivating this QA pass) |
|
|
58
|
+
| TC-I18 | LOG_FILE — server output written to file | PASS (carried forward) | Already verified, see existing Result entry in `docs/qa/tests/infra.md` dated 2026-06-23, unchanged since (no logging/arg-validation code touched by the changes motivating this QA pass) |
|
|
59
|
+
| TC-I19 | ACCESS_LOG_FILE — access lines written to separate file | PASS (carried forward) | Already verified, see existing Result entry in `docs/qa/tests/infra.md` dated 2026-06-23, unchanged since (no logging/arg-validation code touched by the changes motivating this QA pass) |
|
|
60
|
+
| TC-I20 | .env file loaded at startup | PASS (carried forward) | Already verified, see existing Result entry in `docs/qa/tests/infra.md` dated 2026-06-23, unchanged since (no logging/arg-validation code touched by the changes motivating this QA pass) |
|
|
61
|
+
| TC-I21 | Unrecognized tool kwargs raise a validation error (issue #239) | PASS (carried forward) | Already verified, see existing Result entry in `docs/qa/tests/infra.md` dated 2026-07-04, unchanged since (no logging/arg-validation code touched by the changes motivating this QA pass) |
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# QA Run — 2026-07-04 (Sheets Read shard)
|
|
2
|
+
|
|
3
|
+
**Auth:** OAuth
|
|
4
|
+
**Fixtures:** SPREADSHEET_ID=`15hOwO1Jay26PyxjjYtq9Pq-gEd8lDa81g-C13-GyvCA`
|
|
5
|
+
**Scope:** This is one of four parallel shards for the v0.8.1 live QA gate. This shard covers ONLY `docs/qa/tests/sheets_read.md` (39 TCs) — a full run, since `get_sheet_data` changed for issue #235 in this release. No server restarts or env var/config changes were performed (would drop the shared MCP connection all four shards depend on).
|
|
6
|
+
**Start:** ~20:11 · **End:** ~20:57 (run cut short at the very last step — TC-R35 teardown — by a shared-server auth outage; see "Known issues" below)
|
|
7
|
+
|
|
8
|
+
## Step 0 — Fixture verification and cleanup
|
|
9
|
+
|
|
10
|
+
- `list_sheets` → `["Sales", "Empty", "Notes & Misc"]` — all three expected tabs present (a `BrandNew` tab appeared later from a concurrent shard — expected, out of scope).
|
|
11
|
+
- `get_sheet_data(sheet="Sales", range="A1:D6")` (values-only) initially returned **`$100.00`, `$200.00`, `$50.00`, `$300.00`, `$650.00`** for column B instead of plain numbers — column B (rows 2-6) carried stray `CURRENCY` `userEnteredFormat.numberFormat`, left over from a previous, incompletely-torn-down run of TC-R33. This directly conflicts with TC-R01/TC-R02's checks ("Totals values are computed 650/670/705 — not formula/formatted strings"). **Cleaned via `batch_update → repeatCell` clearing `userEnteredFormat.numberFormat` on Sales!B2:B6** before starting. Re-verified: `100, 120, 200, ..., 650, 670, 705` — clean.
|
|
12
|
+
- `get_sheet_data(sheet="Notes & Misc")` → `Date, Note` header + `7/4/2026, Setup complete` — matches known fixture state.
|
|
13
|
+
- Also found (while running TC-R31/32): the header row Sales!A1:D1 carried leftover `bold=true` + light-blue background + center alignment, same stale-leftover class as above. Cleaned incrementally as each TC's own prescribed teardown ran over A1 and B1 (see notes on TC-R31/R32 below).
|
|
14
|
+
|
|
15
|
+
Fixture state: **corrected then correct.** (Rows/tabs beyond row 6 or extra tabs belong to concurrent shards — not treated as corruption, per scope instructions.)
|
|
16
|
+
|
|
17
|
+
## Summary
|
|
18
|
+
|
|
19
|
+
| Category | PASS | FAIL | SKIP | Total |
|
|
20
|
+
|---|---|---|---|---|
|
|
21
|
+
| Sheets Read | 36 | 2 | 1 | 39 |
|
|
22
|
+
|
|
23
|
+
## Failures
|
|
24
|
+
|
|
25
|
+
**TC-R10 — Mixed cells — formulas and literals**
|
|
26
|
+
Expected: "B2 returns `=TODAY()` (formula string)".
|
|
27
|
+
Observed: The test case's cell reference does not match the actual fixture layout. Per `setup.md`'s seed data, Notes & Misc is `Date` (column A), `Note` (column B) — so **A2** contains `=TODAY()` and **B2** contains the literal `"Setup complete"`. Live call to `get_sheet_formulas(sheet="Notes & Misc")` returned `[["Date","Note"],["=TODAY()","Setup complete"]]` — i.e., the tool correctly differentiates formula vs. literal cells (no computed value where a formula exists, header row is literal), but strictly by the check as written, B2 does not return `=TODAY()`. This reads as a test-file documentation defect (swapped A2/B2 reference) rather than a product bug — recording FAIL rather than silently "fixing" the interpretation, per the checks-as-written. Recommend the test file be corrected to reference A2.
|
|
28
|
+
|
|
29
|
+
**TC-R19 — rows_to_fetch=0 — clamped to 1 (🔍 product decision)**
|
|
30
|
+
Expected: "Server clamps to 1 (`max(1, 0)`)" and "Behaves identically to TC-R18."
|
|
31
|
+
Observed: **Cache-state-dependent bug**, not just a product-decision question. Traced to source (`src/mcp_gee_sweet/tools/sheets/data.py:304`, `src/mcp_gee_sweet/cache.py:189`):
|
|
32
|
+
- **Cold cache** (immediately after `refresh_cache`, `rows_to_fetch=0` as the very first call): correctly clamps via `max(1, rows_to_fetch)` on the fetch path (`data.py:304`) → range `A1:1` → `first_rows: []`. Matches TC-R18.
|
|
33
|
+
- **Warm cache** (cache already primed by an earlier call with a larger `rows_to_fetch`, e.g. the default 5 from TC-R16): the cache-hit truncation path (`cache.py:189`, `data["first_rows"][: rows_to_fetch - 1]`) does **not** apply the same clamp. `rows_to_fetch - 1` = `0 - 1` = `-1`, and Python's negative-slice semantics (`[:-1]`) silently drop only the *last* cached row instead of returning an empty list. Live-verified: on a warm cache, `rows_to_fetch=0` returned 3 data rows (Widget/Gadget/Donut — all but the 4th cached row, Gizmo), not the empty list TC-R18-equivalent behavior would produce.
|
|
34
|
+
|
|
35
|
+
This is more than the anticipated 🔍 "should 0 mean only-headers or clamp-to-1" product question — it's an actual inconsistency: the *same* input (`rows_to_fetch=0`) produces different, disagreeing outputs depending on prior cache state, regardless of which product answer is intended. Recording FAIL (not the usual auto-PASS for 🔍-tagged cases) because a real defect was found underneath the product question. Suggested fix: apply the same `max(1, rows_to_fetch)` clamp in `cache.py`'s truncation slice as `data.py` applies on the fetch path.
|
|
36
|
+
|
|
37
|
+
## Known issues at end of run
|
|
38
|
+
|
|
39
|
+
1. **TC-R35 teardown incomplete.** `clear_values(sheet="Empty", range="A1:A10")` (removing the 10 PADTEST marker rows written for the size-cap test) is currently failing with `invalid_grant: Token has been expired or revoked` — a shared-server OAuth auth outage, confirmed also affecting at least one other concurrent shard (`get_storage_quota` and `write_doc_content` also returned 500 in the same window, per `/tmp/mcp-gee-sweet.log`). Retried 4 times over ~2 minutes with no recovery; stopped retrying to avoid hammering the API. **`Empty!A1:A10` on the live fixture still contains the 10 PADTEST rows and needs to be cleared once auth is restored** — this is not fixture corruption from another shard, it's this shard's own untorn-down test data.
|
|
40
|
+
2. This same outage is why `TC-R03e` (which requires a server restart to test `MAX_TOOL_RESPONSE_CHARS`) was never attempted — restarting was already out of scope per instructions (would drop all four shards' shared connection), independent of the auth issue.
|
|
41
|
+
|
|
42
|
+
## Tool coverage (this shard only)
|
|
43
|
+
|
|
44
|
+
| Tool | TC(s) | Status |
|
|
45
|
+
|---|---|---|
|
|
46
|
+
| get_sheet_data | TC-R01, R02, R03, R03b, R03c, R03d, R04, R05, R06, R07, R31, R32, R33 | ✅ all pass |
|
|
47
|
+
| get_sheet_formulas | TC-R08, R09, R10, R11 | ❌ TC-R10 fails (test-file cell-reference defect, not product bug — see Failures) |
|
|
48
|
+
| get_multiple_sheet_data | TC-R12, R13, R14, R15, R34 | ✅ all pass |
|
|
49
|
+
| get_multiple_spreadsheet_summary | TC-R16, R17, R18, R19, R20, R21, R22 | ❌ TC-R19 fails (cache-truncation clamp bug — see Failures) |
|
|
50
|
+
| find_in_spreadsheet | TC-R23, R24, R25, R26, R27, R28, R29, R30, R35 | ✅ all pass |
|
|
51
|
+
|
|
52
|
+
## Full results
|
|
53
|
+
|
|
54
|
+
| TC | Title | Outcome | Notes |
|
|
55
|
+
|---|---|---|---|
|
|
56
|
+
| TC-R01 | Happy path — fetch all data | PASS | 6 rows, 4 cols, Totals row computed (650/670/705), no error. Required a fixture-cleanup pass first (see Step 0). |
|
|
57
|
+
| TC-R02 | Explicit range A1:C3 | PASS | Exactly 3 rows/3 cols, values match (Product/Q1/Q2, Widget/100/120, Gadget/200/180). |
|
|
58
|
+
| TC-R03 | Grid data with explicit range | PASS | `rowData` present for A1:D6, explicit range honored, no auto-detect probe needed. |
|
|
59
|
+
| TC-R03b | Grid data w/o range auto-detects used range (#235) | PASS | No range + `include_grid_data=True` on a sheet with `gridProperties: rowCount=3016, columnCount=33` returned `rowData` scoped to the true 6x4 used range only. No error. |
|
|
60
|
+
| TC-R03c | Densely formatted range over cap raises clear error (#235) | PASS | Created scratch sheet `SizeTestR03`, formatted A1:Z1000 (bold + bg + NUMBER format), then `get_sheet_data(range="A1:Z200", include_grid_data=True)` raised `ValueError`: "the response is 4085899 characters, over the 40000-character safety cap" — mentions size, cap, and narrowing/`local_path`/`MAX_TOOL_RESPONSE_CHARS` options. Scratch sheet deleted after. |
|
|
61
|
+
| TC-R03d | local_path bypasses cap, writes to disk (#235) | PASS | Same setup as R03c + `local_path` → returned `{local_path, spreadsheet_id, sheet, range, bytes_written: 4085899}` (no data inline). File verified on disk: `wc -c` matched `bytes_written` exactly, `rowData` present with formatting (`bold: true` confirmed via `json.load`). Cleaned up. |
|
|
62
|
+
| TC-R03e | MAX_TOOL_RESPONSE_CHARS raises the cap (#235) | SKIP | Requires a server restart with a changed env var — out of scope for this background shard (would drop the shared MCP connection all four shards depend on). Same status as prior runs; unit-test coverage exists per the test file's own note. |
|
|
63
|
+
| TC-R04 | Non-existent sheet name | PASS | `HttpError 400: "Unable to parse range: DoesNotExist"` — clear error referencing the bad name, not an empty result. |
|
|
64
|
+
| TC-R05 | Non-existent spreadsheet ID | PASS | `HttpError 404: "Requested entity was not found."` — clean API error, no crash, no silent empty data. |
|
|
65
|
+
| TC-R06 | Range beyond data bounds | PASS | `A100:Z200` → `{"values": []}` (implicit — empty `valueRanges`), no error field. |
|
|
66
|
+
| TC-R07 | Sheet name with spaces/special chars | PASS | `'Notes & Misc'` resolved correctly, 2 rows (header + data), Date cell = `7/4/2026` (today, computed from `=TODAY()`). |
|
|
67
|
+
| TC-R08 | Formulas — returns formula strings | PASS | Row 6 B–D: `=SUM(B2:B5)`, `=SUM(C2:C5)`, `=SUM(D2:D5)`. Data rows literal (100, 200, etc). Extra rows 7–9 present from a concurrent shard's writes — ignored per scope. |
|
|
68
|
+
| TC-R09 | Sheet with no formulas | PASS | Empty sheet → `{"result": []}`, no error. |
|
|
69
|
+
| TC-R10 | Mixed cells — formulas and literals | **FAIL** | See Failures section — test-file cell-reference defect (A2/B2 swapped vs. actual fixture), not a product bug. |
|
|
70
|
+
| TC-R11 | No range — fetches entire sheet | PASS | Identical to TC-R08's result; confirms default (no range) behavior matches explicit full-sheet fetch. |
|
|
71
|
+
| TC-R12 | Multiple valid queries | PASS | Sales + Notes & Misc both returned correct data, no `error` field on either. |
|
|
72
|
+
| TC-R13 | One query missing required keys | PASS | Sales succeeded; second query (no `sheet` key) returned `{"error": "Missing required keys (spreadsheet_id, sheet)"}` — didn't crash the other result. |
|
|
73
|
+
| TC-R14 | All queries fail | PASS | Both `FakeSheet1`/`FakeSheet2` queries returned per-item `error` fields; response was a list of two error objects, not a top-level exception. |
|
|
74
|
+
| TC-R15 | Empty queries list | PASS | `queries: []` → `{"result": []}`. |
|
|
75
|
+
| TC-R16 | Multiple spreadsheet summary — happy path | PASS | All 3 expected sheets present (Sales, Empty, Notes & Misc) plus concurrent shard's `BrandNew`; Sales headers + first rows correct; Empty sheet has `headers: [], first_rows: []`. |
|
|
76
|
+
| TC-R17 | Cache hit — second call skips API | PASS | Repeated call returned identical data; confirmed via `/tmp/mcp-gee-sweet.log`: `DEBUG mcp_gee_sweet.cache Sheet data cache hit: <id>/0` (and for the other 3 sheet IDs) logged on the second call. |
|
|
77
|
+
| TC-R18 | rows_to_fetch=1 — only header | PASS | `headers` present for Sales, `first_rows: []`. |
|
|
78
|
+
| TC-R19 | rows_to_fetch=0 — clamped to 1 (🔍) | **FAIL** | See Failures section — cache-state-dependent bug found beneath the product-decision question. |
|
|
79
|
+
| TC-R20 | Spreadsheet with empty sheet | PASS | Empty sheet entry: `headers: [], first_rows: []`, no error; other sheets (Sales, Notes & Misc) unaffected in the same response. |
|
|
80
|
+
| TC-R21 | Invalid spreadsheet ID in list | PASS | Valid ID returned normal summary; `invalidid123xyz` entry had `error: "...404...Requested entity was not found..."`; both present, partial failure not top-level error. |
|
|
81
|
+
| TC-R22 | Range format verification, rows_to_fetch=3 (🔍) | PASS | Sales returned header + 2 data rows (Widget, Gadget); columns B/C/D (Q1/Q2/Q3) present, not just column A — confirms `A1:{n}` bare-row-number range returns all columns in production, resolving the product-decision question in `docs/notes-read.md` in favor of "yes, all columns." |
|
|
82
|
+
| TC-R23 | Match found in specific sheet | PASS | `find_in_spreadsheet(query="Gadget", sheet="Sales")` → `[{"sheet":"Sales","cell":"A3","value":"Gadget"}]`. |
|
|
83
|
+
| TC-R24 | Match across all sheets | PASS | `find_in_spreadsheet(query="Setup complete")` (no sheet filter) → found in `Notes & Misc!B2`. |
|
|
84
|
+
| TC-R25 | Case-insensitive match (default) | PASS | Lowercase `"gadget"` query still matched `"Gadget"`. |
|
|
85
|
+
| TC-R26 | Case-sensitive match | PASS | `case_sensitive=true` + `"gadget"` → `[]` (no match, since fixture has capital G). |
|
|
86
|
+
| TC-R27 | max_results respected | PASS | `query="Q", max_results=2` → exactly 2 results (Q1, Q2), despite 3 header cells matching. |
|
|
87
|
+
| TC-R28 | No matches | PASS | `"ZZZnoMatch"` → `[]`. |
|
|
88
|
+
| TC-R29 | Sheet name not found | PASS | `sheet="DoesNotExist"` → `[{"error": "Sheet 'DoesNotExist' not found"}]` — error entry, not a top-level exception, references the sheet name. |
|
|
89
|
+
| TC-R30 | Multiple column matches in same row | PASS | `query="Q"` on Sales → 3 separate results (B1/Q1, C1/Q2, D1/Q3) — per-cell granularity confirmed. |
|
|
90
|
+
| TC-R31 | Bold text format readable via effectiveFormat | PASS | Applied bold to Sales A1, asserted `effectiveFormat.textFormat.bold=true`, `italic=false`, `formattedValue="Product"`. Teardown cleared `userEnteredFormat` from A1 (also removing stale leftover header decoration — see Step 0). |
|
|
91
|
+
| TC-R32 | Background color + italic via effectiveFormat | PASS | First attempt was contaminated by the same stale leftover header format on B1 (`bold=true` persisted since the field mask didn't touch it) — cleaned B1 fully and re-ran cleanly: `italic=true`, `bold=false`, `backgroundColor={red:1, green:0.8980392, blue:0.6}`. All checks pass on the clean re-run. Teardown cleared B1. |
|
|
92
|
+
| TC-R33 | Number format + formattedValue via effectiveFormat | PASS | Applied CURRENCY format to B2, asserted `numberFormat={type:"CURRENCY", pattern:"$#,##0.00"}`, `formattedValue="$100.00"`, `effectiveValue.numberValue=100`. Teardown cleared B2; confirmed back to plain `"100"`. |
|
|
93
|
+
| TC-R34 | get_multiple_sheet_data — 200 queries trips cap (#242) | PASS | 200 identical Sales queries raised `ValueError`: "the response is 93184 characters, over the 40000-character safety cap ... MAX_TOOL_RESPONSE_CHARS". 2 queries + `local_path` succeeded: `{"local_path":..., "bytes_written":2048, "query_count":2}` — file verified (`wc -c` = 2048) then deleted. |
|
|
94
|
+
| TC-R35 | find_in_spreadsheet — max_results bounds count not size (#242) | PASS | Wrote 10x ~4785-char PADTEST strings to `Empty!A1:A10`. Plain search raised `ValueError`: "the response is 44173 characters, over the 40000-character safety cap" despite `match_count=10` ≪ `max_results=50` default. `local_path` call succeeded: `{"local_path":..., "match_count":10, "bytes_written":44173}` — file verified then deleted. **Teardown (`clear_values` on Empty!A1:A10) could not complete — blocked by the auth outage; see "Known issues."** |
|