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](https://github.com/authdog/agents-samples/tree/main/mcp/python). Python 3.10+. Package `mcp==2.3.0`.

[MCP authentication](/docs/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](/docs/agent-frameworks/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.

```python
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}`:

```python
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

```python
@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`. |

> **Note**
>
> `@authdog/mcp-sdk` is a separate package, covered in [MCP SDK](/docs/mcp-sdk). This sample does not install it. It uses `fetch_user_data` from the pinned [web-sdk](https://github.com/authdog/web-sdk) Python package plus `mcp==2.3.0`. Install from the sample `requirements.txt`.

## Related

| Read | To learn how to |
| --- | --- |
| [MCP authentication](/docs/mcp-authentication) | OAuth 2.1 metadata and the client login flow |
| [MCP SDK](/docs/mcp-sdk) | `AuthdogVerifier` and the runtime guard |
| [LangGraph](/docs/agent-frameworks/langgraph) | The same userinfo gate in front of a graph |
