Repository navigation
docs: add auth0-ai-mcp guides #173
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
kishore7snehil
wants to merge
5
commits into
feat/auth0-ai-mcp-example
Choose a base branch
from
docs/auth0-ai-mcp-guides
base: feat/auth0-ai-mcp-example
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
ce4023d
docs: add auth0-ai-mcp guides
kishore7snehil 7c2f382
docs: address review feedback on auth0-ai-mcp guides
kishore7snehil 251d2b1
docs: document remaining gaps and add a Redis store example
kishore7snehil 8a0dd93
docs: describe the scope in the 401 challenge
kishore7snehil de6ffca
docs: describe bearer checks, no-store and scopes_supported validation
kishore7snehil File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| # AI Agent Guidelines for auth0-ai-mcp | ||
|
|
||
| @./CLAUDE.md for all coding guidelines, commands, project structure, code style, testing conventions, and boundaries. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,104 @@ | ||
| # AI Agent Guidelines for auth0-ai-mcp | ||
|
|
||
| ## Your Role | ||
|
|
||
| You are a Python SDK engineer on auth0-ai-mcp, the async library MCP servers use to verify Auth0 access tokens, enforce scopes, and exchange the caller's token for downstream API tokens. It is built on auth0-api-python and owns no MCP framework. | ||
|
|
||
| --- | ||
|
|
||
| ## Working Principles | ||
|
|
||
| - **Think before coding.** State assumptions. When a request is ambiguous, surface the interpretations and ask before building. | ||
| - **Simplicity first.** Write the minimum code that solves the stated problem. No speculative options, single-use abstractions, or error handling for impossible states. | ||
| - **Surgical changes.** Every changed line traces to the request. Clean up imports your change orphaned, leave pre-existing dead code alone. | ||
| - **Verify before done.** Turn the request into a checkable criterion (e.g. "reject X" becomes a failing test for X, then a fix) and run it before claiming success. | ||
|
|
||
| --- | ||
|
|
||
| ## Project Structure | ||
|
|
||
| ``` | ||
| src/auth0_ai_mcp/ | ||
| __init__.py # Public API: everything in __all__ is the supported surface | ||
| client.py # McpServerClient (verify, OBO, client credentials, Token Vault) and create_mcp_client | ||
| middleware.py # Auth0AsgiMiddleware and build_protected_resource_metadata. Not in __all__, import from auth0_ai_mcp.middleware | ||
| principal.py # Principal dataclass and build_principal (normalizes verified claims) | ||
| request_context.py # ContextVar request state and the get_caller_* accessors | ||
| request_scoped_client.py # RequestScopedMcpClient: the client bound to one request's verified token | ||
| scopes.py # require_scopes and the with_required_scopes decorator | ||
| errors.py # Errors owned by this package, all extend BaseAuthError | ||
| utils/_internal/ # Private helpers by topic (exchange, scope_input, urls, headers, mcp_session). A public helper is a plain-named file in utils/ | ||
| tests/ | ||
| test_client.py, test_middleware.py, test_principal.py, test_request_context.py, test_scopes.py, test_docs.py | ||
| docs/ # Hand-written guides, linked from README (see references/docs-update.md) | ||
| examples/b2b_ecommerce/ # Runnable server (server.py, tools.py, token_store.py) with its own README | ||
| README.md, EXAMPLES.md | ||
| pyproject.toml # Poetry manifest and source of truth for dependencies | ||
| .version, .shiprc # Release version source (.shiprc keeps .version and pyproject.toml in sync) | ||
| .ruff.toml # Lint config (CI-enforced) | ||
| ``` | ||
|
|
||
| Token verification, the OBO exchange, caching, and token stores live in auth0-api-python. This package wraps them and does not reimplement them. Before changing a flow, read its row in [references/flow-map.md](references/flow-map.md) for entry points, supporting helpers, tests, and docs. | ||
|
|
||
| --- | ||
|
|
||
| ## Boundaries | ||
|
|
||
| ### Always Do | ||
|
|
||
| - Run `poetry run pytest` and `poetry run ruff check .` before committing (both run in CI, see the Python matrix in `.github/workflows/test.yml`). | ||
| - Make surgical changes: touch only what the request requires. Do not refactor or reformat adjacent code. | ||
| - Follow existing style and naming. Private helpers go in `utils/_internal/` and public modules call them through the module, never by imported name. Never reorder existing declarations. | ||
| - Add unit tests for new functionality. Async tests need no marker because `asyncio_mode = "auto"` is set. Stand in for the core client with a `Stub...` class, as `tests/test_client.py` does (no real network calls). | ||
| - Raise typed errors from `errors.py` (subclasses of `BaseAuthError` with `get_status_code()` / `get_error_code()`). Let the core SDK's own errors propagate unchanged. Never return `None` or a bare `dict` to signal failure. | ||
| - Export any new public symbol through `__init__.py` `__all__`. | ||
| - Update `README.md`, `EXAMPLES.md`, and the matching `docs/*.md` in the same PR when changing the public API or supported integration patterns (see [references/docs-update.md](references/docs-update.md)). | ||
| - Keep code compatible with Python 3.10 (`target-version = "py310"`). | ||
| - Put imports at the top of the module. The `PLC0415` lint rule rejects imports inside functions, and `TYPE_CHECKING` blocks are the place for typing-only imports. | ||
|
|
||
| ### Ask First | ||
|
|
||
| - Any breaking change to a version that has already shipped. Stop and ask the maintainer before writing a renamed or removed public symbol, a changed signature or default, a new required option, or a raised Python floor. | ||
| - Changing an existing error's `get_error_code()` string or `get_status_code()`. Integrators branch on these, so treat them as public API. | ||
| - Adding or bumping dependencies (`pyproject.toml`, `poetry.lock`), including the `auth0-api-python` pin. | ||
| - Changes to token verification, the principal built from claims, scope matching, or the downscope check. | ||
| - Changes to CI/CD configuration (`.github/workflows/`, `.github/actions/`), `.ruff.toml` rule selection, or `.snyk` suppressions. | ||
|
|
||
| ### Never Do | ||
|
|
||
| - Commit secrets, client secrets, real tokens, or private keys. Examples read tenant values from environment variables. | ||
| - Hand-edit `poetry.lock` or `CHANGELOG.md`, or edit `.version` / the `pyproject.toml` version (the release flow owns them via `.shiprc`). | ||
| - Remove or skip a failing test instead of fixing it, or weaken an assertion to make it pass. | ||
| - Fail open. A verification, scope, exchange, or missing-context failure must raise a typed error or return a `401` challenge, never fall through to a permissive default. | ||
| - Hand the raw incoming token to tool code. `RequestScopedMcpClient` closes over it on purpose. | ||
| - Add a cache or token store to this package. Caching belongs to the core SDK and this package only passes the `token_store` through. | ||
| - Run `ruff format .` repo-wide. It is not a CI gate and the tree is not format-clean (see [references/pitfalls.md](references/pitfalls.md)). | ||
| - Modify `.venv*/`, `dist/`, `__pycache__/`, `.coverage`, `coverage.xml`. | ||
| - Log, print, or include access tokens, exchanged tokens, client secrets, or token endpoint response bodies in error messages. | ||
| - Break backward compatibility without asking first and getting explicit approval. | ||
|
|
||
| --- | ||
|
|
||
| ## Security Considerations | ||
|
|
||
| - **Verification is delegated:** every token goes through `McpServerClient.verify_access_token`, which calls the core SDK. Never decode a token yourself, and only pass already-verified claims to `build_principal`. | ||
| - **Fail closed in the middleware:** a missing, blank or non-Bearer token gets a `401` with a bare challenge, and a token that fails verification (including a claim set `build_principal` rejects) or has whitespace inside it gets a `401` with `invalid_token`. Both challenges carry `scope` when `scopes_supported` is set, and every `401` and `503` has `Cache-Control: no-store`. If Auth0 cannot be reached (`AuthServerUnavailableError`) the answer is a `503` with no challenge. WebSocket connections are closed and `OPTIONS` passes through without a token. The tool never runs, so keep it that way for any new rejection path. | ||
| - **Scope checks need every scope:** `require_scopes` raises `InsufficientScopeError` if any required scope is missing and `UnauthenticatedError` when there is no principal. `with_required_scopes` reads the principal from the request context, so a call outside a request is rejected. | ||
| - **Downscope detection:** `exchange.reject_if_downscoped` raises `DownscopedTokenError` when the exchange grants fewer scopes than were requested, for both On-Behalf-Of and client credentials but not Token Vault. An absent `scope` field in the response means the grant matched the request, so only a present field is compared. | ||
| - **Expired results are rejected:** after every exchange `exchange.reject_if_expired` raises the flow's own error when `expires_at` is missing or not in the future, so tool code never gets an already-expired token. Any leeway for clock skew belongs in the core SDK. | ||
| - **Generic error messages:** `InsufficientScopeError` and `DownscopedTokenError` deliberately do not name the scopes, and tests assert it. The invalid-token challenge uses the fixed description "The access token is not valid" and the real reason only goes to the log, so keep verification messages free of token contents. | ||
| - **Token stores:** a store is the integrator's `AbstractTokenStore`, and the base class takes a secret of at least 32 characters and provides `encrypt` and `decrypt` helpers that the store has to call itself in `set` and `get`. Core treats a failing store read as a cache miss and a failed write as non-fatal, so a failing store must not turn into an authorization decision. | ||
| - **Request context is per request:** `RequestContext` is a `ContextVar` set and reset around each request in `Auth0AsgiMiddleware`. Always reset it in a `finally`, otherwise one caller's identity can leak into the next request. | ||
| - **Dependency scanning:** Snyk (`sca_scan.yml`), CodeQL (`codeql.yml`), OpenSSF Scorecard (`scorecard.yml`), and ruff `S` (bandit) rules in CI. | ||
|
|
||
| --- | ||
|
|
||
| ## Reference Files | ||
|
|
||
| - Commands for install, test, lint, and build: [references/commands.md](references/commands.md) | ||
| - Test layout, async mode, and stubbing the core client: [references/testing.md](references/testing.md) | ||
| - Naming, the error pattern, and the comment rules: [references/code-style.md](references/code-style.md) | ||
| - Entry points, helpers, tests, and docs for each flow: [references/flow-map.md](references/flow-map.md) | ||
| - Branches, commits, PR template, CI, and releases: [references/git-workflow.md](references/git-workflow.md) | ||
| - Traps that cost time: [references/pitfalls.md](references/pitfalls.md) | ||
| - Which docs to update for which code change: [references/docs-update.md](references/docs-update.md) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| # Examples | ||
|
|
||
| This page walks through a complete Auth0-secured MCP server using the ASGI adapter shipped with `auth0-ai-mcp`. The goal is to show the full wiring from client construction to a gated tool that mints a downstream token, so you can see what the library handles and what stays in your code. | ||
|
|
||
| - [Building the client](#building-the-client) | ||
| - [Wiring the ASGI adapter](#wiring-the-asgi-adapter) | ||
| - [Writing a gated tool](#writing-a-gated-tool) | ||
| - [Complete example - b2b_ecommerce](#complete-example---b2b_ecommerce) | ||
|
|
||
| ## Building the client | ||
|
|
||
| ```python | ||
| from auth0_ai_mcp import create_mcp_client | ||
|
|
||
| client = create_mcp_client( | ||
| domain="your-tenant.auth0.com", | ||
| audience="https://your-mcp-server/mcp", | ||
| client_id="YOUR_CLIENT_ID", | ||
| client_secret="YOUR_CLIENT_SECRET", | ||
| ) | ||
| ``` | ||
|
|
||
| Every client option is described in [Configuration](./docs/Configuration.md). | ||
|
|
||
| ## Wiring the ASGI adapter | ||
|
|
||
| ```python | ||
| from mcp.server.mcpserver import MCPServer | ||
| from auth0_ai_mcp.middleware import Auth0AsgiMiddleware | ||
|
|
||
| mcp = MCPServer(name="My Server") | ||
|
|
||
| secured_app = Auth0AsgiMiddleware( | ||
| mcp.streamable_http_app(), | ||
| mcp_client=client, | ||
| scopes_supported=["read:weather"], | ||
| resource_name="My Server", | ||
| resource_documentation="https://your-mcp-server/docs", | ||
| ) | ||
| ``` | ||
|
|
||
| From this point on, the library verifies every incoming token, rejects bad ones with a `401` before any tool runs, and serves the protected resource metadata. For a public URL, pass `transport_security` as shown in [Writing an Adapter](./docs/WritingAnAdapter.md#ready-made-integrations), or the `mcp` SDK answers every request with a `421`. See [Token Verification](./docs/TokenVerification.md) and [Configuration](./docs/Configuration.md). | ||
|
|
||
| ## Writing a gated tool | ||
|
|
||
| ```python | ||
| from auth0_ai_mcp import require_caller_mcp_client, with_required_scopes | ||
|
|
||
| @mcp.tool() | ||
| @with_required_scopes("read:weather") | ||
| async def get_weather(location: str) -> dict: | ||
| auth0 = require_caller_mcp_client() | ||
| token = await auth0.get_token_on_behalf_of( | ||
| audience="https://weather-api/", | ||
| scope="read:weather", | ||
| ) | ||
| # Use token["access_token"] in your downstream HTTP call. | ||
| ... | ||
| ``` | ||
|
|
||
| Keep `@mcp.tool()` as the top decorator, see [Scope Enforcement](./docs/ScopeEnforcement.md#decorator-form). For how scope failures are reported, see [Scope Enforcement](./docs/ScopeEnforcement.md#errors). | ||
|
|
||
| For how `require_caller_mcp_client` and the caller's token work, see [Request Context](./docs/RequestContext.md#requestscopedmcpclient). | ||
|
|
||
| ## Complete example - b2b_ecommerce | ||
|
|
||
| The [`examples/b2b_ecommerce`](./examples/b2b_ecommerce/) directory contains a runnable server whose flagship `cancel_and_refund_order` tool touches two downstream services (orders and payments) in one call, minting a separate scoped token for each via On-Behalf-Of. It also shows: | ||
|
|
||
| - An in-memory `token_store` passed to `create_mcp_client` for caching OBO tokens, and `scope_matching` driven by an environment variable to switch between matching modes at deploy time. See [Token Caching](./docs/TokenCaching.md) for what a production store needs and the available modes. | ||
| - Organization policy configured to accept tokens only from a specific organization. The policy is optional and defaults to `"allow"`. See [Organization Policy](./docs/OrganizationPolicy.md). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I could not find
auth0-ai-fastmcpon PyPI or in this repo, and I did not see a PR for it. This line andWritingAnAdapter.mdline 25 tell FastMCP users to use it, so they have nothing to install today.Can we remove the sentence until the package is released, or say clearly that it is planned?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We would like to keep this mention.
auth0-ai-fastmcpis the FastMCP adapter for this package and it is planned to join this repo, so these two lines will not need to change when it does.Until then it cannot be installed, so we can say clearly that it is planned if you prefer. WDYT?