mcp-gee-sweet 0.9.2.dev310__tar.gz → 0.9.2.dev313__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.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/PKG-INFO +1 -1
- mcp_gee_sweet-0.9.2.dev313/docs/design/doc-revision-export.md +126 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/design/index.md +1 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/known-limitations.md +31 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/qa/tests/drive_transfer.md +191 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/roadmap.md +4 -1
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/tools.md +1 -1
- mcp_gee_sweet-0.9.2.dev313/src/mcp_gee_sweet/tools/docs/markdown_export.py +371 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/src/mcp_gee_sweet/tools/drive/transfer.py +162 -45
- mcp_gee_sweet-0.9.2.dev313/tests/test_docs_revision_export.py +468 -0
- mcp_gee_sweet-0.9.2.dev310/src/mcp_gee_sweet/tools/docs/markdown_export.py +0 -141
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/.claude/commands/cleanup-worktrees.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/.claude/commands/merge-pr.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/.claude/commands/notify-kai.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/.claude/commands/notify-partner.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/.claude/commands/orchestrator.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/.claude/commands/prep-for-pr.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/.claude/commands/qa-kickoff.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/.claude/commands/release.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/.claude/commands/retro.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/.claude/commands/roadmap-status.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/.claude/commands/start-worktree.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/.claude/commands/team-member.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/.claude/commands/verify-pr.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/.claude/settings.json +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/.claude/team-roles/amy.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/.claude/team-roles/aziz.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/.claude/team-roles/bob.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/.claude/team-roles/dev.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/.claude/team-roles/joy.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/.claude/team-roles/kai.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/.claude/team-roles/qa.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/.dockerignore +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/.github/dependabot.yml +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/.github/workflows/ci.yml +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/.github/workflows/docs.yml +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/.github/workflows/publish-dev.yml +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/.github/workflows/release.yml +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/.gitignore +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/.pre-commit-config.yaml +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/.python-version +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/CHANGELOG.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/CLAUDE.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/CODE_OF_CONDUCT.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/CONTRIBUTING.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/Dockerfile +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/LICENSE +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/Makefile +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/README.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/SECURITY.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docker-compose.yml +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/assets/logo.png +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/auth.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/client-setup.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/configuration.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/decisions/decision-async-tool-execution.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/decisions/decision-auth-status-limitation-categories.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/decisions/decision-cache-invalidation.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/decisions/decision-chart-theming.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/decisions/decision-comments-first-class.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/decisions/decision-composite-tools.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/decisions/decision-condition-type-docstring-generation.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/decisions/decision-converted-doc-change-detection.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/decisions/decision-dev-team.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/decisions/decision-docs-formatting.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/decisions/decision-fork.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/decisions/decision-gmail-domain.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/decisions/decision-grid-data-size-cap.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/decisions/decision-local-fs-test-harness.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/decisions/decision-mcp-v2-resource-context-access.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/decisions/decision-pillow-image-dependency.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/decisions/decision-prompt-qa-role.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/decisions/decision-publishing.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/decisions/decision-release-docs-roles.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/decisions/decision-repositioning.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/decisions/decision-response-size-cap-generalization.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/decisions/decision-response-size-cap-reevaluation-519.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/decisions/decision-review-finding-ticket-bar.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/decisions/decision-supports-all-drives-lint-check.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/decisions/decision-sync-folder-recursion.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/decisions/decision-testing.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/decisions/decision-tool-error-visibility.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/decisions/index.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/decisions/role-file-history.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/design/blockquote-representation.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/design/borderless-table-columns.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/design/doc-to-markdown.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/design/docs-ast-pipeline.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/design/effectiveformat-spike.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/design/heading-anchor-resolution.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/design/image-conversion.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/design/local-file-access-policy.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/design/markdown-support.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/design/module-history.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/design/oauth-reauthorize-from-tool-call.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/design/phase3-theme-system.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/design/sse-consent-off-event-loop.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/design.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/index.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/notes-read.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/qa/README.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/qa/fixtures/qa-fixture-pixel.png +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/qa/fixtures/tc-d195-create-doc.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/qa/fixtures/tc-d196-create-doc.html +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/qa/fixtures/tc-d213-dollar-escape.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/qa/fixtures/tc-d226-heading-table.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/qa/fixtures/tc-doc102-image-conversion.html +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/qa/fixtures/tc-doc103-image-conversion.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/qa/fixtures/tc-doc104-nested-lists.html +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/qa/fixtures/tc-doc105-nested-lists-gfm.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/qa/fixtures/tc-doc106-nested-lists-4space.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/qa/fixtures/tc-doc135-paragraph-boundary.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/qa/fixtures/tc-doc136-interrupted-block-boundary.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/qa/fixtures/tc-doc137-inline-hr-no-block-ancestor.html +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/qa/fixtures/tc-doc138-nbsp-spacer-paragraph.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/qa/fixtures/tc-doc141-mismatched-list-tags.html +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/qa/fixtures/tc-doc142-heading-anchors.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/qa/fixtures/tc-doc144-anchor-in-table-cell.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/qa/fixtures/tc-doc145-anchor-on-first-element.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/qa/fixtures.template.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/qa/gmail-test-plan.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/qa/operations.yaml +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/qa/playwright_oauth.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/qa/results/.gitkeep +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/qa/results/2026-06-02.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/qa/results/2026-06-14.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/qa/results/2026-06-17.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/qa/results/2026-06-18.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/qa/results/2026-06-21-drive-discovery.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/qa/results/2026-06-21-sheets-delete-clear.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/qa/results/2026-06-28.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/qa/results/2026-07-04-docs.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/qa/results/2026-07-04-drive-smoke.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/qa/results/2026-07-04-infra.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/qa/results/2026-07-04-sheets_read.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/qa/results/2026-07-04.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/qa/results/2026-09-04.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/qa/retro-v0.8.0.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/qa/retro-v0.9.0.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/qa/run.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/qa/runs/README.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/qa/runs/v0.8.0.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/qa/runs/v0.8.1.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/qa/runs/v0.9.0.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/qa/setup.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/qa/tests/calendar.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/qa/tests/docs_content.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/qa/tests/docs_layout.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/qa/tests/docs_style.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/qa/tests/docs_tables.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/qa/tests/drive_activity.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/qa/tests/drive_files.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/qa/tests/drive_sharing.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/qa/tests/gmail.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/qa/tests/infra.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/qa/tests/sheets_charts.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/qa/tests/sheets_mgmt.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/qa/tests/sheets_read.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/qa/tests/sheets_write.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/docs/style-guide.md +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/mkdocs.yml +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/pyproject.toml +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/pyrightconfig.json +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/scripts/check_supports_all_drives.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/scripts/gen_tool_docs.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/scripts/lane_context_hook.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/scripts/oauth_setup.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/scripts/qa_cancel_insert_local_images.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/scripts/qa_gmail_fixtures.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/scripts/setup_team.sh +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/scripts/write_team_mcp_configs.sh +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/server.json +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/src/mcp_gee_sweet/.env.template +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/src/mcp_gee_sweet/__init__.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/src/mcp_gee_sweet/auth.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/src/mcp_gee_sweet/cache.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/src/mcp_gee_sweet/http_transport.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/src/mcp_gee_sweet/reauth.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/src/mcp_gee_sweet/server.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/src/mcp_gee_sweet/sse_shutdown.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/src/mcp_gee_sweet/tools/__init__.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/src/mcp_gee_sweet/tools/atomic_write.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/src/mcp_gee_sweet/tools/cache.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/src/mcp_gee_sweet/tools/calendar.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/src/mcp_gee_sweet/tools/concurrency.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/src/mcp_gee_sweet/tools/docs/__init__.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/src/mcp_gee_sweet/tools/docs/anchors.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/src/mcp_gee_sweet/tools/docs/ast.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/src/mcp_gee_sweet/tools/docs/ast_to_markdown.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/src/mcp_gee_sweet/tools/docs/comments.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/src/mcp_gee_sweet/tools/docs/content.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/src/mcp_gee_sweet/tools/docs/doc_to_ast.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/src/mcp_gee_sweet/tools/docs/editing.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/src/mcp_gee_sweet/tools/docs/emitter.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/src/mcp_gee_sweet/tools/docs/html_parser.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/src/mcp_gee_sweet/tools/docs/images.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/src/mcp_gee_sweet/tools/docs/indices.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/src/mcp_gee_sweet/tools/docs/layout.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/src/mcp_gee_sweet/tools/docs/named_ranges.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/src/mcp_gee_sweet/tools/docs/style.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/src/mcp_gee_sweet/tools/docs/tables.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/src/mcp_gee_sweet/tools/drive/__init__.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/src/mcp_gee_sweet/tools/drive/activity.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/src/mcp_gee_sweet/tools/drive/files.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/src/mcp_gee_sweet/tools/drive/sharing.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/src/mcp_gee_sweet/tools/gmail.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/src/mcp_gee_sweet/tools/pagination.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/src/mcp_gee_sweet/tools/response_limits.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/src/mcp_gee_sweet/tools/sheets/__init__.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/src/mcp_gee_sweet/tools/sheets/data.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/src/mcp_gee_sweet/tools/sheets/helpers.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/src/mcp_gee_sweet/tools/sheets/structure.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/tests/__init__.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/tests/conftest.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/tests/drive/__init__.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/tests/drive/test_activity.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/tests/drive/test_files.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/tests/drive/test_sharing.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/tests/drive/test_transfer.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/tests/fakes.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/tests/integration/__init__.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/tests/integration/conftest.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/tests/integration/test_local_fs_harness.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/tests/sheets/__init__.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/tests/sheets/test_data.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/tests/sheets/test_helpers.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/tests/sheets/test_structure.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/tests/test_atomic_write.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/tests/test_auth.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/tests/test_cache.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/tests/test_calendar.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/tests/test_check_supports_all_drives.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/tests/test_concurrency.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/tests/test_docs_anchors.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/tests/test_docs_comments.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/tests/test_docs_content.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/tests/test_docs_core.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/tests/test_docs_editing.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/tests/test_docs_images.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/tests/test_docs_indices.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/tests/test_docs_layout.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/tests/test_docs_markdown_export.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/tests/test_docs_named_ranges.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/tests/test_docs_style.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/tests/test_docs_tables.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/tests/test_gen_tool_docs.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/tests/test_gmail.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/tests/test_lane_context_hook.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/tests/test_qa_gmail_fixtures.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/tests/test_reauth.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/tests/test_response_limits.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/tests/test_server.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/tests/test_server_json.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/tests/test_sse_shutdown.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/tests/test_team_mcp_configs.py +0 -0
- {mcp_gee_sweet-0.9.2.dev310 → mcp_gee_sweet-0.9.2.dev313}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: mcp-gee-sweet
|
|
3
|
-
Version: 0.9.2.
|
|
3
|
+
Version: 0.9.2.dev313
|
|
4
4
|
Summary: MCP server for Google Workspace — Sheets, Drive, Docs (HTML or Markdown), and Calendar.
|
|
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
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# Design: Google Doc Revisions as Markdown (`export_revision`)
|
|
2
|
+
|
|
3
|
+
**Status:** Implemented. Issue #959. Live-verified against real Drive/Docs API calls via scratch
|
|
4
|
+
scripts per `CLAUDE.md`'s "Verify a ticket's API premise live" convention (2026-10-11).
|
|
5
|
+
|
|
6
|
+
## Problem
|
|
7
|
+
|
|
8
|
+
`export_revision` handled only Sheets (XLSX export → cell values). `list_revisions` already
|
|
9
|
+
listed Doc revisions, but nothing could read one, so "what changed in this Doc since revision N"
|
|
10
|
+
couldn't be answered through the server. The issue asked for a Doc revision as Markdown "through
|
|
11
|
+
the same converter `get_doc_as_markdown` uses", so that diffing two revisions' exports shows only
|
|
12
|
+
the edit.
|
|
13
|
+
|
|
14
|
+
## The constraint
|
|
15
|
+
|
|
16
|
+
`get_doc_as_markdown` walks `documents().get()` into the AST (`doc-to-markdown.md`). The Docs API
|
|
17
|
+
reads only a document's **current** state; it has no revision parameter. Drive is the only source
|
|
18
|
+
of an old revision, and only as a file from the revision's `exportLinks`. Measured on a Doc
|
|
19
|
+
revision: `text/markdown`, `text/x-markdown`, `text/html`, `text/plain`, DOCX, ODT, RTF, PDF, EPUB,
|
|
20
|
+
and a zipped HTML.
|
|
21
|
+
|
|
22
|
+
## Options measured
|
|
23
|
+
|
|
24
|
+
Each option was run against the same Doc, written through `write_doc_content` with headings, bold
|
|
25
|
+
and italic, inline code, a fenced code block, a blockquote, nested ordered/bulleted lists, task
|
|
26
|
+
items, a table, an image, and heading links.
|
|
27
|
+
|
|
28
|
+
1. **Drive's own `text/markdown` export.** Lossy: inline code became plain text, the fenced code
|
|
29
|
+
block became soft-broken plain lines, the blockquote became a plain paragraph, a nested bullet
|
|
30
|
+
inside a numbered list became a separate numbered item with a literal `\-`, and the image was
|
|
31
|
+
inlined as a base64 `data:` URI (10KB for one small logo). It's a different dialect from
|
|
32
|
+
`get_doc_as_markdown`, and a much less faithful one.
|
|
33
|
+
2. **`text/html` export → this project's `html_parser` → AST.** Google's export carries styling in
|
|
34
|
+
CSS classes (`.c3{font-weight:700}`), which `html_parser` doesn't read, so bold, italic and code
|
|
35
|
+
fonts would be lost without a CSS resolver. Importing that HTML back into a Doc (to reuse the
|
|
36
|
+
Docs-side converter) was also measured: it split the code block into one fence per line and
|
|
37
|
+
turned every list item after the blockquote into a quote.
|
|
38
|
+
3. **DOCX export → import as a temporary Google Doc → `document_to_ast` → `ast_to_markdown`.**
|
|
39
|
+
The output was **identical** to `get_doc_as_markdown` on the live Doc, except for the two
|
|
40
|
+
import-specific values below. The zipped-HTML export can't be imported (400).
|
|
41
|
+
|
|
42
|
+
**Chosen: (3).** It is the same converter, not an equivalent one, so everything
|
|
43
|
+
`get_doc_as_markdown` supports and documents carries over unchanged.
|
|
44
|
+
|
|
45
|
+
## What differs on every import, and how it's made stable
|
|
46
|
+
|
|
47
|
+
Measured across two imports of the same DOCX:
|
|
48
|
+
|
|
49
|
+
- **Inline image `contentUri`.** A new URL each import; the image bytes are identical (same
|
|
50
|
+
SHA-256). `_stabilize_images` fetches each image (concurrency 8) while the temporary Doc still
|
|
51
|
+
exists and replaces its `contentUri` with `image:sha256-<first 16 hex digits>`, so an unchanged
|
|
52
|
+
image doesn't show in a diff and a changed one does. A failed fetch renders
|
|
53
|
+
`image:unavailable`. No viewable URL is returned: PR #988's QA round found the temporary Doc's
|
|
54
|
+
image URLs 404 (anonymously and authenticated) once it's deleted, so a returned URL would be
|
|
55
|
+
dead on arrival. (One earlier probe got a 200 right after deletion; it doesn't hold.)
|
|
56
|
+
- **Heading IDs and the document ID.** Every heading gets a new `headingId`, and
|
|
57
|
+
`doc_to_ast._link_url` rebuilds an ID-only heading link (the kind the Docs UI creates) as a URL
|
|
58
|
+
containing the temporary Doc's ID. That changes on every export and points at a deleted Doc.
|
|
59
|
+
`_stabilize_internal_links` rewrites a heading link to `#<slug>`, using anchors.py's GitHub-style
|
|
60
|
+
slug with `-1`/`-2` duplicate suffixes, so the link survives a round trip through the write
|
|
61
|
+
side's anchor resolver. The slug list holds only paragraphs that render as Markdown headings,
|
|
62
|
+
classified by `doc_to_ast._paragraph_to_node` itself (`Heading`, or a TITLE/SUBTITLE
|
|
63
|
+
`NamedBlock`): a bulleted heading renders as a list item and a heading in a table cell as cell
|
|
64
|
+
text, and counting them shifted every later duplicate's suffix onto the wrong heading (PR #988
|
|
65
|
+
QA round 1). A link to a heading that doesn't render as one is dropped, following the project's
|
|
66
|
+
rule that a wrong link is worse than none.
|
|
67
|
+
- **Bookmark links are dropped.** A bookmark has no text to name it, and any `#fragment` made up
|
|
68
|
+
for one can be fuzzy-matched by `anchors.py` to a heading when the Markdown is written back
|
|
69
|
+
(`#bookmark-1` → the second heading titled "Bookmark"; PR #988 QA round 1).
|
|
70
|
+
- URL links written by this project's own write side are already absolute URLs and pass through
|
|
71
|
+
unchanged.
|
|
72
|
+
|
|
73
|
+
## The temporary Doc
|
|
74
|
+
|
|
75
|
+
- Created in the default folder (`lc.folder_id`), like every other create path, so a service
|
|
76
|
+
account works when a Shared Drive is configured. A storageQuotaExceeded error renders with the
|
|
77
|
+
shared `_SA_QUOTA_ERROR` text. Who can see it there, and its 30 days in the trash when the
|
|
78
|
+
identity can only trash, is tracked in #989.
|
|
79
|
+
- Uploaded resumable: Drive takes a multipart upload only up to 5 MB, and a revision with
|
|
80
|
+
embedded images can exceed it.
|
|
81
|
+
- Named `export_revision temp (<file_id> r<revision_id>)`, so an orphan is identifiable.
|
|
82
|
+
- Created inside `anyio.CancelScope(shield=True)`, per `CLAUDE.md`'s cancellation rule: a
|
|
83
|
+
cancellation can't land after Drive created the Doc but before its ID is recorded.
|
|
84
|
+
- Removed in a shielded `finally`: deleted when `capabilities.canDelete` allows it, else trashed
|
|
85
|
+
(a Content manager on a Shared Drive can only trash, #876). The removal retries a 5xx or 429
|
|
86
|
+
(`num_retries=3`), since a failed removal leaves a copy of the revision in Drive. A failed removal
|
|
87
|
+
never hides the export: the result gets a `warning` naming the Doc's ID.
|
|
88
|
+
- Because it writes to Drive, `export_revision` is no longer annotated `readOnlyHint`; it's
|
|
89
|
+
`destructiveHint=False`, since it changes nothing of the caller's.
|
|
90
|
+
- About 5–6 seconds per call, measured, on top of the image fetches.
|
|
91
|
+
|
|
92
|
+
## Errors found while measuring
|
|
93
|
+
|
|
94
|
+
- The export endpoint answers rapid calls with HTTP 429 and an HTML page, without raising. The
|
|
95
|
+
Sheets path handed that page straight to openpyxl. `_fetch_revision_export` checks the status
|
|
96
|
+
for both paths.
|
|
97
|
+
- Docs folds the latest revision into the next one while editing continues: revision 18 was
|
|
98
|
+
listed, an edit 90 seconds later produced 19, and 18 then returned 404. A second run with a
|
|
99
|
+
5-minute gap merged 19 into 20 the same way. `export_revision` turns a "Revision not found" 404
|
|
100
|
+
into an error telling the caller to call `list_revisions` again. Any other 404, such as a bad
|
|
101
|
+
file ID ("File not found"), is re-raised unchanged. A Doc created by Drive import keeps its
|
|
102
|
+
import revision separate (as #814 measured).
|
|
103
|
+
- `revisions.list` returns at most 200 revisions per page by default (discovery document), and
|
|
104
|
+
`list_revisions` read only the first page. It now pages through every revision via the same
|
|
105
|
+
`_list_revisions` helper `sync_folder` uses.
|
|
106
|
+
- A Doc `local_path` is checked with `_validate_local_destination` before the export, so a bad
|
|
107
|
+
destination fails before the temporary Doc round trip.
|
|
108
|
+
|
|
109
|
+
## Live verification
|
|
110
|
+
|
|
111
|
+
Against a Doc imported through `upload_file(convert_to_doc=True)`, then edited by inserting one
|
|
112
|
+
word: exporting revision 1 (the import) and revision 2 and diffing the two `markdown` values gave
|
|
113
|
+
exactly one changed line, the edited paragraph. On a `write_doc_content` Doc with every construct
|
|
114
|
+
above plus two `Setup` headings linked by ID, the export matched `get_doc_as_markdown` except the
|
|
115
|
+
image line and the two links (`#setup`, `#setup-1`). With the first `Setup` bulleted, it rendered
|
|
116
|
+
as `- Setup`, the link to it was dropped, and the link to the second rendered as `#setup`.
|
|
117
|
+
|
|
118
|
+
## What this does NOT do
|
|
119
|
+
|
|
120
|
+
- **Comments.** They aren't part of a revision, so there's no `include_comments`.
|
|
121
|
+
- **Match `get_doc_as_markdown` byte for byte.** Heading links and images differ by design (above).
|
|
122
|
+
Diff a revision against another revision's export, not against the live Doc's
|
|
123
|
+
`get_doc_as_markdown`.
|
|
124
|
+
- **Show an old revision's images.** Only their hashes; see above.
|
|
125
|
+
- **Multi-tab documents: not measured.** How Drive's DOCX export handles a Doc's extra tabs
|
|
126
|
+
wasn't checked; `document_to_ast` reads only the imported Doc's body.
|
|
@@ -15,3 +15,4 @@ These are more granular than the [decision records](../decisions/index.md), whic
|
|
|
15
15
|
| [SSE OAuth Consent Off the Event Loop](sse-consent-off-event-loop.md) | 2026-10-01 | #833: the SSE consent wait on a daemon thread with a stoppable callback server, shared across connections, ended by sse_starlette's shutdown flag on SIGTERM |
|
|
16
16
|
| [Re-authorizing OAuth from the Failing Tool Call](oauth-reauthorize-from-tool-call.md) | 2026-10-06 | #873/#906: a revoked or missing token offers the consent link from the failing call (URL elicitation, or the link in the `ToolError`), detected at `thread_http`'s transport so tools that catch errors are covered, adopted in place on every connection |
|
|
17
17
|
| [Local-File Access Policy](local-file-access-policy.md) | 2026-10-10 | #796: `LOCAL_FILE_ACCESS=full\|roots\|off` resolved at import from argv (plus an HTTP-request tripwire for launches argv can't see), one `check_read`/`check_write` at each choke point and per entry in folder walks, an identity-based (`samefile`) deny-list of the server's own credential/config/cache/log files, `@tool(local_only=True)` unregistering nine tools in `off` mode, mode and roots reported in `server://auth-status` and the refusal |
|
|
18
|
+
| [Google Doc Revisions as Markdown](doc-revision-export.md) | 2026-10-11 | #959: `export_revision` on a Doc via DOCX export → temporary Doc → `get_doc_as_markdown`'s own converter (Drive's Markdown and HTML exports measured lossy), heading links and image URLs rewritten so two exports diff cleanly, shielded temp-Doc cleanup |
|
|
@@ -120,6 +120,37 @@ raw Docs API via `batch_update` passthrough) instead. Related: docs/design/doc-t
|
|
|
120
120
|
|
|
121
121
|
---
|
|
122
122
|
|
|
123
|
+
### `export_revision` on a Google Doc — links, images, and vanishing revision IDs
|
|
124
|
+
|
|
125
|
+
**What:** `export_revision` (#959) returns a Doc revision as Markdown through the same converter
|
|
126
|
+
as `get_doc_as_markdown`, with these differences and gaps:
|
|
127
|
+
|
|
128
|
+
1. A link to a heading renders as a GitHub-style `#slug` anchor, not as a docs.google.com URL.
|
|
129
|
+
A link to a bookmark, or to a heading that doesn't render as one (a bulleted heading, or one
|
|
130
|
+
in a table cell), is dropped and leaves its text. An image renders as
|
|
131
|
+
`image:sha256-<hash>`, with no viewable URL: the temporary Doc's image URLs stop working
|
|
132
|
+
once it's deleted, so an old revision's images can't be viewed this way.
|
|
133
|
+
2. The export doesn't include comments, which aren't part of a revision.
|
|
134
|
+
3. A revision ID from `list_revisions` can stop existing: Docs folds a recent revision into the
|
|
135
|
+
next one while editing continues (measured: edits 90 seconds and 5 minutes later both merged it).
|
|
136
|
+
The export then fails with an error saying to list revisions again.
|
|
137
|
+
4. Each call creates and removes a temporary Google Doc in the default folder, which takes
|
|
138
|
+
several seconds and needs storage there (a service account needs a Shared Drive default
|
|
139
|
+
folder). If removal fails, the result's `warning` names the Doc to delete by hand.
|
|
140
|
+
|
|
141
|
+
**Why:** The Docs API reads only a document's current state, so the revision is exported as DOCX
|
|
142
|
+
and imported into a temporary Doc to be read. That Doc's ID, heading IDs, and image URLs are new
|
|
143
|
+
on every import, so (1) replaces them with values that stay the same between exports and keep
|
|
144
|
+
diffs clean. A bookmark has no text to make a stable anchor from, and an invented one could be
|
|
145
|
+
matched to the wrong heading if the Markdown is written back. (3) is how Drive keeps Docs
|
|
146
|
+
revision history.
|
|
147
|
+
|
|
148
|
+
**Workaround:** For (1), diff one revision's export against another's, not against
|
|
149
|
+
`get_doc_as_markdown` output. For (3), export a revision only after editing has settled.
|
|
150
|
+
Related: docs/design/doc-revision-export.md.
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
123
154
|
## Google Drive
|
|
124
155
|
|
|
125
156
|
### Service account cannot create files in personal Drive
|
|
@@ -1933,11 +1933,202 @@ export_revision(rev 6, no range) → sheet="Sheet1", range=null, values = full 5
|
|
|
1933
1933
|
- Returns a clear error: "No XLSX export available for this revision"
|
|
1934
1934
|
- Does not crash
|
|
1935
1935
|
|
|
1936
|
+
**Superseded by #959:** a Google Doc revision now exports as Markdown (TC-D280), so this case
|
|
1937
|
+
checks a file that is neither a Sheet nor a Doc. Run it as:
|
|
1938
|
+
|
|
1939
|
+
**Setup:** Create a scratch Google Slides file named `qa-959-slides` in `DRIVE_FOLDER_ID`, and
|
|
1940
|
+
type one word on its first slide in the Slides UI (Playwright) so it has a revision.
|
|
1941
|
+
|
|
1942
|
+
**Tool calls**
|
|
1943
|
+
1. `list_revisions(file_id="<slides-id>")`, then
|
|
1944
|
+
`export_revision(file_id="<slides-id>", revision_id="<last revisionId>")`
|
|
1945
|
+
|
|
1946
|
+
**Checks**
|
|
1947
|
+
- Raises a clear error: "No XLSX or DOCX export available for revision <id>. export_revision
|
|
1948
|
+
supports Google Sheets and Google Docs files only."
|
|
1949
|
+
- Does not crash
|
|
1950
|
+
|
|
1951
|
+
**Teardown:** `delete_file(file_id="<slides-id>")`.
|
|
1952
|
+
|
|
1953
|
+
The 2026-09-04 Result below predates #959 and tested a Google Doc.
|
|
1954
|
+
|
|
1955
|
+
**Result (2026-10-11, PR #988 round 1, `8b223c5`) ✅ PASS** — Ran via `mcp-gee-sweet-kit` (OAuth). The Slides file was created blank by a scratch-script `files().create(mimeType=presentation)` rather than through the Slides UI; `list_revisions` still listed revision 1. `export_revision` raised exactly "No XLSX or DOCX export available for revision 1. export_revision supports Google Sheets and Google Docs files only." Slides file trashed.
|
|
1956
|
+
|
|
1936
1957
|
**Result (2026-09-04) ✅ PASS**
|
|
1937
1958
|
export_revision({DOC_ID}, rev 3) → error "No XLSX export available for revision 3. The file may not be a Google Sheets file." Clear error, no crash.
|
|
1938
1959
|
|
|
1939
1960
|
---
|
|
1940
1961
|
|
|
1962
|
+
### TC-D280: Export two Google Doc revisions as Markdown; the diff shows only the edit (issue #959) ⚠️ requires-oauth
|
|
1963
|
+
|
|
1964
|
+
**Background:** The Docs API can't read an old revision, so `export_revision` exports it as DOCX,
|
|
1965
|
+
imports that into a temporary Doc, and converts it with `get_doc_as_markdown`'s own converter.
|
|
1966
|
+
Docs folds the latest revision into the next one while editing continues: during #959, edits 90
|
|
1967
|
+
seconds and 5 minutes later both merged it. A Doc created by Drive import keeps its import
|
|
1968
|
+
revision separate (#814), so the diff below uses one.
|
|
1969
|
+
|
|
1970
|
+
**Setup**
|
|
1971
|
+
1. `create_doc(title="qa-959-rev", content_format="markdown", content=<below>)` with:
|
|
1972
|
+
````
|
|
1973
|
+
# Release Notes
|
|
1974
|
+
|
|
1975
|
+
Intro with **bold**, *italic*, `inline code`, and [external](https://example.com).
|
|
1976
|
+
|
|
1977
|
+
## Setup
|
|
1978
|
+
|
|
1979
|
+
```
|
|
1980
|
+
pip install thing
|
|
1981
|
+
thing --init
|
|
1982
|
+
```
|
|
1983
|
+
|
|
1984
|
+
> A quoted note.
|
|
1985
|
+
|
|
1986
|
+
1. First step
|
|
1987
|
+
2. Second step
|
|
1988
|
+
|
|
1989
|
+
- [ ] open task
|
|
1990
|
+
- [x] done task
|
|
1991
|
+
|
|
1992
|
+
| Key | Value |
|
|
1993
|
+
|-----|-------|
|
|
1994
|
+
| a | 1 |
|
|
1995
|
+
|
|
1996
|
+

|
|
1997
|
+
````
|
|
1998
|
+
Then `list_revisions(file_id="<rev-id>")` and take the last `revisionId` as R.
|
|
1999
|
+
2. `upload_file(name="qa-959-diff.md", source_format="markdown", convert_to_doc=True,
|
|
2000
|
+
content="# Notes\n\nIntro with **bold** text.\n\n## Next\n\nClosing paragraph.\n")`.
|
|
2001
|
+
Then `find_in_doc(doc_id="<diff-id>", query="Intro with")` and
|
|
2002
|
+
`insert_doc_text(doc_id="<diff-id>", insertions=[{"index": <start_index + 6>, "text": "EDITED "}])`.
|
|
2003
|
+
3. Wait about a minute, then `list_revisions(file_id="<diff-id>")`: A is the first revision
|
|
2004
|
+
(the import), B the last. If only one revision is listed, wait another minute and re-list.
|
|
2005
|
+
|
|
2006
|
+
**Tool calls**
|
|
2007
|
+
1. `export_revision(file_id="<rev-id>", revision_id="<R>")`
|
|
2008
|
+
2. `get_doc_as_markdown(doc_id="<rev-id>")`
|
|
2009
|
+
3. `export_revision(file_id="<diff-id>", revision_id="<A>")`
|
|
2010
|
+
4. `export_revision(file_id="<diff-id>", revision_id="<B>")`
|
|
2011
|
+
5. `search_files(query="export_revision temp")`
|
|
2012
|
+
|
|
2013
|
+
**Checks**
|
|
2014
|
+
- Call 1's `markdown` has the heading, bold/italic, `` `inline code` ``, a fenced code block, a
|
|
2015
|
+
`> ` blockquote, the numbered list, both task items, the table, and the image as
|
|
2016
|
+
``. The result has no `images` key: the temporary Doc's image URLs
|
|
2017
|
+
stop serving once it's deleted, so none is returned (PR #988 round 1).
|
|
2018
|
+
- Call 2's `markdown` equals call 1's except the image line (call 2 has a URL).
|
|
2019
|
+
- Diffing call 3's `markdown` against call 4's shows exactly one changed line: `Intro with` →
|
|
2020
|
+
`Intro EDITED with`.
|
|
2021
|
+
- Call 5 returns no temporary Doc for `<rev-id>` or `<diff-id>`: each was removed. `search_files`
|
|
2022
|
+
also lists trashed files, so a hit from an earlier run is fine if `get_file_metadata` shows
|
|
2023
|
+
`trashed: true`.
|
|
2024
|
+
|
|
2025
|
+
**Teardown:** `delete_file` on both `<rev-id>` and `<diff-id>`.
|
|
2026
|
+
|
|
2027
|
+
**Result (2026-10-11, PR #988 round 1, `8b223c5`) ❌ FAIL (sent back)** — Ran via `mcp-gee-sweet-kit` (OAuth). `qa-959-rev` listed revisions 1 and 5; R=5. `qa-959-diff.md` listed revisions 1 and 2 about 10 seconds after the edit (no wait needed). Call 1's `markdown` had every listed construct and the image as ``. Call 2's `markdown` matched it byte for byte except the image line. Calls 3 and 4 differed in exactly one line, `Intro with` → `Intro EDITED with`. Call 5 returned `[]`. **Failed check:** the `images[0].url` from call 1 returned **HTTP 404** both to an anonymous `curl` and to an OAuth-authenticated `AuthorizedSession.get`, while the live Doc's own image URL from call 2 returned 200 `image/png` anonymously. A repeat export, fetched within seconds of the call returning, also returned 404. The temporary Doc's `contentUri` stops serving once that Doc is deleted, so every `images` URL is already dead when the tool returns (contradicts `docs/design/doc-revision-export.md`'s "the URL still serves after the temporary Doc is deleted"). Both Docs trashed.
|
|
2028
|
+
|
|
2029
|
+
**Result (2026-10-11, PR #988 round 2, `15d778d`) ✅ PASS** — Ran via `mcp-gee-sweet-kit` (OAuth). `qa-959-rev` listed revisions 1 and 5; R=5. `qa-959-diff.md` listed 1 and 2 about 10 seconds after the edit. Call 1 returned `revisionId`, `modifiedTime`, `markdown` and no `images` key; `markdown` had every listed construct and the image as `` (same hash as round 1). Call 2 matched call 1 byte for byte except the image line. Calls 3 and 4 differed in exactly one line, `Intro with` → `Intro EDITED with`. Call 5 listed no temporary Doc from this pass; its one hit was a temporary Doc from the Dev's 15:15 probe of another Doc, already `trashed: true` (the trash branch working; `search_files` lists trashed files, so the check now says so). Both Docs trashed.
|
|
2030
|
+
|
|
2031
|
+
---
|
|
2032
|
+
|
|
2033
|
+
### TC-D281: Doc revision heading links render as `#slug` and stay stable (issue #959) ⚠️ requires-oauth
|
|
2034
|
+
|
|
2035
|
+
**Setup**
|
|
2036
|
+
1. `create_doc(title="qa-959-links", content_format="markdown", content="# Top\n\n## Setup\n\nFirst.\n\n## Setup\n\nJump here and back here.\n")`
|
|
2037
|
+
2. Link text to the headings by ID, the way the Docs UI does. No tool sets an ID-only link
|
|
2038
|
+
(`style_doc_range`'s `link_url` sets a URL), so use the Docs UI in Playwright (select "Jump",
|
|
2039
|
+
Insert link → Headings → the first `Setup`; "back" → the second `Setup`), or a scratch script
|
|
2040
|
+
per `CLAUDE.md`'s "MCP restart" note sending `updateTextStyle` with
|
|
2041
|
+
`textStyle: {"link": {"headingId": "<id>"}}`. `get_doc_structure(doc_id="<id>")` gives both
|
|
2042
|
+
headings' `headingId`s.
|
|
2043
|
+
3. `list_revisions(file_id="<id>")` and take the last `revisionId`.
|
|
2044
|
+
4. Make a second Doc, `qa-959-bullet`, the same way (steps 1–2), then turn its first `## Setup`
|
|
2045
|
+
into a bulleted list item: `create_paragraph_bullets` over that heading's range (indices from
|
|
2046
|
+
`get_doc_structure`). `list_revisions` on it and take the last `revisionId`.
|
|
2047
|
+
|
|
2048
|
+
**Tool calls**
|
|
2049
|
+
1. `export_revision(file_id="<id>", revision_id="<last>")`
|
|
2050
|
+
2. The same call again.
|
|
2051
|
+
3. `export_revision(file_id="<bullet-id>", revision_id="<bullet last>")`
|
|
2052
|
+
|
|
2053
|
+
**Checks**
|
|
2054
|
+
- Call 1's `markdown` contains `[Jump](#setup)` and `[back](#setup-1)`, and no
|
|
2055
|
+
`docs.google.com` URL.
|
|
2056
|
+
- Call 2's `markdown` is identical to call 1's.
|
|
2057
|
+
- Call 3's `markdown` renders the first `Setup` as a list item (`- Setup`), leaves "Jump" as
|
|
2058
|
+
plain text (its target doesn't render as a heading), and contains `[back](#setup)`: the
|
|
2059
|
+
bulleted heading takes no slug (PR #988 round 1).
|
|
2060
|
+
|
|
2061
|
+
**Teardown:** `delete_file` on both Docs.
|
|
2062
|
+
|
|
2063
|
+
**Result (2026-10-11, PR #988 round 1, `8b223c5`) ✅ PASS** — Ran via `mcp-gee-sweet-kit` (OAuth). Links set by a scratch script (`updateTextStyle` with `link: {headingId}`). Revision 4: both calls returned `[Jump](#setup) here and [back](#setup-1) here.` with no `docs.google.com` URL, byte-identical. **Extra probe (sent back as a review finding):** a second Doc with the first `## Setup` turned into a bulleted list item (`createParagraphBullets`) and the same two links exported as `- Setup\n\n## Setup\n\n[Jump](#setup) here and [back](#setup-1) here.` The bulleted heading renders as a list item, not a heading, but still takes a slug. `#setup` now names the wrong heading and `#setup-1` names none. Both Docs trashed.
|
|
2064
|
+
|
|
2065
|
+
**Result (2026-10-11, PR #988 round 2, `15d778d`) ✅ PASS** — Ran via `mcp-gee-sweet-kit` (OAuth). Links set by a scratch script (`updateTextStyle` with `link: {headingId}`). Calls 1 and 2 (revision 4) both returned `[Jump](#setup) here and [back](#setup-1) here.`, byte-identical, no `docs.google.com` URL. Call 3 (`qa-959-bullet`, revision 5) returned `# Top\n\n- Setup\n\nFirst.\n\n## Setup\n\nJump here and [back](#setup) here.`: the bulleted heading takes no slug and the link to it is dropped. **Extra probe:** a Doc with `## Setup`, then a table whose first cell is a HEADING_2 `Setup`, then a second body `## Setup`; "Jump" linked to the last body heading and "here" to the cell heading. The export was `[Jump](#setup-1) here.`: the cell heading is skipped when counting duplicates (it was `#setup-2` before the fix), and the link to it is dropped. All three Docs trashed.
|
|
2066
|
+
|
|
2067
|
+
---
|
|
2068
|
+
|
|
2069
|
+
### TC-D282: Doc revision `local_path`, and arguments that don't apply to the file's type (issue #959) ⚠️ local-filesystem ⚠️ requires-oauth
|
|
2070
|
+
|
|
2071
|
+
**Setup:** `mkdir -p /tmp/qa-959` (the directory must exist: a `local_path` naming a missing
|
|
2072
|
+
directory, even with a trailing `/`, is written as a file of that name).
|
|
2073
|
+
`create_doc(title="qa-959-args", content="<p>Saved to disk</p>")`, then
|
|
2074
|
+
`list_revisions` on it and on `TEST_SPREADSHEET_ID`, taking each one's last `revisionId`.
|
|
2075
|
+
|
|
2076
|
+
**Tool calls**
|
|
2077
|
+
1. `export_revision(file_id="<doc-id>", revision_id="<doc rev>", local_path="/tmp/qa-959/")`
|
|
2078
|
+
2. `export_revision(file_id="<doc-id>", revision_id="<doc rev>", range="A1:B2")`
|
|
2079
|
+
3. `export_revision(file_id="<TEST_SPREADSHEET_ID>", revision_id="<sheet rev>", local_path="/tmp/qa-959/")`
|
|
2080
|
+
4. `export_revision(file_id="<TEST_SPREADSHEET_ID>", revision_id="<sheet rev>", range="A1:B2")`
|
|
2081
|
+
5. `export_revision(file_id="<doc-id>", revision_id="999999")`
|
|
2082
|
+
6. `export_revision(file_id="<doc-id>_WRONG", revision_id="<doc rev>")`
|
|
2083
|
+
7. `export_revision(file_id="<doc-id>", revision_id="<doc rev>", local_path="/etc/hosts/x.md")`,
|
|
2084
|
+
then `search_files(query="export_revision temp")`
|
|
2085
|
+
|
|
2086
|
+
**Checks**
|
|
2087
|
+
- Call 1 returns `local_path` ending `/tmp/qa-959/<doc-id>-r<doc rev>.md`, `bytes_written`,
|
|
2088
|
+
`revisionId`, `modifiedTime`, and no `markdown` or `images`. The file holds raw Markdown
|
|
2089
|
+
(`Saved to disk`), not JSON.
|
|
2090
|
+
- Call 2 raises "…is a Google Doc; range and sheet apply only to Google Sheets."
|
|
2091
|
+
- Call 3 raises "…is a Google Sheet; local_path applies only to Google Docs."
|
|
2092
|
+
- Call 4 returns `sheet`, `range`, and `values` exactly as before #959.
|
|
2093
|
+
- Call 5 raises "Revision 999999 of <doc-id> not found…call list_revisions again…".
|
|
2094
|
+
- Call 6 raises Drive's own 404 ("File not found: <doc-id>_WRONG"), not the merged-revision
|
|
2095
|
+
message (PR #988 round 1).
|
|
2096
|
+
- Call 7 raises "local_path '/etc/hosts/x.md' has a non-directory path component at
|
|
2097
|
+
'/etc/hosts'", and `search_files` finds no temporary Doc for `<doc-id>`: the destination is checked
|
|
2098
|
+
before the export (PR #988 round 1).
|
|
2099
|
+
|
|
2100
|
+
**Teardown:** remove `/tmp/qa-959/`; `delete_file(file_id="<doc-id>")`.
|
|
2101
|
+
|
|
2102
|
+
**Result (2026-10-11, PR #988 round 1, `8b223c5`) ✅ PASS** — Ran via `mcp-gee-sweet-kit` (OAuth). The first attempt at call 1 ran without `/tmp/qa-959` existing and wrote a 15-byte *file* named `/tmp/qa-959` (`local_path: "/tmp/qa-959"`); that comes from the shared `write_capped_result_to_disk`, which this PR doesn't touch, so it's ticketed separately and the setup now creates the directory. Rerun with the directory present: call 1 returned `local_path` `/tmp/qa-959/<doc-id>-r3.md`, `bytes_written: 15`, `revisionId`, `modifiedTime`, `images: []`, no `markdown`; the file held `Saved to disk\n\n`, raw Markdown. Calls 2, 3, and 5 raised the expected messages. Call 4 returned `sheet: "Sales"`, `range: "A1:B2"`, `values: [["Product", null], ["Widget", 100]]`. **Extra probe (sent back as a review finding):** `file_id="<doc-id>_WRONG"` raised the same "Revision 3 of … not found. Drive merges a recent revision…" message, misdiagnosing a bad file ID as a merged revision.
|
|
2103
|
+
|
|
2104
|
+
**Result (2026-10-11, PR #988 round 2, `15d778d`) ✅ PASS** — Ran via `mcp-gee-sweet-kit` (OAuth), `/tmp/qa-959` created first. Call 1 returned `local_path` `/tmp/qa-959/<doc-id>-r3.md`, `bytes_written: 15`, `revisionId`, `modifiedTime`, and no `markdown` or `images`; the file held `Saved to disk\n\n`. Calls 2, 3, and 5 raised the expected messages. Call 4 returned `sheet: "Sales"`, `range: "A1:B2"`, `values: [["Product", null], ["Widget", 100]]` (revision 342). Call 6 raised Drive's own `HttpError 404 … "File not found: <doc-id>_WRONG."`, not the merged-revision message. Call 7 raised `local_path '/etc/hosts/x.md' has a non-directory path component at '/etc/hosts'`, and `search_files` found no temporary Doc for `<doc-id>` (only the earlier trashed one noted in TC-D280). Doc trashed, `/tmp/qa-959` removed.
|
|
2105
|
+
|
|
2106
|
+
---
|
|
2107
|
+
|
|
2108
|
+
### TC-D283: A service account (Content manager) exports a Doc revision; the temporary Doc is trashed (issue #959)
|
|
2109
|
+
|
|
2110
|
+
**Background:** A Content manager on a Shared Drive can trash but not permanently delete (#876),
|
|
2111
|
+
so the temporary Doc is trashed instead. Use the `mcp-gee-sweet-sa` server, whose default folder
|
|
2112
|
+
is the Shared Drive.
|
|
2113
|
+
|
|
2114
|
+
**Setup:** On the SA server, `create_doc(title="qa-959-sa", content="<p>SA revision</p>")`, then
|
|
2115
|
+
`list_revisions(file_id="<id>")` and take the last `revisionId`.
|
|
2116
|
+
|
|
2117
|
+
**Tool calls**
|
|
2118
|
+
1. `export_revision(file_id="<id>", revision_id="<last>")` on the SA server
|
|
2119
|
+
2. `search_files(query="export_revision temp")` on the SA server
|
|
2120
|
+
|
|
2121
|
+
**Checks**
|
|
2122
|
+
- Call 1 returns `markdown` containing `SA revision` and no `warning`.
|
|
2123
|
+
- Call 2 finds no live file. If the client can show trashed files, the temporary Doc is in the
|
|
2124
|
+
Shared Drive's trash.
|
|
2125
|
+
|
|
2126
|
+
**Teardown:** `delete_file(file_id="<id>")` on the SA server.
|
|
2127
|
+
|
|
2128
|
+
**Result (2026-10-11, PR #988 round 1, `8b223c5`) ⏭️ SKIP** — Not run: a lane QA session may call only its own `mcp-gee-sweet-kit` server, and the case needs `mcp-gee-sweet-sa`. Pending for a release pass (Aziz may use the `-sa` slot). The trash branch is unit-tested only so far.
|
|
2129
|
+
|
|
2130
|
+
---
|
|
2131
|
+
|
|
1941
2132
|
### TC-D256: `download_folder`'s `progress_unit='bytes'` feeds byte totals into the structured progress fields, with a file-count fallback when sizes aren't fully known (issue #741) ⚠️ local-filesystem
|
|
1942
2133
|
|
|
1943
2134
|
**Background:** TC-D255 (#352) confirmed the message text carries bytes-transferred-so-far as supplementary context, but the structured `progress`/`total` fields passed to `ctx.report_progress()` stayed file-count-based unconditionally — a client rendering a progress bar from those fields alone (not parsing message text) sees the same one-tick-per-file granularity regardless of individual file size. #741's maintainer decision: keep file-count as the default (`progress_unit='files'`), but add an opt-in `progress_unit='bytes'` so a caller can request byte-based structured fields when every candidate's size is known upfront (same `bytes_total_known` gate TC-D255's message-fallback logic already uses) — falling back to file-count when it isn't, since there's no reliable byte total to report against then. `sync_folder` is explicitly out of scope for this decision (recursive descent never has a full upfront size list). Unit-tested deterministically against a mocked `ctx` in `tests/drive/test_transfer.py::TestDownloadFolder::test_progress_unit_bytes_reports_byte_totals_when_sizes_known`/`test_progress_unit_bytes_falls_back_to_files_when_sizes_unknown`; this live check confirms both branches against a real API call.
|
|
@@ -222,9 +222,11 @@ Same shape as v0.8.1 — stabilize on defects that surfaced since v0.9.0 shipped
|
|
|
222
222
|
- [x] `get_doc_as_markdown`: plain text ending in `!` right before a linked run exports as ``, which renders as an image. Affects every link, not just code; fix belongs in `_escape`'s cross-run handling. Found in PR #968 — lane B, after #951 (same file), ahead of #958 (PR #981) ([#969](https://github.com/khuisman/mcp-gee-sweet/issues/969))
|
|
223
223
|
- [x] `get_doc_as_markdown(local_path=…)` writes the JSON result envelope, not the markdown: `markdown_export.py` routes through the generic JSON-to-disk helper — lane B, after #938 (same read-side area) (PR #982) ([#958](https://github.com/khuisman/mcp-gee-sweet/issues/958))
|
|
224
224
|
- [x] `add_chart`: a cell-to-row (`B2:5`) or row-only (`1:5`) range silently charts one column — `_per_column_ranges` defaults `endColumnIndex` to start+1. Found reviewing #931 — lane A, after #917 (PR #983) ([#954](https://github.com/khuisman/mcp-gee-sweet/issues/954))
|
|
225
|
-
- [
|
|
225
|
+
- [x] `_parse_a1_notation` hardening: one compiled grammar, index bounds, validate before the sheet-ID fetch. #931 review follow-ups, no behavior change for valid input — lane A, after #954 (same A1 area); its QA also runs #953's live checks for #931 (folded in) (PR #986) ([#955](https://github.com/khuisman/mcp-gee-sweet/issues/955))
|
|
226
226
|
- [ ] Write side drops image links: `[](url)` (or `<a href><img></a>`) imports through `create_doc`/`create_doc_from_file`/`write_doc_content` without its URL, so it doesn't round-trip with #951's export. Found in PR #976 — lane A, after #955 ([#977](https://github.com/khuisman/mcp-gee-sweet/issues/977))
|
|
227
|
+
- [ ] `_parse_a1_notation` rejects `A:B5`, which the Sheets values API accepts as `A5:B` — decided 2026-10-11: keep rejecting (a destructive GridRange tool acting on "row 5 onward" is worse than an error); document in the docstring that this is deliberately stricter than Sheets. Found in PR #986 — lane A, after #977 ([#987](https://github.com/khuisman/mcp-gee-sweet/issues/987))
|
|
227
228
|
- [ ] Comment quoted text comes back HTML-escaped (`Café`) from `add_doc_comment`, `list_doc_comments`, and `get_doc_as_markdown(include_comments=True)`: `comments.py` passes Drive's escaped `quotedFileContent.value` through unchanged. Found reviewing #958 — lane B, after #959 (defect, next pick) ([#984](https://github.com/khuisman/mcp-gee-sweet/issues/984))
|
|
229
|
+
- [ ] `write_capped_result_to_disk` writes `dir/` as a file when the directory doesn't exist: a trailing separator should mean "directory" (create it, append `default_filename`). Shared by every `local_path` writer. Found in PR #988 — lane B, after #984 (defect) ([#990](https://github.com/khuisman/mcp-gee-sweet/issues/990))
|
|
228
230
|
|
|
229
231
|
**Sheets hardening** _(triaged out of `backlog` 2026-09-11 — see the "Backlog triage" note below)_
|
|
230
232
|
- [x] `update_sheet_properties`/dimension-tool hardening + dedup follow-ups from PR #321's review ([#323](https://github.com/khuisman/mcp-gee-sweet/issues/323)) (PR #734)
|
|
@@ -251,6 +253,7 @@ Same shape as v0.8.1 — stabilize on defects that surfaced since v0.9.0 shipped
|
|
|
251
253
|
- [ ] `write_doc_content`/`update_doc_from_file` leave the doc empty when the replacement fails after the clear — decided 2026-10-04: record the pre-clear revision ID and name it in the error so the caller can recover via `export_revision` ([#878](https://github.com/khuisman/mcp-gee-sweet/issues/878))
|
|
252
254
|
- [ ] `update_doc_from_file`: opt-in incremental (`mode="patch"`) update that diffs block by block and rewrites only changed blocks, so comment anchors and revision history survive — lane A, after #894 (same write-path files) ([#957](https://github.com/khuisman/mcp-gee-sweet/issues/957))
|
|
253
255
|
- [ ] `export_revision`: support Google Docs revisions (returned as markdown), and fix `list_revisions`' docstring claim that native-file revisions are pruned after 30 days. Also what #878's recovery path points callers at — lane B, after #958 ([#959](https://github.com/khuisman/mcp-gee-sweet/issues/959))
|
|
256
|
+
- [ ] `export_revision`'s temporary Doc exposes an old revision's content to the default folder's audience (a whole Shared Drive) — decided 2026-10-11: under OAuth create it in My Drive root; a service account keeps the default folder, and that remaining exposure goes in `docs/known-limitations.md`. Found in PR #988 — lane B, after #990 ([#989](https://github.com/khuisman/mcp-gee-sweet/issues/989))
|
|
254
257
|
- [x] `insert_local_images`: temporary `anyone:reader` image shares aren't revoked when the call is cancelled — apply PR #877's shielded-cleanup pattern — lane B ([#883](https://github.com/khuisman/mcp-gee-sweet/issues/883)) (PR #903)
|
|
255
258
|
- [ ] **Bundle:** Docs image-share cancellation follow-ups (from PR #903), one PR closing [#904](https://github.com/khuisman/mcp-gee-sweet/issues/904), [#907](https://github.com/khuisman/mcp-gee-sweet/issues/907), [#908](https://github.com/khuisman/mcp-gee-sweet/issues/908), [#666](https://github.com/khuisman/mcp-gee-sweet/issues/666) — lane A ([#909](https://github.com/khuisman/mcp-gee-sweet/issues/909))
|
|
256
259
|
- [x] `_apply_doc_content`: temporary `anyone:reader` image shares aren't revoked when the doc edit raises — revoke pass should run in a `finally`. Same share/revoke surface as #666 and #511 ([#789](https://github.com/khuisman/mcp-gee-sweet/issues/789)) (PR #877)
|
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
|---|---|---|
|
|
95
95
|
| `export_file` | Export or download a file from Google Drive. | `file_id`, `export_format` |
|
|
96
96
|
| `list_revisions` | List available revisions for a Google Drive file (Sheets, Docs, or any file). | `file_id` |
|
|
97
|
-
| `export_revision` | Export a historical revision of a Google Sheets file
|
|
97
|
+
| `export_revision` | Export a historical revision of a Google Sheets file (cell data) or a Google Doc (Markdown). Use list_revisions to find the revision_id. | `file_id`, `revision_id`, `range?`, `sheet?`, `local_path?` |
|
|
98
98
|
| `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?` |
|
|
99
99
|
| `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?`, `convert?` |
|
|
100
100
|
| `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?`, `convert?` |
|