Skip to content

docs: add auth0-ai-fastmcp guides - #176

Open
kishore7snehil wants to merge 3 commits into
feat/auth0-ai-fastmcp-examplefrom
docs/auth0-ai-fastmcp-guides
Open

kishore7snehil wants to merge 3 commits into
feat/auth0-ai-fastmcp-examplefrom
docs/auth0-ai-fastmcp-guides

Conversation

@kishore7snehil

Copy link
Copy Markdown
Contributor

📋 Changes

Adds the guides for auth0-ai-fastmcp, links them from the README, and adds a test that checks the code samples in them. This PR is stacked on #175.

✨ Features

  • docs/Configuration.md covers every Auth0FastMCPOptions field, how requests are verified, and the errors callers see.
  • docs/ToolScopes.md covers how scopes are advertised at login and how tools, resources and prompts are gated.
  • EXAMPLES.md shows a full server, gated tools, resources and prompts.
  • README.md gets a Documentation section that links the guides and the auth0-ai-mcp guides it builds on.
  • CLAUDE.md, AGENTS.md and references/ give contributors and coding agents the package layout, commands, code style, and which docs to update for which change.

📖 Documentation

Links from the example README.md to docs/Configuration.md now resolve.

🧪 Testing

tests/test_docs.py parses every Python snippet in the README, EXAMPLES.md and docs/, and checks that the names imported from auth0_ai_fastmcp, auth0_ai_mcp and fastmcp exist. Tests and ruff check pass on Python 3.13.

  • This change adds unit test coverage
  • This change adds integration test coverage
  • This change has been tested on the latest version of the platform/language or why not

Contributor Checklist

@kishore7snehil
kishore7snehil marked this pull request as ready for review November 4, 2026 12:32
@kishore7snehil
kishore7snehil requested a review from a team as a code owner November 4, 2026 12:32
@nandan-bhat nandan-bhat mentioned this pull request Nov 4, 2026
3 of 5 tasks
```python
@mcp.resource("orders://recent")
async def recent_orders() -> str:
require_scopes(get_caller_principal(), "resource:recent_orders")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The server above advertises only the two tool:* scopes in tool_scopes. A client that follows the MCP spec asks for the advertised scopes, so it never gets resource:recent_orders or prompt:order_summary, and both handlers deny everyone. In my run the resource gave Missing one or more required scopes.

Can we add both scopes to tool_scopes in the sample above, and say in ToolScopes.md that tool_scopes should list every scope clients need, for tools, resources and prompts?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 03cd8c1.


There are two distinct authorization layers at play in an `auth0-ai-fastmcp` server.

The first layer is the MCP server's own tool authorization. The `tool:*` scopes you list in `tool_scopes` are included in the RFC 9728 protected-resource metadata under `scopes_supported`, and so is every scope in `required_scopes`. When an MCP client connects, it reads this metadata and requests those scopes during the login flow. The `with_required_scopes` decorator enforces them on a tool. If the caller's token does not carry a required scope, the tool call is rejected before it runs.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This says the client requests the tool_scopes and the required_scopes from the metadata. With required_scopes set, the 401 challenge carries only required_scopes in scope, and a client that follows the MCP spec uses that first. So the tool_scopes are not requested at login. The Configuration.md lines 29, 36 and 98 repeat it or point here.

Can we fix this together with my comment on config.py in #174? Either the provider returns both lists in the challenge, or these lines say what really happens.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 5a62b8c.


`resource_url` is the base URL of your server with no MCP path on the end. FastMCP adds the path the MCP endpoint is mounted at (`/mcp` by default) and advertises the result as the resource in the protected-resource metadata.

The MCP client reads that advertised resource and asks Auth0 for a token for it, so the audience that incoming tokens are validated against has to be the same value. It defaults to `resource_url` plus `mcp_path`, which means `resource_url="https://your-api.example.com"` gives the audience `https://your-api.example.com/mcp`. That value is also the API identifier to register in Auth0.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registering the API is not enough. The tenant also needs the compatibility resource parameter profile, the scopes added to the API as permissions, and a client grant for the MCP client. Without the profile, Auth0 ignores resource, so login fails or the token has no API audience. This is only in the example README and in FastMCP's provider docs.

Can we add one sentence here, and in the README Getting Started, linking to 'What you need on the tenant' in the example README?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 03cd8c1.


`Auth0ContextMiddleware` runs on every MCP request after the token is verified, including tool calls, resource reads, prompt requests and the list calls. It builds the caller's `Principal` from the verified token and makes it available to `get_caller_principal()` and `require_caller_mcp_client()` for the length of the request. The context is cleared when the request ends, even if the handler raises.

A request with no verified token raises `UnauthenticatedError`. With the provider mounted this does not happen, because FastMCP turns the request away first, so seeing it means the server was created without `auth=auth0.provider`. It is logged with a traceback so the missing provider is easy to spot. The `initialize` and `ping` requests are the exception, since they carry no caller. A token without a usable `sub`, `exp` or client id never counts as verified, so it gets a `401` before the middleware runs.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not true for a client that runs in the same process. With Client(mcp) in a test there is no bearer token, server/discover fails with this error, and the client cannot connect. So seeing it does not always mean auth= is missing. references/pitfalls.md line 9 and CLAUDE.md line 81 say the same.

Can we word it as 'no verified token' (no auth=, an in memory client or stdio), and add a line on testing over HTTP with a token?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 03cd8c1.

mcp.add_middleware(auth0.middleware)
```

Settings are checked when `Auth0FastMCP(options)` runs, so a bad combination fails at server startup rather than on the first request.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not true for client_id and client_secret. With both missing, or only client_id set, Auth0FastMCP starts without error and the first exchange fails. The client sees Client credentials are required to use get_token_by_exchange_profile, a core name the user never touched, or only Error calling tool with mask_error_details=True.

Can we limit this sentence to the checks listed below, and add a line under client_id that missing credentials show up only at the first exchange?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 5a62b8c.

> With `non_strict`, a tool can get a token with more access than it asked for. A tool that asks for `read:inventory` can be handed a saved token that also carries `write:inventory`. Only choose it when that is acceptable for every tool on the server.

```python
store = YourIndexedTokenStore(secret="YOUR_STORE_SECRET") # your IndexedTokenStore subclass

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The store base class needs a secret of at least 32 characters, and this placeholder has 17. Someone who pastes it gets a ConfigurationError.

Can we use a placeholder such as secret=os.environ["TOKEN_STORE_SECRET"], or a 32 character dummy value?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 03cd8c1.


- `StubMcpClient` in `tests/test_verifier.py` stands in for `McpServerClient`. It returns a `VerifiedToken` with the claims a test chooses, or raises the error a test chooses.
- `tests/test_middleware.py` patches `get_access_token` in the middleware module to control whether a request has a verified token. Its `Recorder` is a `call_next` that records the caller visible while the request runs, so a test can assert on both the bound context and its reset afterwards.
- `tests/test_config.py` patches `create_mcp_client` and `Auth0MCPProvider` to stay offline. Where a test needs the real provider, it patches `OIDCConfiguration.get_oidc_configuration` instead.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tests/test_config.py patches config._Auth0Provider, and no test patches Auth0MCPProvider. I tried the sentence as written, and it leaves one live call to the OIDC discovery URL, so the test needs the network.

Can we change the name here? Also flow-map.md line 14 says the verifier writes a debug log, but it is WARNING, and code-style.md line 12 says no error classes while scopes.py has two private ones.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 03cd8c1.

if isinstance(node, ast.ImportFrom) and node.module and node.module.split(".")[0] in CHECKED_PACKAGES:
module = importlib.import_module(node.module)
missing = [alias.name for alias in node.names if not hasattr(module, alias.name)]
assert not missing, f"{node.module} has no {missing}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test checks only the names in from x import y lines. A wrong keyword in Auth0FastMCPOptions(...), a missing method or import x.nope still passes, and if every fence gets another tag, the test is skipped and CI stays green. Also the SIGNATURE branch looks dead, as no snippet matches it.

Can we also check the keywords of Auth0FastMCPOptions(...) calls against the dataclass fields? I tried a seven line check. It passes on this head and fails when I rename a keyword.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test now checks Auth0FastMCPOptions(...) keywords against the real dataclass fields, resolves import x.y forms, and fails if the docs have no Python snippets. It does not check method names such as auth0.something(...), because that needs type information and a wrong name would show up as soon as the examples run. I also removed the one-line signature skip, since it did not apply to any current snippet. Fixed in 03cd8c1.

Comment thread packages/auth0-ai-fastmcp/README.md Outdated

- [Configuration](./docs/Configuration.md) - all `Auth0FastMCPOptions` fields.
- [Tool Scopes](./docs/ToolScopes.md) - how scopes are advertised at login and how tools, resources and prompts are gated.
- [Examples](./EXAMPLES.md) - a full runnable server.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These relative links work on GitHub, but PyPI shows this README as the package page, and there they lead nowhere. The auth0-ai-mcp README uses full https://github.com/... links for its docs, and the 'Core guides' list below does too.

Also EXAMPLES.md has ... placeholders, so 'a full runnable server' is not right. The runnable one is examples/b2b_ecommerce.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 03cd8c1.

Comment thread packages/auth0-ai-fastmcp/README.md Outdated

### Core guides

These guides cover the behavior this package builds on.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These guides import with_required_scopes and require_scopes from auth0_ai_mcp. In a FastMCP server that import still denies, but with mask_error_details=True the client sees only Error calling tool 'x', and the server logs an ERROR traceback.

Can we add one line here: 'In FastMCP code, import the scope helpers from auth0_ai_fastmcp'? ToolScopes.md says it, but most readers copy the snippet first.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 03cd8c1.

@kishore7snehil
kishore7snehil force-pushed the feat/auth0-ai-fastmcp-example branch from ed71bf7 to 14a7f6b Compare November 5, 2026 14:07
@kishore7snehil
kishore7snehil force-pushed the docs/auth0-ai-fastmcp-guides branch from 790e09d to e83c6ef Compare November 5, 2026 14:07
@kishore7snehil
kishore7snehil force-pushed the feat/auth0-ai-fastmcp-example branch from 14a7f6b to bdafc92 Compare November 5, 2026 15:02
@kishore7snehil
kishore7snehil force-pushed the docs/auth0-ai-fastmcp-guides branch from e83c6ef to e17aa27 Compare November 5, 2026 15:02
@kishore7snehil
kishore7snehil force-pushed the feat/auth0-ai-fastmcp-example branch from bdafc92 to 78d92a7 Compare November 5, 2026 15:48
@kishore7snehil
kishore7snehil force-pushed the docs/auth0-ai-fastmcp-guides branch from e17aa27 to 359cc8a Compare November 5, 2026 15:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants