Repository navigation
feat: advertise MCP events and list the event catalog - #131
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 A · Mergeable after minor fixes
Adds 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.
Fix with agent prompt### Issue 1
tests/unit/test_events_catalog.py:28-30
**Keep unit coverage for unreachable catalog logic**
This rewrite removes the `StatusFilter.matches` cases and the `Event.validate`/published-schema parity matrix, while the end-to-end suite only checks the published schemas and cannot exercise those methods until subscribe is wired. Regressions in status filtering or validation consistency would therefore pass CI; these are the catalog behaviors the repository's unit-test guidance reserves unit tests for.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.📂 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.
| class ArgumentValidationTests(unittest.TestCase): | ||
| def test_ids_can_never_widen_a_pattern(self): | ||
| for event in EVENTS: |
There was a problem hiding this comment.
Keep unit coverage for unreachable catalog logic
This rewrite removes the StatusFilter.matches cases and the Event.validate/published-schema parity matrix, while the end-to-end suite only checks the published schemas and cannot exercise those methods until subscribe is wired. Regressions in status filtering or validation consistency would therefore pass CI; these are the catalog behaviors the repository's unit-test guidance reserves unit tests for.
Prompt To Fix With AI
This is a comment left during a code review.
Path: tests/unit/test_events_catalog.py
Line: 28-30
Comment:
**Keep unit coverage for unreachable catalog logic**
This rewrite removes the `StatusFilter.matches` cases and the `Event.validate`/published-schema parity matrix, while the end-to-end suite only checks the published schemas and cannot exercise those methods until subscribe is wired. Regressions in status filtering or validation consistency would therefore pass CI; these are the catalog behaviors the repository's unit-test guidance reserves unit tests for.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.🟡 Minor · testing · Reply if this doesn't apply.
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