MCP Security Best Practices: Authorizing and Auditing Model Context Protocol Traffic
The Model Context Protocol gives an LLM a standard way to call tools, and over HTTP transport those calls are AI traffic you can authorize and audit. This covers the practices that matter at the request boundary: verify identity on every call, scope tools per role, treat tool descriptions as untrusted, fail closed, and log per decision. It also marks the line where local stdio servers fall outside a gateway.

The Model Context Protocol standardizes how an LLM discovers and calls tools. When an MCP server runs over the Streamable HTTP transport, every tool call is an HTTP request between an authenticated caller and a service, which means it is AI traffic a policy layer can inspect, authorize, and record. That framing decides which practices actually hold. I want to walk through the ones that operate at the request boundary and mark the point where a class of MCP risk sits outside it.
These practices build on the authorization model in Model Context Protocol security and the MCP server authorization patterns already covered on the blog.
Authorize every call, not just the connection
MCP's June 2025 authorization spec places the server as an OAuth 2.1 resource server, so a caller presents a token and the server validates it. Token validation at connection time answers who is calling. It leaves open the question that matters per request: is this caller allowed to invoke this specific tool with these arguments right now. Treat each tools/call as its own authorization decision, evaluated against the caller's role and the current policy, rather than trusting a session that was approved once.
Scope tools per role and fail closed
An MCP server often exposes a dozen tools, and most callers need two. Grant each role the minimum tool surface its task requires, and default to deny for everything else. A support agent's session reaches the ticket-lookup tool and nothing that writes to billing. When a policy is ambiguous or a token cannot be validated, the call fails closed. A fail-closed posture turns an outage or a gap into a safe denial rather than an open door.
A per-route policy for a sensitive tool reads plainly:
Treat tool descriptions and tool output as untrusted
An MCP tool advertises itself with a natural-language description, and the model reads that description to decide when to call it. An attacker who controls a server can write a description that instructs the model to exfiltrate data or call another tool, which is the tool-poisoning and prompt-injection-via-tool-description class. Two habits reduce the exposure. Pin and review tool descriptions from third-party servers rather than accepting live updates, and treat tool output as untrusted content that gets filtered on the way back, the same way you treat any external data entering the context window.
Log every decision as a signed record
MCP calls move data between systems, so the log has evidentiary value. Commit a signed record for each decision containing the caller identity, the tool, the argument summary, the policy version, and the outcome. Because the record is written at the boundary before the response returns, the calling application cannot selectively omit the calls that failed. That property is what makes the log a system of record rather than a convenience artifact.
Know where the boundary ends
A policy layer at the request boundary governs MCP traffic that crosses the network. It does not govern an MCP server running locally over stdio, where the model's host launches the server as a child process and calls happen through pipes with no network hop. Those deployments are a workload-isolation and host-hardening problem: run the server with least OS privilege, restrict its filesystem and network egress, and keep untrusted servers off machines that hold credentials. Being precise about this line is what keeps the HTTP-transport practices credible, because it stops a gateway from claiming reach it does not have.
DeepInspect
This is exactly what DeepInspect does for MCP traffic on the HTTP transport. DeepInspect sits inline as a stateless proxy in front of your MCP endpoints and evaluates every tools/call against the caller's verified identity, the tool being invoked, the argument content, and organizational policy, then permits, redacts, or denies before the call executes. Tool output is inspected on the return path, and every decision commits a signed, per-decision audit record the calling application cannot suppress.
The result is per-route, per-role authorization over your MCP surface and an audit trail that reconstructs which tools ran, for whom, under which policy. For stdio and local servers, DeepInspect is honest about the boundary and points you to host-level isolation instead.
If your agents are calling MCP tools in production without per-call authorization, book an AI readiness assessment at deepinspect.ai.
Frequently asked questions
- Does MCP's built-in OAuth handle authorization for me?
It handles authentication and coarse access. The 2025 spec makes the MCP server an OAuth 2.1 resource server, so it can validate that a caller holds a valid token and consented to a scope. That is necessary and not sufficient. Scope consent granted once does not evaluate whether a given
tools/callwith specific arguments is appropriate at the moment it happens. Per-call authorization against role and policy is a separate decision that sits on top of token validation, and it is where least privilege on the tool surface actually gets enforced.- How do I secure a local stdio MCP server?
Treat it as a host-hardening problem, because a gateway on the network cannot see stdio traffic. Run the server as a low-privilege OS user, restrict its filesystem access to the directories it needs, and constrain its outbound network so a compromised or malicious server cannot reach the internet or internal services freely. Keep untrusted third-party servers off machines that hold cloud credentials or production database access. For anything that needs central policy and audit, prefer the HTTP transport so the traffic becomes inspectable.
- What is tool poisoning in MCP?
Tool poisoning is when a malicious or compromised MCP server publishes a tool whose description or output is crafted to manipulate the model. Because the model reads tool descriptions to decide when and how to call tools, an instruction hidden in a description can push it toward exfiltration or toward calling a more dangerous tool. Defenses include pinning and reviewing descriptions from third-party servers, filtering tool output as untrusted content, and scoping which tools a session can reach so a poisoned tool has no privileged neighbor to abuse.
- Should MCP tool output be filtered?
Yes. Tool output enters the model's context window as content, so it can carry injected instructions or sensitive data that should not flow onward. Inspect and filter output on the return path against the same data-classification and policy rules you apply to prompts. This matters most when tools return data from external or user-controlled sources, which is exactly where indirect prompt injection enters an agentic workflow.
- How does per-call MCP authorization support compliance?
Each authorization decision produces a per-decision record with identity, tool, arguments, policy version, and outcome. That record maps onto the traceability that frameworks like the EU AI Act's Article 12 expect: enough detail to reconstruct what the system did and who was involved. Because the record is signed and written at the boundary before the response returns, it stands up as evidence rather than as a log the calling application could have edited.