feat(mcp): support custom JSON-RPC methods and capabilities - #2420
feat(mcp): support custom JSON-RPC methods and capabilities#2420zensucht wants to merge 6 commits into
Conversation
Per JSON-RPC 2.0, notifications never receive a response. Unknown notifications/* methods previously got a -32601 error response; they are now dropped with a debug log.
Adds customMethods and customCapabilities to the MCP plugin config. Custom methods are dispatched on the MCP endpoint after the built-ins and receive a context with executeGraphQL (full server pipeline, request headers and server context forwarded), getSchema, and transport details. MCPMethodError produces JSON-RPC error responses with specific codes. Name collisions with built-in methods fail at startup.
Code Review by Qodo
1.
|
Code Review by Qodo
1. Unknown notifications still respond
|
PR Summary by QodoMCP: add custom JSON-RPC methods/capabilities with spec-correct notifications WalkthroughsDescription• Add customMethods and customCapabilities to extend the MCP JSON-RPC surface. • Dispatch built-ins via a registry and drop unknown notifications per JSON-RPC 2.0. • Execute custom GraphQL operations through Yoga with forwarded headers and full pipeline. Diagramflowchart TD
A["MCP client"] --> B["POST /mcp"] --> C["useMCP plugin"] --> D["JSON-RPC dispatch"]
D --> E["Built-in methods"] --> H["Response / 204"]
D --> F["Custom methods"] --> G["Yoga handle /graphql"] --> H
High-Level AssessmentThe following are alternative approaches to this PR: 1. Use MCP tools (`tools/call`) instead of adding custom JSON-RPC methods
2. Adopt a generic JSON-RPC router/middleware library for dispatch
3. Expose an MCP dispatcher hook (middleware) instead of `customMethods` map
Recommendation: The PR’s approach (explicit File ChangesEnhancement (4)
Tests (2)
Documentation (2)
|
There was a problem hiding this comment.
Code Review
This pull request introduces support for customMethods and customCapabilities in the MCP plugin configuration, allowing users to register custom JSON-RPC methods and advertise them in the initialize response. Custom handlers receive a context that enables executing GraphQL operations through the full server pipeline with forwarded headers, accessing the schema, and retrieving transport details. Additionally, unknown notification methods are now silently ignored per the JSON-RPC 2.0 specification. The review feedback suggests improving robustness by stripping additional hop-by-hop headers (such as host and transfer-encoding) during internal GraphQL execution, and validating that customMethods and customCapabilities are plain objects at startup.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
Review follow-ups: - Guard custom method lookup with Object.hasOwn so prototype-chain names (constructor, toString) resolve to -32601 instead of being dispatched as handlers - Drop unknown notifications/* methods even when an id is sent, matching the custom-method dispatch behavior - Strip transport-mechanics headers (host, connection, keep-alive, transfer-encoding) from internal GraphQL execution requests - Validate customMethods and customCapabilities are plain objects at startup, covering non-TypeScript config paths
|
Addressed the review findings in 3a14272:
One finding not addressed here: the |
…rnal responses - Strip the full RFC 7230 hop-by-hop set (proxy-authorization, proxy-authenticate, te, trailer, upgrade) from internal GraphQL execution requests - Non-OK internal responses without GraphQL result shape (e.g. a 401 JSON body from an auth plugin) now surface as errors instead of being returned as execution results; GraphQL-shaped error responses still pass through as data
|
Pushed 09ad6fa from a further hardening pass on this branch's internals:
|
Description
@graphql-hive/plugin-mcpdispatches a fixed set of MCP methods. This PR makes that dispatch extensible: integrators can serve additional JSON-RPC methods from the MCP endpoint viacustomMethodsand advertise matching entries in theinitializeresponse viacustomCapabilities.Handlers receive a context with
executeGraphQL(runs the operation through the full server pipeline — every configured plugin sees it, original request headers forwarded),getSchema(), andtransport(the incoming request and its headers). The return value becomes the JSON-RPCresult; throwingMCPMethodErrorproduces an error response with a specific code.This keeps the plugin's built-in surface untouched while letting gateway operators expose purpose-built methods (for example, direct GraphQL execution for agent clients) without forking the plugin.
Commits
refactor(mcp): extract built-in method dispatch into a registry— behavior-preserving restructure ofhandleMCPRequest: the six built-in method cases move from aswitchinto named handler functions dispatched via a map. No public API change; the existing test suite passes unchanged. This is the bulk of the diff by line count.fix(mcp): suppress responses for unknown notification methods— spec-correctness fix found while restructuring the dispatcher: unknownnotifications/*methods previously received a-32601error response, but per JSON-RPC 2.0 §4.1 notifications never receive a response. They are now dropped with a debug log. This is the PR's only behavior change to an existing path; it is called out in the changeset.feat(mcp): support custom JSON-RPC methods and capabilities— the feature: public types (MCPMethodHandler,MCPMethodContext,MCPMethodTransport,MCPGraphQLOperation), theMCPMethodErrorclass, config fields, dispatch, capability merge, and docs.Design notes
executeGraphQLruns the full pipeline. Internal execution goes throughyoga.handle()— the same entry the server adapter wraps — with the incoming request's server context and forwarded headers. Header-driven plugins (auth, tracing) treat the operation like any HTTP request; the operation shares the surrounding MCP request's server context, which is documented on the context type.tools/callfidelity.tools/callalready executes GraphQL with the original request's headers; custom methods behave identically. Policy (header filtering, rate limits) remains where it already lives — the gateway's plugin chain, which runs on the internal request too.customMethodsnames that shadow built-ins (initialize,tools/list,tools/call,resources/list,resources/templates/list,resources/read,notifications/initialized) throw with a message listing the conflicts. Non-function values also throw.notifications/never produce a response, even if a client mistakenly sends an id — matching the built-innotifications/initializedbehavior. Handler errors on notifications are logged and swallowed.pathto collide with the GraphQL endpoint.Testing
28 new tests (351 total in the plugin):
MCPMethodErrorconversion (with and withoutdata), error bubbling, notification semantics (no-id, with-id, thrown errors), built-in-first ordering, unknown-method fallthrough.tools/callreservation), non-function rejection.createGatewayRuntimewith a proxied upstream: full round trip over HTTP, capability advertisement, pipeline execution verified by anonExecutehook firing and the upstream resolver being reached, header forwarding into internal execution, HTTP 204 for notifications, and a second gateway whose/graphqlreturns 401 HTML to pin the non-JSON error path.Changeset
Minor bump for
@graphql-hive/plugin-mcp. The notification behavior change is documented there.