omnigraph/docs/releases/v0.8.0.md
Ragnor Comerford 4d4c2164de
Merge branch 'main' into ragnorc/omnigraph-mcp-crate
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).
2026-06-25 15:53:53 +02:00

8.7 KiB

Omnigraph v0.8.0

v0.8.0 has two headline changes:

  1. Every served graph becomes an MCP (Model Context Protocol) server — an MCP-capable agent (Claude Code/Desktop, Cursor, the OpenAI Responses mcp tool, 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.
  2. 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/json JSON-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 a 409 under cluster-backed serving — evolve via cluster apply and restart), and a graph_health liveness 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 a stored_query_list + stored_query_run discovery/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/list is 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 "protect main, write feature branches" policy, graph_mutate is listed for an actor who can write unprotected branches.
  • Stored queries sit behind the coarse invoke_query gate (a stored mutation is additionally change-gated); for a caller without invoke_query, a stored tool masks as an unknown tool so the catalog can't be probed. An expose: false query 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 @instruction annotation is folded into the MCP tool description (after @description), so the how/when-to-use guidance shows up in tools/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-Schema description in both the MCP tool input schema and the GET /queries catalog.
  • @mcp(tool_name: "…", expose: <bool>). A dedicated MCP-presentation annotation: tool_name overrides the tool id (unique-checked at boot, can't shadow a built-in); expose: false hides the query from the agent tool surface (tools/list / stored_query_list / stored_query_run) while keeping it HTTP/service-callable by name. expose is presentation only — Cedar invoke_query remains 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 Host set (127.0.0.1, ::1, localhost) regardless of which IP stack it bound; a non-loopback bind rejects an unexpected browser Origin and restricts Host to the configured public hosts.
  • The MCP-Protocol-Version header is validated on follow-up requests (an unsupported version is a 400); initialize negotiates 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 __manifest is stamped v4, and an older binary will refuse to open it — read-write and read-only — with an upgrade omnigraph before opening this graph error 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}/queries is now invoke_query-gated (was read). The stored-query catalog uses the same authority as invocation and the MCP tools/list surface, so discovery and invocation agree ("see the menu iff you can order from it"). A caller with only read (and no invoke_query) now gets 403 instead of a listing; in default-deny mode the endpoint returns 403 until an invoke_query rule is configured. This is the one observable REST behavior change in this release.
  • The MCP endpoint is additive. Apart from the GET /queries gate 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>/mcp and 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.