Skip to content

sdk+mcp: surface saved queries as first-class tools#7

Closed
joanreyero wants to merge 1 commit into
ModernRelay:mainfrom
joanreyero:feat/saved-queries-tools
Closed

sdk+mcp: surface saved queries as first-class tools#7
joanreyero wants to merge 1 commit into
ModernRelay:mainfrom
joanreyero:feat/saved-queries-tools

Conversation

@joanreyero

@joanreyero joanreyero commented May 15, 2026

Copy link
Copy Markdown

Depends on ModernRelay/omnigraph#96 — the new /queries CRUD endpoints land in omnigraph-server first. This PR is opened as a draft so the integration is reviewable now; check-drift CI will fail until the upstream PR merges and a release tag is cut (the spec is pinned to a server version in root package.json). Bump omnigraph.serverVersion to that tag and the check goes green.

Summary

  • Adds an og.queries SDK resource (list / get / save / delete) wrapping the new /queries endpoints. CamelCased like the rest of the public types.
  • MCP server now registers one tool per saved query at startup, prefixed q_<name> so user-defined tools cannot shadow built-ins (read, change, …). The tool's input schema is built from the parameter signature the server persisted alongside the source.
  • New omnigraph://queries resource exposes the full list as JSON for clients that prefer browsing over scrolling the tool catalogue.
  • createOmnigraphMcpServer is now async (it calls into the server during setup). bin.ts updated; no other callers in-tree.
  • Best-effort startup: if the server is older than the release that ships server: add saved-query CRUD at /queries omnigraph#96 (or unreachable, or /queries returns malformed JSON), we log to stderr and skip per-query registration. All built-in tools remain functional. Covered by a dedicated test.

Why this shape

Agents currently have to know how to author .gq source and feed it to read. Saved queries let an operator (or another agent) author a query once, give it a name and a description, and have every downstream MCP client see it as a discoverable, typed tool — q_find_person({ name: "Alice" }).

The q_ prefix is deliberate defence-in-depth on top of the server-side reserved-name check; the prefix also keeps the catalogue obviously partitioned (built-ins vs user-saved) for humans browsing it.

Param-type → zod mapping

Intentionally permissive. Scalar types (String, Bool, I32/I64/U32/U64, F32/F64, Date/DateTime) get specific zod schemas. Anything else (Vector(N), [String], Blob, future scalars) is passed through as z.unknown(). The server still parses and typechecks the saved query against the live schema at execution time, so duplicating that typecheck client-side would only introduce false negatives.

Test plan

  • pnpm typecheck — clean.
  • pnpm test — SDK 60 pass / 18 e2e-skipped (4 new queries-resource tests); MCP 15 pass (3 new tests covering tool registration, snapshot routing, older-server fallback).
  • pnpm build — both packages build.
  • pnpm check-coverage — 18 call sites bound to 18 spec operations.
  • Live smoke test against a local omnigraph-server build of server: add saved-query CRUD at /queries omnigraph#96, driven via @modelcontextprotocol/inspector: both q_<name> tools appear with correct input schemas and dispatch to /read with the saved source.
  • CI check-driftexpected to fail until server: add saved-query CRUD at /queries omnigraph#96 merges and a release is tagged. Bump omnigraph.serverVersion in root package.json to that tag at that point.

🤖 Generated with Claude Code


Summary by cubic

Expose saved queries as first-class tools. The SDK adds og.queries CRUD for /queries, and the MCP server auto-registers a q_<name> tool per saved query with typed inputs that dispatch through read.

  • New Features

    • SDK: new og.queries resource (list/get/save/delete), with camelCased types.
    • MCP: registers one q_<name> tool per saved query; input schema derives from saved params; supports optional branch/snapshot; routes to read.
    • Resource: omnigraph://queries returns the full saved-query list as JSON.
    • Best-effort startup: if /queries is missing or fails, we log and skip per-query tools; built-ins still work.
  • Migration

    • createOmnigraphMcpServer is now async. Update call sites to await createOmnigraphMcpServer(...) before connecting.

Written for commit 2e71af0. Summary will update on new commits.

Pairs with omnigraph PR (server-side /queries CRUD). The MCP server now
fetches every saved query at startup and registers one MCP tool per
saved query under the `q_<name>` prefix, with a zod input schema built
from the persisted parameter signature. Agents see saved queries in
their tool catalogue alongside `read` / `change` / etc., instead of
having to know to call `read` with a hand-rolled source string.

Surface:
- New `og.queries` SDK resource (list/get/save/delete), camelCased like
  the rest of the public types.
- `createOmnigraphMcpServer` is now async (calls into the server during
  setup). `bin.ts` updated; no other callers in-tree.
- On older omnigraph-server (no /queries endpoint), the list call 404s,
  we log to stderr and skip per-query registration — built-in tools
  remain functional. Tested.
- A new `omnigraph://queries` resource exposes the same list as JSON
  for clients that prefer browsing over the tool catalogue.

Param-type → zod mapping is intentionally permissive (`I32`, `String`,
etc. constrained; `Vector(N)`, `[String]`, future scalars passed as
`z.unknown()`). The server still parses and typechecks against the
live schema, so duplicating the typecheck client-side would only
introduce false negatives.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Signed-off-by: Joan Reyero <joan@reyero.io>
@joanreyero joanreyero marked this pull request as ready for review May 15, 2026 09:39

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 11 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/mcp/src/server.ts">

<violation number="1" location="packages/mcp/src/server.ts:118">
P2: Saved-query parameters named `branch` or `snapshot` are silently shadowed by transport-level options, making it impossible to pass those values as actual query parameters.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

// Every saved query is dispatched through `/read`, so the caller may
// also pin a branch or a snapshot at invocation time without having
// to bake it into the saved source.
shape.branch = z.string().optional();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Saved-query parameters named branch or snapshot are silently shadowed by transport-level options, making it impossible to pass those values as actual query parameters.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/mcp/src/server.ts, line 118:

<comment>Saved-query parameters named `branch` or `snapshot` are silently shadowed by transport-level options, making it impossible to pass those values as actual query parameters.</comment>

<file context>
@@ -67,7 +71,67 @@ function plainText(text: string) {
+  // Every saved query is dispatched through `/read`, so the caller may
+  // also pin a branch or a snapshot at invocation time without having
+  // to bake it into the saved source.
+  shape.branch = z.string().optional();
+  shape.snapshot = z.string().optional();
+  return shape;
</file context>

@ragnorc

ragnorc commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Closing in favor of a server-side MCP surface. We're moving MCP tool projection into omnigraph-server (RFC-003): one Cedar-gated /mcp endpoint over Streamable HTTP, so the tool catalogue and the invoke_query authorization live in the engine rather than in the stdio client.

This PR's approach (client-side q_<name> registration, dispatch by replaying the saved source through /read) predates the stored-query design that shipped in omnigraph#128: the catalogue is declarative config (no PUT/save/delete), GET /queries returns typed params without source, and invocation goes through POST /queries/{name} behind the invoke_query gate. The SDK side already shipped the matching surface in #11 (og.queries.list/invoke), which supersedes the og.queries CRUD here.

Ideas we'll carry into the server-side work: the q_ prefix to partition user tools from built-ins, the permissive param->schema mapping, and the best-effort startup. Once the server /mcp endpoint is GA, the stdio package becomes a thin proxy over it.

Thanks @joanreyero for the prototype — it mapped the shape well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants