diff --git a/AGENTS.md b/AGENTS.md index 34a9e57db6..1d6ff4e9b2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -6,22 +6,20 @@ FastMCP is a comprehensive Python framework (Python ≥3.10) for building Model ## Required Development Workflow -**CRITICAL**: Always run these commands in sequence before committing: +**CRITICAL**: Always run these commands in sequence before committing. ```bash uv sync # Install dependencies -uv run prek run --all-files # Ruff + Prettier + ty uv run pytest -n auto # Run full test suite ``` -**All three must pass** - this is enforced by CI. Alternative: `just build && just typecheck && just test` +In addition, you must pass static checks. This is generally done as a pre-commit hook with `prek` but you can run it manually with: -**Tests must pass and lint/typing must be clean before committing.** +```bash +uv run prek run --all-files # Ruff + Prettier + ty +``` -**Before creating a PR**, evaluate whether documentation needs updating: -- New features or APIs require corresponding docs -- Changed behavior should be reflected in existing docs -- Check `docs/` for affected pages +**Tests must pass and lint/typing must be clean before committing.** ## Repository Structure diff --git a/src/fastmcp/server/middleware/authorization.py b/src/fastmcp/server/middleware/authorization.py index 7b377b4786..46038f6c93 100644 --- a/src/fastmcp/server/middleware/authorization.py +++ b/src/fastmcp/server/middleware/authorization.py @@ -137,7 +137,7 @@ async def on_call_tool( ) # Get tool (component auth is checked in get_tool, raises if unauthorized) - tool = await fastmcp.fastmcp._get_tool(tool_name) + tool = await fastmcp.fastmcp.get_tool(tool_name) if tool is None: raise AuthorizationError( f"Authorization failed for tool '{tool_name}': tool not found" @@ -202,9 +202,9 @@ async def on_read_resource( ) # Get resource/template (component auth is checked in get_*, raises if unauthorized) - component = await fastmcp.fastmcp._get_resource(str(uri)) + component = await fastmcp.fastmcp.get_resource(str(uri)) if component is None: - component = await fastmcp.fastmcp._get_resource_template(str(uri)) + component = await fastmcp.fastmcp.get_resource_template(str(uri)) if component is None: raise AuthorizationError( f"Authorization failed for resource '{uri}': resource not found" @@ -295,7 +295,7 @@ async def on_get_prompt( ) # Get prompt (component auth is checked in get_prompt, raises if unauthorized) - prompt = await fastmcp.fastmcp._get_prompt(prompt_name) + prompt = await fastmcp.fastmcp.get_prompt(prompt_name) if prompt is None: raise AuthorizationError( f"Authorization failed for prompt '{prompt_name}': prompt not found" diff --git a/src/fastmcp/server/providers/__init__.py b/src/fastmcp/server/providers/__init__.py index 39ea4d0ff9..565266c1fe 100644 --- a/src/fastmcp/server/providers/__init__.py +++ b/src/fastmcp/server/providers/__init__.py @@ -13,11 +13,11 @@ class DatabaseProvider(Provider): def __init__(self, db_url: str): self.db = Database(db_url) - async def list_tools(self) -> list[Tool]: + async def _list_tools(self) -> list[Tool]: rows = await self.db.fetch("SELECT * FROM tools") return [self._make_tool(row) for row in rows] - async def get_tool(self, name: str) -> Tool | None: + async def _get_tool(self, name: str) -> Tool | None: row = await self.db.fetchone("SELECT * FROM tools WHERE name = ?", name) return self._make_tool(row) if row else None diff --git a/src/fastmcp/server/providers/aggregate.py b/src/fastmcp/server/providers/aggregate.py index 2f4548043d..b0a7bcf779 100644 --- a/src/fastmcp/server/providers/aggregate.py +++ b/src/fastmcp/server/providers/aggregate.py @@ -123,15 +123,15 @@ def __repr__(self) -> str: # Tools # ------------------------------------------------------------------------- - async def list_tools(self) -> Sequence[Tool]: + async def _list_tools(self) -> Sequence[Tool]: """List all tools from all providers (with transforms applied).""" results = await gather( - *[p._list_tools() for p in self._providers], + *[p.list_tools() for p in self._providers], return_exceptions=True, ) return self._collect_list_results(results, "list_tools") - async def get_tool( + async def _get_tool( self, name: str, version: VersionSpec | None = None ) -> Tool | None: """Get tool by name. @@ -142,7 +142,7 @@ async def get_tool( If specified, returns highest version matching the spec from any provider. """ results = await gather( - *[p._get_tool(name, version) for p in self._providers], + *[p.get_tool(name, version) for p in self._providers], return_exceptions=True, ) return self._get_highest_version_result(results, f"get_tool({name!r})") # type: ignore[return-value] @@ -151,15 +151,15 @@ async def get_tool( # Resources # ------------------------------------------------------------------------- - async def list_resources(self) -> Sequence[Resource]: + async def _list_resources(self) -> Sequence[Resource]: """List all resources from all providers (with transforms applied).""" results = await gather( - *[p._list_resources() for p in self._providers], + *[p.list_resources() for p in self._providers], return_exceptions=True, ) return self._collect_list_results(results, "list_resources") - async def get_resource( + async def _get_resource( self, uri: str, version: VersionSpec | None = None ) -> Resource | None: """Get resource by URI. @@ -170,7 +170,7 @@ async def get_resource( If specified, returns highest version matching the spec from any provider. """ results = await gather( - *[p._get_resource(uri, version) for p in self._providers], + *[p.get_resource(uri, version) for p in self._providers], return_exceptions=True, ) return self._get_highest_version_result(results, f"get_resource({uri!r})") # type: ignore[return-value] @@ -179,15 +179,15 @@ async def get_resource( # Resource Templates # ------------------------------------------------------------------------- - async def list_resource_templates(self) -> Sequence[ResourceTemplate]: + async def _list_resource_templates(self) -> Sequence[ResourceTemplate]: """List all resource templates from all providers (with transforms applied).""" results = await gather( - *[p._list_resource_templates() for p in self._providers], + *[p.list_resource_templates() for p in self._providers], return_exceptions=True, ) return self._collect_list_results(results, "list_resource_templates") - async def get_resource_template( + async def _get_resource_template( self, uri: str, version: VersionSpec | None = None ) -> ResourceTemplate | None: """Get resource template by URI. @@ -198,7 +198,7 @@ async def get_resource_template( If specified, returns highest version matching the spec from any provider. """ results = await gather( - *[p._get_resource_template(uri, version) for p in self._providers], + *[p.get_resource_template(uri, version) for p in self._providers], return_exceptions=True, ) return self._get_highest_version_result( @@ -209,15 +209,15 @@ async def get_resource_template( # Prompts # ------------------------------------------------------------------------- - async def list_prompts(self) -> Sequence[Prompt]: + async def _list_prompts(self) -> Sequence[Prompt]: """List all prompts from all providers (with transforms applied).""" results = await gather( - *[p._list_prompts() for p in self._providers], + *[p.list_prompts() for p in self._providers], return_exceptions=True, ) return self._collect_list_results(results, "list_prompts") - async def get_prompt( + async def _get_prompt( self, name: str, version: VersionSpec | None = None ) -> Prompt | None: """Get prompt by name. @@ -228,7 +228,7 @@ async def get_prompt( If specified, returns highest version matching the spec from any provider. """ results = await gather( - *[p._get_prompt(name, version) for p in self._providers], + *[p.get_prompt(name, version) for p in self._providers], return_exceptions=True, ) return self._get_highest_version_result(results, f"get_prompt({name!r})") # type: ignore[return-value] diff --git a/src/fastmcp/server/providers/base.py b/src/fastmcp/server/providers/base.py index 2ae7e973ca..f6ab8acc79 100644 --- a/src/fastmcp/server/providers/base.py +++ b/src/fastmcp/server/providers/base.py @@ -14,11 +14,11 @@ def __init__(self, db_url: str): super().__init__() self.db = Database(db_url) - async def list_tools(self) -> list[Tool]: + async def _list_tools(self) -> list[Tool]: rows = await self.db.fetch("SELECT * FROM tools") return [self._make_tool(row) for row in rows] - async def get_tool(self, name: str) -> Tool | None: + async def _get_tool(self, name: str) -> Tool | None: row = await self.db.fetchone("SELECT * FROM tools WHERE name = ?", name) return self._make_tool(row) if row else None @@ -100,7 +100,7 @@ def add_transform(self, transform: Transform) -> None: # Internal transform chain building # ------------------------------------------------------------------------- - async def _list_tools(self) -> Sequence[Tool]: + async def list_tools(self) -> Sequence[Tool]: """List tools with all transforms applied. Builds a middleware chain: base → transforms (in order). @@ -111,7 +111,7 @@ async def _list_tools(self) -> Sequence[Tool]: """ async def base() -> Sequence[Tool]: - return await self.list_tools() + return await self._list_tools() chain = base for transform in self.transforms: @@ -119,7 +119,7 @@ async def base() -> Sequence[Tool]: return await chain() - async def _get_tool( + async def get_tool( self, name: str, version: VersionSpec | None = None ) -> Tool | None: """Get tool by transformed name with all transforms applied. @@ -133,7 +133,7 @@ async def _get_tool( """ async def base(n: str, version: VersionSpec | None = None) -> Tool | None: - return await self.get_tool(n, version) + return await self._get_tool(n, version) chain = base for transform in self.transforms: @@ -141,11 +141,11 @@ async def base(n: str, version: VersionSpec | None = None) -> Tool | None: return await chain(name, version=version) - async def _list_resources(self) -> Sequence[Resource]: + async def list_resources(self) -> Sequence[Resource]: """List resources with all transforms applied.""" async def base() -> Sequence[Resource]: - return await self.list_resources() + return await self._list_resources() chain = base for transform in self.transforms: @@ -153,7 +153,7 @@ async def base() -> Sequence[Resource]: return await chain() - async def _get_resource( + async def get_resource( self, uri: str, version: VersionSpec | None = None ) -> Resource | None: """Get resource by transformed URI with all transforms applied. @@ -164,7 +164,7 @@ async def _get_resource( """ async def base(u: str, version: VersionSpec | None = None) -> Resource | None: - return await self.get_resource(u, version) + return await self._get_resource(u, version) chain = base for transform in self.transforms: @@ -172,11 +172,11 @@ async def base(u: str, version: VersionSpec | None = None) -> Resource | None: return await chain(uri, version=version) - async def _list_resource_templates(self) -> Sequence[ResourceTemplate]: + async def list_resource_templates(self) -> Sequence[ResourceTemplate]: """List resource templates with all transforms applied.""" async def base() -> Sequence[ResourceTemplate]: - return await self.list_resource_templates() + return await self._list_resource_templates() chain = base for transform in self.transforms: @@ -184,7 +184,7 @@ async def base() -> Sequence[ResourceTemplate]: return await chain() - async def _get_resource_template( + async def get_resource_template( self, uri: str, version: VersionSpec | None = None ) -> ResourceTemplate | None: """Get resource template by transformed URI with all transforms applied. @@ -197,7 +197,7 @@ async def _get_resource_template( async def base( u: str, version: VersionSpec | None = None ) -> ResourceTemplate | None: - return await self.get_resource_template(u, version) + return await self._get_resource_template(u, version) chain = base for transform in self.transforms: @@ -205,11 +205,11 @@ async def base( return await chain(uri, version=version) - async def _list_prompts(self) -> Sequence[Prompt]: + async def list_prompts(self) -> Sequence[Prompt]: """List prompts with all transforms applied.""" async def base() -> Sequence[Prompt]: - return await self.list_prompts() + return await self._list_prompts() chain = base for transform in self.transforms: @@ -217,7 +217,7 @@ async def base() -> Sequence[Prompt]: return await chain() - async def _get_prompt( + async def get_prompt( self, name: str, version: VersionSpec | None = None ) -> Prompt | None: """Get prompt by transformed name with all transforms applied. @@ -228,7 +228,7 @@ async def _get_prompt( """ async def base(n: str, version: VersionSpec | None = None) -> Prompt | None: - return await self.get_prompt(n, version) + return await self._get_prompt(n, version) chain = base for transform in self.transforms: @@ -237,10 +237,10 @@ async def base(n: str, version: VersionSpec | None = None) -> Prompt | None: return await chain(name, version=version) # ------------------------------------------------------------------------- - # Public list/get methods (override these to provide components) + # Private list/get methods (override these to provide components) # ------------------------------------------------------------------------- - async def list_tools(self) -> Sequence[Tool]: + async def _list_tools(self) -> Sequence[Tool]: """Return all available tools. Override to provide tools dynamically. Returns ALL versions of all tools. @@ -248,12 +248,12 @@ async def list_tools(self) -> Sequence[Tool]: """ return [] - async def get_tool( + async def _get_tool( self, name: str, version: VersionSpec | None = None ) -> Tool | None: """Get a specific tool by name. - Default implementation filters list_tools() and picks the highest version + Default implementation filters _list_tools() and picks the highest version that matches the spec. Args: @@ -264,7 +264,7 @@ async def get_tool( Returns: The Tool if found, or None to continue searching other providers. """ - tools = await self.list_tools() + tools = await self._list_tools() matching = [t for t in tools if t.name == name] if version: matching = [t for t in matching if version.matches(t.version)] @@ -272,7 +272,7 @@ async def get_tool( return None return max(matching, key=version_sort_key) # type: ignore[type-var] - async def list_resources(self) -> Sequence[Resource]: + async def _list_resources(self) -> Sequence[Resource]: """Return all available resources. Override to provide resources dynamically. Returns ALL versions of all resources. @@ -280,12 +280,12 @@ async def list_resources(self) -> Sequence[Resource]: """ return [] - async def get_resource( + async def _get_resource( self, uri: str, version: VersionSpec | None = None ) -> Resource | None: """Get a specific resource by URI. - Default implementation filters list_resources() and returns highest + Default implementation filters _list_resources() and returns highest version matching the spec. Args: @@ -295,7 +295,7 @@ async def get_resource( Returns: The Resource if found, or None to continue searching other providers. """ - resources = await self.list_resources() + resources = await self._list_resources() matching = [r for r in resources if str(r.uri) == uri] if version: matching = [r for r in matching if version.matches(r.version)] @@ -303,7 +303,7 @@ async def get_resource( return None return max(matching, key=version_sort_key) # type: ignore[type-var] - async def list_resource_templates(self) -> Sequence[ResourceTemplate]: + async def _list_resource_templates(self) -> Sequence[ResourceTemplate]: """Return all available resource templates. Override to provide resource templates dynamically. Returns ALL versions. @@ -311,7 +311,7 @@ async def list_resource_templates(self) -> Sequence[ResourceTemplate]: """ return [] - async def get_resource_template( + async def _get_resource_template( self, uri: str, version: VersionSpec | None = None ) -> ResourceTemplate | None: """Get a resource template that matches the given URI. @@ -326,7 +326,7 @@ async def get_resource_template( Returns: The ResourceTemplate if a matching one is found, or None to continue searching. """ - templates = await self.list_resource_templates() + templates = await self._list_resource_templates() matching = [t for t in templates if t.matches(uri) is not None] if version: matching = [t for t in matching if version.matches(t.version)] @@ -334,7 +334,7 @@ async def get_resource_template( return None return max(matching, key=version_sort_key) # type: ignore[type-var] - async def list_prompts(self) -> Sequence[Prompt]: + async def _list_prompts(self) -> Sequence[Prompt]: """Return all available prompts. Override to provide prompts dynamically. Returns ALL versions of all prompts. @@ -342,12 +342,12 @@ async def list_prompts(self) -> Sequence[Prompt]: """ return [] - async def get_prompt( + async def _get_prompt( self, name: str, version: VersionSpec | None = None ) -> Prompt | None: """Get a specific prompt by name. - Default implementation filters list_prompts() and picks the highest version + Default implementation filters _list_prompts() and picks the highest version matching the spec. Args: @@ -357,7 +357,7 @@ async def get_prompt( Returns: The Prompt if found, or None to continue searching other providers. """ - prompts = await self.list_prompts() + prompts = await self._list_prompts() matching = [p for p in prompts if p.name == name] if version: matching = [p for p in matching if version.matches(p.version)] @@ -380,10 +380,10 @@ async def get_tasks(self) -> Sequence[FastMCPComponent]: """ # Fetch all component types in parallel results = await gather( - self.list_tools(), - self.list_resources(), - self.list_resource_templates(), - self.list_prompts(), + self._list_tools(), + self._list_resources(), + self._list_resource_templates(), + self._list_prompts(), ) tools = cast(Sequence[Tool], results[0]) resources = cast(Sequence[Resource], results[1]) diff --git a/src/fastmcp/server/providers/fastmcp_provider.py b/src/fastmcp/server/providers/fastmcp_provider.py index a435bc0ac1..78cde81216 100644 --- a/src/fastmcp/server/providers/fastmcp_provider.py +++ b/src/fastmcp/server/providers/fastmcp_provider.py @@ -482,7 +482,7 @@ def __init__(self, server: FastMCP[Any]): # Tool methods # ------------------------------------------------------------------------- - async def list_tools(self) -> Sequence[Tool]: + async def _list_tools(self) -> Sequence[Tool]: """List all tools from the mounted server as FastMCPProviderTools. Runs the mounted server's middleware so filtering/transformation applies. @@ -492,16 +492,16 @@ async def list_tools(self) -> Sequence[Tool]: raw_tools = await self.server.get_tools(run_middleware=True) return [FastMCPProviderTool.wrap(self.server, t) for t in raw_tools] - async def get_tool( + async def _get_tool( self, name: str, version: VersionSpec | None = None ) -> Tool | None: """Get a tool by name as a FastMCPProviderTool. Passes the full VersionSpec to the nested server, which handles both - exact version matching and range filtering. Uses _get_tool to ensure + exact version matching and range filtering. Uses get_tool to ensure the nested server's transforms are applied. """ - raw_tool = await self.server._get_tool(name, version) + raw_tool = await self.server.get_tool(name, version) if raw_tool is None: return None return FastMCPProviderTool.wrap(self.server, raw_tool) @@ -510,7 +510,7 @@ async def get_tool( # Resource methods # ------------------------------------------------------------------------- - async def list_resources(self) -> Sequence[Resource]: + async def _list_resources(self) -> Sequence[Resource]: """List all resources from the mounted server as FastMCPProviderResources. Runs the mounted server's middleware so filtering/transformation applies. @@ -520,16 +520,16 @@ async def list_resources(self) -> Sequence[Resource]: raw_resources = await self.server.get_resources(run_middleware=True) return [FastMCPProviderResource.wrap(self.server, r) for r in raw_resources] - async def get_resource( + async def _get_resource( self, uri: str, version: VersionSpec | None = None ) -> Resource | None: """Get a concrete resource by URI as a FastMCPProviderResource. Passes the full VersionSpec to the nested server, which handles both - exact version matching and range filtering. Uses _get_resource to ensure + exact version matching and range filtering. Uses get_resource to ensure the nested server's transforms are applied. """ - raw_resource = await self.server._get_resource(uri, version) + raw_resource = await self.server.get_resource(uri, version) if raw_resource is None: return None return FastMCPProviderResource.wrap(self.server, raw_resource) @@ -538,7 +538,7 @@ async def get_resource( # Resource template methods # ------------------------------------------------------------------------- - async def list_resource_templates(self) -> Sequence[ResourceTemplate]: + async def _list_resource_templates(self) -> Sequence[ResourceTemplate]: """List all resource templates from the mounted server. Runs the mounted server's middleware so filtering/transformation applies. @@ -550,16 +550,16 @@ async def list_resource_templates(self) -> Sequence[ResourceTemplate]: FastMCPProviderResourceTemplate.wrap(self.server, t) for t in raw_templates ] - async def get_resource_template( + async def _get_resource_template( self, uri: str, version: VersionSpec | None = None ) -> ResourceTemplate | None: """Get a resource template that matches the given URI. Passes the full VersionSpec to the nested server, which handles both - exact version matching and range filtering. Uses _get_resource_template + exact version matching and range filtering. Uses get_resource_template to ensure the nested server's transforms are applied. """ - raw_template = await self.server._get_resource_template(uri, version) + raw_template = await self.server.get_resource_template(uri, version) if raw_template is None: return None return FastMCPProviderResourceTemplate.wrap(self.server, raw_template) @@ -568,7 +568,7 @@ async def get_resource_template( # Prompt methods # ------------------------------------------------------------------------- - async def list_prompts(self) -> Sequence[Prompt]: + async def _list_prompts(self) -> Sequence[Prompt]: """List all prompts from the mounted server as FastMCPProviderPrompts. Runs the mounted server's middleware so filtering/transformation applies. @@ -578,16 +578,16 @@ async def list_prompts(self) -> Sequence[Prompt]: raw_prompts = await self.server.get_prompts(run_middleware=True) return [FastMCPProviderPrompt.wrap(self.server, p) for p in raw_prompts] - async def get_prompt( + async def _get_prompt( self, name: str, version: VersionSpec | None = None ) -> Prompt | None: """Get a prompt by name as a FastMCPProviderPrompt. Passes the full VersionSpec to the nested server, which handles both - exact version matching and range filtering. Uses _get_prompt to ensure + exact version matching and range filtering. Uses get_prompt to ensure the nested server's transforms are applied. """ - raw_prompt = await self.server._get_prompt(name, version) + raw_prompt = await self.server.get_prompt(name, version) if raw_prompt is None: return None return FastMCPProviderPrompt.wrap(self.server, raw_prompt) diff --git a/src/fastmcp/server/providers/filesystem.py b/src/fastmcp/server/providers/filesystem.py index 9225ff6366..2306a48a70 100644 --- a/src/fastmcp/server/providers/filesystem.py +++ b/src/fastmcp/server/providers/filesystem.py @@ -174,53 +174,53 @@ async def _ensure_loaded(self) -> None: # Override provider methods to support reload mode - async def list_tools(self) -> Sequence[Tool]: + async def _list_tools(self) -> Sequence[Tool]: """Return all tools, reloading if in reload mode.""" await self._ensure_loaded() - return await super().list_tools() + return await super()._list_tools() - async def get_tool( + async def _get_tool( self, name: str, version: VersionSpec | None = None ) -> Tool | None: """Get a tool by name, reloading if in reload mode.""" await self._ensure_loaded() - return await super().get_tool(name, version) + return await super()._get_tool(name, version) - async def list_resources(self) -> Sequence[Resource]: + async def _list_resources(self) -> Sequence[Resource]: """Return all resources, reloading if in reload mode.""" await self._ensure_loaded() - return await super().list_resources() + return await super()._list_resources() - async def get_resource( + async def _get_resource( self, uri: str, version: VersionSpec | None = None ) -> Resource | None: """Get a resource by URI, reloading if in reload mode.""" await self._ensure_loaded() - return await super().get_resource(uri, version) + return await super()._get_resource(uri, version) - async def list_resource_templates(self) -> Sequence[ResourceTemplate]: + async def _list_resource_templates(self) -> Sequence[ResourceTemplate]: """Return all resource templates, reloading if in reload mode.""" await self._ensure_loaded() - return await super().list_resource_templates() + return await super()._list_resource_templates() - async def get_resource_template( + async def _get_resource_template( self, uri: str, version: VersionSpec | None = None ) -> ResourceTemplate | None: """Get a resource template, reloading if in reload mode.""" await self._ensure_loaded() - return await super().get_resource_template(uri, version) + return await super()._get_resource_template(uri, version) - async def list_prompts(self) -> Sequence[Prompt]: + async def _list_prompts(self) -> Sequence[Prompt]: """Return all prompts, reloading if in reload mode.""" await self._ensure_loaded() - return await super().list_prompts() + return await super()._list_prompts() - async def get_prompt( + async def _get_prompt( self, name: str, version: VersionSpec | None = None ) -> Prompt | None: """Get a prompt by name, reloading if in reload mode.""" await self._ensure_loaded() - return await super().get_prompt(name, version) + return await super()._get_prompt(name, version) def __repr__(self) -> str: return f"FileSystemProvider(root={self._root!r}, reload={self._reload})" diff --git a/src/fastmcp/server/providers/local_provider.py b/src/fastmcp/server/providers/local_provider.py index 61c2b64fa8..506c7b4a1f 100644 --- a/src/fastmcp/server/providers/local_provider.py +++ b/src/fastmcp/server/providers/local_provider.py @@ -492,7 +492,7 @@ def remove_prompt(self, name: str, version: str | None = None) -> None: # Provider interface implementation # ========================================================================= - async def list_tools(self) -> Sequence[Tool]: + async def _list_tools(self) -> Sequence[Tool]: """Return all visible tools.""" return [ v @@ -500,7 +500,7 @@ async def list_tools(self) -> Sequence[Tool]: if isinstance(v, Tool) and self._is_component_enabled(v) ] - async def get_tool( + async def _get_tool( self, name: str, version: VersionSpec | None = None ) -> Tool | None: """Get a tool by name. @@ -520,7 +520,7 @@ async def get_tool( return None return max(matching, key=version_sort_key) # type: ignore[type-var] - async def list_resources(self) -> Sequence[Resource]: + async def _list_resources(self) -> Sequence[Resource]: """Return all visible resources.""" return [ v @@ -528,7 +528,7 @@ async def list_resources(self) -> Sequence[Resource]: if isinstance(v, Resource) and self._is_component_enabled(v) ] - async def get_resource( + async def _get_resource( self, uri: str, version: VersionSpec | None = None ) -> Resource | None: """Get a resource by URI. @@ -550,7 +550,7 @@ async def get_resource( return None return max(matching, key=version_sort_key) # type: ignore[type-var] - async def list_resource_templates(self) -> Sequence[ResourceTemplate]: + async def _list_resource_templates(self) -> Sequence[ResourceTemplate]: """Return all visible resource templates.""" return [ v @@ -558,7 +558,7 @@ async def list_resource_templates(self) -> Sequence[ResourceTemplate]: if isinstance(v, ResourceTemplate) and self._is_component_enabled(v) ] - async def get_resource_template( + async def _get_resource_template( self, uri: str, version: VersionSpec | None = None ) -> ResourceTemplate | None: """Get a resource template that matches the given URI. @@ -583,7 +583,7 @@ async def get_resource_template( return None return max(matching, key=version_sort_key) # type: ignore[type-var] - async def list_prompts(self) -> Sequence[Prompt]: + async def _list_prompts(self) -> Sequence[Prompt]: """Return all visible prompts.""" return [ v @@ -591,7 +591,7 @@ async def list_prompts(self) -> Sequence[Prompt]: if isinstance(v, Prompt) and self._is_component_enabled(v) ] - async def get_prompt( + async def _get_prompt( self, name: str, version: VersionSpec | None = None ) -> Prompt | None: """Get a prompt by name. diff --git a/src/fastmcp/server/providers/openapi/provider.py b/src/fastmcp/server/providers/openapi/provider.py index 3f216aef0c..7f13fb4da1 100644 --- a/src/fastmcp/server/providers/openapi/provider.py +++ b/src/fastmcp/server/providers/openapi/provider.py @@ -349,11 +349,11 @@ def _create_openapi_template( # Provider interface # ------------------------------------------------------------------------- - async def list_tools(self) -> Sequence[Tool]: + async def _list_tools(self) -> Sequence[Tool]: """Return all tools created from the OpenAPI spec.""" return list(self._tools.values()) - async def get_tool( + async def _get_tool( self, name: str, version: VersionSpec | None = None ) -> Tool | None: """Get a tool by name.""" @@ -364,11 +364,11 @@ async def get_tool( return None return tool - async def list_resources(self) -> Sequence[Resource]: + async def _list_resources(self) -> Sequence[Resource]: """Return all resources created from the OpenAPI spec.""" return list(self._resources.values()) - async def get_resource( + async def _get_resource( self, uri: str, version: VersionSpec | None = None ) -> Resource | None: """Get a resource by URI.""" @@ -379,11 +379,11 @@ async def get_resource( return None return resource - async def list_resource_templates(self) -> Sequence[ResourceTemplate]: + async def _list_resource_templates(self) -> Sequence[ResourceTemplate]: """Return all resource templates created from the OpenAPI spec.""" return list(self._templates.values()) - async def get_resource_template( + async def _get_resource_template( self, uri: str, version: VersionSpec | None = None ) -> ResourceTemplate | None: """Get a resource template that matches the given URI.""" @@ -396,7 +396,7 @@ async def get_resource_template( return None return max(matching, key=version_sort_key) # type: ignore[type-var] - async def list_prompts(self) -> Sequence[Prompt]: + async def _list_prompts(self) -> Sequence[Prompt]: """Return empty list - OpenAPI doesn't create prompts.""" return [] diff --git a/src/fastmcp/server/providers/proxy.py b/src/fastmcp/server/providers/proxy.py index 56827ced21..d063373961 100644 --- a/src/fastmcp/server/providers/proxy.py +++ b/src/fastmcp/server/providers/proxy.py @@ -517,7 +517,7 @@ async def _get_client(self) -> Client: # Tool methods # ------------------------------------------------------------------------- - async def list_tools(self) -> Sequence[Tool]: + async def _list_tools(self) -> Sequence[Tool]: """List all tools from the remote server.""" try: client = await self._get_client() @@ -535,7 +535,7 @@ async def list_tools(self) -> Sequence[Tool]: # Resource methods # ------------------------------------------------------------------------- - async def list_resources(self) -> Sequence[Resource]: + async def _list_resources(self) -> Sequence[Resource]: """List all resources from the remote server.""" try: client = await self._get_client() @@ -554,7 +554,7 @@ async def list_resources(self) -> Sequence[Resource]: # Resource template methods # ------------------------------------------------------------------------- - async def list_resource_templates(self) -> Sequence[ResourceTemplate]: + async def _list_resource_templates(self) -> Sequence[ResourceTemplate]: """List all resource templates from the remote server.""" try: client = await self._get_client() @@ -573,7 +573,7 @@ async def list_resource_templates(self) -> Sequence[ResourceTemplate]: # Prompt methods # ------------------------------------------------------------------------- - async def list_prompts(self) -> Sequence[Prompt]: + async def _list_prompts(self) -> Sequence[Prompt]: """List all prompts from the remote server.""" try: client = await self._get_client() diff --git a/src/fastmcp/server/server.py b/src/fastmcp/server/server.py index 7917bfdc6b..1cde0cc4a9 100644 --- a/src/fastmcp/server/server.py +++ b/src/fastmcp/server/server.py @@ -867,38 +867,38 @@ def _collect_list_results( # Provider interface overrides (aggregate from sub-providers) # ------------------------------------------------------------------------- - async def list_tools(self) -> Sequence[Tool]: + async def _list_tools(self) -> Sequence[Tool]: """Aggregate tools from all sub-providers. - This is the Provider interface implementation. The inherited _list_tools() + This is the Provider interface implementation. The inherited list_tools() applies server-level transforms over this method. """ results = await gather( - *[p._list_tools() for p in self._providers], + *[p.list_tools() for p in self._providers], return_exceptions=True, ) return self._collect_list_results(results, "list_tools") - async def list_resources(self) -> Sequence[Resource]: + async def _list_resources(self) -> Sequence[Resource]: """Aggregate resources from all sub-providers.""" results = await gather( - *[p._list_resources() for p in self._providers], + *[p.list_resources() for p in self._providers], return_exceptions=True, ) return self._collect_list_results(results, "list_resources") - async def list_resource_templates(self) -> Sequence[ResourceTemplate]: + async def _list_resource_templates(self) -> Sequence[ResourceTemplate]: """Aggregate resource templates from all sub-providers.""" results = await gather( - *[p._list_resource_templates() for p in self._providers], + *[p.list_resource_templates() for p in self._providers], return_exceptions=True, ) return self._collect_list_results(results, "list_resource_templates") - async def list_prompts(self) -> Sequence[Prompt]: + async def _list_prompts(self) -> Sequence[Prompt]: """Aggregate prompts from all sub-providers.""" results = await gather( - *[p._list_prompts() for p in self._providers], + *[p.list_prompts() for p in self._providers], return_exceptions=True, ) return self._collect_list_results(results, "list_prompts") @@ -1106,7 +1106,7 @@ async def get_tools(self, *, run_middleware: bool = False) -> list[Tool]: ) # Query through full transform chain (provider transforms + server transforms + visibility) - tools = await self._list_tools() + tools = await self.list_tools() # Get auth context (skip_auth=True for STDIO which has no auth concept) skip_auth, token = _get_auth_context() @@ -1125,12 +1125,12 @@ async def get_tools(self, *, run_middleware: bool = False) -> list[Tool]: return _dedupe_with_versions(authorized, lambda t: t.name) - async def get_tool( + async def _get_tool( self, name: str, version: VersionSpec | None = None ) -> Tool | None: """Get a tool by name via aggregation from providers. - This is the raw lookup that Provider._get_tool() wraps with transforms. + This is the raw lookup that Provider.get_tool() wraps with transforms. Aggregates from all sub-providers and applies component-level auth. Args: @@ -1143,7 +1143,7 @@ async def get_tool( # Aggregate from all sub-providers (each applies their own transforms) results = await gather( - *[p._get_tool(name, version) for p in self._providers], + *[p.get_tool(name, version) for p in self._providers], return_exceptions=True, ) @@ -1177,7 +1177,7 @@ async def get_tool( return tool - # _get_tool is inherited from Provider - wraps get_tool() with transforms + # get_tool() is inherited from Provider - wraps _get_tool() with transforms async def get_resources(self, *, run_middleware: bool = False) -> list[Resource]: """Get all enabled resources from providers. @@ -1204,7 +1204,7 @@ async def get_resources(self, *, run_middleware: bool = False) -> list[Resource] ) # Query through full transform chain (provider transforms + server transforms + visibility) - resources = await self._list_resources() + resources = await self.list_resources() # Get auth context (skip_auth=True for STDIO which has no auth concept) skip_auth, token = _get_auth_context() @@ -1223,12 +1223,12 @@ async def get_resources(self, *, run_middleware: bool = False) -> list[Resource] return _dedupe_with_versions(authorized, lambda r: str(r.uri)) - async def get_resource( + async def _get_resource( self, uri: str, version: VersionSpec | None = None ) -> Resource | None: """Get a resource by URI via aggregation from providers. - This is the raw lookup that Provider._get_resource() wraps with transforms. + This is the raw lookup that Provider.get_resource() wraps with transforms. Aggregates from all sub-providers and applies component-level auth. Args: @@ -1240,7 +1240,7 @@ async def get_resource( """ # Aggregate from all sub-providers (each applies their own transforms) results = await gather( - *[p._get_resource(uri, version) for p in self._providers], + *[p.get_resource(uri, version) for p in self._providers], return_exceptions=True, ) @@ -1274,7 +1274,7 @@ async def get_resource( return resource - # _get_resource is inherited from Provider - wraps get_resource() with transforms + # get_resource() is inherited from Provider - wraps _get_resource() with transforms async def get_resource_templates( self, *, run_middleware: bool = False @@ -1305,7 +1305,7 @@ async def get_resource_templates( ) # Query through full transform chain (provider transforms + server transforms + visibility) - templates = await self._list_resource_templates() + templates = await self.list_resource_templates() # Get auth context (skip_auth=True for STDIO which has no auth concept) skip_auth, token = _get_auth_context() @@ -1324,12 +1324,12 @@ async def get_resource_templates( return _dedupe_with_versions(authorized, lambda t: t.uri_template) - async def get_resource_template( + async def _get_resource_template( self, uri: str, version: VersionSpec | None = None ) -> ResourceTemplate | None: """Get a resource template by URI via aggregation from providers. - This is the raw lookup that Provider._get_resource_template() wraps with transforms. + This is the raw lookup that Provider.get_resource_template() wraps with transforms. Aggregates from all sub-providers and applies component-level auth. Args: @@ -1341,7 +1341,7 @@ async def get_resource_template( """ # Aggregate from all sub-providers (each applies their own transforms) results = await gather( - *[p._get_resource_template(uri, version) for p in self._providers], + *[p.get_resource_template(uri, version) for p in self._providers], return_exceptions=True, ) @@ -1375,7 +1375,7 @@ async def get_resource_template( return template - # _get_resource_template is inherited from Provider - wraps get_resource_template() with transforms + # get_resource_template() is inherited from Provider - wraps _get_resource_template() with transforms async def get_prompts(self, *, run_middleware: bool = False) -> list[Prompt]: """Get all enabled prompts from providers. @@ -1402,7 +1402,7 @@ async def get_prompts(self, *, run_middleware: bool = False) -> list[Prompt]: ) # Query through full transform chain (provider transforms + server transforms + visibility) - prompts = await self._list_prompts() + prompts = await self.list_prompts() # Get auth context (skip_auth=True for STDIO which has no auth concept) skip_auth, token = _get_auth_context() @@ -1421,12 +1421,12 @@ async def get_prompts(self, *, run_middleware: bool = False) -> list[Prompt]: return _dedupe_with_versions(authorized, lambda p: p.name) - async def get_prompt( + async def _get_prompt( self, name: str, version: VersionSpec | None = None ) -> Prompt | None: """Get a prompt by name via aggregation from providers. - This is the raw lookup that Provider._get_prompt() wraps with transforms. + This is the raw lookup that Provider.get_prompt() wraps with transforms. Aggregates from all sub-providers and applies component-level auth. Args: @@ -1438,7 +1438,7 @@ async def get_prompt( """ # Aggregate from all sub-providers (each applies their own transforms) results = await gather( - *[p._get_prompt(name, version) for p in self._providers], + *[p.get_prompt(name, version) for p in self._providers], return_exceptions=True, ) @@ -1472,7 +1472,7 @@ async def get_prompt( return prompt - # _get_prompt is inherited from Provider - wraps get_prompt() with transforms + # get_prompt() is inherited from Provider - wraps _get_prompt() with transforms @overload async def call_tool( @@ -1559,7 +1559,7 @@ async def call_tool( with server_span( f"tools/call {name}", "tools/call", self.name, "tool", name ) as span: - tool = await self._get_tool(name, version=version) + tool = await self.get_tool(name, version=version) if tool is None: raise NotFoundError(f"Unknown tool: {name!r}") span.set_attributes(tool.get_span_attributes()) @@ -1666,7 +1666,7 @@ async def read_resource( resource_uri=uri, ) as span: # Try concrete resources first (transforms + auth via _get_resource) - resource = await self._get_resource(uri, version=version) + resource = await self.get_resource(uri, version=version) if resource is not None: span.set_attributes(resource.get_span_attributes()) if task_meta is not None and task_meta.fn_key is None: @@ -1686,8 +1686,8 @@ async def read_resource( f"Error reading resource {uri!r}: {e}" ) from e - # Try templates (transforms + auth via _get_resource_template) - template = await self._get_resource_template(uri, version=version) + # Try templates (transforms + auth via get_resource_template) + template = await self.get_resource_template(uri, version=version) if template is None: if version is None: raise NotFoundError(f"Unknown resource: {uri!r}") @@ -1791,7 +1791,7 @@ async def render_prompt( with server_span( f"prompts/get {name}", "prompts/get", self.name, "prompt", name ) as span: - prompt = await self._get_prompt(name, version=version) + prompt = await self.get_prompt(name, version=version) if prompt is None: raise NotFoundError(f"Unknown prompt: {name!r}") span.set_attributes(prompt.get_span_attributes()) diff --git a/tests/server/providers/test_base_provider.py b/tests/server/providers/test_base_provider.py index d9cf63a6f7..084e5ff460 100644 --- a/tests/server/providers/test_base_provider.py +++ b/tests/server/providers/test_base_provider.py @@ -24,7 +24,7 @@ def __init__(self, tools: list[Tool] | None = None): super().__init__() self._tools = tools or [] - async def list_tools(self) -> list[Tool]: + async def _list_tools(self) -> list[Tool]: return self._tools diff --git a/tests/server/providers/test_local_provider.py b/tests/server/providers/test_local_provider.py index 5c4d104723..7dc69b3e31 100644 --- a/tests/server/providers/test_local_provider.py +++ b/tests/server/providers/test_local_provider.py @@ -578,7 +578,7 @@ def original_tool(x: int) -> int: # Get tool through layer with call_next async def get_tool(name: str, version=None): - return await provider.get_tool(name, version) + return await provider._get_tool(name, version) tool = await layer.get_tool("transformed_tool", get_tool) assert tool is not None @@ -604,7 +604,7 @@ def my_tool(x: int) -> int: ) async def get_tool(name: str, version=None): - return await provider.get_tool(name, version) + return await provider._get_tool(name, version) tool = await layer.get_tool("my_tool", get_tool) assert tool is not None @@ -621,12 +621,12 @@ async def test_provider_unaffected_by_transforms(self): def my_tool(x: int) -> int: return x - # Add layer to provider (layers are applied by server, not list_tools) + # Add layer to provider (layers are applied by server, not _list_tools) layer = ToolTransform({"my_tool": ToolTransformConfig(name="renamed")}) provider.add_transform(layer) - # Provider's list_tools returns raw tools (transforms applied when queried via chain) - original_tools = await provider.list_tools() + # Provider's _list_tools returns raw tools (transforms applied when queried via list_tools) + original_tools = await provider._list_tools() assert original_tools[0].name == "my_tool" # Transform modifies them when applied via call_next diff --git a/tests/server/providers/test_local_provider_prompts.py b/tests/server/providers/test_local_provider_prompts.py index 666ecb62dd..d23eff6112 100644 --- a/tests/server/providers/test_local_provider_prompts.py +++ b/tests/server/providers/test_local_provider_prompts.py @@ -377,8 +377,8 @@ def sample_prompt() -> str: prompts = await mcp.get_prompts() assert len(prompts) == 0 - # _get_prompt() applies visibility transform, returns None for disabled - prompt = await mcp._get_prompt("sample_prompt") + # get_prompt() applies visibility transform, returns None for disabled + prompt = await mcp.get_prompt("sample_prompt") assert prompt is None async def test_get_prompt_and_disable(self): @@ -388,15 +388,15 @@ async def test_get_prompt_and_disable(self): def sample_prompt() -> str: return "Hello, world!" - prompt = await mcp._get_prompt("sample_prompt") + prompt = await mcp.get_prompt("sample_prompt") assert prompt is not None mcp.disable(keys=["prompt:sample_prompt@"]) prompts = await mcp.get_prompts() assert len(prompts) == 0 - # _get_prompt() applies visibility transform, returns None for disabled - prompt = await mcp._get_prompt("sample_prompt") + # get_prompt() applies visibility transform, returns None for disabled + prompt = await mcp.get_prompt("sample_prompt") assert prompt is None async def test_cant_get_disabled_prompt(self): @@ -408,8 +408,8 @@ def sample_prompt() -> str: mcp.disable(keys=["prompt:sample_prompt@"]) - # _get_prompt() applies visibility transform, returns None for disabled - prompt = await mcp._get_prompt("sample_prompt") + # get_prompt() applies visibility transform, returns None for disabled + prompt = await mcp.get_prompt("sample_prompt") assert prompt is None @@ -459,15 +459,15 @@ async def test_read_prompt_includes_tags(self): result = await prompt.render({}) assert result.messages[0].content.text == "1" - prompt = await mcp._get_prompt("prompt_2") + prompt = await mcp.get_prompt("prompt_2") assert prompt is None async def test_read_prompt_excludes_tags(self): mcp = self.create_server(exclude_tags={"a"}) - # _get_prompt applies visibility transform (tag filtering) - prompt = await mcp._get_prompt("prompt_1") + # get_prompt applies visibility transform (tag filtering) + prompt = await mcp.get_prompt("prompt_1") assert prompt is None - prompt = await mcp._get_prompt("prompt_2") + prompt = await mcp.get_prompt("prompt_2") result = await prompt.render({}) assert result.messages[0].content.text == "2" diff --git a/tests/server/providers/test_transforming_provider.py b/tests/server/providers/test_transforming_provider.py index 8a7d6a2546..b2021abd67 100644 --- a/tests/server/providers/test_transforming_provider.py +++ b/tests/server/providers/test_transforming_provider.py @@ -159,7 +159,7 @@ def my_tool() -> str: # Create call_next that delegates to provider async def get_tool(name: str, version=None): - return await provider.get_tool(name, version) + return await provider._get_tool(name, version) tool = await layer.get_tool("ns_my_tool", get_tool) @@ -178,7 +178,7 @@ def original() -> str: layer = ToolTransform({"original": ToolTransformConfig(name="renamed")}) async def get_tool(name: str, version=None): - return await provider.get_tool(name, version) + return await provider._get_tool(name, version) tool = await layer.get_tool("renamed", get_tool) @@ -197,7 +197,7 @@ def my_resource() -> str: layer = Namespace("ns") async def get_resource(uri: str, version=None): - return await provider.get_resource(uri, version) + return await provider._get_resource(uri, version) resource = await layer.get_resource("resource://ns/data", get_resource) @@ -216,7 +216,7 @@ def my_tool() -> str: layer = Namespace("ns") async def get_tool(name: str, version=None): - return await provider.get_tool(name, version) + return await provider._get_tool(name, version) # Wrong namespace prefix assert await layer.get_tool("wrong_my_tool", get_tool) is None diff --git a/tests/server/test_providers.py b/tests/server/test_providers.py index ec71902b1b..396a52c257 100644 --- a/tests/server/test_providers.py +++ b/tests/server/test_providers.py @@ -48,11 +48,11 @@ def __init__(self, tools: list[Tool] | None = None): self.list_tools_call_count = 0 self.get_tool_call_count = 0 - async def list_tools(self) -> list[Tool]: + async def _list_tools(self) -> list[Tool]: self.list_tools_call_count += 1 return self._tools - async def get_tool( + async def _get_tool( self, name: str, version: VersionSpec | None = None ) -> Tool | None: self.get_tool_call_count += 1 @@ -73,7 +73,7 @@ def __init__(self, tools: list[Tool]): self._tools = tools self.list_tools_call_count = 0 - async def list_tools(self) -> list[Tool]: + async def _list_tools(self) -> list[Tool]: self.list_tools_call_count += 1 return self._tools @@ -384,7 +384,7 @@ async def test_read_resource_default_implementation(self): """Test that default read_resource uses get_resource and reads it.""" class ResourceProvider(Provider): - async def list_resources(self) -> Sequence[Resource]: + async def _list_resources(self) -> Sequence[Resource]: return [ FunctionResource( uri=AnyUrl("test://data"), @@ -406,7 +406,7 @@ async def test_read_resource_template_default(self): """Test that read_resource_template handles template-based resources.""" class TemplateProvider(Provider): - async def list_resource_templates(self) -> Sequence[ResourceTemplate]: + async def _list_resource_templates(self) -> Sequence[ResourceTemplate]: return [ FunctionResourceTemplate.from_function( fn=lambda name: f"content of {name}", @@ -428,7 +428,7 @@ async def test_render_prompt_default_implementation(self): """Test that default render_prompt uses get_prompt and renders it.""" class PromptProvider(Provider): - async def list_prompts(self) -> Sequence[Prompt]: + async def _list_prompts(self) -> Sequence[Prompt]: return [ FunctionPrompt.from_function( fn=lambda name: f"Hello, {name}!", diff --git a/tests/server/test_versioning.py b/tests/server/test_versioning.py index f2b0713dcf..c78164aa9f 100644 --- a/tests/server/test_versioning.py +++ b/tests/server/test_versioning.py @@ -483,13 +483,13 @@ def add(x: int) -> int: assert len(tools) == 1 assert tools[0].version == "3.0" - # Can request specific versions in range (use _get_tool to apply transforms) - tool_v2 = await mcp._get_tool("add", VersionSpec(eq="2.0")) + # Can request specific versions in range (use get_tool to apply transforms) + tool_v2 = await mcp.get_tool("add", VersionSpec(eq="2.0")) assert tool_v2 is not None assert tool_v2.version == "2.0" # Cannot request version outside range - returns None - assert await mcp._get_tool("add", VersionSpec(eq="1.0")) is None + assert await mcp.get_tool("add", VersionSpec(eq="1.0")) is None async def test_version_range(self): """VersionFilter(version_gte='2.0', version_lt='3.0') shows only v2.x.""" @@ -520,14 +520,14 @@ def calc() -> int: assert len(tools) == 1 assert tools[0].version == "2.5" - # Can request specific versions in range (use _get_tool to apply transforms) - tool_v2 = await mcp._get_tool("calc", VersionSpec(eq="2.0")) + # Can request specific versions in range (use get_tool to apply transforms) + tool_v2 = await mcp.get_tool("calc", VersionSpec(eq="2.0")) assert tool_v2 is not None assert tool_v2.version == "2.0" # Versions outside range are not accessible - return None - assert await mcp._get_tool("calc", VersionSpec(eq="1.0")) is None - assert await mcp._get_tool("calc", VersionSpec(eq="3.0")) is None + assert await mcp.get_tool("calc", VersionSpec(eq="1.0")) is None + assert await mcp.get_tool("calc", VersionSpec(eq="3.0")) is None async def test_unversioned_always_passes(self): """Unversioned components pass through any filter.""" @@ -589,8 +589,8 @@ def only_v5() -> str: mcp.add_transform(VersionFilter(version_lt="3.0")) - # Tool exists but is filtered out - returns None (use _get_tool to apply transforms) - assert await mcp._get_tool("only_v5") is None + # Tool exists but is filtered out - returns None (use get_tool to apply transforms) + assert await mcp.get_tool("only_v5") is None async def test_must_specify_at_least_one(self): """VersionFilter() with no args raises ValueError.""" @@ -831,8 +831,8 @@ def high_version_tool() -> int: tools = await parent.get_tools() assert len(tools) == 0 - # _get_tool should also return None (respects filter, applies transforms) - assert await parent._get_tool("child_high_version_tool") is None + # get_tool should also return None (respects filter, applies transforms) + assert await parent.get_tool("child_high_version_tool") is None class TestMountedRangeFiltering: @@ -857,8 +857,8 @@ def calc() -> int: parent.add_transform(VersionFilter(version_lt="2.0")) # Should return v1.0 (the highest version that matches <2.0) - # Use _get_tool to apply transforms - tool = await parent._get_tool("child_calc") + # Use get_tool to apply transforms + tool = await parent.get_tool("child_calc") assert tool is not None assert tool.version == "1.0" @@ -884,13 +884,13 @@ def calc() -> int: parent.mount(child, "child") parent.add_transform(VersionFilter(version_gte="1.0", version_lt="3.0")) - # Request specific version within range (use _get_tool to apply transforms) - tool = await parent._get_tool("child_calc", VersionSpec(eq="1.0")) + # Request specific version within range (use get_tool to apply transforms) + tool = await parent.get_tool("child_calc", VersionSpec(eq="1.0")) assert tool is not None assert tool.version == "1.0" # Request version outside range should return None - result = await parent._get_tool("child_calc", VersionSpec(eq="3.0")) + result = await parent.get_tool("child_calc", VersionSpec(eq="3.0")) assert result is None