From 6dd1de62e4d369d9fa2066d2a658af73ca5b2802 Mon Sep 17 00:00:00 2001 From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com> Date: Sat, 17 Jan 2026 14:20:10 -0500 Subject: [PATCH] Address PR review feedback - Filter task-eligible components in FastMCPProvider.get_tasks() - Catch AuthorizationError in get_* methods and return None for consistency - Remove AggregateProvider from top-level exports --- src/fastmcp/server/providers/__init__.py | 2 - .../server/providers/fastmcp_provider.py | 13 +++-- src/fastmcp/server/server.py | 56 +++++++++---------- 3 files changed, 37 insertions(+), 34 deletions(-) diff --git a/src/fastmcp/server/providers/__init__.py b/src/fastmcp/server/providers/__init__.py index 8d997e5ff..39ea4d0ff 100644 --- a/src/fastmcp/server/providers/__init__.py +++ b/src/fastmcp/server/providers/__init__.py @@ -27,7 +27,6 @@ Example: from typing import TYPE_CHECKING -from fastmcp.server.providers.aggregate import AggregateProvider from fastmcp.server.providers.base import Provider from fastmcp.server.providers.fastmcp_provider import FastMCPProvider from fastmcp.server.providers.filesystem import FileSystemProvider @@ -38,7 +37,6 @@ if TYPE_CHECKING: from fastmcp.server.providers.proxy import ProxyProvider as ProxyProvider __all__ = [ - "AggregateProvider", "FastMCPProvider", "FileSystemProvider", "LocalProvider", diff --git a/src/fastmcp/server/providers/fastmcp_provider.py b/src/fastmcp/server/providers/fastmcp_provider.py index 4ad388ebd..a435bc0ac 100644 --- a/src/fastmcp/server/providers/fastmcp_provider.py +++ b/src/fastmcp/server/providers/fastmcp_provider.py @@ -642,11 +642,16 @@ class FastMCPProvider(Provider): ) prompts_chain = partial(transform.list_prompts, call_next=prompts_chain) + # Filter to only task-eligible components (same as base Provider) return [ - *await tools_chain(), - *await resources_chain(), - *await templates_chain(), - *await prompts_chain(), + c + for c in [ + *await tools_chain(), + *await resources_chain(), + *await templates_chain(), + *await prompts_chain(), + ] + if c.task_config.supports_tasks() ] # ------------------------------------------------------------------------- diff --git a/src/fastmcp/server/server.py b/src/fastmcp/server/server.py index 7054cd2dd..7917bfdc6 100644 --- a/src/fastmcp/server/server.py +++ b/src/fastmcp/server/server.py @@ -1138,10 +1138,7 @@ class FastMCP(Provider, Generic[LifespanResultT]): version: Version filter (None returns highest version). Returns: - The tool if found and authorized, None if not found. - - Raises: - AuthorizationError: If component-level auth fails. + The tool if found and authorized, None if not found or unauthorized. """ # Aggregate from all sub-providers (each applies their own transforms) @@ -1168,12 +1165,15 @@ class FastMCP(Provider, Generic[LifespanResultT]): tool: Tool = max(valid, key=version_sort_key) # type: ignore[type-var] - # Component auth - raises if unauthorized + # Component auth - return None if unauthorized (consistent with list filtering) skip_auth, token = _get_auth_context() if not skip_auth and tool.auth is not None: ctx = AuthContext(token=token, component=tool) - if not run_auth_checks(tool.auth, ctx): - raise AuthorizationError(f"Unauthorized access to tool: {name!r}") + try: + if not run_auth_checks(tool.auth, ctx): + return None + except AuthorizationError: + return None return tool @@ -1236,10 +1236,7 @@ class FastMCP(Provider, Generic[LifespanResultT]): version: Version filter (None returns highest version). Returns: - The resource if found and authorized, None if not found. - - Raises: - AuthorizationError: If component-level auth fails. + The resource if found and authorized, None if not found or unauthorized. """ # Aggregate from all sub-providers (each applies their own transforms) results = await gather( @@ -1265,12 +1262,15 @@ class FastMCP(Provider, Generic[LifespanResultT]): resource: Resource = max(valid, key=version_sort_key) # type: ignore[type-var] - # Component auth - raises if unauthorized + # Component auth - return None if unauthorized (consistent with list filtering) skip_auth, token = _get_auth_context() if not skip_auth and resource.auth is not None: ctx = AuthContext(token=token, component=resource) - if not run_auth_checks(resource.auth, ctx): - raise AuthorizationError(f"Unauthorized access to resource: {uri!r}") + try: + if not run_auth_checks(resource.auth, ctx): + return None + except AuthorizationError: + return None return resource @@ -1337,10 +1337,7 @@ class FastMCP(Provider, Generic[LifespanResultT]): version: Version filter (None returns highest version). Returns: - The template if found and authorized, None if not found. - - Raises: - AuthorizationError: If component-level auth fails. + The template if found and authorized, None if not found or unauthorized. """ # Aggregate from all sub-providers (each applies their own transforms) results = await gather( @@ -1366,12 +1363,15 @@ class FastMCP(Provider, Generic[LifespanResultT]): template: ResourceTemplate = max(valid, key=version_sort_key) # type: ignore[type-var] - # Component auth - raises if unauthorized + # Component auth - return None if unauthorized (consistent with list filtering) skip_auth, token = _get_auth_context() if not skip_auth and template.auth is not None: ctx = AuthContext(token=token, component=template) - if not run_auth_checks(template.auth, ctx): - raise AuthorizationError(f"Unauthorized access to template: {uri!r}") + try: + if not run_auth_checks(template.auth, ctx): + return None + except AuthorizationError: + return None return template @@ -1434,10 +1434,7 @@ class FastMCP(Provider, Generic[LifespanResultT]): version: Version filter (None returns highest version). Returns: - The prompt if found and authorized, None if not found. - - Raises: - AuthorizationError: If component-level auth fails. + The prompt if found and authorized, None if not found or unauthorized. """ # Aggregate from all sub-providers (each applies their own transforms) results = await gather( @@ -1463,12 +1460,15 @@ class FastMCP(Provider, Generic[LifespanResultT]): prompt: Prompt = max(valid, key=version_sort_key) # type: ignore[type-var] - # Component auth - raises if unauthorized + # Component auth - return None if unauthorized (consistent with list filtering) skip_auth, token = _get_auth_context() if not skip_auth and prompt.auth is not None: ctx = AuthContext(token=token, component=prompt) - if not run_auth_checks(prompt.auth, ctx): - raise AuthorizationError(f"Unauthorized access to prompt: {name!r}") + try: + if not run_auth_checks(prompt.auth, ctx): + return None + except AuthorizationError: + return None return prompt