In this guide
A valid access token answers an important question: the request carries credentials your service recognizes. It does not answer every question that follows. Can this person read this project? Can they change its visibility? Can the integration use a downstream administrator credential on their behalf?
An MCP service needs those decisions close to the operation. Otherwise a correctly authenticated assistant becomes a convenient route around the application's actual permission model.
Separate the permission layers
Treat authorization as a chain of checks, with a named owner for each one. Transport authentication identifies the caller. Tenant membership limits the organizational boundary. Resource policy limits the object being accessed. Operation policy distinguishes reading from modifying, exporting or deleting. A downstream system may add another authorization decision of its own.
These layers should not rely on a model remembering a rule. A model can provide a resource identifier, but the service should derive the principal and tenant context from authenticated state. A string called workspaceId supplied in tool arguments is still caller-controlled input.
A narrow tool helps make the desired action legible. It does not eliminate the server-side check. Another client may invoke the same tool without your user interface, and an old client may retain a capability your current interface no longer displays.
Understand which token is meant for which service
The MCP authorization specification describes HTTP authorization using OAuth roles and resource-specific access tokens. It requires the MCP service to validate that a presented token is intended for it, and forbids simply passing that inbound token through to an upstream API.
Keep those two credential relationships distinct. A token issued to access your MCP service is not automatically an access token for a repository provider, calendar service or issue tracker. The downstream credential needs its own issuance, scope and storage policy.
This also makes revocation understandable. Removing a user's access to the MCP service should stop new operations through that service. Revoking a downstream connection should prevent use of that connection even if the user's MCP session is still valid. Test both paths instead of assuming one revocation covers every credential involved.
Work through a multi-tenant issue tracker
Suppose a tool exposes update_issue_status. Its arguments identify an issue and the desired status. Alice belongs to the support workspace. Bob belongs to a different workspace. Both users have valid sessions.
The service should first establish Alice's identity and workspace membership from its session or validated token. It then loads the issue through a query restricted to that workspace. Only after locating an authorized issue does it check whether Alice's role permits the transition. A move from “open” to “closed” may have different rules from moving to “waiting for customer.”
If Bob supplies Alice's issue identifier, the same lookup should not escape Bob's workspace boundary. Merely checking that the identifier exists would turn the tool into a cross-tenant object-access bug.
Here is a simplified application policy sequence. It assumes authentication has already been implemented and is not a complete OAuth example:
principal = authenticate(request)
workspace = require_membership(principal, active_workspace)
issue = find_issue_within(workspace, arguments.issue_id)
require_transition_permission(principal, issue, arguments.status)
apply_transition(issue, arguments.status, expected_version)
record_outcome(principal, issue, policy_version)
The expected_version check addresses a different problem: the issue may have changed since it was reviewed. Permission to edit an object does not mean a stale write is correct.
Narrow the downstream credential as well
If every call uses a shared organization-wide administrator token, the application becomes the only barrier protecting all downstream resources. Sometimes that integration model is unavoidable, but it should be an explicit risk with compensating controls and an owner.
Prefer a downstream credential whose permissions match the integration's job. A read-only reporting service should not receive write permissions merely because a future feature might need them. A tool that updates issue status should not expose a generic arbitrary-request capability under the same credential without a separate review.
Where practical, distinguish user-delegated access from service-owned automation. The audit record should make clear whether an operation acted under Alice's delegation or under a workspace automation identity. Calling both “the agent” obscures the question responders will need to answer later.
A useful permission matrix
Before implementing a new capability, fill in a matrix with concrete resource types:
| Operation | Required resource boundary | Additional control |
|---|---|---|
| Read an issue summary | Current workspace and visible project | Return only fields needed for the task |
| Change issue status | Same boundary plus allowed role | Validate transition and object version |
| Export issue attachments | Same boundary plus export permission | Review destination and retention |
| Delete a project | Explicit project administration | Specific confirmation and recovery plan |
“Additional control” is not a universal requirement for every product. It is where the team records the consequence-specific decision. A routine status update and a project deletion should not inherit identical handling simply because both are writes.
Test denial as a normal outcome
Create test cases for a valid token with the wrong audience, a valid user outside the workspace, a visible issue with a forbidden transition, an expired downstream connection and a revoked role. Add a case where permissions change between preparation and execution.
Make the result understandable without leaking another tenant's data. A denial should tell the client what the requester can do next, such as selecting an accessible project or reconnecting an expired integration. It should not expose private object names in a detailed debugging message.
Do not automatically retry authorization failures through a more privileged identity. That converts a boundary into a fallback preference. Escalation should be an explicit authorized workflow with a visible change of identity or scope.
Maintain the model after launch
Keep the permission matrix alongside the tool contract and review it when adding fields, destinations or downstream scopes. A tool can become more powerful without changing its name. Adding attachments, bulk selection or external delivery may materially expand what the same authenticated caller can accomplish.
Authentication makes the caller recognizable. Resource and operation checks make the caller's authority specific. Keeping both visible is what allows an assistant to be useful without inheriting every permission available somewhere in its execution path.
Sources and further reading
- MCP authorization
HTTP OAuth roles, audience/resource binding and prohibition of inbound token passthrough.