Skip to content

Add authorization checks to components and servers#2855

Merged
jlowin merged 8 commits intomainfrom
feat/auth-stdio-bypass
Jan 13, 2026
Merged

Add authorization checks to components and servers#2855
jlowin merged 8 commits intomainfrom
feat/auth-stdio-bypass

Conversation

@jlowin
Copy link
Copy Markdown
Member

@jlowin jlowin commented Jan 12, 2026

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.

from fastmcp import FastMCP
from fastmcp.server.auth import require_auth, require_scopes

mcp = FastMCP()

@mcp.tool(auth=require_auth)
def protected_tool(): ...

@mcp.resource("data://secret", auth=require_scopes("read"))
def secret_data(): ...

@mcp.prompt(auth=require_auth)
def admin_prompt(): ...

AuthMiddleware provides server-wide enforcement for all component types. restrict_tag is useful there for tag-based policies:

from fastmcp.server.middleware import AuthMiddleware
from fastmcp.server.auth import restrict_tag

# Components tagged "admin" require "admin" scope
mcp = FastMCP(middleware=[AuthMiddleware(auth=restrict_tag("admin", scopes=["admin"]))])

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.
@marvin-context-protocol marvin-context-protocol Bot added feature Major new functionality. Reserved for 2-4 significant PRs per release. Not for issues. auth Related to authentication (Bearer, JWT, OAuth, WorkOS) for client or server. labels Jan 12, 2026
@marvin-context-protocol
Copy link
Copy Markdown
Contributor

marvin-context-protocol Bot commented Jan 12, 2026

Test Failure Analysis

Summary: Two tests fail with AccessToken type conversion error when listing tools and calling tools via OAuth authentication.

Root Cause: The get_access_token() function in src/fastmcp/server/dependencies.py fails to convert the SDK's AccessToken to FastMCP's AccessToken because it passes claims=None, which violates FastMCP's AccessToken field constraint that expects a dict.

FastMCP's AccessToken (line 43-46 in src/fastmcp/server/auth/auth.py) extends the MCP SDK's AccessToken and adds a claims field with default_factory=dict. When converting from SDK AccessToken to FastMCP AccessToken, the code tries to pass claims=None (line 420 in dependencies.py), but Pydantic validation fails because None is not a valid dict.

Suggested Solution:

Fix src/fastmcp/server/dependencies.py:420 to handle the missing claims field properly:

# Change line 420 from:
claims=access_token_as_dict.get("claims"),

# To:
claims=access_token_as_dict.get("claims", {}),

This ensures that when claims is missing from the SDK AccessToken, we pass an empty dict instead of None, which matches the default_factory behavior.

Detailed Analysis

Failing Tests

  • tests/client/auth/test_oauth_client.py::test_list_tools
  • tests/client/auth/test_oauth_client.py::test_call_tool

Both fail with:

mcp.shared.exceptions.McpError: Expected fastmcp.server.auth.auth.AccessToken, 
got AccessToken. Ensure the SDK is using the correct AccessToken type.

Error Location

src/fastmcp/server/dependencies.py:424 raises the TypeError when the conversion at lines 412-421 fails.

Why It Fails

When an OAuth client connects:

  1. The MCP SDK's bearer auth returns an SDK AccessToken object
  2. get_access_token() retrieves it and attempts conversion (lines 411-421)
  3. The code does access_token_as_dict = access_token.model_dump() which returns:
    {
      "token": "...",
      "client_id": "...",
      "scopes": [...],
      "expires_at": None,
      "resource": None
      # Note: no "claims" field - SDK AccessToken doesn't have it
    }
  4. When constructing FastMCP AccessToken, it passes claims=None
  5. Pydantic validation fails: claims must be a dict, not None

Verification

from 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
  • src/fastmcp/server/dependencies.py:420 - Line that needs the fix
  • src/fastmcp/server/auth/auth.py:43-46 - FastMCP AccessToken class definition with claims field
  • tests/client/auth/test_oauth_client.py:79,87 - Failing tests
  • src/fastmcp/server/middleware/authorization.py:98,136 - Calls get_access_token()
  • src/fastmcp/server/server.py:869,906 - Also calls get_access_token()

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

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 12, 2026

Walkthrough

This PR introduces a comprehensive callable-based authorization system for FastMCP. It adds a new AuthorizationError exception, an authorization module with AuthContext dataclass and built-in checks (require_auth, require_scopes, restrict_tag), an AuthMiddleware class for server-wide enforcement, and extends tools, resources, and prompts with per-component auth parameter support. The authorization utilities are exported from the auth and middleware packages, integrated into the server and provider implementations, and documented with usage examples.

Possibly related PRs

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning The description provides clear examples of the new authorization feature but lacks required checklist completions (all checkboxes are unchecked) and does not include self-review or review-ready confirmations. Complete all checkboxes in both Contributors and Review checklists to confirm issue tracking, testing, documentation, self-review, and review readiness before merging.
Docstring Coverage ⚠️ Warning Docstring coverage is 70.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding authorization checks to components and servers, which is the primary focus of the changeset.

✏️ 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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

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. Since run_auth_checks() explicitly re-raises AuthorizationError (allowing auth checks to signal explicit denial with a custom message), any AuthorizationError raised during the loop will propagate up and fail the entire get_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 False are correctly skipped via continue, but auth checks that raise AuthorizationError will break the entire request.

Wrap the run_auth_checks() call in a try-except that catches AuthorizationError and treats it like a False return (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 the auth parameter.

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 supporting Sequence for broader input flexibility.

The current implementation only handles list explicitly. While the type annotation specifies list, 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

📥 Commits

Reviewing files that changed from the base of the PR and between ebf6e3d and a17a414.

⛔ Files ignored due to path filters (2)
  • tests/server/auth/test_authorization.py is excluded by none and included by none
  • uv.lock is excluded by !**/*.lock and included by none
📒 Files selected for processing (10)
  • docs/servers/auth/authorization.mdx
  • src/fastmcp/exceptions.py
  • src/fastmcp/server/auth/__init__.py
  • src/fastmcp/server/auth/authorization.py
  • src/fastmcp/server/middleware/__init__.py
  • src/fastmcp/server/middleware/authorization.py
  • src/fastmcp/server/providers/local_provider.py
  • src/fastmcp/server/server.py
  • src/fastmcp/tools/tool.py
  • src/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 bare except - 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__.py
  • src/fastmcp/server/middleware/authorization.py
  • src/fastmcp/exceptions.py
  • src/fastmcp/server/providers/local_provider.py
  • src/fastmcp/tools/tool.py
  • src/fastmcp/server/server.py
  • src/fastmcp/server/auth/__init__.py
  • src/fastmcp/server/auth/authorization.py
  • src/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__.py
  • src/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.mdx
  • src/fastmcp/server/server.py
  • src/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 auth attribute is properly inherited from the parent tool. This ensures transformed tools maintain the same authorization requirements as their parent.

Consider whether auth should be an optional parameter in from_tool() to allow overriding the parent's auth (similar to annotations, tags, etc.), but the current inheritance-only behavior is reasonable for this initial implementation.

src/fastmcp/exceptions.py (1)

40-43: LGTM!

The new AuthorizationError class follows the established exception hierarchy pattern, properly inheriting from FastMCPError with a clear docstring.

src/fastmcp/server/middleware/__init__.py (1)

1-15: LGTM!

The AuthMiddleware is 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_tools implementation correctly:

  1. Calls downstream first to get all tools
  2. Bypasses filtering for STDIO transport
  3. Evaluates auth checks for each tool individually
  4. Returns only authorized tools

108-144: Solid fail-closed enforcement with appropriate error handling.

The on_call_tool implementation:

  1. Correctly bypasses auth for STDIO transport
  2. Fails closed when context is missing (good security posture)
  3. Retrieves the tool and enforces middleware-level auth checks

Note: get_tool at line 134 may also run tool-level auth checks (per server.py lines 889-922), but this is intentional—tool-level auth and middleware-level auth are 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 AuthCheckCallable for type annotations.


349-409: Auth parameter correctly added to all overloads and implementation.

The auth parameter 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] | None matches the definition in tool.py.


489-508: LGTM! Auth correctly propagated in both decoration paths.

  • Line 502: auth=auth passed to Tool.from_function for immediate registration
  • Line 541: auth=auth included in partial for deferred decoration

Both 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.

AuthContext provides appropriate context for auth checks, and the AuthCheck type alias clearly defines the callable contract.


64-103: LGTM: Auth check implementations are correct.

Both require_auth and require_scopes correctly 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_tag function correctly allows access when the tag is absent and enforces scope requirements only when the tag is present on the tool.


165-175: The broad Exception catch is appropriate here for security.

Ruff flags BLE001 on line 168, but this is an intentional security pattern. Masking unexpected exceptions prevents information leakage about internal implementation details. The AuthorizationError is 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 uses Any to avoid circular imports.

The comment clearly explains why Any is used instead of the actual AuthContext type. 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 in Tool.from_function.

The parameter is passed through to FunctionTool.from_function maintaining consistency.


451-515: LGTM: Auth propagation complete in FunctionTool.from_function.

The auth parameter is correctly passed to the cls() constructor, completing the propagation chain.


796-841: LGTM: Standalone @tool decorator overloads include auth parameter.

All three overload signatures consistently include the auth parameter 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 partial call 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 in get_tool mirrors get_tools correctly.

The STDIO bypass and auth check logic is consistent with get_tools. When auth fails, the loop continues to the next provider, ultimately raising NotFoundError if no authorized tool is found—this prevents leaking that the tool exists but is unauthorized.


1771-1826: LGTM: @server.tool decorator overloads include auth parameter.

Both overloads consistently include the auth: AuthCheckCallable | list[AuthCheckCallable] | None parameter.


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.

Comment thread docs/servers/authorization.mdx
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 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".

Comment thread src/fastmcp/server/server.py Outdated
Comment on lines +882 to +885
# 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):
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

@jlowin jlowin changed the title Add callable-based tool authorization with STDIO bypass Add callable-based tool authorization Jan 12, 2026
@jlowin jlowin changed the title Add callable-based tool authorization Add authorization checks to components and servers Jan 12, 2026
- 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)
@marvin-context-protocol
Copy link
Copy Markdown
Contributor

Test Failure Analysis

Summary: Static analysis failed because 9 files now exceed their configured line limits in loq.toml after adding authorization checks.

Root Cause: The PR adds authorization functionality to tools, resources, prompts, and the server, increasing file sizes. The loq pre-commit hook enforces file size limits to prevent files from growing too large. Several files now exceed their configured maximums.

Suggested Solution: Update loq.toml with the new line counts by running:

uv run loq baseline

This will automatically update all the line limits in loq.toml to match the current file sizes. Then commit the updated loq.toml file.

Detailed Analysis

The loq hook reported these violations:

✖    953 > 952    src/fastmcp/tools/tool_transform.py
✖    573 > 565    src/fastmcp/resources/template.py
✖    640 > 627    src/fastmcp/resources/resource.py
✖    751 > 738    src/fastmcp/server/providers/local_provider.py
✖    793 > 779    src/fastmcp/prompts/prompt.py
✖    967 > 950    src/fastmcp/tools/tool.py
✖  2_280 > 2_228  docs/changelog.mdx
✖    604 > 500    tests/server/auth/test_authorization.py (new file)
✖  2_811 > 2_682  src/fastmcp/server/server.py

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 loq baseline command will update the baseline limits to the current sizes.

Related Files
  • loq.toml - File size configuration that needs updating
  • .pre-commit-config.yaml - Configures the loq hook that enforces these limits

@marvin-context-protocol
Copy link
Copy Markdown
Contributor

Test Failure Analysis

Summary: 5 tests failed - 4 OAuth client tests and 1 logging middleware test.

Root Cause:

  1. OAuth Tests (4 failures): The dependency resolver is converting SDK objects to FastMCP's subclass, but the conversion is failing with a type mismatch error. The error message "Expected fastmcp.server.auth.auth.AccessToken, got AccessToken" suggests the type check at line 404 in dependencies.py is not correctly identifying SDK AccessToken instances.

  2. Logging Test (1 failure): The ResourceTemplate now includes an auth field (line 118-120 in template.py), which is being serialized in the logging middleware output. The test snapshot expects the old serialization without the auth field.

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_token

This check should pass for SDK AccessToken objects (which FastMCP's AccessToken inherits from), but it seems to be failing. The issue might be that:

  • The SDK's _SDKAccessToken import or the inheritance chain is broken
  • There's a module reload/import issue causing type identity problems

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 auth field:

# 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 ,\auth\:null at the end before the closing brace.

Detailed Analysis

OAuth Test Failures

All 4 OAuth tests are failing with the same error pattern:

Expected fastmcp.server.auth.auth.AccessToken, got AccessToken

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:

  • test_list_tools (test_oauth_client.py:79)
  • test_call_tool (test_oauth_client.py:87)
  • test_list_resources (test_oauth_client.py:96)
  • test_read_resource (test_oauth_client.py:104)

Logging Test Failure

The test_on_message_with_resource_template_in_payload test is comparing serialized JSON output and expects specific fields. The new auth field was added to ResourceTemplate in this PR, which changed the serialization output.

Related Files

Primary files requiring changes:

  • src/fastmcp/server/dependencies.py:404-426 - Type check and conversion logic for AccessToken
  • tests/server/middleware/test_logging.py:335 - Test snapshot expecting serialized ResourceTemplate

Related to the issue:

  • src/fastmcp/server/auth/auth.py:43-46 - FastMCP's AccessToken class definition
  • src/fastmcp/resources/template.py:118-120 - New auth field on ResourceTemplate
  • src/fastmcp/prompts/prompt.py:204-206 - New auth field on Prompt (similar change)
  • src/fastmcp/tools/tool.py - Likely has similar auth field addition

@marvin-context-protocol
Copy link
Copy Markdown
Contributor

Test Failure Analysis

Summary: 5 tests failed - 4 OAuth client tests and 1 logging middleware test.

Root Cause:

  1. OAuth Tests (4 failures): The get_access_token() dependency resolver is converting SDK AccessToken objects to FastMCP's AccessToken subclass, but the conversion is failing with a type mismatch error. The error message "Expected fastmcp.server.auth.auth.AccessToken, got AccessToken" suggests the type check at line 404 in dependencies.py is not correctly identifying SDK AccessToken instances.

  2. Logging Test (1 failure): The ResourceTemplate now includes an auth field (line 118-120 in template.py), which is being serialized in the logging middleware output. The test snapshot expects the old serialization without the auth field.

Suggested Solution:

Fix 1: OAuth Type Check Issue

File: 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 Snapshot

File: 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 Analysis

OAuth Test Failures

All 4 OAuth tests are failing with the same error pattern:

Expected fastmcp.server.auth.auth.AccessToken, got AccessToken

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:

  • test_list_tools (test_oauth_client.py:79)
  • test_call_tool (test_oauth_client.py:87)
  • test_list_resources (test_oauth_client.py:96)
  • test_read_resource (test_oauth_client.py:104)

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 Failure

The 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 Files

Primary files requiring changes:

  • src/fastmcp/server/dependencies.py:404-426 - Type check and conversion logic for AccessToken
  • tests/server/middleware/test_logging.py:335 - Test snapshot expecting serialized ResourceTemplate

Related to the issue:

  • src/fastmcp/server/auth/auth.py:43-46 - FastMCP's AccessToken class definition
  • src/fastmcp/resources/template.py:118-120 - New auth field on ResourceTemplate
  • src/fastmcp/prompts/prompt.py:204-206 - New auth field on Prompt (similar change)
  • src/fastmcp/tools/tool.py - Likely has similar auth field addition

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

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 in create_resource().

The template's auth field is currently not passed to Resource.from_function() when creating the ephemeral resource. Since Resource.from_function() accepts the auth parameter, 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: standalone prompt(...) decorator overloads + implementation consistently accept/propagate auth.

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 from fastmcp.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 by token = None if is_stdio else get_access_token() is duplicated across 8 methods in server.py and multiple methods in middleware/authorization.py. Extract this into a small internal helper to reduce duplication and make future changes easier to propagate.

Note: The ContextVar _current_transport is defined with default=None, so calling .get() is safe and will not raise LookupError.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a17a414 and 030e719.

⛔ Files ignored due to path filters (1)
  • tests/server/auth/test_authorization.py is excluded by none and included by none
📒 Files selected for processing (7)
  • src/fastmcp/prompts/prompt.py
  • src/fastmcp/resources/resource.py
  • src/fastmcp/resources/template.py
  • src/fastmcp/server/auth/authorization.py
  • src/fastmcp/server/middleware/authorization.py
  • src/fastmcp/server/providers/local_provider.py
  • src/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 bare except - 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.py
  • src/fastmcp/server/server.py
  • src/fastmcp/server/providers/local_provider.py
  • src/fastmcp/server/auth/authorization.py
  • src/fastmcp/resources/resource.py
  • src/fastmcp/prompts/prompt.py
  • src/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.py
  • src/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.auth is 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 to FunctionPrompt.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—AuthCheckCallable is defined in fastmcp.tools.tool specifically to avoid circular imports with authorization.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.auth is 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 to FunctionResource.


427-477: Good: FunctionResource.from_function(..., auth=...) persists auth on the instance.


510-638: Good: standalone resource(...) decorator accepts and forwards auth for both Resource and ResourceTemplate paths.

src/fastmcp/resources/template.py (2)

30-31: Good: template-level auth is 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 to Tool.from_function and 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 .tool accessor is a clean bridge.


147-190: No changes needed — BLE001 would not flag this code.

Ruff's BLE001 (blind-except) rule explicitly exempts except Exception blocks that log the exception with exc_info=True. This code already follows that exemption pattern with logger.warning(..., exc_info=True), so the linter would not fail and no noqa comment 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 swallowing AuthorizationError during 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: internal get_* methods hide auth failures as NotFoundError, while middleware enforces explicit AuthorizationError at execution time.

The code implements intentional two-level authorization:

  1. 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.

  2. 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 raises AuthorizationError if they fail. This provides denial-of-service clarity at call time.

The design choice appears intentional but creates a behavioral asymmetry: calling get_tool directly on an authorized-but-inaccessible tool returns NotFoundError, while the MCP protocol's call_tool request (which goes through middleware) returns AuthorizationError. Consider documenting whether this intentional information hiding at the discovery layer is the desired behavior, and whether direct get_* 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: TRY003 with a brief rationale if this pattern is intentional.


96-101: No issue found - _current_transport has a default value.

The ContextVar is initialized with default=None (line 81-83 in context.py), so calling .get() without arguments is safe and will return None if no transport is set. LookupError only occurs when a ContextVar has 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.

Comment thread src/fastmcp/server/middleware/authorization.py
- 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
@marvin-context-protocol
Copy link
Copy Markdown
Contributor

marvin-context-protocol Bot commented Jan 12, 2026

Test Failure Analysis

Summary: The static analysis job failed because 9 files exceed their configured line limits in loq.toml.

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 loq (line quota) limits:

  • src/fastmcp/tools/tool_transform.py: 953 lines (limit: 952) +1 line
  • src/fastmcp/resources/template.py: 576 lines (limit: 565) +11 lines
  • src/fastmcp/resources/resource.py: 640 lines (limit: 627) +13 lines
  • src/fastmcp/server/providers/local_provider.py: 751 lines (limit: 738) +13 lines
  • src/fastmcp/prompts/prompt.py: 793 lines (limit: 779) +14 lines
  • src/fastmcp/tools/tool.py: 967 lines (limit: 950) +17 lines
  • docs/changelog.mdx: 2,280 lines (limit: 2,228) +52 lines
  • tests/server/auth/test_authorization.py: 604 lines (limit: 500) +104 lines
  • src/fastmcp/server/server.py: 2,795 lines (limit: 2,682) +113 lines

Suggested Solution: Run uv run loq baseline to update the baseline limits in loq.toml for these files, then commit the updated configuration.

uv run loq baseline
git add loq.toml
Detailed Analysis

The loq pre-commit hook enforces file size limits to encourage maintainable file sizes. The violations are:

✖    953 > 952    src/fastmcp/tools/tool_transform.py
✖    576 > 565    src/fastmcp/resources/template.py
✖    640 > 627    src/fastmcp/resources/resource.py
✖    751 > 738    src/fastmcp/server/providers/local_provider.py
✖    793 > 779    src/fastmcp/prompts/prompt.py
✖    967 > 950    src/fastmcp/tools/tool.py
✖  2_280 > 2_228  docs/changelog.mdx
✖    604 > 500    tests/server/auth/test_authorization.py
✖  2_795 > 2_682  src/fastmcp/server/server.py

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 CLAUDE.md: "When modifying MCP functionality, changes typically need to be applied across all object types."

The loq baseline command exists specifically for updating limits after legitimate feature additions.

Related Files
  • loq.toml: Configuration file defining line limits - needs baseline update
  • .pre-commit-config.yaml: Pre-commit hook configuration that runs loq checks
  • All 9 files listed above that exceed their limits

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
@marvin-context-protocol
Copy link
Copy Markdown
Contributor

Test Failure Analysis

Summary: The static analysis workflow failed due to file size limit violations. Ten files exceed their configured maximum line counts in the loq pre-commit hook.

Root Cause: The authorization feature adds new code and configuration to several files, pushing them over their previous line count limits. The loq hook enforces strict line count limits to prevent files from growing too large.

Suggested Solution: Update the line count limits in .pre-commit-config.yaml for the affected files. The hook output shows the current vs. allowed sizes:

# 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 2682
Detailed Analysis

The loq hook enforces file size limits to maintain code quality by preventing files from growing unbounded. The authorization feature legitimately adds functionality to:

  • Core component types (tools, resources, prompts, resource templates)
  • The main server implementation
  • The local provider
  • Documentation (docs.json and changelog)
  • New test file for authorization

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:

loq (file size limits)..................................................[41mFailed[49m
  ✖    593 > 592    docs/docs.json
  ✖    953 > 952    src/fastmcp/tools/tool_transform.py
  ✖    576 > 565    src/fastmcp/resources/template.py
  ✖    640 > 627    src/fastmcp/resources/resource.py
  ✖    751 > 738    src/fastmcp/server/providers/local_provider.py
  ✖    793 > 779    src/fastmcp/prompts/prompt.py
  ✖    967 > 950    src/fastmcp/tools/tool.py
  ✖  2_280 > 2_228  docs/changelog.mdx
  ✖    604 > 500    tests/server/auth/test_authorization.py
  ✖  2_795 > 2_682  src/fastmcp/server/server.py
  10 violations (19ms)
Related Files
  • .pre-commit-config.yaml - Contains the loq hook configuration that needs updating
  • All 10 files listed above - Legitimate additions from the authorization feature

🤖 Automated analysis by marvin

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

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 extracting ResourceContent and ResourceResult classes 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:

  1. Extract transport running logic (run_stdio_async, run_http_async) to a separate module
  2. Extract the deprecated import_server method
  3. 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 tools
src/fastmcp/resources/resource.py (1)

36-36: Consider moving AuthCheckCallable to a shared types module.

Importing AuthCheckCallable from fastmcp.tools.tool creates a dependency from the resources module to the tools module. A shared location like fastmcp.utilities.types or a dedicated fastmcp.server.auth.types would be cleaner.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 030e719 and 5dc1bd3.

📒 Files selected for processing (11)
  • docs/development/v3-notes/v3-features.mdx
  • docs/docs.json
  • docs/servers/auth/authorization.mdx
  • docs/servers/lifespan.mdx
  • src/fastmcp/prompts/prompt.py
  • src/fastmcp/resources/resource.py
  • src/fastmcp/resources/template.py
  • src/fastmcp/server/dependencies.py
  • src/fastmcp/server/middleware/authorization.py
  • src/fastmcp/server/server.py
  • src/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 bare except - 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.py
  • src/fastmcp/server/middleware/authorization.py
  • src/fastmcp/resources/resource.py
  • src/fastmcp/tools/tool.py
  • src/fastmcp/server/server.py
  • src/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.py
  • docs/development/v3-notes/v3-features.mdx
  • src/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 explicit None values for claims, ensuring downstream authorization code always receives a dict. This is preferable to get("claims", {}) since the SDK may return claims: None explicitly.

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 AuthMiddleware implementation 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 actual AuthContext type 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=True ensures 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:

  • Resource class field with exclude=True
  • Resource.from_functionFunctionResource.from_function
  • resource decorator → Resource.from_function / ResourceTemplate.from_function

Also 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.auth is not None
  • Silently excludes unauthorized tools from results

1862-1862: LGTM - Auth parameter consistently added to all decorator/method signatures.

The auth parameter 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

Comment on lines +199 to +203
# 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))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 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 py

Repository: jlowin/fastmcp

Length of output: 7487


🏁 Script executed:

# Search for get_resource implementation
rg -A 10 'def get_resource' --type py | head -40

Repository: jlowin/fastmcp

Length of output: 2595


🏁 Script executed:

# Check how NotFoundError is defined and used
rg 'class NotFoundError|raise NotFoundError' --type py -A 2

Repository: 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.py

Repository: 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.py

Repository: 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.py

Repository: 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.py

Repository: 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.py

Repository: 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.py

Repository: 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.py

Repository: jlowin/fastmcp

Length of output: 2459


🏁 Script executed:

# Get the complete get_resource implementation
sed -n '999,1028p' src/fastmcp/server/server.py

Repository: jlowin/fastmcp

Length of output: 1348


🏁 Script executed:

# Get the complete get_resource_template implementation
sed -n '1095,1130p' src/fastmcp/server/server.py

Repository: 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.py

Repository: 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

Comment on lines +929 to +933
# 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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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 result

The 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.

Suggested change
# 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

Comment thread src/fastmcp/tools/tool.py
meta=meta,
task=task,
auth=auth,
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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:

  1. Extracting the decorator logic to a separate module
  2. Moving helper functions like _convert_to_content to 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
@marvin-context-protocol
Copy link
Copy Markdown
Contributor

Test Failure Analysis

Summary: The static analysis workflow failed due to file size limit violations enforced by the loq hook. 11 files now exceed their configured line count limits.

Root Cause: This PR adds new authorization functionality which increases the size of several files beyond their loq baseline limits defined in loq.toml. The loq tool enforces file size constraints to keep files manageable (default max is 500 lines, with specific baselines for files that exceed this).

Suggested Solution: Run uv run loq baseline to update the baseline limits for these files in loq.toml, then commit the updated configuration:

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 Analysis

The loq pre-commit hook failed with 11 violations:

✖    593 > 592    docs/docs.json
✖    953 > 952    src/fastmcp/tools/tool_transform.py
✖    650 > 648    docs/servers/context.mdx
✖    576 > 565    src/fastmcp/resources/template.py
✖    640 > 627    src/fastmcp/resources/resource.py
✖    751 > 738    src/fastmcp/server/providers/local_provider.py
✖    793 > 779    src/fastmcp/prompts/prompt.py
✖    967 > 950    src/fastmcp/tools/tool.py
✖  2,280 > 2,228  docs/changelog.mdx
✖    604 > 500    tests/server/auth/test_authorization.py
✖  2,795 > 2,682  src/fastmcp/server/server.py

From the workflow logs at line 2026-01-13T00:48:21.6141620Z:

  • Hook ID: loq
  • Exit code: 1
  • Step: "Run prek" in static_analysis job

The loq tool is configured in .pre-commit-config.yaml (lines 36-40) and uses loq.toml for baseline configuration. The tool helps maintain code quality by flagging files that grow too large, encouraging refactoring when appropriate.

Related Files
  • .pre-commit-config.yaml:36-40 - Defines the loq pre-commit hook
  • loq.toml - Configuration file with baseline line limits for each file
  • All 11 files listed in the violations need their baselines updated

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

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

📥 Commits

Reviewing files that changed from the base of the PR and between 5dc1bd3 and f096301.

📒 Files selected for processing (4)
  • docs/docs.json
  • docs/servers/authorization.mdx
  • docs/servers/context.mdx
  • docs/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/authorization page to the Features group and maintain logical ordering. This aligns with the PR's introduction of the authorization subsystem.

Comment thread docs/servers/context.mdx
Comment on lines +305 to +306
<VersionBadge version="3.0.0" />

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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.

@jlowin jlowin merged commit 766641a into main Jan 13, 2026
12 of 13 checks passed
@jlowin jlowin deleted the feat/auth-stdio-bypass branch January 13, 2026 00:56
@marvin-context-protocol
Copy link
Copy Markdown
Contributor

Test Failure Analysis

Summary: The static analysis job failed because 11 files exceeded their line count limits enforced by the loq hook.

Root Cause: PR #2855 added authorization checks throughout the codebase, increasing line counts in multiple core files. The loq pre-commit hook enforces maximum line counts per file, and these additions pushed 11 files over their configured limits.

Suggested Solution: Update the loq.toml baseline to reflect the new line counts. Run:

loq baseline

This will update loq.toml with the current line counts for all files that have grown beyond their limits.

Detailed Analysis

The following files exceeded their limits:

File Current Limit Over
src/fastmcp/server/server.py 2,795 2,682 113
docs/changelog.mdx 2,280 2,228 52
src/fastmcp/tools/tool.py 967 950 17
src/fastmcp/tools/tool_transform.py 953 952 1
src/fastmcp/prompts/prompt.py 793 779 14
src/fastmcp/server/providers/local_provider.py 751 738 13
docs/servers/context.mdx 650 648 2
src/fastmcp/resources/resource.py 640 627 13
tests/server/auth/test_authorization.py 604 500 104
docs/docs.json 593 592 1
src/fastmcp/resources/template.py 576 565 11

The authorization feature added auth parameter checks throughout the core MCP component system (tools, resources, prompts), which explains why so many files grew by small amounts.

From the logs:

loq (file size limits)..................................................Failed
- hook id: loq
- exit code: 1

  ✖    593 > 592    docs/docs.json
  ✖    953 > 952    src/fastmcp/tools/tool_transform.py
  ✖    650 > 648    docs/servers/context.mdx
  ✖    576 > 565    src/fastmcp/resources/template.py
  ✖    640 > 627    src/fastmcp/resources/resource.py
  ✖    751 > 738    src/fastmcp/server/providers/local_provider.py
  ✖    793 > 779    src/fastmcp/prompts/prompt.py
  ✖    967 > 950    src/fastmcp/tools/tool.py
  ✖  2_280 > 2_228  docs/changelog.mdx
  ✖    604 > 500    tests/server/auth/test_authorization.py
  ✖  2_795 > 2_682  src/fastmcp/server/server.py
  11 violations (16ms)
Related Files

Core files affected by authorization additions:

  • src/fastmcp/server/server.py - Added auth parameter to tool/resource/prompt decorators and auth check logic
  • src/fastmcp/tools/tool.py - Added auth parameter support
  • src/fastmcp/resources/resource.py - Added auth parameter support
  • src/fastmcp/prompts/prompt.py - Added auth parameter support
  • tests/server/auth/test_authorization.py - New test file for authorization functionality

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

auth Related to authentication (Bearer, JWT, OAuth, WorkOS) for client or server. feature Major new functionality. Reserved for 2-4 significant PRs per release. Not for issues.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant