Repository navigation
feat: add auth0-ai-fastmcp package - #174
kishore7snehil wants to merge 9 commits into
Conversation
| """Return the error code only version of `exc` if FastMCP wrapped a failed token exchange, else None.""" | ||
| cause = exc.__cause__ | ||
| # FastMCP's own wrapper message starts with this prefix, a handler's own error does not. | ||
| if isinstance(cause, ApiError | DownscopedTokenError) and str(exc).startswith(wrapper_prefix): |
There was a problem hiding this comment.
This works only when the error text starts with the exact name from the request. With mcp.mount(child, namespace="orders"), the child wraps the error with its own tool name, so the prefix does not match and Auth0's error_description reaches the client. I saw the same with mask_error_details=True on the parent, and with FastMCP's ErrorHandlingMiddleware added after auth0.middleware.
Can we match only the generic start (Error calling tool , Error reading resource , Error rendering prompt ) and mention the middleware order in the docs? Lines 78, 91 and 104 have the same issue.
| config_url=f"https://{options.domain}/.well-known/openid-configuration", | ||
| base_url=options.resource_url, | ||
| resource_base_url=options.resource_url, | ||
| scopes_supported=list(dict.fromkeys([*options.tool_scopes, *(options.required_scopes or [])])), |
There was a problem hiding this comment.
With required_scopes set, the metadata lists tool_scopes and required_scopes, but the 401 WWW-Authenticate carries only required_scopes in scope. A client that follows the MCP spec uses the challenge scope first, so it logs in with mcp:access only and never asks for tool_scopes. Then every gated tool returns a tool error with HTTP 200.
Can we override get_challenge_scopes in _Auth0Provider to return scopes_supported? Otherwise the docstring at line 25 and the guides in #176 should say this.
| from fastmcp.server.dependencies import get_access_token | ||
| from fastmcp.server.middleware import CallNext, Middleware, MiddlewareContext | ||
|
|
||
| _METHODS_WITHOUT_CALLER = frozenset({"initialize", "ping"}) |
There was a problem hiding this comment.
server/discover is missing from this set. With Client(server) in memory, which is how most people unit test a FastMCP server, the connect fails with connection is serving the 2026-07-28 protocol; the initialize handshake is not accepted. In legacy mode it connects, but every call fails with No verified access token on this request. Over HTTP it is fine.
Can we add server/discover here, as it carries no caller? Calls will still need a token, so a short note in the docs on how to test tool code would help too.
|
|
||
| def __init__(self, options: Auth0FastMCPOptions) -> None: | ||
| self._options = options | ||
| validation.require_bare_domain(options.domain) |
There was a problem hiding this comment.
The scope lists are not checked. required_scopes=["read write"] gives 403 for every request, as no token can hold a scope with a space. A plain string like required_scopes="read" is split into r e a d, and tool_scopes=None fails with a bare TypeError.
require_scopes in the lower package already raises ConfigurationError for a blank or whitespace entry. Can we run the same check on tool_scopes and required_scopes here?
| assert auth0.provider.required_scopes == ["read:a"] | ||
|
|
||
|
|
||
| class StubVerifyingClient: |
There was a problem hiding this comment.
All verify tests use this stub, so the cnf, aud and JWKS handling of the real core never run in this package. I removed the cnf check from McpServerClient.verify_access_token and all 117 tests still passed.
Can we add a few tests with a real McpServerClient and a token from generate_token? A valid token runs the tool, a cnf token gets 401, a wrong aud gets 401, a JWKS outage gets 503. I wrote four such tests, and they pass on this head.
|
|
||
| ### 1. Install the SDK | ||
|
|
||
| `auth0-ai-fastmcp` requires Python 3.10 or later and FastMCP 4.0.10 or later. It is tested with FastMCP 4.0.10 to 4.1.6. |
There was a problem hiding this comment.
CI installs only the locked FastMCP 4.0.10. I ran the suite on 4.1.6 and it passes, but nothing keeps it so. Also 'or later' is wider than ^4.0.10, which stops before 5.0.
The adapter uses FastMCP internals, like the error wrapper text and the provider routes. Can we add one CI job that installs the latest 4.x and runs the tests?
There was a problem hiding this comment.
Fixed in a6875a8. The range is now >=4.0.10 and a new CI job installs the newest FastMCP and runs the tests. The README line stays as the tested range.
| tool_scopes: Scopes advertised in the metadata so the client requests them at login. `required_scopes` are advertised too. | ||
| client_id: Needed for the on-behalf-of exchange, client credentials tokens and Token Vault. Token verification works without it. | ||
| client_secret: Needed alongside `client_id` for the same flows. | ||
| token_store: Backing store for cached OBO tokens. Nothing is cached without one. |
There was a problem hiding this comment.
The token_store also receives Token Vault tokens and client credentials tokens, not only OBO tokens. In my run it got writes for all three kinds.
Can we say 'cached exchange tokens' here, so people who write their own store or set a TTL know what will come in?
| _BODY = json.dumps( | ||
| { | ||
| "jsonrpc": "2.0", | ||
| "error": {"code": -32002, "message": "Service unavailable: the token could not be verified"}, |
There was a problem hiding this comment.
FastMCP's own ErrorHandlingMiddleware uses -32002 for 'Resource not found', and the MCP types list it as reserved and never to be reused. Here it means 'service unavailable', so a client can read the same code in two ways.
Can we use a free code from the implementation range, such as -32003, or the standard -32603?
| python = ">=3.10,<4.0" | ||
| fastmcp = "^4.0.10" | ||
| # Path dependency while reviewed with auth0-ai-mcp, becomes a version range once it is released. | ||
| auth0-ai-mcp = { path = "../auth0-ai-mcp", develop = true } |
There was a problem hiding this comment.
With a path dependency, the built wheel gets Requires-Dist: auth0-ai-mcp @ file:///... in its METADATA, and PyPI rejects an upload with such a direct reference. So this can merge, but it cannot be released until this line is a version range.
The description says the PR stays a draft until this and the core pin are replaced, but it is open for review. I assume the merge waits for that.
There was a problem hiding this comment.
This is a path dependency while auth0-ai-mcp is unreleased. It becomes a version range once auth0-ai-mcp is published, and this PR will not merge before that.
|
|
||
| 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: | ||
|
|
||
| You must give any other recipients of the Work or Derivative Works a copy of this License; and |
There was a problem hiding this comment.
Section 4 lost its (a) to (d) markers here, and the clauses (b) and (c) are joined in one line. The words match the root LICENSE, but the structure does not. auth0-ai-mcp/LICENSE has the same copy.
Can we copy the root LICENSE file as it is? License scanners read this file.
ce2c3b2 to
109c402
Compare
1cdf42a to
43e7f5a
Compare
109c402 to
73155b4
Compare
43e7f5a to
aaacf22
Compare
73155b4 to
de6ffca
Compare
aaacf22 to
afbcbf5
Compare
📋 Changes
Adds
auth0-ai-fastmcp, the adapter that secures Prefect FastMCP servers with Auth0. It is built onauth0-ai-mcpand wires token verification, the caller's identity and token exchange into a FastMCP server from one options object. The example and guides follow in separate PRs. This PR is stacked on #173.✨ Features
Auth0FastMCPtakes anAuth0FastMCPOptionsand exposes aproviderforFastMCP(auth=...)and amiddlewareforadd_middleware(...).Auth0TokenVerifierverifies each bearer token throughauth0-ai-mcp, so a token that fails verification gets a401and the tool never runs.required_scopesare passed to FastMCP, which answers403for a token missing one.Auth0ContextMiddlewaremakes the verified caller and a request-scoped client available to tools, resources and prompts, using the same accessors asauth0-ai-mcp.with_required_scopesandrequire_scopesgate tools, resources and prompts on the caller's scopes.domainis checked at startup, and a failed token exchange reaches the client as an error code only.test.yml,dependabot.ymlandpublish.ymlget an entry for the package.🔧 API Changes
New public API exported from
auth0_ai_fastmcp. Nothing existing changes.📖 Documentation
Adds a short
README.mdandCONTRIBUTING.md. The guides and example are added in follow-up PRs.🧪 Testing
The package has 117 unit tests that stub the core client, so they need no Auth0 tenant.
pytestandruff check .pass on Python 3.10, 3.13 and 3.14, andpoetry buildpasses.auth0-ai-mcpis a path dependency until it is released, and itsauth0-api-pythondependency is pinned to a commit SHA. This PR stays a draft until both are replaced with released versions.Contributor Checklist