Check identity, tenant and tool permissions before an MCP call
An MCP server exposes capabilities to a client. It does not make every tool call authorized merely because the request arrived through MCP. Before connecting a model to a write-capable tool, decide which authenticated id
An MCP server exposes capabilities to a client. It does not make every tool call authorized merely because the request arrived through MCP. Before connecting a model to a write-capable tool, decide which authenticated identity can perform which action on which tenant's data.
This article adapts AI Automation Blueprint's MCP deployment-security topic into a narrow authorization design. The JavaScript example is a local policy predicate. It isn't an MCP server, an OAuth implementation, or evidence that a deployment is safe.
Which checks happen before a tool runs?
Verify identity before checking permissions. Validate the tool inputs separately. A request can carry a valid identity and still target the wrong tenant. A permitted tool can still receive invalid arguments. An allowed read does not imply permission to send a message or modify a record.
Write down the boundary for each exposed operation. For a support-document lookup, that might be a read-only tool with a tenant identifier and a bounded query. Keep a customer-message tool behind a separate permission and release decision. Do not infer that broader permission just because the same model calls both tools.
| Boundary | Example question |
|---|---|
| Authenticated identity | Who did the trusted authentication layer establish? |
| Tenant | Which tenant does that identity belong to? |
| Tool permission | Is this exact operation allowed? |
| Argument validation | Are the requested inputs valid for that operation? |
What can a local policy check demonstrate?
The predicate below accepts a principal created by a trusted authentication layer and a requested tool call. The request body must not supply or rewrite that principal. Server configuration determines the tenant and permission list associated with the authenticated identity.
function mayCallTool(principal, request) {
if (!principal || !request) return false;
if (typeof principal.tenant !== "string" || principal.tenant.length === 0) {
return false;
}
if (!Array.isArray(principal.allowedTools)) return false;
if (typeof request.tool !== "string") return false;
return request.tenant === principal.tenant &&
principal.allowedTools.includes(request.tool);
}
The function has been checked with local fixtures for allowed calls, cross-tenant requests, and missing permissions. It does not authenticate a principal, validate tool arguments, revoke access, or enforce a quota. Those duties belong elsewhere. Keep the example's limits attached to the code when you reuse it.
How does this relate to MCP authorization?
For restricted remote MCP resources, follow the authorization requirements for the transport and protocol revision you implement. The current MCP authorization documentation describes how authorization applies to protected resources. Do not substitute the predicate above for that flow or invent an authentication protocol around a model-generated identifier.
The MCP project's security guidance warns against token passthrough and accepting tokens intended for another service. Keep the credential intended for a downstream API behind its own boundary. And a client token arriving at an MCP endpoint is never a credential to forward blindly to every connected service.
This is also why a transport connection and a tool permission cannot share a single "access granted" flag. Record enough context to explain a decision, while keeping bearer tokens, raw sensitive arguments, and full customer messages out of routine logs.
How can you test the boundary before deployment?
- Begin with a fixture principal that can call only a read-only tool.
- Request that tool for the principal's tenant and inspect the decision.
- Change only the tenant, then only the tool permission, and require denial.
- Verify that request data cannot replace the trusted principal in the real handler.
Repeat these checks at the actual transport boundary before exposing a service. A unit test of a predicate says nothing about whether a reverse proxy authenticates requests correctly, a handler trusts attacker-supplied identity, or a downstream service enforces its own permissions.
Does filtering model text solve authorization?
No. A model can describe a permitted action, misunderstand a request, or follow malicious instructions in retrieved content. Enforce the operation's permissions in code before the side effect happens. Keep tool output as untrusted data and retain a separate approval boundary for consequential writes. Test denied paths as carefully as successful reads.
Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.