Add authorization checks to components and servers#2855
Conversation
Tools can now specify auth checks via the `auth` parameter:
```python
@mcp.tool(auth=require_auth)
def protected_tool(): ...
@mcp.tool(auth=require_scopes("admin"))
def admin_tool(): ...
```
Auth checks filter tools from list_tools and enforce at call_tool time.
STDIO transport bypasses all auth (no OAuth concept in local transport).
AuthMiddleware provides server-wide enforcement.
Test Failure AnalysisSummary: Two tests fail with AccessToken type conversion error when listing tools and calling tools via OAuth authentication. Root Cause: The FastMCP's Suggested Solution: Fix # Change line 420 from:
claims=access_token_as_dict.get("claims"),
# To:
claims=access_token_as_dict.get("claims", {}),This ensures that when Detailed AnalysisFailing Tests
Both fail with: Error Location
Why It FailsWhen an OAuth client connects:
Verificationfrom mcp.server.auth.provider import AccessToken as SDKAccessToken
from fastmcp.server.auth.auth import AccessToken as FastMCPAccessToken
sdk_token = SDKAccessToken(token="test", client_id="client123", scopes=["read"])
access_token_as_dict = sdk_token.model_dump()
print(access_token_as_dict)
# {'token': 'test', 'client_id': 'client123', 'scopes': ['read'], 'expires_at': None, 'resource': None}
# This fails with ValidationError:
FastMCPAccessToken(**access_token_as_dict, claims=None)
# ValidationError: claims - Input should be a valid dictionary [type=dict_type, input_value=None]
# This works:
FastMCPAccessToken(**access_token_as_dict, claims={})
# Success!Related Files
Note: This analysis addresses the test failures. There are also static analysis failures (ruff lambda check and loq file size limits) that were analyzed in my previous comment and need to be addressed separately. 🤖 Updated analysis by Marvin - Run 20936464286 |
WalkthroughThis PR introduces a comprehensive callable-based authorization system for FastMCP. It adds a new Possibly related PRs
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/fastmcp/server/server.py (1)
863-888: AuthorizationError from auth checks will propagate and break get_tools().The code at lines 880-882 calls
run_auth_checks()without error handling. Sincerun_auth_checks()explicitly re-raisesAuthorizationError(allowing auth checks to signal explicit denial with a custom message), anyAuthorizationErrorraised during the loop will propagate up and fail the entireget_tools()call.However, the test suite demonstrates the intended behavior is silent filtering—unauthorized tools should simply not appear in the returned list. Tools failing auth checks that return
Falseare correctly skipped viacontinue, but auth checks that raiseAuthorizationErrorwill break the entire request.Wrap the
run_auth_checks()call in a try-except that catchesAuthorizationErrorand treats it like aFalsereturn (continue to the next tool):if not is_stdio and tool.auth is not None: ctx = AuthContext(token=token, tool=tool) try: if not run_auth_checks(tool.auth, ctx): continue except AuthorizationError: continue
🧹 Nitpick comments (2)
src/fastmcp/server/providers/local_provider.py (1)
415-454: Consider documenting theauthparameter.The docstring documents most parameters but omits
auth. For consistency with other documented parameters, consider adding:auth: Optional authorization checks for the tool. Can be a single check or list of checks that must all pass (AND logic).src/fastmcp/server/auth/authorization.py (1)
158-159: Consider supportingSequencefor broader input flexibility.The current implementation only handles
listexplicitly. While the type annotation specifieslist, users might intuitively pass tuples.♻️ Optional: Support Sequence types
- check_list = [checks] if not isinstance(checks, list) else checks + check_list = [checks] if callable(checks) and not isinstance(checks, (list, tuple)) else list(checks)Or update the type signature to
Sequence[AuthCheck]if you want to formally support this.
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (2)
tests/server/auth/test_authorization.pyis excluded by none and included by noneuv.lockis excluded by!**/*.lockand included by none
📒 Files selected for processing (10)
docs/servers/auth/authorization.mdxsrc/fastmcp/exceptions.pysrc/fastmcp/server/auth/__init__.pysrc/fastmcp/server/auth/authorization.pysrc/fastmcp/server/middleware/__init__.pysrc/fastmcp/server/middleware/authorization.pysrc/fastmcp/server/providers/local_provider.pysrc/fastmcp/server/server.pysrc/fastmcp/tools/tool.pysrc/fastmcp/tools/tool_transform.py
🧰 Additional context used
📓 Path-based instructions (3)
src/**/*.py
📄 CodeRabbit inference engine (AGENTS.md)
src/**/*.py: Python source code must use Python ≥3.10 with full type annotations
Never use bareexcept- be specific with exception types
Prioritize readable, understandable code - clarity over cleverness; avoid obfuscated or confusing patterns
Follow existing patterns and maintain consistency across the codebase
Files:
src/fastmcp/server/middleware/__init__.pysrc/fastmcp/server/middleware/authorization.pysrc/fastmcp/exceptions.pysrc/fastmcp/server/providers/local_provider.pysrc/fastmcp/tools/tool.pysrc/fastmcp/server/server.pysrc/fastmcp/server/auth/__init__.pysrc/fastmcp/server/auth/authorization.pysrc/fastmcp/tools/tool_transform.py
src/**/__init__.py
📄 CodeRabbit inference engine (AGENTS.md)
Be intentional about re-exports - don't blindly re-export everything to parent namespaces; only re-export fundamental types to fastmcp.*
Files:
src/fastmcp/server/middleware/__init__.pysrc/fastmcp/server/auth/__init__.py
docs/**/*.mdx
📄 CodeRabbit inference engine (docs/.cursor/rules/mintlify.mdc)
docs/**/*.mdx: Use clear, direct language appropriate for technical audiences
Write in second person ('you') for instructions and procedures in MDX documentation
Use active voice over passive voice in MDX technical documentation
Employ present tense for current states and future tense for outcomes in MDX documentation
Maintain consistent terminology throughout all MDX documentation
Keep sentences concise while providing necessary context in MDX documentation
Use parallel structure in lists, headings, and procedures in MDX documentation
Lead with the most important information using inverted pyramid structure in MDX documentation
Use progressive disclosure in MDX documentation: present basic concepts before advanced ones
Break complex procedures into numbered steps in MDX documentation
Include prerequisites and context before instructions in MDX documentation
Provide expected outcomes for each major step in MDX documentation
End sections with next steps or related information in MDX documentation
Use descriptive, keyword-rich headings for navigation and SEO in MDX documentation
Focus on user goals and outcomes rather than system features in MDX documentation
Anticipate common questions and address them proactively in MDX documentation
Include troubleshooting for likely failure points in MDX documentation
Provide multiple pathways (beginner vs advanced) but offer an opinionated path to avoid overwhelming users in MDX documentation
Always include complete, runnable code examples that users can copy and execute in MDX documentation
Show proper error handling and edge case management in MDX code examples
Use realistic data instead of placeholder values in MDX code examples
Include expected outputs and results for verification in MDX code examples
Test all code examples thoroughly before publishing in MDX documentation
Specify language and include filename when relevant in MDX code examples
Add explanatory comments for complex logic in MDX code examples
Document all API...
Files:
docs/servers/auth/authorization.mdx
🧠 Learnings (5)
📓 Common learnings
Learnt from: jlowin
Repo: jlowin/fastmcp PR: 0
File: :0-0
Timestamp: 2025-12-01T15:48:05.095Z
Learning: PR #2505 in fastmcp adds NEW functionality to get_access_token(): it now first checks request.scope["user"] for the token (which never existed before), then falls back to _sdk_get_access_token() (the only thing the original code did). This is not a reversal of order but entirely new functionality to fix stale token issues.
📚 Learning: 2025-12-01T15:48:05.095Z
Learnt from: jlowin
Repo: jlowin/fastmcp PR: 0
File: :0-0
Timestamp: 2025-12-01T15:48:05.095Z
Learning: PR #2505 in fastmcp adds NEW functionality to get_access_token(): it now first checks request.scope["user"] for the token (which never existed before), then falls back to _sdk_get_access_token() (the only thing the original code did). This is not a reversal of order but entirely new functionality to fix stale token issues.
Applied to files:
docs/servers/auth/authorization.mdxsrc/fastmcp/server/server.pysrc/fastmcp/server/auth/authorization.py
📚 Learning: 2025-11-26T21:52:08.947Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: docs/.cursor/rules/mintlify.mdc:0-0
Timestamp: 2025-11-26T21:52:08.947Z
Learning: Applies to docs/**/*.mdx : Provide authentication examples showing proper format in MDX API documentation
Applied to files:
docs/servers/auth/authorization.mdx
📚 Learning: 2026-01-12T16:24:54.978Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: .cursor/rules/core-mcp-objects.mdc:0-0
Timestamp: 2026-01-12T16:24:54.978Z
Learning: Applies to src/tools/**/*.{ts,tsx,js,jsx} : Changes affecting MCP Tools (like adding tags, importing, etc.) must be adopted, applied, and tested consistently
Applied to files:
src/fastmcp/tools/tool.py
📚 Learning: 2026-01-12T16:25:10.972Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-12T16:25:10.972Z
Learning: Applies to src/**/__init__.py : Be intentional about re-exports - don't blindly re-export everything to parent namespaces; only re-export fundamental types to fastmcp.*
Applied to files:
src/fastmcp/server/auth/__init__.py
🧬 Code graph analysis (6)
src/fastmcp/server/middleware/__init__.py (1)
src/fastmcp/server/middleware/authorization.py (1)
AuthMiddleware(48-144)
src/fastmcp/server/middleware/authorization.py (6)
src/fastmcp/server/auth/authorization.py (2)
AuthContext(45-57)run_auth_checks(134-177)src/fastmcp/server/dependencies.py (1)
get_access_token(375-426)src/fastmcp/server/middleware/middleware.py (3)
CallNext(42-43)Middleware(79-205)MiddlewareContext(47-63)src/fastmcp/server/providers/local_provider.py (4)
tool(350-367)tool(370-387)tool(393-542)get_tool(266-269)src/fastmcp/server/server.py (5)
tool(1772-1787)tool(1790-1805)tool(1807-1892)name(427-428)get_tool(890-923)src/fastmcp/tools/tool.py (6)
tool(793-793)tool(797-809)tool(813-826)tool(829-967)Tool(148-408)ToolResult(87-145)
src/fastmcp/server/providers/local_provider.py (3)
src/fastmcp/server/context.py (1)
fastmcp(186-191)src/fastmcp/server/server.py (3)
tool(1772-1787)tool(1790-1805)tool(1807-1892)src/fastmcp/tools/tool.py (6)
tool(793-793)tool(797-809)tool(813-826)tool(829-967)FunctionTool(411-560)Tool(148-408)
src/fastmcp/server/server.py (5)
src/fastmcp/server/context.py (1)
fastmcp(186-191)src/fastmcp/server/auth/authorization.py (2)
AuthContext(45-57)run_auth_checks(134-177)src/fastmcp/server/dependencies.py (1)
get_access_token(375-426)src/fastmcp/tools/tool.py (7)
tool(793-793)tool(797-809)tool(813-826)tool(829-967)FunctionTool(411-560)Tool(148-408)ToolResult(87-145)src/fastmcp/server/providers/base.py (1)
_is_component_enabled(331-342)
src/fastmcp/server/auth/__init__.py (1)
src/fastmcp/server/auth/authorization.py (5)
AuthContext(45-57)require_auth(64-75)require_scopes(78-103)restrict_tag(106-131)run_auth_checks(134-177)
src/fastmcp/server/auth/authorization.py (5)
src/fastmcp/exceptions.py (1)
AuthorizationError(42-43)src/fastmcp/server/auth/auth.py (1)
AccessToken(43-46)src/fastmcp/server/providers/local_provider.py (3)
tool(350-367)tool(370-387)tool(393-542)src/fastmcp/server/server.py (3)
tool(1772-1787)tool(1790-1805)tool(1807-1892)src/fastmcp/tools/tool.py (5)
tool(793-793)tool(797-809)tool(813-826)tool(829-967)Tool(148-408)
🪛 GitHub Actions: Run static analysis
src/fastmcp/server/providers/local_provider.py
[error] 1-1: Pre-commit loq: file size limit exceeded (743 > 738).
src/fastmcp/tools/tool.py
[error] 1-1: Pre-commit loq: file size limit exceeded (967 > 950).
src/fastmcp/server/server.py
[error] 1-1: Pre-commit loq: file size limit exceeded (2713 > 2682).
src/fastmcp/server/auth/authorization.py
[error] ruff-format made changes to files during pre-commit; please re-run formatting checks.
src/fastmcp/tools/tool_transform.py
[error] 1-1: Pre-commit loq: file size limit exceeded (953 > 952).
🪛 Ruff (0.14.10)
src/fastmcp/server/middleware/authorization.py
130-132: Avoid specifying long messages outside the exception class
(TRY003)
140-142: Avoid specifying long messages outside the exception class
(TRY003)
src/fastmcp/server/auth/authorization.py
168-168: Do not catch blind exception: Exception
(BLE001)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: Run tests: Python 3.13 on ubuntu-latest
- GitHub Check: Run tests: Python 3.10 on ubuntu-latest
- GitHub Check: Run tests: Python 3.10 on windows-latest
- GitHub Check: Run tests with lowest-direct dependencies
🔇 Additional comments (26)
src/fastmcp/tools/tool_transform.py (1)
597-597: Auth propagation looks correct.The
authattribute is properly inherited from the parent tool. This ensures transformed tools maintain the same authorization requirements as their parent.Consider whether
authshould be an optional parameter infrom_tool()to allow overriding the parent's auth (similar toannotations,tags, etc.), but the current inheritance-only behavior is reasonable for this initial implementation.src/fastmcp/exceptions.py (1)
40-43: LGTM!The new
AuthorizationErrorclass follows the established exception hierarchy pattern, properly inheriting fromFastMCPErrorwith a clear docstring.src/fastmcp/server/middleware/__init__.py (1)
1-15: LGTM!The
AuthMiddlewareis properly imported and exported, following the existing pattern for middleware components. The__all__list is alphabetically ordered.src/fastmcp/server/auth/__init__.py (1)
8-15: LGTM!The authorization primitives are appropriately re-exported as they are fundamental types intended for public use. The exports include:
- Core types (
AuthCheck,AuthContext)- Built-in checks (
require_auth,require_scopes,restrict_tag)- Utility function (
run_auth_checks)The
__all__list maintains alphabetical ordering. Based on learnings, these are intentional re-exports of fundamental types.Also applies to: 22-39
docs/servers/auth/authorization.mdx (1)
1-382: Documentation is comprehensive and well-structured.The authorization documentation follows the coding guidelines effectively:
- Complete runnable code examples with realistic patterns
- Clear progression from basic concepts to advanced topics
- Proper use of
Note,Warning, and tables for reference- Good coverage of exception handling patterns
- Clear distinction between tool-level visibility and middleware enforcement
src/fastmcp/server/middleware/authorization.py (2)
83-106: LGTM! Proper tool filtering with STDIO bypass.The
on_list_toolsimplementation correctly:
- Calls downstream first to get all tools
- Bypasses filtering for STDIO transport
- Evaluates auth checks for each tool individually
- Returns only authorized tools
108-144: Solid fail-closed enforcement with appropriate error handling.The
on_call_toolimplementation:
- Correctly bypasses auth for STDIO transport
- Fails closed when context is missing (good security posture)
- Retrieves the tool and enforces middleware-level auth checks
Note:
get_toolat line 134 may also run tool-level auth checks (perserver.pylines 889-922), but this is intentional—tool-levelauthand middleware-levelauthare separate concerns that stack appropriately.The static analysis hints about
TRY003(long exception messages) are acceptable here since the messages provide useful context for debugging authorization failures.src/fastmcp/server/providers/local_provider.py (3)
44-44: LGTM!Import correctly updated to include
AuthCheckCallablefor type annotations.
349-409: Auth parameter correctly added to all overloads and implementation.The
authparameter is consistently added to:
- First overload (line 366) for direct function decoration
- Second overload (line 386) for parameterized decoration
- Main implementation (line 409)
Type annotation
AuthCheckCallable | list[AuthCheckCallable] | Nonematches the definition intool.py.
489-508: LGTM! Auth correctly propagated in both decoration paths.
- Line 502:
auth=authpassed toTool.from_functionfor immediate registration- Line 541:
auth=authincluded inpartialfor deferred decorationBoth paths correctly propagate the auth configuration.
Also applies to: 527-542
src/fastmcp/server/auth/authorization.py (5)
1-42: LGTM: Well-structured module with clear documentation.The module docstring clearly explains the authorization semantics including how different exception types are handled. Imports are appropriate and the TYPE_CHECKING guard avoids circular imports correctly.
44-61: LGTM: Clean dataclass and type alias definitions.
AuthContextprovides appropriate context for auth checks, and theAuthChecktype alias clearly defines the callable contract.
64-103: LGTM: Auth check implementations are correct.Both
require_authandrequire_scopescorrectly handle the None token case and implement the expected AND semantics for multiple scopes.
106-131: LGTM: Tag-based scope restriction logic is sound.The
restrict_tagfunction correctly allows access when the tag is absent and enforces scope requirements only when the tag is present on the tool.
165-175: The broadExceptioncatch is appropriate here for security.Ruff flags
BLE001on line 168, but this is an intentional security pattern. Masking unexpected exceptions prevents information leakage about internal implementation details. TheAuthorizationErroris explicitly allowed to propagate, while other exceptions are logged and treated as denial. This follows security best practices for auth code.src/fastmcp/tools/tool.py (7)
54-56: LGTM: Type alias appropriately usesAnyto avoid circular imports.The comment clearly explains why
Anyis used instead of the actualAuthContexttype. This is a pragmatic solution for the circular import issue.
173-176: LGTM: Auth field properly added to Tool model.The field accepts single callable, list of callables, or None, aligning with the flexible auth check API.
226-243: LGTM: Auth parameter correctly propagated inTool.from_function.The parameter is passed through to
FunctionTool.from_functionmaintaining consistency.
451-515: LGTM: Auth propagation complete inFunctionTool.from_function.The auth parameter is correctly passed to the
cls()constructor, completing the propagation chain.
796-841: LGTM: Standalone@tooldecorator overloads include auth parameter.All three overload signatures consistently include the
authparameter with the correct type.
924-936: LGTM: Auth passed through in direct function decoration path.
955-967: LGTM: Auth included in partial return for deferred decoration.The auth parameter is correctly included in the
partialcall for cases where the function isn't immediately available.src/fastmcp/server/server.py (4)
71-72: LGTM: Auth-related imports added.The imports bring in the necessary authorization infrastructure from the new auth module.
901-923: Auth check inget_toolmirrorsget_toolscorrectly.The STDIO bypass and auth check logic is consistent with
get_tools. When auth fails, the loop continues to the next provider, ultimately raisingNotFoundErrorif no authorized tool is found—this prevents leaking that the tool exists but is unauthorized.
1771-1826: LGTM:@server.tooldecorator overloads include auth parameter.Both overloads consistently include the
auth: AuthCheckCallable | list[AuthCheckCallable] | Noneparameter.
1876-1892: LGTM: Auth correctly delegated to LocalProvider.The auth parameter is properly passed through to
self._local_provider.tool(), ensuring the authorization configuration reaches the tool registration.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a17a4142d8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| # Check tool-level auth (skip for STDIO) | ||
| if not is_stdio and tool.auth is not None: | ||
| ctx = AuthContext(token=token, tool=tool) | ||
| if not run_auth_checks(tool.auth, ctx): |
There was a problem hiding this comment.
Treat auth check errors as list_tools denials
run_auth_checks can raise AuthorizationError (the new docs even recommend it for explicit denials), but this call in get_tools does not catch it. That means a single tool with an auth check that raises will cause list_tools to fail for the entire request instead of simply hiding that tool, which breaks discovery for otherwise-authorized clients. Consider catching AuthorizationError here (and treating it as a failed check) so list_tools still returns the rest of the tools.
Useful? React with 👍 / 👎.
- AuthContext now uses `component` field instead of `tool` (tool is a backwards-compatible property) - Add `auth` parameter to all resource/prompt decorators - Add auth filtering to get_resources, get_prompts, etc. - AuthMiddleware now handles all component types - Catch AuthorizationError in list operations (hide instead of fail)
Test Failure AnalysisSummary: Static analysis failed because 9 files now exceed their configured line limits in Root Cause: The PR adds authorization functionality to tools, resources, prompts, and the server, increasing file sizes. The Suggested Solution: Update uv run loq baselineThis will automatically update all the line limits in Detailed AnalysisThe Each file exceeded its limit by a small amount (1-129 lines), which is expected when adding a new cross-cutting feature like authorization. The Related Files
|
Test Failure AnalysisSummary: 5 tests failed - 4 OAuth client tests and 1 logging middleware test. Root Cause:
Suggested Solution: Fix 1: OAuth Type Check Issue (src/fastmcp/server/dependencies.py:404)The problem is likely with the type check. The current code: if access_token is None or isinstance(access_token, AccessToken):
return access_tokenThis check should pass for SDK AccessToken objects (which FastMCP's AccessToken inherits from), but it seems to be failing. The issue might be that:
Recommended fix: Update the type check to be more lenient: # At line 404 in src/fastmcp/server/dependencies.py
if access_token is None:
return access_token
# Check if it's already our AccessToken type
if isinstance(access_token, AccessToken):
return access_token
# Check if it's at least an SDK AccessToken (duck typing check)
if hasattr(access_token, 'token') and hasattr(access_token, 'client_id') and hasattr(access_token, 'scopes'):
# SDK AccessToken or compatible - proceed with conversion
try:
access_token_as_dict = access_token.model_dump()
return AccessToken(
token=access_token_as_dict["token"],
client_id=access_token_as_dict["client_id"],
scopes=access_token_as_dict["scopes"],
expires_at=access_token_as_dict.get("expires_at"),
resource=access_token_as_dict.get("resource"),
claims=access_token_as_dict.get("claims", {}),
)
except Exception as e:
raise TypeError(
f"Expected AccessToken-like object, got {type(access_token).__name__}. "
"Ensure the SDK is using the correct AccessToken type."
) from e
raise TypeError(f"Unexpected access_token type: {type(access_token).__name__}")Fix 2: Logging Test Snapshot (tests/server/middleware/test_logging.py:335)Update the expected snapshot to include the new # At line 335 in tests/server/middleware/test_logging.py
'{\name\:\tmpl\,\title\:null,\description\:null,\icons\:null,\tags\:[],\meta\:null,\task_config\:{\mode\:\forbidden\,\poll_interval\:\PT5S\},\uri_template\:\tmpl://{id}\,\mime_type\:\text/plain\,\parameters\:{\id\:{\type\:\string\}},\annotations\:null,\auth\:null}"The snapshot now needs to include Detailed AnalysisOAuth Test FailuresAll 4 OAuth tests are failing with the same error pattern: This occurs in The failing tests are:
Logging Test FailureThe Related FilesPrimary files requiring changes:
Related to the issue:
|
Test Failure AnalysisSummary: 5 tests failed - 4 OAuth client tests and 1 logging middleware test. Root Cause:
Suggested Solution: Fix 1: OAuth Type Check IssueFile: src/fastmcp/server/dependencies.py:404-426 The problem is with the type check at line 404. The current code has an isinstance() check that should pass for SDK AccessToken objects, but it's failing. This might be due to module reloading or import issues causing type identity problems. Recommended approach: Add duck typing fallback to handle SDK AccessToken objects more gracefully. Instead of relying solely on isinstance(), check for the presence of required attributes and attempt the conversion for any AccessToken-like object. The key change needed: Restructure the conditional logic to first check for None, then for FastMCP AccessToken, then attempt conversion for any object with the required AccessToken attributes. The existing try/except should catch any actual incompatibilities. Fix 2: Logging Test SnapshotFile: tests/server/middleware/test_logging.py:335 Update the expected snapshot to include the new auth field. The serialized JSON now includes "auth":null at the end. Detailed AnalysisOAuth Test FailuresAll 4 OAuth tests are failing with the same error pattern: This occurs in src/fastmcp/server/dependencies.py:424 within the get_access_token() function. The function attempts to convert SDK AccessToken objects to FastMCP's AccessToken subclass, but the conversion logic is throwing a TypeError. The failing tests are:
All tests use the client_with_headless_oauth fixture which performs OAuth authentication. The failure happens when the server tries to process requests with the authenticated token. Logging Test FailureThe test_on_message_with_resource_template_in_payload test compares serialized JSON output. The test creates a ResourceTemplate and logs it via StructuredLoggingMiddleware with include_payloads=True. The test expects the old serialization without the auth field, but now the auth field is included in the output. Related FilesPrimary files requiring changes:
Related to the issue:
|
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/fastmcp/resources/template.py (1)
447-573: Propagate template auth to ephemeral Resource increate_resource().The template's
authfield is currently not passed toResource.from_function()when creating the ephemeral resource. SinceResource.from_function()accepts theauthparameter, it should be propagated to ensure the authorization policy is maintained if downstream code evaluates the Resource directly instead of the template.Fix
diff --git a/src/fastmcp/resources/template.py b/src/fastmcp/resources/template.py @@ async def create_resource(self, uri: str, params: dict[str, Any]) -> Resource: """Create a resource from the template with the given parameters.""" async def resource_read_fn() -> str | bytes | ResourceResult: # Call function and check if result is a coroutine result = await self.read(arguments=params) return result return Resource.from_function( fn=resource_read_fn, uri=uri, name=self.name, description=self.description, mime_type=self.mime_type, tags=self.tags, task=self.task_config, + auth=self.auth, )
🧹 Nitpick comments (3)
src/fastmcp/prompts/prompt.py (1)
636-793: Good: standaloneprompt(...)decorator overloads + implementation consistently accept/propagateauth.Minor: this file now has multiple “auth union” spellings (field vs overloads). If you find yourself repeating this elsewhere, a local
type PromptAuth = ...alias can help.src/fastmcp/resources/resource.py (1)
36-37: Same coupling note as prompts: importing auth typing fromfastmcp.tools.tool—confirm that’s intentional.src/fastmcp/server/server.py (1)
864-871: Refactor duplicated auth-check pattern into a helper function.The pattern of checking
is_stdio = _current_transport.get() == "stdio"followed bytoken = None if is_stdio else get_access_token()is duplicated across 8 methods inserver.pyand multiple methods inmiddleware/authorization.py. Extract this into a small internal helper to reduce duplication and make future changes easier to propagate.Note: The ContextVar
_current_transportis defined withdefault=None, so calling.get()is safe and will not raiseLookupError.
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
tests/server/auth/test_authorization.pyis excluded by none and included by none
📒 Files selected for processing (7)
src/fastmcp/prompts/prompt.pysrc/fastmcp/resources/resource.pysrc/fastmcp/resources/template.pysrc/fastmcp/server/auth/authorization.pysrc/fastmcp/server/middleware/authorization.pysrc/fastmcp/server/providers/local_provider.pysrc/fastmcp/server/server.py
🧰 Additional context used
📓 Path-based instructions (1)
src/**/*.py
📄 CodeRabbit inference engine (AGENTS.md)
src/**/*.py: Python source code must use Python ≥3.10 with full type annotations
Never use bareexcept- be specific with exception types
Prioritize readable, understandable code - clarity over cleverness; avoid obfuscated or confusing patterns
Follow existing patterns and maintain consistency across the codebase
Files:
src/fastmcp/resources/template.pysrc/fastmcp/server/server.pysrc/fastmcp/server/providers/local_provider.pysrc/fastmcp/server/auth/authorization.pysrc/fastmcp/resources/resource.pysrc/fastmcp/prompts/prompt.pysrc/fastmcp/server/middleware/authorization.py
🧠 Learnings (3)
📓 Common learnings
Learnt from: jlowin
Repo: jlowin/fastmcp PR: 0
File: :0-0
Timestamp: 2025-12-01T15:48:05.095Z
Learning: PR #2505 in fastmcp adds NEW functionality to get_access_token(): it now first checks request.scope["user"] for the token (which never existed before), then falls back to _sdk_get_access_token() (the only thing the original code did). This is not a reversal of order but entirely new functionality to fix stale token issues.
📚 Learning: 2025-12-01T15:48:05.095Z
Learnt from: jlowin
Repo: jlowin/fastmcp PR: 0
File: :0-0
Timestamp: 2025-12-01T15:48:05.095Z
Learning: PR #2505 in fastmcp adds NEW functionality to get_access_token(): it now first checks request.scope["user"] for the token (which never existed before), then falls back to _sdk_get_access_token() (the only thing the original code did). This is not a reversal of order but entirely new functionality to fix stale token issues.
Applied to files:
src/fastmcp/server/server.pysrc/fastmcp/server/auth/authorization.py
📚 Learning: 2026-01-12T16:24:54.978Z
Learnt from: CR
Repo: jlowin/fastmcp PR: 0
File: .cursor/rules/core-mcp-objects.mdc:0-0
Timestamp: 2026-01-12T16:24:54.978Z
Learning: Applies to src/prompts/**/*.{ts,tsx,js,jsx} : Changes affecting MCP Prompts (like adding tags, importing, etc.) must be adopted, applied, and tested consistently
Applied to files:
src/fastmcp/prompts/prompt.py
🧬 Code graph analysis (4)
src/fastmcp/resources/template.py (3)
src/fastmcp/server/providers/local_provider.py (3)
tool(350-367)tool(370-387)tool(393-542)src/fastmcp/server/server.py (3)
tool(1864-1879)tool(1882-1897)tool(1899-1984)src/fastmcp/tools/tool.py (4)
tool(793-793)tool(797-809)tool(813-826)tool(829-967)
src/fastmcp/server/auth/authorization.py (5)
src/fastmcp/exceptions.py (1)
AuthorizationError(42-43)src/fastmcp/server/auth/auth.py (1)
AccessToken(43-46)src/fastmcp/server/providers/local_provider.py (3)
tool(350-367)tool(370-387)tool(393-542)src/fastmcp/tools/tool.py (5)
tool(793-793)tool(797-809)tool(813-826)tool(829-967)Tool(148-408)src/fastmcp/utilities/components.py (1)
FastMCPComponent(34-184)
src/fastmcp/resources/resource.py (3)
src/fastmcp/server/auth/authorization.py (1)
tool(63-70)src/fastmcp/server/providers/local_provider.py (3)
tool(350-367)tool(370-387)tool(393-542)src/fastmcp/server/server.py (3)
tool(1864-1879)tool(1882-1897)tool(1899-1984)
src/fastmcp/prompts/prompt.py (4)
src/fastmcp/server/auth/authorization.py (1)
tool(63-70)src/fastmcp/server/providers/local_provider.py (3)
tool(350-367)tool(370-387)tool(393-542)src/fastmcp/server/server.py (3)
tool(1864-1879)tool(1882-1897)tool(1899-1984)src/fastmcp/tools/tool.py (4)
tool(793-793)tool(797-809)tool(813-826)tool(829-967)
🪛 GitHub Actions: Run static analysis
src/fastmcp/resources/template.py
[error] 1-1: loq: 9 violations detected across codebase.
src/fastmcp/server/server.py
[error] 1-1: loq: 9 violations detected across codebase.
[error] 1-1: loq: 9 violations detected across codebase.
[error] 1-1: loq: 9 violations detected across codebase.
src/fastmcp/server/providers/local_provider.py
[error] 1-1: loq: 9 violations detected across codebase.
src/fastmcp/resources/resource.py
[error] 1-1: loq: 9 violations detected across codebase.
src/fastmcp/prompts/prompt.py
[error] 1-1: loq: 9 violations detected across codebase.
🪛 Ruff (0.14.10)
src/fastmcp/server/auth/authorization.py
181-181: Do not catch blind exception: Exception
(BLE001)
src/fastmcp/server/middleware/authorization.py
135-137: Avoid specifying long messages outside the exception class
(TRY003)
145-147: Avoid specifying long messages outside the exception class
(TRY003)
195-197: Avoid specifying long messages outside the exception class
(TRY003)
205-207: Avoid specifying long messages outside the exception class
(TRY003)
281-283: Avoid specifying long messages outside the exception class
(TRY003)
291-293: Avoid specifying long messages outside the exception class
(TRY003)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: Run tests with lowest-direct dependencies
- GitHub Check: Run tests: Python 3.13 on ubuntu-latest
- GitHub Check: Run tests: Python 3.10 on windows-latest
- GitHub Check: Run tests: Python 3.10 on ubuntu-latest
🔇 Additional comments (20)
src/fastmcp/prompts/prompt.py (4)
204-207: Good:Prompt.authis stored on the component and stays out of the MCP shape (to_mcp_prompt).One thing to double-check: any code path that dumps the model (or includes FastMCP meta) shouldn’t attempt to JSON-serialize this callable field.
235-265: Good:Prompt.from_function(..., auth=...)correctly forwards toFunctionPrompt.from_function.
404-515: Good:FunctionPrompt.from_function(..., auth=...)threads auth into the constructed prompt instance.
35-36: The import location is correct and intentional—AuthCheckCallableis defined infastmcp.tools.toolspecifically to avoid circular imports withauthorization.py, as documented in the code itself.This is the established cross-component canonical location. Five other files (
server.py,local_provider.py,resource.py,template.py) already import from the same location. Moving it would not improve the design and risks reintroducing the circular import issue.src/fastmcp/resources/resource.py (4)
233-237: Good:Resource.authis modeled explicitly and documented.Please confirm any “meta”/logging/debug dumping paths don’t try to serialize this callable.
238-266: Good:Resource.from_function(..., auth=...)forwards auth toFunctionResource.
427-477: Good:FunctionResource.from_function(..., auth=...)persists auth on the instance.
510-638: Good: standaloneresource(...)decorator accepts and forwardsauthfor both Resource and ResourceTemplate paths.src/fastmcp/resources/template.py (2)
30-31: Good: template-levelauthis added and documented.Also applies to: 118-121
125-153: Good:ResourceTemplate.from_function(..., auth=...)forwards auth.src/fastmcp/server/providers/local_provider.py (4)
44-45: Good: LocalProvider imports/wires auth typing and tool types in one place.
349-543: Good:LocalProvider.tool(..., auth=...)forwards auth toTool.from_functionand preserves it through partial/decorator flows.
544-627: Good:LocalProvider.resource(..., auth=...)delegates to standalone resource decorator with auth preserved.
628-751: Good:LocalProvider.prompt(..., auth=...)delegates to standalone prompt decorator with auth preserved.src/fastmcp/server/auth/authorization.py (2)
46-75: LGTM:AuthContext+ backward-compatible.toolaccessor is a clean bridge.
147-190: No changes needed — BLE001 would not flag this code.Ruff's BLE001 (blind-except) rule explicitly exempts
except Exceptionblocks that log the exception withexc_info=True. This code already follows that exemption pattern withlogger.warning(..., exc_info=True), so the linter would not fail and nonoqacomment is required.Likely an incorrect or invalid review comment.
src/fastmcp/server/server.py (2)
864-893: Solid: list endpoints hide unauthorized components by filtering and swallowingAuthorizationErrorduring listing.Also applies to: 906-927, 963-993, 1006-1027, 1065-1098, 1110-1133, 1169-1199, 1212-1233
895-929: Authorization enforcement uses dual-layer approach: internalget_*methods hide auth failures asNotFoundError, while middleware enforces explicitAuthorizationErrorat execution time.The code implements intentional two-level authorization:
Internal discovery layer (
get_tool,get_resource, etc. in server.py 895-929, 995-1029, 1099-1135, 1201-1235): When a component fails auth checks, the method continues searching other providers rather than raising an exception. This prevents information leakage—an unauthorized tool appears indistinguishable from a non-existent one.Execution enforcement layer (middleware in authorization.py 113-160, 175-210, 261-295): After calling
get_tool/get_resource/etc, the middleware independently re-runs auth checks and explicitly raisesAuthorizationErrorif they fail. This provides denial-of-service clarity at call time.The design choice appears intentional but creates a behavioral asymmetry: calling
get_tooldirectly on an authorized-but-inaccessible tool returnsNotFoundError, while the MCP protocol'scall_toolrequest (which goes through middleware) returnsAuthorizationError. Consider documenting whether this intentional information hiding at the discovery layer is the desired behavior, and whether directget_*calls should match the middleware's explicit denial semantics.src/fastmcp/server/middleware/authorization.py (2)
129-148: Address Ruff TRY003 violations in three locations. The exception messages at lines 129–148, 190–208, and 276–294 contain f-strings with dynamic context. Either shorten the messages to a fixed string and log the context separately, or add# noqa: TRY003with a brief rationale if this pattern is intentional.
96-101: No issue found -_current_transporthas a default value.The
ContextVaris initialized withdefault=None(line 81-83 in context.py), so calling.get()without arguments is safe and will returnNoneif no transport is set.LookupErroronly occurs when aContextVarhas no default AND.get()is called without a default argument. Additionally, using.get("stdio")would be semantically incorrect—it would treat "stdio" as the fallback when transport is unset, changing the intended logic.
- Add exclude=True to auth field on Tool, Resource, ResourceTemplate, Prompt - Fix AccessToken claims conversion to handle None values
- Catch NotFoundError in on_read_resource and fall back to get_resource_template - Fix misleading comment about auth requirements for untagged tools
Test Failure AnalysisSummary: The static analysis job failed because 9 files exceed their configured line limits in Root Cause: This PR adds authorization functionality across tools, resources, prompts, and the server itself. The new code has increased line counts beyond the configured
Suggested Solution: Run uv run loq baseline
git add loq.tomlDetailed AnalysisThe The increases are reasonable given this PR adds a new feature (authorization checks) across all component types (tools, resources, prompts) as required by the MCP architecture pattern mentioned in The Related Files
Note: This comment has been updated to reflect the latest workflow run analysis. |
- Propagate auth from template to ephemeral Resource in create_resource() - Extract _get_auth_context() helper to reduce duplication in server.py
Test Failure AnalysisSummary: The static analysis workflow failed due to file size limit violations. Ten files exceed their configured maximum line counts in the Root Cause: The authorization feature adds new code and configuration to several files, pushing them over their previous line count limits. The Suggested Solution: Update the line count limits in # In .pre-commit-config.yaml, update the loq hook limits:
- id: loq
args:
- docs/docs.json=593 # was 592
- src/fastmcp/tools/tool_transform.py=953 # was 952
- src/fastmcp/resources/template.py=576 # was 565
- src/fastmcp/resources/resource.py=640 # was 627
- src/fastmcp/server/providers/local_provider.py=751 # was 738
- src/fastmcp/prompts/prompt.py=793 # was 779
- src/fastmcp/tools/tool.py=967 # was 950
- docs/changelog.mdx=2280 # was 2228
- tests/server/auth/test_authorization.py=604 # was 500
- src/fastmcp/server/server.py=2795 # was 2682Detailed AnalysisThe
All failures are straightforward: the new code increases file sizes beyond the previously configured limits. This is expected for a feature addition of this scope. Failure Output: Related Files
🤖 Automated analysis by marvin |
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
src/fastmcp/resources/resource.py (1)
751-751: Pipeline failure: File exceeds LOQ size limit (751 > 738).Similar to
tool.py, this file exceeds the maximum allowed size. Consider extractingResourceContentandResourceResultclasses to a separate module in a follow-up.src/fastmcp/resources/template.py (1)
640-640: Pipeline failure: File exceeds LOQ size limit (640 > 627).This file also exceeds the maximum allowed size. The URI matching utilities (
build_regex,match_uri_template,extract_query_params) could potentially be extracted to a separate module.src/fastmcp/server/server.py (1)
2795-2795: Pipeline failure: File exceeds LOQ size limit (2795 > 2682).This file significantly exceeds the maximum allowed size. This is the largest violation among the files in this PR. Consider a follow-up to modularize:
- Extract transport running logic (
run_stdio_async,run_http_async) to a separate module- Extract the deprecated
import_servermethod- Extract MCP handler methods (
_list_tools_mcp,_call_tool_mcp, etc.) to a handlers module
🧹 Nitpick comments (2)
src/fastmcp/server/middleware/authorization.py (1)
96-101: Consider extracting STDIO transport check to a helper.The STDIO transport check and late import pattern is repeated in every method. A small helper could reduce duplication.
♻️ Optional refactor to reduce duplication
+def _is_stdio_transport() -> bool: + """Check if current transport is STDIO (no auth concept).""" + from fastmcp.server.context import _current_transport + return _current_transport.get() == "stdio" + + class AuthMiddleware(Middleware): ... async def on_list_tools( self, context: MiddlewareContext[mt.ListToolsRequest], call_next: CallNext[mt.ListToolsRequest, Sequence[Tool]], ) -> Sequence[Tool]: """Filter tools/list response based on auth checks.""" tools = await call_next(context) - # STDIO has no auth concept, skip filtering - # Late import to avoid circular import with context.py - from fastmcp.server.context import _current_transport - - if _current_transport.get() == "stdio": + if _is_stdio_transport(): return toolssrc/fastmcp/resources/resource.py (1)
36-36: Consider movingAuthCheckCallableto a shared types module.Importing
AuthCheckCallablefromfastmcp.tools.toolcreates a dependency from the resources module to the tools module. A shared location likefastmcp.utilities.typesor a dedicatedfastmcp.server.auth.typeswould be cleaner.
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (11)
docs/development/v3-notes/v3-features.mdxdocs/docs.jsondocs/servers/auth/authorization.mdxdocs/servers/lifespan.mdxsrc/fastmcp/prompts/prompt.pysrc/fastmcp/resources/resource.pysrc/fastmcp/resources/template.pysrc/fastmcp/server/dependencies.pysrc/fastmcp/server/middleware/authorization.pysrc/fastmcp/server/server.pysrc/fastmcp/tools/tool.py
✅ Files skipped from review due to trivial changes (1)
- docs/servers/lifespan.mdx
🚧 Files skipped from review as they are similar to previous changes (2)
- src/fastmcp/prompts/prompt.py
- docs/servers/auth/authorization.mdx
🧰 Additional context used
📓 Path-based instructions (2)
src/**/*.py
📄 CodeRabbit inference engine (AGENTS.md)
src/**/*.py: Python source code must use Python ≥3.10 with full type annotations
Never use bareexcept- be specific with exception types
Prioritize readable, understandable code - clarity over cleverness; avoid obfuscated or confusing patterns
Follow existing patterns and maintain consistency across the codebase
Files:
src/fastmcp/server/dependencies.pysrc/fastmcp/server/middleware/authorization.pysrc/fastmcp/resources/resource.pysrc/fastmcp/tools/tool.pysrc/fastmcp/server/server.pysrc/fastmcp/resources/template.py
docs/**/*.mdx
📄 CodeRabbit inference engine (docs/.cursor/rules/mintlify.mdc)
docs/**/*.mdx: Use clear, direct language appropriate for technical audiences
Write in second person ('you') for instructions and procedures in MDX documentation
Use active voice over passive voice in MDX technical documentation
Employ present tense for current states and future tense for outcomes in MDX documentation
Maintain consistent terminology throughout all MDX documentation
Keep sentences concise while providing necessary context in MDX documentation
Use parallel structure in lists, headings, and procedures in MDX documentation
Lead with the most important information using inverted pyramid structure in MDX documentation
Use progressive disclosure in MDX documentation: present basic concepts before advanced ones
Break complex procedures into numbered steps in MDX documentation
Include prerequisites and context before instructions in MDX documentation
Provide expected outcomes for each major step in MDX documentation
End sections with next steps or related information in MDX documentation
Use descriptive, keyword-rich headings for navigation and SEO in MDX documentation
Focus on user goals and outcomes rather than system features in MDX documentation
Anticipate common questions and address them proactively in MDX documentation
Include troubleshooting for likely failure points in MDX documentation
Provide multiple pathways (beginner vs advanced) but offer an opinionated path to avoid overwhelming users in MDX documentation
Always include complete, runnable code examples that users can copy and execute in MDX documentation
Show proper error handling and edge case management in MDX code examples
Use realistic data instead of placeholder values in MDX code examples
Include expected outputs and results for verification in MDX code examples
Test all code examples thoroughly before publishing in MDX documentation
Specify language and include filename when relevant in MDX code examples
Add explanatory comments for complex logic in MDX code examples
Document all API...
Files:
docs/development/v3-notes/v3-features.mdx
🧠 Learnings (2)
📓 Common learnings
Learnt from: jlowin
Repo: jlowin/fastmcp PR: 0
File: :0-0
Timestamp: 2025-12-01T15:48:05.095Z
Learning: PR #2505 in fastmcp adds NEW functionality to get_access_token(): it now first checks request.scope["user"] for the token (which never existed before), then falls back to _sdk_get_access_token() (the only thing the original code did). This is not a reversal of order but entirely new functionality to fix stale token issues.
📚 Learning: 2025-12-01T15:48:05.095Z
Learnt from: jlowin
Repo: jlowin/fastmcp PR: 0
File: :0-0
Timestamp: 2025-12-01T15:48:05.095Z
Learning: PR #2505 in fastmcp adds NEW functionality to get_access_token(): it now first checks request.scope["user"] for the token (which never existed before), then falls back to _sdk_get_access_token() (the only thing the original code did). This is not a reversal of order but entirely new functionality to fix stale token issues.
Applied to files:
src/fastmcp/server/dependencies.pydocs/development/v3-notes/v3-features.mdxsrc/fastmcp/server/server.py
🧬 Code graph analysis (2)
src/fastmcp/resources/resource.py (3)
src/fastmcp/server/server.py (3)
tool(1848-1863)tool(1866-1881)tool(1883-1968)src/fastmcp/tools/tool.py (4)
tool(793-793)tool(797-809)tool(813-826)tool(829-967)src/fastmcp/server/providers/local_provider.py (3)
tool(350-367)tool(370-387)tool(393-542)
src/fastmcp/resources/template.py (3)
src/fastmcp/server/server.py (3)
tool(1848-1863)tool(1866-1881)tool(1883-1968)src/fastmcp/tools/tool.py (4)
tool(793-793)tool(797-809)tool(813-826)tool(829-967)src/fastmcp/server/providers/local_provider.py (3)
tool(350-367)tool(370-387)tool(393-542)
🪛 GitHub Actions: Run static analysis
docs/docs.json
[error] 593-593: LOQ size limit violation: 593 > 592. File exceeds maximum allowed size.
[error] 576-576: LOQ size limit violation: 576 > 565. File exceeds maximum allowed size.
src/fastmcp/resources/resource.py
[error] 751-751: LOQ size limit violation: 751 > 738. File exceeds maximum allowed size.
src/fastmcp/tools/tool.py
[error] 967-967: LOQ size limit violation: 967 > 950. File exceeds maximum allowed size.
src/fastmcp/server/server.py
[error] 2795-2795: LOQ size limit violation: 2795 > 2682. File exceeds maximum allowed size.
src/fastmcp/resources/template.py
[error] 640-640: LOQ size limit violation: 640 > 627. File exceeds maximum allowed size.
🪛 Ruff (0.14.10)
src/fastmcp/server/middleware/authorization.py
135-137: Avoid specifying long messages outside the exception class
(TRY003)
145-147: Avoid specifying long messages outside the exception class
(TRY003)
195-197: Avoid specifying long messages outside the exception class
(TRY003)
209-211: Avoid specifying long messages outside the exception class
(TRY003)
285-287: Avoid specifying long messages outside the exception class
(TRY003)
295-297: Avoid specifying long messages outside the exception class
(TRY003)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: Run tests: Python 3.13 on ubuntu-latest
- GitHub Check: Run tests: Python 3.10 on ubuntu-latest
- GitHub Check: Run tests: Python 3.10 on windows-latest
- GitHub Check: Run tests with lowest-direct dependencies
🔇 Additional comments (14)
src/fastmcp/server/dependencies.py (1)
418-421: LGTM! Defensive handling for None claims.The
or {}pattern correctly handles both missing and explicitNonevalues forclaims, ensuring downstream authorization code always receives a dict. This is preferable toget("claims", {})since the SDK may returnclaims: Noneexplicitly.docs/development/v3-notes/v3-features.mdx (3)
270-290: LGTM - Clear documentation of component-level authorization.The section header and component-level auth examples are well-structured. The code examples demonstrate the three main component types (tool, resource, prompt) with different auth checks, providing clear usage patterns.
292-305: LGTM - Middleware examples are clear and practical.The server-wide auth examples effectively demonstrate both simple authentication requirements and tag-based restrictions.
307-319: LGTM - Built-in checks and custom check pattern are well documented.The built-in checks list is concise and the custom check example correctly demonstrates accessing
AuthContext. The STDIO bypass note is appropriately included as an important caveat.docs/docs.json (1)
141-141: Navigation entry is correctly placed; no pipeline size limit violations exist.The new "servers/auth/authorization" entry is properly positioned in the Authentication section. The file contains valid JSON with no size limit violations or configuration errors.
Likely an incorrect or invalid review comment.
src/fastmcp/server/middleware/authorization.py (1)
1-299: LGTM overall - Well-structured authorization middleware.The
AuthMiddlewareimplementation is solid:
- Consistent fail-closed security model when context is missing
- STDIO transport bypass is correctly applied across all operations
- Filtering for list operations and enforcement for access operations follow best practices
- AND semantics for multiple auth checks is clearly documented
src/fastmcp/tools/tool.py (2)
54-56: LGTM - Pragmatic approach to avoid circular imports.Using
Callable[[Any], bool]instead of the actualAuthContexttype is a reasonable trade-off to avoid circular import issues with the authorization module. The comment clearly explains the intent.
173-176: LGTM - Auth field correctly excluded from serialization.The
exclude=Trueensures auth callables aren't serialized (which would fail anyway), keeping this as a runtime-only attribute.src/fastmcp/resources/resource.py (1)
233-236: LGTM - Auth parameter correctly threaded through resource creation paths.The auth parameter is consistently propagated through:
Resourceclass field withexclude=TrueResource.from_function→FunctionResource.from_functionresourcedecorator →Resource.from_function/ResourceTemplate.from_functionAlso applies to: 251-251, 265-265, 441-441, 476-476, 522-522, 549-549, 622-622, 637-637
src/fastmcp/resources/template.py (2)
373-382: LGTM - Auth correctly propagated from template to ephemeral resource.This ensures template-level auth checks apply when resources are created from templates. Critical for consistent authorization enforcement.
118-122: LGTM - Auth parameter correctly threaded through template creation paths.The auth parameter is consistently propagated through all template creation and instantiation paths.
Also applies to: 140-140, 154-154, 464-464, 575-575
src/fastmcp/server/server.py (3)
161-175: LGTM - Clean helper for auth context retrieval.The
_get_auth_context()helper nicely encapsulates the STDIO detection and token retrieval pattern used throughout the file.
881-904: LGTM - Component-level auth checks correctly integrated into tool listing.The auth check correctly:
- Skips for STDIO transport
- Only checks when
tool.authis not None- Silently excludes unauthorized tools from results
1862-1862: LGTM - Auth parameter consistently added to all decorator/method signatures.The
authparameter is correctly propagated through all tool, resource, and prompt registration paths.Also applies to: 1880-1880, 1897-1897, 2005-2005, 2070-2070, 2101-2101, 2116-2116, 2130-2130, 2214-2214
| # Try concrete resource first, then template (for template-backed URIs) | ||
| try: | ||
| component = await fastmcp.fastmcp.get_resource(str(uri)) | ||
| except NotFoundError: | ||
| component = await fastmcp.fastmcp.get_resource_template(str(uri)) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's examine the file and see the full context around lines 199-203
cat -n src/fastmcp/server/middleware/authorization.py | sed -n '190,210p'Repository: jlowin/fastmcp
Length of output: 1074
🏁 Script executed:
# Search for the get_resource_template implementation
rg -A 10 'def get_resource_template' --type pyRepository: jlowin/fastmcp
Length of output: 7487
🏁 Script executed:
# Search for get_resource implementation
rg -A 10 'def get_resource' --type py | head -40Repository: jlowin/fastmcp
Length of output: 2595
🏁 Script executed:
# Check how NotFoundError is defined and used
rg 'class NotFoundError|raise NotFoundError' --type py -A 2Repository: jlowin/fastmcp
Length of output: 5234
🏁 Script executed:
# Get the full implementation of get_resource_template in server.py
rg -B 5 -A 20 'async def get_resource_template\(self, uri: str\)' src/fastmcp/server/server.pyRepository: jlowin/fastmcp
Length of output: 1152
🏁 Script executed:
# Also check get_resource implementation for comparison
rg -B 5 -A 20 'async def get_resource\(self, uri: str\)' src/fastmcp/server/server.pyRepository: jlowin/fastmcp
Length of output: 1097
🏁 Script executed:
# Get more context to see the full get_resource_template implementation
sed -n '250,310p' src/fastmcp/server/server.pyRepository: jlowin/fastmcp
Length of output: 2657
🏁 Script executed:
# Get more context to see the full get_resource implementation
sed -n '200,250p' src/fastmcp/server/server.pyRepository: jlowin/fastmcp
Length of output: 2197
🏁 Script executed:
# Find the complete get_resource_template and get_resource implementations with line numbers
rg -n 'async def get_resource' src/fastmcp/server/server.pyRepository: jlowin/fastmcp
Length of output: 328
🏁 Script executed:
# Get the lines around the first get_resource to see its complete implementation
sed -n '415,470p' src/fastmcp/server/server.pyRepository: jlowin/fastmcp
Length of output: 1963
🏁 Script executed:
# Get the lines around get_resource_template to see its complete implementation
sed -n '475,530p' src/fastmcp/server/server.pyRepository: jlowin/fastmcp
Length of output: 2459
🏁 Script executed:
# Get the complete get_resource implementation
sed -n '999,1028p' src/fastmcp/server/server.pyRepository: jlowin/fastmcp
Length of output: 1348
🏁 Script executed:
# Get the complete get_resource_template implementation
sed -n '1095,1130p' src/fastmcp/server/server.pyRepository: jlowin/fastmcp
Length of output: 1585
🏁 Script executed:
# Get the context around the problematic code in authorization.py
sed -n '195,215p' src/fastmcp/server/middleware/authorization.pyRepository: jlowin/fastmcp
Length of output: 826
Error message should clearly indicate the URI was not found in either concrete or template forms.
The current code raises NotFoundError(f"Unknown resource template: {uri}") when both lookups fail, which is misleading. The error message emphasizes the template aspect rather than simply indicating the URI doesn't exist. When neither a concrete resource nor a template matches the URI, callers should receive a clear error message like "Unknown resource: {uri}" instead of one that implies the issue is specifically about templates.
Consider either:
- Catching the second exception and re-raising with a clearer message indicating both concrete and template lookups failed
- Providing a unified error message that doesn't specifically mention templates
| # Check tool-level auth (skip for STDIO) | ||
| if not skip_auth and result.auth is not None: | ||
| ctx = AuthContext(token=token, component=result) | ||
| if not run_auth_checks(result.auth, ctx): | ||
| continue |
There was a problem hiding this comment.
Potential inconsistency: get_tool doesn't catch AuthorizationError unlike list operations.
In get_tools (line 901), AuthorizationError is caught and treated as a denial. However, in get_tool, if run_auth_checks raises AuthorizationError, it will propagate. For security consistency (not revealing resource existence), consider catching and continuing:
🔧 Suggested fix
if isinstance(result, Tool) and self._is_component_enabled(result):
# Check tool-level auth (skip for STDIO)
if not skip_auth and result.auth is not None:
ctx = AuthContext(token=token, component=result)
- if not run_auth_checks(result.auth, ctx):
- continue
+ try:
+ if not run_auth_checks(result.auth, ctx):
+ continue
+ except AuthorizationError:
+ continue
return resultThe same pattern applies to get_resource (lines 1021-1025), get_resource_template (lines 1119-1123), and get_prompt (lines 1211-1215).
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| # Check tool-level auth (skip for STDIO) | |
| if not skip_auth and result.auth is not None: | |
| ctx = AuthContext(token=token, component=result) | |
| if not run_auth_checks(result.auth, ctx): | |
| continue | |
| if isinstance(result, Tool) and self._is_component_enabled(result): | |
| # Check tool-level auth (skip for STDIO) | |
| if not skip_auth and result.auth is not None: | |
| ctx = AuthContext(token=token, component=result) | |
| try: | |
| if not run_auth_checks(result.auth, ctx): | |
| continue | |
| except AuthorizationError: | |
| continue | |
| return result |
| meta=meta, | ||
| task=task, | ||
| auth=auth, | ||
| ) |
There was a problem hiding this comment.
Pipeline failure: File exceeds LOQ size limit (967 > 950).
The static analysis pipeline reports this file exceeds the maximum allowed size. This PR adds ~17 new lines for auth support. Consider addressing the file size in a follow-up by:
- Extracting the decorator logic to a separate module
- Moving helper functions like
_convert_to_contentto a utilities module
🧰 Tools
🪛 GitHub Actions: Run static analysis
[error] 967-967: LOQ size limit violation: 967 > 950. File exceeds maximum allowed size.
- Move authorization.mdx to servers/ and add to Features group - Reorganize authorization doc structure (component-level, server-level, reference) - Add tag: NEW to lifespan and authorization - Add VersionBadge 3.0.0 to context transport section
Test Failure AnalysisSummary: The static analysis workflow failed due to file size limit violations enforced by the Root Cause: This PR adds new authorization functionality which increases the size of several files beyond their Suggested Solution: Run uv run loq baseline
git add loq.toml
git commit -m "Update loq baselines for authorization feature"This will automatically update the baseline rules for all files that currently exceed their limits. Detailed AnalysisThe From the workflow logs at line 2026-01-13T00:48:21.6141620Z:
The Related Files
|
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
docs/docs.json (1)
1-593: Address pipeline failure: file exceeds allowed line count.The static analysis reports that the file has 593 lines, exceeding the allowed maximum of 592 by 1 line. Consider removing unnecessary whitespace or consolidating entries if possible to stay within the limit.
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
docs/docs.jsondocs/servers/authorization.mdxdocs/servers/context.mdxdocs/servers/lifespan.mdx
✅ Files skipped from review due to trivial changes (1)
- docs/servers/authorization.mdx
🚧 Files skipped from review as they are similar to previous changes (1)
- docs/servers/lifespan.mdx
🧰 Additional context used
📓 Path-based instructions (1)
docs/**/*.mdx
📄 CodeRabbit inference engine (docs/.cursor/rules/mintlify.mdc)
docs/**/*.mdx: Use clear, direct language appropriate for technical audiences
Write in second person ('you') for instructions and procedures in MDX documentation
Use active voice over passive voice in MDX technical documentation
Employ present tense for current states and future tense for outcomes in MDX documentation
Maintain consistent terminology throughout all MDX documentation
Keep sentences concise while providing necessary context in MDX documentation
Use parallel structure in lists, headings, and procedures in MDX documentation
Lead with the most important information using inverted pyramid structure in MDX documentation
Use progressive disclosure in MDX documentation: present basic concepts before advanced ones
Break complex procedures into numbered steps in MDX documentation
Include prerequisites and context before instructions in MDX documentation
Provide expected outcomes for each major step in MDX documentation
End sections with next steps or related information in MDX documentation
Use descriptive, keyword-rich headings for navigation and SEO in MDX documentation
Focus on user goals and outcomes rather than system features in MDX documentation
Anticipate common questions and address them proactively in MDX documentation
Include troubleshooting for likely failure points in MDX documentation
Provide multiple pathways (beginner vs advanced) but offer an opinionated path to avoid overwhelming users in MDX documentation
Always include complete, runnable code examples that users can copy and execute in MDX documentation
Show proper error handling and edge case management in MDX code examples
Use realistic data instead of placeholder values in MDX code examples
Include expected outputs and results for verification in MDX code examples
Test all code examples thoroughly before publishing in MDX documentation
Specify language and include filename when relevant in MDX code examples
Add explanatory comments for complex logic in MDX code examples
Document all API...
Files:
docs/servers/context.mdx
🧠 Learnings (1)
📓 Common learnings
Learnt from: jlowin
Repo: jlowin/fastmcp PR: 0
File: :0-0
Timestamp: 2025-12-01T15:48:05.095Z
Learning: PR #2505 in fastmcp adds NEW functionality to get_access_token(): it now first checks request.scope["user"] for the token (which never existed before), then falls back to _sdk_get_access_token() (the only thing the original code did). This is not a reversal of order but entirely new functionality to fix stale token issues.
🪛 GitHub Actions: Run static analysis
docs/servers/context.mdx
[error] 650-650: loq: line 650 exceeds allowed size (650 > 648)
docs/docs.json
[error] 593-593: loq: line 593 exceeds allowed size (593 > 592)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: Run tests: Python 3.13 on ubuntu-latest
- GitHub Check: Run tests with lowest-direct dependencies
- GitHub Check: Run tests: Python 3.10 on windows-latest
- GitHub Check: Run tests: Python 3.10 on ubuntu-latest
🔇 Additional comments (2)
docs/servers/context.mdx (1)
305-306: LGTM!The VersionBadge addition for 3.0.0 follows the established pattern used throughout this documentation file and correctly documents when the Transport feature was introduced.
docs/docs.json (1)
123-134: LGTM!The navigation updates correctly add the new
servers/authorizationpage to the Features group and maintain logical ordering. This aligns with the PR's introduction of the authorization subsystem.
| <VersionBadge version="3.0.0" /> | ||
|
|
There was a problem hiding this comment.
Address pipeline failure: file exceeds allowed line count.
The static analysis reports that the file now has 650 lines, exceeding the allowed maximum of 648. This change adds 2 lines. Consider removing unnecessary blank lines elsewhere in the file or splitting content into separate documentation pages to stay within the limit.
Test Failure AnalysisSummary: The static analysis job failed because 11 files exceeded their line count limits enforced by the Root Cause: PR #2855 added authorization checks throughout the codebase, increasing line counts in multiple core files. The Suggested Solution: Update the loq baselineThis will update Detailed AnalysisThe following files exceeded their limits:
The authorization feature added From the logs: Related FilesCore files affected by authorization additions:
|
Tools, resources, and prompts can now declare authorization requirements using callable checks. When a component has auth configured, it's filtered from list responses for unauthorized clients and enforcement happens at access time.
AuthMiddlewareprovides server-wide enforcement for all component types.restrict_tagis useful there for tag-based policies: