Omnigraph
Operations

MCP

ChatGPT, Cursor and other MCP clients can work with a graph with a URL and the

omnigraph-server serves the Model Context Protocol at /mcp, so Claude, ChatGPT, Cursor and other MCP clients can work with a graph with a URL and the caller's own credential. MCP shares the server's listener and lifecycle; no separate service is needed.

Every tool calls the same handler as its HTTP route, under the same actor and the same policy action. A tool appearing in the list is not permission to use it: a call the policy refuses returns the route's refusal.

Enable it

/mcp is mounted whenever the server has a bearer credential source: static tokens, signed data tokens, or an OIDC resource identity (--oidc-identity-trust). A server without credentials has no MCP.

Credentials/mcpOAuth discoveryChallenge on a 401
Static or signed data tokensmountednoneBearer
OIDC resource identity, alone or with tokensmountedservedBearer resource_metadata="<url>"

With static or signed tokens, configure the client with the URL and an Authorization: Bearer <token> header; Claude Code and Cursor support this directly. With OIDC, the resource identifier in the trust file is the URL users enter in their MCP client, for example https://graphs.example.com/mcp, and the client signs in through OAuth. With an identifier naming the service root, clients that accept a path prefix can also sign in from other routes; see Sign-in and discovery. A client that can only start a command uses omnigraph mcp.

The endpoint is stateless Streamable HTTP built on the official Rust MCP SDK: each request is a POST that carries its own bearer credential and gets a JSON response. There is no MCP session, and a successful initialize never admits a later unauthenticated request.

Upgrade tool schemas

After upgrading the server, reconnect clients and refresh cached tool schemas with tools/list. The access tool now returns actor_id instead of actor; clients with fixed response types must update those types before reconnecting. Its output matches the HTTP access report.

MCP does not use the Omnigraph-Http-Api header, so an old MCP client is not automatically refused. MCP protocol negotiation is unchanged, and the old field has no alias.

Sign-in and discovery

A request without a valid credential is refused with 401 before its body is read, with the same challenge as every other route. With OIDC, the challenge names the protected-resource metadata:

WWW-Authenticate: Bearer resource_metadata="https://graphs.example.com/.well-known/oauth-protected-resource/mcp"

When a bearer credential was presented and refused (expired, wrong audience, unknown subject), the challenge also carries error="invalid_token", which tells a client to refresh or sign in again. Without OIDC the challenge is Bearer, which advertises no sign-in flow.

The server has one resource identifier, so every route's challenge names the same metadata. RFC 9728 §3.3 has a client that followed a challenge require the metadata's resource to equal the URL it requested, so a strict client uses the metadata only on the URL the identifier names. Some clients accept an identifier whose path is a prefix of that URL, as the MCP TypeScript SDK does (checkResourceAllowed). For them, an identifier naming the service root, such as https://graphs.example.com, covers every route, and one naming https://graphs.example.com/mcp covers only this endpoint. Name the service root when clients that accept a prefix should discover sign-in from any route.

The server answers the metadata both at /.well-known/oauth-protected-resource and at the advertised path: the well-known prefix followed by the resource identifier's path (/mcp above). Any other path below the prefix is 404. No proxy rewrite is needed.

Host and Origin

The SDK refuses a request whose Host is not allowed with 403. Allowed hosts are localhost, 127.0.0.1, ::1, the OIDC resource identifier's authority, and every --mcp-allowed-host. A server reached by another name, for example behind a load balancer without OIDC, needs that name listed.

With OIDC, a browser Origin must be the resource identifier's origin. Without OIDC there is no origin to allow, so a request carrying any Origin is refused with 403. Native clients send no Origin.

Connect a stdio agent

An agent that starts its MCP servers as child processes, such as Claude Code or Codex in a terminal, connects to an existing server through the CLI:

{"command": "omnigraph", "args": ["mcp", "--target", "staging"]}

omnigraph mcp --target NAME [--graph G] [--write] relays each JSON-RPC message on stdin to the server target's /mcp, one POST each, and writes each answer as one line on stdout; diagnostics go to stderr. Tools, their logic and their refusals stay the server's.

  • Credential. Requests carry the token of the target's auth, acquired as every served command acquires it (authentication providers); it never appears in the agent's configuration or output. A target whose auth is provider: anonymous sends none. --as ACTOR sends Omnigraph-If-Actor with every request, so another actor's token is refused with 412 actor_mismatch.
  • One graph. --graph, else the target's default_graph; without either the command refuses before any request. A tool call that omits graph gets it, a call or resource URI naming another graph is refused with a JSON-RPC error, and graphs and omnigraph://graphs list only this graph. Run one bridge per graph.
  • Two consents for writes. The server lists write tools only with --mcp-allow-writes. Without --write, the bridge lists and admits only the tools the server annotates readOnlyHint: true, and refuses a call to any other before sending it. The applied policy still decides every call.
  • Existing servers only. A direct graph, root or catalogue target, and a managed target, refuse with target_kind_unsupported before any I/O. The bridge opens no storage and never starts or discovers a server.

The bridge relays up to 16 requests at once, the server's own limit, and answers them as they complete. It applies notifications/cancelled itself: the cancelled request's exchange is dropped and it gets no answer, unless its answer was already written. A cancelled write may still have landed: read the target before running it again. It never sends a request again:

FailureJSON-RPC error data
The server refused the request over HTTP (401, 412, 429, …)status and the server's error body as error. A 401 also carries the server's WWW-Authenticate challenge, verbatim, as www_authenticate when it sends one
Nothing was sent: the server could not be reached, or speaks another HTTP contracterror.code service_unavailable, or api_contract_mismatch, with effects: none
The answer was lost, or took longer than 45 secondsoutcome_unknown with effects: unknown for a call to a tool that may write; otherwise service_unavailable, deadline_exceeded or result_too_large with effects: none

These errors carry code -32000; refusals by the graph binding or write consent carry -32602, with a bad_request body. After a 401 the bridge reads the credential from its source again once, for later requests: a token rewritten in a file source, or returned anew by an exec helper, reaches them without a restart. An env source is fixed when the bridge starts, and a token replaced with omnigraph credentials set belongs to a new credential generation, so both take a restart; the error says how to replace the token. A server that stays unreachable fails each request; the bridge keeps running until stdin closes.

Settings

FlagEnvironmentDefaultEffect
--mcp-allow-adhocOMNIGRAPH_MCP_ALLOW_ADHOC=1offLists and admits the read tool
--mcp-allow-writesOMNIGRAPH_MCP_ALLOW_WRITES=1offLists and admits the write tools
--mcp-allowed-host <host[:port]>, repeatableOMNIGRAPH_MCP_ALLOWED_HOSTS=a,bnoneAdds allowed Host values

Settings are read once at boot. They enable tools; they grant nothing. A tool that is off is absent from tools/list, and a call to it is refused as an unknown tool. The opt-ins stay off even when a policy is applied: turn them on for a deployment whose policy was written with an LLM caller in mind.

Tools

Read tools

ToolArgumentsRunsPolicy action
healthnoneGET /healthznone
graphsnoneGET /graphs/discovery for an identity credential; otherwise GET /graphs, narrowed to ids and namesidentity, or graph_list
schemagraphGET /graphs/{graph_id}/schemaread
queriesgraphGET /graphs/{graph_id}/queries, stored mutations removedread
querygraph, name, optional params, branchPOST /graphs/{graph_id}/queries/{query_name} as a readinvoke_query, then read
read (opt-in)graph, query, optional name, params, branch, snapshotPOST /graphs/{graph_id}/queryread; change for explain load csv
snapshotgraph, optional branchGET /graphs/{graph_id}/snapshotread
accessgraph, optional branch, target_branchGET /graphs/{graph_id}/accessnone
branches_listgraphGET /graphs/{graph_id}/branchesread
commits_listgraph, optional branch, limit, page_tokenGET /graphs/{graph_id}/commitsread
commits_getgraph, commit_idGET /graphs/{graph_id}/commits/{commit_id}read
commits_changesgraph, commit_id, optional kind, type, op, page_token, limitGET /graphs/{graph_id}/commits/{commit_id}/changesread
changes_pollgraph, optional branch, cursor, start, kind, type, op, page_token, limitGET /graphs/{graph_id}/changesread

graph_list is a server-scoped action, and a server without an applied cluster policy denies every server-scoped action. So with a static or other non-identity token, graphs returns 403 until the cluster policy permits graph_list; the other tools still work with a graph id the user supplies.

The server's initialize instructions tell a model to call graphs, then schema, then a stored read with query or, when enabled, its own GQ read with read.

  • queries lists each stored read with its typed parameters (name, kind, nullable, plus item_kind for lists and vector_dim for vectors).
  • query refuses stored mutations.
  • read takes GQ source with typed parameter declarations, the same body as POST /query. It also answers branch list, branch diff, branch merge-base, branch merge … dry run and explain query …. explain load csv … requires change on its target branch before probing source metadata; it reads no payload byte and publishes nothing. It accepts branch, while refusing snapshot, name and params. The tool refuses insert, update, delete and branch writes with the route's 400.

Write tools (opt-in)

ToolArgumentsRunsPolicy action
mutategraph, query, optional name, params, branch, if_graph_commitPOST /graphs/{graph_id}/mutate, or /mutate/if-graph-commit with if_graph_commitchange; a branch statement takes its branch action
loadgraph, mode, data, optional branch, fromPOST /graphs/{graph_id}/loadchange, plus branch_create when from creates the branch
branches_creategraph, name, optional fromPOST /graphs/{graph_id}/branchesbranch_create
branches_deletegraph, nameDELETE /graphs/{graph_id}/branches/{branch_name}branch_delete
branches_mergegraph, source, optional targetPOST /graphs/{graph_id}/branches/mergebranch_merge

Write tools follow one contract:

  • Conditional writes. Pass a read's graph_commit_id to mutate as if_graph_commit. If the branch has moved, the call fails with 412 and nothing is written.
  • No retries. The server never replays a tool call.
  • A lost response is an unknown outcome. A write runs to completion even when the caller stops waiting. A write still running after 30 seconds, or one whose completion the server cannot confirm, returns isError with code: outcome_unknown. A write the client cancelled, timed out or lost the connection for returns nothing, and means the same. Before replaying it, read the target with read or snapshot and compare graph_commit_id, or list the branch with commits_list; replay a mutate with if_graph_commit, so a write that did land fails with 412 instead of applying twice. The 30 second wait is below the MCP TypeScript SDK's default 60 second request timeout; a client with a shorter timeout loses the answer.
  • Size. The whole call, including load data, fits in the 64 KiB request. Use POST /load/ndjson for larger loads.

Schema changes and cluster deployments are not MCP tools; operators run them with omnigraph cluster plan and apply.

Resources

URIContent
omnigraph://graphsThe graphs result, JSON, under the same rule
omnigraph://graphs/{graph}/schema (template)The graph's .pg source
omnigraph://graphs/{graph}/branches (template)The branches_list result, JSON
omnigraph://best-practices/indexWhich reference to read for which task, Markdown
omnigraph://best-practices/{topic} for queries, data, schema, search, changesThe agent skill's reference of that name, Markdown

The best-practices references are the omnigraph agent skill's own files, compiled into the server, so they always match its version.

A resource read runs the same handler and policy action as its tool. A refusal is a JSON-RPC error whose data carries the refused tool call's content below; an unknown resource or graph is error -32002.

Results and errors

A successful call returns the route's JSON response body as structured content, plus a text copy for older clients. health, graphs, schema, queries, access and branches_list declare an output schema.

A refused call returns isError: true with {"status": <HTTP status>, "error": <error body>}: the error body every HTTP route answers, with its code, disposition and details, and the status the same failure answers over HTTP, for example 403 for a policy denial, 404 for an unknown or hidden graph, 503 for a graph that is not available. The MCP layer's own refusals use the same body: unauthorized without a verified credential, too_many_requests beyond the concurrent limit, and cancelled, deadline_exceeded or result_too_large, which only MCP sends and which carry no status. A write that may have happened, one whose call was cancelled or passed its wait included, answers outcome_unknown with effects: unknown.

Limits

LimitValue
Request body64 KiB
Complete tool result, including the text copy1 MiB
Wait for one read30 seconds
Wait for one write30 seconds; the write continues past it
Concurrent tool executions16

A cancelled or expired call stops the caller's wait. The execution keeps its input reservation and slot until it finishes, and there is no later result lookup. See admission and shutdown.

Logs

Verbose MCP SDK logs contain query arguments and results, so the server keeps them out of its log; see Logs.

On this page