Authdog

MCP agents

Last updated Oct 20, 2026
View as Markdown

An MCP server exposes token_verifier. Implement that hook so a missing or rejected Authdog bearer never reaches a tool. The sample's whoami tool returns the subject the verifier already stored.

Sample: mcp/python. Python 3.10+. Package mcp==2.3.0.

MCP authentication is the hosted login an MCP client uses to obtain a token. This page is the resource-server check after the client presents one. The sample's caller is an agent client_credentials token, the same mint as A2A.

The verifier

Parse PK_AUTHDOG with validate_and_parse_public_key. That payload carries identity_host and environment_id. verify_token calls fetch_user_data — the same userinfo check as require_auth — and returns an MCP AccessToken only when the payload is authenticated and has a subject.

class AuthdogTokenVerifier:
    async def verify_token(self, token: str) -> AccessToken | None:
        try:
            data = await fetch_user_data(
                self._payload.identity_host,
                self._payload.environment_id,
                token,
            )
        except httpx.HTTPError:
            return None
        if not is_authenticated_user_info(data):
            return None
        subject = subject_of(data.get("user"))
        if not subject:
            return None
        return AccessToken(
            token=token,
            client_id=subject,
            scopes=["agent:invoke"],
            subject=subject,
        )

None refuses the bearer. subject_of reads id, then sub.

Pass the verifier on MCPServer and point AuthSettings.issuer_url at {identity_host}/oidc/{environment_id}:

server = MCPServer(
    name="authdog-mcp",
    token_verifier=AuthdogTokenVerifier(payload),
    auth=AuthSettings(
        issuer_url=issuer,
        resource_server_url=resource_url,
        required_scopes=["agent:invoke"],
        validate_token_resource=False,
    ),
)

validate_token_resource=False because this verifier checks the token by userinfo. It does not require an aud claim equal to the MCP resource URL. Set the audience on the Authdog client when you need that binding. The sample does not add a second JWT parser.

The tool

@server.tool()
def whoami() -> str:
    """Return the Authdog subject of the calling agent."""
    token = get_access_token()
    if token is None or not token.subject:
        raise PermissionError("Unauthorized")
    return token.subject

get_access_token() is the MCP auth context the verifier filled in. The tool reads subject from that context.

What you should see

POST /mcp with no bearer returns 401. The initialize method does not run as an authenticated session.

Repeat with Authorization: Bearer <token> that userinfo accepts, then call whoami. The text is the Authdog subject.

pytest in the sample checks the unauthenticated 401 and that the verifier returns subject agent_1 for an accepted userinfo response.

Configure

Variable Purpose
PK_AUTHDOG Public key. The server never sees the client secret.
MCP_RESOURCE_URL Resource server URL passed to AuthSettings. Defaults to http://127.0.0.1:8000.
Read To learn how to
MCP authentication OAuth 2.1 metadata and the client login flow
MCP SDK AuthdogVerifier and the runtime guard
LangGraph The same userinfo gate in front of a graph

Learn more