attesta_tool_handler— a decorator for MCPcall_toolhandlers. Use this when you author your own MCP servers in Python.MCPProxy— a stdio proxy that wraps any existing MCP server with Attesta approval, requiring zero code changes to the upstream server.
Installation
Architecture
tools/call JSON-RPC requests, evaluates them through Attesta, and either forwards the request to the upstream server (approved) or returns an error response directly (denied). All other messages (tool listings, notifications, etc.) pass through unchanged.
Pattern 1: Decorator (Custom MCP Servers)
Useattesta_tool_handler when you are writing your own MCP server in Python. Place it between @server.call_tool() and your handler function.
API
| Parameter | Type | Description |
|---|---|---|
attesta | Attesta | A configured Attesta instance |
risk_overrides | dict[str, str] | None | Optional {tool_name: risk_level} mapping |
Full Example
Behavior on Denial
When a tool call is denied, the decorator returns an MCP-compatibleTextContent error instead of calling the handler:
The decorator sets
metadata={"source": "mcp"} on all ActionContext objects. This allows you to write audit queries that filter for MCP-specific tool calls.Pattern 2: MCPProxy (Zero Code Changes)
MCPProxy wraps any existing MCP server with Attesta approval. No modifications to the upstream server are needed. This is the recommended approach for enforcing organization-wide HITL policies across all MCP tools.
API
| Parameter | Type | Description |
|---|---|---|
attesta | Attesta | A configured Attesta instance |
upstream_command | list[str] | Command to start the upstream MCP server |
risk_overrides | dict[str, str] | None | Optional {tool_name: risk_level} mapping |
| Method | Description |
|---|---|
run() | Start the proxy. Blocks until the upstream server exits or the client disconnects. |
CLI Usage
The simplest way to use the proxy is through theattesta mcp wrap CLI command:
Editor Configuration
Configure your editor to use the proxy instead of calling the MCP server directly:Programmatic Usage
How the Proxy Works
Startup
The proxy spawns the upstream MCP server as a child process via
subprocess.Popen, connected by stdin/stdout pipes. The upstream server’s stderr passes through to the terminal for debugging.Request Interception
The proxy reads JSON-RPC messages from its own stdin (the MCP client). When it sees a
tools/call request, it extracts the tool name and arguments for evaluation.Attesta Evaluation
The tool call is evaluated through
attesta.evaluate() with an ActionContext containing:function_name: the tool name from the requestkwargs: the tool argumentsmetadata:{"source": "mcp_proxy"}
Denial Response Format
Denied tool calls return a JSON-RPC response withisError: true:
Protocol Support
The proxy auto-detects the framing format used by the MCP client and server:| Format | Description | Used By |
|---|---|---|
| Content-Length framing | Content-Length: N\r\n\r\n{...} (official MCP/LSP spec) | Most MCP servers |
| Newline-delimited JSON | One JSON object per line | Some MCP implementations |
Logging
The proxy logs all approval and denial decisions to stderr (visible in the terminal, not sent to the MCP client):CLI Reference: mcp wrap
Full CLI options for attesta mcp wrap
Vercel AI SDK
TypeScript tool wrappers and middleware