MCP Governance Architecture
Waxell provides three products for MCP governance: the auto-instrumentor, server middleware, and governance proxy. Each product addresses a different deployment scenario, but all produce the same waxell.mcp.* span attributes and connect to the same controlplane for centralized policy management.
This page helps you choose the right product, understand how each deploys, and see how they compose for mixed environments.
Decision Flowchart
Use this flowchart to determine which product fits your deployment scenario.
Summary:
- You write the agent and connect to servers you don't control -- use the auto-instrumentor (and optionally the proxy for third-party servers).
- You build the MCP server and want governance regardless of which agents connect -- use server middleware.
- You control neither but can deploy infrastructure between them -- use the governance proxy.
Product Deployment Diagrams
Auto-Instrumentor
The auto-instrumentor patches the MCP ClientSession.call_tool method inside your agent process. No separate infrastructure is needed.
The wrapper intercepts every call_tool invocation, performs governance checks (policy, PII, rate limit), records the result on an OTel span, and forwards the call to the MCP server. Spans use SpanKind.CLIENT because the agent is making an outbound request.
Install: pip install waxell-observe[mcp]
Setup: Call waxell.init() and configure_session() on your ClientSession.
Overview: MCP Governance Overview | Quickstart: Auto-Instrumentor Quickstart
Server Middleware
Server middleware runs inside your FastMCP server process. Every inbound tool call passes through GovernanceMiddleware before reaching your tool function.
The middleware runs policy checks, PII scanning, and rate limiting before each tool executes. It creates independent root traces (no client trace propagation) using SpanKind.SERVER because the server is receiving inbound requests.
Install: pip install waxell-observe[mcp-server]
Setup: Add GovernanceMiddleware to your FastMCP server.
Details: Middleware Quickstart
Governance Proxy
The governance proxy is a standalone MCP server that forwards calls to a target server. No code changes on either side.
The proxy adds SecurityMiddleware (for tool fingerprinting and rug pull detection) in front of GovernanceMiddleware. SecurityMiddleware runs first to capture baselines before governance checks. Like server middleware, proxy spans use SpanKind.SERVER.
Install: pip install waxell-observe[mcp-server]
Setup: Run wax observe mcp-proxy --target <url> or use a config file.
Details: Proxy Quickstart
Product Comparison
| Feature | Auto-Instrumentor | Server Middleware | Governance Proxy |
|---|---|---|---|
| Audience | Agent developers | Server builders | Platform teams |
| Install | waxell-observe[mcp] | waxell-observe[mcp-server] | waxell-observe[mcp-server] |
| Code changes | Agent code | Server code | None (config file) |
| SpanKind | CLIENT | SERVER | SERVER |
| Policy checks | Yes | Yes | Yes |
| PII scanning | Yes | Yes | Yes |
| Rate limiting | Via controlplane | Yes (built-in) | Yes (built-in) |
| Approval workflows | Yes | Yes | Yes |
| Tool fingerprinting | Yes | No | Yes |
| Rug pull detection | Yes | No | Yes |
| Per-tool config | configure_session() | @governance decorator | YAML/JSON config |
| Caller identity | N/A (is the caller) | Yes (client_id, IP) | Yes (client_id, IP) |
| Fail-open | Yes (default) | Yes (default) | Yes (default) |
| Infrastructure | None | None | Proxy process |
Composability
The three products are designed to work together. In real deployments, you often need more than one.
Auto-Instrumentor + Proxy
The most common combination. Use the auto-instrumentor for MCP servers you connect to directly, and the proxy for third-party servers you need to govern without modifying.
Example scenario: Your agent connects to an internal file server (direct, governed by the auto-instrumentor) and Composio (via proxy, governed by proxy config). Both produce waxell.mcp.* spans that appear in the same trace.
In this configuration, the auto-instrumentor creates CLIENT spans for the tool calls it intercepts, and the proxy creates SERVER spans for the same calls it processes. Both span sets appear in your dashboard, giving you end-to-end visibility.
Auto-Instrumentor + Middleware
Use this when you build both the agent and the server. The auto-instrumentor adds client-side governance (agent context, session-level config), and the middleware adds server-side governance (rate limiting, caller identity, server-level policies).
Example scenario: You build a database query server with GovernanceMiddleware for rate limiting and PII scanning. Your agents use the auto-instrumentor for policy checks and approval workflows. Both layers apply -- a tool call is checked by the instrumentor on the client side and by the middleware on the server side.
The two governance layers operate independently:
- The auto-instrumentor checks client-side policies based on agent context (user, session, agent name).
- The middleware checks server-side policies based on caller identity (client_id, IP address).
- Each produces its own span -- CLIENT on the agent side, SERVER on the server side.
Attribute Namespace Consistency
All three products use the same waxell.mcp.* attribute namespace on their spans. This means governance queries work regardless of which product generated the span.
| Attribute | Auto-Instrumentor | Middleware | Proxy |
|---|---|---|---|
waxell.mcp.tool_name | Yes | Yes | Yes |
waxell.mcp.server_name | Yes | Yes | Yes |
waxell.mcp.governance_checked | Yes | Yes | Yes |
waxell.mcp.governance_action | Yes | Yes | Yes |
waxell.mcp.pii_detected | Yes | Yes | Yes |
waxell.mcp.params_hash | Yes | Yes | Yes |
waxell.mcp.arguments | Yes | Yes | Yes |
waxell.mcp.result | Yes | Yes | Yes |
Querying across products. A TraceQL query like { span.waxell.mcp.governance_action = "block" } returns blocked tool calls from all three products. You don't need to know which product generated the span.
SpanKind Differences
While the attributes are consistent, the SpanKind differs based on the product's role:
| Product | SpanKind | Reason |
|---|---|---|
| Auto-Instrumentor | CLIENT | The agent is making an outbound request to an MCP server |
| Server Middleware | SERVER | The server is receiving an inbound request from an agent |
| Governance Proxy | SERVER | The proxy is receiving an inbound request and forwarding it |
This distinction matters when building dashboards:
- Filter on
span.kind = "client"to see the agent's view of tool calls. - Filter on
span.kind = "server"to see the server's or proxy's view. - When using auto-instrumentor + proxy together, you see both CLIENT and SERVER spans for the same tool call, giving you round-trip visibility.
Server-Side Specific Attributes
The middleware and proxy add caller identity attributes that the auto-instrumentor does not:
| Attribute | Description | Products |
|---|---|---|
waxell.mcp.server_side | true for server/proxy spans | Middleware, Proxy |
waxell.mcp.middleware_active | true when governance middleware is processing | Middleware, Proxy |
waxell.mcp.caller_client_id | MCP client_id from initialize | Middleware, Proxy |
waxell.mcp.caller_session_id | MCP session ID | Middleware, Proxy |
waxell.mcp.caller_ip | Caller IP from HTTP headers | Middleware, Proxy |
waxell.mcp.caller_user_agent | User-agent header | Middleware, Proxy |
waxell.mcp.rate_limited | true if rate limit was exceeded | Middleware, Proxy |
waxell.mcp.rate_limit_remaining | Remaining calls in window | Middleware, Proxy |
Next Steps
- MCP Governance Overview -- What MCP governance is and why SDK-level beats gateway approaches.
- Auto-Instrumentor Quickstart -- Add governance to your agent in 5 minutes.
- Middleware Quickstart -- Add governance to your FastMCP server.
- Proxy Quickstart -- Wrap a third-party server with the governance proxy.
- API Reference -- Complete reference for all MCP governance classes, functions, and configuration options.
- Span Attributes Reference -- Full list of all
waxell.mcp.*span attributes.