Folds in v0.7.2 (release #301) + RFC-013 Phase 7 (graph lineage in __manifest, internal schema v3→v4 migration #299; WriteTxn #298; recovery convergence #296) under the MCP branch. Conflict resolutions (2 files): - crates/omnigraph-server/Cargo.toml: take main's 0.7.2 path-dep constraints; keep our omnigraph-mcp dep (bumped to 0.7.2). - docs/releases/v0.8.0.md (add/add): both branches drafted v0.8.0 notes for the same next minor — combined them. v0.8.0 now documents BOTH the MCP surface (ours) and main's __manifest lineage fold + the breaking internal-schema-v4 upgrade-order requirement (kept prominent under Upgrade notes). Corrected our 'no breaking changes / on-disk format unchanged' line, which the v4 migration makes false. Coherence: omnigraph-mcp [package] + Cargo.lock bumped 0.7.1→0.7.2; openapi.json auto-merged to info.version 0.7.2 (no API-surface drift from the incoming engine-internal commits). Verification deferred to CI (no local rebuild).
8.7 KiB
Omnigraph v0.8.0
v0.8.0 has two headline changes:
- Every served graph becomes an MCP (Model Context Protocol) server — an
MCP-capable agent (Claude Code/Desktop, Cursor, the OpenAI Responses
mcptool, and others) can connect to a graph and operate it directly. The surface adds no new capability and no new business logic; every tool delegates to the same engine/handler path the REST routes use and is gated by the same Cedar policy. It is additive. - Graph commit lineage moves into
__manifest(RFC-013 Phase 7), folded into the publish CAS, via a one-time on-disk migration (internal schema v3 → v4). This is the first internal-schema change since v0.4.0 and carries an upgrade-order requirement — read the upgrade notes before rolling it out.
MCP surface (POST /graphs/{id}/mcp)
An MCP-capable agent can connect to a graph and run reads and mutations, load data, manage branches, browse commits, read the schema, and invoke the graph's curated stored queries.
- One MCP endpoint per served graph, mounted automatically by the cluster
server — no separate flag. It is a stateless Streamable-HTTP transport: a
single
application/jsonJSON-RPC response per call, no SSE, no session id. - Built-in tools cover the operational surface:
graph_query,graph_mutate,graph_load,graph_snapshot,schema_get,branch_list,branch_create/branch_delete/branch_merge,commit_list/commit_get,schema_apply(disabled with a409under cluster-backed serving — evolve viacluster applyand restart), and agraph_healthliveness probe. - Stored queries as tools. A graph's stored-query registry is projected as
tools, in one of two modes chosen automatically from the exposed-query count:
per_query(each exposed query is its own typed tool) below a threshold, or astored_query_list+stored_query_rundiscovery/execute pair at or above it, so a client's tool count stays bounded. - Resources. The graph schema (
omnigraph://schema) and branch list (omnigraph://branches) are exposed as MCP resources. - Structured output. Tool results carry
structuredContent(the same typed result envelopes as the REST routes) plus a text mirror.
Authorization parity with REST
- Every tool and resource resolves the actor from the bearer token and passes the same Cedar gate as the equivalent REST route; the call-time gate is authoritative.
tools/listis a relaxation of the per-call gate: a tool the actor could invoke on some branch is listed, so listing never hides a tool you can call, while an actor with no grant for an action still does not see its tools. Under the common "protectmain, write feature branches" policy,graph_mutateis listed for an actor who can write unprotected branches.- Stored queries sit behind the coarse
invoke_querygate (a stored mutation is additionallychange-gated); for a caller withoutinvoke_query, a stored tool masks as an unknown tool so the catalog can't be probed. Anexpose: falsequery is unreachable on the MCP surface entirely (not listed, not runnable by name) while remaining HTTP/service callable.
Authoring stored queries as MCP tools
.gq gains the controls to shape how a stored query appears as an MCP tool, all
carried in the query source:
@instruction("…")reaches agents. The query's@instructionannotation is folded into the MCP tool description (after@description), so the how/when-to-use guidance shows up intools/list— previously it surfaced only in the REST catalog.- Per-parameter docs. A leading
@description("…")on a parameter (@description("the user's slug") $slug: String) is surfaced into the parameter's JSON-Schemadescriptionin both the MCP tool input schema and theGET /queriescatalog. @mcp(tool_name: "…", expose: <bool>). A dedicated MCP-presentation annotation:tool_nameoverrides the tool id (unique-checked at boot, can't shadow a built-in);expose: falsehides the query from the agent tool surface (tools/list/stored_query_list/stored_query_run) while keeping it HTTP/service-callable by name.exposeis presentation only — Cedarinvoke_queryremains the authority for who may call a query.
Transport hardening
- Fail-closed Host / Origin posture, derived from the bind address at
startup. A loopback bind accepts the full loopback
Hostset (127.0.0.1,::1,localhost) regardless of which IP stack it bound; a non-loopback bind rejects an unexpected browserOriginand restrictsHostto the configured public hosts. - The
MCP-Protocol-Versionheader is validated on follow-up requests (an unsupported version is a400);initializenegotiates the version in its body and is exempt by design.
Graph lineage now lives in __manifest (internal schema v4)
The graph commit DAG (commits, parents, merge parents, per-branch heads, and the
authoring actor) is now stored in __manifest as graph_commit / graph_head
rows, written in the same commit (CAS) as the table-version rows of a graph
publish. Previously the lineage lived in a separate _graph_commits.lance
dataset written after the manifest commit, leaving a narrow window where a crash
could land a manifest version with no matching lineage row. Folding the lineage
into the publish closes that gap by construction: a graph commit and its lineage
now land atomically at one manifest version. The in-memory commit graph is a
projection of those manifest rows; _graph_commits.lance is retained only as a
carrier for Lance branch refs and no longer receives commit rows.
This bumps the __manifest internal schema stamp from v3 to v4.
Existing graphs migrate seamlessly on first write
A graph created by an earlier binary (internal schema v3) keeps its lineage in
_graph_commits.lance with none in __manifest. On the first read-write
open, Omnigraph backfills that lineage into __manifest (the migrate_v3_to_v4
internal-schema step) and bumps the stamp to v4. The migration:
- is per-branch — each branch backfills on its first write;
- is idempotent and crash-safe — the stamp bump is the last step, and the backfill is keyed on the commit id, so a crash mid-migration re-runs harmlessly on the next open;
- preserves all data — every commit, parent, merge parent, actor, and head is carried over; commit ids are stable, so existing references still resolve.
No data is lost and no operator action is required beyond upgrading the binary.
Before its first write migrates the graph, a read-only open of a v3 graph
(e.g. omnigraph commit list, NDJSON export) still reads correct history via a
transitional fallback that sources the commit DAG from _graph_commits.lance —
read-only opens never write, so they never migrate, but they never show an empty
history either.
Upgrade notes
- Breaking: internal schema v4 — upgrade writer (and reader) binaries first.
Internal schema v4 is a hard version gate. Once a graph has been opened for
write by a v0.8.0 binary, its
__manifestis stamped v4, and an older binary will refuse to open it — read-write and read-only — with anupgrade omnigraph before opening this grapherror rather than silently misreading the new lineage. This is the standard forward-version protection (same shape as the v1→v2 / v2→v3 steps), now enforced on the read-only path too. Upgrade every writer (and reader) binary that touches a graph to v0.8.0 before, or together with, the first write under the new version. A mixed fleet where an old binary still writes the same graph is unsupported, as with any internal-schema bump. GET /graphs/{id}/queriesis nowinvoke_query-gated (wasread). The stored-query catalog uses the same authority as invocation and the MCPtools/listsurface, so discovery and invocation agree ("see the menu iff you can order from it"). A caller with onlyread(and noinvoke_query) now gets403instead of a listing; in default-deny mode the endpoint returns403until aninvoke_queryrule is configured. This is the one observable REST behavior change in this release.- The MCP endpoint is additive. Apart from the
GET /queriesgate change and the v4 on-disk migration above, the REST surface, CLI, and cluster config are unchanged. - Pointing an agent at a graph: configure your MCP client with the URL
https://<host>/graphs/<id>/mcpand the same bearer token you use for REST. See docs/user/operations/mcp.md for the connect recipe, the tool catalog, projection modes, and the Host/Origin and protocol-version contracts. Design and rationale: RFC-003.