-
Notifications
You must be signed in to change notification settings - Fork 1.2k
[TT-16492] mcp request handling json rpc routing #7709
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
[TT-16492] mcp request handling json rpc routing #7709
Conversation
…hnologies/tyk into TT-16492-mcp-request-handling-json-rpc-routing
…ting # Conflicts: # apidef/oas/linter_test.go # apidef/oas/mcp_primitive.go # apidef/oas/mcp_primitive_test.go # apidef/oas/mcp_test.go # apidef/oas/middleware.go
…to TT-16492-mcp-request-handling-json-rpc-routing
|
API Changes --- prev.txt 2026-01-30 15:36:27.217940239 +0000
+++ current.txt 2026-01-30 15:36:16.876973654 +0000
@@ -3766,6 +3766,12 @@
resources, prompts). It embeds Operation to reuse all standard middleware
(rate limiting, transforms, caching, etc.).
+func (m *MCPPrimitive) ExtractToExtendedPaths(ep *apidef.ExtendedPathsSet, path string, method string)
+ ExtractToExtendedPaths extracts middleware config, delegating to embedded
+ Operation but allowing MCPPrimitive-specific overrides. Methods without
+ overrides are promoted to Operation. Methods with empty overrides (like
+ extractTransformResponseBodyTo) are effectively disabled for MCP primitives.
+
type MCPPrimitives map[string]*MCPPrimitive
MCPPrimitives maps primitive names to their middleware configurations.
For tools: key is tool name (e.g., "get-weather"). For resources: key is
@@ -8367,8 +8373,10 @@
SelfLooping
// RequestStartTime holds the time when the request entered the middleware chain
RequestStartTime
- // MCPRouting indicates the request came via MCP JSON-RPC routing
- MCPRouting
+ // JsonRPCRouting indicates the request came via JSON-RPC routing (MCP, A2A, etc.)
+ JsonRPCRouting
+ // JSONRPCRequest stores parsed JSON-RPC request data for protocol routing (MCP, A2A, etc.)
+ JSONRPCRequest
)
# Package: ./dlpython
@@ -9474,6 +9482,13 @@
func (a *APISpec) Expired() bool
+func (a *APISpec) FindAllVEMChainSpecs(r *http.Request, rxPaths []URLSpec, mode URLStatus) []*URLSpec
+ FindAllVEMChainSpecs returns all URLSpecs matching the given status from
+ the VEM chain. For MCP APIs with JSON-RPC routing, this returns specs from
+ all VEMs in the chain (operation VEM + tool VEM), allowing middleware to be
+ applied at each stage. For non-MCP APIs, it returns only the spec matching
+ the current path.
+
func (a *APISpec) FindSpecMatchesStatus(r *http.Request, rxPaths []URLSpec, mode URLStatus) (*URLSpec, bool)
FindSpecMatchesStatus checks if a URL spec has a specific status and returns
the URLSpec for it.
@@ -10781,6 +10796,28 @@
Timestamp time.Time
}
+type JSONRPCError struct {
+ Code int `json:"code"`
+ Message string `json:"message"`
+ Data interface{} `json:"data,omitempty"`
+}
+ JSONRPCError represents a JSON-RPC 2.0 error object.
+
+type JSONRPCErrorResponse struct {
+ JSONRPC string `json:"jsonrpc"`
+ Error JSONRPCError `json:"error"`
+ ID interface{} `json:"id"`
+}
+ JSONRPCErrorResponse represents a JSON-RPC 2.0 error response.
+
+type JSONRPCRequest struct {
+ JSONRPC string `json:"jsonrpc"`
+ Method string `json:"method"`
+ Params json.RawMessage `json:"params,omitempty"`
+ ID interface{} `json:"id,omitempty"`
+}
+ JSONRPCRequest represents a JSON-RPC 2.0 request structure.
+
type JSVM struct {
Spec *APISpec
VM *otto.Otto `json:"-"`
@@ -10962,6 +10999,25 @@
Init initializes the LogMessageEventHandler instance with the given
configuration.
+type MCPJSONRPCMiddleware struct {
+ *BaseMiddleware
+}
+ MCPJSONRPCMiddleware handles JSON-RPC 2.0 request detection and routing
+ for MCP APIs. When a client sends a JSON-RPC request to an MCP endpoint,
+ the middleware detects it, extracts the method and primitive name, routes to
+ the correct VEM, and enables the middleware chain to execute before proxying
+ to upstream.
+
+func (m *MCPJSONRPCMiddleware) EnabledForSpec() bool
+ EnabledForSpec returns true if this middleware should be enabled for the API
+ spec. It requires the API to be an MCP API with JSON-RPC 2.0 protocol.
+
+func (m *MCPJSONRPCMiddleware) Name() string
+ Name returns the middleware name.
+
+func (m *MCPJSONRPCMiddleware) ProcessRequest(w http.ResponseWriter, r *http.Request, _ interface{}) (error, int)
+ ProcessRequest handles JSON-RPC request detection and routing.
+
type MethodNotAllowedHandler struct{}
func (m MethodNotAllowedHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
@@ -11250,6 +11306,14 @@
func (k *OrganizationMonitor) SetOrgSentinel(orgChan chan bool, orgId string)
+type ParamExtractionResult struct {
+ Value string
+ ErrorMessage string
+ IsValid bool
+}
+ ParamExtractionResult represents the result of extracting a parameter from
+ JSON-RPC params.
+
type PersistGraphQLOperationMiddleware struct {
*BaseMiddleware
} |
|
This pull request introduces a significant enhancement to the gateway's Model Context Protocol (MCP) capabilities by adding a native Previously, MCP traffic was largely opaque to the gateway's middleware chain. This change addresses that by parsing the JSON-RPC payload, rewriting the request to an internal-only Virtual Endpoint (VEM), and then running it through the standard middleware pipeline. This also introduces the concept of a "VEM Chain" to apply layered policies (e.g., operation-level then primitive-level), which resolves a critical bug where operation-level and global API rate limits were not being enforced on MCP APIs. Files Changed AnalysisThe changes are substantial, with over 3,200 additions across 19 files, centered around the new middleware, its extensive test suite, and a more generic protocol-handling framework.
Architecture & Impact Assessment
JSON-RPC Request Handling FlowsequenceDiagram
participant Client
participant Tyk Gateway
participant MCPJSONRPCMiddleware
participant RateLimitMiddleware
participant VersionCheckMiddleware
participant Upstream Service
Client->>Tyk Gateway: POST /mcp (JSON-RPC: method='tools/call', params={name:'get-weather'})
Tyk Gateway->>MCPJSONRPCMiddleware: ProcessRequest
MCPJSONRPCMiddleware->>MCPJSONRPCMiddleware: Parse body, build VEM chain ['/mcp-operation:tools/call', '/mcp-tool:get-weather']
MCPJSONRPCMiddleware->>Tyk Gateway: Rewrite URL to '/mcp-tool:get-weather' & set JsonRPCRouting=true
Tyk Gateway->>RateLimitMiddleware: Process request for VEM chain
RateLimitMiddleware->>RateLimitMiddleware: Check rate limits for both VEMs in the chain
RateLimitMiddleware->>VersionCheckMiddleware: Forward request
VersionCheckMiddleware->>VersionCheckMiddleware: Validate access to internal VEM via JsonRPCRouting flag
VersionCheckMiddleware->>Upstream Service: Proxy original request body
Upstream Service-->>Client: JSON-RPC Response
Scope Discovery & Context Expansion
Metadata
Powered by Visor from Probelabs Last updated: 2026-01-30T15:39:04.132Z | Triggered by: pr_updated | Commit: 3f6f2c4 💡 TIP: You can chat with Visor using |
✅ Security Check PassedNo security issues found – changes LGTM. Architecture Issues (2)
Performance Issues (3)
Quality Issues (1)
Powered by Visor from Probelabs Last updated: 2026-01-30T15:39:07.387Z | Triggered by: pr_updated | Commit: 3f6f2c4 💡 TIP: You can chat with Visor using |
…to TT-16492-mcp-request-handling-json-rpc-routing
…to TT-16492-mcp-request-handling-json-rpc-routing # Conflicts: # gateway/api_definition.go
…eneration # Conflicts: # gateway/api_definition.go # gateway/model_apispec.go
…to TT-16492-mcp-request-handling-json-rpc-routing # Conflicts: # gateway/model_apispec.go
…ting # Conflicts: # ctx/ctx.go # gateway/api_definition.go # gateway/mcp_vem_test.go # internal/mcp/mcp.go
gateway/model_apispec.go
Outdated
| } | ||
|
|
||
| if a.Proxy.ListenPath != "/" { | ||
| if a.Proxy.ListenPath != "/" && !agentprotocol.IsProtocolVEMPath(matchPath) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Q: would it be beneficial to do this only if the API is JSON-RPC and the payload is JSON-RPC
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
refactored
gateway/mw_mcp_jsonrpc.go
Outdated
|
|
||
| // maxJSONRPCRequestSize is the maximum allowed size for JSON-RPC request bodies. | ||
| // This limit protects against DoS attacks when the global MaxRequestBodySize is not configured. | ||
| const maxJSONRPCRequestSize = 1 << 20 // 1MB |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we use the GW config for the request size limit
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also we have at MCP definition level this config
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
adapted
gateway/mw_mcp_jsonrpc.go
Outdated
| return nil, middleware.StatusRespond | ||
| } | ||
| if !found { | ||
| if m.mcpAllowListEnabled() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure why we need to do custom Allow middleware check here, since the JSON-RPC methods are transformed in Paths, and the request to the internal VEM should behave same as a normal path request and all middleware should run and be applied (including Allow)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
recatored
| }) | ||
|
|
||
| // Enable JSON-RPC routing (allows access to internal VEM endpoints) | ||
| httpctx.SetJsonRPCRouting(r, true) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@andrei-tyk to check why is this still here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
refactored
gateway/mw_mcp_jsonrpc.go
Outdated
| } | ||
| } | ||
|
|
||
| func (m *MCPJSONRPCMiddleware) mcpAllowListEnabled() bool { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@andrei-tyk to check here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
refactored
|
|
||
| // matchResourceURI matches a resource URI against configured patterns. | ||
| // It first tries an exact match, then falls back to wildcard matching. | ||
| func (m *MCPJSONRPCMiddleware) matchResourceURI(uri string, primitives map[string]string) (vemPath string, found bool) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
our paths already have support for regexes. Once the resource mcpPrimitive is stored as a path, we should have regex handling by defualt
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we need some separate logic for them as mcp have regex like characters in them and the OAS logic has support for {id} params
gateway/mw_mcp_jsonrpc.go
Outdated
|
|
||
| // shouldPassthrough returns true if the method should be passed through to upstream | ||
| // without requiring a configured VEM (e.g., discovery operations, notifications). | ||
| func (m *MCPJSONRPCMiddleware) shouldPassthrough(method string) bool { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should passthrough by default not just for notifications
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
refactored and fixed
| // MCP JSON-RPC method names as defined in the Model Context Protocol specification. | ||
| const ( | ||
| // Tool methods | ||
| MethodToolsCall = "tools/call" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we need to intercept just tools/call resource/read and prompts/get
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cleaned it up
- Add constants for JSON-RPC parameter keys, primitive prefixes, and error messages - Replace magic strings throughout gateway/mw_mcp_jsonrpc.go - Update test to use constant for error message assertion - Use consistent key format in mcp_vem.go
- Add extractAndValidateParam helper to eliminate repeated validation pattern - Replace switch statement with methodPrefixMap for cleaner buildUnregisteredVEMPath - Simplify parameter extraction in routeRequest method
- Extract validateJSONRPCRequest to check request type - Extract readAndParseJSONRPC to handle parsing and validation - Extract setupJSONRPCRouting to configure context and routing - Simplify ProcessRequest to show high-level flow - Improve variable naming in matchResourceURI for better readability
- Add detailed precedence rules for matchResourceURI wildcard matching - Expand passthrough comment to explain discovery, notifications, and operations - Clarify which requests are handled by upstream MCP server
Operation-level rate limits were ignored after JSON-RPC routing to VEM paths. Fixed by extracting operation middleware using listenPath for MCP APIs and checking rate limits on both VEM and original paths.
…on-rpc-routing' into TT-16492-mcp-request-handling-json-rpc-routing
|



Description
Related Issue
Motivation and Context
How This Has Been Tested
Screenshots (if appropriate)
Types of changes
Checklist
Ticket Details
TT-16492
Generated at: 2026-01-30 15:35:49