mirror of
https://github.com/ModernRelay/omnigraph.git
synced 2026-06-18 02:24:27 +02:00
docs(mcp): document the MCP surface, authoring controls, and skill (v0.8.0)
Document the per-graph MCP surface (POST /graphs/{id}/mcp, shipped in the
preceding commits and landing under v0.8.0) and the `.gq` authoring controls
that shape stored-query tools.
- New docs/user/operations/mcp.md: the client-facing guide — transport, tool
catalog (built-ins + stored queries), projection modes, structured output,
authorization (call-authoritative + list-relaxation), Host/Origin policy, the
protocol-version contract.
- docs/user/operations/server.md: the /mcp endpoint + an "MCP surface" section;
docs/user/index.md: a "Connect an MCP agent" pointer.
- docs/user/queries/index.md: an Annotations section — query @description /
@instruction / @mcp(expose, tool_name) and per-parameter @description.
- AGENTS.md: topic-table row + MCP note on the HTTP-server capability row.
- docs/dev/testing.md: the omnigraph-mcp crate + server tests/mcp.rs.
- docs/dev/rfc-005 §D5: retire the "cluster = everything exposed" bridge —
cluster mode honors source `@mcp(expose: …)`; presentation vs authorization
split made explicit.
- skills/omnigraph: server-policy.md MCP section; stored-queries.md corrected
(per-query controls now ship via @mcp, not "planned"); SKILL.md MCP triggers,
Deep Dives row, version → 0.8.0.
- docs/releases/v0.8.0.md: the MCP surface + authoring-controls release notes.
Crate version manifests are deliberately NOT bumped — that is the v0.8.0
release-cut step; this lands on the feature branch.
This commit is contained in:
parent
c8e91c11f0
commit
c06343362a
11 changed files with 349 additions and 13 deletions
|
|
@ -88,9 +88,24 @@ Boot is fail-fast, matching the server's existing stance (bad policy YAML refuse
|
|||
| stored query fails type-check against the live schema | boot error (existing `validate_and_attach` behavior) |
|
||||
| state lock held | **not** an error — boot takes no lock; it reads a point-in-time snapshot of an immutable-once-written state file (the CAS discipline means a concurrent apply produces a *new* file atomically; the server reads whichever was current at open) |
|
||||
|
||||
### D5. The `mcp.expose` bridge in cluster mode
|
||||
### D5. MCP presentation (`@mcp(expose, tool_name)`) in cluster mode
|
||||
|
||||
The cluster query registry has no `expose` flag by design (axiom 14: exposure is a policy decision — Phase 6). Until Phase 6 ships, cluster-mode servers list **all** stored queries in `GET /queries`. This is the documented bridge: *cluster mode = everything exposed; omnigraph.yaml mode = `mcp.expose` honored as today*. Its named sunset is Phase 6's policy-filtered catalog (Compatibility Stance #9). Invocation remains gated by the existing coarse `invoke_query` Cedar action in both modes.
|
||||
**Superseded (v0.8.0).** The old "bridge" — cluster mode force-lists every stored
|
||||
query because the cluster registry had no `expose` flag — is gone. Per-query MCP
|
||||
presentation is now carried in the `.gq` **source** via the `@mcp(expose: …,
|
||||
tool_name: …)` annotation (re-parsed at boot from the content-addressed query
|
||||
blob), so cluster mode honors it the same as any other deployment; the boot path
|
||||
no longer hardcodes `expose: true`. Default with no `@mcp`: exposed, tool name =
|
||||
query name.
|
||||
|
||||
Crucially this splits two axes the original bridge conflated: **`expose` is
|
||||
presentation** (does the query appear on the agent tool surface — `tools/list` /
|
||||
`GET /queries`), carried in source; **authorization** (who may invoke a query)
|
||||
stays the coarse `invoke_query` Cedar action, with a per-query refinement the
|
||||
durable future direction. An `expose: false` query is still HTTP/service-callable
|
||||
by name for any caller holding `invoke_query`. See
|
||||
[../user/queries/index.md](../user/queries/index.md#annotations) and
|
||||
[../user/operations/mcp.md](../user/operations/mcp.md).
|
||||
|
||||
### D6. Migration path (exit criterion 7)
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ This file is the always-on map of the test surface. **Consult it before every ta
|
|||
| `omnigraph` (engine) | `crates/omnigraph/tests/` | Integration tests (28 files), fixture-driven, share `tests/helpers/mod.rs` |
|
||||
| `omnigraph-cli` | `crates/omnigraph-cli/tests/` | Per-area suites (post-modularization): `cli_cluster.rs` (cluster command surface + operator-actor cascade), `cli_cluster_e2e.rs` (spawned-binary lifecycle compositions — lost-state re-import recovery, out-of-band drift, graph-root destruction, multi-graph mixed-disposition convergence), `cli_data.rs` (load/read/change/branch/commit/export/snapshot/policy/embed/maintenance + operator format cascade), `cli_schema_config.rs` (init/config, schema plan/apply), `cli_queries.rs`, `parity_matrix.rs` (RFC-009 Phase 1: the embedded-vs-remote referee — every forked verb run against both arms with matched Cedar policy and the same actor, scrubbed-JSON + exit-code equality; divergences are pinned in its `KNOWN_DIVERGENCES` ledger, never silently repaired), `system_local.rs` (full-cycle cluster lifecycle with a spawned `--cluster` server, applied-policy enforcement over HTTP, keyed-credential auth, operator aliases), `system_remote.rs`; share `tests/support/mod.rs` (hermetic `OMNIGRAPH_HOME` by default) |
|
||||
| `omnigraph-cluster` | mostly in-source `#[cfg(test)] mod tests`; `tests/failpoints.rs` (feature-gated); `tests/s3_cluster.rs` (bucket-gated full lifecycle on object storage) | Cluster config parser, local JSON state diff, state CAS/lock handling/recovery, read-only validate/plan/status plus explicit refresh/import graph observations, config-only apply (content-addressed payload publish, disposition gating, composite-digest convergence, idempotent re-apply), catalog payload verification (status read-only, refresh drift + self-heal), failpoint crash-mid-apply / CAS-race coverage, Stage 4A graph creation (create executor, recovery sidecars + sweep rows, create crash windows), Stage 4B schema apply (migration previews in plan, schema executor, schema-apply sweep classification, schema crash windows), Stage 4C gated deletes (digest-bound approvals, delete executor + tombstones, delete sweep rows, delete crash windows), and 5A policy binding metadata (applies_to in the applied revision, binding-change diffing + convergence, pre-5A backfill), and the 5B serving-snapshot read API (converged read, refusal rows) |
|
||||
| `omnigraph-server` | `crates/omnigraph-server/tests/` | Per-area suites (post-modularization): `auth_policy.rs`, `data_routes.rs`, `schema_routes.rs`, `stored_queries.rs`, `multi_graph.rs` (cluster-mode boot — converged serving, policy binding wiring, boot refusals — + the concurrent branch-ops matrix), `boot_settings.rs` (mode inference, PolicySource), `s3.rs` (bucket-gated: single-graph serving + config-free `--cluster s3://` boot), `openapi.rs` (OpenAPI drift / regeneration); share `tests/support/mod.rs` |
|
||||
| `omnigraph-server` | `crates/omnigraph-server/tests/` | Per-area suites (post-modularization): `auth_policy.rs`, `data_routes.rs`, `schema_routes.rs`, `stored_queries.rs`, `mcp.rs` (the `/graphs/{id}/mcp` surface — protocol conformance, Cedar-filtered listing + the argument-scoped relaxation, stored-tool projection modes + `expose` reachability, structured output), `multi_graph.rs` (cluster-mode boot — converged serving, policy binding wiring, boot refusals — + the concurrent branch-ops matrix), `boot_settings.rs` (mode inference, PolicySource), `s3.rs` (bucket-gated: single-graph serving + config-free `--cluster s3://` boot), `openapi.rs` (OpenAPI drift / regeneration); share `tests/support/mod.rs` |
|
||||
| `omnigraph-mcp` | `crates/omnigraph-mcp/tests/standalone.rs` | Crate-level MCP transport conformance with a trivial backend (no engine dep): `initialize`/`tools/list`, `405` for `GET`/`DELETE`, fail-closed Origin, the full loopback `Host` set (incl. `::1`), and the `MCP-Protocol-Version` contract (non-init → 400, init exempt). Also the **rmcp surface guard** — first smoke check on an rmcp bump. |
|
||||
| `omnigraph-compiler` | mostly in-source `#[cfg(test)] mod tests` | Parser, type-checker, IR lowering, lint |
|
||||
|
||||
The engine's `tests/` is the principal coverage surface; most graph-shaped behavior is exercised there.
|
||||
|
|
|
|||
89
docs/releases/v0.8.0.md
Normal file
89
docs/releases/v0.8.0.md
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
# Omnigraph v0.8.0
|
||||
|
||||
v0.8.0 makes every served graph 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: run reads and
|
||||
mutations, load data, manage branches, browse commits, read the schema, and
|
||||
invoke the graph's curated stored queries. 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.
|
||||
|
||||
## Highlights
|
||||
|
||||
### MCP surface (`POST /graphs/{id}/mcp`)
|
||||
|
||||
- **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.
|
||||
|
||||
## Upgrade notes
|
||||
|
||||
- **No breaking changes.** The REST surface, CLI, cluster config, and on-disk
|
||||
format are unchanged. The MCP endpoint is additive.
|
||||
- **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](../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.
|
||||
|
|
@ -45,6 +45,7 @@ start with install, then follow the section that matches your task.
|
|||
|---|---|
|
||||
| Deploy the binary or container | [deployment.md](deployment.md) |
|
||||
| Use HTTP endpoints | [operations/server.md](operations/server.md) |
|
||||
| Connect an MCP agent (Claude, Cursor, …) | [operations/mcp.md](operations/mcp.md) |
|
||||
| Compact, repair, and clean old versions | [operations/maintenance.md](operations/maintenance.md) |
|
||||
| Configure Cedar authorization | [operations/policy.md](operations/policy.md) |
|
||||
| Track actors and audit behavior | [operations/audit.md](operations/audit.md) |
|
||||
|
|
|
|||
154
docs/user/operations/mcp.md
Normal file
154
docs/user/operations/mcp.md
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
# MCP Surface (`POST /graphs/{id}/mcp`)
|
||||
|
||||
Every graph a cluster server serves is also exposed as a [Model Context
|
||||
Protocol](https://modelcontextprotocol.io) server, so an MCP-capable agent
|
||||
(Claude Code/Desktop, Cursor, the OpenAI Responses `mcp` tool, …) can operate the
|
||||
graph directly — no bespoke client, no `.gq` source on the wire. The MCP surface
|
||||
adds **no new capability or business logic**: every tool delegates to the same
|
||||
engine/handler path the REST routes use and is gated by the same Cedar policy.
|
||||
|
||||
Available since **v0.8.0**. It is served automatically by `omnigraph-server
|
||||
--cluster …` — there is no separate flag to enable it.
|
||||
|
||||
## Transport
|
||||
|
||||
One endpoint per served graph:
|
||||
|
||||
```
|
||||
POST /graphs/{id}/mcp
|
||||
```
|
||||
|
||||
It is a **stateless Streamable-HTTP** MCP transport: each call returns a single
|
||||
`application/json` JSON-RPC response — no SSE, no session id. `GET`/`DELETE`
|
||||
return `405` (`Allow: POST`).
|
||||
|
||||
```bash
|
||||
curl -sS https://graph.example.com/graphs/sales/mcp \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Accept: application/json, text/event-stream" \
|
||||
-d '{"jsonrpc":"2.0","id":1,"method":"initialize",
|
||||
"params":{"protocolVersion":"2025-11-25","capabilities":{},
|
||||
"clientInfo":{"name":"my-agent","version":"0"}}}'
|
||||
```
|
||||
|
||||
A typical MCP client is configured with just the URL and the bearer token; it
|
||||
then drives `initialize` → `tools/list` / `resources/list` → `tools/call` /
|
||||
`resources/read` itself.
|
||||
|
||||
Every tool operates on the single graph in the URL path, so the graph id never
|
||||
appears in tool arguments or output.
|
||||
|
||||
## Tools
|
||||
|
||||
### Built-in tools
|
||||
|
||||
| Tool | Action | Notes |
|
||||
|---|---|---|
|
||||
| `graph_health` | — | liveness/identity probe; always visible |
|
||||
| `graph_query` | `read` | run an ad-hoc read-only GQ query (mutations rejected) |
|
||||
| `graph_snapshot` | `read` | manifest version + per-table metadata of a branch |
|
||||
| `schema_get` | `read` | the graph's `.pg` source |
|
||||
| `branch_list` | `read` | branch names |
|
||||
| `commit_list` / `commit_get` | `read` | commit history |
|
||||
| `graph_mutate` | `change` | ad-hoc GQ insert/update/delete against a branch |
|
||||
| `graph_load` | `change` (+ `branch_create` with `from`) | bulk NDJSON load |
|
||||
| `branch_create` / `branch_delete` / `branch_merge` | `branch_create` / `branch_delete` / `branch_merge` | branch ops |
|
||||
| `schema_apply` | `schema_apply` | **disabled (409) under cluster-backed serving** — evolve via `cluster apply` + restart |
|
||||
|
||||
Tool names are domain-qualified `snake_case`. Read tools are annotated
|
||||
`readOnly`; writers are annotated `destructive` so clients can prompt for
|
||||
confirmation (annotations are advisory hints — Cedar is the enforcement
|
||||
boundary).
|
||||
|
||||
### Stored-query tools
|
||||
|
||||
The graph's stored-query registry (declared in `cluster.yaml`, see
|
||||
[stored queries](server.md#stored-query-catalog-get-queries)) is projected as
|
||||
tools too, in one of two modes chosen automatically from the count of exposed
|
||||
queries:
|
||||
|
||||
- **`per_query`** (fewer than 24 exposed queries) — each exposed query is its own
|
||||
tool, named by its `@mcp(tool_name: …)` (default: the query name), with a typed
|
||||
input schema. Query parameters are nested under a `params` object; `branch`
|
||||
(and, for reads, `snapshot`) sit alongside it.
|
||||
- **`meta`** (24 or more) — the per-query tools collapse to a discovery + execute
|
||||
pair, `stored_query_list` (filter/inspect the catalog) and
|
||||
`stored_query_run(name, params, branch?, snapshot?)`, so a client's tool count
|
||||
stays bounded.
|
||||
|
||||
A stored-query tool's metadata comes from the `.gq` source (see
|
||||
[query annotations](../queries/index.md#annotations)):
|
||||
|
||||
- **description** = `@description`, with `@instruction` folded in after a blank
|
||||
line (so the agent sees both in `tools/list`).
|
||||
- **tool name** = `@mcp(tool_name: …)`, else the query name.
|
||||
- **parameter docs** = each parameter's `@description`, surfaced into the input
|
||||
schema's per-property `description`.
|
||||
|
||||
Only **exposed** queries are reachable on the MCP surface in either mode. Set
|
||||
`@mcp(expose: false)` to hide a query from `tools/list`, from
|
||||
`stored_query_list`, and from `stored_query_run` (by name). This is presentation
|
||||
only — the query stays HTTP/service-callable via `POST /queries/{name}` for any
|
||||
caller with the `invoke_query` grant.
|
||||
|
||||
## Resources
|
||||
|
||||
| URI | Gate | Contents |
|
||||
|---|---|---|
|
||||
| `omnigraph://schema` | `read` | the graph's `.pg` schema source |
|
||||
| `omnigraph://branches` | `read` | branch names as JSON |
|
||||
|
||||
## Structured output
|
||||
|
||||
Tool results carry **structured output**: `structuredContent` (the typed result
|
||||
DTO — the same shape as the REST `ReadOutput` / `ChangeOutput` envelopes) plus a
|
||||
text mirror for clients that don't parse it.
|
||||
|
||||
## Authorization
|
||||
|
||||
Authorization is identical to the REST routes — the bearer token resolves to a
|
||||
server-side actor, and every tool/resource hits the same Cedar gate:
|
||||
|
||||
- **Calls are authoritative.** A built-in tool runs only if the actor's Cedar
|
||||
grant permits the action on the *actual* branch argument; a denial comes back
|
||||
as a tool error (`isError`), not a silent success.
|
||||
- **Listing is a relaxation.** `tools/list` shows a tool if the actor could
|
||||
invoke it on *some* branch, so a tool you can call is never hidden. Under the
|
||||
canonical "protect `main`, write feature branches" policy, `graph_mutate` is
|
||||
listed for an actor who can change unprotected branches even though a write to
|
||||
`main` would be denied. An actor with no write grant at all does not see the
|
||||
write tools.
|
||||
- **Stored queries** sit behind one coarse `invoke_query` gate (a stored
|
||||
*mutation* is additionally `change`-gated). For a caller lacking `invoke_query`,
|
||||
a stored tool masks as an **unknown tool**, so the catalog can't be probed.
|
||||
|
||||
## Host & Origin policy
|
||||
|
||||
The transport derives a fail-closed DNS-rebinding / browser posture from the bind
|
||||
address at startup:
|
||||
|
||||
- **Loopback bind** (`127.0.0.1` or `::1`) — the `Host` allow-list is the full
|
||||
loopback set `127.0.0.1`, `::1`, `localhost` (so either IP stack works
|
||||
regardless of which one the server bound), and `Origin` is unchecked (local-dev
|
||||
convenience).
|
||||
- **Non-loopback bind** — the `Host` allow-list is the configured public host(s)
|
||||
(or unrestricted if none are configured — rely on the bearer token there), and
|
||||
any *present* browser `Origin` is rejected (`403`) unless it is in the
|
||||
configured browser-origins list. A non-browser MCP client sends no `Origin` and
|
||||
passes.
|
||||
|
||||
A disallowed `Host` is `403`.
|
||||
|
||||
## Protocol version
|
||||
|
||||
The `MCP-Protocol-Version` header is validated on follow-up requests (an
|
||||
unsupported version → `400`). The `initialize` request is exempt by design — it
|
||||
negotiates the version in its JSON-RPC body (`protocolVersion`), so the header is
|
||||
not checked there.
|
||||
|
||||
## Not supported
|
||||
|
||||
MCP prompts, elicitation, sampling, tasks, and `tools/list_changed` /
|
||||
`resources/list_changed` subscriptions are not implemented — the surface is
|
||||
`initialize` + `tools` + `resources` over the stateless POST transport.
|
||||
|
|
@ -46,6 +46,7 @@ graph id from the cluster's applied revision:
|
|||
| POST | `/graphs/{id}/change` | bearer + `change` | **deprecated** alias of `/mutate` (carries `Deprecation: true` + `Link: <mutate>; rel="successor-version"`) |
|
||||
| GET | `/graphs/{id}/queries` | bearer + `read` | list the `mcp.expose` stored queries as a typed tool catalog |
|
||||
| POST | `/graphs/{id}/queries/{name}` | bearer + `invoke_query` (+ `change` for a stored mutation) | invoke a named query from the `queries:` registry; deny == 404 |
|
||||
| POST | `/graphs/{id}/mcp` | bearer + same per-tool Cedar gate | MCP (Model Context Protocol) surface — built-ins + stored queries as tools, schema/branches as resources (see [mcp.md](mcp.md)) |
|
||||
| GET | `/graphs/{id}/schema` | bearer + `read` | get current `.pg` source |
|
||||
| POST | `/graphs/{id}/schema/apply` | bearer + `schema_apply` (target=`main`) | disabled for cluster-backed serving; returns 409 and points operators at `omnigraph cluster apply` + restart |
|
||||
| POST | `/graphs/{id}/load` | bearer + `branch_create` (only when `from` is set and the branch is created) + `change` | bulk load (canonical); branch creation is opt-in via `from` — without it a missing `branch` is a 404, never an implicit fork (32 MB body limit) |
|
||||
|
|
@ -65,7 +66,7 @@ Server-level management endpoints:
|
|||
|
||||
### Stored-query catalog (`GET /queries`)
|
||||
|
||||
List the graph's **`mcp.expose`** stored queries as a typed tool catalog — enough for a client (e.g. an MCP server) to register each as a tool without fetching `.gq` source. Each entry: `{ name, tool_name, description, instruction, mutation, params }`, where each param is `{ name, kind, item_kind?, vector_dim?, nullable }`. `kind` is one of `string | bool | int | bigint | float | date | datetime | blob | vector | list` (decomposed so a consumer maps it with a closed `switch`, never re-parsing GQ type spelling). `bigint` (I64/U64), `date`, `datetime`, and `blob` are carried as JSON **strings** — a 64-bit integer loses precision as a JSON number, dates are ISO strings, and a blob is a URI string.
|
||||
List the graph's **`mcp.expose`** stored queries as a typed tool catalog — enough for a client to register each as a tool without fetching `.gq` source. (The server also projects these queries as live MCP tools at `POST /graphs/{id}/mcp` — see [mcp.md](mcp.md); this catalog endpoint is the REST view of the same registry.) Each entry: `{ name, tool_name, description, instruction, mutation, params }`, where each param is `{ name, kind, item_kind?, vector_dim?, nullable }`. `kind` is one of `string | bool | int | bigint | float | date | datetime | blob | vector | list` (decomposed so a consumer maps it with a closed `switch`, never re-parsing GQ type spelling). `bigint` (I64/U64), `date`, `datetime`, and `blob` are carried as JSON **strings** — a 64-bit integer loses precision as a JSON number, dates are ISO strings, and a blob is a URI string.
|
||||
|
||||
- **Read-gated** (works in default-deny mode). The catalog is **graph-wide** (branch-independent; `read` is authorized against `main`).
|
||||
- **`mcp.expose` defaults to `true`** — declaring a query in `queries:` lists it; set `mcp: { expose: false }` to keep it HTTP/service-callable but hidden from the catalog.
|
||||
|
|
@ -80,6 +81,22 @@ 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 surface (`POST /graphs/{id}/mcp`)
|
||||
|
||||
Each served graph is also an MCP (Model Context Protocol) server at
|
||||
`POST /graphs/{id}/mcp` — a stateless Streamable-HTTP transport that projects the
|
||||
built-in operations and the graph's stored-query registry as MCP **tools**, and
|
||||
the schema / branch list as MCP **resources**. It adds no new capability: every
|
||||
tool delegates to the same engine/handler path the REST routes use and is gated
|
||||
by the same Cedar policy (resolved from the same bearer token). `tools/list` is a
|
||||
*relaxation* of the per-call gate — a tool callable on some branch is never
|
||||
hidden, while the per-call gate stays authoritative. Served automatically by the
|
||||
cluster server; no separate flag.
|
||||
|
||||
Full client guide — connecting, the tool catalog, projection modes, structured
|
||||
output, Host/Origin policy, and the protocol-version contract — is in
|
||||
[mcp.md](mcp.md).
|
||||
|
||||
## Adding and removing graphs
|
||||
|
||||
Runtime add/remove via API is **not** exposed — neither `POST /graphs`
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@
|
|||
## Query declarations
|
||||
|
||||
```
|
||||
query <name>($p1: T1, $p2: T2?, …)
|
||||
@description("…") @instruction("…") {
|
||||
query <name>(@description("…") $p1: T1, $p2: T2?, …)
|
||||
@description("…") @instruction("…") @mcp(tool_name: "…", expose: true) {
|
||||
…
|
||||
}
|
||||
```
|
||||
|
|
@ -19,6 +19,31 @@ Multi-modal search functions (`nearest`, `bm25`, `rrf`, …) used inside `match`
|
|||
|
||||
Param types reuse all schema scalars; trailing `?` makes a param optional. The compiler reserves `$__nanograph_now` for `now()`.
|
||||
|
||||
### Annotations
|
||||
|
||||
Annotations after the param list are optional and order-independent:
|
||||
|
||||
- `@description("…")` — human-readable summary of the query (shown in the
|
||||
stored-query catalog and as the MCP tool description).
|
||||
- `@instruction("…")` — agent-facing *how/when to use* guidance. It is folded
|
||||
into the [MCP](../operations/mcp.md) tool description (appended after
|
||||
`@description`), so an agent reading `tools/list` sees it.
|
||||
- `@mcp(...)` — **MCP-presentation** controls for when the query is served as an
|
||||
agent tool (see [mcp.md](../operations/mcp.md)). Both keys are optional:
|
||||
- `tool_name: "<name>"` — the tool id to expose the query under (default: the
|
||||
query name). Must be unique across exposed queries and must not shadow a
|
||||
built-in tool, or the server refuses to boot.
|
||||
- `expose: <bool>` — whether the query appears on the agent tool surface
|
||||
(default `true`). `expose: false` keeps the query HTTP/service-callable by
|
||||
name but hides it from `tools/list` and the catalog. This is **presentation
|
||||
only** — not an authorization control (Cedar `invoke_query` governs who may
|
||||
call it).
|
||||
|
||||
A **per-parameter** `@description("…")` (written before the variable, e.g.
|
||||
`@description("the user's slug") $slug: String`) documents that argument; it is
|
||||
surfaced into the parameter's JSON-Schema `description` in the catalog and the
|
||||
MCP tool input schema.
|
||||
|
||||
## MATCH clauses
|
||||
|
||||
- **Binding**: `$x: NodeType { prop: <literal | $param | now()>, … }`
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue