Skip to content

Blog Article

MCP Authentication and OAuth: A Practical Guide

Understand MCP authentication for HTTP and stdio servers, OAuth discovery, token audience, PKCE, and the checks that locate a failed connection.

By AppHandoff Team · Published · 11 min read

Illustration of an MCP connection between an agent and a shared server
MCPSecurityOAuth

MCP authentication is the part of a connection that answers who the client is acting for and whether the server should accept its credential. It does not answer whether a particular project or tool action is allowed. For a remote HTTP MCP server, an OAuth flow can give the client a token intended for that server. For a local stdio server, the client starts a process that usually receives credentials from its environment. This guide is for developers connecting coding agents to protected MCP servers and for teams implementing one. It separates those cases, traces HTTP discovery, and shows how to find the first failing layer.

The protocol rules here follow the official MCP 2026-07-28 authorization specification. It describes OAuth 2.1 as an IETF draft, not a finalized standard. AppHandoff details below follow its current getting-started documentation and served tool registry, checked October 9, 2026. The failure examples are original illustrations; no live authentication or client compatibility test was run for this article.

First identify the transport

MCP has standard stdio and Streamable HTTP transports. The transport specification describes stdio as messages exchanged with a client-launched subprocess, while Streamable HTTP sends requests to an HTTP endpoint. Authentication follows that deployment boundary. A local process may use a credential supplied by its launcher; a protected remote endpoint must decide whether each incoming request has a valid credential. The MCP authorization specification says authorization is optional overall, HTTP implementations that support it should conform to its OAuth-based rules, and stdio implementations should obtain credentials from the environment instead of following the HTTP OAuth flow.

A stdio server can still call a protected service behind the scenes. In that case, decide how the launcher provides its credential, restrict the process environment and file access, and make the remote service enforce its own permissions. Do not copy an HTTP bearer token into a shared repository config merely because the MCP connection itself is local. An HTTP server may use an authorization server hosted at the same origin or elsewhere. Neither transport makes tool inputs safe, grants a project membership, or replaces application permission checks.

Three actors in HTTP authorization

In the protected HTTP flow, the MCP client requests access on behalf of a resource owner. The MCP server is the protected resource server: it receives tool requests and validates access tokens. An authorization server identifies the user when needed and issues tokens. The MCP server advertises where the client can find that authorization server. These are roles in a flow, not necessarily three separately hosted products. A successful sign-in only gets the client a credential; the MCP server must still check its audience, scopes, and application rules before returning data or performing a write.

The client should begin with the exact server URL. In the authorization server discovery rules, a protected server publishes Protected Resource Metadata with at least one authorization server. A 401 response can point to that metadata in WWW-Authenticate; otherwise the client tries the specified well-known locations. The client then discovers and validates authorization server metadata to learn the issuer and endpoints. When several authorization servers are advertised, client credentials and tokens issued by one must not be silently reused with another.

  1. Set the intended MCP HTTP endpoint and let the client request its protected resource metadata.
  2. Select an advertised authorization server, then validate its metadata and issuer before starting sign-in.
  3. Obtain an appropriate client ID, complete the authorization-code flow with PKCE, and request a token for this MCP resource.
  4. Send that token in the Authorization header on each HTTP request; let the MCP server validate it and check the requested action.
  5. After connecting, discover tools, resolve a permitted project, and make a named read before attempting a write.

This is a sequence of responsibilities, not a claim that every client automates every step. The selected client may offer a browser sign-in, require registration details, or fail before it reaches the server. Compare its actual behavior with the server's documented connection method. For AppHandoff, the canonical endpoint is https://api.apphandoff.com/mcp; its getting-started guide documents protected resource discovery for that URL and OAuth sign-in for supported client setups. The AppHandoff connection guide covers client-specific settings.

Registration and PKCE are client responsibilities

Before the authorization redirect, the client needs an ID understood by the chosen authorization server. The MCP client registration rules describe pre-registration, HTTPS Client ID Metadata Documents, and Dynamic Client Registration. Clients and authorization servers should support Client ID Metadata Documents. Dynamic Client Registration may be supported for backward compatibility and is deprecated; it is not a universal prerequisite. A client with existing registration should use that for the server before trying another mechanism. If none works, the client must surface the missing setup rather than inventing credentials.

PKCE helps prevent someone redeeming a stolen authorization code without the client's verifier: the client retains that verifier and sends a derived challenge before the user authorizes. The authorization security requirements require checking PKCE support and using S256 when technically capable. On return, it supplies the verifier during the token exchange. Before sending the code to a token endpoint, the client checks any returned iss against the issuer saved from validated metadata. If that metadata advertises authorization-response issuer support, a missing iss must also be rejected. These checks matter for desktop and other public clients that cannot safely keep a shared client secret. A redirect opening in a browser is therefore only one part of a valid grant; it is not proof that the token exchange finished or that the token works at the MCP server.

Bind the token to the server you meant to reach

The MCP client must include the OAuth resource parameter in both authorization and token requests, identifying the intended MCP server by its canonical URI. The protected server must validate that the resulting access token was issued for it as the intended audience. The client sends the token with Authorization: Bearer on every HTTP request; the specification forbids putting access tokens in URL query strings. A token minted for some other API or MCP endpoint cannot be made valid by copying it into a connection file. The authorization specification states the resource, header, and audience requirements.

Illustrative request shape only; no token or live call is shown:
POST https://mcp.example.com/mcp
Authorization: Bearer <access-token-for-this-resource>
Content-Type: application/json

The authorization request and token request both name:
resource=https://mcp.example.com/mcp

Use the server's documented canonical URI, not a guessed origin or a copied token for a nearby service. A 401 can mean authorization is required or the token is invalid or expired. A 403 can indicate insufficient scope or permission. A server should state the needed scope in an appropriate challenge when it can, and a client can seek a new grant for that operation. An OAuth scope still does not grant a user access to a project the application denies. Keep transport errors, token failures, and project permission refusals separate in logs and in user-facing remedies.

A failure matrix for the first connection

Locate the first boundary that fails. The following symptoms and next checks are diagnostic examples, not results of an authentication run. Read an actual response before changing a server setting or asking for broader permission.

  • Endpoint cannot be reached: check URL, HTTP versus stdio selection, DNS or process startup. OAuth cannot repair a connection that never reaches a server.
  • 401 before sign-in: inspect the WWW-Authenticate resource_metadata pointer or well-known metadata location. Confirm that its resource and advertised authorization server match the intended endpoint.
  • Browser sign-in loops or redirect is refused: check the chosen registration method, registered redirect URI, issuer validation and client PKCE state. Keep the failing response, without secrets.
  • Token exchange works but MCP returns 401: compare the resource used in both OAuth requests with the server URI, then check token expiry and audience validation. Do not put a token in the URL.
  • MCP returns 403 or a tool refusal: inspect the needed scope and the account's project or action permissions. A wider OAuth grant does not override a denied project membership.
  • Tools appear but a project read fails: list the connected identity and resolve an accessible project before changing transport or registration settings.

The HTTP status is only a starting point. Some servers return structured tool errors after the HTTP layer succeeds. For AppHandoff, the documented first call is bootstrap, followed by get or find for an authorized read. Its served catalogue has bootstrap, get, find, ticket, plan, message, project, and decide_lifecycle_proposal. The last is a signed-in human approval card action and may not be listed to a client without that support. Seeing a catalogue or a successful bootstrap does not authorize an agent to call every write action. The MCP Inspector guide walks through connection and read-only tool diagnosis.

A bounded AppHandoff read path

Choose a supported client, add https://api.apphandoff.com/mcp, and complete its documented OAuth flow with the intended AppHandoff account. AppHandoff also documents user-created personal access tokens for headless use; a portal API key is a different credential and is not accepted on this MCP endpoint. Once connected, request the tool catalogue, call bootstrap with a project the account should reach, then use get on a named ref or find for a bounded list. An account with several accessible projects may need an explicit choice. Confirm the returned project and caller context before using an action that writes.

AppHandoff reports an authenticated token missing a tool scope in-band: HTTP 200, isError: true, and INSUFFICIENT_SCOPE in the structured result. It does not send a 403 step-up challenge on this endpoint. Inspect the required and granted scopes. After a human approves a wider OAuth grant, refresh the token so its scope claim changes; replace a fixed-scope personal access token when different scopes are needed. AppHandoff's write tools enforce action-specific scope, revision, and approval rules. A human-gated lifecycle action produces an approval path; the agent must wait for the signed-in person's decision. This is an application rule on top of MCP authentication, not a property that OAuth supplies by itself. A user should judge a connection by one permitted read against the expected project, and judge a write separately by its actual rule result. The MCP versus API comparison explains why tool discovery and application authorization remain distinct.

Check configuration, then verify the real session

A config file can be well formed while the OAuth issuer, token audience, project permission, or tool call still fails. The MCP config checker examines selected core fields of strict JSON for Claude Code, Cursor, or VS Code in the browser. Use placeholder credentials. It does not contact an endpoint, run a command, test OAuth, or prove full client acceptance. After that narrow check, use the actual client or a suitable inspector to confirm connection, tool discovery, and a permitted project read. Record which layer succeeded and which response failed so the next retry changes the cause.

Frequently asked questions

Does every MCP server require OAuth?

No. MCP authorization is optional overall. The 2026-07-28 authorization specification addresses HTTP transports when authorization is supported; stdio servers should obtain credentials from their environment instead.

Why can I list tools but fail to read a project?

Tool discovery and project authorization are different checks. Confirm the connected identity, token audience and scope, then resolve a project the account can access. A listed tool does not grant access to its data.

Can I reuse a token from another MCP server?

No. An HTTP MCP server must validate that the token was issued for it as the intended audience. The client must request a token for the target resource and send it in the Authorization header, never a URL query string.