mcp-gee-sweet 0.9.2.dev212__tar.gz → 0.9.2.dev215__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.dev212 → mcp_gee_sweet-0.9.2.dev215}/PKG-INFO +1 -1
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/qa/gmail-test-plan.md +3 -2
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/qa/tests/gmail.md +30 -5
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/roadmap.md +9 -1
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/tools.md +2 -2
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/src/mcp_gee_sweet/tools/gmail.py +61 -25
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/tests/test_gmail.py +128 -1
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/.claude/commands/cleanup-worktrees.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/.claude/commands/merge-pr.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/.claude/commands/notify-kai.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/.claude/commands/notify-partner.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/.claude/commands/orchestrator.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/.claude/commands/prep-for-pr.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/.claude/commands/qa-kickoff.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/.claude/commands/release.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/.claude/commands/retro.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/.claude/commands/roadmap-status.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/.claude/commands/start-worktree.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/.claude/commands/team-member.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/.claude/commands/verify-pr.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/.claude/settings.json +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/.claude/team-roles/amy.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/.claude/team-roles/aziz.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/.claude/team-roles/bob.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/.claude/team-roles/dev.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/.claude/team-roles/joy.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/.claude/team-roles/kai.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/.claude/team-roles/qa.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/.dockerignore +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/.github/dependabot.yml +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/.github/workflows/ci.yml +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/.github/workflows/docs.yml +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/.github/workflows/publish-dev.yml +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/.github/workflows/release.yml +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/.gitignore +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/.pre-commit-config.yaml +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/.python-version +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/CHANGELOG.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/CLAUDE.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/CODE_OF_CONDUCT.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/CONTRIBUTING.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/Dockerfile +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/LICENSE +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/Makefile +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/README.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/SECURITY.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docker-compose.yml +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/assets/logo.png +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/auth.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/client-setup.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/configuration.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/decisions/decision-async-tool-execution.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/decisions/decision-auth-status-limitation-categories.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/decisions/decision-cache-invalidation.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/decisions/decision-chart-theming.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/decisions/decision-comments-first-class.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/decisions/decision-composite-tools.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/decisions/decision-condition-type-docstring-generation.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/decisions/decision-converted-doc-change-detection.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/decisions/decision-dev-team.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/decisions/decision-docs-formatting.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/decisions/decision-fork.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/decisions/decision-gmail-domain.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/decisions/decision-grid-data-size-cap.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/decisions/decision-local-fs-test-harness.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/decisions/decision-mcp-v2-resource-context-access.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/decisions/decision-pillow-image-dependency.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/decisions/decision-prompt-qa-role.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/decisions/decision-publishing.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/decisions/decision-release-docs-roles.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/decisions/decision-repositioning.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/decisions/decision-response-size-cap-generalization.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/decisions/decision-response-size-cap-reevaluation-519.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/decisions/decision-supports-all-drives-lint-check.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/decisions/decision-sync-folder-recursion.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/decisions/decision-testing.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/decisions/index.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/design/blockquote-representation.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/design/borderless-table-columns.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/design/doc-to-markdown.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/design/docs-ast-pipeline.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/design/effectiveformat-spike.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/design/heading-anchor-resolution.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/design/image-conversion.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/design/index.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/design/markdown-support.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/design/module-history.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/design/phase3-theme-system.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/design/sse-consent-off-event-loop.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/design.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/index.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/known-limitations.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/notes-read.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/qa/README.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/qa/fixtures/qa-fixture-pixel.png +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/qa/fixtures/tc-d195-create-doc.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/qa/fixtures/tc-d196-create-doc.html +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/qa/fixtures/tc-d213-dollar-escape.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/qa/fixtures/tc-d226-heading-table.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/qa/fixtures/tc-doc102-image-conversion.html +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/qa/fixtures/tc-doc103-image-conversion.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/qa/fixtures/tc-doc104-nested-lists.html +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/qa/fixtures/tc-doc105-nested-lists-gfm.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/qa/fixtures/tc-doc106-nested-lists-4space.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/qa/fixtures/tc-doc135-paragraph-boundary.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/qa/fixtures/tc-doc136-interrupted-block-boundary.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/qa/fixtures/tc-doc137-inline-hr-no-block-ancestor.html +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/qa/fixtures/tc-doc138-nbsp-spacer-paragraph.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/qa/fixtures/tc-doc141-mismatched-list-tags.html +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/qa/fixtures/tc-doc142-heading-anchors.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/qa/fixtures/tc-doc144-anchor-in-table-cell.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/qa/fixtures/tc-doc145-anchor-on-first-element.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/qa/fixtures.template.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/qa/operations.yaml +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/qa/playwright_oauth.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/qa/results/.gitkeep +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/qa/results/2026-06-02.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/qa/results/2026-06-14.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/qa/results/2026-06-17.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/qa/results/2026-06-18.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/qa/results/2026-06-21-drive-discovery.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/qa/results/2026-06-21-sheets-delete-clear.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/qa/results/2026-06-28.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/qa/results/2026-07-04-docs.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/qa/results/2026-07-04-drive-smoke.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/qa/results/2026-07-04-infra.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/qa/results/2026-07-04-sheets_read.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/qa/results/2026-07-04.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/qa/results/2026-09-04.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/qa/retro-v0.8.0.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/qa/retro-v0.9.0.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/qa/run.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/qa/runs/README.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/qa/runs/v0.8.0.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/qa/runs/v0.8.1.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/qa/runs/v0.9.0.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/qa/setup.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/qa/tests/calendar.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/qa/tests/docs_content.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/qa/tests/docs_layout.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/qa/tests/docs_style.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/qa/tests/docs_tables.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/qa/tests/drive_activity.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/qa/tests/drive_files.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/qa/tests/drive_sharing.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/qa/tests/drive_transfer.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/qa/tests/infra.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/qa/tests/sheets_charts.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/qa/tests/sheets_mgmt.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/qa/tests/sheets_read.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/qa/tests/sheets_write.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/docs/style-guide.md +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/mkdocs.yml +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/pyproject.toml +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/pyrightconfig.json +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/scripts/check_supports_all_drives.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/scripts/gen_tool_docs.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/scripts/lane_context_hook.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/scripts/oauth_setup.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/scripts/qa_gmail_fixtures.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/scripts/setup_team.sh +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/scripts/write_team_mcp_configs.sh +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/server.json +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/src/mcp_gee_sweet/.env.template +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/src/mcp_gee_sweet/__init__.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/src/mcp_gee_sweet/auth.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/src/mcp_gee_sweet/cache.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/src/mcp_gee_sweet/http_transport.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/src/mcp_gee_sweet/server.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/src/mcp_gee_sweet/tools/__init__.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/src/mcp_gee_sweet/tools/cache.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/src/mcp_gee_sweet/tools/calendar.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/src/mcp_gee_sweet/tools/concurrency.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/src/mcp_gee_sweet/tools/docs/__init__.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/src/mcp_gee_sweet/tools/docs/anchors.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/src/mcp_gee_sweet/tools/docs/ast.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/src/mcp_gee_sweet/tools/docs/ast_to_markdown.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/src/mcp_gee_sweet/tools/docs/comments.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/src/mcp_gee_sweet/tools/docs/content.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/src/mcp_gee_sweet/tools/docs/doc_to_ast.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/src/mcp_gee_sweet/tools/docs/editing.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/src/mcp_gee_sweet/tools/docs/emitter.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/src/mcp_gee_sweet/tools/docs/html_parser.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/src/mcp_gee_sweet/tools/docs/images.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/src/mcp_gee_sweet/tools/docs/indices.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/src/mcp_gee_sweet/tools/docs/layout.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/src/mcp_gee_sweet/tools/docs/markdown_export.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/src/mcp_gee_sweet/tools/docs/named_ranges.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/src/mcp_gee_sweet/tools/docs/style.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/src/mcp_gee_sweet/tools/docs/tables.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/src/mcp_gee_sweet/tools/drive/__init__.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/src/mcp_gee_sweet/tools/drive/activity.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/src/mcp_gee_sweet/tools/drive/files.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/src/mcp_gee_sweet/tools/drive/sharing.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/src/mcp_gee_sweet/tools/drive/transfer.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/src/mcp_gee_sweet/tools/response_limits.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/src/mcp_gee_sweet/tools/sheets/__init__.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/src/mcp_gee_sweet/tools/sheets/data.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/src/mcp_gee_sweet/tools/sheets/helpers.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/src/mcp_gee_sweet/tools/sheets/structure.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/tests/__init__.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/tests/conftest.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/tests/drive/__init__.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/tests/drive/test_activity.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/tests/drive/test_files.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/tests/drive/test_sharing.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/tests/drive/test_transfer.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/tests/integration/__init__.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/tests/integration/conftest.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/tests/integration/test_local_fs_harness.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/tests/sheets/__init__.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/tests/sheets/test_data.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/tests/sheets/test_helpers.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/tests/sheets/test_structure.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/tests/test_auth.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/tests/test_cache.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/tests/test_calendar.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/tests/test_check_supports_all_drives.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/tests/test_concurrency.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/tests/test_docs_anchors.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/tests/test_docs_comments.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/tests/test_docs_content.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/tests/test_docs_core.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/tests/test_docs_editing.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/tests/test_docs_images.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/tests/test_docs_indices.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/tests/test_docs_layout.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/tests/test_docs_markdown_export.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/tests/test_docs_named_ranges.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/tests/test_docs_style.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/tests/test_docs_tables.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/tests/test_gen_tool_docs.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/tests/test_lane_context_hook.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/tests/test_qa_gmail_fixtures.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/tests/test_response_limits.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/tests/test_server.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/tests/test_server_json.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/tests/test_team_mcp_configs.py +0 -0
- {mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/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.dev215
|
|
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
|
|
@@ -73,7 +73,7 @@ Add `scripts/qa_gmail_fixtures.py` (new), modeled on the TC-GM23 setup snippet:
|
|
|
73
73
|
|---|---|---|---|---|
|
|
74
74
|
| `plain` | Sent | Single `text/plain` from the sender mailbox; lands `INBOX`+`UNREAD` | `TEST_MESSAGE_ID` | GM01, 03, 19 |
|
|
75
75
|
| `alt-unicode` | Sent | `body` + `body_html`, non-ASCII subject and body, astral emoji | `TEST_GMAIL_UNICODE_ID` | GM27 |
|
|
76
|
-
| `attachments` | Sent + Inserted | Sent: body + small PDF + CSV. Inserted: a `multipart/related` inline PNG with `Content-ID` and **no filename** (`send_message` can't build `related`) | `TEST_GMAIL_ATTACH_ID`, `TEST_GMAIL_INLINE_ID` | GM28 |
|
|
76
|
+
| `attachments` | Sent + Inserted | Sent: body + small PDF + CSV. Inserted: a `multipart/related` inline PNG with `Content-ID` and **no filename** (`send_message` can't build `related`) | `TEST_GMAIL_ATTACH_ID`, `TEST_GMAIL_INLINE_ID` | GM28, 46 |
|
|
77
77
|
| `thread` | Sent (both sides) | Sender sends; the QA mailbox replies; the sender replies again. A real two-party, 3-message thread with Gmail-generated `Message-ID`/`References` | `TEST_THREAD_ID` | GM05, 07, 33, 43 |
|
|
78
78
|
| `reply-to` | Inserted | Foreign `From: noreply@example.invalid`, `Reply-To` = `+tc-gm23` (`send_message` has no `Reply-To` param) | `TEST_GMAIL_REPLYTO_ID` | GM23 (replaces its inline setup) |
|
|
79
79
|
| `forwarded` | Sent | Outer body, then a forwarded message as a real `message/rfc822` part named `forwarded.eml` | `TEST_GMAIL_FWD_ID` | GM29 |
|
|
@@ -112,7 +112,7 @@ Keep the IDs and intent. Rewrite each to name the exact tool and params and to r
|
|
|
112
112
|
| GM28 | `get_message` | Both attachments (PDF, CSV) listed with filename, MIME type, size, `attachment_id`. The inline PNG (`inline` fixture) is listed with `filename: null`; Gmail stores it by `attachmentId` (§5, P3: not a defect). | read |
|
|
113
113
|
| GM29 | `get_message` | `body_plain` is the *outer* message's body; the `.eml` is listed as a `message/rfc822` attachment (§5, P2: not a defect with Gmail's real layout) | read |
|
|
114
114
|
| GM30 | `get_message`, `get_thread` | `large-body` (Gmail delivers both 3 MB parts by `attachmentId`, #825). Without `local_path`: the size-cap error, naming `local_path`, since ~6 MB exceeds the default cap. With `local_path`: the written JSON has `body_plain` (3,000,002 chars) and `body_html` (3,037,987 chars) filled, `attachments: []`, no `body_fetch_errors`. Same for `get_thread` on its thread. Written in the #825 PR (#829). | read |
|
|
115
|
-
| GM31 | `get_thread` | Over-cap thread returns the size-cap error, not a dropped connection or a truncated body
|
|
115
|
+
| GM31 | `get_thread` | Over-cap thread returns the size-cap error, not a dropped connection or a truncated body, and the error points at `include_body=False`. With `include_body=False` the same thread lists its 3 message IDs and headers with no bodies (#793). | read |
|
|
116
116
|
| GM32 | `get_message` | Latin-1 body decodes correctly. A zero-setup regression companion to TC-GM26: #792 closed with the finding that Gmail transcodes every text part to UTF-8, so this is expected to **pass**. | read |
|
|
117
117
|
| GM33 | `get_thread` | 3 messages in chronological order, each shaped like `get_message`, `in_reply_to`/`references` populated | read |
|
|
118
118
|
| GM34 | `list_messages`, `list_threads` | `max_results=3` over the 7 `page` fixtures: `next_page_token` present, page 2 has no overlap with page 1, final page has no token. Also `max_results=0` and `9999` clamp without error. | read |
|
|
@@ -127,6 +127,7 @@ Keep the IDs and intent. Rewrite each to name the exact tool and params and to r
|
|
|
127
127
|
| GM43 | `reply_to_message` | Reply into the 3-message `thread` from its middle message: `references` chains all prior IDs, reply lands in the same thread. M2 was sent by the QA mailbox, so the reply is delivered to the sender mailbox | write, cross-mailbox |
|
|
128
128
|
| GM44 | `reply_to_message` (QA) → `list_messages` (sender) | End-to-end delivery. The QA mailbox replies to the `plain` fixture. On the sender side, `list_messages(query='subject:"[mcp-qa:plain]"')` shows the reply arrived in the **same thread** as the original. Proves `In-Reply-To`/`References` thread correctly in the recipient's mailbox, not just in ours. Plus-address cases can't show this. | write, cross-mailbox |
|
|
129
129
|
| GM45 | `send_message` (QA) → `get_message` (sender) | QA sends to the sender mailbox with `cc` = a QA plus-address and `bcc` = another. On the sender side, `get_message` shows `cc` present and **no** `bcc` header. Bcc must be stripped from delivered copies; GM39 only sees the sender's copy. | write, cross-mailbox |
|
|
130
|
+
| GM46 | `get_message` | `include_body=False` on the `attachments` fixture: headers, snippet, labels, and `size_estimate` present; no `body_plain`, `body_html`, or `attachments` key, since Gmail's `metadata` format returns no payload parts (#793) | read |
|
|
130
131
|
|
|
131
132
|
Every write case uses a `+tc-gmNN` plus-address and an `[mcp-qa:tc-gmNN]` subject (so `reset` sweeps it), and ends with a **Cleanup** step.
|
|
132
133
|
|
|
@@ -333,6 +333,24 @@ Record the printed ID as `{UTF16_ID}`.
|
|
|
333
333
|
|
|
334
334
|
---
|
|
335
335
|
|
|
336
|
+
### TC-GM46: `include_body: false` returns headers without body or attachments (issue #793)
|
|
337
|
+
|
|
338
|
+
**Background:** `include_body: false` fetches with Gmail's `format='metadata'`, which returns headers but no payload parts (confirmed live 2026-10-04). So the result omits attachment metadata as well as the body.
|
|
339
|
+
|
|
340
|
+
**Action**
|
|
341
|
+
1. `get_message` with `message_id: "{TEST_GMAIL_ATTACH_ID}"`, `include_body: false`
|
|
342
|
+
|
|
343
|
+
**Checks**
|
|
344
|
+
- `id` is `{TEST_GMAIL_ATTACH_ID}`; `thread_id`, `snippet`, `label_ids`, `internal_date`, and a positive `size_estimate` are present
|
|
345
|
+
- `headers.subject` starts `[mcp-qa:`, `headers.from` contains `{SENDER}`, `headers.message_id` is non-empty
|
|
346
|
+
- No `body_plain`, `body_html`, `attachments`, `body_decode_errors`, `body_fetch_errors`, or `error` key
|
|
347
|
+
|
|
348
|
+
**Result** (2026-10-04, PR #885 round 1 at `47937d8`, `mcp-gee-sweet-kit`, OAuth token with `gmail.modify`): **PASS**. `id`, `thread_id`, `snippet` (`mcp-gee-sweet attachments fixture: a PDF and a CSV.`), `label_ids`, `internal_date` and `size_estimate` = 7893 present. `headers.subject` = `[mcp-qa:attachments] PDF + CSV`, `headers.from` = `{SENDER}`, `headers.message_id` non-empty. No body, attachment, error-list or `error` keys. The PR still went back to the Dev for code-review findings this case doesn't exercise; see the PR #885 comment.
|
|
349
|
+
|
|
350
|
+
**Result** (2026-10-04, PR #885 round 2 at `48bf23f`, `mcp-gee-sweet-kit`, OAuth token with `gmail.modify`): **PASS (regression)**. The output matches round 1 field for field. The fix's `metadataHeaders` filter can't be seen through shaping, so it was checked against the raw API instead (see TC-GM31's round 2 result).
|
|
351
|
+
|
|
352
|
+
---
|
|
353
|
+
|
|
336
354
|
## `list_threads`
|
|
337
355
|
|
|
338
356
|
### TC-GM05: List the fixture threads
|
|
@@ -381,18 +399,25 @@ Record the printed ID as `{UTF16_ID}`.
|
|
|
381
399
|
|
|
382
400
|
---
|
|
383
401
|
|
|
384
|
-
### TC-GM31: An over-cap thread returns the size-cap error
|
|
402
|
+
### TC-GM31: An over-cap thread returns the size-cap error, and `include_body: false` lists it (issue #793)
|
|
385
403
|
|
|
386
|
-
**Background:** the `over-cap-thread` fixture is 3 messages of ~400 KB each, which is over the default `MAX_TOOL_RESPONSE_CHARS` in full format but under it per message.
|
|
404
|
+
**Background:** the `over-cap-thread` fixture is 3 messages of ~400 KB each, which is over the default `MAX_TOOL_RESPONSE_CHARS` in full format but under it per message. The full fetch should refuse cleanly rather than drop the connection or truncate. Since #793, `include_body: false` fetches only metadata, which is small enough to list the thread's message IDs.
|
|
387
405
|
|
|
388
406
|
**Action**
|
|
389
407
|
1. `get_thread` with `thread_id: "{TEST_GMAIL_BIG_THREAD_ID}"`
|
|
390
|
-
2. `
|
|
408
|
+
2. `get_thread` with `thread_id: "{TEST_GMAIL_BIG_THREAD_ID}"`, `include_body: false`
|
|
409
|
+
3. `get_message` with `message_id` set to the first message ID from step 2
|
|
391
410
|
|
|
392
411
|
**Checks**
|
|
393
|
-
- Step 1: the tool call fails with an error containing `get_thread: the response is` and `safety cap
|
|
412
|
+
- Step 1: the tool call fails with an error containing `get_thread: the response is` and `safety cap`. The error suggests `include_body=False` followed by `get_message` on individual IDs. The session stays connected: the next call works.
|
|
394
413
|
- Step 1: no partial or truncated thread is returned
|
|
395
|
-
- Step 2:
|
|
414
|
+
- Step 2: no `error`. `id` is `{TEST_GMAIL_BIG_THREAD_ID}`, and `messages` has 3 entries, each with `id`, `thread_id`, `snippet`, `label_ids`, `size_estimate`, and `headers` (`headers.subject` contains `[mcp-qa:over-cap-thread]`)
|
|
415
|
+
- Step 2: no message has a `body_plain`, `body_html`, or `attachments` key, and the whole response is a few KB, not ~1.2 MB
|
|
416
|
+
- Step 3: succeeds, with a `body_plain` that starts `mcp-gee-sweet over-cap-thread message`
|
|
417
|
+
|
|
418
|
+
**Result** (2026-10-04, PR #885 round 1 at `47937d8`, `mcp-gee-sweet-kit`, OAuth token with `gmail.modify`): **PASS**. Step 1: `get_thread: the response is 1275124 characters, over the 1000000-character safety cap`, with the hint naming `include_body=False` then `get_message` on individual IDs; no partial thread, and the next call worked. Step 2: no `error`, 3 messages, each with `id`, `thread_id`, `snippet`, `label_ids`, `size_estimate` (~412 KB each) and `headers` (subjects contain `[mcp-qa:over-cap-thread]`; `in_reply_to`/`references` chain correctly), no body or attachment keys; the response was about 3 KB. Step 3: run with `local_path` to keep ~425 KB out of the QA session's context (424,899 bytes written); `body_plain` starts `mcp-gee-sweet over-cap-thread message 1.`. The thread-level `snippet` is `null`, the same as the full format (`threads.get` returns none).
|
|
419
|
+
|
|
420
|
+
**Result** (2026-10-04, PR #885 round 2 at `48bf23f`, `mcp-gee-sweet-kit`, OAuth token with `gmail.modify`): **PASS**. Steps 1–3 matched round 1 exactly (1,275,124-character cap error with the `include_body=False` hint; 3 metadata-only messages; step 3 via `local_path`, 424,899 bytes). The `metadataHeaders` filter was checked with a fresh-interpreter raw `threads.get` on the same thread using this slot's token. Plain `format=metadata` returned 24,591 bytes and 88 headers (ARC-*, DKIM-Signature, Received, X-Gm-*, and others). `_get_format_kwargs(False)` returned 3,003 bytes and 19 headers, all from the shaped set (Date, From, In-Reply-To, Message-ID, References, Subject, To).
|
|
396
421
|
|
|
397
422
|
---
|
|
398
423
|
|
|
@@ -196,6 +196,8 @@ Same shape as v0.8.1 — stabilize on defects that surfaced since v0.9.0 shipped
|
|
|
196
196
|
- [ ] `_timed` logs a `200` status for tool calls that catch their own exception and return `{error: ...}` — observability reports success for a call that actually failed ([#579](https://github.com/khuisman/mcp-gee-sweet/issues/579))
|
|
197
197
|
- [ ] `emitter.py` table-cell images have no cursor-advance handling — latent infinite loop if that code path is ever reached ([#509](https://github.com/khuisman/mcp-gee-sweet/issues/509))
|
|
198
198
|
- [ ] Calendar ACL tools use a bare `except Exception` instead of the `HttpError` friendly-error pattern used elsewhere in `calendar.py` ([#459](https://github.com/khuisman/mcp-gee-sweet/issues/459))
|
|
199
|
+
- [ ] `delete_file(permanent=True)` surfaces a misleading 404 when the caller can trash but not permanently delete (Shared Drive Content manager) — an agent can read it as "already gone" and skip cleanup; check `capabilities.canDelete` first — lane B ([#876](https://github.com/khuisman/mcp-gee-sweet/issues/876))
|
|
200
|
+
- [ ] SSE: SIGTERM with an SSE stream open logs an "Exception in ASGI application" traceback; the server still exits cleanly, pre-existing before #833 — lane A ([#868](https://github.com/khuisman/mcp-gee-sweet/issues/868))
|
|
199
201
|
- [x] `sync_folder`'s `convert_markdown` restamp-failure handling: quota-message gap, duplicated create()+update() try/except, test fixture isolation — companion to #420 (PR #669) ([#650](https://github.com/khuisman/mcp-gee-sweet/issues/650))
|
|
200
202
|
- [x] `sync_folder convert_markdown`'s `modifiedTime` restamp fires for all convert types (over-broad), and its create()+update() pattern is duplicated across two call sites ([#435](https://github.com/khuisman/mcp-gee-sweet/issues/435)) (PR #817)
|
|
201
203
|
- [ ] Harden `tests/integration`'s local-fs live QA harness — error surfacing on API failures, subprocess/handshake timeouts, cleanup-masking a real test failure, per-test subprocess overhead ([#648](https://github.com/khuisman/mcp-gee-sweet/issues/648))
|
|
@@ -242,6 +244,8 @@ Same shape as v0.8.1 — stabilize on defects that surfaced since v0.9.0 shipped
|
|
|
242
244
|
- [ ] `ast_to_markdown.py`: image-render duplication + falsy-based code-block buffer flush ([#600](https://github.com/khuisman/mcp-gee-sweet/issues/600))
|
|
243
245
|
- [ ] `html_parser.py` `handle_data`: a SIM114 lint merge conflates two structurally distinct buffering triggers ([#638](https://github.com/khuisman/mcp-gee-sweet/issues/638))
|
|
244
246
|
- [ ] Docs image-sharing failure paths: unrevocable `anyone:reader` grant + missing folder-cache invalidation in `_replace_doc_content` ([#666](https://github.com/khuisman/mcp-gee-sweet/issues/666))
|
|
247
|
+
- [ ] `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))
|
|
248
|
+
- [ ] `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))
|
|
245
249
|
- [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)
|
|
246
250
|
- [ ] Docstrings: `create_paragraph_bullets` carries internal API-quirk mechanics plus residual bare issue refs in `style.py` tool docstrings ([#683](https://github.com/khuisman/mcp-gee-sweet/issues/683))
|
|
247
251
|
- [ ] `TestIsolatedDepthRunGlyph` asserts request shape, not rendered nesting outcome (PR #711) ([#715](https://github.com/khuisman/mcp-gee-sweet/issues/715))
|
|
@@ -270,6 +274,7 @@ Same shape as v0.8.1 — stabilize on defects that surfaced since v0.9.0 shipped
|
|
|
270
274
|
- [x] Deduplicate the `max_results` clamp expression, hand-copied 8x across `drive/files.py` with inconsistent caps (PR #736 follow-up) ([#737](https://github.com/khuisman/mcp-gee-sweet/issues/737)) (PR #781)
|
|
271
275
|
- [x] `download_file`: one pre-flight `local_path` check before any Drive call, for both trailing-slash and plain branches, naming the blocking path (#724/#827 follow-up; on-deck, `good first issue`) (PR #855) ([#836](https://github.com/khuisman/mcp-gee-sweet/issues/836))
|
|
272
276
|
- [ ] `sync_folder`: a failed mid-stream download leaves a truncated local file with a fresh mtime, which the next sync uploads over the intact Drive copy — download to a temp file and `os.replace` on success; check `download_file`/`download_folder` too. Found by code review, not yet observed live (lane-a, after #814 since both touch `_run_one`) ([#844](https://github.com/khuisman/mcp-gee-sweet/issues/844))
|
|
277
|
+
- [ ] `download_file` local-path preflight follow-ups (#836/PR #855): mkdir-race fallback, trailing `/.` treated as a file destination, dangling-symlink inconsistency — lane A ([#874](https://github.com/khuisman/mcp-gee-sweet/issues/874))
|
|
273
278
|
|
|
274
279
|
**Calendar hardening** _(triaged out of `backlog` 2026-09-11)_
|
|
275
280
|
- [ ] `list_calendar_acl` pagination fix (#460) needs a response-size cap, loop safety bound, and partial-failure handling ([#615](https://github.com/khuisman/mcp-gee-sweet/issues/615))
|
|
@@ -379,6 +384,9 @@ Follows the design in [`docs/decisions/decision-comments-first-class.md`](decisi
|
|
|
379
384
|
- [ ] Decide handling of ASYNC240 findings — sync `pathlib` calls in async tool functions (16 sites); **decision needed** ([#378](https://github.com/khuisman/mcp-gee-sweet/issues/378))
|
|
380
385
|
- [ ] Investigate other MCP clients' response-overflow handling for platform-specific `MAX_TOOL_RESPONSE_CHARS` guidance ([#555](https://github.com/khuisman/mcp-gee-sweet/issues/555))
|
|
381
386
|
|
|
387
|
+
**QA**
|
|
388
|
+
- [ ] Dedicated QA calendar, teardown for creation-only tests, and a `cleanup_stray_artifacts` sweep — split from #304, no second account needed — Aziz ([#886](https://github.com/khuisman/mcp-gee-sweet/issues/886))
|
|
389
|
+
|
|
382
390
|
### Tier 3 — Advanced / occasionally needed _(target: [v1.0.0](https://github.com/khuisman/mcp-gee-sweet/issues?q=is%3Aissue+label%3Av1.0))_
|
|
383
391
|
|
|
384
392
|
**Sheets**
|
|
@@ -509,7 +517,7 @@ The test expected the API to silently truncate a 2D array that's wider than the
|
|
|
509
517
|
- [x] Google Docs tools (all 13) — `docs.md`
|
|
510
518
|
- [x] Calendar tools — `calendar.md`
|
|
511
519
|
- [x] Infrastructure — `infra.md`
|
|
512
|
-
- [ ] QA gaps targeted for v0.9 — disposition of v0.8.0 SKIP entries, done, triaged the rest below ([x] [#227](https://github.com/khuisman/mcp-gee-sweet/issues/227), PR #493); dedicated QA Google account + calendar + pollution root-cause fixes, consolidates #225/#226/#249 — deferred out of v0.9.0 (2026-09-02, paid-account cost not justified for the volume); revisit via alternate OAuth client configs ([#304](https://github.com/khuisman/mcp-gee-sweet/issues/304)); second Drive folder + shared drive fixtures, consolidates #44/#48 — done as a full fixture-set re-provision into the `mcp-gee-sweet-shared` Shared Drive (Workspace-org migration overtook the original scope); `TEST_FOLDER_2_ID` half retired, TC-D123 permanently skipped ([x] [#305](https://github.com/khuisman/mcp-gee-sweet/issues/305), PR #677); two Aziz-routed test-doc fixups surfaced by PR #677's fixture re-provisioning, both to be handled during Full Regression pass prep — TC-DOC80 can no longer trip the response cap post-#519, needs a per-case low-cap override ([#678](https://github.com/khuisman/mcp-gee-sweet/issues/678)); several `drive_files.md` cases assume My-Drive semantics now that fixtures live in a Shared Drive (TC-D58/D61/D03/D59/D27/D04/D175), and TC-D27 may expose a real `list_folders` Shared-Drive tool gap ([#680](https://github.com/khuisman/mcp-gee-sweet/issues/680)); domain/public sharing coverage — moved to v0.9.1 (2026-09-02) ([#49](https://github.com/khuisman/mcp-gee-sweet/issues/49)); local-filesystem coverage ([x] [#50](https://github.com/khuisman/mcp-gee-sweet/issues/50), PR #644); environment-constraint coverage — closed 2026-09-02 as duplicate, untangled into #305 (shared drives) / #49 (external + public sharing) / #304 (second account, deferred) / #673 (concurrent-session procedure) ([x] [#53](https://github.com/khuisman/mcp-gee-sweet/issues/53)); concurrent-session test procedure — 2 subagents + `mkdir` barrier against a main-checkout server, split from #53 — procedure + pre-approved-SKIP retirement via PR #675, `aziz.md` release-pass wiring via PR #674; live TC-I24/TC-I02/TC-R36 runs happen in the v0.9.0 Full Regression pass (tracked in `docs/qa/runs/v0.9.0.md`), not a standalone issue ([x] [#673](https://github.com/khuisman/mcp-gee-sweet/issues/673), PRs #674/#675); split `drive.md`/`docs.md` test files by submodule, do before #264/#224 to avoid a file-rename conflict ([x] [#233](https://github.com/khuisman/mcp-gee-sweet/issues/233), PR #582); Playwright tag consistency audit + formalize the required/spot-check/skip tiers, raised while fixing #248, sequenced after #233 ([x] [#264](https://github.com/khuisman/mcp-gee-sweet/issues/264), PR #592, #598); image fixtures for `insert_inline_image`, sequenced after #233 ([x] [#224](https://github.com/khuisman/mcp-gee-sweet/issues/224), PR #639); dedicated screenshots folder, independent of the rest ([x] [#231](https://github.com/khuisman/mcp-gee-sweet/issues/231), PR #583); `add_chart` test cases leave charts behind with no teardown, polluting the shared Sales fixture across every QA run — same pollution-cluster shape as #304/#305 ([x] [#322](https://github.com/khuisman/mcp-gee-sweet/issues/322), PR #633); plus 7 small unit-test-coverage gaps filed 2026-08-02/08, each self-contained/ad hoc pickup — [x] [#487](https://github.com/khuisman/mcp-gee-sweet/issues/487) (PR #559), [x] [#488](https://github.com/khuisman/mcp-gee-sweet/issues/488) (PR #563), [x] [#489](https://github.com/khuisman/mcp-gee-sweet/issues/489) (PR #569), [x] [#490](https://github.com/khuisman/mcp-gee-sweet/issues/490) (PR #572), [x] [#491](https://github.com/khuisman/mcp-gee-sweet/issues/491) (PR #575), [x] [#494](https://github.com/khuisman/mcp-gee-sweet/issues/494) (PR #577), [x] [#495](https://github.com/khuisman/mcp-gee-sweet/issues/495) (PR #634)
|
|
520
|
+
- [ ] QA gaps targeted for v0.9 — disposition of v0.8.0 SKIP entries, done, triaged the rest below ([x] [#227](https://github.com/khuisman/mcp-gee-sweet/issues/227), PR #493); dedicated QA Google account + calendar + pollution root-cause fixes, consolidates #225/#226/#249 — deferred out of v0.9.0 (2026-09-02, paid-account cost not justified for the volume); revisit via alternate OAuth client configs; split 2026-10-04: multi-user testing stays here at v1.0, pollution cleanup moved to [#886](https://github.com/khuisman/mcp-gee-sweet/issues/886) (v0.9.3) ([#304](https://github.com/khuisman/mcp-gee-sweet/issues/304)); second Drive folder + shared drive fixtures, consolidates #44/#48 — done as a full fixture-set re-provision into the `mcp-gee-sweet-shared` Shared Drive (Workspace-org migration overtook the original scope); `TEST_FOLDER_2_ID` half retired, TC-D123 permanently skipped ([x] [#305](https://github.com/khuisman/mcp-gee-sweet/issues/305), PR #677); two Aziz-routed test-doc fixups surfaced by PR #677's fixture re-provisioning, both to be handled during Full Regression pass prep — TC-DOC80 can no longer trip the response cap post-#519, needs a per-case low-cap override ([#678](https://github.com/khuisman/mcp-gee-sweet/issues/678)); several `drive_files.md` cases assume My-Drive semantics now that fixtures live in a Shared Drive (TC-D58/D61/D03/D59/D27/D04/D175), and TC-D27 may expose a real `list_folders` Shared-Drive tool gap ([#680](https://github.com/khuisman/mcp-gee-sweet/issues/680)); domain/public sharing coverage — moved to v0.9.1 (2026-09-02) ([#49](https://github.com/khuisman/mcp-gee-sweet/issues/49)); local-filesystem coverage ([x] [#50](https://github.com/khuisman/mcp-gee-sweet/issues/50), PR #644); environment-constraint coverage — closed 2026-09-02 as duplicate, untangled into #305 (shared drives) / #49 (external + public sharing) / #304 (second account, deferred) / #673 (concurrent-session procedure) ([x] [#53](https://github.com/khuisman/mcp-gee-sweet/issues/53)); concurrent-session test procedure — 2 subagents + `mkdir` barrier against a main-checkout server, split from #53 — procedure + pre-approved-SKIP retirement via PR #675, `aziz.md` release-pass wiring via PR #674; live TC-I24/TC-I02/TC-R36 runs happen in the v0.9.0 Full Regression pass (tracked in `docs/qa/runs/v0.9.0.md`), not a standalone issue ([x] [#673](https://github.com/khuisman/mcp-gee-sweet/issues/673), PRs #674/#675); split `drive.md`/`docs.md` test files by submodule, do before #264/#224 to avoid a file-rename conflict ([x] [#233](https://github.com/khuisman/mcp-gee-sweet/issues/233), PR #582); Playwright tag consistency audit + formalize the required/spot-check/skip tiers, raised while fixing #248, sequenced after #233 ([x] [#264](https://github.com/khuisman/mcp-gee-sweet/issues/264), PR #592, #598); image fixtures for `insert_inline_image`, sequenced after #233 ([x] [#224](https://github.com/khuisman/mcp-gee-sweet/issues/224), PR #639); dedicated screenshots folder, independent of the rest ([x] [#231](https://github.com/khuisman/mcp-gee-sweet/issues/231), PR #583); `add_chart` test cases leave charts behind with no teardown, polluting the shared Sales fixture across every QA run — same pollution-cluster shape as #304/#305 ([x] [#322](https://github.com/khuisman/mcp-gee-sweet/issues/322), PR #633); plus 7 small unit-test-coverage gaps filed 2026-08-02/08, each self-contained/ad hoc pickup — [x] [#487](https://github.com/khuisman/mcp-gee-sweet/issues/487) (PR #559), [x] [#488](https://github.com/khuisman/mcp-gee-sweet/issues/488) (PR #563), [x] [#489](https://github.com/khuisman/mcp-gee-sweet/issues/489) (PR #569), [x] [#490](https://github.com/khuisman/mcp-gee-sweet/issues/490) (PR #572), [x] [#491](https://github.com/khuisman/mcp-gee-sweet/issues/491) (PR #575), [x] [#494](https://github.com/khuisman/mcp-gee-sweet/issues/494) (PR #577), [x] [#495](https://github.com/khuisman/mcp-gee-sweet/issues/495) (PR #634)
|
|
513
521
|
- See [GitHub Issues (label: qa)](https://github.com/khuisman/mcp-gee-sweet/issues?q=label%3Aqa) for open QA gaps and fixture issues
|
|
514
522
|
|
|
515
523
|
---
|
|
@@ -197,9 +197,9 @@ Requires the `drive.activity.readonly` scope; uses the Drive Activity API v2.
|
|
|
197
197
|
| Tool | Description | Key parameters |
|
|
198
198
|
|---|---|---|
|
|
199
199
|
| `list_messages` | List messages in the authenticated user's mailbox. | `query?`, `label_ids?`, `max_results?`, `page_token?`, `include_spam_trash?` |
|
|
200
|
-
| `get_message` | Fetch a single message by ID, including headers, body, and attachment metadata. | `message_id`, `local_path?` |
|
|
200
|
+
| `get_message` | Fetch a single message by ID, including headers, body, and attachment metadata. | `message_id`, `local_path?`, `include_body?` |
|
|
201
201
|
| `list_threads` | List conversation threads in the authenticated user's mailbox. | `query?`, `label_ids?`, `max_results?`, `page_token?`, `include_spam_trash?` |
|
|
202
|
-
| `get_thread` | Fetch all messages in a conversation thread. | `thread_id`, `local_path?` |
|
|
202
|
+
| `get_thread` | Fetch all messages in a conversation thread. | `thread_id`, `local_path?`, `include_body?` |
|
|
203
203
|
| `list_labels` | List all labels in the authenticated user's mailbox (system and user-defined). | — |
|
|
204
204
|
| `send_message` | Send a new email message. | `to`, `subject`, `body`, `cc?`, `bcc?`, `body_html?`, `attachments?` |
|
|
205
205
|
| `create_draft` | Create a draft email without sending it. | `to`, `subject`, `body`, `cc?`, `bcc?`, `body_html?`, `attachments?` |
|
|
@@ -398,6 +398,34 @@ class _PendingMessage:
|
|
|
398
398
|
parts: _PayloadParts | None
|
|
399
399
|
|
|
400
400
|
|
|
401
|
+
# The headers a shaped message surfaces, as (result key, header name). Also the
|
|
402
|
+
# metadataHeaders filter for a body-less fetch, so it requests exactly these (#793).
|
|
403
|
+
_SHAPED_HEADERS = (
|
|
404
|
+
("from", "From"),
|
|
405
|
+
# Surfaced because reply_to_message prefers it over From when replying to
|
|
406
|
+
# someone else's message (#791).
|
|
407
|
+
("reply_to", "Reply-To"),
|
|
408
|
+
("to", "To"),
|
|
409
|
+
("cc", "Cc"),
|
|
410
|
+
("bcc", "Bcc"),
|
|
411
|
+
("subject", "Subject"),
|
|
412
|
+
("date", "Date"),
|
|
413
|
+
("message_id", "Message-ID"),
|
|
414
|
+
("in_reply_to", "In-Reply-To"),
|
|
415
|
+
("references", "References"),
|
|
416
|
+
)
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def _get_format_kwargs(include_body: bool) -> dict[str, Any]:
|
|
420
|
+
"""The format kwargs for messages.get / threads.get. 'metadata' returns headers,
|
|
421
|
+
snippet, labels, and size but no payload parts, so no body or attachment data;
|
|
422
|
+
metadataHeaders keeps it from also returning every Received/ARC/DKIM header
|
|
423
|
+
that shaping would drop (#793)."""
|
|
424
|
+
if include_body:
|
|
425
|
+
return {"format": "full"}
|
|
426
|
+
return {"format": "metadata", "metadataHeaders": [name for _, name in _SHAPED_HEADERS]}
|
|
427
|
+
|
|
428
|
+
|
|
401
429
|
def _start_shaping(msg: dict[str, Any], *, include_body: bool = True) -> _PendingMessage:
|
|
402
430
|
payload = msg.get("payload") or {}
|
|
403
431
|
headers = _header_map(payload.get("headers"))
|
|
@@ -408,20 +436,7 @@ def _start_shaping(msg: dict[str, Any], *, include_body: bool = True) -> _Pendin
|
|
|
408
436
|
"label_ids": msg.get("labelIds") or [],
|
|
409
437
|
"internal_date": msg.get("internalDate"),
|
|
410
438
|
"size_estimate": msg.get("sizeEstimate"),
|
|
411
|
-
"headers": {
|
|
412
|
-
"from": headers.get("from"),
|
|
413
|
-
# Surfaced because reply_to_message prefers it over From when replying
|
|
414
|
-
# to someone else's message (#791).
|
|
415
|
-
"reply_to": headers.get("reply-to"),
|
|
416
|
-
"to": headers.get("to"),
|
|
417
|
-
"cc": headers.get("cc"),
|
|
418
|
-
"bcc": headers.get("bcc"),
|
|
419
|
-
"subject": headers.get("subject"),
|
|
420
|
-
"date": headers.get("date"),
|
|
421
|
-
"message_id": headers.get("message-id"),
|
|
422
|
-
"in_reply_to": headers.get("in-reply-to"),
|
|
423
|
-
"references": headers.get("references"),
|
|
424
|
-
},
|
|
439
|
+
"headers": {key: headers.get(name.lower()) for key, name in _SHAPED_HEADERS},
|
|
425
440
|
}
|
|
426
441
|
parts = _extract_bodies_and_attachments(payload) if include_body else None
|
|
427
442
|
return _PendingMessage(shaped, parts)
|
|
@@ -653,7 +668,10 @@ def register(tool):
|
|
|
653
668
|
|
|
654
669
|
@tool(annotations=ToolAnnotations(title="Get Message", readOnlyHint=True))
|
|
655
670
|
async def get_message(
|
|
656
|
-
message_id: str,
|
|
671
|
+
message_id: str,
|
|
672
|
+
local_path: str | None = None,
|
|
673
|
+
include_body: bool = True,
|
|
674
|
+
ctx: Context = None,
|
|
657
675
|
) -> dict[str, Any]:
|
|
658
676
|
"""
|
|
659
677
|
Fetch a single message by ID, including headers, body, and attachment metadata.
|
|
@@ -663,6 +681,9 @@ def register(tool):
|
|
|
663
681
|
local_path: Optional local filesystem path (file or directory) to write the
|
|
664
682
|
message JSON to instead of returning it inline. Bypasses the
|
|
665
683
|
response-size cap, for messages with very large bodies.
|
|
684
|
+
include_body: When False, fetch only metadata (Gmail's format='metadata'):
|
|
685
|
+
no body is downloaded, and body_plain, body_html, and
|
|
686
|
+
attachments are left out of the result.
|
|
666
687
|
|
|
667
688
|
Returns:
|
|
668
689
|
Message with id, thread_id, snippet, label_ids, headers (from/to/cc/bcc/
|
|
@@ -678,8 +699,9 @@ def register(tool):
|
|
|
678
699
|
set; a separately delivered body already over the cap raises before
|
|
679
700
|
being downloaded. If local_path is set, returns {local_path, message_id,
|
|
680
701
|
bytes_written} instead, plus body_fetch_errors / body_decode_errors
|
|
681
|
-
(each entry with message_id) when present.
|
|
682
|
-
|
|
702
|
+
(each entry with message_id) when present. With include_body=False, only
|
|
703
|
+
id, thread_id, snippet, label_ids, internal_date, size_estimate, and
|
|
704
|
+
headers. On API failure, {"error": "..."}.
|
|
683
705
|
"""
|
|
684
706
|
if unauthorized := get_gmail_unauthorized_message():
|
|
685
707
|
return {"error": unauthorized}
|
|
@@ -688,15 +710,16 @@ def register(tool):
|
|
|
688
710
|
msg = await execute_in_thread(
|
|
689
711
|
lc.gmail_service.users()
|
|
690
712
|
.messages()
|
|
691
|
-
.get(userId=_USER, id=message_id,
|
|
713
|
+
.get(userId=_USER, id=message_id, **_get_format_kwargs(include_body))
|
|
692
714
|
.execute,
|
|
693
715
|
lc.gmail_service,
|
|
694
716
|
)
|
|
695
717
|
except Exception as e:
|
|
696
718
|
return {"error": str(e)}
|
|
697
719
|
|
|
698
|
-
|
|
699
|
-
|
|
720
|
+
# No body is fetched with include_body=False, so only headers can be large.
|
|
721
|
+
hint = "The message body is large. " if include_body else "The message headers are large. "
|
|
722
|
+
pending = [_start_shaping(msg, include_body=include_body)]
|
|
700
723
|
if not local_path:
|
|
701
724
|
enforce_response_size_floor(
|
|
702
725
|
_deferred_body_bytes(pending), tool_name="get_message", hint=hint
|
|
@@ -784,7 +807,10 @@ def register(tool):
|
|
|
784
807
|
|
|
785
808
|
@tool(annotations=ToolAnnotations(title="Get Thread", readOnlyHint=True))
|
|
786
809
|
async def get_thread(
|
|
787
|
-
thread_id: str,
|
|
810
|
+
thread_id: str,
|
|
811
|
+
local_path: str | None = None,
|
|
812
|
+
include_body: bool = True,
|
|
813
|
+
ctx: Context = None,
|
|
788
814
|
) -> dict[str, Any]:
|
|
789
815
|
"""
|
|
790
816
|
Fetch all messages in a conversation thread.
|
|
@@ -794,9 +820,14 @@ def register(tool):
|
|
|
794
820
|
local_path: Optional local filesystem path (file or directory) to write the
|
|
795
821
|
thread JSON to instead of returning it inline. Bypasses the
|
|
796
822
|
response-size cap, for threads with very large bodies.
|
|
823
|
+
include_body: When False, fetch only each message's metadata (Gmail's
|
|
824
|
+
format='metadata'): no body is downloaded, and body_plain,
|
|
825
|
+
body_html, and attachments are left out. Use it to list a
|
|
826
|
+
thread too large to read whole, then get_message each ID.
|
|
797
827
|
|
|
798
828
|
Returns:
|
|
799
|
-
Thread with id, snippet, history_id, and messages (same shape as get_message
|
|
829
|
+
Thread with id, snippet, history_id, and messages (same shape as get_message,
|
|
830
|
+
including its include_body=False shape).
|
|
800
831
|
Raises an error naming the size if the thread is over the response-size cap
|
|
801
832
|
and local_path is not set. If local_path is set, returns {local_path,
|
|
802
833
|
thread_id, message_count, bytes_written} instead, plus body_fetch_errors /
|
|
@@ -810,15 +841,20 @@ def register(tool):
|
|
|
810
841
|
thread = await execute_in_thread(
|
|
811
842
|
lc.gmail_service.users()
|
|
812
843
|
.threads()
|
|
813
|
-
.get(userId=_USER, id=thread_id,
|
|
844
|
+
.get(userId=_USER, id=thread_id, **_get_format_kwargs(include_body))
|
|
814
845
|
.execute,
|
|
815
846
|
lc.gmail_service,
|
|
816
847
|
)
|
|
817
848
|
except Exception as e:
|
|
818
849
|
return {"error": str(e)}
|
|
819
850
|
|
|
820
|
-
hint =
|
|
821
|
-
|
|
851
|
+
hint = (
|
|
852
|
+
"The thread is large; call get_thread with include_body=False to list its "
|
|
853
|
+
"message IDs and headers, then get_message on individual IDs. "
|
|
854
|
+
if include_body
|
|
855
|
+
else "The thread has too many messages to list inline. "
|
|
856
|
+
)
|
|
857
|
+
pending = [_start_shaping(m, include_body=include_body) for m in thread.get("messages", [])]
|
|
822
858
|
if not local_path:
|
|
823
859
|
enforce_response_size_floor(
|
|
824
860
|
_deferred_body_bytes(pending), tool_name="get_thread", hint=hint
|
|
@@ -49,6 +49,22 @@ def _raw_payload_from_send_call(gmail_svc) -> bytes:
|
|
|
49
49
|
_gmail_tool, _gmail_tools = _make_tool_registry()
|
|
50
50
|
gmail_module.register(_gmail_tool)
|
|
51
51
|
|
|
52
|
+
# Every header a shaped message surfaces, which a body-less fetch requests via
|
|
53
|
+
# metadataHeaders (#793). Spelled out rather than imported from gmail.py, so a
|
|
54
|
+
# change to the filter has to be made here too.
|
|
55
|
+
_SHAPED_HEADER_NAMES = [
|
|
56
|
+
"From",
|
|
57
|
+
"Reply-To",
|
|
58
|
+
"To",
|
|
59
|
+
"Cc",
|
|
60
|
+
"Bcc",
|
|
61
|
+
"Subject",
|
|
62
|
+
"Date",
|
|
63
|
+
"Message-ID",
|
|
64
|
+
"In-Reply-To",
|
|
65
|
+
"References",
|
|
66
|
+
]
|
|
67
|
+
|
|
52
68
|
|
|
53
69
|
class TestGmailNotAuthorized:
|
|
54
70
|
"""#790 / PR #807 QA round 1: when the OAuth token lacks only the Gmail scope,
|
|
@@ -189,6 +205,42 @@ class TestGetMessage:
|
|
|
189
205
|
|
|
190
206
|
assert "error" in result
|
|
191
207
|
|
|
208
|
+
async def test_include_body_false_fetches_metadata_only(self):
|
|
209
|
+
"""#793: metadata format has no payload parts, so no body keys and no fetches."""
|
|
210
|
+
gmail_svc = MagicMock()
|
|
211
|
+
messages = gmail_svc.users.return_value.messages.return_value
|
|
212
|
+
messages.get.return_value.execute.return_value = {
|
|
213
|
+
"id": "m1",
|
|
214
|
+
"threadId": "t1",
|
|
215
|
+
"snippet": "Hello",
|
|
216
|
+
"labelIds": ["INBOX"],
|
|
217
|
+
"internalDate": "1700000000000",
|
|
218
|
+
"sizeEstimate": 162270,
|
|
219
|
+
"payload": {
|
|
220
|
+
"mimeType": "multipart/mixed",
|
|
221
|
+
"headers": [
|
|
222
|
+
{"name": "From", "value": "a@example.com"},
|
|
223
|
+
{"name": "Subject", "value": "Hi"},
|
|
224
|
+
],
|
|
225
|
+
},
|
|
226
|
+
}
|
|
227
|
+
ctx = _make_ctx(gmail_service=gmail_svc)
|
|
228
|
+
|
|
229
|
+
result = await _gmail_tools["get_message"](message_id="m1", include_body=False, ctx=ctx)
|
|
230
|
+
|
|
231
|
+
messages.get.assert_called_once_with(
|
|
232
|
+
userId="me", id="m1", format="metadata", metadataHeaders=_SHAPED_HEADER_NAMES
|
|
233
|
+
)
|
|
234
|
+
assert result["id"] == "m1"
|
|
235
|
+
assert result["thread_id"] == "t1"
|
|
236
|
+
assert result["snippet"] == "Hello"
|
|
237
|
+
assert result["size_estimate"] == 162270
|
|
238
|
+
assert result["headers"]["from"] == "a@example.com"
|
|
239
|
+
assert result["headers"]["subject"] == "Hi"
|
|
240
|
+
for key in ("body_plain", "body_html", "attachments"):
|
|
241
|
+
assert key not in result
|
|
242
|
+
messages.attachments.return_value.get.assert_not_called()
|
|
243
|
+
|
|
192
244
|
|
|
193
245
|
def _b64(raw: bytes, *, pad: bool = True) -> str:
|
|
194
246
|
encoded = base64.urlsafe_b64encode(raw).decode()
|
|
@@ -837,9 +889,40 @@ class TestMessageSizeCap:
|
|
|
837
889
|
}
|
|
838
890
|
ctx = _make_ctx(gmail_service=gmail_svc)
|
|
839
891
|
|
|
840
|
-
with pytest.raises(ValueError, match=r"get_thread.*Pass local_path"):
|
|
892
|
+
with pytest.raises(ValueError, match=r"get_thread.*include_body=False.*Pass local_path"):
|
|
841
893
|
await _gmail_tools["get_thread"](thread_id="t1", ctx=ctx)
|
|
842
894
|
|
|
895
|
+
async def test_get_message_body_less_over_cap_blames_headers_not_body(self, small_cap):
|
|
896
|
+
"""#793 QA: no body is fetched with include_body=False, so the hint can't
|
|
897
|
+
call the body large."""
|
|
898
|
+
gmail_svc = MagicMock()
|
|
899
|
+
gmail_svc.users.return_value.messages.return_value.get.return_value.execute.return_value = {
|
|
900
|
+
"id": "m1",
|
|
901
|
+
"threadId": "t1",
|
|
902
|
+
"payload": {"headers": [{"name": "References", "value": "<r@x> " * 100}]},
|
|
903
|
+
}
|
|
904
|
+
ctx = _make_ctx(gmail_service=gmail_svc)
|
|
905
|
+
|
|
906
|
+
with pytest.raises(ValueError, match=r"get_message.*headers are large") as exc:
|
|
907
|
+
await _gmail_tools["get_message"](message_id="m1", include_body=False, ctx=ctx)
|
|
908
|
+
assert "body" not in str(exc.value)
|
|
909
|
+
|
|
910
|
+
async def test_get_thread_body_less_over_cap_drops_include_body_hint(self, small_cap):
|
|
911
|
+
"""#793: suggesting include_body=False to a call already using it is useless."""
|
|
912
|
+
gmail_svc = MagicMock()
|
|
913
|
+
gmail_svc.users.return_value.threads.return_value.get.return_value.execute.return_value = {
|
|
914
|
+
"id": "t1",
|
|
915
|
+
"messages": [
|
|
916
|
+
{"id": f"m{i}", "threadId": "t1", "snippet": "s" * 50, "payload": {}}
|
|
917
|
+
for i in range(5)
|
|
918
|
+
],
|
|
919
|
+
}
|
|
920
|
+
ctx = _make_ctx(gmail_service=gmail_svc)
|
|
921
|
+
|
|
922
|
+
with pytest.raises(ValueError, match=r"get_thread.*Pass local_path") as exc:
|
|
923
|
+
await _gmail_tools["get_thread"](thread_id="t1", include_body=False, ctx=ctx)
|
|
924
|
+
assert "include_body" not in str(exc.value)
|
|
925
|
+
|
|
843
926
|
async def test_get_thread_local_path_writes_full_thread(self, small_cap, tmp_path):
|
|
844
927
|
gmail_svc = self._large()
|
|
845
928
|
gmail_svc.users.return_value.threads.return_value.get.return_value.execute.return_value = {
|
|
@@ -970,6 +1053,50 @@ class TestGetThread:
|
|
|
970
1053
|
|
|
971
1054
|
assert "error" in result
|
|
972
1055
|
|
|
1056
|
+
async def test_default_fetches_full_format(self):
|
|
1057
|
+
gmail_svc = MagicMock()
|
|
1058
|
+
threads = gmail_svc.users.return_value.threads.return_value
|
|
1059
|
+
threads.get.return_value.execute.return_value = {"id": "t1", "messages": []}
|
|
1060
|
+
ctx = _make_ctx(gmail_service=gmail_svc)
|
|
1061
|
+
|
|
1062
|
+
await _gmail_tools["get_thread"](thread_id="t1", ctx=ctx)
|
|
1063
|
+
|
|
1064
|
+
threads.get.assert_called_once_with(userId="me", id="t1", format="full")
|
|
1065
|
+
|
|
1066
|
+
async def test_include_body_false_lists_ids_and_headers_only(self):
|
|
1067
|
+
"""#793: a thread too large to read whole can still be listed by message ID."""
|
|
1068
|
+
gmail_svc = MagicMock()
|
|
1069
|
+
threads = gmail_svc.users.return_value.threads.return_value
|
|
1070
|
+
threads.get.return_value.execute.return_value = {
|
|
1071
|
+
"id": "t1",
|
|
1072
|
+
"historyId": "5",
|
|
1073
|
+
"messages": [
|
|
1074
|
+
{
|
|
1075
|
+
"id": f"m{i}",
|
|
1076
|
+
"threadId": "t1",
|
|
1077
|
+
"snippet": f"snippet {i}",
|
|
1078
|
+
"payload": {
|
|
1079
|
+
"mimeType": "multipart/mixed",
|
|
1080
|
+
"headers": [{"name": "Subject", "value": f"Re: {i}"}],
|
|
1081
|
+
},
|
|
1082
|
+
}
|
|
1083
|
+
for i in (1, 2)
|
|
1084
|
+
],
|
|
1085
|
+
}
|
|
1086
|
+
ctx = _make_ctx(gmail_service=gmail_svc)
|
|
1087
|
+
|
|
1088
|
+
result = await _gmail_tools["get_thread"](thread_id="t1", include_body=False, ctx=ctx)
|
|
1089
|
+
|
|
1090
|
+
threads.get.assert_called_once_with(
|
|
1091
|
+
userId="me", id="t1", format="metadata", metadataHeaders=_SHAPED_HEADER_NAMES
|
|
1092
|
+
)
|
|
1093
|
+
assert [m["id"] for m in result["messages"]] == ["m1", "m2"]
|
|
1094
|
+
assert result["messages"][1]["headers"]["subject"] == "Re: 2"
|
|
1095
|
+
for message in result["messages"]:
|
|
1096
|
+
for key in ("body_plain", "body_html", "attachments"):
|
|
1097
|
+
assert key not in message
|
|
1098
|
+
gmail_svc.users.return_value.messages.return_value.attachments.assert_not_called()
|
|
1099
|
+
|
|
973
1100
|
|
|
974
1101
|
class TestListLabels:
|
|
975
1102
|
async def test_maps_system_and_user_labels(self):
|
{mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/.claude/commands/cleanup-worktrees.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/.claude/commands/notify-partner.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/.claude/commands/roadmap-status.md
RENAMED
|
File without changes
|
{mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/.claude/commands/start-worktree.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/.github/ISSUE_TEMPLATE/bug_report.md
RENAMED
|
File without changes
|
{mcp_gee_sweet-0.9.2.dev212 → mcp_gee_sweet-0.9.2.dev215}/.github/ISSUE_TEMPLATE/feature_request.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|