Appearance
AI and MCP
An AI client such as Claude can connect to your MCP server and act for one of your users. MicroAuth handles the part in between: the person signs in on your sign in pages, sees what the client asks for, and approves it. The client gets an access token for your MCP server only, with only the permissions that person holds and approved.
This follows the MCP 2026-07-28 authorization spec, so clients that support it connect without any setup on their side.
How a client connects
- The client calls your MCP server without a token and gets
401. The response points to your server's metadata. - The metadata names your sign in service as the place to get tokens.
- The client identifies itself, usually with a metadata document at a URL it owns, and sends the person to your sign in pages.
- The person signs in and sees a consent screen with the client's name and each permission it asks for.
- The client gets an access token for your MCP server and calls tools with it. When it needs more permissions later, it asks again for just those.
People see the clients they've allowed on their account page and can disconnect them at any time. A client they disconnect can't refresh its tokens, and the access token it already has works until it expires, 10 minutes at most. See apps people connect.
Set it up
- Turn on apps people connect under Sign in. Your plan needs to include them. See pricing.
- Register the MCP server under APIs and MCP servers with its URL, such as
https://app.example.com/mcp, and the permissions its tools need. - Protect it in code with
McpAuth, below. - Click Check MCP server on the server's page. MicroAuth fetches your server's metadata and confirms that it names this sign in service and this server's URL. It also warns when a request without a token doesn't get a
401that points to the metadata. The check only reaches servers on https at a public address, so it can't check one running on your own machine. You can run it 30 times an hour per service.
Protect an MCP server
McpAuth puts your sign in service in front of an MCP server built with the official MCP Python SDK.
bash
pip install "microauth-fastapi[mcp]>=3.1"python
from fastapi import FastAPI
from mcp.server.mcpserver import MCPServer
from microauth_fastapi import McpAuth, current_identity
mcp = MCPServer("Invoices", log_level="WARNING")
auth = McpAuth(scopes=["invoices:read"]) # reads MICROAUTH_ISSUER and MICROAUTH_RESOURCE
INVOICES: list[dict] = []
@mcp.tool()
@auth.tool("invoices:read")
def list_invoices() -> list[dict]:
me = current_identity().sub
return [invoice for invoice in INVOICES if invoice["owner"] == me]
@mcp.tool()
@auth.tool("invoices:write")
def create_invoice(customer: str, amount_cents: int) -> dict: ...
app = FastAPI()
auth.mount(app, mcp)bash
MICROAUTH_ISSUER=https://acme.microauth.dev
MICROAUTH_RESOURCE=https://app.example.com/mcpauth.mount(app, mcp) serves the MCP server at /mcp, the path of its resource, next to your app's other routes. It also serves the metadata and starts and stops the MCP server with your app. Keep log_level="WARNING", or the MCP SDK logs a line for every request. When the MCP server is the whole site, app = auth.wrap(mcp.streamable_http_app(...)) at the root of the host works too. The SDK reference has the details. Then:
- Every request needs a valid access token for that server with the
scopesgiven toMcpAuth. Without one, the client gets401with a challenge that names the metadata URL and those scopes. - A
tools/callalso needs the scopes declared for that tool, with@auth.tool(...)orMcpAuth(tool_scopes={"create_invoice": ["invoices:write"]}). A token missing one gets403witherror="insufficient_scope"and every scope the tool needs, so the client asks the person once. - A
403names the missing scope inWWW-Authenticateand in a JSON-RPC error body, so clients can show the person what's wrong. A refused POST with401,429or503gets a JSON-RPC body with the reason too. - The server's metadata is served at
/.well-known/oauth-protected-resource/mcp, the path inserted URL forhttps://app.example.com/mcp. The root/.well-known/oauth-protected-resourcestays free for an API on the same host. - Each POST carries one JSON-RPC message, and the
Mcp-MethodandMcp-Nameheaders must match the body. - Inside a tool,
current_identity()returns the caller'sTokenClaims:subis the person andclient_idis the AI client. The MCP SDK's ownget_access_token()works too.
Already using the MCP SDK's own auth settings? MicroAuthTokenVerifier plugs into them and checks tokens the same way. See the SDK reference.
Scopes are not ownership
A scope says what kind of action a client may take, not which records it may touch. invoices:read lets a client read invoices, but your tool still has to return only the ones that belong to current_identity().sub.
Clients that can't do OAuth can use an API key
Some MCP clients can only send a fixed header. For them, your users can create an API key for your MCP server with the permissions they choose, and paste it into the client's settings as Authorization: Bearer cms_....
Turn on keys for the service, give the MCP server its server credentials (in the dashboard, open APIs and MCP servers, then the server, then Server credentials), and add api_keys=True:
python
from microauth_fastapi import McpAuth
auth = McpAuth(scopes=["invoices:read"], api_keys=True)bash
MICROAUTH_RESOURCE_CLIENT_ID=...
MICROAUTH_RESOURCE_SECRET=...A key's permissions act as its scopes, so per tool scopes work the same with keys. A key without invoices:write gets 403 on create_invoice. To use that tool, the person creates a key that includes it. Inside a tool, current_identity().via is "api_key" and key_id names the key.
McpAuth enforces the per person limit and the unknown key limit, with the same settings as TokenAuth. MicroAuthTokenVerifier(api_keys=True) accepts keys too, but the MCP SDK hands it only the token, so it enforces neither limit. Use McpAuth when you need them.
Other languages
An MCP server in any language needs three things:
- Serve protected resource metadata that names your issuer in
authorization_serversand your server's URL asresource. It lives at the well-known path with your server's path added after it:/.well-known/oauth-protected-resource/mcpforhttps://app.example.com/mcp. Only a server URL without a path uses the bare/.well-known/oauth-protected-resource. - Answer requests without a valid token with
401andWWW-Authenticate: Bearer resource_metadata="<metadata URL>". - Check access tokens with the access token rules, or API keys with introspection.