feat(ensapi): introduce Model Context Protocol (MCP) server for Omnigraph API#2301
feat(ensapi): introduce Model Context Protocol (MCP) server for Omnigraph API#2301djstrong wants to merge 16 commits into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
🦋 Changeset detectedLatest commit: 1509598 The changes in this PR will be included in the next version bump. This PR includes changesets to release 24 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds a new MCP server endpoint ( ChangesOmnigraph MCP Server and Related Infrastructure
Sequence DiagramsequenceDiagram
participant MCPClient
participant HonoRouter
participant StreamableHTTPTransport
participant McpServer
participant omnigraph_query
participant Yoga
MCPClient->>HonoRouter: POST /api/mcp (initialize request)
HonoRouter->>StreamableHTTPTransport: Create new transport with UUID
HonoRouter->>McpServer: Create and connect server
HonoRouter->>StreamableHTTPTransport: handleRequest(initialize)
StreamableHTTPTransport->>McpServer: Process initialize
McpServer-->>StreamableHTTPTransport: Initialized response
HonoRouter-->>MCPClient: HTTP 200 + mcp-session-id header
MCPClient->>HonoRouter: POST /api/mcp (omnigraph_query tool call)
HonoRouter->>StreamableHTTPTransport: handleRequest(tool call) using session-id
StreamableHTTPTransport->>McpServer: Invoke tool
McpServer->>omnigraph_query: Execute query or resolve exampleId
omnigraph_query->>Yoga: POST /api/omnigraph { query, variables }
Yoga-->>omnigraph_query: { data, errors }
omnigraph_query-->>McpServer: Append hints, return GraphQL JSON
McpServer-->>StreamableHTTPTransport: Tool result
HonoRouter-->>MCPClient: HTTP 200 with result
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
This PR adds a first-party Model Context Protocol (MCP) server surface to ENSApi so MCP clients can execute ENS Omnigraph GraphQL queries via a single omnigraph_query tool at /api/mcp, and updates docs + dependencies accordingly.
Changes:
- Add
/api/mcpendpoint backed by@hono/mcp+@modelcontextprotocol/sdk, exposing theomnigraph_querytool. - Add unit + integration tests for the MCP server/tool behavior.
- Add documentation pages/links describing how to connect MCP clients to the new endpoint.
Reviewed changes
Copilot reviewed 11 out of 12 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| pnpm-lock.yaml | Locks new MCP-related dependencies (@hono/mcp, @modelcontextprotocol/sdk) and their transitive deps. |
| apps/ensapi/package.json | Adds MCP server dependencies to ensapi. |
| apps/ensapi/src/handlers/api/router.ts | Mounts the new MCP sub-router at /api/mcp. |
| apps/ensapi/src/handlers/api/mcp/mcp-api.ts | Implements MCP server + omnigraph_query tool and streamable-HTTP transport handler. |
| apps/ensapi/src/handlers/api/mcp/mcp-api.test.ts | Unit tests for tool registration and yoga delegation via in-memory transport. |
| apps/ensapi/src/handlers/api/mcp/mcp-api.integration.test.ts | End-to-end integration tests against a live ENSApi instance. |
| .changeset/ensapi-omnigraph-mcp.md | Changeset documenting the new MCP feature as a minor bump for ensapi. |
| docs/ensnode.io/src/content/docs/docs/integrate/integration-options/omnigraph-mcp.mdx | New documentation page for the MCP endpoint and client setup. |
| docs/ensnode.io/src/content/docs/docs/integrate/integration-options/omnigraph-graphql-api.mdx | Cross-links GraphQL docs to the new MCP surface. |
| docs/ensnode.io/src/content/docs/docs/integrate/integration-options/index.mdx | Adds MCP as an integration option in the index page. |
| docs/ensnode.io/config/integrations/starlight/sidebar-topics/integrate.ts | Adds MCP page to the Integrate sidebar. |
| docs/ensnode.io/src/content/docs/docs/integrate/why-ensnode/keep-ens-working.mdx | Adds MCP to the “ENS AI Agents” dependency table. |
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // NOTE: this bypasses the indexing-status middleware on the `/api/omnigraph` HTTP route, so | ||
| // queries run against whatever the index currently holds. `canAccelerate` only affects the | ||
| // Resolution API, so `false` is correct for generic Omnigraph queries. | ||
| const response = await yoga.fetch( |
…tation - Introduced a new MCP server for handling Omnigraph queries at the `/api/mcp` endpoint. - Updated OpenAPI document to exclude the new MCP endpoint from generated documentation. - Added documentation for integrating with the Omnigraph MCP, including usage instructions for clients like Cursor and Claude. - Enhanced existing documentation with a new link card for the Omnigraph MCP.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.gitignore:
- Around line 50-52: The `data-*` pattern on line 52 is too broad and will
ignore matching files or directories anywhere in the repository, potentially
hiding legitimate assets or fixtures. Scope this pattern more narrowly by
placing it under the same directory constraint as the npm skills entry above it
(the `**/skills/npm-*` pattern), so that `data-*` files are only ignored when
they exist within the npm-managed skills directory tree, not globally across the
entire repository.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 5580846c-9e8a-4819-ba3b-6c101e38393a
📒 Files selected for processing (5)
.gitignoreapps/ensapi/src/handlers/api/mcp/mcp-api.tsapps/ensapi/src/openapi-document.tsdocs/ensnode.io/src/content/docs/docs/integrate/ai-llm.mdxdocs/ensnode.io/src/content/docs/docs/integrate/integration-options/omnigraph-mcp.mdx
| # Agent skills from npm packages (managed by skills-npm) | ||
| **/skills/npm-* | ||
| data-* |
There was a problem hiding this comment.
Scope the new data-* ignore entry more narrowly.
As written, data-* ignores any matching file or directory anywhere in the repo, which can accidentally hide legitimate fixtures or docs assets. If these artifacts are only generated under the npm-managed skill tree, keep the pattern scoped there.
Proposed fix
- data-*
+ **/skills/npm-*/data-*📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| # Agent skills from npm packages (managed by skills-npm) | |
| **/skills/npm-* | |
| data-* | |
| # Agent skills from npm packages (managed by skills-npm) | |
| **/skills/npm-* | |
| **/skills/npm-*/data-* |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.gitignore around lines 50 - 52, The `data-*` pattern on line 52 is too
broad and will ignore matching files or directories anywhere in the repository,
potentially hiding legitimate assets or fixtures. Scope this pattern more
narrowly by placing it under the same directory constraint as the npm skills
entry above it (the `**/skills/npm-*` pattern), so that `data-*` files are only
ignored when they exist within the npm-managed skills directory tree, not
globally across the entire repository.
|
|
||
| The server exposes a single, read-only tool: | ||
|
|
||
| - **`omnigraph_query`** — accepts a GraphQL `query` (and optional `variables`) and returns the raw `{ data, errors }` JSON, exactly as the [HTTP endpoint](/docs/integrate/integration-options/omnigraph-graphql-api#1-the-endpoint) does. Because it's the full Omnigraph behind one tool, an agent can answer any question the Omnigraph can — resolve records, search Domains, list a user's Domains, and much more — without a fixed, hand-written tool per use case. |
| "Before writing custom GraphQL:", | ||
| "1. Read omnigraph://schema/condensed or call omnigraph_schema.", | ||
| "2. Prefer omnigraph_query with exampleId (vetted queries) over guessing field names.", | ||
| "3. Read omnigraph://examples/index for available exampleId values.", | ||
| "", | ||
| "Entry points:", | ||
| "- account(by: { address }) — address-owned domains, permissions, reverse resolution", | ||
| "- domain(by: { name }) — a single name", | ||
| "- domains(where: { … }, first: N) — search canonical domains", | ||
| "", | ||
| "Common patterns:", | ||
| "- Address overview (primary name + profile + ENSv1/v2 counts): exampleId account-profile", | ||
| "- Primary name: account.resolve.primaryName(by: { chainName: ETHEREUM })", | ||
| "- Profile: domain.resolve.profile or primaryName.resolve.profile", |
| /** Active MCP sessions keyed by `mcp-session-id` (one server + transport pair per client). */ | ||
| const sessions = new Map<string, McpSession>(); | ||
|
|
||
| /** Cap stored sessions to limit memory growth from repeated initialize requests. */ | ||
| const MAX_MCP_SESSIONS = 200; |
…ndle SDL for Vite/Rollup compatibility
| export function listGraphqlApiExampleQueryIds(): string[] { | ||
| return GRAPHQL_API_EXAMPLE_QUERIES.map((example) => example.id); | ||
| } |
| if (sessions.size > MAX_MCP_SESSIONS) { | ||
| const oldestId = sessions.keys().next().value; | ||
| if (typeof oldestId === "string" && oldestId !== id) { | ||
| void closeSession(oldestId); | ||
| } | ||
| } |
Greptile SummaryThis PR introduces a first-party MCP (Model Context Protocol) server at
Confidence Score: 4/5Safe to merge with one fix: the condensed schema resource served to MCP agents tells them to run The MCP session lifecycle, eviction logic, validation hints, and example-query alias handling are all well-implemented and thoroughly tested. The one concrete issue is in packages/ensnode-sdk/src/omnigraph-api/schema-reference.ts — the "Other types" section in Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Agent as AI Agent (MCP Client)
participant ENSApi as ENSApi /api/mcp
participant Session as McpSession (Server+Transport)
participant Omnigraph as /api/omnigraph (Yoga)
Agent->>ENSApi: POST initialize (no session header)
ENSApi->>Session: createOmnigraphMcpServer() + StreamableHTTPTransport
Session-->>ENSApi: onsessioninitialized(sessionId) → storeSession
ENSApi-->>Agent: 200 mcp-session-id: id
Agent->>ENSApi: GET (SSE stream, mcp-session-id header)
ENSApi->>Session: touchSession + evictIdleSessions
Session-->>Agent: text/event-stream open
Agent->>ENSApi: POST omnigraph_query (mcp-session-id header)
ENSApi->>Session: transport.handleRequest
Session->>Omnigraph: internal fetch POST /api/omnigraph
Omnigraph-->>Session: "{ data, errors } JSON"
Session->>Session: appendValidationHints
Session-->>Agent: "tool result { content: [text] }"
Agent->>ENSApi: DELETE (mcp-session-id header)
ENSApi->>Session: transport.handleRequest + closeSession
Session->>Session: sessions.delete, transport.close, server.close
ENSApi-->>Agent: 202
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant Agent as AI Agent (MCP Client)
participant ENSApi as ENSApi /api/mcp
participant Session as McpSession (Server+Transport)
participant Omnigraph as /api/omnigraph (Yoga)
Agent->>ENSApi: POST initialize (no session header)
ENSApi->>Session: createOmnigraphMcpServer() + StreamableHTTPTransport
Session-->>ENSApi: onsessioninitialized(sessionId) → storeSession
ENSApi-->>Agent: 200 mcp-session-id: id
Agent->>ENSApi: GET (SSE stream, mcp-session-id header)
ENSApi->>Session: touchSession + evictIdleSessions
Session-->>Agent: text/event-stream open
Agent->>ENSApi: POST omnigraph_query (mcp-session-id header)
ENSApi->>Session: transport.handleRequest
Session->>Omnigraph: internal fetch POST /api/omnigraph
Omnigraph-->>Session: "{ data, errors } JSON"
Session->>Session: appendValidationHints
Session-->>Agent: "tool result { content: [text] }"
Agent->>ENSApi: DELETE (mcp-session-id header)
ENSApi->>Session: transport.handleRequest + closeSession
Session->>Session: sessions.delete, transport.close, server.close
ENSApi-->>Agent: 202
Reviews (5): Last reviewed commit: "feat(ensapi): refine Omnigraph MCP serve..." | Re-trigger Greptile |
…on for Omnigraph queries
…te Omnigraph tools documentation
…ts and remove specific task prompts
…put validation for queries
Lite PR
Tip: Review docs on the ENSNode PR process
Summary
/api/mcpon every ENSApi instance (streamable-HTTP), with tools (omnigraph_query,omnigraph_schema), schema/example resources, prompts, and validation hints for common GraphQL mistakes.@ensnode/ensnode-sdk/internal(lookupOmnigraphSchema,buildCondensedSchemaReference) with bundled SDL so schema loading works in Vite/Rollup builds (no NodecreateRequire).omnigraph-mcp.mdx, AI/LLM integration page link) and refactors enscli / ensskills to share the same schema helpers.Why
Testing
Notes for Reviewer (Optional)
Pre-Review Checklist (Blocking)