mcp-gee-sweet 0.7.0.dev67__tar.gz → 0.7.0.dev68__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/PKG-INFO +1 -1
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/configuration.md +2 -2
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/tests/calendar.md +138 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/tools.md +4 -1
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/scripts/gen_tool_docs.py +3 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/tools/calendar.py +134 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/tests/test_calendar.py +186 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/.claude/commands/prep-for-pr.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/.dockerignore +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/.github/dependabot.yml +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/.github/workflows/ci.yml +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/.github/workflows/docs.yml +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/.github/workflows/publish-dev.yml +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/.github/workflows/release.yml +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/.gitignore +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/.pre-commit-config.yaml +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/.python-version +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/CHANGELOG.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/CLAUDE.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/CODE_OF_CONDUCT.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/CONTRIBUTING.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/Dockerfile +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/LICENSE +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/Makefile +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/README.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/SECURITY.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docker-compose.yml +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/auth.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/client-setup.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/decisions/decision-chart-theming.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/decisions/decision-composite-tools.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/decisions/decision-docs-formatting.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/decisions/decision-fork.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/decisions/decision-grid-data-size-cap.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/decisions/decision-publishing.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/decisions/decision-response-size-cap-generalization.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/decisions/decision-testing.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/decisions/index.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/design/docs-ast-pipeline.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/design/effectiveformat-spike.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/design/index.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/design/markdown-support.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/design/phase3-theme-system.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/design.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/index.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/known-limitations.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/notes-read.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/README.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/fixtures/tc-d195-create-doc.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/fixtures/tc-d196-create-doc.html +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/fixtures/tc-d213-dollar-escape.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/fixtures/tc-d226-heading-table.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/fixtures.template.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/operations.yaml +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/playwright_oauth.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/results/.gitkeep +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/results/2026-06-02.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/results/2026-06-14.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/results/2026-06-17.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/results/2026-06-18.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/results/2026-06-21-drive-discovery.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/results/2026-06-21-sheets-delete-clear.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/results/2026-06-28.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/results/2026-07-04-docs.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/results/2026-07-04-drive-smoke.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/results/2026-07-04-infra.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/results/2026-07-04-sheets_read.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/results/2026-07-04.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/retro-v0.8.0.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/run.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/runs/README.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/runs/v0.8.0.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/runs/v0.8.1.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/setup.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/tests/docs.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/tests/drive.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/tests/infra.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/tests/sheets_charts.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/tests/sheets_mgmt.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/tests/sheets_read.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/tests/sheets_write.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/qa-checklist.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/roadmap.md +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/mkdocs.yml +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/pyproject.toml +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/scripts/oauth_setup.py +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/.env.template +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/__init__.py +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/auth.py +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/cache.py +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/server.py +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/tools/__init__.py +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/tools/cache.py +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/tools/docs/__init__.py +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/tools/docs/ast.py +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/tools/docs/content.py +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/tools/docs/emitter.py +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/tools/docs/html_parser.py +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/tools/docs/layout.py +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/tools/docs/style.py +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/tools/docs/tables.py +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/tools/drive/__init__.py +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/tools/drive/activity.py +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/tools/drive/files.py +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/tools/drive/sharing.py +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/tools/drive/transfer.py +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/tools/response_limits.py +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/tools/sheets/__init__.py +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/tools/sheets/data.py +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/tools/sheets/helpers.py +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/tools/sheets/structure.py +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/tests/__init__.py +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/tests/drive/__init__.py +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/tests/drive/test_activity.py +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/tests/drive/test_files.py +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/tests/drive/test_sharing.py +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/tests/drive/test_transfer.py +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/tests/sheets/__init__.py +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/tests/sheets/test_data.py +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/tests/sheets/test_helpers.py +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/tests/sheets/test_structure.py +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/tests/test_auth.py +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/tests/test_cache.py +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/tests/test_docs_content.py +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/tests/test_docs_core.py +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/tests/test_docs_layout.py +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/tests/test_docs_style.py +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/tests/test_docs_tables.py +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/tests/test_gen_tool_docs.py +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/tests/test_response_limits.py +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/tests/test_server.py +0 -0
- {mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mcp-gee-sweet
|
|
3
|
-
Version: 0.7.0.
|
|
3
|
+
Version: 0.7.0.dev68
|
|
4
4
|
Summary: MCP server for Google Workspace — Sheets, Drive, Docs, and Calendar. 60+ tools for AI clients.
|
|
5
5
|
Project-URL: Homepage, https://github.com/khuisman/mcp-gee-sweet
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/khuisman/mcp-gee-sweet/issues
|
|
@@ -69,7 +69,7 @@ Access logs are emitted at `INFO` level — they appear when `DEBUG_LEVEL` is `D
|
|
|
69
69
|
|
|
70
70
|
<!-- Auto-generated by `scripts/gen_tool_docs.py` — do not edit by hand, your changes will be overwritten on the next commit. -->
|
|
71
71
|
|
|
72
|
-
By default all
|
|
72
|
+
By default all 87 tools are registered. Use `ENABLED_TOOLS` (or `--include-tools` on the CLI) to restrict the server to exactly the tools you need. This reduces the AI's context window cost — each registered tool is a name the model must reason about on every call.
|
|
73
73
|
|
|
74
74
|
```bash
|
|
75
75
|
# Environment variable
|
|
@@ -100,7 +100,7 @@ create_doc,get_doc_content,get_doc_structure,write_doc_content,insert_doc_text,i
|
|
|
100
100
|
|
|
101
101
|
**Calendar only:**
|
|
102
102
|
```
|
|
103
|
-
list_calendars,get_calendar,list_events,get_event,create_event,update_event,delete_event,find_free_slots
|
|
103
|
+
list_calendars,get_calendar,create_calendar,update_calendar,delete_calendar,list_events,get_event,create_event,update_event,delete_event,find_free_slots
|
|
104
104
|
```
|
|
105
105
|
|
|
106
106
|
See [Tools](tools.md) for the full list of tool names.
|
|
@@ -105,6 +105,144 @@ Fixtures: see [`docs/qa/setup.md`](../setup.md). Substitute your `{CALENDAR_ID}`
|
|
|
105
105
|
|
|
106
106
|
---
|
|
107
107
|
|
|
108
|
+
## `create_calendar`
|
|
109
|
+
|
|
110
|
+
### TC-CAL44: Create a secondary calendar ⚠️ destructive
|
|
111
|
+
|
|
112
|
+
**Prompt**
|
|
113
|
+
> "Create a new calendar called 'mcp-gee-sweet-qa-lifecycle-test' with description 'QA scratch calendar' and timezone 'America/Los_Angeles'"
|
|
114
|
+
|
|
115
|
+
**Checks**
|
|
116
|
+
- Returns `id`, `summary`, `description`, `time_zone`
|
|
117
|
+
- `summary` is `mcp-gee-sweet-qa-lifecycle-test`
|
|
118
|
+
- `time_zone` is `America/Los_Angeles`
|
|
119
|
+
- The new calendar appears in a follow-up `list_calendars` call (cache invalidated)
|
|
120
|
+
|
|
121
|
+
**Result (2026-07-05) ✅** — Created via OAuth; response had `summary`, `description`, and `time_zone: "America/Los_Angeles"` exactly as requested. Confirmed present in a follow-up `list_calendars` call.
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
### TC-CAL45: Create with only summary
|
|
126
|
+
|
|
127
|
+
**Prompt**
|
|
128
|
+
> "Create a new calendar called 'mcp-gee-sweet-qa-minimal'"
|
|
129
|
+
|
|
130
|
+
**Checks**
|
|
131
|
+
- Succeeds with only `summary` provided
|
|
132
|
+
- `description` is `null`/absent
|
|
133
|
+
- `time_zone` reflects the account default
|
|
134
|
+
|
|
135
|
+
**Result (2026-07-05) ✅** — Created with only `summary` provided. `description: null`. `time_zone: "UTC"` (account default rather than the creator's local timezone — noted for documentation purposes). Confirmed present in a follow-up `list_calendars` call.
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## `update_calendar`
|
|
140
|
+
|
|
141
|
+
### TC-CAL46: Rename and change timezone ⚠️ destructive
|
|
142
|
+
|
|
143
|
+
**Setup:** use the calendar created in TC-CAL44
|
|
144
|
+
|
|
145
|
+
**Prompt**
|
|
146
|
+
> "Update calendar 'mcp-gee-sweet-qa-lifecycle-test' — rename it to 'mcp-gee-sweet-qa-renamed' and set its timezone to 'America/New_York'"
|
|
147
|
+
|
|
148
|
+
**Checks**
|
|
149
|
+
- `summary` is `mcp-gee-sweet-qa-renamed`
|
|
150
|
+
- `time_zone` is `America/New_York`
|
|
151
|
+
- `description` unchanged from TC-CAL44
|
|
152
|
+
- `get_calendar` on the same ID reflects the new values
|
|
153
|
+
|
|
154
|
+
**Result (2026-07-05) ✅** — Patched with `summary: "mcp-gee-sweet-qa-renamed"` and `timezone: "America/New_York"`. Response reflected both changes; `description` unchanged from TC-CAL44 (`"QA scratch calendar"`).
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
### TC-CAL47: Change color only ⚠️ destructive
|
|
159
|
+
|
|
160
|
+
**Setup:** use the calendar from TC-CAL44/46
|
|
161
|
+
|
|
162
|
+
**Prompt**
|
|
163
|
+
> "Change the color of calendar 'mcp-gee-sweet-qa-renamed' to color ID '5'"
|
|
164
|
+
|
|
165
|
+
**Checks**
|
|
166
|
+
- `color_id` in the response is `5`
|
|
167
|
+
- `summary`/`description`/`time_zone` unchanged (verify with `get_calendar`)
|
|
168
|
+
- Confirms the color patch goes through `calendarList().patch()`, not `calendars().patch()`
|
|
169
|
+
|
|
170
|
+
**Result (2026-07-05) ✅** — Patched `color_id="5"` only (no summary/description/timezone). Response had `color_id: "5"` with `summary`, `description`, `time_zone` all unchanged from TC-CAL46 — confirms the tool fell back to `calendars().get()` for the base fields and routed color through `calendarList().patch()`.
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
### TC-CAL48: No fields provided
|
|
175
|
+
|
|
176
|
+
**Prompt**
|
|
177
|
+
> "Update calendar {CALENDAR_ID} with no changes — just fetch its current state through update_calendar"
|
|
178
|
+
|
|
179
|
+
**Checks**
|
|
180
|
+
- Returns current calendar metadata unchanged
|
|
181
|
+
- `color_id` is `null` (no color patch attempted)
|
|
182
|
+
- No API error
|
|
183
|
+
|
|
184
|
+
**Result (2026-07-05) ✅** — Called with `calendar_id="primary"` and no other fields. Returned the account's real primary calendar summary/timezone unchanged, `color_id: null`. Confirms the no-op path uses `calendars().get()` without issuing any patch.
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
### TC-CAL49: Non-existent calendar ID
|
|
189
|
+
|
|
190
|
+
**Prompt**
|
|
191
|
+
> "Update calendar 'totally-invalid-cal-id@example.com' — rename it to 'Nope'"
|
|
192
|
+
|
|
193
|
+
**Checks**
|
|
194
|
+
- Returns `{"error": "..."}` — not a top-level exception
|
|
195
|
+
|
|
196
|
+
**Result (2026-07-05) ✅** — Returned `{"error": "<HttpError 404 ... 'reason': 'notFound' ...>"}`. No exception raised.
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## `delete_calendar`
|
|
201
|
+
|
|
202
|
+
### TC-CAL50: Delete a calendar ⚠️ destructive
|
|
203
|
+
|
|
204
|
+
**Setup:** use the calendar created in TC-CAL44 (after TC-CAL46/47 updates)
|
|
205
|
+
|
|
206
|
+
**Prompt**
|
|
207
|
+
> "Delete the calendar 'mcp-gee-sweet-qa-renamed'"
|
|
208
|
+
|
|
209
|
+
**Checks**
|
|
210
|
+
- Returns `{"calendar_id": "...", "action": "deleted"}`
|
|
211
|
+
- Calendar no longer appears in a follow-up `list_calendars` call
|
|
212
|
+
- `get_calendar` on the deleted ID returns an error
|
|
213
|
+
|
|
214
|
+
**Result (2026-07-05) ✅** — Returned `{"calendar_id": "...", "action": "deleted"}`. Confirmed absent from a follow-up `list_calendars` call. `get_calendar` on the deleted ID returned a 404 `notFound` error.
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
### TC-CAL51: Delete the minimal test calendar ⚠️ destructive
|
|
219
|
+
|
|
220
|
+
**Setup:** use the calendar created in TC-CAL45
|
|
221
|
+
|
|
222
|
+
**Prompt**
|
|
223
|
+
> "Delete the calendar 'mcp-gee-sweet-qa-minimal'"
|
|
224
|
+
|
|
225
|
+
**Checks**
|
|
226
|
+
- Returns `{"calendar_id": "...", "action": "deleted"}`
|
|
227
|
+
- Cleans up the fixture so it doesn't linger in the account's calendar list
|
|
228
|
+
|
|
229
|
+
**Result (2026-07-05) ✅** — Returned `{"calendar_id": "...", "action": "deleted"}`. Confirmed removed from the account's `list_calendars` output alongside TC-CAL50's cleanup.
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
### TC-CAL52: Non-existent calendar ID
|
|
234
|
+
|
|
235
|
+
**Prompt**
|
|
236
|
+
> "Delete the calendar 'totally-invalid-cal-id@example.com'"
|
|
237
|
+
|
|
238
|
+
**Checks**
|
|
239
|
+
- Returns `{"error": "..."}` — not a top-level exception
|
|
240
|
+
- No side effects
|
|
241
|
+
|
|
242
|
+
**Result (2026-07-05) ✅** — Returned `{"error": "<HttpError 404 ... 'reason': 'notFound' ...>"}`. No side effects.
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
108
246
|
## `list_events`
|
|
109
247
|
|
|
110
248
|
### TC-CAL09: No time filters — upcoming events
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
<!-- Auto-generated by `scripts/gen_tool_docs.py` — do not edit by hand, your changes will be overwritten on the next commit. -->
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
87 tools. All tool names are lowercase with underscores — use these exact strings with `ENABLED_TOOLS` or `--include-tools`.
|
|
6
6
|
|
|
7
7
|
## Sheets — data
|
|
8
8
|
|
|
@@ -141,6 +141,9 @@ Requires the `drive.activity.readonly` scope; uses the Drive Activity API v2.
|
|
|
141
141
|
|---|---|---|
|
|
142
142
|
| `list_calendars` | List all calendars accessible to the authenticated user. | — |
|
|
143
143
|
| `get_calendar` | Fetch metadata for a single calendar. | `calendar_id` |
|
|
144
|
+
| `create_calendar` | Create a new secondary calendar owned by the authenticated user. | `summary`, `description?`, `timezone?` |
|
|
145
|
+
| `update_calendar` | Update fields on an existing calendar using patch semantics. Only the fields you provide are changed; omitted fields are left as-is. | `calendar_id`, `summary?`, `description?`, `timezone?`, `color_id?` |
|
|
146
|
+
| `delete_calendar` | Permanently delete a calendar. For calendars you own, this deletes the calendar entirely for all users it is shared with — use with caution. To only stop seeing a calendar in your own list, use remove_calendar_from_list instead. | `calendar_id` |
|
|
144
147
|
| `list_events` | List events in a calendar. | `calendar_id`, `time_min?`, `time_max?`, `query?`, `max_results?`, `expand_recurring?` |
|
|
145
148
|
| `get_event` | Fetch a single event by calendar ID and event ID. | `calendar_id`, `event_id` |
|
|
146
149
|
| `create_event` | Create a new event in a calendar. | `calendar_id`, `summary`, `start`, `end`, `description?`, `location?`, `attendees?`, `timezone?`, `recurrence?` |
|
|
@@ -73,6 +73,140 @@ def register(tool):
|
|
|
73
73
|
cache.store(calendar_id, result)
|
|
74
74
|
return result
|
|
75
75
|
|
|
76
|
+
@tool(annotations=ToolAnnotations(title="Create Calendar", destructiveHint=True))
|
|
77
|
+
def create_calendar(
|
|
78
|
+
summary: str,
|
|
79
|
+
description: str | None = None,
|
|
80
|
+
timezone: str | None = None,
|
|
81
|
+
ctx: Context = None,
|
|
82
|
+
) -> dict[str, Any]:
|
|
83
|
+
"""
|
|
84
|
+
Create a new secondary calendar owned by the authenticated user.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
summary: Calendar title.
|
|
88
|
+
description: Optional calendar description.
|
|
89
|
+
timezone: IANA timezone name (e.g. 'America/Los_Angeles'). Defaults to
|
|
90
|
+
the account's timezone if omitted.
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
Created calendar: id, summary, description, time_zone.
|
|
94
|
+
"""
|
|
95
|
+
lc = ctx.request_context.lifespan_context
|
|
96
|
+
|
|
97
|
+
body: dict[str, Any] = {"summary": summary}
|
|
98
|
+
if description:
|
|
99
|
+
body["description"] = description
|
|
100
|
+
if timezone:
|
|
101
|
+
body["timeZone"] = timezone
|
|
102
|
+
|
|
103
|
+
try:
|
|
104
|
+
c = lc.calendar_service.calendars().insert(body=body).execute()
|
|
105
|
+
except Exception as e:
|
|
106
|
+
return {"error": str(e)}
|
|
107
|
+
|
|
108
|
+
lc.calendar_cache.mark_dirty(c["id"])
|
|
109
|
+
logger.debug("Created calendar %s", c["id"])
|
|
110
|
+
return {
|
|
111
|
+
"id": c["id"],
|
|
112
|
+
"summary": c.get("summary", ""),
|
|
113
|
+
"description": c.get("description"),
|
|
114
|
+
"time_zone": c.get("timeZone"),
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
@tool(annotations=ToolAnnotations(title="Update Calendar", destructiveHint=True))
|
|
118
|
+
def update_calendar(
|
|
119
|
+
calendar_id: str,
|
|
120
|
+
summary: str | None = None,
|
|
121
|
+
description: str | None = None,
|
|
122
|
+
timezone: str | None = None,
|
|
123
|
+
color_id: str | None = None,
|
|
124
|
+
ctx: Context = None,
|
|
125
|
+
) -> dict[str, Any]:
|
|
126
|
+
"""
|
|
127
|
+
Update fields on an existing calendar using patch semantics. Only the
|
|
128
|
+
fields you provide are changed; omitted fields are left as-is.
|
|
129
|
+
|
|
130
|
+
Note: color_id is set via the authenticated user's calendar list entry
|
|
131
|
+
(calendarList().patch()), not the calendar resource itself — it is a
|
|
132
|
+
per-user setting, not a property of the calendar shared with others.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
calendar_id: The calendar ID, or 'primary'.
|
|
136
|
+
summary: New calendar title.
|
|
137
|
+
description: New calendar description.
|
|
138
|
+
timezone: New IANA timezone name (e.g. 'America/Los_Angeles').
|
|
139
|
+
color_id: New color ID from the palette returned by the Calendar API's
|
|
140
|
+
colors().get() endpoint (predefined IDs, typically '1'-'24').
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
Updated calendar: id, summary, description, time_zone, color_id.
|
|
144
|
+
"""
|
|
145
|
+
lc = ctx.request_context.lifespan_context
|
|
146
|
+
|
|
147
|
+
patch: dict[str, Any] = {}
|
|
148
|
+
if summary is not None:
|
|
149
|
+
patch["summary"] = summary
|
|
150
|
+
if description is not None:
|
|
151
|
+
patch["description"] = description
|
|
152
|
+
if timezone is not None:
|
|
153
|
+
patch["timeZone"] = timezone
|
|
154
|
+
|
|
155
|
+
try:
|
|
156
|
+
if patch:
|
|
157
|
+
c = (
|
|
158
|
+
lc.calendar_service.calendars()
|
|
159
|
+
.patch(calendarId=calendar_id, body=patch)
|
|
160
|
+
.execute()
|
|
161
|
+
)
|
|
162
|
+
else:
|
|
163
|
+
c = lc.calendar_service.calendars().get(calendarId=calendar_id).execute()
|
|
164
|
+
|
|
165
|
+
result_color_id = None
|
|
166
|
+
if color_id is not None:
|
|
167
|
+
list_entry = (
|
|
168
|
+
lc.calendar_service.calendarList()
|
|
169
|
+
.patch(calendarId=calendar_id, body={"colorId": color_id})
|
|
170
|
+
.execute()
|
|
171
|
+
)
|
|
172
|
+
result_color_id = list_entry.get("colorId")
|
|
173
|
+
except Exception as e:
|
|
174
|
+
return {"error": str(e)}
|
|
175
|
+
|
|
176
|
+
lc.calendar_cache.mark_dirty(calendar_id)
|
|
177
|
+
logger.debug("Updated calendar %s", calendar_id)
|
|
178
|
+
return {
|
|
179
|
+
"id": c.get("id", calendar_id),
|
|
180
|
+
"summary": c.get("summary", ""),
|
|
181
|
+
"description": c.get("description"),
|
|
182
|
+
"time_zone": c.get("timeZone"),
|
|
183
|
+
"color_id": result_color_id,
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
@tool(annotations=ToolAnnotations(title="Delete Calendar", destructiveHint=True))
|
|
187
|
+
def delete_calendar(calendar_id: str, ctx: Context = None) -> dict[str, Any]:
|
|
188
|
+
"""
|
|
189
|
+
Permanently delete a calendar. For calendars you own, this deletes the
|
|
190
|
+
calendar entirely for all users it is shared with — use with caution.
|
|
191
|
+
To only stop seeing a calendar in your own list, use
|
|
192
|
+
remove_calendar_from_list instead.
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
calendar_id: The calendar ID to delete.
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
Confirmation with calendar_id and action 'deleted'.
|
|
199
|
+
"""
|
|
200
|
+
lc = ctx.request_context.lifespan_context
|
|
201
|
+
try:
|
|
202
|
+
lc.calendar_service.calendars().delete(calendarId=calendar_id).execute()
|
|
203
|
+
except Exception as e:
|
|
204
|
+
return {"error": str(e)}
|
|
205
|
+
|
|
206
|
+
lc.calendar_cache.mark_dirty(calendar_id)
|
|
207
|
+
logger.debug("Deleted calendar %s", calendar_id)
|
|
208
|
+
return {"calendar_id": calendar_id, "action": "deleted"}
|
|
209
|
+
|
|
76
210
|
@tool(annotations=ToolAnnotations(title="List Events", readOnlyHint=True))
|
|
77
211
|
def list_events(
|
|
78
212
|
calendar_id: str,
|
|
@@ -73,6 +73,192 @@ class TestListCalendars:
|
|
|
73
73
|
cache.store_list.assert_called_once_with(result)
|
|
74
74
|
|
|
75
75
|
|
|
76
|
+
class TestCreateCalendar:
|
|
77
|
+
"""create_calendar inserts a new secondary calendar and invalidates the list cache."""
|
|
78
|
+
|
|
79
|
+
def test_builds_body_from_provided_fields_and_maps_response(self):
|
|
80
|
+
"""summary/description/timezone go in the insert body; response is field-mapped."""
|
|
81
|
+
cal_svc = MagicMock()
|
|
82
|
+
cal_svc.calendars.return_value.insert.return_value.execute.return_value = {
|
|
83
|
+
"id": "new-cal-1",
|
|
84
|
+
"summary": "Team Events",
|
|
85
|
+
"description": "Shared team calendar",
|
|
86
|
+
"timeZone": "America/Los_Angeles",
|
|
87
|
+
}
|
|
88
|
+
cache = MagicMock()
|
|
89
|
+
ctx = _make_ctx(calendar_service=cal_svc, calendar_cache=cache)
|
|
90
|
+
|
|
91
|
+
result = _cal_tools["create_calendar"](
|
|
92
|
+
summary="Team Events",
|
|
93
|
+
description="Shared team calendar",
|
|
94
|
+
timezone="America/Los_Angeles",
|
|
95
|
+
ctx=ctx,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
body = cal_svc.calendars.return_value.insert.call_args.kwargs["body"]
|
|
99
|
+
assert body == {
|
|
100
|
+
"summary": "Team Events",
|
|
101
|
+
"description": "Shared team calendar",
|
|
102
|
+
"timeZone": "America/Los_Angeles",
|
|
103
|
+
}
|
|
104
|
+
assert result["id"] == "new-cal-1"
|
|
105
|
+
assert result["time_zone"] == "America/Los_Angeles"
|
|
106
|
+
|
|
107
|
+
def test_omits_optional_fields_from_body_when_not_provided(self):
|
|
108
|
+
"""When description/timezone are omitted, the insert body must only have summary."""
|
|
109
|
+
cal_svc = MagicMock()
|
|
110
|
+
cal_svc.calendars.return_value.insert.return_value.execute.return_value = {
|
|
111
|
+
"id": "new-cal-2",
|
|
112
|
+
"summary": "Minimal",
|
|
113
|
+
}
|
|
114
|
+
ctx = _make_ctx(calendar_service=cal_svc, calendar_cache=MagicMock())
|
|
115
|
+
|
|
116
|
+
_cal_tools["create_calendar"](summary="Minimal", ctx=ctx)
|
|
117
|
+
|
|
118
|
+
body = cal_svc.calendars.return_value.insert.call_args.kwargs["body"]
|
|
119
|
+
assert body == {"summary": "Minimal"}
|
|
120
|
+
|
|
121
|
+
def test_marks_cache_dirty_with_new_calendar_id(self):
|
|
122
|
+
"""calendar_cache.mark_dirty must be called with the newly created calendar's id."""
|
|
123
|
+
cal_svc = MagicMock()
|
|
124
|
+
cal_svc.calendars.return_value.insert.return_value.execute.return_value = {
|
|
125
|
+
"id": "new-cal-3",
|
|
126
|
+
"summary": "Test",
|
|
127
|
+
}
|
|
128
|
+
cache = MagicMock()
|
|
129
|
+
ctx = _make_ctx(calendar_service=cal_svc, calendar_cache=cache)
|
|
130
|
+
|
|
131
|
+
_cal_tools["create_calendar"](summary="Test", ctx=ctx)
|
|
132
|
+
|
|
133
|
+
cache.mark_dirty.assert_called_once_with("new-cal-3")
|
|
134
|
+
|
|
135
|
+
def test_api_error_returns_error_dict_without_marking_cache_dirty(self):
|
|
136
|
+
"""When calendars().insert() raises, result must be {"error": ...} and cache stays clean."""
|
|
137
|
+
cal_svc = MagicMock()
|
|
138
|
+
cal_svc.calendars.return_value.insert.return_value.execute.side_effect = Exception(
|
|
139
|
+
"quotaExceeded"
|
|
140
|
+
)
|
|
141
|
+
cache = MagicMock()
|
|
142
|
+
ctx = _make_ctx(calendar_service=cal_svc, calendar_cache=cache)
|
|
143
|
+
|
|
144
|
+
result = _cal_tools["create_calendar"](summary="Test", ctx=ctx)
|
|
145
|
+
|
|
146
|
+
assert "error" in result
|
|
147
|
+
cache.mark_dirty.assert_not_called()
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class TestUpdateCalendar:
|
|
151
|
+
"""update_calendar uses patch semantics for core fields and calendarList for color."""
|
|
152
|
+
|
|
153
|
+
def test_only_summary_in_body_when_only_summary_provided(self):
|
|
154
|
+
"""If only summary is given, the patch body must not include description or timeZone."""
|
|
155
|
+
cal_svc = MagicMock()
|
|
156
|
+
cal_svc.calendars.return_value.patch.return_value.execute.return_value = {
|
|
157
|
+
"id": "cal-1",
|
|
158
|
+
"summary": "Renamed",
|
|
159
|
+
}
|
|
160
|
+
ctx = _make_ctx(calendar_service=cal_svc, calendar_cache=MagicMock())
|
|
161
|
+
|
|
162
|
+
_cal_tools["update_calendar"](calendar_id="cal-1", summary="Renamed", ctx=ctx)
|
|
163
|
+
|
|
164
|
+
body = cal_svc.calendars.return_value.patch.call_args.kwargs["body"]
|
|
165
|
+
assert body == {"summary": "Renamed"}
|
|
166
|
+
|
|
167
|
+
def test_color_id_patches_calendar_list_not_calendars_resource(self):
|
|
168
|
+
"""color_id must go through calendarList().patch(), not calendars().patch()."""
|
|
169
|
+
cal_svc = MagicMock()
|
|
170
|
+
cal_svc.calendars.return_value.get.return_value.execute.return_value = {
|
|
171
|
+
"id": "cal-1",
|
|
172
|
+
"summary": "Existing",
|
|
173
|
+
}
|
|
174
|
+
cal_svc.calendarList.return_value.patch.return_value.execute.return_value = {"colorId": "5"}
|
|
175
|
+
ctx = _make_ctx(calendar_service=cal_svc, calendar_cache=MagicMock())
|
|
176
|
+
|
|
177
|
+
result = _cal_tools["update_calendar"](calendar_id="cal-1", color_id="5", ctx=ctx)
|
|
178
|
+
|
|
179
|
+
cal_svc.calendars.return_value.patch.assert_not_called()
|
|
180
|
+
cal_svc.calendarList.return_value.patch.assert_called_once_with(
|
|
181
|
+
calendarId="cal-1", body={"colorId": "5"}
|
|
182
|
+
)
|
|
183
|
+
assert result["color_id"] == "5"
|
|
184
|
+
|
|
185
|
+
def test_no_fields_provided_falls_back_to_get_without_patching(self):
|
|
186
|
+
"""With no fields set, the tool must fetch via calendars().get(), not patch()."""
|
|
187
|
+
cal_svc = MagicMock()
|
|
188
|
+
cal_svc.calendars.return_value.get.return_value.execute.return_value = {
|
|
189
|
+
"id": "cal-1",
|
|
190
|
+
"summary": "Unchanged",
|
|
191
|
+
}
|
|
192
|
+
ctx = _make_ctx(calendar_service=cal_svc, calendar_cache=MagicMock())
|
|
193
|
+
|
|
194
|
+
result = _cal_tools["update_calendar"](calendar_id="cal-1", ctx=ctx)
|
|
195
|
+
|
|
196
|
+
cal_svc.calendars.return_value.patch.assert_not_called()
|
|
197
|
+
cal_svc.calendars.return_value.get.assert_called_once_with(calendarId="cal-1")
|
|
198
|
+
assert result["summary"] == "Unchanged"
|
|
199
|
+
assert result["color_id"] is None
|
|
200
|
+
|
|
201
|
+
def test_marks_cache_dirty_with_correct_calendar_id(self):
|
|
202
|
+
"""After a successful update, calendar_cache.mark_dirty is called with the calendar_id."""
|
|
203
|
+
cal_svc = MagicMock()
|
|
204
|
+
cal_svc.calendars.return_value.patch.return_value.execute.return_value = {
|
|
205
|
+
"id": "cal-2",
|
|
206
|
+
"summary": "Renamed",
|
|
207
|
+
}
|
|
208
|
+
cache = MagicMock()
|
|
209
|
+
ctx = _make_ctx(calendar_service=cal_svc, calendar_cache=cache)
|
|
210
|
+
|
|
211
|
+
_cal_tools["update_calendar"](calendar_id="cal-2", summary="Renamed", ctx=ctx)
|
|
212
|
+
|
|
213
|
+
cache.mark_dirty.assert_called_once_with("cal-2")
|
|
214
|
+
|
|
215
|
+
def test_api_error_returns_error_dict_without_marking_cache_dirty(self):
|
|
216
|
+
"""When calendars().patch() raises, result must be {"error": ...} and cache stays clean."""
|
|
217
|
+
cal_svc = MagicMock()
|
|
218
|
+
cal_svc.calendars.return_value.patch.return_value.execute.side_effect = Exception(
|
|
219
|
+
"notFound"
|
|
220
|
+
)
|
|
221
|
+
cache = MagicMock()
|
|
222
|
+
ctx = _make_ctx(calendar_service=cal_svc, calendar_cache=cache)
|
|
223
|
+
|
|
224
|
+
result = _cal_tools["update_calendar"](
|
|
225
|
+
calendar_id="cal-missing", summary="Renamed", ctx=ctx
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
assert "error" in result
|
|
229
|
+
cache.mark_dirty.assert_not_called()
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
class TestDeleteCalendar:
|
|
233
|
+
"""delete_calendar returns a structured confirmation and invalidates the cache."""
|
|
234
|
+
|
|
235
|
+
def test_success_returns_confirmation_dict_and_marks_cache_dirty(self):
|
|
236
|
+
"""Result must be {calendar_id, action='deleted'} and cache is dirtied."""
|
|
237
|
+
cal_svc = MagicMock()
|
|
238
|
+
cal_svc.calendars.return_value.delete.return_value.execute.return_value = None
|
|
239
|
+
cache = MagicMock()
|
|
240
|
+
ctx = _make_ctx(calendar_service=cal_svc, calendar_cache=cache)
|
|
241
|
+
|
|
242
|
+
result = _cal_tools["delete_calendar"](calendar_id="cal-1", ctx=ctx)
|
|
243
|
+
|
|
244
|
+
assert result == {"calendar_id": "cal-1", "action": "deleted"}
|
|
245
|
+
cache.mark_dirty.assert_called_once_with("cal-1")
|
|
246
|
+
|
|
247
|
+
def test_api_error_returns_error_dict_without_marking_cache_dirty(self):
|
|
248
|
+
"""If delete raises, result has 'error' key and the cache must not be dirtied."""
|
|
249
|
+
cal_svc = MagicMock()
|
|
250
|
+
cal_svc.calendars.return_value.delete.return_value.execute.side_effect = Exception(
|
|
251
|
+
"Not Found"
|
|
252
|
+
)
|
|
253
|
+
cache = MagicMock()
|
|
254
|
+
ctx = _make_ctx(calendar_service=cal_svc, calendar_cache=cache)
|
|
255
|
+
|
|
256
|
+
result = _cal_tools["delete_calendar"](calendar_id="cal-missing", ctx=ctx)
|
|
257
|
+
|
|
258
|
+
assert "error" in result
|
|
259
|
+
cache.mark_dirty.assert_not_called()
|
|
260
|
+
|
|
261
|
+
|
|
76
262
|
class TestCreateEvent:
|
|
77
263
|
"""create_event detects all-day events, passes attendees, and invalidates the cache."""
|
|
78
264
|
|
|
File without changes
|
|
File without changes
|
{mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/.github/ISSUE_TEMPLATE/bug_report.md
RENAMED
|
File without changes
|
{mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/.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
|
{mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/decisions/decision-chart-theming.md
RENAMED
|
File without changes
|
{mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/decisions/decision-composite-tools.md
RENAMED
|
File without changes
|
{mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/decisions/decision-docs-formatting.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/decisions/decision-publishing.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/design/effectiveformat-spike.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
|
{mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/fixtures/tc-d195-create-doc.md
RENAMED
|
File without changes
|
{mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/fixtures/tc-d196-create-doc.html
RENAMED
|
File without changes
|
{mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/fixtures/tc-d213-dollar-escape.md
RENAMED
|
File without changes
|
{mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/fixtures/tc-d226-heading-table.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
|
{mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/results/2026-07-04-drive-smoke.md
RENAMED
|
File without changes
|
|
File without changes
|
{mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/docs/qa/results/2026-07-04-sheets_read.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.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/tools/docs/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/tools/docs/content.py
RENAMED
|
File without changes
|
{mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/tools/docs/emitter.py
RENAMED
|
File without changes
|
{mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/tools/docs/html_parser.py
RENAMED
|
File without changes
|
{mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/tools/docs/layout.py
RENAMED
|
File without changes
|
{mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/tools/docs/style.py
RENAMED
|
File without changes
|
{mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/tools/docs/tables.py
RENAMED
|
File without changes
|
{mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/tools/drive/__init__.py
RENAMED
|
File without changes
|
{mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/tools/drive/activity.py
RENAMED
|
File without changes
|
{mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/tools/drive/files.py
RENAMED
|
File without changes
|
{mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/tools/drive/sharing.py
RENAMED
|
File without changes
|
{mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/tools/drive/transfer.py
RENAMED
|
File without changes
|
{mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/tools/response_limits.py
RENAMED
|
File without changes
|
{mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/tools/sheets/__init__.py
RENAMED
|
File without changes
|
{mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/tools/sheets/data.py
RENAMED
|
File without changes
|
{mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/tools/sheets/helpers.py
RENAMED
|
File without changes
|
{mcp_gee_sweet-0.7.0.dev67 → mcp_gee_sweet-0.7.0.dev68}/src/mcp_gee_sweet/tools/sheets/structure.py
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
|