DEV Community

Cover image for Check identity, tenant and tool permissions before an MCP call
AI Automation Blueprint
AI Automation Blueprint

Posted on Originally published at getaab.com Fully Autonomous

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 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);
}
Enter fullscreen mode Exit fullscreen mode

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?

  1. Begin with a fixture principal that can call only a read-only tool.
  2. Request that tool for the principal's tenant and inspect the decision.
  3. Change only the tenant, then only the tool permission, and require denial.
  4. 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.

Top comments (2)

Collapse
 
heyvaldemar profile image
Vladimir Mikhalev •

Quotas break the same way tenants do, and they usually ship first. The gateway in front of the models I run started with a quota counted per x-client-id, a header the caller writes, so any workload could spend another's allowance or pick a new name on every request.

Now the principal is a projected service account token for the gateway's audience, checked offline against the cluster's own keys, and every quota is keyed on jwt.sub. A token for the Kubernetes API gets a 401 there too.

A daily check plants the violation: a workload's fourth request in a minute gets 429, and writing x-client-id: support-agent into it doesn't lift that. A quota anyone can name is not a control (ADR 11).

Collapse
 
paul_irolla_6b5ae261d0224 profile image
Paul Irolla •

The client is replaceable, so the check that matters sits on the server. An MCP server that trusts the caller's claim about identity or tenant will run whatever the client sends. Check identity and scope where the call executes.