[TT-16566] mcp policy filter tools resource prompt lists#7868
[TT-16566] mcp policy filter tools resource prompt lists#7868
Conversation
Add response-time filtering for tools/list, prompts/list, resources/list, and resources/templates/list so consumers only see primitives they are authorised to use based on their MCPAccessRights allow/block lists. Two complementary handlers cover both transport modes: - MCPListFilterResponseHandler: filters standard HTTP JSON responses - MCPListFilterSSEHook: filters SSE events in Streamable HTTP transport Both use the same checkAccessControlRules() logic as invocation-time enforcement, supporting exact match and regex/wildcard patterns with deny-takes-precedence semantics.
…uplication - Extract filterItems, reencodeEnvelope, filterParsedJSONRPC as shared functions used by both the HTTP response handler and SSE hook - Deduplicate mcpListConfig definitions into a shared mcpListConfigs map - Extract rulesForAPI, readAndCloseBody, filterJSONRPCBody to shrink HandleResponse from ~130 lines to ~40 - Extract filterSSEData to keep FilterEvent focused on SSE concerns - Simplify inferListConfigFromResult to use shared config map
Move pure filtering logic (no gateway dependencies) from gateway/ to internal/mcp/list_filter.go: - ListFilterConfig, ListFilterConfigs, JSONRPCResponse (types) - FilterItems, FilterJSONRPCBody, FilterParsedJSONRPC (filtering) - ReencodeEnvelope, ExtractStringField (helpers) - InferListConfigFromResult (SSE config inference) - CheckAccessControlRules, matchPattern (ACL evaluation) Gateway files become thin wrappers: the response handler and SSE hook handle transport concerns (HTTP body, SSE events) and delegate to internal/mcp for access control filtering.
|
This PR introduces response filtering for MCP (Multi-Cloud Proxy) list endpoints to enforce Token-Based Access Control (TBAC). When a consumer has The implementation adds a new response handler for standard HTTP/JSON responses and a new SSE hook for streaming responses. The core access control logic has been refactored into a shared internal package ( Files Changed AnalysisThe changes involve 14 files, with a net addition of 3,174 lines. The bulk of the new code consists of comprehensive unit tests and benchmarks, reflecting a high standard of quality assurance.
Architecture & Impact Assessment
Component Interaction DiagramsequenceDiagram
participant Client
participant Gateway
participant Upstream
Client->>+Gateway: POST /mcp (e.g., tools/list)
Gateway->>+Upstream: Forward request
Upstream-->>-Gateway: Full list response (JSON or SSE)
alt Standard HTTP Response
Gateway->>Gateway: MCPListFilterResponseHandler
Note right of Gateway: 1. Parse response body<br/>2. Get session MCPAccessRights<br/>3. Filter items in list<br/>4. Rewrite response body & Content-Length
else SSE Streaming Response
Gateway->>Gateway: SSETap with MCPListFilterSSEHook
Note right of Gateway: For each SSE 'message' event:<br/>1. Parse event data (JSON-RPC)<br/>2. Infer list type from result keys<br/>3. Filter items<br/>4. Rewrite event data
end
Gateway-->>-Client: Filtered list response
Scope Discovery & Context ExpansionThe scope of this change is well-contained within the gateway's MCP processing pipeline. By building upon the existing The refactoring of References
Metadata
Powered by Visor from Probelabs Last updated: 2026-04-16T13:41:38.628Z | Triggered by: pr_updated | Commit: 47582c7 💡 TIP: You can chat with Visor using |
|
API Changes --- prev.txt 2026-04-16 13:40:27.684972002 +0000
+++ current.txt 2026-04-16 13:40:19.140901883 +0000
@@ -11395,6 +11395,51 @@
state.PrimitiveType is empty (non-primitive methods such as initialize,
ping, tools/list).
+type MCPListFilterResponseHandler struct {
+ BaseTykResponseHandler
+}
+ MCPListFilterResponseHandler filters MCP list responses (tools/list,
+ prompts/list, resources/list, resources/templates/list) to show only
+ primitives the consumer is authorized to see based on their MCPAccessRights
+ allow/block lists.
+
+func (h *MCPListFilterResponseHandler) Base() *BaseTykResponseHandler
+ Base returns the base handler for middleware decoration.
+
+func (h *MCPListFilterResponseHandler) Enabled() bool
+ Enabled returns true only for MCP APIs.
+
+func (h *MCPListFilterResponseHandler) HandleResponse(_ http.ResponseWriter, res *http.Response, req *http.Request, ses *user.SessionState) error
+ HandleResponse filters MCP list responses based on session access rights.
+
+func (h *MCPListFilterResponseHandler) Init(_ any, spec *APISpec) error
+ Init initializes the handler with the given spec.
+
+func (h *MCPListFilterResponseHandler) Name() string
+ Name returns the handler name for logging and debugging.
+
+type MCPListFilterSSEHook struct {
+ // Has unexported fields.
+}
+ MCPListFilterSSEHook filters MCP list responses (tools/list, prompts/list,
+ resources/list, resources/templates/list) inside SSE events when the
+ upstream uses Streamable HTTP transport.
+
+ In Streamable HTTP, the server may respond to any JSON-RPC method with an
+ SSE stream where each "message" event carries a complete JSON-RPC response.
+ This hook intercepts those events and applies the same access-control
+ filtering as MCPListFilterResponseHandler does for regular HTTP responses.
+
+func NewMCPListFilterSSEHook(apiID string, ses *user.SessionState) *MCPListFilterSSEHook
+ NewMCPListFilterSSEHook creates a hook that filters list response events
+ based on the session's MCPAccessRights for the given API. Returns nil if no
+ filtering is needed (nil session or no ACL rules).
+
+func (h *MCPListFilterSSEHook) FilterEvent(event *SSEEvent) (bool, *SSEEvent)
+ FilterEvent inspects an SSE event. If it contains a JSON-RPC list response,
+ the primitive array is filtered by access-control rules. Non-list events and
+ non-message events pass through unmodified.
+
type MCPVEMContinuationMiddleware struct {
*BaseMiddleware
} |
Architecture Issues (3)
Architecture Issues (3)
Quality Issues (1)
Powered by Visor from Probelabs Last updated: 2026-04-16T13:41:11.446Z | Triggered by: pr_updated | Commit: 47582c7 💡 TIP: You can chat with Visor using |
🚨 Jira Linter FailedCommit: The Jira linter failed to validate your PR. Please check the error details below: 🔍 Click to view error detailsNext Steps
This comment will be automatically deleted once the linter passes. |
|



Description
This PR implements MCP list response filtering based on access control rules (TBAC - Token-Based Access Control). When a consumer has
mcp_access_rightsconfigured (either directly on a key or via a policy), the gateway now filters the responses fromtools/list,prompts/list,resources/list, andresources/templates/listto show only primitives the consumer is authorized to see.Key Changes
New Response Handler (
res_handler_mcp_list_filter.go):MCPAccessRightsContent-Lengthheader after filteringNew SSE Hook (
sse_hook_mcp_list_filter.go):Shared Filtering Logic (
internal/mcp/list_filter.go):internal/mcppackageCheckAccessControlRules()- evaluates allow/block lists with regex supportFilterJSONRPCBody()/FilterParsedJSONRPC()- parse and filter JSON-RPC responsesListFilterConfigs- configuration for each list type (tools, prompts, resources, resourceTemplates)Refactoring:
checkAccessControlRules()andmatchPattern()fromgateway/mw_jsonrpc_helpers.gotointernal/mcp/list_filter.goFiltering Behavior
nextCursorand other result fields are preserved after filteringRelated Issue
TT-16566
Motivation and Context
MCP APIs expose tools, prompts, and resources to AI agents. Organizations need to control which primitives different consumers can discover and use. While invocation-time access control (blocking
tools/call,resources/read, etc.) was already implemented, consumers could still see all available primitives in list responses.This creates a security concern: consumers could see tools/resources they're not authorized to use, potentially leaking sensitive capability information. This PR completes the access control story by filtering discovery responses to match invocation permissions.
How This Has Been Tested
Unit Tests (this repo)
Response Handler Tests (
res_handler_mcp_list_filter_test.go- 1080 lines):SSE Hook Tests (
sse_hook_mcp_list_filter_test.go- 1097 lines):Shared Logic Tests (
internal/mcp/list_filter_test.go- 329 lines):ExtractStringFieldfor various JSON shapesFilterItemswith allow/block rulesCheckAccessControlRulescomprehensive test matrixInferListConfigFromResultfor all list typesIntegration Tests (tyk-analytics repo)
tests/api/tests/mcp/mcp_list_filter_test.py(491 lines):test_tools_list_filtered_by_allowlist- exact name filteringtest_tools_list_filtered_by_allowlist_wildcard_suffix- regexget_.*test_tools_list_filtered_by_denylist- exact name blockingtest_tools_list_filtered_by_denylist_wildcard_prefix- regex.*_usertest_tools_list_deny_takes_precedence_over_allowtest_tools_list_no_filtering_when_no_acltest_prompts_list_filtered_by_allowlisttest_prompts_list_filtered_by_denylisttest_resources_list_filtered_by_allowlisttest_resources_list_filtered_by_denylisttest_resource_templates_list_filtered_by_denylisttest_resource_templates_list_filtered_by_allowlisttest_tool_rules_do_not_filter_prompts_or_resources- cross-primitive isolationtest_tools_list_wildcard_star_blocks_everythingtest_tools_list_wildcard_star_allows_everythingtest_tools_list_alternation_pattern- regexget_.*|validate_.*test_resources_list_wildcard_uri_patterntest_empty_allowed_list_blocks_everythingtest_prompts_list_wildcard_suffix_patterntest_policy_acl_filters_tools_list- policy-based filteringTicket Details
TT-16566
Generated at: 2026-03-10 15:38:14