Authorization is where MCP stops being a demo and starts behaving like a production interface to real systems. Remote MCP servers should authenticate callers, map them to scopes or entitlements, and enforce those checks per capability.
The current MCP authorization model is transport-level and OAuth-based for HTTP deployments. Capability filtering and per-call authorization still remain server responsibilities.
Treat a remote MCP server like a protected resource API that also happens to expose AI-friendly discovery and invocation semantics.
| Layer | What It Answers |
|---|---|
| Authentication | Who is calling this server? |
| Capability filtering | What should this caller even see in tools/list or resources/list? |
| Per-call authorization | Can this caller perform this exact action on this exact target right now? |
| Auditing | What happened, who did it, and why was it allowed or denied? |
Authentication establishes identity. Authorization establishes allowed actions. That distinction matters because many MCP servers serve both read-only and write capabilities, and users often need access to some but not all of them.
Do not rely on host names, friendly labels, or client UI identity alone. Remote MCP servers should verify real credentials and tie them to durable access policy.
Current MCP authorization guidance for HTTP-based transports uses OAuth 2.x patterns. The specification requires clients to discover authorization information through OAuth protected resource metadata instead of guessing or hardcoding token endpoints.
That requirement matters for enterprise consistency. It lets resource servers describe how authorization works in a machine-readable way while keeping the MCP endpoint and auth metadata aligned.
Authorization should influence discovery as well as execution. If a caller does not have permission to use deployment tools, they generally should not see those tools in normal listings at all.
That reduces accidental misuse and makes host UX cleaner. It also avoids teaching the model about capabilities it should never try to invoke for that user.
Filtering alone is not enough. Access control must still run during the actual call because permissions can change, dynamic targets can be sensitive, and list views can become stale.
For example, a user may be allowed to read policy documents generally but not read one confidential HR investigation report. That check belongs in the resource read or tool handler, not only at list time.
type SessionUser = {
id: string;
scopes: string[];
};
function requireScope(user: SessionUser, scope: string): void {
if (!user.scopes.includes(scope)) {
throw new Error(`Missing required scope: ${scope}`);
}
}
server.registerTool(
'create_ticket',
{
description: 'Create an incident ticket in the approved queue.',
inputSchema: {
title: z.string().min(10),
},
},
async ({ title }, extra) => {
const user = extra.authInfo as SessionUser;
requireScope(user, 'tickets:write');
const ticketId = await createTicket(title, user.id);
return {
content: [{ type: 'text', text: `Created ticket ${ticketId}` }],
};
}
);
Authentication answers who is calling. Authorization answers what that caller may do now. In MCP systems, authorization must exist at transport, discovery, invocation, and object levels. A token with a broad read scope may allow connecting to the server, but it should not automatically allow reading every customer, ticket, file, or report.
Discovery filtering is helpful but not sufficient. A host may cache capabilities, a user role may change, or a model may call a capability through a path that was previously visible. The server must repeat authorization at invocation time using the current identity, tenant, scopes, target object, and action. Sensitive tools should also record the reason a request was allowed or denied.
For remote servers, align MCP authorization with the organization existing identity model. Use OAuth metadata discovery where applicable, map scopes to capability families, and keep object-level policy close to the backend source of truth. Avoid inventing a parallel permission model that drifts away from production systems.
Authorization is not the same as consent. A user may be allowed to send an email, but the host should still ask before a model-mediated workflow sends one. Consent prompts should explain the target system, action, important arguments, and consequence in plain language. Users should not have to inspect JSON to understand what will happen.
Consent should be risk-based. Reading a public document might not require an interrupt. Posting to a shared channel, deleting a record, granting access, or spending money should require explicit approval. Repeated approvals can sometimes be batched, but only when the user can review the exact scope and cancel before execution.
Scopes are necessary but not enough. A token may say a caller can read tickets, but the server still needs to know which tenant, project, customer, or ticket the caller may access. Object-level authorization is where many real security bugs live.
Remote MCP authorization also requires correct discovery and token audience behavior. Clients should learn protected resource metadata, request tokens for the intended MCP server resource, use secure flows such as PKCE where required, and avoid accepting tokens issued for another resource. These details prevent confused-deputy and token misuse problems.
Authorization should happen more than once. Discovery filtering decides which capabilities appear. Invocation authorization decides whether this caller can use this capability right now. Backend authorization decides whether the underlying data system permits the exact operation. Repeating checks may feel redundant, but each layer catches a different class of mistake.
Finally, design denial behavior carefully. A denial should be clear enough for the host and user to understand next steps, but not so detailed that it reveals hidden resource names, policy internals, or tenant information.
Write an authorization matrix for one remote MCP server. Rows are capabilities; columns are scopes, tenant checks, object checks, approval needs, and audit requirements. This makes it obvious where a simple scope is not enough.
Then simulate a user whose role changes during a session. Discovery may have shown a capability earlier, but invocation must still check current access. This exercise reinforces why authorization must happen at multiple layers.
For sensitive systems, review authorization decisions with sample identities from different tenants and roles, not only with an administrator token.
Discovery and invocation should apply the same trusted authorization context.
Authenticated subject: user-42
Tenant: tenant-a
Scopes: policies:read
tools/list:
include search_policies
omit update_policy
tools/call search_policies:
verify policies:read
constrain query to tenant-a
emit audit event
Usually no, but it still needs trust review, local permission thinking, and server-side validation.
No. They should be explicit enough for operators and hosts to distinguish denial from server malfunction, while remaining safe for users.
Explore 500+ free tutorials across 20+ languages and frameworks.