mcp-server-appwrite 2.2.2__tar.gz → 2.3.0__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_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/.env.example +5 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/AGENTS.md +7 -4
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/PKG-INFO +12 -1
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/README.md +11 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/docs/apps.md +1 -1
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/docs/self-hosted.md +25 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/docs/tool-surface.md +17 -3
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/src/mcp_server_appwrite/apps/views.py +54 -17
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/src/mcp_server_appwrite/assets/instructions.json +4 -2
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/src/mcp_server_appwrite/assets/view.html +5 -1
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/src/mcp_server_appwrite/context.py +73 -18
- mcp_server_appwrite-2.3.0/src/mcp_server_appwrite/data/docs_index.npz +4 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/src/mcp_server_appwrite/error_classification.py +7 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/src/mcp_server_appwrite/error_monitoring.py +3 -0
- mcp_server_appwrite-2.3.0/src/mcp_server_appwrite/headers.py +104 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/src/mcp_server_appwrite/operator.py +99 -10
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/src/mcp_server_appwrite/server.py +69 -7
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/tests/e2e/support.py +46 -1
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/tests/e2e/test_apps.py +35 -0
- mcp_server_appwrite-2.3.0/tests/e2e/test_stdio.py +180 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/tests/e2e/test_tools.py +179 -2
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/tests/unit/test_operator.py +1 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/tests/unit/test_server.py +4 -0
- mcp_server_appwrite-2.2.2/src/mcp_server_appwrite/data/docs_index.npz +0 -4
- mcp_server_appwrite-2.2.2/tests/e2e/test_stdio.py +0 -100
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/.dockerignore +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/.github/workflows/ci.yml +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/.github/workflows/production.yml +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/.github/workflows/publish.yml +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/.github/workflows/staging.yml +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/.github/workflows/update-docs-index.yml +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/.gitignore +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/.python-version +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/CLAUDE.md +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/Dockerfile +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/LICENSE +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/compose.yaml +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/docs/appwrite-mcp-flow.png +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/docs/appwrite-mcp-flow.svg +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/docs/authentication.md +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/docs/development.md +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/docs/documentation-search.md +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/docs/events.md +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/docs/flags.md +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/docs/images/claude-desktop-local-mcp-servers.png +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/docs/images/codex-mcp.png +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/docs/images/cursor-mcp.png +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/glama.json +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/pyproject.toml +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/scripts/build_docs_index.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/scripts/docs_release_notes.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/scripts/render_server_json.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/server.template.json +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/src/mcp_server_appwrite/__init__.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/src/mcp_server_appwrite/__main__.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/src/mcp_server_appwrite/apps/__init__.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/src/mcp_server_appwrite/apps/executions.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/src/mcp_server_appwrite/apps/files.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/src/mcp_server_appwrite/apps/protocol.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/src/mcp_server_appwrite/apps/resource.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/src/mcp_server_appwrite/apps/usage.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/src/mcp_server_appwrite/apps/users.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/src/mcp_server_appwrite/assets/__init__.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/src/mcp_server_appwrite/assets/favicon.svg +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/src/mcp_server_appwrite/auth.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/src/mcp_server_appwrite/catalog_policy.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/src/mcp_server_appwrite/constants.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/src/mcp_server_appwrite/docs.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/src/mcp_server_appwrite/docs_index.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/src/mcp_server_appwrite/docs_search.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/src/mcp_server_appwrite/docs_source.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/src/mcp_server_appwrite/events/__init__.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/src/mcp_server_appwrite/events/bus.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/src/mcp_server_appwrite/events/catalog.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/src/mcp_server_appwrite/events/delivery.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/src/mcp_server_appwrite/events/egress.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/src/mcp_server_appwrite/events/envelope.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/src/mcp_server_appwrite/events/errors.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/src/mcp_server_appwrite/events/ingress.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/src/mcp_server_appwrite/events/projection.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/src/mcp_server_appwrite/events/protocol.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/src/mcp_server_appwrite/events/streams.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/src/mcp_server_appwrite/events/subscriptions.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/src/mcp_server_appwrite/events/webhooks.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/src/mcp_server_appwrite/flags.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/src/mcp_server_appwrite/http_app.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/src/mcp_server_appwrite/instructions.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/src/mcp_server_appwrite/legacy.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/src/mcp_server_appwrite/service.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/src/mcp_server_appwrite/telemetry.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/src/mcp_server_appwrite/tool_manager.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/tests/e2e/fixtures/appwrite/deployment_function_duplicate.json +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/tests/e2e/fixtures/appwrite/deployment_function_failed.json +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/tests/e2e/fixtures/appwrite/deployment_function_ready.json +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/tests/e2e/fixtures/appwrite/deployment_site_ready.json +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/tests/e2e/fixtures/appwrite/execution_async_failed.json +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/tests/e2e/fixtures/appwrite/execution_async_waiting.json +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/tests/e2e/fixtures/appwrite/execution_sync_failed.json +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/tests/e2e/fixtures/appwrite/file_created.json +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/tests/e2e/fixtures/appwrite/function_activation.json +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/tests/e2e/fixtures/appwrite/row_created.json +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/tests/e2e/fixtures/appwrite/user_created.json +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/tests/e2e/test_events_ingress.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/tests/e2e/test_events_protocol.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/tests/e2e/test_events_stream.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/tests/e2e/test_events_subscribe.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/tests/integration/support.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/tests/integration/test_avatars.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/tests/integration/test_console_sdk_oauth.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/tests/integration/test_docs_index.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/tests/integration/test_docs_source.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/tests/integration/test_functions.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/tests/integration/test_locale.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/tests/integration/test_messaging.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/tests/integration/test_oauth_discovery.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/tests/integration/test_operator.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/tests/integration/test_sites.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/tests/integration/test_storage.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/tests/integration/test_tables_db.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/tests/integration/test_teams.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/tests/integration/test_users.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/tests/test_all.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/tests/unit/fixtures/jwe-v1.json +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/tests/unit/test_auth.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/tests/unit/test_context.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/tests/unit/test_docs.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/tests/unit/test_docs_search.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/tests/unit/test_error_classification.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/tests/unit/test_error_monitoring.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/tests/unit/test_events_delivery.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/tests/unit/test_events_egress.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/tests/unit/test_events_envelope.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/tests/unit/test_events_protocol.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/tests/unit/test_http_app.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/tests/unit/test_instructions.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/tests/unit/test_release_metadata.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/tests/unit/test_service.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/tests/unit/test_telemetry.py +0 -0
- {mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/uv.lock +0 -0
|
@@ -32,6 +32,11 @@ PORT=8000
|
|
|
32
32
|
# Generate a key with `openssl rand -base64 32`.
|
|
33
33
|
# MCP_EVENTS_SEALING_KEYS=k1:<base64 key>
|
|
34
34
|
|
|
35
|
+
# stdio only. The project environment (Appwrite Cloud) to act in, with
|
|
36
|
+
# APPWRITE_PROJECT_ID set to the ID of the project it belongs to. Sent as
|
|
37
|
+
# X-Appwrite-Environment on every request.
|
|
38
|
+
# APPWRITE_ENVIRONMENT_ID=staging
|
|
39
|
+
|
|
35
40
|
# Integration tests only (not used by the running server).
|
|
36
41
|
# The live integration suite authenticates to the Appwrite API with an API key
|
|
37
42
|
# and runs against APPWRITE_PROJECT_ID (above).
|
|
@@ -13,7 +13,8 @@ operator-style tools, supporting two deployments from one codebase:
|
|
|
13
13
|
Appwrite REST API. Served primarily at `mcp.appwrite.io/`, with `/mcp` also
|
|
14
14
|
available as a conventional alias.
|
|
15
15
|
- **Self-hosted (`stdio`):** runs locally and authenticates with a project API
|
|
16
|
-
key (`APPWRITE_PROJECT_ID`, `APPWRITE_API_KEY`, `APPWRITE_ENDPOINT
|
|
16
|
+
key (`APPWRITE_PROJECT_ID`, `APPWRITE_API_KEY`, `APPWRITE_ENDPOINT`, and
|
|
17
|
+
optionally `APPWRITE_ENVIRONMENT_ID` for a project environment).
|
|
17
18
|
|
|
18
19
|
Python ≥ 3.12, packaged with `hatchling`, managed with `uv`.
|
|
19
20
|
|
|
@@ -31,7 +32,8 @@ Source lives in `src/mcp_server_appwrite/`:
|
|
|
31
32
|
| `tool_manager.py` | Registry of all services and their generated tools. |
|
|
32
33
|
| `operator.py` | The compact "operator" surface — `appwrite_search_tools`, `appwrite_call_tool` / `appwrite_call_write_tool`, tool annotations, result/resource storage, write confirmation. |
|
|
33
34
|
| `instructions.py` | `serverInfo.description` and the server instructions sent on initialize, rendered from `assets/instructions.json` (a description plus ordered, transport/docs-conditional Markdown sections). Edit the JSON to change the wording; keep the `pyproject.toml` description in sync (a test enforces it). |
|
|
34
|
-
| `context.py` | `appwrite_get_context` — workspace summary (project, services, account/org for OAuth). |
|
|
35
|
+
| `context.py` | `appwrite_get_context` — workspace summary (project, services, account/org for OAuth, a focused project's environments). |
|
|
36
|
+
| `headers.py` | Per-call request headers: `X-Appwrite-Environment` (a project environment, next to the project's ID) and `X-Appwrite-Reason` (why a write is made, validated and percent-encoded as Appwrite expects). |
|
|
35
37
|
| `docs_search.py` | In-process semantic docs search (`appwrite_search_docs`) over a prebuilt index. |
|
|
36
38
|
| `docs_source.py` | Fetches the published docs (`/docs/llms.txt` + per-page `.md` exports on appwrite.io) and chunks them for the index build. |
|
|
37
39
|
| `docs_index.py` | Writes the index artifact deterministically and atomically: page and chunk hashes, reuse of cached chunk vectors, build lock, and the added/changed/removed page report. |
|
|
@@ -59,8 +61,9 @@ reads, `appwrite_call_write_tool` for everything else, and optionally
|
|
|
59
61
|
`appwrite_search_docs`), while the full OAuth Appwrite catalog (41
|
|
60
62
|
services) stays internal and is searched at runtime. API-key stdio uses a filtered
|
|
61
63
|
27-service catalog. Mutating hidden tools run only through
|
|
62
|
-
`appwrite_call_write_tool` and require `confirm_write=true
|
|
63
|
-
|
|
64
|
+
`appwrite_call_write_tool` and require `confirm_write=true` and a one-line
|
|
65
|
+
`reason` (sent as `X-Appwrite-Reason`, recorded on the audit log), so each
|
|
66
|
+
exposed tool's annotations match every call it accepts. Large outputs are stored as MCP
|
|
64
67
|
resources and returned as a preview + resource URI.
|
|
65
68
|
|
|
66
69
|
With MCP Apps on (`flags.APPS`), hosts that render them also see
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: mcp-server-appwrite
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.0
|
|
4
4
|
Summary: Build and deploy application backends. Add authentication, PostgreSQL/MySQL databases, vector databases, storage, serverless functions, realtime, messaging, hosting, domains, CDN and firewall.
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Requires-Python: >=3.12
|
|
@@ -339,6 +339,17 @@ A browser window opens to authenticate on first connect.
|
|
|
339
339
|
|
|
340
340
|
</details>
|
|
341
341
|
|
|
342
|
+
## Reasons and environments
|
|
343
|
+
|
|
344
|
+
Every write the agent makes carries a short `reason` (why it made the change).
|
|
345
|
+
Appwrite records it on the project's audit log, so you can review what your
|
|
346
|
+
agent changed, and why, in the project's **Activity** tab.
|
|
347
|
+
|
|
348
|
+
On Appwrite Cloud, the agent can act in a project's environments (for example
|
|
349
|
+
`staging`): `appwrite_get_context` lists them, and each call takes the
|
|
350
|
+
environment's ID as `environment_id` next to the project's `project_id`. See
|
|
351
|
+
[Tool surface](docs/tool-surface.md#behavior).
|
|
352
|
+
|
|
342
353
|
## Self-hosted Appwrite
|
|
343
354
|
|
|
344
355
|
Running your own Appwrite instance? Run the MCP server locally over `stdio` and
|
|
@@ -307,6 +307,17 @@ A browser window opens to authenticate on first connect.
|
|
|
307
307
|
|
|
308
308
|
</details>
|
|
309
309
|
|
|
310
|
+
## Reasons and environments
|
|
311
|
+
|
|
312
|
+
Every write the agent makes carries a short `reason` (why it made the change).
|
|
313
|
+
Appwrite records it on the project's audit log, so you can review what your
|
|
314
|
+
agent changed, and why, in the project's **Activity** tab.
|
|
315
|
+
|
|
316
|
+
On Appwrite Cloud, the agent can act in a project's environments (for example
|
|
317
|
+
`staging`): `appwrite_get_context` lists them, and each call takes the
|
|
318
|
+
environment's ID as `environment_id` next to the project's `project_id`. See
|
|
319
|
+
[Tool surface](docs/tool-surface.md#behavior).
|
|
320
|
+
|
|
310
321
|
## Self-hosted Appwrite
|
|
311
322
|
|
|
312
323
|
Running your own Appwrite instance? Run the MCP server locally over `stdio` and
|
|
@@ -10,7 +10,7 @@ interactive view inside the chat. This server offers one view,
|
|
|
10
10
|
| Name | Seen by | What it does |
|
|
11
11
|
| --- | --- | --- |
|
|
12
12
|
| `appwrite_show` | model and view | `view=table`: a page of a table's rows, newest first, with `cursor` paging. `view=deployment`: a Function or Site deployment (the latest unless `deployment_id` is given) with the last 200 build log lines. `view=executions`: a Function's executions, newest first, optionally one `status`, with `cursor` paging; `execution_id` also opens one with its request and response headers, status code, duration, and the last 200 lines of logs and of errors. `view=files`: a Storage bucket's files, newest first, with `search` (file name) and `cursor` paging; `file_id` also opens one. `view=users`: the project's Auth users, newest first, with `search` (name, email, phone or ID) and `cursor` paging; `user_id` also opens one with their ten newest sessions. `view=usage`: a project's requests, bandwidth (in and out) and executions per interval over `range` (`24h` hourly, `7d` or `30d` daily), with totals and the newest snapshot of its users, resource counts and storage. Read-only. |
|
|
13
|
-
| `appwrite_view_write` | view only (`visibility: ["app"]`) | Runs one of `tables_db_update_row`, `functions_update_deployment_status`, `sites_update_deployment_status`, `users_update_status`, `users_update_labels`, `users_update_email_verification`, only with `confirm_write=true`. |
|
|
13
|
+
| `appwrite_view_write` | view only (`visibility: ["app"]`) | Runs one of `tables_db_update_row`, `functions_update_deployment_status`, `sites_update_deployment_status`, `users_update_status`, `users_update_labels`, `users_update_email_verification`, only with `confirm_write=true`. A `reason` is optional here (a person confirmed the change in the view) and sent as `X-Appwrite-Reason` when given. |
|
|
14
14
|
| `ui://appwrite/view` | host | One HTML document (`assets/view.html`) that renders every view. |
|
|
15
15
|
|
|
16
16
|
`appwrite_show` returns a text summary in `content`, which is what the model and
|
|
@@ -43,6 +43,31 @@ For a local Docker Appwrite instance the endpoint is typically
|
|
|
43
43
|
`http://localhost:9501/v1` (HTTP, not HTTPS). The value must be the API base
|
|
44
44
|
(`…/v1`), not the Console URL.
|
|
45
45
|
|
|
46
|
+
## Project environments
|
|
47
|
+
|
|
48
|
+
On Appwrite Cloud, a project can have environments (for example `staging`).
|
|
49
|
+
To point a local server at one, keep `APPWRITE_PROJECT_ID` set to the ID of
|
|
50
|
+
the project it belongs to and add the environment's ID:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
APPWRITE_PROJECT_ID=<YOUR_PROJECT_ID> \
|
|
54
|
+
APPWRITE_ENVIRONMENT_ID=staging \
|
|
55
|
+
APPWRITE_API_KEY=<YOUR_API_KEY> \
|
|
56
|
+
uvx mcp-server-appwrite
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Every request then carries `X-Appwrite-Environment: staging` next to the
|
|
60
|
+
project's ID. The environment is fixed for the server's lifetime: a tool call
|
|
61
|
+
that names another `environment_id` is refused rather than run in this one.
|
|
62
|
+
Run one server per environment you want the agent to reach.
|
|
63
|
+
|
|
64
|
+
## Reasons on writes
|
|
65
|
+
|
|
66
|
+
Every write the agent makes through `appwrite_call_write_tool` carries a
|
|
67
|
+
one-line `reason`, which Appwrite records on the project's audit log
|
|
68
|
+
(`X-Appwrite-Reason`). Self-hosted Appwrite versions that predate the header
|
|
69
|
+
ignore it.
|
|
70
|
+
|
|
46
71
|
## Connect your client
|
|
47
72
|
|
|
48
73
|
<details open>
|
|
@@ -35,10 +35,10 @@ flowchart LR
|
|
|
35
35
|
|
|
36
36
|
| Tool | What it does |
|
|
37
37
|
| --- | --- |
|
|
38
|
-
| `appwrite_get_context` | Workspace summary. API key → project + readable service totals/samples. OAuth → also account, organization, discovered projects. |
|
|
38
|
+
| `appwrite_get_context` | Workspace summary. API key → project + readable service totals/samples. OAuth → also account, organization, discovered projects. A project in focus lists its environments; `environment_id` summarizes one of them instead. |
|
|
39
39
|
| `appwrite_search_tools` | Searches the internal catalog at runtime. |
|
|
40
40
|
| `appwrite_call_tool` | Runs a read catalog tool (`list`, `get`). |
|
|
41
|
-
| `appwrite_call_write_tool` | Runs every other catalog tool (create, update, delete, upsert, unknown verbs). Requires `confirm_write=true`. |
|
|
41
|
+
| `appwrite_call_write_tool` | Runs every other catalog tool (create, update, delete, upsert, unknown verbs). Requires `confirm_write=true` and a `reason`. |
|
|
42
42
|
| `appwrite_search_docs` | Semantic search over Appwrite docs (conditional — see above). |
|
|
43
43
|
|
|
44
44
|
## Behavior
|
|
@@ -50,7 +50,21 @@ flowchart LR
|
|
|
50
50
|
idempotent, `appwrite_call_write_tool` is destructive. Each search result
|
|
51
51
|
names its `call_tool`, and calling a tool through the other one returns an
|
|
52
52
|
error naming the right one.
|
|
53
|
-
- **Writes** require `confirm_write=true
|
|
53
|
+
- **Writes** require `confirm_write=true` and a `reason`: one line, at most 256
|
|
54
|
+
characters, saying why the change is made. It is sent as `X-Appwrite-Reason`
|
|
55
|
+
(percent-encoded like `encodeURIComponent`), and Appwrite records it on the
|
|
56
|
+
project's audit log, shown in the Activity tab next to the change. A reason
|
|
57
|
+
Appwrite would refuse (blank, too long, or with line breaks or other control
|
|
58
|
+
characters) is refused before any request is made. Reads send no reason.
|
|
59
|
+
- **Environments**: `appwrite_call_tool`, `appwrite_call_write_tool`,
|
|
60
|
+
`appwrite_get_context` and `appwrite_show` take an optional `environment_id`
|
|
61
|
+
to act in one of a project's environments (Appwrite Cloud). It is sent as
|
|
62
|
+
`X-Appwrite-Environment` together with the project's own ID in
|
|
63
|
+
`X-Appwrite-Project`, since an environment's project ID is internal, and
|
|
64
|
+
needs `project_id` on the hosted server. `appwrite_get_context` with
|
|
65
|
+
`project_id` lists the project's environments (with the `project.read` scope;
|
|
66
|
+
self-hosted Appwrite has none). Over stdio the environment is fixed by
|
|
67
|
+
`APPWRITE_ENVIRONMENT_ID`, and a call naming another one is refused.
|
|
54
68
|
- **Annotations**: every exposed tool sets a `title` and all four hints
|
|
55
69
|
(`readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`)
|
|
56
70
|
explicitly, as the Anthropic and OpenAI connector directories require.
|
{mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/src/mcp_server_appwrite/apps/views.py
RENAMED
|
@@ -35,7 +35,12 @@ from . import executions, files, protocol, resource, usage, users
|
|
|
35
35
|
|
|
36
36
|
Run = Callable[[dict[str, Any], bool], list[Any]]
|
|
37
37
|
"""Runs a hidden catalog tool from raw call arguments (``tool_name``,
|
|
38
|
-
``arguments``, ``project_id``, ``confirm_write
|
|
38
|
+
``arguments``, ``project_id``, ``environment_id``, ``confirm_write``,
|
|
39
|
+
``reason``), as a write or a read."""
|
|
40
|
+
|
|
41
|
+
Read = Callable[[str, Any, dict[str, Any]], dict[str, Any]]
|
|
42
|
+
"""Reads one catalog tool's JSON result in the project (and environment) a
|
|
43
|
+
view shows: ``(tool_name, project_id, arguments)``."""
|
|
39
44
|
|
|
40
45
|
Handler = Callable[[dict[str, Any]], types.CallToolResult]
|
|
41
46
|
|
|
@@ -100,26 +105,39 @@ class Views:
|
|
|
100
105
|
|
|
101
106
|
def _show(self, arguments: dict[str, Any]) -> types.CallToolResult:
|
|
102
107
|
view = arguments.get("view")
|
|
108
|
+
# A view shows one project, or one of its environments, and every read
|
|
109
|
+
# it makes stays there.
|
|
110
|
+
environment_id = arguments.get("environment_id") or None
|
|
111
|
+
|
|
112
|
+
def read(tool_name: str, project_id: Any, target: dict[str, Any]) -> Any:
|
|
113
|
+
return self._read(tool_name, project_id, target, environment_id)
|
|
114
|
+
|
|
115
|
+
def preview(project_id: Any, target: dict[str, Any]) -> str | None:
|
|
116
|
+
return self._preview(project_id, target, environment_id)
|
|
117
|
+
|
|
103
118
|
if view == View.TABLE:
|
|
104
|
-
data = self._table(arguments)
|
|
119
|
+
data = self._table(read, arguments)
|
|
105
120
|
text = _table_summary(data)
|
|
106
121
|
elif view == View.DEPLOYMENT:
|
|
107
|
-
data = self._deployment(arguments)
|
|
122
|
+
data = self._deployment(read, arguments)
|
|
108
123
|
text = _deployment_summary(data)
|
|
109
124
|
elif view == View.EXECUTIONS:
|
|
110
|
-
data = executions.read(
|
|
125
|
+
data = executions.read(read, arguments)
|
|
111
126
|
text = executions.summary(data)
|
|
112
127
|
elif view == View.FILES:
|
|
113
|
-
data = files.read(
|
|
128
|
+
data = files.read(read, preview, arguments)
|
|
114
129
|
text = files.summary(data)
|
|
115
130
|
elif view == View.USERS:
|
|
116
|
-
data = users.read(
|
|
131
|
+
data = users.read(read, arguments)
|
|
117
132
|
text = users.summary(data)
|
|
118
133
|
elif view == View.USAGE:
|
|
119
|
-
data = usage.read(
|
|
134
|
+
data = usage.read(read, arguments)
|
|
120
135
|
text = usage.summary(data)
|
|
121
136
|
else:
|
|
122
137
|
raise ValueError(f"view must be one of: {', '.join(View)}. Got {view!r}.")
|
|
138
|
+
if environment_id:
|
|
139
|
+
# The view sends it back on every call it makes, as it does projectId.
|
|
140
|
+
data["environmentId"] = environment_id
|
|
123
141
|
return types.CallToolResult(
|
|
124
142
|
content=[types.TextContent(type="text", text=text)],
|
|
125
143
|
structured_content=data,
|
|
@@ -160,13 +178,18 @@ class Views:
|
|
|
160
178
|
)
|
|
161
179
|
|
|
162
180
|
def _read(
|
|
163
|
-
self,
|
|
181
|
+
self,
|
|
182
|
+
tool_name: str,
|
|
183
|
+
project_id: Any,
|
|
184
|
+
arguments: dict[str, Any],
|
|
185
|
+
environment_id: str | None = None,
|
|
164
186
|
) -> dict[str, Any]:
|
|
165
187
|
result = _json(
|
|
166
188
|
self._run(
|
|
167
189
|
{
|
|
168
190
|
"tool_name": tool_name,
|
|
169
191
|
"project_id": project_id,
|
|
192
|
+
"environment_id": environment_id,
|
|
170
193
|
"arguments": arguments,
|
|
171
194
|
},
|
|
172
195
|
False,
|
|
@@ -176,7 +199,12 @@ class Views:
|
|
|
176
199
|
raise ValueError(f"{tool_name} returned an unexpected response.")
|
|
177
200
|
return result
|
|
178
201
|
|
|
179
|
-
def _preview(
|
|
202
|
+
def _preview(
|
|
203
|
+
self,
|
|
204
|
+
project_id: Any,
|
|
205
|
+
arguments: dict[str, Any],
|
|
206
|
+
environment_id: str | None = None,
|
|
207
|
+
) -> str | None:
|
|
180
208
|
"""A file preview as a data URI. A file Appwrite cannot transform
|
|
181
209
|
shows without one rather than failing the page."""
|
|
182
210
|
try:
|
|
@@ -184,6 +212,7 @@ class Views:
|
|
|
184
212
|
{
|
|
185
213
|
"tool_name": "storage_get_file_preview",
|
|
186
214
|
"project_id": project_id,
|
|
215
|
+
"environment_id": environment_id,
|
|
187
216
|
"arguments": arguments,
|
|
188
217
|
},
|
|
189
218
|
False,
|
|
@@ -195,21 +224,19 @@ class Views:
|
|
|
195
224
|
return f"data:{item.mime_type};base64,{item.data}"
|
|
196
225
|
return None
|
|
197
226
|
|
|
198
|
-
def _table(self, arguments: dict[str, Any]) -> dict[str, Any]:
|
|
227
|
+
def _table(self, read: Read, arguments: dict[str, Any]) -> dict[str, Any]:
|
|
199
228
|
project_id = arguments.get("project_id")
|
|
200
229
|
database_id = _required(arguments, "database_id")
|
|
201
230
|
table_id = _required(arguments, "table_id")
|
|
202
231
|
cursor = arguments.get("cursor")
|
|
203
232
|
target = {"database_id": database_id, "table_id": table_id}
|
|
204
233
|
|
|
205
|
-
table =
|
|
234
|
+
table = read("tables_db_get_table", project_id, target)
|
|
206
235
|
# One row past the page tells whether another page follows.
|
|
207
236
|
queries = [Query.order_desc("$createdAt"), Query.limit(PAGE_SIZE + 1)]
|
|
208
237
|
if cursor:
|
|
209
238
|
queries.append(Query.cursor_after(str(cursor)))
|
|
210
|
-
page =
|
|
211
|
-
"tables_db_list_rows", project_id, {**target, "queries": queries}
|
|
212
|
-
)
|
|
239
|
+
page = read("tables_db_list_rows", project_id, {**target, "queries": queries})
|
|
213
240
|
rows = [_row(each) for each in page.get("rows") or []]
|
|
214
241
|
more = len(rows) > PAGE_SIZE
|
|
215
242
|
rows = rows[:PAGE_SIZE]
|
|
@@ -231,7 +258,7 @@ class Views:
|
|
|
231
258
|
"next": rows[-1].get("$id") if more else None,
|
|
232
259
|
}
|
|
233
260
|
|
|
234
|
-
def _deployment(self, arguments: dict[str, Any]) -> dict[str, Any]:
|
|
261
|
+
def _deployment(self, read: Read, arguments: dict[str, Any]) -> dict[str, Any]:
|
|
235
262
|
project_id = arguments.get("project_id")
|
|
236
263
|
function_id = arguments.get("function_id")
|
|
237
264
|
site_id = arguments.get("site_id")
|
|
@@ -247,13 +274,13 @@ class Views:
|
|
|
247
274
|
|
|
248
275
|
deployment_id = arguments.get("deployment_id")
|
|
249
276
|
if deployment_id:
|
|
250
|
-
deployment =
|
|
277
|
+
deployment = read(
|
|
251
278
|
f"{service}_get_deployment",
|
|
252
279
|
project_id,
|
|
253
280
|
{**target, "deployment_id": deployment_id},
|
|
254
281
|
)
|
|
255
282
|
else:
|
|
256
|
-
latest =
|
|
283
|
+
latest = read(
|
|
257
284
|
f"{service}_list_deployments",
|
|
258
285
|
project_id,
|
|
259
286
|
{
|
|
@@ -321,6 +348,14 @@ def _show_tool() -> types.Tool:
|
|
|
321
348
|
"type": "string",
|
|
322
349
|
"description": "Appwrite project ID the data lives in.",
|
|
323
350
|
},
|
|
351
|
+
"environment_id": {
|
|
352
|
+
"type": "string",
|
|
353
|
+
"description": (
|
|
354
|
+
"ID of the project environment (Appwrite Cloud) the data "
|
|
355
|
+
"lives in, with project_id set to the project it belongs "
|
|
356
|
+
"to. Omit for the project itself."
|
|
357
|
+
),
|
|
358
|
+
},
|
|
324
359
|
"database_id": {
|
|
325
360
|
"type": "string",
|
|
326
361
|
"description": "view=table: the TablesDB database ID.",
|
|
@@ -424,7 +459,9 @@ def _write_tool() -> types.Tool:
|
|
|
424
459
|
"tool_name": {"type": "string", "enum": list(WRITES)},
|
|
425
460
|
"arguments": {"type": "object"},
|
|
426
461
|
"project_id": {"type": "string"},
|
|
462
|
+
"environment_id": {"type": "string"},
|
|
427
463
|
"confirm_write": {"type": "boolean"},
|
|
464
|
+
"reason": {"type": "string"},
|
|
428
465
|
},
|
|
429
466
|
"required": ["tool_name", "arguments", "confirm_write"],
|
|
430
467
|
"additionalProperties": False,
|
|
@@ -80,7 +80,8 @@
|
|
|
80
80
|
"lines": [
|
|
81
81
|
"Using these tools",
|
|
82
82
|
"- Use appwrite_get_context to understand the current connection and available project resources, then use appwrite_search_tools to find the operation you need.",
|
|
83
|
-
"- Run read operations (list, get) with appwrite_call_tool. Run every other operation (create, update, delete, upsert) with appwrite_call_write_tool and confirm_write=true; each search result names its call_tool."
|
|
83
|
+
"- Run read operations (list, get) with appwrite_call_tool. Run every other operation (create, update, delete, upsert) with appwrite_call_write_tool and confirm_write=true; each search result names its call_tool.",
|
|
84
|
+
"- Give every write a reason: one line (max 256 characters) saying why you are making the change, such as \"Add the orders table the checkout flow reads\". Appwrite records it on the project's audit log, where the user reviews it in the Activity tab next to the change."
|
|
84
85
|
]
|
|
85
86
|
},
|
|
86
87
|
{
|
|
@@ -92,7 +93,7 @@
|
|
|
92
93
|
{
|
|
93
94
|
"transports": ["stdio"],
|
|
94
95
|
"lines": [
|
|
95
|
-
"- This local connection uses the API key, endpoint, and project configured in the server environment. Appwrite API calls target that configured APPWRITE_PROJECT_ID by default.",
|
|
96
|
+
"- This local connection uses the API key, endpoint, and project configured in the server environment. Appwrite API calls target that configured APPWRITE_PROJECT_ID by default, and act in the project environment APPWRITE_ENVIRONMENT_ID names when it is set.",
|
|
96
97
|
"- Project API keys cannot manage dedicated PostgreSQL/MySQL databases, domain registration, migrations or usage metrics; those need the hosted server at mcp.appwrite.io.",
|
|
97
98
|
"- Large results are stored as resources; read the URI returned by the tool."
|
|
98
99
|
]
|
|
@@ -103,6 +104,7 @@
|
|
|
103
104
|
"- You authenticate against the Appwrite console, which can list your organizations and projects but stores no project data itself.",
|
|
104
105
|
"- Project-scoped tools (every database type, users, teams, storage, functions, sites, messaging, firewall, advisor) need a target project: use appwrite_get_context first, then pass the selected project id as project_id to appwrite_call_tool or appwrite_call_write_tool.",
|
|
105
106
|
"- Organization-scoped console tools (e.g. creating a project or managing domains) need organization_id.",
|
|
107
|
+
"- A project can have environments, such as staging, each with its own resources and users. appwrite_get_context with project_id lists them. To act in one, pass its ID as environment_id together with the project's ID as project_id; without environment_id, calls act in the project itself.",
|
|
106
108
|
"- File/image uploads: pass a public URL as the file argument (e.g. {\"url\": \"https://...\"}) so the server downloads it directly; for very small files you may pass inline base64 ({\"filename\": ..., \"content\": ..., \"encoding\": \"base64\"}).",
|
|
107
109
|
"- Hosted HTTP returns tool results inline, including images and other binary payloads."
|
|
108
110
|
]
|
{mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/src/mcp_server_appwrite/assets/view.html
RENAMED
|
@@ -576,6 +576,7 @@ details.headers td { height: 32px; max-width: none; white-space: normal; overflo
|
|
|
576
576
|
function showArgs(extra) {
|
|
577
577
|
const args = { view: data.view };
|
|
578
578
|
if (data.projectId) args.project_id = data.projectId;
|
|
579
|
+
if (data.environmentId) args.environment_id = data.environmentId;
|
|
579
580
|
return { ...args, ...extra };
|
|
580
581
|
}
|
|
581
582
|
|
|
@@ -728,6 +729,7 @@ details.headers td { height: 32px; max-width: none; white-space: normal; overflo
|
|
|
728
729
|
const result = await callTool(WRITE_TOOL, {
|
|
729
730
|
tool_name: "tables_db_update_row",
|
|
730
731
|
project_id: data.projectId || undefined,
|
|
732
|
+
environment_id: data.environmentId || undefined,
|
|
731
733
|
confirm_write: true,
|
|
732
734
|
arguments: { database_id: data.databaseId, table_id: data.tableId, row_id: row.$id, data: values },
|
|
733
735
|
});
|
|
@@ -1320,7 +1322,8 @@ details.headers td { height: 32px; max-width: none; white-space: normal; overflo
|
|
|
1320
1322
|
render();
|
|
1321
1323
|
try {
|
|
1322
1324
|
const result = await callTool(WRITE_TOOL, {
|
|
1323
|
-
tool_name: change.tool, project_id: data.projectId || undefined,
|
|
1325
|
+
tool_name: change.tool, project_id: data.projectId || undefined, environment_id: data.environmentId || undefined,
|
|
1326
|
+
confirm_write: true, arguments: change.arguments,
|
|
1324
1327
|
});
|
|
1325
1328
|
const updated = { ...user, ...(result.structuredContent || {}) };
|
|
1326
1329
|
data.user = updated;
|
|
@@ -1515,6 +1518,7 @@ details.headers td { height: 32px; max-width: none; white-space: normal; overflo
|
|
|
1515
1518
|
await callTool(WRITE_TOOL, {
|
|
1516
1519
|
tool_name: data.kind === "site" ? "sites_update_deployment_status" : "functions_update_deployment_status",
|
|
1517
1520
|
project_id: data.projectId || undefined,
|
|
1521
|
+
environment_id: data.environmentId || undefined,
|
|
1518
1522
|
confirm_write: true,
|
|
1519
1523
|
arguments: { [data.kind === "site" ? "site_id" : "function_id"]: data.resourceId, deployment_id: data.deployment.id },
|
|
1520
1524
|
});
|
|
@@ -11,6 +11,7 @@ from appwrite_console.models.user import User
|
|
|
11
11
|
from appwrite_console.query import Query
|
|
12
12
|
|
|
13
13
|
from .constants import REDACTED_KEYS, SERVICE_PROBES
|
|
14
|
+
from .headers import environment, scoped
|
|
14
15
|
|
|
15
16
|
ContextClientFactory = Callable[[str | None, str | None], Client]
|
|
16
17
|
DEFAULT_SERVICE_PROJECT_LIMIT = 5
|
|
@@ -25,10 +26,17 @@ def get_appwrite_context(
|
|
|
25
26
|
client_factory: ContextClientFactory | None = None,
|
|
26
27
|
project_id: str | None = None,
|
|
27
28
|
organization_id: str | None = None,
|
|
29
|
+
environment_id: str | None = None,
|
|
28
30
|
include_services: bool = True,
|
|
29
31
|
sample_limit: int = 5,
|
|
30
32
|
service_detail: str = DEFAULT_SERVICE_DETAIL,
|
|
31
33
|
) -> dict[str, Any]:
|
|
34
|
+
"""Summarize what the connection can reach.
|
|
35
|
+
|
|
36
|
+
A project in focus (``project_id``, or the API key's project) lists its
|
|
37
|
+
environments, so the model can pass one as ``environment_id``. With
|
|
38
|
+
``environment_id`` (or ``APPWRITE_ENVIRONMENT_ID`` on the API-key client)
|
|
39
|
+
the project is summarized as that environment instead."""
|
|
32
40
|
sample_limit = _normalize_sample_limit(sample_limit)
|
|
33
41
|
service_detail = _normalize_service_detail(service_detail)
|
|
34
42
|
client_factory = client_factory or (
|
|
@@ -50,15 +58,22 @@ def get_appwrite_context(
|
|
|
50
58
|
project = _get_current_project(project_client, configured_project_id)
|
|
51
59
|
if project is None:
|
|
52
60
|
project = {"$id": configured_project_id}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
61
|
+
summary = _project_context(
|
|
62
|
+
project,
|
|
63
|
+
project_client,
|
|
64
|
+
include_services=include_services,
|
|
65
|
+
sample_limit=sample_limit,
|
|
66
|
+
service_detail=service_detail,
|
|
67
|
+
)
|
|
68
|
+
acting_in = environment(project_client)
|
|
69
|
+
if acting_in:
|
|
70
|
+
summary["environmentId"] = acting_in
|
|
71
|
+
else:
|
|
72
|
+
# Listed only from the project itself: an environment has none.
|
|
73
|
+
environments = _list_environments(project_client)
|
|
74
|
+
if environments is not None:
|
|
75
|
+
summary["environments"] = environments
|
|
76
|
+
context["projects"] = [summary]
|
|
62
77
|
context["serviceSummary"] = _service_summary_metadata(
|
|
63
78
|
include_services=include_services,
|
|
64
79
|
effective_include_services=include_services,
|
|
@@ -137,18 +152,36 @@ def get_appwrite_context(
|
|
|
137
152
|
if needs_project_client
|
|
138
153
|
else console_client
|
|
139
154
|
)
|
|
155
|
+
environments = None
|
|
156
|
+
requested = project_id is not None and discovered_project_id == project_id
|
|
157
|
+
if project_id and requested:
|
|
158
|
+
# Environments are listed through the project they belong to, and
|
|
159
|
+
# only for the project asked about: a request per discovered
|
|
160
|
+
# project would multiply with the account's projects.
|
|
161
|
+
parent_client = (
|
|
162
|
+
project_client
|
|
163
|
+
if needs_project_client
|
|
164
|
+
else client_factory(project_id, project_organization_id)
|
|
165
|
+
)
|
|
166
|
+
environments = _list_environments(parent_client)
|
|
167
|
+
if environment_id:
|
|
168
|
+
project_client = scoped(parent_client, environment_id=environment_id)
|
|
169
|
+
project = _get_current_project(project_client, project_id) or project
|
|
140
170
|
if project_id and project == {"$id": project_id}:
|
|
141
171
|
project = _get_current_project(project_client, project_id) or project
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
service_detail=service_detail,
|
|
150
|
-
)
|
|
172
|
+
summary = _project_context(
|
|
173
|
+
project,
|
|
174
|
+
project_client,
|
|
175
|
+
organization_id=project_organization_id,
|
|
176
|
+
include_services=effective_include_services,
|
|
177
|
+
sample_limit=sample_limit,
|
|
178
|
+
service_detail=service_detail,
|
|
151
179
|
)
|
|
180
|
+
if requested and environment_id:
|
|
181
|
+
summary["environmentId"] = environment_id
|
|
182
|
+
if environments is not None:
|
|
183
|
+
summary["environments"] = environments
|
|
184
|
+
projects.append(summary)
|
|
152
185
|
|
|
153
186
|
context["projects"] = projects
|
|
154
187
|
context["serviceSummary"] = _service_summary_metadata(
|
|
@@ -202,6 +235,28 @@ def _get_current_project(client: Client, project_id: str) -> dict[str, Any] | No
|
|
|
202
235
|
return {"$id": project_id, "error": result.error} if not result.ok else None
|
|
203
236
|
|
|
204
237
|
|
|
238
|
+
def _list_environments(client: Client) -> list[dict[str, Any]] | None:
|
|
239
|
+
"""The environments of ``client``'s project, by the ID passed as
|
|
240
|
+
``environment_id``. ``None`` when they cannot be listed: environments are
|
|
241
|
+
an Appwrite Cloud feature (self-hosted Appwrite has no such route), and
|
|
242
|
+
listing them needs the ``project.read`` scope."""
|
|
243
|
+
result = _safe_call(client, "get", "/project/environments")
|
|
244
|
+
if not result.ok or not isinstance(result.value, dict):
|
|
245
|
+
return None
|
|
246
|
+
items = result.value.get("environments")
|
|
247
|
+
if not isinstance(items, list):
|
|
248
|
+
return None
|
|
249
|
+
return [
|
|
250
|
+
{
|
|
251
|
+
key: item[key]
|
|
252
|
+
for key in ("$id", "name")
|
|
253
|
+
if isinstance(item.get(key), str) and item[key]
|
|
254
|
+
}
|
|
255
|
+
for item in items
|
|
256
|
+
if isinstance(item, dict) and isinstance(item.get("$id"), str)
|
|
257
|
+
]
|
|
258
|
+
|
|
259
|
+
|
|
205
260
|
def _accessible_resource_ids(client: Client, resource: str) -> list[dict[str, Any]]:
|
|
206
261
|
"""Fallback discovery via the OAuth2 accessible-resources endpoints
|
|
207
262
|
(``/oauth2/<project>/organizations`` / ``/oauth2/<project>/projects``).
|
|
@@ -40,6 +40,13 @@ class WriteConfirmationRequired(RuntimeError):
|
|
|
40
40
|
"""A mutating hidden tool was called without explicit confirmation."""
|
|
41
41
|
|
|
42
42
|
|
|
43
|
+
class WriteReasonRequired(WriteConfirmationRequired):
|
|
44
|
+
"""A mutating hidden tool was called without a usable ``reason``.
|
|
45
|
+
|
|
46
|
+
Like a missing ``confirm_write``, this is the model leaving out something a
|
|
47
|
+
write must carry, so it is classified and monitored the same way."""
|
|
48
|
+
|
|
49
|
+
|
|
43
50
|
class WrongCallTool(ValueError):
|
|
44
51
|
"""A hidden tool was called through the public tool for the other
|
|
45
52
|
classification: a mutation through the read-only call tool, or a read
|
{mcp_server_appwrite-2.2.2 → mcp_server_appwrite-2.3.0}/src/mcp_server_appwrite/error_monitoring.py
RENAMED
|
@@ -133,6 +133,7 @@ def capture_appwrite_exception(
|
|
|
133
133
|
classification: str,
|
|
134
134
|
project_id: str | None = None,
|
|
135
135
|
organization_id: str | None = None,
|
|
136
|
+
environment_id: str | None = None,
|
|
136
137
|
) -> bool:
|
|
137
138
|
"""Capture publishable Appwrite API failures.
|
|
138
139
|
|
|
@@ -147,6 +148,7 @@ def capture_appwrite_exception(
|
|
|
147
148
|
"appwrite.error_type": getattr(exc, "type", None),
|
|
148
149
|
"appwrite.project_id": project_id,
|
|
149
150
|
"appwrite.organization_id": organization_id,
|
|
151
|
+
"appwrite.environment_id": environment_id,
|
|
150
152
|
}
|
|
151
153
|
return capture_exception(
|
|
152
154
|
exc,
|
|
@@ -160,6 +162,7 @@ def capture_appwrite_exception(
|
|
|
160
162
|
"error_type": getattr(exc, "type", None),
|
|
161
163
|
"project_id": project_id,
|
|
162
164
|
"organization_id": organization_id,
|
|
165
|
+
"environment_id": environment_id,
|
|
163
166
|
},
|
|
164
167
|
},
|
|
165
168
|
transaction=f"appwrite.{service or 'unknown'}.{action or 'unknown'}",
|