mcp-gee-sweet 0.9.2.dev233__tar.gz → 0.9.2.dev236__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.dev233 → mcp_gee_sweet-0.9.2.dev236}/PKG-INFO +1 -1
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/design/sse-consent-off-event-loop.md +1 -1
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/qa/tests/infra.md +31 -1
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/roadmap.md +1 -1
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/src/mcp_gee_sweet/server.py +17 -2
- mcp_gee_sweet-0.9.2.dev236/src/mcp_gee_sweet/sse_shutdown.py +60 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/src/mcp_gee_sweet/tools/drive/files.py +5 -7
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/tests/test_server.py +26 -1
- mcp_gee_sweet-0.9.2.dev236/tests/test_sse_shutdown.py +189 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/.claude/commands/cleanup-worktrees.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/.claude/commands/merge-pr.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/.claude/commands/notify-kai.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/.claude/commands/notify-partner.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/.claude/commands/orchestrator.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/.claude/commands/prep-for-pr.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/.claude/commands/qa-kickoff.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/.claude/commands/release.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/.claude/commands/retro.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/.claude/commands/roadmap-status.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/.claude/commands/start-worktree.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/.claude/commands/team-member.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/.claude/commands/verify-pr.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/.claude/settings.json +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/.claude/team-roles/amy.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/.claude/team-roles/aziz.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/.claude/team-roles/bob.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/.claude/team-roles/dev.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/.claude/team-roles/joy.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/.claude/team-roles/kai.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/.claude/team-roles/qa.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/.dockerignore +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/.github/dependabot.yml +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/.github/workflows/ci.yml +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/.github/workflows/docs.yml +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/.github/workflows/publish-dev.yml +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/.github/workflows/release.yml +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/.gitignore +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/.pre-commit-config.yaml +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/.python-version +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/CHANGELOG.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/CLAUDE.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/CODE_OF_CONDUCT.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/CONTRIBUTING.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/Dockerfile +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/LICENSE +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/Makefile +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/README.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/SECURITY.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docker-compose.yml +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/assets/logo.png +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/auth.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/client-setup.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/configuration.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/decisions/decision-async-tool-execution.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/decisions/decision-auth-status-limitation-categories.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/decisions/decision-cache-invalidation.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/decisions/decision-chart-theming.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/decisions/decision-comments-first-class.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/decisions/decision-composite-tools.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/decisions/decision-condition-type-docstring-generation.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/decisions/decision-converted-doc-change-detection.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/decisions/decision-dev-team.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/decisions/decision-docs-formatting.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/decisions/decision-fork.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/decisions/decision-gmail-domain.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/decisions/decision-grid-data-size-cap.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/decisions/decision-local-fs-test-harness.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/decisions/decision-mcp-v2-resource-context-access.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/decisions/decision-pillow-image-dependency.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/decisions/decision-prompt-qa-role.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/decisions/decision-publishing.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/decisions/decision-release-docs-roles.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/decisions/decision-repositioning.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/decisions/decision-response-size-cap-generalization.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/decisions/decision-response-size-cap-reevaluation-519.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/decisions/decision-review-finding-ticket-bar.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/decisions/decision-supports-all-drives-lint-check.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/decisions/decision-sync-folder-recursion.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/decisions/decision-testing.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/decisions/decision-tool-error-visibility.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/decisions/index.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/design/blockquote-representation.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/design/borderless-table-columns.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/design/doc-to-markdown.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/design/docs-ast-pipeline.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/design/effectiveformat-spike.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/design/heading-anchor-resolution.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/design/image-conversion.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/design/index.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/design/markdown-support.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/design/module-history.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/design/phase3-theme-system.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/design.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/index.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/known-limitations.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/notes-read.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/qa/README.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/qa/fixtures/qa-fixture-pixel.png +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/qa/fixtures/tc-d195-create-doc.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/qa/fixtures/tc-d196-create-doc.html +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/qa/fixtures/tc-d213-dollar-escape.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/qa/fixtures/tc-d226-heading-table.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/qa/fixtures/tc-doc102-image-conversion.html +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/qa/fixtures/tc-doc103-image-conversion.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/qa/fixtures/tc-doc104-nested-lists.html +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/qa/fixtures/tc-doc105-nested-lists-gfm.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/qa/fixtures/tc-doc106-nested-lists-4space.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/qa/fixtures/tc-doc135-paragraph-boundary.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/qa/fixtures/tc-doc136-interrupted-block-boundary.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/qa/fixtures/tc-doc137-inline-hr-no-block-ancestor.html +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/qa/fixtures/tc-doc138-nbsp-spacer-paragraph.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/qa/fixtures/tc-doc141-mismatched-list-tags.html +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/qa/fixtures/tc-doc142-heading-anchors.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/qa/fixtures/tc-doc144-anchor-in-table-cell.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/qa/fixtures/tc-doc145-anchor-on-first-element.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/qa/fixtures.template.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/qa/gmail-test-plan.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/qa/operations.yaml +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/qa/playwright_oauth.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/qa/results/.gitkeep +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/qa/results/2026-06-02.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/qa/results/2026-06-14.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/qa/results/2026-06-17.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/qa/results/2026-06-18.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/qa/results/2026-06-21-drive-discovery.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/qa/results/2026-06-21-sheets-delete-clear.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/qa/results/2026-06-28.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/qa/results/2026-07-04-docs.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/qa/results/2026-07-04-drive-smoke.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/qa/results/2026-07-04-infra.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/qa/results/2026-07-04-sheets_read.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/qa/results/2026-07-04.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/qa/results/2026-09-04.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/qa/retro-v0.8.0.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/qa/retro-v0.9.0.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/qa/run.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/qa/runs/README.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/qa/runs/v0.8.0.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/qa/runs/v0.8.1.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/qa/runs/v0.9.0.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/qa/setup.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/qa/tests/calendar.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/qa/tests/docs_content.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/qa/tests/docs_layout.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/qa/tests/docs_style.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/qa/tests/docs_tables.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/qa/tests/drive_activity.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/qa/tests/drive_files.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/qa/tests/drive_sharing.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/qa/tests/drive_transfer.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/qa/tests/gmail.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/qa/tests/sheets_charts.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/qa/tests/sheets_mgmt.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/qa/tests/sheets_read.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/qa/tests/sheets_write.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/style-guide.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/tools.md +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/mkdocs.yml +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/pyproject.toml +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/pyrightconfig.json +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/scripts/check_supports_all_drives.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/scripts/gen_tool_docs.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/scripts/lane_context_hook.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/scripts/oauth_setup.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/scripts/qa_cancel_insert_local_images.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/scripts/qa_gmail_fixtures.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/scripts/setup_team.sh +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/scripts/write_team_mcp_configs.sh +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/server.json +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/src/mcp_gee_sweet/.env.template +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/src/mcp_gee_sweet/__init__.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/src/mcp_gee_sweet/auth.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/src/mcp_gee_sweet/cache.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/src/mcp_gee_sweet/http_transport.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/src/mcp_gee_sweet/tools/__init__.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/src/mcp_gee_sweet/tools/cache.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/src/mcp_gee_sweet/tools/calendar.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/src/mcp_gee_sweet/tools/concurrency.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/src/mcp_gee_sweet/tools/docs/__init__.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/src/mcp_gee_sweet/tools/docs/anchors.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/src/mcp_gee_sweet/tools/docs/ast.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/src/mcp_gee_sweet/tools/docs/ast_to_markdown.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/src/mcp_gee_sweet/tools/docs/comments.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/src/mcp_gee_sweet/tools/docs/content.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/src/mcp_gee_sweet/tools/docs/doc_to_ast.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/src/mcp_gee_sweet/tools/docs/editing.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/src/mcp_gee_sweet/tools/docs/emitter.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/src/mcp_gee_sweet/tools/docs/html_parser.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/src/mcp_gee_sweet/tools/docs/images.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/src/mcp_gee_sweet/tools/docs/indices.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/src/mcp_gee_sweet/tools/docs/layout.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/src/mcp_gee_sweet/tools/docs/markdown_export.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/src/mcp_gee_sweet/tools/docs/named_ranges.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/src/mcp_gee_sweet/tools/docs/style.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/src/mcp_gee_sweet/tools/docs/tables.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/src/mcp_gee_sweet/tools/drive/__init__.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/src/mcp_gee_sweet/tools/drive/activity.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/src/mcp_gee_sweet/tools/drive/sharing.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/src/mcp_gee_sweet/tools/drive/transfer.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/src/mcp_gee_sweet/tools/gmail.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/src/mcp_gee_sweet/tools/response_limits.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/src/mcp_gee_sweet/tools/sheets/__init__.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/src/mcp_gee_sweet/tools/sheets/data.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/src/mcp_gee_sweet/tools/sheets/helpers.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/src/mcp_gee_sweet/tools/sheets/structure.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/tests/__init__.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/tests/conftest.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/tests/drive/__init__.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/tests/drive/test_activity.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/tests/drive/test_files.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/tests/drive/test_sharing.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/tests/drive/test_transfer.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/tests/integration/__init__.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/tests/integration/conftest.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/tests/integration/test_local_fs_harness.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/tests/sheets/__init__.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/tests/sheets/test_data.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/tests/sheets/test_helpers.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/tests/sheets/test_structure.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/tests/test_auth.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/tests/test_cache.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/tests/test_calendar.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/tests/test_check_supports_all_drives.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/tests/test_concurrency.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/tests/test_docs_anchors.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/tests/test_docs_comments.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/tests/test_docs_content.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/tests/test_docs_core.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/tests/test_docs_editing.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/tests/test_docs_images.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/tests/test_docs_indices.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/tests/test_docs_layout.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/tests/test_docs_markdown_export.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/tests/test_docs_named_ranges.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/tests/test_docs_style.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/tests/test_docs_tables.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/tests/test_gen_tool_docs.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/tests/test_gmail.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/tests/test_lane_context_hook.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/tests/test_qa_gmail_fixtures.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/tests/test_response_limits.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/tests/test_server_json.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/tests/test_team_mcp_configs.py +0 -0
- {mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/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.dev236
|
|
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
|
{mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/design/sse-consent-off-event-loop.md
RENAMED
|
@@ -60,7 +60,7 @@ Patching mcp's transport instead was rejected: it means carrying a copy of `conn
|
|
|
60
60
|
| Stray callback request | | degrades in 0.08s (`MismatchingStateError`) |
|
|
61
61
|
| Missing-scope token, client POSTs `initialize` on the endpoint event, then SIGTERM | n/a (no endpoint before the raise) | before §5: 25 of 25 hang. After: 0 of 25, POST `202`; a real `sse_client` initializes and gets the missing-scopes message as the tool error |
|
|
62
62
|
|
|
63
|
-
An `Exception in ASGI application ... Expected ASGI message 'http.response.body'` traceback at shutdown
|
|
63
|
+
An `Exception in ASGI application ... Expected ASGI message 'http.response.body'` traceback at shutdown appeared on `develop` too, for any SSE stream open at SIGTERM, so it was unrelated to this change. #868 fixed it with `SingleResponseGuard` (`sse_shutdown.py`).
|
|
64
64
|
|
|
65
65
|
## Not changed
|
|
66
66
|
|
|
@@ -1026,7 +1026,7 @@ Start `uv run mcp-gee-sweet --transport sse` with `AUTH_METHOD=oauth`, `TOKEN_PA
|
|
|
1026
1026
|
- 5: the stderr file has `Starting without Google access: ... the server shut down before the browser consent completed`
|
|
1027
1027
|
- 5: `lsof -i :<port>` and `lsof -i :<cb>` are both empty afterward
|
|
1028
1028
|
|
|
1029
|
-
An `Exception in ASGI application ... Expected ASGI message 'http.response.body', but got 'http.response.start'` traceback during shutdown isn't a failure of this case
|
|
1029
|
+
An `Exception in ASGI application ... Expected ASGI message 'http.response.body', but got 'http.response.start'` traceback during shutdown isn't a failure of this case. Any SSE stream open at SIGTERM used to produce it, with or without a consent wait (pre-existing, seen on `develop` before #833). Fixed in #868, which TC-I47 covers.
|
|
1030
1030
|
|
|
1031
1031
|
**Cleanup:** if the server is still running, SIGKILL its process group and record the case as failed.
|
|
1032
1032
|
|
|
@@ -1128,3 +1128,33 @@ Live `mcp-gee-sweet-sky` server, OAuth, mcp 2.3.0. 1: `Error executing tool list
|
|
|
1128
1128
|
|
|
1129
1129
|
**Result (2026-10-04, PR #905 round 2 @ 42ab5e6, Sky) ✅ PASS**
|
|
1130
1130
|
Live `mcp-gee-sweet-sky` server, mcp 2.3.0: steps 1 and 2 return the same `HttpError 404 ... Requested entity was not found.` text as round 1. Extra check over a stdio subprocess with the same credentials (`DEBUG_LEVEL=INFO`): the server logs `WARNING mcp_gee_sweet.server Reporting HttpError to the client: ...` with the full traceback through `execute_in_thread`, and the access line is `"TOOL list_sheets" 500`.
|
|
1131
|
+
|
|
1132
|
+
---
|
|
1133
|
+
|
|
1134
|
+
### TC-I47: SIGTERM with an SSE stream open shuts down without an ASGI traceback (issue #868) ⚠️ local-filesystem
|
|
1135
|
+
|
|
1136
|
+
**Background:** on SIGTERM, sse_starlette cancels an open SSE stream without sending its closing chunk, and mcp's `/sse` endpoint then returns an empty `Response()` on the same connection. uvicorn rejected that second response with `Exception in ASGI application ... RuntimeError: Expected ASGI message 'http.response.body', but got 'http.response.start'` on every shutdown with a stream open, and the client saw a truncated chunked stream. `SingleResponseGuard` (`sse_shutdown.py`) now closes the cut-off stream and drops the late second response, but only once `AppStatus.should_exit` is set. Before shutdown it passes everything through, so uvicorn still reports a genuine double response, and after an ordinary client disconnect uvicorn no-ops mcp's late `Response()` itself. `main()` serves the guarded app for plain `--transport sse` as well as `--reload`, since `mcp.run(transport="sse")` builds its own unguarded app. It launches it the way mcp's `run_sse_async()` does (`anyio.run(uvicorn.Server(config).serve)`), not with `uvicorn.run()`, which would pick uvloop when installed.
|
|
1137
|
+
|
|
1138
|
+
**Setup:** none beyond a checkout of this branch. No Google credentials are needed: the case only needs an idle, fully started SSE connection.
|
|
1139
|
+
|
|
1140
|
+
**Action**
|
|
1141
|
+
Start `uv run mcp-gee-sweet --transport sse` with `HOST=127.0.0.1`, `PORT=<free port>`, `AUTH_METHOD=oauth`, `CREDENTIALS_PATH=<nonexistent>`, `TOKEN_PATH=<nonexistent>`, `DEBUG_LEVEL=DEBUG`, `PYTHONUNBUFFERED=1`, stdout and stderr to one file.
|
|
1142
|
+
1. Open `GET http://127.0.0.1:<port>/sse` (e.g. Python `urllib.request.urlopen`) and read the first line (`event: endpoint`). Wait 3 seconds
|
|
1143
|
+
2. Send the server process `SIGTERM` (not SIGKILL) and time its exit. Then read the rest of the SSE response to EOF
|
|
1144
|
+
3. Repeat steps 1–2 two more times against a fresh server, then once more with `--reload` added to the command line
|
|
1145
|
+
4. Start a fresh server the same way; with the `mcp` SDK's `sse_client` + `ClientSession`, `initialize` and `list_tools`, close the session, then SIGTERM the server
|
|
1146
|
+
|
|
1147
|
+
**Checks**
|
|
1148
|
+
- 2, 3: the log has no `Exception in ASGI application`, no `RuntimeError: Expected ASGI message`, and no `ASGI callable returned without completing response`. (The setup's nonexistent `CREDENTIALS_PATH` logs a `DEBUG ... Auth failure detail` traceback ending in `RuntimeError: '<path>' not found` at connection time; that's the expected degraded start, not a failure.) With `DEBUG_LEVEL=DEBUG` it has `DEBUG mcp_gee_sweet.sse_shutdown Dropping a second response on /sse during shutdown (#868)`
|
|
1149
|
+
- 2, 3: the server exits within a few seconds of SIGTERM, and `lsof -i :<port>` is empty afterwards
|
|
1150
|
+
- 2, 3: reading the SSE response to the end ends with a clean EOF, not an `IncompleteRead` (before this fix the chunked stream was never terminated)
|
|
1151
|
+
- 4: `initialize` and `list_tools` succeed (the guard doesn't touch a normal session), and the log has no traceback other than the expected `Auth failure detail` one
|
|
1152
|
+
- 4: the log has no `Dropping a second response` line at all. The session's stream closed before SIGTERM, so the guard has nothing to drop, and it acts only during shutdown (round 1 found it acting on an ordinary client disconnect). Don't check the line's order against uvicorn's `Shutting down` instead: in steps 2–3 the two are logged in either order during a real shutdown.
|
|
1153
|
+
|
|
1154
|
+
**Cleanup:** SIGKILL any server still running (and record the case as failed).
|
|
1155
|
+
|
|
1156
|
+
**Result (2026-10-05, PR #911 round 1 @ d6ba1ec, Sky) ✅ PASS**
|
|
1157
|
+
uvicorn 0.47.0, run through `uv run` (so the parent's exit code is uv's 143 after forwarding SIGTERM; `--reload` exits 0). Steps 2–3 (three plain runs plus one `--reload`): every log has the `Dropping a second response on /sse (#868)` line and none of the three error strings. The server exits 0.2–0.8s after SIGTERM, the SSE response reads to a clean EOF, and the port is free afterwards. Baseline: the same driver against an `origin/develop` export logs `Exception in ASGI application ... RuntimeError: Expected ASGI message 'http.response.body', but got 'http.response.start'`, and the client gets `IncompleteRead(64 bytes read)`. Step 4: `initialize` and `list_tools` (139 tools) succeed, with no ASGI error. But the log shows `Dropping a second response on /sse (#868)` right after the client closed its session, about 1s *before* SIGTERM. So the guard also handles mcp's late `Response()` after an ordinary client disconnect, not only at shutdown (see the round-1 PR comment). Side check: `--transport streamable-http` with an open `GET /mcp` stream still logs `ASGI callable returned without completing response.` on SIGTERM, and the client gets `IncompleteRead(0 bytes read)`. That transport isn't in this PR's scope; filed as #917.
|
|
1158
|
+
|
|
1159
|
+
**Result (2026-10-05, PR #911 round 2 @ 1f138a5, Sky) ✅ PASS**
|
|
1160
|
+
Same drivers as round 1. Steps 2–3 (three plain runs plus one `--reload`): each log has `Dropping a second response on /sse during shutdown (#868)` and none of the three error strings. The server exits 0.2–0.6s after SIGTERM, every SSE response reads to a clean EOF, and the ports are free. The drop line and uvicorn's `Shutting down` were logged in either order across these runs, so I changed step 4's check from an ordering check to an absence check. Step 4: `initialize` and `list_tools` (139 tools) succeed, and the log has no `Dropping` line and no ASGI error. So after an ordinary client disconnect, the late `Response()` now reaches uvicorn, which ignores it without error. Plain `--transport sse` now launches via `anyio.run(uvicorn.Server(config).serve)` and behaves the same as `--reload`.
|
|
@@ -330,7 +330,7 @@ Requires `gmail/v1` client and `https://www.googleapis.com/auth/gmail.modify` sc
|
|
|
330
330
|
- [ ] TC-DOC80 can no longer trip the response-size cap post-#519 — large-doc fixture is under the raised default; needs a per-case low-cap override — Aziz ([#678](https://github.com/khuisman/mcp-gee-sweet/issues/678))
|
|
331
331
|
- [ ] Define the correctness rubric and own execution for the composite-vs-primitive efficiency benchmark — Aziz ([#584](https://github.com/khuisman/mcp-gee-sweet/issues/584))
|
|
332
332
|
- [ ] QA `.env` has no single source of truth — `setup_team.sh` only copies `.env` into a worktree when absent, so fixture-ID changes never propagate (had to hand-sync 9 copies for #305); also `run.md` vs `setup.md` disagree on `docs/qa/.env` vs repo-root `.env`. Surfaced by PR #677, relates to [#648](https://github.com/khuisman/mcp-gee-sweet/issues/648) ([#679](https://github.com/khuisman/mcp-gee-sweet/issues/679))
|
|
333
|
-
- [
|
|
333
|
+
- [x] `list_files`/`get_file_metadata` docstrings cite a stale `upload_local_file` `modifiedTime` caveat (fixed at the root by PR #472) and carry a broken self-referential contrast — from Bob's `docs/tools.md` docstring sweep; Dev/QA track, batches with #682/#683 ([#681](https://github.com/khuisman/mcp-gee-sweet/issues/681)) (PR 898)
|
|
334
334
|
- [ ] `create_doc` family docstrings name only the ~25MP inline-image limit; the ~50MB byte-size limit (#562) is equally enforced, and 3 deferring docstrings inherit the gap — from Bob's `docs/tools.md` docstring sweep ([#682](https://github.com/khuisman/mcp-gee-sweet/issues/682))
|
|
335
335
|
- [ ] `qa(docs)`: no comprehensive fixture exercising every Docs formatting construct in one pass — Aziz, triaged out of `backlog` 2026-09-11 ([#477](https://github.com/khuisman/mcp-gee-sweet/issues/477))
|
|
336
336
|
- [ ] Fact-check and word competitive/benchmark claims once real benchmark numbers exist — routed to Amy, triaged out of `backlog` 2026-09-11 ([#585](https://github.com/khuisman/mcp-gee-sweet/issues/585))
|
|
@@ -92,6 +92,7 @@ from .auth import ( # noqa: E402
|
|
|
92
92
|
set_raise_auth_failures,
|
|
93
93
|
spreadsheet_lifespan,
|
|
94
94
|
)
|
|
95
|
+
from .sse_shutdown import SingleResponseGuard # noqa: E402
|
|
95
96
|
|
|
96
97
|
|
|
97
98
|
def _parse_enabled_tools() -> set | None:
|
|
@@ -124,8 +125,10 @@ mcp = MCPServer(
|
|
|
124
125
|
)
|
|
125
126
|
|
|
126
127
|
# mcp v2 moved host/port from the constructor to call-time kwargs on
|
|
127
|
-
# sse_app()/run_sse_async()/run() itself (confirmed live against mcp==2.0.0, issue #175)
|
|
128
|
-
|
|
128
|
+
# sse_app()/run_sse_async()/run() itself (confirmed live against mcp==2.0.0, issue #175).
|
|
129
|
+
# SingleResponseGuard keeps a SIGTERM with an open SSE stream from logging an ASGI
|
|
130
|
+
# traceback (#868); main() serves this app for every SSE launch so the guard applies.
|
|
131
|
+
app = SingleResponseGuard(mcp.sse_app(host=_resolved_host))
|
|
129
132
|
|
|
130
133
|
|
|
131
134
|
_tool_access_logger = logging.getLogger("mcp_gee_sweet.access")
|
|
@@ -597,6 +600,18 @@ def main():
|
|
|
597
600
|
# the connection without Google access instead (PR #867).
|
|
598
601
|
set_raise_auth_failures(True)
|
|
599
602
|
mcp.run(transport=transport)
|
|
603
|
+
elif transport == "sse":
|
|
604
|
+
# mcp.run(transport="sse") would build a second, unguarded sse_app, so serve
|
|
605
|
+
# the guarded module-level app with run_sse_async()'s own launch (#868):
|
|
606
|
+
# anyio.run on the plain asyncio loop, which #833's auth handoff was built
|
|
607
|
+
# and QA'd on. uvicorn.run() would pick uvloop whenever it's installed.
|
|
608
|
+
import anyio
|
|
609
|
+
import uvicorn
|
|
610
|
+
|
|
611
|
+
config = uvicorn.Config(
|
|
612
|
+
app, host=_resolved_host, port=_resolved_port, log_level=mcp.settings.log_level.lower()
|
|
613
|
+
)
|
|
614
|
+
anyio.run(uvicorn.Server(config).serve)
|
|
600
615
|
else:
|
|
601
616
|
# mcp v2 moved host/port from the constructor to call-time kwargs (see the
|
|
602
617
|
# mcp.sse_app() call above) — stdio's own overload doesn't accept them.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""ASGI guard for the SSE stream a server shutdown cuts off (#868).
|
|
2
|
+
|
|
3
|
+
On SIGTERM, sse_starlette's ``EventSourceResponse`` sees ``AppStatus.should_exit``
|
|
4
|
+
and cancels its own task group, so its closing ``http.response.body``
|
|
5
|
+
(``more_body=False``) is never sent: the response is left started but incomplete.
|
|
6
|
+
mcp's ``sse_app`` endpoint then returns an empty ``Response()``, which Starlette
|
|
7
|
+
sends on the same connection, and uvicorn rejects that second
|
|
8
|
+
``http.response.start`` with "Exception in ASGI application". A client disconnect
|
|
9
|
+
doesn't hit this, because uvicorn turns every send after a disconnect into a no-op.
|
|
10
|
+
|
|
11
|
+
``SingleResponseGuard`` drops that late second response, but only once
|
|
12
|
+
``AppStatus.should_exit`` is set. If the first one is still open, it sends the
|
|
13
|
+
missing closing body first, so the client gets a properly terminated stream
|
|
14
|
+
instead of a truncated one. Before shutdown every message passes through
|
|
15
|
+
untouched, so uvicorn still reports a genuine double response (PR #911 QA round 1).
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import logging
|
|
19
|
+
|
|
20
|
+
from sse_starlette.sse import AppStatus
|
|
21
|
+
from starlette.types import ASGIApp, Message, Receive, Scope, Send
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class SingleResponseGuard:
|
|
27
|
+
"""During shutdown, drop a second ``http.response.start`` on a request that already started one."""
|
|
28
|
+
|
|
29
|
+
def __init__(self, app: ASGIApp) -> None:
|
|
30
|
+
self.app = app
|
|
31
|
+
|
|
32
|
+
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
|
|
33
|
+
if scope["type"] != "http":
|
|
34
|
+
await self.app(scope, receive, send)
|
|
35
|
+
return
|
|
36
|
+
|
|
37
|
+
started = False
|
|
38
|
+
complete = False
|
|
39
|
+
discarding = False
|
|
40
|
+
|
|
41
|
+
async def guarded_send(message: Message) -> None:
|
|
42
|
+
nonlocal started, complete, discarding
|
|
43
|
+
if discarding:
|
|
44
|
+
return
|
|
45
|
+
if message["type"] == "http.response.start":
|
|
46
|
+
if started and AppStatus.should_exit:
|
|
47
|
+
discarding = True
|
|
48
|
+
logger.debug(
|
|
49
|
+
"Dropping a second response on %s during shutdown (#868)", scope.get("path")
|
|
50
|
+
)
|
|
51
|
+
if not complete:
|
|
52
|
+
complete = True
|
|
53
|
+
await send({"type": "http.response.body", "body": b"", "more_body": False})
|
|
54
|
+
return
|
|
55
|
+
started = True
|
|
56
|
+
elif message["type"] == "http.response.body" and not message.get("more_body", False):
|
|
57
|
+
complete = True
|
|
58
|
+
await send(message)
|
|
59
|
+
|
|
60
|
+
await self.app(scope, receive, guarded_send)
|
{mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/src/mcp_gee_sweet/tools/drive/files.py
RENAMED
|
@@ -560,10 +560,9 @@ def register(tool):
|
|
|
560
560
|
starred status, and md5_checksum. md5_checksum is only present for
|
|
561
561
|
binary files — Google Workspace files (Docs, Sheets, Slides, etc.) have
|
|
562
562
|
no fixed byte content and the Drive API omits the field for them, so
|
|
563
|
-
it's None for those. Use it to detect real content drift (
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
modifiedTime alone.
|
|
563
|
+
it's None for those. Use it to detect real content drift (for example,
|
|
564
|
+
a local overwrite that preserves mtime) instead of inferring change
|
|
565
|
+
from modifiedTime alone.
|
|
567
566
|
Results are cached; call refresh_cache(folder_id=folder_id) to invalidate,
|
|
568
567
|
or refresh_cache() to clear all caches.
|
|
569
568
|
"""
|
|
@@ -774,9 +773,8 @@ def register(tool):
|
|
|
774
773
|
md5_checksum (like size) is only present for binary files — Google
|
|
775
774
|
Workspace files have no fixed byte content and the Drive API omits it
|
|
776
775
|
for them. Use it to detect real content drift instead of inferring
|
|
777
|
-
change from modifiedTime alone (
|
|
778
|
-
|
|
779
|
-
sync_folder does). starred reflects star_file/unstar_file's own effect
|
|
776
|
+
change from modifiedTime alone (for example, a local overwrite that
|
|
777
|
+
preserves mtime). starred reflects star_file/unstar_file's own effect
|
|
780
778
|
(#388) — previously there was no way to read a file's starred state
|
|
781
779
|
independently of calling one of those two mutating tools.
|
|
782
780
|
"""
|
|
@@ -8,7 +8,9 @@ import sys
|
|
|
8
8
|
from types import SimpleNamespace
|
|
9
9
|
from unittest.mock import MagicMock
|
|
10
10
|
|
|
11
|
+
import anyio
|
|
11
12
|
import pytest
|
|
13
|
+
import uvicorn
|
|
12
14
|
from google.auth.exceptions import RefreshError
|
|
13
15
|
from googleapiclient import errors as googleapiclient_errors
|
|
14
16
|
from googleapiclient.errors import HttpError
|
|
@@ -30,6 +32,7 @@ from mcp_gee_sweet.server import (
|
|
|
30
32
|
mcp,
|
|
31
33
|
tool,
|
|
32
34
|
)
|
|
35
|
+
from mcp_gee_sweet.sse_shutdown import SingleResponseGuard
|
|
33
36
|
|
|
34
37
|
|
|
35
38
|
def _http_error(status: int, message: str) -> HttpError:
|
|
@@ -727,11 +730,33 @@ class TestMainAuthAndTransport:
|
|
|
727
730
|
|
|
728
731
|
def test_sse_keeps_interactive_consent(self, monkeypatch):
|
|
729
732
|
monkeypatch.setattr(sys, "argv", ["mcp-gee-sweet", "--transport", "sse"])
|
|
730
|
-
monkeypatch.setattr(
|
|
733
|
+
monkeypatch.setattr(anyio, "run", MagicMock())
|
|
731
734
|
main()
|
|
732
735
|
assert auth_module._interactive_consent is True
|
|
733
736
|
assert auth_module._raise_auth_failures is False
|
|
734
737
|
|
|
738
|
+
def test_sse_serves_the_guarded_app_like_run_sse_async(self, monkeypatch):
|
|
739
|
+
# #868: mcp.run(transport="sse") builds its own app without SingleResponseGuard,
|
|
740
|
+
# so main() serves the guarded app with run_sse_async()'s own launch:
|
|
741
|
+
# anyio.run(uvicorn.Server(config).serve), not uvicorn.run (which picks uvloop).
|
|
742
|
+
monkeypatch.setattr(sys, "argv", ["mcp-gee-sweet", "--transport", "sse"])
|
|
743
|
+
monkeypatch.setattr(server, "_resolved_host", "127.0.0.9")
|
|
744
|
+
monkeypatch.setattr(server, "_resolved_port", 47123)
|
|
745
|
+
monkeypatch.setattr(mcp.settings, "log_level", "DEBUG")
|
|
746
|
+
mcp_run, uvicorn_run, anyio_run = MagicMock(), MagicMock(), MagicMock()
|
|
747
|
+
monkeypatch.setattr(mcp, "run", mcp_run)
|
|
748
|
+
monkeypatch.setattr(uvicorn, "run", uvicorn_run)
|
|
749
|
+
monkeypatch.setattr(anyio, "run", anyio_run)
|
|
750
|
+
main()
|
|
751
|
+
mcp_run.assert_not_called()
|
|
752
|
+
uvicorn_run.assert_not_called()
|
|
753
|
+
(serve,) = anyio_run.call_args.args
|
|
754
|
+
assert serve.__func__ is uvicorn.Server.serve
|
|
755
|
+
config = serve.__self__.config
|
|
756
|
+
assert isinstance(server.app, SingleResponseGuard)
|
|
757
|
+
assert config.app is server.app
|
|
758
|
+
assert (config.host, config.port, config.log_level) == ("127.0.0.9", 47123, "debug")
|
|
759
|
+
|
|
735
760
|
|
|
736
761
|
class TestMainLogsVersion:
|
|
737
762
|
"""Issue #356: main() logs the running package version at startup so a
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"""Tests for SingleResponseGuard: a SIGTERM with an open SSE stream (#868)."""
|
|
2
|
+
|
|
3
|
+
import threading
|
|
4
|
+
|
|
5
|
+
import anyio
|
|
6
|
+
import pytest
|
|
7
|
+
from mcp.server.mcpserver import MCPServer
|
|
8
|
+
from sse_starlette import sse as sse_starlette_sse
|
|
9
|
+
from sse_starlette.sse import AppStatus
|
|
10
|
+
|
|
11
|
+
from mcp_gee_sweet.sse_shutdown import SingleResponseGuard
|
|
12
|
+
|
|
13
|
+
_HTTP_SCOPE = {"type": "http", "method": "GET", "path": "/sse"}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class _StrictSend:
|
|
17
|
+
"""Records sends, enforcing the ordering checks of uvicorn's h11 ``send``."""
|
|
18
|
+
|
|
19
|
+
def __init__(self):
|
|
20
|
+
self.messages = []
|
|
21
|
+
self.started = False
|
|
22
|
+
self.complete = False
|
|
23
|
+
|
|
24
|
+
async def __call__(self, message):
|
|
25
|
+
if not self.started:
|
|
26
|
+
if message["type"] != "http.response.start":
|
|
27
|
+
raise RuntimeError(
|
|
28
|
+
f"Expected ASGI message 'http.response.start', but got '{message['type']}'."
|
|
29
|
+
)
|
|
30
|
+
self.started = True
|
|
31
|
+
elif not self.complete:
|
|
32
|
+
if message["type"] != "http.response.body":
|
|
33
|
+
raise RuntimeError(
|
|
34
|
+
f"Expected ASGI message 'http.response.body', but got '{message['type']}'."
|
|
35
|
+
)
|
|
36
|
+
if not message.get("more_body", False):
|
|
37
|
+
self.complete = True
|
|
38
|
+
else:
|
|
39
|
+
raise RuntimeError(
|
|
40
|
+
f"Unexpected ASGI message '{message['type']}' sent, after response already completed."
|
|
41
|
+
)
|
|
42
|
+
self.messages.append(message)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
async def _never_receive():
|
|
46
|
+
await anyio.sleep_forever()
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _app_sending(*messages):
|
|
50
|
+
async def app(scope, receive, send):
|
|
51
|
+
for message in messages:
|
|
52
|
+
await send(message)
|
|
53
|
+
|
|
54
|
+
return app
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
_START = {"type": "http.response.start", "status": 200, "headers": []}
|
|
58
|
+
_CHUNK = {"type": "http.response.body", "body": b"data: x\n\n", "more_body": True}
|
|
59
|
+
_END = {"type": "http.response.body", "body": b"", "more_body": False}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class TestSingleResponseGuard:
|
|
63
|
+
@pytest.fixture
|
|
64
|
+
def shutting_down(self, monkeypatch):
|
|
65
|
+
monkeypatch.setattr(AppStatus, "should_exit", True)
|
|
66
|
+
|
|
67
|
+
@pytest.fixture(autouse=True)
|
|
68
|
+
def _not_shutting_down_by_default(self, monkeypatch):
|
|
69
|
+
monkeypatch.setattr(AppStatus, "should_exit", False)
|
|
70
|
+
|
|
71
|
+
@pytest.mark.parametrize("should_exit", [False, True])
|
|
72
|
+
async def test_a_single_response_passes_through_untouched(self, monkeypatch, should_exit):
|
|
73
|
+
monkeypatch.setattr(AppStatus, "should_exit", should_exit)
|
|
74
|
+
send = _StrictSend()
|
|
75
|
+
await SingleResponseGuard(_app_sending(_START, _CHUNK, _END))(
|
|
76
|
+
_HTTP_SCOPE, _never_receive, send
|
|
77
|
+
)
|
|
78
|
+
assert send.messages == [_START, _CHUNK, _END]
|
|
79
|
+
|
|
80
|
+
@pytest.mark.parametrize(
|
|
81
|
+
"messages",
|
|
82
|
+
[(_START, _CHUNK, _START, _END), (_START, _END, _START, _END)],
|
|
83
|
+
ids=["open", "complete"],
|
|
84
|
+
)
|
|
85
|
+
async def test_before_shutdown_a_second_response_still_reaches_the_server(self, messages):
|
|
86
|
+
# PR #911 QA round 1: outside shutdown a double response is a real bug, so
|
|
87
|
+
# uvicorn must still see (and report) it, and an open stream isn't closed.
|
|
88
|
+
send = _StrictSend()
|
|
89
|
+
with pytest.raises(RuntimeError, match=r"'http\.response\.start'"):
|
|
90
|
+
await SingleResponseGuard(_app_sending(*messages))(_HTTP_SCOPE, _never_receive, send)
|
|
91
|
+
assert send.messages == list(messages[:2])
|
|
92
|
+
|
|
93
|
+
@pytest.mark.usefixtures("shutting_down")
|
|
94
|
+
async def test_a_second_response_on_an_open_stream_closes_it_and_is_dropped(self):
|
|
95
|
+
# The #868 shape: the stream is cut off without its closing body, then
|
|
96
|
+
# mcp's empty Response() starts a second response on the same connection.
|
|
97
|
+
send = _StrictSend()
|
|
98
|
+
late_response = ({"type": "http.response.start", "status": 200, "headers": []}, _END)
|
|
99
|
+
app = _app_sending(_START, _CHUNK, *late_response)
|
|
100
|
+
await SingleResponseGuard(app)(_HTTP_SCOPE, _never_receive, send)
|
|
101
|
+
assert send.messages == [_START, _CHUNK, _END]
|
|
102
|
+
assert send.complete
|
|
103
|
+
|
|
104
|
+
@pytest.mark.usefixtures("shutting_down")
|
|
105
|
+
async def test_a_second_response_after_a_complete_one_is_dropped(self):
|
|
106
|
+
send = _StrictSend()
|
|
107
|
+
app = _app_sending(_START, _END, _START, _END)
|
|
108
|
+
await SingleResponseGuard(app)(_HTTP_SCOPE, _never_receive, send)
|
|
109
|
+
assert send.messages == [_START, _END]
|
|
110
|
+
|
|
111
|
+
@pytest.mark.usefixtures("shutting_down")
|
|
112
|
+
async def test_other_misuse_still_reaches_the_server(self):
|
|
113
|
+
# Only a duplicate start is absorbed, even during shutdown; a body after
|
|
114
|
+
# completion still raises.
|
|
115
|
+
send = _StrictSend()
|
|
116
|
+
app = _app_sending(_START, _END, _CHUNK)
|
|
117
|
+
with pytest.raises(RuntimeError, match="after response already completed"):
|
|
118
|
+
await SingleResponseGuard(app)(_HTTP_SCOPE, _never_receive, send)
|
|
119
|
+
|
|
120
|
+
async def test_non_http_scopes_pass_straight_through(self):
|
|
121
|
+
seen = []
|
|
122
|
+
|
|
123
|
+
async def app(scope, receive, send):
|
|
124
|
+
seen.append((scope, send))
|
|
125
|
+
|
|
126
|
+
def send(message): # never wrapped, so never awaited
|
|
127
|
+
raise AssertionError
|
|
128
|
+
|
|
129
|
+
scope = {"type": "lifespan"}
|
|
130
|
+
await SingleResponseGuard(app)(scope, _never_receive, send)
|
|
131
|
+
assert seen == [(scope, send)]
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class TestShutdownWithOpenSseStream:
|
|
135
|
+
"""mcp's real sse_app, shut down the way uvicorn's SIGTERM handler does."""
|
|
136
|
+
|
|
137
|
+
@pytest.fixture(autouse=True)
|
|
138
|
+
def _fresh_shutdown_state(self, monkeypatch):
|
|
139
|
+
monkeypatch.setattr(AppStatus, "should_exit", False)
|
|
140
|
+
# sse_starlette's shutdown watcher is per thread; don't inherit another test's.
|
|
141
|
+
monkeypatch.setattr(sse_starlette_sse, "_thread_state", threading.local())
|
|
142
|
+
|
|
143
|
+
@staticmethod
|
|
144
|
+
async def _serve_then_shut_down(app):
|
|
145
|
+
send = _StrictSend()
|
|
146
|
+
scope = {
|
|
147
|
+
"type": "http",
|
|
148
|
+
"asgi": {"version": "3.0"},
|
|
149
|
+
"http_version": "1.1",
|
|
150
|
+
"method": "GET",
|
|
151
|
+
"scheme": "http",
|
|
152
|
+
"path": "/sse",
|
|
153
|
+
"raw_path": b"/sse",
|
|
154
|
+
"root_path": "",
|
|
155
|
+
"query_string": b"",
|
|
156
|
+
"headers": [(b"host", b"127.0.0.1:8000"), (b"accept", b"text/event-stream")],
|
|
157
|
+
"client": ("127.0.0.1", 50000),
|
|
158
|
+
"server": ("127.0.0.1", 8000),
|
|
159
|
+
}
|
|
160
|
+
errors = []
|
|
161
|
+
|
|
162
|
+
async def serve():
|
|
163
|
+
try:
|
|
164
|
+
await app(scope, _never_receive, send)
|
|
165
|
+
except Exception as exc: # what uvicorn logs as "Exception in ASGI application"
|
|
166
|
+
errors.append(exc)
|
|
167
|
+
|
|
168
|
+
with anyio.fail_after(10):
|
|
169
|
+
async with anyio.create_task_group() as tg:
|
|
170
|
+
tg.start_soon(serve)
|
|
171
|
+
while not send.messages: # the stream is open once its endpoint event is out
|
|
172
|
+
await anyio.sleep(0.01)
|
|
173
|
+
AppStatus.should_exit = True
|
|
174
|
+
return send, errors
|
|
175
|
+
|
|
176
|
+
async def test_unguarded_app_reproduces_the_traceback(self):
|
|
177
|
+
# Pins the premise: without the guard, mcp's sse_app hits uvicorn's check.
|
|
178
|
+
_, errors = await self._serve_then_shut_down(MCPServer("t").sse_app(host="0.0.0.0"))
|
|
179
|
+
assert len(errors) == 1
|
|
180
|
+
assert "but got 'http.response.start'" in str(errors[0])
|
|
181
|
+
|
|
182
|
+
async def test_guarded_app_shuts_down_cleanly(self):
|
|
183
|
+
app = SingleResponseGuard(MCPServer("t").sse_app(host="0.0.0.0"))
|
|
184
|
+
send, errors = await self._serve_then_shut_down(app)
|
|
185
|
+
assert errors == []
|
|
186
|
+
starts = [m for m in send.messages if m["type"] == "http.response.start"]
|
|
187
|
+
assert len(starts) == 1
|
|
188
|
+
assert send.messages[-1] == _END
|
|
189
|
+
assert send.complete
|
{mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/.claude/commands/cleanup-worktrees.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/.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.dev233 → mcp_gee_sweet-0.9.2.dev236}/.claude/commands/roadmap-status.md
RENAMED
|
File without changes
|
{mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/.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.dev233 → mcp_gee_sweet-0.9.2.dev236}/.github/ISSUE_TEMPLATE/bug_report.md
RENAMED
|
File without changes
|
{mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/.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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/decisions/decision-chart-theming.md
RENAMED
|
File without changes
|
|
File without changes
|
{mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/decisions/decision-composite-tools.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/decisions/decision-dev-team.md
RENAMED
|
File without changes
|
{mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/decisions/decision-docs-formatting.md
RENAMED
|
File without changes
|
|
File without changes
|
{mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/decisions/decision-gmail-domain.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/decisions/decision-prompt-qa-role.md
RENAMED
|
File without changes
|
{mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/decisions/decision-publishing.md
RENAMED
|
File without changes
|
|
File without changes
|
{mcp_gee_sweet-0.9.2.dev233 → mcp_gee_sweet-0.9.2.dev236}/docs/decisions/decision-repositioning.md
RENAMED
|
File without changes
|