Back to writing
MCP · 8 min read

Designing Authentication for MCP Servers

An MCP server is a protected resource, not an OAuth client wearing a different name. It must publish trust metadata and validate audience-bound access tokens.

The fastest way to make an MCP server insecure is to treat authentication as an API key field in the client configuration. It works in a demo, then the server grows ten tools, connects to customer data, and one copied secret can call everything. MCP changes how tools are discovered. It does not change what a protected resource must prove before executing them.

An HTTP MCP server should behave like an OAuth protected resource. It receives access tokens. It does not authenticate the human directly, and it should not collect the user's identity-provider password, full stop.

Discovery is part of the boundary

The July 2026 MCP authorization specification requires protected-resource metadata for HTTP authorization. RFC 9728 defines the /.well-known/oauth-protected-resource document that tells a client which authorization servers and scopes belong to the resource.

An MCP server is an OAuth protected resource

{
  "resource": "https://mcp.example.com",
  "authorization_servers": ["https://identity.example.com"],
  "scopes_supported": ["tickets:read", "tickets:update"]
}

The client must validate that the returned resource identifier matches the server it contacted. Otherwise discovery can become an authorization-server substitution path. The server must also validate that the access token was issued specifically for its own resource identifier, not merely signed by a familiar issuer.

Authentication and tool authorization are separate

A valid token answers which subject and client are calling. It does not mean every advertised tool is allowed. Map each tool to a required scope and evaluate resource context inside the handler.

const TOOL_SCOPES = {
  get_ticket: "tickets:read",
  update_ticket: "tickets:update",
} as const;
 
function authorizeTool(token: AccessToken, tool: keyof typeof TOOL_SCOPES) {
  if (token.aud !== "https://mcp.example.com") throw new Error("wrong audience");
  if (!token.scopes.has(TOOL_SCOPES[tool])) throw new Error("insufficient scope");
}

The handler must still check tenant and object-level access. tickets:update is not permission to update every ticket in every customer's account.

For local STDIO servers, the trust boundary is different because the process inherits credentials from its environment. Do not copy the HTTP OAuth flow blindly into that transport. Protect the environment, minimize the credential, and make the host application explicit about which local server it launches.

Preserve both human and client identity

An MCP client often calls on behalf of a human, but the client is an actor also. Audit records should preserve the user subject, client identifier, MCP server, tool, scope, tenant, and outcome. If the token reduces everything to one service account, the server cannot distinguish a user-approved action from an autonomous retry.

The takeaway is that MCP authentication is normal resource-server security applied to a powerful new interface. Publish trustworthy metadata, accept only audience-bound tokens, and authorize each tool call. The protocol can discover capabilities. Identity decides which capabilities become actions.

Akash Devdhar is a Senior Software Engineer specializing in enterprise identity, authentication, authorization, and AI infrastructure. He writes about building secure AI systems using OAuth, OIDC, RBAC, and modern identity architectures.