Skip to content

feat: advertise MCP events and list the event catalog - #131

Open
ChiragAgg5k wants to merge 5 commits into
feat/eventsfrom
feat/events-protocol
Open

ChiragAgg5k wants to merge 5 commits into
feat/eventsfrom
feat/events-protocol

Conversation

@ChiragAgg5k

@ChiragAgg5k ChiragAgg5k commented Oct 9, 2026 •

Copy link
Copy Markdown
Member

Stack

feat/events ← #131 ← #132 ← #133 ← #134 ← #135 (events/subscribe / events/unsubscribe)

Merges into feat/events; feat/events → main lands as one feature after appwrite/appwrite#14293. Each PR's diff shows only its own layer, and every layer passes the full checklist on its own.

Summary

First of four PRs for MCP Events (#127). Behind a new MCP_EVENTS flag (off by default, HTTP transport only), the server now advertises the events capability and answers events/list with the v1 catalog of six events. Nothing is subscribable yet.

What's in this PR

Covers plan steps 2 (Catalog) and the advertisement and events/list half of 3 (Protocol plumbing) from #127.

  • flags.py: events flag (--events / MCP_EVENTS) plus a flags.enabled() helper for on/off flags (1, true, yes, on).
  • events/errors.py: error codes as constants with the ChatGPT values (-32011..-32015), the SEP-3415 renumbering (-32023..-32027) noted beside each, a CallbackFailure enum for data.reason, and EventsError(MCPError) with named constructors. The SDK dispatcher turns it into the JSON-RPC error as-is.
  • events/catalog.py: the six v1 events as frozen dataclasses: arguments, Appwrite pattern templates, an IDs-only payload schema with nullable timestamps, and the status filter the ingress will apply, all declared as data. Arguments must match ^[A-Za-z0-9][A-Za-z0-9_-]{0,35}$ before they reach a pattern, so * and . can never widen a subscription. lookup(name) raises NotFound with data.kind = "event".
  • events/protocol.py: a Server.middleware that adds capabilities.events = {} and capabilities.extensions["io.modelcontextprotocol/events"] = {"listChanged": false} to server/discover (and legacy initialize), keeping any existing extensions. This works around python-sdk#3640. It also registers the events/list handler. The SDK adds resultType: "complete".
  • Wired in build_mcp_server().
  • docs/events.md (what it is, flag, catalog, error codes), a docs/flags.md entry, and a README link.

Not in this PR

  • Sealed envelope (plan step 4): separate PR, events/envelope.py.
  • SSRF-safe egress, verification and delivery (steps 6 and 7): separate PR, events/delivery.py and events/egress.py.
  • events/subscribe / events/unsubscribe, authorization, ingress route and cleanup (steps 5, 7 and 8): later PR. These methods still return -32601.
  • Telemetry, Sentry tags and caller-filtered events/list (steps 3 and 9): later.

Tests

Tests are end to end. This PR adds the shared harness and CI job the whole stack uses.

Harness (tests/e2e/support.py). Server boots the real http_app.build_app() under uvicorn on a random 127.0.0.1 port in a background thread. Client speaks raw JSON-RPC over Streamable HTTP the way ChatGPT does (MCP-Protocol-Version: 2026-07-28, Mcp-Method, params._meta), plus a 2025-11-25 initialize, and asserts on the wire (the SDK client drops the events capability). The only stub is AppwriteTokenVerifier.verify_token, which accepts one fixed bearer token, because Cloud OAuth is not reachable from CI. This is the same seam the http_app unit tests use.

CI. New E2E job (Python 3.12, uv 0.11.22, uv sync --frozen --group e2e). It needs no credentials, so it runs on every PR, forks included. AGENTS.md's pre-PR checklist and docs/development.md list the command. A new e2e dependency group holds the test-only jsonschema.

E2E flows (tests/e2e/test_events_protocol.py):

  • EventsEnabledFlow: server/discover on / and /mcp returns capabilities.events = {} and extensions["io.modelcontextprotocol/events"] = {"listChanged": false} alongside the SDK's tools / resources. Legacy initialize (2025-11-25) does too. events/list returns resultType: "complete" with no nextCursor and the 6 events in order, each with exactly name, description, delivery: ["webhook"], inputSchema and payloadSchema. Every schema passes Draft202012Validator.check_schema. The published input schemas require project_id, forbid extra keys, reject *, a.b, a leading - and 37-char IDs in every ID argument, and accept only ready / failed for status. Payload timestamps are nullable, no payload declares content or PII fields, and users.user.created carries only user_id and created_at. An unknown cursor returns HTTP 400 with -32602. events/subscribe and events/unsubscribe return -32601.
  • EventsDisabledFlow: with MCP_EVENTS unset, 0 or false, neither capability shape appears on server/discover or initialize, and events/list returns -32601.

Unit tests kept, and why they are not e2e:

  • tests/unit/test_events_catalog.py (2 tests): server-side Event.patterns rejects wildcards, dots, leading _ / -, spaces and over-long IDs for every ID argument, and lookup of an unknown event raises NotFound with data.kind = "event". Nothing calls either over HTTP until events/subscribe (PR 5). Replace them with the subscribe e2e flow there.
  • tests/unit/test_events_protocol.py (1 test): stdio never serves events, even with the flag on. The stdio transport validates an API key against a live Appwrite project at startup, so it cannot run in the credential-free e2e suite.

All other catalog and protocol unit tests were removed because the flows above cover them. The EventsError constructors for -32012..-32015 and their data shapes have no caller yet; PR 5's subscribe e2e will cover them.

Verification

Run locally on Python 3.12.8, lockfile written with uv 0.11.22 (the CI version):

  • uv run --group dev ruff check src tests: pass
  • uv run --group dev black --check src tests: pass
  • uv run --group dev pyright: 0 errors
  • uv run python -m unittest discover -s tests/unit: 268 tests OK
  • uv run --group e2e python -m unittest discover -s tests/e2e: 2 tests OK (about 4 s)
  • docker build -t appwrite-mcp:e2e .: builds

Six v1 events, each declared as data: arguments, Appwrite pattern templates, an IDs-only payload schema and the status filter the ingress will apply. Arguments are checked against a dot- and wildcard-free Appwrite ID pattern before they are spliced into an Appwrite event pattern, so a subscription can never widen past the named resource. Error codes sit behind constants because SEP-3415 renumbers them. The MCP_EVENTS flag gates the feature (off by default).
The SDK drops unknown capability keys (python-sdk#3640), so a Server.middleware adds capabilities.events for ChatGPT and the SEP-3415 extension entry to the discover and initialize results. Only on the HTTP transport with MCP_EVENTS on. Tests assert on raw HTTP through the hosted app because the SDK client models drop the key too.
@hansi-codes

hansi-codes Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

🟢 Tier S · Ready to merge

Adds an HTTP-only MCP Events feature flag, capability advertisement, an events/list handler, and a declarative six-event catalog with schemas and error types. The pull request also documents the feature and adds hosted-server end-to-end testing over real HTTP, including CI integration.

Latest changes: The latest commits add a credential-free uvicorn/HTTP end-to-end harness and CI job, move hosted protocol coverage into that suite, and update testing guidance.

Verdict New comments Fixed Still open
✅ Approved 0 0 0
📂 Walkthrough · 18
File Change
.github/workflows/ci.yml Adds a CI job to run the hosted-server end-to-end suite.
AGENTS.md Documents the end-to-end suite and updated local test workflow.
README.md Links to the MCP Events documentation.
docs/development.md Documents unit, end-to-end, and integration test commands.
docs/events.md Documents the event feature, catalog, errors, and end-to-end tests.
docs/flags.md Documents the MCP_EVENTS flag and usage.
pyproject.toml Adds the end-to-end test dependency group.
uv.lock Locks dependencies for the end-to-end test group.
src/mcp_server_appwrite/events/__init__.py Introduces the events package.
src/mcp_server_appwrite/events/catalog.py Defines event schemas, argument validation, patterns, and status filters.
src/mcp_server_appwrite/events/errors.py Defines MCP Events error codes and constructors.
src/mcp_server_appwrite/events/protocol.py Advertises event capabilities and serves events/list.
src/mcp_server_appwrite/flags.py Adds the events flag and on/off value helper.
src/mcp_server_appwrite/server.py Registers event protocol support for enabled HTTP servers.
tests/e2e/support.py Adds a real-HTTP hosted-server harness with a stubbed OAuth verifier.
tests/e2e/test_events_protocol.py Tests event discovery and listing against the hosted server over HTTP.
tests/unit/test_events_catalog.py Retains focused unit coverage for event argument and lookup logic.
tests/unit/test_events_protocol.py Retains a unit check that events are disabled for stdio.

Reviewed the commits since 101d8de · Details · Comment @hansi-codes review to re-run, or mention @hansi-codes with a question.

@hansi-codes hansi-codes Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Tier S · Looks good to merge. Summary

Boot the real Starlette app under uvicorn and drive it over HTTP like
ChatGPT, so the wire is what is asserted. Unit tests the e2e flow now
covers are removed; only what HTTP cannot reach before subscribe stays.
It needs no credentials, so unlike integration it also runs for forks.
@ChiragAgg5k
ChiragAgg5k changed the base branch from main to feat/events October 9, 2026 13:53

@hansi-codes hansi-codes Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Tier A · Looks good to merge. Summary

Comment thread tests/unit/test_events_catalog.py
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant