-
-
Notifications
You must be signed in to change notification settings - Fork 1
Description
Problem
The MCP server announces 25-32 tools via tools/list, consuming significant tokens in the LLM client context. Deployments that only need a subset of registered tools (e.g., only Trino tools) pay the full token cost for all tool descriptions on every request.
Proposed Solution
Add a config-driven tools: allow/deny filter implemented as MCP middleware that filters tools/list responses. This is a visibility filter (reduces token usage), not a security boundary — persona-based auth continues to gate tools/call.
Config Syntax
New top-level tools: section using allow/deny glob patterns (consistent with existing persona ToolRulesDef syntax):
tools:
allow:
- "trino_*"
- "datahub_search"
- "datahub_get_entity"
- "platform_info"
deny:
- "*_delete_*"Semantics:
- No
tools:section = all tools visible (backward compatible) allowset = only matching tools passdenyset = matching tools excluded- Both set = allow first, then deny removes from that set
- Empty
allow: []withdeny= all tools pass, then deny removes matches
Implementation Approach
Middleware
New file pkg/middleware/mcp_visibility.go with three functions:
MCPToolVisibilityMiddleware(allow, deny []string)— middleware factory, takes slices directly (decoupled from config types)filterToolVisibility(allow, deny []string, method string, result mcp.Result)— extracted for unit testabilityisToolVisible(name string, allow, deny []string) bool— core filter usingfilepath.Match(same aspersona/filter.go)
Follows the same pattern as mcpapps.ToolMetadataMiddleware — intercept tools/list response, type-assert to *mcp.ListToolsResult, filter listResult.Tools.
Wiring
Added as the very last AddReceivingMiddleware call in finalizeSetup() (after mcpapps), making it the absolute outermost middleware. Only registered when patterns are configured:
if len(p.config.Tools.Allow) > 0 || len(p.config.Tools.Deny) > 0 {
p.mcpServer.AddReceivingMiddleware(
middleware.MCPToolVisibilityMiddleware(p.config.Tools.Allow, p.config.Tools.Deny),
)
}Updated execution order:
Tool visibility → Apps metadata → Auth/Authz → Audit → Rules → Enrichment → handler
Design Decisions
- Middleware over registration-time filtering — toolkits call
server.AddTool()directly and the MCP SDK has noRemoveTool. Response-time filtering is cleaner and handles dynamically registered tools. - No
tools/callblocking — visibility only filterstools/list. Persona auth already gatestools/call. Goal is token reduction, not security. - Conditional registration — middleware only added when patterns are configured, avoiding overhead for the common case.
filepath.Matcherror handling — invalid glob patterns silently treated as non-matches (consistent with persona filter).
Files to Create/Modify
| File | Action | Description |
|---|---|---|
pkg/platform/config.go |
Modify | Add ToolsConfig struct and Tools field to Config |
pkg/middleware/mcp_visibility.go |
Create | Middleware with MCPToolVisibilityMiddleware, filterToolVisibility, isToolVisible |
pkg/middleware/mcp_visibility_test.go |
Create | Table-driven unit tests for filter logic |
pkg/middleware/middleware_chain_test.go |
Modify | Integration test TestMiddlewareChain_ToolVisibility |
pkg/platform/platform.go |
Modify | Wire middleware in finalizeSetup() after mcpapps block |
pkg/platform/config_test.go |
Modify | YAML parsing test for tools: section |
configs/platform.yaml |
Modify | Commented example for tools: section |
Acceptance Criteria
-
make verifypasses - Config with no
tools:section behaves identically to today (backward compatible) - Config with
tools.allow: ["trino_*"]only shows trino tools intools/list - Config with
tools.deny: ["*_delete_*"]hides delete tools - Integration test proves end-to-end filtering through real MCP server wiring
Edge Cases
| Edge Case | Expected Behavior |
|---|---|
No tools: section |
All tools visible (backward compatible) |
allow: ["*"] |
All tools pass allow, then deny applies |
allow: [] with deny: ["s3_*"] |
All tools pass, then deny removes s3 tools |
| Invalid glob pattern | filepath.Match error → treated as non-match, silently skipped |
Empty tools list from tools/list |
Passes through, no error |
| Tool registered after middleware setup | Filtered at response time, so dynamically registered tools are also filtered |
| Interaction with persona filtering | Independent — persona gates tools/call, visibility gates tools/list |
| Interaction with mcpapps metadata | Visibility runs outermost; denied tools removed entirely including _meta.ui metadata |
Test Plan
isToolVisibleunit tests: no rules, allow-only, deny-only, allow+deny, invalid patterns, exact match, wildcard, multiple patternsfilterToolVisibilityunit tests: non-tools/list passthrough, nil result, non-ListToolsResult type, empty tools, actual filtering- Integration test: Wire real
mcp.Server+AddReceivingMiddleware, register 4 tools, add visibility middleware withallow: ["trino_*"], callsession.ListTools(), assert only trino tools appear - Config parsing test: YAML with
tools:section deserializes correctly