docs: document the in-server MCP surface (RFC-003)

- server.md: add the `POST /mcp` section (transport, tool + resource catalog,
  the Cedar gate + deny-masking, the branch-scope tools/list caveat, connecting
  + the OAuth caveat) and an endpoint-inventory row; list MCP OAuth under
  "not implemented".
- rfc-003: mark Status partially-implemented (shipped in #157); refresh the
  stale stdio-package figures (the package re-synced to 0.6.1 — 16 tools, ~9
  resources) and note omnigraph-ts#7 was closed in favor of this surface.
- testing.md: record that the mcp_* tests live in server.rs (reusing its
  app_*/json_response helpers).

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Ragnor Comerford 2026-06-09 15:14:45 +02:00
parent 94bbf67b55
commit bdb1368a67
No known key found for this signature in database
3 changed files with 43 additions and 4 deletions

View file

@ -51,6 +51,7 @@ Per-graph endpoints — same body shape across modes; URLs differ:
| POST | `/branches/merge` | `/graphs/{id}/branches/merge` | bearer + `branch_merge` | merge `source → target` | `server_branch_merge` |
| GET | `/commits?branch=` | `/graphs/{id}/commits?branch=` | bearer + `read` | list | `server_commit_list` |
| GET | `/commits/{commit_id}` | `/graphs/{id}/commits/{commit_id}` | bearer + `read` | show | `server_commit_show` |
| POST | `/mcp` | `/graphs/{id}/mcp` | bearer + per-tool Cedar | MCP Streamable HTTP (JSON-RPC: `initialize`/`tools/*`/`resources/*`); `GET` → 405; not in `openapi.json` | `mcp::mcp_router` |
Server-level management endpoints (v0.6.0+):
@ -75,6 +76,40 @@ Invoke a curated, server-side stored query by **name** — the source comes from
- **Requires an explicit policy grant when auth is on.** In default-deny mode (bearer tokens but no `policy.file`), only `read` is permitted, so *every* `/queries/{name}` call returns `404` until an `invoke_query` rule is configured.
- A stored mutation cannot target a `snapshot` (`400`); a parameter type error is a structured `400` naming the parameter.
## MCP server surface (`POST /mcp`)
`POST /mcp` exposes the server as a **Model Context Protocol** endpoint over [Streamable HTTP](https://modelcontextprotocol.io/specification/2025-11-25/basic/transports) (built on `rmcp`). It projects the server's operations as MCP **tools** and **resources** for LLM clients (Claude Code, Cursor, VS Code, the Claude Messages API connector), Cedar-gated through the **same `authorize` path the REST routes use** — there is no separate MCP authorization. Single-graph mode serves it at `/mcp`; multi-graph mode nests it at `/graphs/{id}/mcp` (per-graph isolation).
**Transport.** Stateless, POST-only, `application/json` responses (no SSE, no `Mcp-Session-Id`); each request is authenticated independently by the same bearer middleware as the REST routes. `GET`/`DELETE /mcp``405`; a disallowed `Host`/`Origin``403` (DNS-rebinding guard, loopback by default); an unsupported `MCP-Protocol-Version``400`. JSON-RPC methods: `initialize`, `notifications/initialized`, `ping`, `tools/list`, `tools/call`, `resources/list`, `resources/read`. Being JSON-RPC rather than REST, it is **not** described in `openapi.json`.
**Tools.** 14 built-ins, each reusing the exact Cedar action and engine path of its REST route, plus one tool per **`mcp.expose`** stored query (named by its `tool_name`, parameters projected to JSON Schema):
| Tool(s) | Cedar action |
|---|---|
| `health` | none |
| `snapshot`, `schema_get`, `branches_list`, `commits_list`, `commits_get`, `query` | `read` |
| `mutate`, `ingest` | `change` (+ `branch_create` for a forking ingest) |
| `branches_create` / `branches_delete` / `branches_merge` | `branch_create` / `branch_delete` / `branch_merge` |
| `schema_apply` | `schema_apply` |
| `graphs_list` | `graph_list` (server-scoped; multi-graph only) |
| *stored queries* | `invoke_query` (coarse), then inner `read`/`change` |
Tools carry `readOnlyHint`/`destructiveHint` annotations from their read/mutate nature; the engine is a closed world, so `openWorldHint` is always `false`.
**Resources.** `omnigraph://schema` (`read`), `omnigraph://branches` (`read`), and `omnigraph://graphs` (`graph_list`, multi-graph only).
**Authorization (the gate).** `tools/list` and `resources/list` return **only** what the actor's policy permits — an actor with no grants sees an empty catalog (default-deny). `tools/call` enforces the same gate and **masks a denied or unknown tool identically** (`unknown tool: <name>`), so the catalog can't be probed without the grant. Stored-query tools are gated by the **coarse** `invoke_query` action (all exposed queries on the graph, or none — per-query scope is a future refinement) and then double-gated by the inner `read`/`change`. A bad/absent bearer is a transport-level `401` before any tool runs; an engine or validation error inside a tool is a `CallToolResult` with `isError: true` (so the model can self-correct), distinct from a JSON-RPC protocol error (unknown method/tool).
> **`tools/list` is best-effort for branch-scoped policies.** Visibility is evaluated against the default branch, so a branch-scoped `change` policy may list `mutate` yet deny a call on a protected branch. `tools/call` is always the authoritative gate.
**Connecting.** With a static bearer:
```
claude mcp add --transport http omnigraph https://og.example/mcp --header "Authorization: Bearer $OG_TOKEN"
```
This works with Claude Code, Cursor, VS Code, and the Claude Messages API MCP connector. **claude.ai web and ChatGPT consumer connectors require OAuth** (RFC 9728 + an authorization server), a planned additive layer: the handler already consumes a resolved actor and is agnostic to how the token was verified, so OAuth slots in without changing the tool surface.
## Adding and removing graphs (multi mode)
Runtime add/remove via API is **not** exposed in v0.6.0 — neither
@ -215,3 +250,7 @@ See [deployment.md](deployment.md) for token-source operational details.
add `tower_http::limit` if a graph-wide cap is needed.
- Pagination — none (commits/branches return everything; export streams).
- Runtime graph add/remove — edit `omnigraph.yaml` and restart.
- MCP OAuth — `/mcp` accepts a static bearer only today; OAuth (RFC 9728
protected-resource metadata + an authorization server) for claude.ai web /
ChatGPT connectors is a planned additive layer (the handler is already
auth-method-agnostic).