Skip to content

Commit 0f89376

Browse files
committed
Docs: shorter validate_token_resource guidance as a list; spell out test tokens
1 parent 84c7c13 commit 0f89376

2 files changed

Lines changed: 21 additions & 12 deletions

File tree

docs/run/authorization.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,18 @@ The SDK has no opinion about what a valid token looks like. You tell it, by impl
2323
```
2424

2525
* `TokenVerifier` is a protocol with one async method. `verify_token` gets the raw token from the `Authorization` header and returns an **`AccessToken`** if it's valid, `None` if it isn't. There is nothing else to implement.
26-
* This one looks the token up in a table that belongs to this server. A real one verifies a JWT signature or calls the authorization server's token-introspection endpoint, and puts the token's audience (`aud`) in `AccessToken.resource` so the SDK can check the token was issued for this server (see `validate_token_resource` below); if `aud` is a list, use the entry that equals your `resource_server_url`. That code is yours; the SDK only calls it.
26+
* This one looks the token up in a table. A real one verifies a JWT signature or calls the authorization server's token-introspection endpoint, and reports who the token was issued for (its `aud`) in `AccessToken.resource`. That code is yours; the SDK only calls it.
2727
* `token_verifier=` and `auth=` always travel together. Pass one without the other and `MCPServer(...)` raises a `ValueError` before it ever serves a request.
2828

2929
`AuthSettings` is the public face of your resource server:
3030

3131
* `issuer_url`: the authorization server that issues your tokens.
3232
* `resource_server_url`: the public URL of this MCP endpoint. It names *which* resource a token is for, and it's where the discovery document lives.
3333
* `required_scopes`: every token must carry all of them.
34-
* `validate_token_resource`: refuse any token your verifier does not report as issued for `resource_server_url` (its `AccessToken.resource`, compared as a URL, a trailing slash aside). Turn it on when your authorization server binds tokens to the `resource` a client asks for, which is what MCP clients send, and keep `resource_server_url` the exact endpoint URL clients connect to: a client that skips the discovery document binds its token to that URL, and a parent such as the bare origin will not match. If your authorization server issues its own audience identifiers instead (an Auth0 API identifier, an Entra application ID), leave it off and check `aud` against that identifier in your verifier: a token you cannot tie to this server should come back as `None`.
34+
* `validate_token_resource`: refuse any token whose `AccessToken.resource` is not `resource_server_url`. Off by default.
35+
* Turn it on when your authorization server binds tokens to the `resource` the client requested, which MCP clients always send. Keep `resource_server_url` the exact URL clients connect to.
36+
* Leave it off when your authorization server uses its own audience identifiers (an Auth0 API identifier, an Entra application ID) and check `aud` in your verifier instead, returning `None` for a token that isn't for this server.
37+
* If `aud` is a list, put the entry that equals `resource_server_url` in `resource`.
3538

3639
!!! tip
3740
`examples/servers/simple-auth/` in the SDK repository has an `IntrospectionTokenVerifier` that calls

tests/interaction/auth/test_bearer.py

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,18 +31,24 @@
3131
_PAST = int(time.time()) - 3600
3232

3333

34-
def tok(name: str, *, scopes: list[str], expires_at: int, resource: str | None = RESOURCE) -> AccessToken:
35-
return AccessToken(token=name, client_id="c", scopes=scopes, expires_at=expires_at, resource=resource)
36-
37-
3834
TOKENS = {
39-
"tok-valid": tok("tok-valid", scopes=[REQUIRED_SCOPE], expires_at=_FUTURE),
40-
"tok-expired": tok("tok-expired", scopes=[REQUIRED_SCOPE], expires_at=_PAST),
41-
"tok-noscope": tok("tok-noscope", scopes=["other:thing"], expires_at=_FUTURE),
42-
"tok-wrong-aud": tok(
43-
"tok-wrong-aud", scopes=[REQUIRED_SCOPE], expires_at=_FUTURE, resource="https://other.example/mcp"
35+
"tok-valid": AccessToken(
36+
token="tok-valid", client_id="c", scopes=[REQUIRED_SCOPE], expires_at=_FUTURE, resource=RESOURCE
37+
),
38+
"tok-expired": AccessToken(
39+
token="tok-expired", client_id="c", scopes=[REQUIRED_SCOPE], expires_at=_PAST, resource=RESOURCE
40+
),
41+
"tok-noscope": AccessToken(
42+
token="tok-noscope", client_id="c", scopes=["other:thing"], expires_at=_FUTURE, resource=RESOURCE
43+
),
44+
"tok-wrong-aud": AccessToken(
45+
token="tok-wrong-aud",
46+
client_id="c",
47+
scopes=[REQUIRED_SCOPE],
48+
expires_at=_FUTURE,
49+
resource="https://other.example/mcp",
4450
),
45-
"tok-no-aud": tok("tok-no-aud", scopes=[REQUIRED_SCOPE], expires_at=_FUTURE, resource=None),
51+
"tok-no-aud": AccessToken(token="tok-no-aud", client_id="c", scopes=[REQUIRED_SCOPE], expires_at=_FUTURE),
4652
}
4753

4854

0 commit comments

Comments
 (0)