How OpenSEO Implements JSON-RPC Transport for Its MCP Server
OpenSEO implements JSON-RPC transport for its MCP server by leveraging the WebStandardStreamableHTTPServerTransport class from the Model Context Protocol SDK with enableJsonResponse enabled, routing legacy requests through specialized handlers in src/server/mcp/transport.ts while maintaining strict CORS and validation policies.
The OpenSEO repository provides a robust Model Context Protocol (MCP) implementation that supports both modern Server-Sent Events (SSE) streaming and traditional JSON-RPC request/response patterns. Understanding how OpenSEO implements JSON-RPC transport reveals a dual-mode architecture designed to accommodate both legacy clients and modern streaming consumers within a unified request handler.
Core JSON-RPC Transport Architecture
Transport Layer Configuration
At the heart of the implementation lies the WebStandardStreamableHTTPServerTransport class imported from the @modelcontextprotocol/server package. When configured with enableJsonResponse: true, this transport buffers the entire MCP response into a single JSON payload instead of streaming SSE events. This configuration is essential for supporting traditional JSON-RPC clients that expect a complete response in one HTTP round-trip.
The transport instantiation occurs within the handleLegacyJsonRequest function, where it is paired with an OpenSEO-specific server instance:
// src/server/mcp/transport.ts
const transport = new WebStandardStreamableHTTPServerTransport({
sessionIdGenerator: undefined,
enableJsonResponse: true, // Enables JSON-RPC mode
});
CORS Handling for Cross-Origin Requests
To ensure consistent behavior across both transport modes, OpenSEO defines a constant MCP_CORS_HEADERS that mirrors the Model Context Protocol SDK's default CORS options. The helper function withMcpCors injects these headers into every response, guaranteeing that legacy JSON-RPC calls receive the same cross-origin treatment as modern SSE connections. This prevents CORS-related failures when browser-based clients or cross-origin scripts invoke the MCP endpoint.
Legacy Request Processing Pipeline
Request Validation and Origin Checking
Before processing any legacy JSON-RPC call, OpenSEO enforces strict security through the validateLegacyRequest function. This utility applies the same host and origin validation checks used by the Agents SDK for modern requests, utilizing hostHeaderValidationResponse and originValidationResponse to inspect incoming headers. Requests that fail these checks are immediately rejected with appropriate error responses, to which CORS headers are appended to ensure the client can read the rejection reason.
The handleLegacyJsonRequest Method
The handleLegacyJsonRequest function serves as the primary entry point for JSON-RPC processing when a POST request lacks the modern _meta envelope. The implementation follows a strict three-step protocol:
-
Method Guarding – Rejects any non-POST requests with a JSON-RPC error object (
code: -32000), ensuring only valid JSON-RPC method invocations proceed. -
Server Instantiation – Creates an OpenSEO-specific MCP server via
createOpenSeoMcpServer(props), which configures the available tools and capabilities for the requesting context. -
Transport Coordination – Constructs the
WebStandardStreamableHTTPServerTransportwith JSON response mode enabled, then processes the incoming request and wraps the output with CORS headers viawithMcpCors.
// Example: Manual JSON-RPC call to the OpenSEO MCP endpoint
const resp = await fetch("https://open-seo.test/mcp", {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify({
jsonrpc: "2.0",
id: 1,
method: "tools/list",
}),
});
const data = await resp.json(); // JSON-RPC response object
console.log(data);
Request Routing and Entry Points
Unified Request Routing Logic
The createRequestHandler function acts as the central router for all MCP traffic. It first short-circuits OPTIONS preflight requests with static CORS headers, then verifies that the request path matches the MCP_ROUTE constant. The critical routing decision occurs via isLegacyRequest, which detects the absence of the modern _meta envelope. Legacy requests are funneled through validateLegacyRequest and subsequently handleLegacyJsonRequest, while modern requests utilize the SSE-based createMcpHandler.
// Example: Internal invocation of the legacy transport
async function legacyCall(request: Request, props: McpProps) {
// handleLegacyJsonRequest creates server + transport with JSON mode enabled
return await handleLegacyJsonRequest(request, props);
}
Authenticated (Hosted) Flow
For authenticated deployments, handleAuthenticatedOpenSeoMcpRequest extracts the OAuth-validated MCP context from the incoming request. It verifies the required MCP_SCOPE, checks user membership through the authentication repository, and constructs per-request McpProps via createWorkersOAuthMcpProps. These properties are then passed to createRequestHandler, which automatically selects the JSON-RPC transport for legacy requests while applying the hosted security context.
Self-Hosted Flow
Self-hosted instances utilize handleSelfHostedOpenSeoMcpRequest, which resolves identity using either the Cloudflare Access resolver or a local no-authentication resolver. After constructing the appropriate McpProps, the function forwards the request to the same unified createRequestHandler, ensuring that self-hosted deployments retain full JSON-RPC compatibility without requiring OAuth infrastructure.
Summary
- OpenSEO uses
WebStandardStreamableHTTPServerTransportwithenableJsonResponse: trueto buffer MCP responses into single JSON payloads for legacy clients. - The
handleLegacyJsonRequestfunction insrc/server/mcp/transport.tsguards HTTP methods, instantiates the MCP server, and manages JSON-specific transport configuration. - CORS headers are standardized across both transport modes via
MCP_CORS_HEADERSand thewithMcpCorshelper to ensure cross-origin compatibility. validateLegacyRequestenforces host and origin validation using SDK utilitieshostHeaderValidationResponseandoriginValidationResponsebefore processing legacy calls.- The
createRequestHandlerfunction automatically routes requests to the appropriate transport based on the presence of modern_metaenvelopes in the request body. - Both authenticated (
handleAuthenticatedOpenSeoMcpRequest) and self-hosted (handleSelfHostedOpenSeoMcpRequest) entry points support legacy JSON-RPC through the unified routing pipeline.
Frequently Asked Questions
What is the difference between OpenSEO's SSE and JSON-RPC transport modes?
The SSE (Server-Sent Events) mode streams responses as discrete events for modern MCP clients that support progressive updates, while the JSON-RPC mode buffers the entire response into a single JSON payload suitable for traditional HTTP clients. OpenSEO determines which mode to use based on whether the incoming request contains the modern _meta envelope or requires legacy handling.
How does OpenSEO handle CORS for JSON-RPC requests?
OpenSEO uses the MCP_CORS_HEADERS constant to define standard cross-origin headers matching the Model Context Protocol SDK defaults. The withMcpCors helper function wraps every JSON-RPC response—both successful results and validation rejections—with these headers, ensuring browser-based clients and cross-origin scripts can interact with the MCP endpoint without CORS errors.
What happens if a non-POST request is sent to the JSON-RPC endpoint?
The handleLegacyJsonRequest function explicitly guards against non-POST methods by returning a JSON-RPC error object with code -32000. This adheres to the JSON-RPC 2.0 specification expectations while preventing invalid HTTP methods from reaching the MCP server logic.
How does the server distinguish between modern and legacy MCP requests?
The createRequestHandler function relies on isLegacyRequest to detect the absence of the modern _meta envelope in POST request bodies. Requests lacking this envelope are routed to validateLegacyRequest and subsequently handleLegacyJsonRequest for JSON-RPC processing, while requests containing the envelope are handled by the modern SSE-capable createMcpHandler.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →