Guidance for AI agents and human contributors working in this repository.
mcp-server-appwrite is a Model Context Protocol
server for Appwrite. It exposes Appwrite's API to MCP clients as a small set of
operator-style tools, supporting two deployments from one codebase:
- Cloud (hosted HTTP): a Starlette ASGI app that acts as an OAuth 2.1
Resource Server. It validates the client's bearer token and forwards it to the
Appwrite REST API. Served primarily at
mcp.appwrite.io/, with/mcpalso available as a conventional alias. - Self-hosted (
stdio): runs locally and authenticates with a project API key (APPWRITE_PROJECT_ID,APPWRITE_API_KEY,APPWRITE_ENDPOINT).
Python ≥ 3.12, packaged with hatchling, managed with uv.
Source lives in src/mcp_server_appwrite/:
| File | Responsibility |
|---|---|
__main__.py / server.py |
Entry point, CLI args, transport selection (--transport stdio|http), service registration, low-level MCP server. |
http_app.py |
Hosted Streamable-HTTP transport: / plus the /mcp alias, RFC 9728 protected-resource metadata, /healthz. |
auth.py |
OAuth 2.1 resource-server layer — bearer-token validation against the project's Appwrite authorization server. |
flags.py |
Central registry of tester/testing flags (CLI arg + env var per flag). Enabling and testing them is documented in docs/flags.md. |
service.py |
Service base class: introspects an Appwrite SDK service and turns its methods into MCP tool definitions. |
tool_manager.py |
Registry of all services and their generated tools. |
operator.py |
The compact "operator" surface — appwrite_search_tools, appwrite_call_tool, result/resource storage, write confirmation. |
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). |
context.py |
appwrite_get_context — workspace summary (project, services, account/org for OAuth). |
docs_search.py |
In-process semantic docs search (appwrite_search_docs) over a prebuilt index. |
docs_source.py |
Fetches the published docs (/docs/llms.txt + per-page .md exports on appwrite.io) and chunks them for the index build. |
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. |
events/ |
MCP Events (behind flags.EVENTS, HTTP only): catalog.py (event catalog), protocol.py (capability, events/list and the subscribe methods), subscriptions.py + webhooks.py (events/subscribe / events/unsubscribe: one managed Appwrite webhook per subscription), envelope.py (subscription state sealed with the mcp-event-subscriptions JWE profile), egress.py + delivery.py (SSRF-safe signed delivery), projection.py + ingress.py (Appwrite webhook ingress at /appwrite/webhooks/{id}), streams.py + bus.py (push delivery: events/stream, behind flags.EVENTS_PUSH, with events routed between replicas over Redis pub/sub). See docs/events.md. |
telemetry.py |
OpenTelemetry metrics layer (OTLP/HTTP). No-op unless an OTLP endpoint is configured and the transport is http. |
data/ |
Committed docs index artifact (docs_index.npz: vectors, chunk map, chunk hashes, embedded meta.json), shipped in the wheel/image. |
scripts/build_docs_index.py rebuilds the docs index (requires OPENAI_API_KEY).
telemetry.py emits OpenTelemetry metrics (prefixed mcp.) over OTLP/HTTP. It is a
no-op unless the transport is http and OTEL_EXPORTER_OTLP_ENDPOINT is set, so
stdio and unconfigured servers stay silent. In the cluster that endpoint is the
Alloy collector, which adds the deployment.* labels and forwards upstream — the app
needs no credentials. Dashboards live in the dashboards repo under MCP/.
The server boots in a compact workflow: the client sees up to 4 tools
(appwrite_get_context, appwrite_search_tools, appwrite_call_tool, and
optionally appwrite_search_docs), while the full OAuth Appwrite catalog (41
services) stays internal and is searched at runtime. API-key stdio uses a filtered
27-service catalog. Mutating hidden tools require
confirm_write=true. Large outputs are stored as MCP resources and returned as a
preview + resource URI.
# Install uv, then sync deps
uv sync # runtime deps
uv sync --group dev # + black, ruff (lint/format)
uv sync --group e2e # + e2e-test deps
uv sync --extra integration # + integration-test deps
# Run hosted HTTP transport
MCP_PUBLIC_URL=http://localhost:8000 APPWRITE_ENDPOINT=https://cloud.appwrite.io/v1 \
uv run mcp-server-appwrite --transport http
# Run self-hosted stdio transport
APPWRITE_ENDPOINT=http://localhost:9501/v1 \
APPWRITE_PROJECT_ID=<id> APPWRITE_API_KEY=<key> \
uv run mcp-server-appwrite
# Or via Docker (hosted HTTP/OAuth)
docker compose up --build # compose.yaml; endpoint at http://localhost:8000/Run these locally before opening a PR. They mirror the CI workflow
(.github/workflows/ci.yml), which runs on every pull request and on pushes to
main. All five jobs must pass.
-
Lint (
lintjob)uv sync --group dev uv run --group dev ruff check src tests
Ruff config:
target-version = py312, rulesE,F,W,I(import sorting), withE501(line length) delegated to black. -
Format (
lintjob)uv run --group dev black --check src tests
Run
uv run --group dev black src tests(without--check) to auto-fix. -
Type check (
lintjob)uv run --group dev pyright
Pyright config lives in
pyproject.toml([tool.pyright]): basic mode oversrc/, Python 3.12, resolving against the project.venv. -
Unit tests (
unitjob)uv sync uv run python -m unittest discover -s tests/unit -v
Fast, no external services or credentials required.
-
E2E tests (
e2ejob)uv sync --group e2e uv run --group e2e python -m unittest discover -s tests/e2e -v
Boots the real hosted HTTP app on a random localhost port and drives it over real HTTP the way an MCP client does. No credentials: only the OAuth token verifier is stubbed. Runs on every PR, forks included. The push delivery flows start a real
dragonfly(CI) orredis-server, which must be onPATH(brew install redisorapt-get install redis-server). Prefer adding assertions to an existing flow over adding unit tests; add a unit test only for what no e2e flow can reach, and say why in its module docstring. -
Docker build (
dockerjob)docker build -t appwrite-mcp:ci .The hosted HTTP image must build cleanly.
-
Integration tests (
integrationjob) — CI runs these only for pushes and for PRs from branches on the same repo (not forks). They create and delete real Appwrite resources, so they need live credentials and are skipped when absent:uv sync --extra integration APPWRITE_PROJECT_ID=<id> APPWRITE_API_KEY=<key> APPWRITE_ENDPOINT=<url> \ uv run --extra integration python -m unittest discover -s tests/integration -v
CI pins Python 3.12 and uv 0.11.22. Match these locally if you hit
version-specific differences.
Rebuilding src/mcp_server_appwrite/data/ requires OPENAI_API_KEY. Re-run the
build script and commit the refreshed artifact:
OPENAI_API_KEY=sk-... uv run python scripts/build_docs_index.pypublish.yml— package publishing.staging.yml/production.yml— deployment (publishes images to Docker Hub).
These are not gated on PRs the way ci.yml is, but be mindful when touching the
Dockerfile, pyproject.toml version, or deployment config.
Release versions come from Git tags. Publish GitHub releases with tags in the
form vMAJOR.MINOR.PATCH (for example, v0.8.8). Python package builds derive
their version from the tag via Hatch VCS, and server.json is generated from
server.template.json during the publish workflow before sending metadata to the
MCP Registry.
Do not hand-edit a version in pyproject.toml, constants.py, or server.json.
For local registry metadata checks, render the generated file explicitly:
uv run python scripts/render_server_json.py 0.8.8- Keep the exposed tool surface small — new Appwrite capabilities should flow through the operator/catalog mechanism, not become top-level tools.
- New SDK services are registered automatically; you generally don't hand-write tool definitions.
- Match existing style: black formatting, ruff-clean imports, type hints, module
docstrings explaining intent (see
auth.py,http_app.py,docs_search.py). - Cover hosted-server behavior with end-to-end flows under
tests/e2e/(shared harness intests/e2e/support.py). Add unit tests undertests/unit/only for what no e2e flow can reach. Add integration coverage undertests/integration/when touching real Appwrite API behavior.