This repository was archived by the owner on Aug 5, 2026. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 19
feat(ensapi): introduce Model Context Protocol (MCP) server for Omnigraph API #2301
Open
djstrong
wants to merge
16
commits into
main
Choose a base branch
from
omnigraph-mcp
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
b419590
feat(ensapi): add MCP server for Omnigraph queries and update documen…
djstrong ed71208
feat(ensapi): integrate MCP API and add tests for Omnigraph queries
djstrong fbade93
feat(ensapi): update MCP API with new Omnigraph features and tests
djstrong 1d67e55
feat(ensapi): add offline Omnigraph schema lookup helpers and enhance…
djstrong d7f01d0
feat(ensapi): refine Omnigraph schema input validation and enhance er…
djstrong d397758
feat(ensapi): add auto-generated Omnigraph schema SDL file for enhanc…
djstrong 9bddeb2
fix(ensapi): improve error handling in MCP API and update schema SDL …
djstrong 605d015
refactor(ensapi): update Omnigraph schema handling and CI configuration
djstrong d793f4f
feat(ensapi): enhance MCP API tests and update example queries
djstrong 8b4c1f8
feat(ensapi): enhance MCP API with new filter input types and validat…
djstrong ab4d17d
feat(ensnode-sdk): add offline Omnigraph schema lookup helpers and bu…
djstrong 9836b3e
feat(ensapi): add Omnigraph MCP server and enable custom GraphQL queries
djstrong 8ce8d07
feat(ensapi): improve MCP API session management and enhance validati…
djstrong 891db57
feat(ensapi): implement least-recently-used session eviction and upda…
djstrong ba25bec
feat(ensapi): update Omnigraph MCP server to use plain-language promp…
djstrong 1509598
feat(ensapi): refine Omnigraph MCP server instructions and enhance in…
djstrong File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "ensapi": patch | ||
| --- | ||
|
|
||
| Add Omnigraph MCP server at `/api/mcp` (streamable HTTP). MCP clients can run vetted example queries or custom GraphQL via `omnigraph_query`, with schema and example resources. Server name is `ENS`; task-specific MCP prompts removed. Initialize instructions require `exampleId` first and inline the full example catalog. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@ensnode/ensnode-sdk": patch | ||
| --- | ||
|
|
||
| Add offline Omnigraph schema lookup helpers (`lookupOmnigraphSchema`, `buildCondensedSchemaReference`) on `@ensnode/ensnode-sdk/internal` for enscli and ensskills. Bundle Omnigraph SDL in ensnode-sdk (generated from `enssdk/omnigraph/schema.graphql`) so schema loading works in Vite/Rollup builds without Node `createRequire`. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -20,6 +20,7 @@ dist | |||||||||||||
| # Ponder | ||||||||||||||
| generated | ||||||||||||||
| !packages/enssdk/src/omnigraph/generated/ | ||||||||||||||
| !packages/ensnode-sdk/src/omnigraph-api/generated/ | ||||||||||||||
| .ponder | ||||||||||||||
|
|
||||||||||||||
| #jetbrains elements | ||||||||||||||
|
|
@@ -46,3 +47,7 @@ apps/ensrainbow/test-* | |||||||||||||
| apps/fallback-ensapi/dist | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| # Agent skills from npm packages (managed by skills-npm) | ||||||||||||||
| **/skills/npm-* | ||||||||||||||
| data-* | ||||||||||||||
|
Comment on lines
+51
to
+53
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Scope the new As written, Proposed fix- data-*
+ **/skills/npm-*/data-*📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
113 changes: 113 additions & 0 deletions
113
apps/ensapi/src/handlers/api/mcp/mcp-api.integration.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,113 @@ | ||
| import { Client } from "@modelcontextprotocol/sdk/client/index.js"; | ||
| import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; | ||
| import { afterEach, beforeEach, describe, expect, it } from "vitest"; | ||
|
|
||
| const ensnodeUrl = process.env.ENSNODE_URL; | ||
| if (!ensnodeUrl) { | ||
| throw new Error( | ||
| "ENSNODE_URL environment variable must be configured for Omnigraph MCP integration tests to run.", | ||
| ); | ||
| } | ||
|
|
||
| const MCP_URL = new URL("/api/mcp", ensnodeUrl); | ||
|
|
||
| type TextContent = { type: "text"; text: string }; | ||
|
|
||
| /** Connects an MCP client to the live ENSApi `/api/mcp` endpoint over Streamable HTTP. */ | ||
| async function connect() { | ||
| const client = new Client({ name: "ensapi-integration-test", version: "0.0.0" }); | ||
| const transport = new StreamableHTTPClientTransport(MCP_URL); | ||
| await client.connect(transport); | ||
| return client; | ||
| } | ||
|
|
||
| /** Calls `omnigraph_query` and parses the single text content block as GraphQL JSON. */ | ||
| async function omnigraphQuery<T = unknown>( | ||
| client: Client, | ||
| query: string, | ||
| variables?: Record<string, unknown>, | ||
| ): Promise<{ data?: T; errors?: Array<{ message: string }> }> { | ||
| const result = await client.callTool({ | ||
| name: "omnigraph_query", | ||
| arguments: { query, variables }, | ||
| }); | ||
|
|
||
| const content = result.content as TextContent[]; | ||
| expect(content).toHaveLength(1); | ||
| expect(content[0].type).toBe("text"); | ||
| return JSON.parse(content[0].text); | ||
| } | ||
|
|
||
| describe("Omnigraph MCP API (/api/mcp)", () => { | ||
| let client: Client; | ||
|
|
||
| beforeEach(async () => { | ||
| client = await connect(); | ||
| }); | ||
|
|
||
| afterEach(async () => { | ||
| await client.close(); | ||
| }); | ||
|
|
||
| it("advertises the omnigraph_query tool", async () => { | ||
| const { tools } = await client.listTools(); | ||
| expect(tools.map((t) => t.name)).toEqual( | ||
| expect.arrayContaining(["omnigraph_query", "omnigraph_schema"]), | ||
| ); | ||
| }); | ||
|
|
||
| it("executes a trivial query end-to-end through Yoga", async () => { | ||
| const { data, errors } = await omnigraphQuery<{ __typename: string }>(client, "{ __typename }"); | ||
|
|
||
| expect(errors).toBeUndefined(); | ||
| expect(data).toEqual({ __typename: "Query" }); | ||
| }); | ||
|
|
||
| it("resolves a seeded devnet domain via Query.domain", async () => { | ||
| const { data, errors } = await omnigraphQuery<{ | ||
| domain: { canonical: { name: { interpreted: string } } } | null; | ||
| }>( | ||
| client, | ||
| /* GraphQL */ ` | ||
| query DomainByName($name: InterpretedName!) { | ||
| domain(by: { name: $name }) { | ||
| canonical { name { interpreted } } | ||
| } | ||
| } | ||
| `, | ||
| { name: "test.eth" }, | ||
| ); | ||
|
|
||
| expect(errors).toBeUndefined(); | ||
| expect(data).toMatchObject({ | ||
| domain: { canonical: { name: { interpreted: "test.eth" } } }, | ||
| }); | ||
| }); | ||
|
|
||
| it("surfaces GraphQL errors for invalid queries in the response payload", async () => { | ||
| const { errors } = await omnigraphQuery(client, "{ thisFieldDoesNotExist }"); | ||
| expect(errors).toBeDefined(); | ||
| expect(errors?.length ?? 0).toBeGreaterThan(0); | ||
| }); | ||
|
|
||
| it("runs a vetted example query by exampleId", async () => { | ||
| const result = await client.callTool({ | ||
| name: "omnigraph_query", | ||
| arguments: { | ||
| exampleId: "domain-by-name", | ||
| variables: { name: "test.eth" }, | ||
| }, | ||
| }); | ||
|
|
||
| const content = result.content as TextContent[]; | ||
| const { data, errors } = JSON.parse(content[0].text) as { | ||
| data?: { domain: { canonical: { name: { interpreted: string } } } | null }; | ||
| errors?: unknown[]; | ||
| }; | ||
|
|
||
| expect(errors).toBeUndefined(); | ||
| expect(data).toMatchObject({ | ||
| domain: { canonical: { name: { interpreted: "test.eth" } } }, | ||
| }); | ||
| }); | ||
| }); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.