Enable mixed authentication
Keep theoauth provider on the server, add mixedAuth: true, and declare securitySchemes on each tool that does not need sign-in.
mixedAuthrequires anoauthprovider. Setting it without one is a type error and throws aTypeErrorat construction. It is a constructor option, not a provider option.mixedAuthdoes not make anything public. Tools withoutsecuritySchemesstill require sign-in, and resources and prompts always do. Only the tools’ views load signed out.- With
mixedAuthoff (the default), every request to the MCP endpoint needs a valid token. - Every tool stays in
tools/listfor every caller. A signed-out user must see a sign-in tool, or the model never calls it and sign-in never starts. - A server with
mixedAuth: trueand nonoauthtools is valid. Anyone can connect and see what it offers, and every call needs sign-in. Directories and indexers can probe it before anyone signs in.
Declare securitySchemes on a tool
securitySchemes is an array of schemes the tool accepts. It uses the same shape ChatGPT reads on tools/list. There are two scheme types:
{ type: "noauth" }: the tool runs without a token.{ type: "oauth2", scopes }: the tool accepts a bearer token withscopesplus the provider’srequiredScopes.scopesis required; use[]for the provider’srequiredScopesalone.
requiredScopes is the baseline for every sign-in check.
- A tool requires sign-in unless its
securitySchemesincludenoauth. - A token that is sent is always verified, even on
noauthtools. An invalid or expired token gets401so the client refreshes it instead of silently getting the signed-out result. noauthrequiresmixedAuth: true.oauth2requires an OAuth provider. It also works withoutmixedAuth; see WithoutmixedAuth.scopes: []requires the provider to haverequiredScopes.- The order of the array does not matter. Each type may appear once.
Optional tools
An optional tool accepts bothnoauth and oauth2. It runs for everyone and branches on ctx.auth.
Optional tools with scopes
Theoauth2 scopes of an optional tool advertise what unlocks more, but never refuse a caller. The server does not check them, even for a signed-in token that lacks them, so the callback checks them itself.
An optional tool cannot bring up a sign-in prompt. Its result text can tell
the user to sign in, but the prompt only appears when the user calls a sign-in
tool, or signs in or reconnects from the host.
ctx.auth types
server.tool() reads the literal securitySchemes value. A tool that requires sign-in gets a required ctx.auth, and a tool that accepts noauth gets one that is possibly undefined.
securitySchemes comes from a variable typed as ToolSecurityScheme[], the array could include noauth, so ctx.auth is typed as possibly undefined. Write the array inline, or declare the variable as const, to get a required ctx.auth.
Resources, prompts, and views
Resources, resource templates, and prompts have nosecuritySchemes. On an OAuth server they always require sign-in with the provider’s requiredScopes, and their callbacks always get a required ctx.auth.
resources/read,prompts/get,completion/complete, andresources/subscriberequire sign-in. The list methods are always open on amixedAuthserver.- Every tool’s view loads signed out on a
mixedAuthserver, whatever the tool’ssecuritySchemes. ChatGPT reads every view while an app is created, before anyone signs in, and a refused read blocks creation. A view is static UI; the data arrives in the tool result, which the tool’ssecuritySchemesstill gate. subscriptions/listenis open signed out only when it subscribes to no resources, so it carries only list-changed notifications.- A refused resource read or prompt fetch always gets HTTP
401or403.
Signed-out requests
On amixedAuth server, a signed-out caller can always send:
initialize(2025-era protocols) andserver/discover(2026-07-28);pingandlogging/setLevel, which read no data;tools/list,resources/list,resources/templates/list, andprompts/list;resources/readof a tool’s view;subscriptions/listenwithout resource subscriptions;- notifications;
- an HTML browser request for the landing page at the MCP path.
tools/call follows the tool’s securitySchemes. Unknown tools require sign-in. Every other method, including resources/read of other resources, prompts/get, tasks/*, and the skills methods, requires sign-in.
How hosts see a refused call
The server refuses a call before the callback runs. Claude and ChatGPT need the refusal in different forms, so mcp-use picks the format from theUser-Agent header.
- Claude and other spec clients start OAuth only from HTTP
401, or from403witherror="insufficient_scope", with aWWW-Authenticateheader. Claude treats a200withisError: trueas an ordinary tool failure. - ChatGPT needs
securitySchemeson each tool intools/list, then a200tool result withisError: trueand_meta["mcp/www_authenticate"]. It does not start OAuth from a401on a tool call.
tools/call from a User-Agent that matches chatgpt or openai (case-insensitive) gets the tool-result format. Every other tools/call, including one without a User-Agent, and every refusal of another method gets HTTP.
No token:
scopeis the full set the call needs, the baseline plus the tool’s scopes, not only the missing ones. Claude does not reliably remember scopes from an earlier step-up.- The messages are fixed defaults. An invalid or expired token carries the verifier’s reason instead.
- Ordinary callback errors, such as an out-of-stock product, are ordinary tool results and never start OAuth.
Tool list metadata
mcp-use advertises the resolvedsecuritySchemes for every tool on a mixedAuth server, and for every tool that declares securitySchemes on other servers. The schemes appear at the top level of each tool, where ChatGPT reads them, and are mirrored in _meta.securitySchemes. The advertised oauth2 scopes include the provider’s requiredScopes, and noauth comes first.
oauth2 scheme with an empty scope list. Give the provider requiredScopes so tools without securitySchemes, and optional tools with scopes: [], advertise a scope.
Hand-written _meta.securitySchemes
Some servers already set _meta.securitySchemes on tools by hand for ChatGPT. mcp-use keeps it working and never enforces it.
Move the value to the top-level
securitySchemes field to have mcp-use enforce it.
Invalid configurations
These throw aTypeError at construction or registration instead of failing at request time.
Without mixedAuth
oauth without mixedAuth keeps endpoint-wide authentication: every request needs a token with the provider’s requiredScopes.
securitySchemes: [{ type: "oauth2", scopes }]still works on tools. Those scopes are enforced on top of the baseline.- Tools that declare
securitySchemesadvertise them. Tools without it advertise nothing, unless they set_meta.securitySchemesby hand. noauththrows at registration.
Verify locally
The mixed-oauth example runs a Better Auth authorization server and amixedAuth server on http://localhost:3000/mcp, with a tool for every securitySchemes shape. Its names start with their access level: public_ping is public, optional_welcome is optional with the profile scope, and protected_profile omits securitySchemes. From the example’s directory:
200 with the tool result:
401 with WWW-Authenticate:
200 with an isError result and _meta["mcp/www_authenticate"]:
pnpm dev --tunnel, restart it with MCP_URL set to the tunnel’s origin, and add the tunnel’s /mcp URL as a custom connector. The example’s README lists every item and a test checklist.
Next steps
OAuth
Choose and configure an OAuth provider.
User Context
Read identity, scopes, and permissions inside callbacks.