Repository navigation
feat: advertise MCP events and list the event catalog - #131
Open
ChiragAgg5k wants to merge 5 commits into
Open
ChiragAgg5k wants to merge 5 commits into
ChiragAgg5k wants to merge 5 commits into
Conversation
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.
🟢 Tier S · Ready to mergeAdds an HTTP-only MCP Events feature flag, capability advertisement, an 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.
📂 Walkthrough · 18
Reviewed the commits since |
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.
This was referenced Oct 9, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stack
feat/events← #131 ← #132 ← #133 ← #134 ← #135 (events/subscribe/events/unsubscribe)Merges into
feat/events;feat/events→mainlands 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_EVENTSflag (off by default, HTTP transport only), the server now advertises the events capability and answersevents/listwith 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/listhalf of 3 (Protocol plumbing) from #127.flags.py:eventsflag (--events/MCP_EVENTS) plus aflags.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, aCallbackFailureenum fordata.reason, andEventsError(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 withdata.kind = "event".events/protocol.py: aServer.middlewarethat addscapabilities.events = {}andcapabilities.extensions["io.modelcontextprotocol/events"] = {"listChanged": false}toserver/discover(and legacyinitialize), keeping any existing extensions. This works around python-sdk#3640. It also registers theevents/listhandler. The SDK addsresultType: "complete".build_mcp_server().docs/events.md(what it is, flag, catalog, error codes), adocs/flags.mdentry, and a README link.Not in this PR
events/envelope.py.events/delivery.pyandevents/egress.py.events/subscribe/events/unsubscribe, authorization, ingress route and cleanup (steps 5, 7 and 8): later PR. These methods still return-32601.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).Serverboots the realhttp_app.build_app()under uvicorn on a random127.0.0.1port in a background thread.Clientspeaks raw JSON-RPC over Streamable HTTP the way ChatGPT does (MCP-Protocol-Version: 2026-07-28,Mcp-Method,params._meta), plus a 2025-11-25initialize, and asserts on the wire (the SDK client drops theeventscapability). The only stub isAppwriteTokenVerifier.verify_token, which accepts one fixed bearer token, because Cloud OAuth is not reachable from CI. This is the same seam thehttp_appunit tests use.CI. New
E2Ejob (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 anddocs/development.mdlist the command. A newe2edependency group holds the test-onlyjsonschema.E2E flows (
tests/e2e/test_events_protocol.py):EventsEnabledFlow:server/discoveron/and/mcpreturnscapabilities.events = {}andextensions["io.modelcontextprotocol/events"] = {"listChanged": false}alongside the SDK'stools/resources. Legacyinitialize(2025-11-25) does too.events/listreturnsresultType: "complete"with nonextCursorand the 6 events in order, each with exactlyname,description,delivery: ["webhook"],inputSchemaandpayloadSchema. Every schema passesDraft202012Validator.check_schema. The published input schemas requireproject_id, forbid extra keys, reject*,a.b, a leading-and 37-char IDs in every ID argument, and accept onlyready/failedforstatus. Payload timestamps are nullable, no payload declares content or PII fields, andusers.user.createdcarries onlyuser_idandcreated_at. An unknown cursor returns HTTP 400 with-32602.events/subscribeandevents/unsubscribereturn-32601.EventsDisabledFlow: withMCP_EVENTSunset,0orfalse, neither capability shape appears onserver/discoverorinitialize, andevents/listreturns-32601.Unit tests kept, and why they are not e2e:
tests/unit/test_events_catalog.py(2 tests): server-sideEvent.patternsrejects wildcards, dots, leading_/-, spaces and over-long IDs for every ID argument, andlookupof an unknown event raises NotFound withdata.kind = "event". Nothing calls either over HTTP untilevents/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
EventsErrorconstructors for-32012..-32015and theirdatashapes 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: passuv run --group dev black --check src tests: passuv run --group dev pyright: 0 errorsuv run python -m unittest discover -s tests/unit: 268 tests OKuv run --group e2e python -m unittest discover -s tests/e2e: 2 tests OK (about 4 s)docker build -t appwrite-mcp:e2e .: builds