MCP Proxy Tool Pattern
July 16, 2025 · View on GitHub
The proxy tool pattern enables dynamic access to MCP capabilities through a single, unified tool interface. This pattern is particularly useful for aggregators, gateways, and other scenarios where MCP servers need to expose capabilities from other servers.
Overview
The proxy tool provides a single entry point for:
- Listing available tools, resources, or prompts across servers
- Getting detailed information about specific capabilities
- Calling tools, reading resources, or getting prompts
This approach simplifies client implementation and enables powerful aggregation scenarios without requiring clients to manage multiple connections.
Tool Interface
Parameters
The proxy tool accepts the following parameters:
-
action (required):
"list"|"info"|"call"list: List all capabilities of the specified typeinfo: Get detailed information about a specific capabilitycall: Execute a tool, read a resource, or get a prompt
-
type (required):
"tool"|"resource"|"prompt"- Specifies which type of MCP capability to interact with
- Parameter is aliased as
a_typein the server implementation to avoid Python keyword conflict
-
path (optional): string
- Name or URI of the specific capability
- Required for
infoandcallactions - Not allowed for
listaction
-
args (optional): object | string
- Arguments to pass when using the
callaction - Can be provided as a dict or JSON string (automatically parsed)
- Only allowed for
callaction - Not allowed for
listandinfoactions - For tools: tool-specific arguments
- For resources: typically not used (URI is in path)
- For prompts: prompt-specific arguments
- Arguments to pass when using the
-
limit (optional): integer (1-1000)
- Maximum number of items to return
- Only allowed for
listaction - Default: 100
-
offset (optional): integer (≥0)
- Number of items to skip
- Only allowed for
listaction - Default: 0
-
filter_server (optional): string
- Filter results by server name prefix
- Only allowed for
listaction - Example:
"serena_"to show only tools from the serena server
Examples
// List all tools (with default pagination: limit=100, offset=0)
{
"action": "list",
"type": "tool"
}
// List tools with pagination
{
"action": "list",
"type": "tool",
"limit": 50,
"offset": 50
}
// List tools from a specific server only
{
"action": "list",
"type": "tool",
"filter_server": "serena_"
}
// Get info about a specific tool
{
"action": "info",
"type": "tool",
"path": "calculator_add"
}
// Call a tool (with dict args)
{
"action": "call",
"type": "tool",
"path": "calculator_add",
"args": {"a": 5, "b": 3}
}
// Call a tool (with JSON string args)
{
"action": "call",
"type": "tool",
"path": "calculator_add",
"args": "{\"a\": 5, \"b\": 3}"
}
// Read a resource
{
"action": "call",
"type": "resource",
"path": "config://settings.json"
}
Response Format
Query Actions (list/info)
For list and info actions, the proxy tool returns a list containing a single EmbeddedResource with:
- JSON-encoded representation of the MCP objects
- Annotations indicating the proxy context and metadata
Example list response structure:
[
EmbeddedResource(
resource=TextResourceContents(
uri="proxy:list/tool",
mimeType="application/json",
text='[{"name": "tool1", "description": "...", ...}]'
),
annotations=Annotations(
proxyAction="list",
proxyType="tool",
pythonType="Tool", # Or "Resource | ResourceTemplate", "Prompt"
many=True, # For list actions
totalCount=250, # Total number of items available
offset=0, # Current offset
limit=100 # Current limit
)
)
]
Example info response structure:
[
EmbeddedResource(
resource=TextResourceContents(
uri="proxy:info/tool/calculator_add",
mimeType="application/json",
text='{"name": "calculator_add", "description": "...", ...}'
),
annotations=Annotations(
proxyAction="info",
proxyType="tool",
proxyPath="calculator_add",
pythonType="Tool",
many=False # Single object
)
)
]
Call Actions
For call actions, the proxy tool returns the actual result from the called capability:
- Tools: List of
TextContent,ImageContent, orEmbeddedResourcewith proxy annotations - Resources: List of
EmbeddedResource(resource results converted to tool result format) - Prompts: List with single
EmbeddedResourcecontaining the prompt result
All call results include annotations with:
proxyType: The capability type ("tool", "resource", or "prompt")proxyAction: Always "call" for call actionsproxyPath: The name/URI of the called capability
Client Implementation
Magg provides a ProxyClient class that simplifies interaction with proxy-enabled servers:
from magg.proxy.client import ProxyClient
# Create a proxy-aware client
async with ProxyClient("http://localhost:8080/mcp") as client:
# Natural method interface - returns raw proxy tool results
result = await client.proxy("tool", "list")
# Result is a list with one EmbeddedResource for query actions
result = await client.proxy("tool", "call", "calculator_add", arguments={"a": 5, "b": 3})
# Result is the tool's actual output (list of content items)
# Transparent mode - redirects standard methods through proxy
async with ProxyClient("http://localhost:8080/mcp", transparent=True) as client:
tools = await client.list_tools() # Uses proxy internally, returns list[Tool]
result = await client.call_tool("calculator_add", {"a": 5, "b": 3}) # Returns content list
# Resources and prompts work transparently too
resources = await client.list_resources() # Returns list[Resource | ResourceTemplate]
resource_data = await client.read_resource("config://settings.json")
prompt_result = await client.get_prompt("greeting", {"name": "Alice"})
Key Features
- Natural Interface:
proxy(type, action, path, arguments, timeout, progress_handler)method - Raw Results: The
proxy()method returns raw proxy tool results without transformation - Transparent Mode: Overrides standard client methods to use proxy, with automatic result transformation
- Type Safety: Full type annotations with proper return types
- Validation: Built-in parameter validation matching server requirements
- Progress Support: Optional timeout and progress handler parameters
ProxyClient Constructor
ProxyClient(
*args,
transparent: bool = False,
proxy_tool_name: str | None = None,
**kwds
)
transparent: If True, override standard methods to use proxyproxy_tool_name: Name of the proxy tool (default: "proxy")- Other arguments are passed to the base FastMCP Client
ProxyClient Methods
proxy(proxy_type, action, path=None, arguments=None, timeout=None, progress_handler=None)
Direct access to the proxy tool. Returns raw results as returned by the proxy tool:
- For
list/info: List with single EmbeddedResource containing JSON-encoded data - For
call: The actual tool/resource/prompt results
Transparent Mode Methods
When transparent=True, these methods automatically use the proxy tool:
list_tools()→list[Tool]list_resources()→list[Resource | ResourceTemplate](includes templates)list_prompts()→list[Prompt]call_tool(name, arguments)→list[TextContent | ImageContent | EmbeddedResource]read_resource(uri)→list[TextResourceContents | BlobResourceContents]get_prompt(name, arguments)→GetPromptResult
All transparent methods handle result transformation automatically using the transform utilities.
Server Implementation
Magg's proxy server implementation provides:
- Self-introspection: Server can list its own capabilities via FastMCPTransport
- Result transformation: Automatic conversion between MCP types
- Rich annotations: Metadata for result interpretation with ProxyResponseInfo
- Validation: Parameter validation via
validate_operation()
Key Classes
ProxyFastMCP
A wrapper class that adds proxy functionality to FastMCP instances:
from magg.proxy.server import ProxyFastMCP
from fastmcp import FastMCP
# Wrap an existing FastMCP instance
mcp = FastMCP(name="my-server")
proxy_mcp = ProxyFastMCP(mcp)
ProxyMCP
A mixin class that servers can inherit from:
from magg.proxy.mixin import ProxyMCP
from magg.server.manager import ManagedServer
class MyAggregator(ManagedServer, ProxyMCP):
def __init__(self):
super().__init__()
# ProxyMCP expects server_manager attribute
self._register_proxy_tool()
ProxyResponseInfo
Metadata extracted from proxy response annotations:
from magg.proxy.server import ProxyResponseInfo
# Extract metadata from annotations
info = ProxyResponseInfo.from_annotations(result.annotations)
# info.proxy_type: "tool" | "resource" | "prompt"
# info.proxy_action: "list" | "info" | "call"
# info.proxy_path: The specific capability name/URI
Helper Methods
ProxyMCP.validate_operation(action, a_type): Validates proxy parametersProxyMCP.get_proxy_query_result(result): Decodes query action results_register_proxy_tool(wrapper): Registers the proxy tool with optional wrapper
Benefits
- Simplified Clients: Single tool interface instead of managing multiple connections
- Dynamic Discovery: Capabilities can change at runtime
- Reduced Complexity: No need for clients to understand server mounting
- Standardized Interface: Consistent way to access any MCP capability
- Future-proof: Easy to extend with new capability types
Advanced Usage
Result Type Preservation
The proxy tool preserves type information through annotations:
pythonType: Original Python type name (e.g., "Tool", "Resource | ResourceTemplate")many: Whether the result is a list (True for list actions)proxyType: The capability type ("tool", "resource", "prompt")proxyAction: The action performed ("list", "info", "call")proxyPath: The specific capability path (for info and call actions)
Transform Utilities
Magg provides transform utilities for working with proxy results:
from magg.util.transform import (
tool_result_as_prompt_result,
tool_result_as_resource_result,
get_embedded_resource_python_object,
deserialize_embedded_resource_python_object
)
# Extract prompt result from tool result format
prompt_result = tool_result_as_prompt_result(tool_result)
# Extract resource result from tool result format
resource_result = tool_result_as_resource_result(tool_result)
# Get metadata from embedded resource
python_type, json_data, many = get_embedded_resource_python_object(embedded_resource)
# Deserialize to proper MCP types
obj = deserialize_embedded_resource_python_object(
target_type=Tool,
python_type=python_type,
json_data=json_data,
many=many
)
Error Handling
Proxy errors are returned as standard MCP errors:
- Invalid parameters (e.g., missing path for info/call, args provided for list/info)
- Unknown capability types or actions
- Failed server connections
- Tool execution errors
- Invalid result format
Performance Considerations
- Self-client connection is reused across calls
- Use targeted queries when possible
- Pagination is implemented with
limit,offset, andfilter_serverparameters - Default limit is 100 items to prevent token limit issues
- Transparent mode adds minimal overhead (one extra tool call)
Future Extensions
Potential enhancements for the proxy pattern:
- Streaming Support: For large list results or real-time updates
- Filtering: Server-side filtering of list results
- Batch Operations: Multiple operations in a single call
- Capability Negotiation: Discover proxy tool capabilities
- Security: Fine-grained access control per capability
Specification Proposal
The proxy tool pattern could be standardized as part of MCP with:
- Well-known tool name:
proxyormcp:proxy(configurable via constructor) - Standard parameter schema:
action: "list" | "info" | "call"type: "tool" | "resource" | "prompt"path: Required for info/call, forbidden for listargs: Only for call action
- Result format conventions:
- Query actions: List with single EmbeddedResource containing JSON
- Call actions: Direct results with proxy annotations
- Annotation standards:
proxyType,proxyAction,proxyPathfor trackingpythonType,manyfor deserialization
- Discovery mechanism: Check for tool named "proxy" in tool list
This pattern enables powerful aggregation and gateway scenarios while maintaining the simplicity and elegance of the MCP protocol.
Implementation Notes
Known Limitations
-
Resource Templates:
list_resources()in transparent mode returns both resources and resource templates together, unlike the standard client which has separate methods. -
Prompt Results: Prompt results are wrapped in EmbeddedResource format when called through proxy, requiring transformation back to GetPromptResult.
-
Error Messages: Validation errors from the proxy tool provide clear messages about parameter requirements.
Best Practices
- Use transparent mode when you want a drop-in replacement for standard MCP client
- Use direct proxy() calls when you need access to raw results or metadata
- Validate parameters early using
ProxyMCP.validate_operation() - Handle empty results gracefully - list actions may return empty lists
- Check annotations when you need to distinguish between different result types