Commit graph

2 commits

Author SHA1 Message Date
Ragnor Comerford
8dab7e2e61
test(mcp): harden symptomatic MCP fixes to construction-enforced
Four guards/refactors that convert previously convention-enforced MCP
invariants into ones a future edit can't silently break:

- H1 (loopback Host set): standalone.rs surface-guard asserts our loopback
  allow-list is a superset of rmcp's own default set, so an rmcp bump that
  adds a loopback form turns red instead of 403'ing that client. Pins the
  ::1 regression by construction rather than by a hand-kept literal list.

- H2 (one stored-query gate): extract a single invoke_query_request() in
  handlers.rs used by GET /queries, POST /queries/{name}, and (imported) the
  MCP tools/list + tools/call stored paths. REST and MCP can no longer drift
  on which Cedar action governs the catalog. Deletes mcp.rs's local copy.

- H3 (expose chokepoint): tests/mcp.rs source-walk guard bans .lookup( and
  registry.iter( in mcp.rs, so the agent surface can only reach stored
  queries through exposed()/exposed_by_name() — an @mcp(expose:false) query
  cannot leak back into tools/list via the expose-ignoring registry methods.

- H4 (list-gate relaxation lower-bound): a permit-all actor must see every
  built-in tool, pinning the other end of the list-gate fix (no callable
  tool may be hidden from tools/list).

cargo test -p omnigraph-mcp -p omnigraph-server green (the lone schema_routes
flake was disk-pressure during the clean build; passes in isolation).
2026-06-20 17:09:27 +02:00
Ragnor Comerford
bcd0d9c867
feat(mcp): MCP server surface — Streamable-HTTP transport + tool/resource projection (RFC-003)
Add the `omnigraph-mcp` crate (stateless Streamable-HTTP transport, `McpBackend`
seam, fail-closed Host/Origin policy) and the server backend projecting built-in
operations and the per-graph stored-query registry as MCP tools + resources over
`POST /graphs/{id}/mcp`. Every tool delegates to the same engine/handler
functions the REST routes use and is gated by the same Cedar `authorize` path;
reads/writes carry structured output.

Includes three correctness fixes from review + live testing:

- tools/list is a faithful relaxation of the per-call gate: a built-in whose
  authorization depends on a caller-chosen branch is shown iff the actor could
  invoke it on some branch, via PolicyEngine::permits_on_any_branch (capability
  probe through the same Cedar authorizer). A fabricated-`main` probe wrongly
  hid graph_mutate under the canonical "protect main, write unprotected" policy.
- The stored-query surface honors mode + `expose` on call as well as on list:
  resolve_stored_tool is the single membership test, so the meta pair
  (stored_query_list/stored_query_run) is callable only in `meta` mode and
  stored_query_run resolves exposed-only. An `expose:false` query is unreachable
  by name on the agent surface (it stays HTTP/service-callable).
- The loopback Host allow-list is the full set [127.0.0.1, ::1, localhost]
  (matches rmcp's default), so an IPv6 loopback `Host: [::1]` is accepted
  regardless of which stack the server bound.

The protocol-version contract is documented (initialize negotiates the version
in its body, so the MCP-Protocol-Version header is validated on non-init
requests only) and pinned by a test.

Tests: omnigraph-mcp/tests/standalone.rs, omnigraph-server/tests/mcp.rs,
omnigraph-policy permits_on_any_branch unit test, omnigraph-api-types schema
projection. Full workspace gate green.
2026-06-17 14:00:52 +02:00