Skip to content

Conversation

@lghiur
Copy link
Collaborator

@lghiur lghiur commented Jan 31, 2026

Description

Related Issue

Motivation and Context

How This Has Been Tested

Screenshots (if appropriate)

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Refactoring or add test (improvements in base code or adds test coverage to functionality)

Checklist

  • I ensured that the documentation is up to date
  • I explained why this PR updates go.mod in detail with reasoning why it's required
  • I would like a code coverage CI quality gate exception and have explained why

Ticket Details

TT-16492
Status In Dev
Summary MCP Request Handling & JSON-RPC Routing

Generated at: 2026-02-01 06:26:09

andrei-tyk and others added 30 commits January 27, 2026 11:04
…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
…to TT-16492-mcp-request-handling-json-rpc-routing
…to TT-16492-mcp-request-handling-json-rpc-routing
andrei-tyk and others added 15 commits January 30, 2026 14:25
- 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
@github-actions
Copy link
Contributor

github-actions bot commented Jan 31, 2026

API Changes

--- prev.txt	2026-02-01 06:26:57.357203288 +0000
+++ current.txt	2026-02-01 06:26:47.106221947 +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
@@ -3796,6 +3802,10 @@
 func (m *Middleware) Fill(api apidef.APIDefinition)
     Fill fills *Middleware from apidef.APIDefinition.
 
+func (m *Middleware) HasMCPPrimitivesMocks() bool
+    HasMCPPrimitivesMocks checks if any MCP primitives (tools, resources,
+    prompts) have enabled mock responses.
+
 type MockResponse struct {
 	// Enabled activates the mock response middleware.
 	Enabled bool `bson:"enabled" json:"enabled"`
@@ -5378,6 +5388,9 @@
 }
     ValidateRequest holds configuration required for validating requests.
 
+func (v *ValidateRequest) ExtractTo(meta *apidef.ValidateRequestMeta)
+    ExtractTo extracts *ValidateRequest into apidef.ValidateRequestMeta.
+
 func (v *ValidateRequest) Fill(meta apidef.ValidatePathMeta)
     Fill fills *ValidateRequest receiver from apidef.ValidateRequestMeta.
 
@@ -8367,8 +8380,13 @@
 	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
+	// JSONRPCRoutingState stores the routing state for sequential MCP VEM processing.
+	// Used by MCPJSONRPCMiddleware and MCPVEMContinuationMiddleware.
+	JSONRPCRoutingState
 )
 # Package: ./dlpython
 
@@ -9444,9 +9462,28 @@
 	// Value: VEM path (e.g., "/mcp-tool:get-weather")
 	MCPPrimitives map[string]string
 
+	JSONRPCRouter jsonrpc.Router
+
+	// OperationsAllowListEnabled is true if any JSON-RPC operation (method-level) has
+	// an allow rule enabled. Pre-calculated during API loading.
+	OperationsAllowListEnabled bool
+
+	// ToolsAllowListEnabled is true if any MCP tool has an allow rule enabled.
+	// Pre-calculated during API loading.
+	ToolsAllowListEnabled bool
+
+	// ResourcesAllowListEnabled is true if any MCP resource has an allow rule enabled.
+	// Pre-calculated during API loading.
+	ResourcesAllowListEnabled bool
+
+	// PromptsAllowListEnabled is true if any MCP prompt has an allow rule enabled.
+	// Pre-calculated during API loading.
+	PromptsAllowListEnabled bool
+
 	// MCPAllowListEnabled is true if any MCP primitive (tool, resource, prompt) has an
 	// allow rule enabled. Pre-calculated during API loading to avoid iterating through
 	// all primitives on every JSON-RPC request that doesn't match a VEM.
+	// This is a convenience flag that combines ToolsAllowListEnabled, ResourcesAllowListEnabled, and PromptsAllowListEnabled.
 	MCPAllowListEnabled bool
 	// Has unexported fields.
 }
@@ -10781,6 +10818,46 @@
 	Timestamp time.Time
 }
 
+type JSONRPCError struct {
+	Code    int    `json:"code"`
+	Message string `json:"message"`
+	Data    any    `json:"data,omitempty"`
+}
+    JSONRPCError represents a JSON-RPC 2.0 error object.
+
+type JSONRPCErrorResponse struct {
+	JSONRPC string       `json:"jsonrpc"`
+	Error   JSONRPCError `json:"error"`
+	ID      any          `json:"id"`
+}
+    JSONRPCErrorResponse represents a JSON-RPC 2.0 error response.
+
+type JSONRPCMiddleware struct {
+	*BaseMiddleware
+}
+    JSONRPCMiddleware handles JSON-RPC 2.0 request detection and routing. When
+    a client sends a JSON-RPC request to a JSON-RPC endpoint, the middleware
+    detects it, extracts the method, routes to the correct VEM, and enables the
+    middleware chain to execute before proxying to upstream.
+
+func (m *JSONRPCMiddleware) EnabledForSpec() bool
+    EnabledForSpec returns true if this middleware should be enabled for the API
+    spec. It requires the API to use JSON-RPC 2.0 protocol.
+
+func (m *JSONRPCMiddleware) Name() string
+    Name returns the middleware name.
+
+func (m *JSONRPCMiddleware) ProcessRequest(w http.ResponseWriter, r *http.Request, _ any) (error, int)
+    ProcessRequest handles JSON-RPC request detection and routing.
+
+type JSONRPCRequest struct {
+	JSONRPC string          `json:"jsonrpc"`
+	Method  string          `json:"method"`
+	Params  json.RawMessage `json:"params,omitempty"`
+	ID      any             `json:"id,omitempty"`
+}
+    JSONRPCRequest represents a JSON-RPC 2.0 request structure.
+
 type JSVM struct {
 	Spec    *APISpec
 	VM      *otto.Otto `json:"-"`
@@ -10962,6 +11039,25 @@
     Init initializes the LogMessageEventHandler instance with the given
     configuration.
 
+type MCPVEMContinuationMiddleware struct {
+	*BaseMiddleware
+}
+    MCPVEMContinuationMiddleware handles sequential VEM routing for MCP
+    JSON-RPC requests. After each VEM stage completes its middleware chain,
+    this middleware checks the routing state and either continues to the next
+    VEM or allows the request to proceed to upstream.
+
+func (m *MCPVEMContinuationMiddleware) EnabledForSpec() bool
+    EnabledForSpec returns true if this middleware should run for the given API
+    spec. Only enabled for MCP APIs with JSON-RPC 2.0.
+
+func (m *MCPVEMContinuationMiddleware) Name() string
+    Name returns the middleware name for logging and debugging.
+
+func (m *MCPVEMContinuationMiddleware) ProcessRequest(_ http.ResponseWriter, r *http.Request, _ interface{}) (error, int)
+    ProcessRequest handles VEM chain continuation logic. This is GENERIC routing
+    - just follows the NextVEM chain, no protocol logic.
+
 type MethodNotAllowedHandler struct{}
 
 func (m MethodNotAllowedHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
@probelabs
Copy link

probelabs bot commented Jan 31, 2026

This PR introduces a significant architectural refactor for handling Model Context Protocol (MCP) requests over JSON-RPC. It replaces the previous direct routing mechanism with a new sequential, multi-stage middleware processing pipeline. This enables the granular application of policies, such as rate limiting and access control, at both the general operation level (e.g., for all tools/call requests) and the specific primitive level (e.g., for an individual tool like get-weather).

Files Changed Analysis

This is a substantial feature implementation, reflected in the 30 changed files with 4027 additions and 129 deletions. The changes are concentrated in a few key areas:

  • New Middleware: The core logic is encapsulated in two new middlewares, gateway/mw_jsonrpc.go (the entrypoint for parsing and initiating routing) and gateway/mw_mcp_vem_continuation.go (which orchestrates the sequential flow between stages).
  • VEM Generation Rework: gateway/mcp_vem.go has been heavily modified to generate Virtual Endpoints (VEMs) for both operations and primitives, and to support granular allow-list enforcement.
  • Generic Abstractions: New packages like internal/agentprotocol and internal/jsonrpc, along with the replacement of internal/httpctx/mcp.go with generic jsonrpc context helpers, establish a reusable framework for any JSON-RPC 2.0 based protocol, not just MCP.
  • Extensive Testing: A large number of new test files have been added to validate the new complex, multi-stage request flow.

Architecture & Impact Assessment

  • What this PR accomplishes: It establishes a robust, two-stage middleware execution pipeline for JSON-RPC requests. This ensures that security and traffic management policies defined at both the generic operation level and the specific primitive level are correctly and sequentially applied.
  • Key technical changes introduced:
    1. Sequential VEM Processing: A single incoming JSON-RPC request now triggers a series of internal processing loops. The first executes middleware for the operation-level VEM, and the second executes middleware for the specific primitive's VEM.
    2. Centralized JSON-RPC Handling: The new JSONRPCMiddleware acts as the single entry point for all JSON-RPC 2.0 APIs. It parses the request, determines the VEM chain, and initiates the first internal redirect.
    3. Stateful Routing Context: A new JSONRPCRoutingState object, passed via the request context, tracks the VEM chain and orchestrates the flow between middleware stages.
    4. Granular Allow-list Enforcement: The API spec now contains separate boolean flags (OperationsAllowListEnabled, ToolsAllowListEnabled, etc.) to provide fine-grained access control for different categories of primitives.
  • Affected system components: Gateway Middleware Chain (gateway/api_loader.go), API Definition Loading & VEM Generation (gateway/mcp_vem.go), and Access Control Logic (gateway/mw_version_check.go).

Visualization of the New Request Flow

sequenceDiagram
    participant Client
    participant Gateway as "Gateway (Listen Path)"
    participant JSONRPCMiddleware
    participant OpVEM as "Operation VEM Chain"
    participant ContinuationMiddleware
    participant PrimVEM as "Primitive VEM Chain"
    participant Upstream

    Client->>+Gateway: POST /mcp (JSON-RPC Request)
    Gateway->>+JSONRPCMiddleware: ProcessRequest()
    JSONRPCMiddleware-->>JSONRPCMiddleware: Parse body, build VEM chain: [OpVEM, PrimVEM]
    JSONRPCMiddleware-->>Gateway: Internal redirect to OpVEM
    Gateway->>+OpVEM: Execute operation-level middleware
    OpVEM->>+ContinuationMiddleware: ProcessRequest()
    ContinuationMiddleware-->>ContinuationMiddleware: Get next VEM from context (PrimVEM)
    ContinuationMiddleware-->>Gateway: Internal redirect to PrimVEM
    Gateway->>+PrimVEM: Execute primitive-level middleware
    PrimVEM->>+ContinuationMiddleware: ProcessRequest()
    ContinuationMiddleware-->>ContinuationMiddleware: VEM chain is complete
    ContinuationMiddleware-->>Gateway: Allow request to proceed to upstream
    Gateway->>+Upstream: Forward request
    Upstream-->>-Gateway: Response
    Gateway-->>-Client: Response
Loading

Scope Discovery & Context Expansion

  • The changes establish a generic and extensible framework for handling any JSON-RPC 2.0 based protocol within the gateway, not just MCP. The core logic in mw_jsonrpc.go and the sequential VEM routing pattern can be reused for other agent-based protocols.
  • The refactoring from an MCP-specific MCPRouting context flag to a structured, generic JSONRPCRoutingState is a key enabler for this more complex and capable processing model.
  • The strategic placement of JSONRPCMiddleware at the beginning and MCPVEMContinuationMiddleware at the end of the middleware chain (in gateway/api_loader.go) is the core mechanism enabling this sequential processing.
Metadata
  • Review Effort: 5 / 5
  • Primary Label: feature

Powered by Visor from Probelabs

Last updated: 2026-02-01T06:28:47.769Z | Triggered by: pr_updated | Commit: 7b5be86

💡 TIP: You can chat with Visor using /visor ask <your question>

@probelabs
Copy link

probelabs bot commented Jan 31, 2026

Security Issues (2)

Severity Location Issue
🟠 Error gateway/mw_version_check.go:114
When a request is made to a non-existent MCP primitive and the API is not in allow-list mode, the request is silently forwarded to the upstream service instead of being rejected. The logic at line 114 returns `http.StatusOK`, allowing the request to proceed down the middleware chain to the upstream. This violates the JSON-RPC specification, which requires a 'Method not found' error, and could lead to unintended access to upstream endpoints if a catch-all route exists.
💡 SuggestionThe logic should be modified to always terminate the request and return a JSON-RPC 'Method not found' error (code -32601) when an MCP primitive does not exist, regardless of the allow-list configuration. The request should not be forwarded to the upstream.
🟡 Warning gateway/mw_jsonrpc.go:181
The JSON-RPC error handler reflects the raw error message from the internal `RouteMethod` call directly to the client via `err.Error()`. If an underlying function returns an error containing sensitive details (e.g., stack traces, internal paths, database errors), this information could be leaked to the client.
💡 SuggestionReplace `err.Error()` with a generic, pre-defined error message for the client response (e.g., 'Invalid parameters'). The detailed internal error should be logged on the server side for debugging but not exposed to the client.

Architecture Issues (2)

Severity Location Issue
🟢 Info gateway/api_loader.go:304-553
The new architecture for handling JSON-RPC requests introduces a sequential, multi-stage processing pipeline using internal redirects (e.g., listen path -> operation VEM -> primitive VEM). While this provides powerful, granular middleware application, it has a notable performance implication: the entire middleware chain is executed multiple times for a single incoming client request. Consequently, expensive operations like rate limiting, quota checks, and analytics processing are performed at each stage. This is a fundamental architectural trade-off of flexibility versus performance that should be clearly documented and understood, as it increases the latency and resource consumption for each MCP request.
💡 SuggestionConsider adding documentation to the `JSONRPCMiddleware` or related architectural documents that explicitly states this performance characteristic. For performance-critical paths, this design may require future optimization, such as a mechanism to selectively disable certain middleware for subsequent stages of the internal processing loop.
🟡 Warning apidef/oas/mcp_primitive.go:20-45
The `ExtractToExtendedPaths` method is almost entirely duplicated from the embedded `Operation` struct to override the behavior of a single internal function call (`extractTransformResponseBodyTo`). This creates a significant maintenance risk, as any new middleware extraction added to `Operation.ExtractToExtendedPaths` must be manually copied to this method, otherwise it will be silently ignored for MCP primitives. This violates the DRY (Don't Repeat Yourself) principle and makes the code brittle.
💡 SuggestionRefactor the `Operation.ExtractToExtendedPaths` method to be more composable. For example, break it down into smaller, potentially exported helper methods. This would allow `MCPPrimitive.ExtractToExtendedPaths` to call the shared logic from the embedded `Operation` and only implement the specific override it needs, eliminating the large-scale code duplication and the associated maintenance burden.

Performance Issues (2)

Severity Location Issue
🟡 Warning gateway/mw_jsonrpc.go:101-106
The `readAndParseJSONRPC` function reads the entire request body into a memory buffer using `io.ReadAll`. For large JSON-RPC request bodies, this can cause significant memory pressure, especially under high concurrency. The body is then held in memory for the duration of the request after being restored.
💡 SuggestionTo reduce memory allocations and support larger request bodies more efficiently, consider using a streaming approach. An `io.TeeReader` can be used to wrap the request body, allowing a `json.Decoder` to parse the stream while simultaneously writing the body to a buffer for later proxying. This avoids allocating a single large byte slice for the entire body.
🟡 Warning gateway/mw_version_check.go:121-135
The middleware performs a redundant loop over the API's version paths (`versionPaths`). The `v.Spec.RequestValid(r)` call on line 100 already iterates through these paths to validate the request. Subsequently, for JSON-RPC VEM requests with an active allow-list, the logic calls `checkVEMWhiteListEntry`, which iterates through the same list of paths a second time to find a `WhiteList` entry. This double iteration on a critical request path can degrade performance, with the impact scaling linearly with the number of paths defined in the API.
💡 SuggestionRefactor the path validation logic to avoid the second iteration. The initial validation in `URLAllowedAndIgnored` could be enhanced to return more detailed information about the matched path, including whether a `WhiteList` entry exists for a VEM. This would allow `VersionCheck` to make its decision based on the result of the first pass without needing to re-scan the entire path list.

Quality Issues (5)

Severity Location Issue
🟠 Error gateway/mw_jsonrpc_test.go:1475
The test `TestJSONRPCMiddleware_OperationHeaderInjection` is missing assertions to verify the behavior of operation-level header injection. While it checks for the presence of `X-Operation-Header`, it doesn't fail if the header is missing, which can lead to a false sense of security. The test logs indicate that this header is likely not being injected correctly, making this a critical gap in test coverage for the new sequential VEM processing logic.
💡 SuggestionStrengthen the test by adding `require.Contains` or `assert.Contains` for the `X-Operation-Header` to ensure the test fails if the header is not injected as expected. This will validate that middleware is correctly applied at both the operation and primitive VEM stages.
🟠 Error gateway/api_definition.go:1515
The logic to enable whitelist mode for an API is flawed. It is set to `false` for all MCP APIs, which overrides any user-configured allow lists at the operation or primitive level. This prevents the new granular allow-list feature from working correctly, as the gateway will not enforce the whitelist rules defined in the API specification.
💡 SuggestionRemove the conditional logic that forces `whiteListEnabled` to `false` for MCP APIs. The preceding logic correctly determines the whitelist status based on the presence of `whiteListPaths` or `apiSpec.OperationsAllowListEnabled`. Removing this override will allow the granular allow-list feature to function as intended.
🟠 Error gateway/mw_version_check.go:105
When an unregistered MCP primitive is called in allow-list mode, the system correctly identifies it as `MCPPrimitiveNotFound`. However, instead of returning a `403 Forbidden` error as expected for an allow-list violation, it incorrectly proceeds with normal request flow after resetting the routing state. This allows the request to pass through to the upstream, bypassing the intended security policy.
💡 SuggestionModify the handling of `MCPPrimitiveNotFound` within an active routing state. When this status is encountered and an allow-list is enabled for the primitive type, the middleware should immediately return a `403 Forbidden` error instead of continuing the request processing.
🟠 Error gateway/mcp_vem.go:182
The VEM generation logic for MCP APIs does not create the necessary catch-all `BlackList` rules when an allow-list is active. This is a critical omission because, without these rules, any unregistered or non-allowed primitives will not be blocked, effectively disabling the allow-list functionality and creating a security gap.
💡 SuggestionImplement the generation of catch-all `BlackList` VEMs for each primitive category (tools, resources, prompts) when their respective allow-list flags (`ToolsAllowListEnabled`, etc.) are enabled. This will ensure that any primitive not explicitly allowed via a `WhiteList` entry is correctly blocked.
🟠 Error gateway/mw_jsonrpc.go:154
The `JSONRPCMiddleware` does not handle routing for unregistered primitives correctly when an allow-list is active. It should generate a VEM path for the unregistered primitive so that the request can be forwarded to the `VersionCheck` middleware, which will then block it based on the catch-all `BlackList` rule. Currently, it fails to do this, preventing the allow-list from being enforced.
💡 SuggestionUpdate the routing logic in `JSONRPCMiddleware` to always generate a VEM path for a primitive, even if it's not found in the `MCPPrimitives` map, but only when the corresponding allow-list is enabled. This ensures the request is routed through the middleware chain where it can be properly blocked by the `VersionCheck` middleware.

Powered by Visor from Probelabs

Last updated: 2026-02-01T06:28:50.694Z | Triggered by: pr_updated | Commit: 7b5be86

💡 TIP: You can chat with Visor using /visor ask <your question>

@lghiur lghiur changed the title Tt 16492 json rpc mcp request handling Jan 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

3 participants