Merge pull request #2902 from jlowin/swap-methods

This commit is contained in:
Jeremiah Lowin 2026-01-17 15:25:46 -05:00 committed by GitHub
commit 96782c0330
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 194 additions and 196 deletions

View file

@ -6,23 +6,21 @@ 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:
```bash
uv run prek run --all-files # Ruff + Prettier + ty
```
**Tests must pass and lint/typing must be clean before committing.**
**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
## Repository Structure
| Path | Purpose |

View file

@ -137,7 +137,7 @@ class AuthMiddleware(Middleware):
)
# 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 @@ class AuthMiddleware(Middleware):
)
# 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 @@ class AuthMiddleware(Middleware):
)
# 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"

View file

@ -13,11 +13,11 @@ Example:
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

View file

@ -123,15 +123,15 @@ class AggregateProvider(Provider):
# 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 @@ class AggregateProvider(Provider):
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 @@ class AggregateProvider(Provider):
# 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 @@ class AggregateProvider(Provider):
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 @@ class AggregateProvider(Provider):
# 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 @@ class AggregateProvider(Provider):
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 @@ class AggregateProvider(Provider):
# 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 @@ class AggregateProvider(Provider):
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]

View file

@ -14,11 +14,11 @@ Example:
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 @@ class Provider:
# 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 @@ class Provider:
"""
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 @@ class Provider:
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 @@ class Provider:
"""
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 @@ class Provider:
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 @@ class Provider:
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 @@ class Provider:
"""
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 @@ class Provider:
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 @@ class Provider:
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 @@ class Provider:
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 @@ class Provider:
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 @@ class Provider:
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 @@ class Provider:
"""
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 @@ class Provider:
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 @@ class Provider:
"""
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 @@ class Provider:
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 @@ class Provider:
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 @@ class Provider:
"""
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 @@ class Provider:
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 @@ class Provider:
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 @@ class Provider:
"""
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 @@ class Provider:
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 @@ class Provider:
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 @@ class Provider:
"""
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 @@ class Provider:
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 @@ class Provider:
"""
# 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])

View file

@ -482,7 +482,7 @@ class FastMCPProvider(Provider):
# 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 @@ class FastMCPProvider(Provider):
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 @@ class FastMCPProvider(Provider):
# 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 @@ class FastMCPProvider(Provider):
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 @@ class FastMCPProvider(Provider):
# 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 @@ class FastMCPProvider(Provider):
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 @@ class FastMCPProvider(Provider):
# 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 @@ class FastMCPProvider(Provider):
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)

View file

@ -174,53 +174,53 @@ class FileSystemProvider(LocalProvider):
# 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})"

View file

@ -492,7 +492,7 @@ class LocalProvider(Provider):
# 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 @@ class LocalProvider(Provider):
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 @@ class LocalProvider(Provider):
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 @@ class LocalProvider(Provider):
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 @@ class LocalProvider(Provider):
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 @@ class LocalProvider(Provider):
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 @@ class LocalProvider(Provider):
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 @@ class LocalProvider(Provider):
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.

View file

@ -349,11 +349,11 @@ class OpenAPIProvider(Provider):
# 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 @@ class OpenAPIProvider(Provider):
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 @@ class OpenAPIProvider(Provider):
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 @@ class OpenAPIProvider(Provider):
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 []

View file

@ -517,7 +517,7 @@ class ProxyProvider(Provider):
# 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 @@ class ProxyProvider(Provider):
# 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 @@ class ProxyProvider(Provider):
# 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 @@ class ProxyProvider(Provider):
# 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()

View file

@ -867,38 +867,38 @@ class FastMCP(Provider, Generic[LifespanResultT]):
# 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 @@ class FastMCP(Provider, Generic[LifespanResultT]):
)
# 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 @@ class FastMCP(Provider, Generic[LifespanResultT]):
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 @@ class FastMCP(Provider, Generic[LifespanResultT]):
# 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 @@ class FastMCP(Provider, Generic[LifespanResultT]):
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 @@ class FastMCP(Provider, Generic[LifespanResultT]):
)
# 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 @@ class FastMCP(Provider, Generic[LifespanResultT]):
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 @@ class FastMCP(Provider, Generic[LifespanResultT]):
"""
# 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 @@ class FastMCP(Provider, Generic[LifespanResultT]):
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 @@ class FastMCP(Provider, Generic[LifespanResultT]):
)
# 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 @@ class FastMCP(Provider, Generic[LifespanResultT]):
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 @@ class FastMCP(Provider, Generic[LifespanResultT]):
"""
# 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 @@ class FastMCP(Provider, Generic[LifespanResultT]):
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 @@ class FastMCP(Provider, Generic[LifespanResultT]):
)
# 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 @@ class FastMCP(Provider, Generic[LifespanResultT]):
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 @@ class FastMCP(Provider, Generic[LifespanResultT]):
"""
# 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 @@ class FastMCP(Provider, Generic[LifespanResultT]):
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 @@ class FastMCP(Provider, Generic[LifespanResultT]):
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 @@ class FastMCP(Provider, Generic[LifespanResultT]):
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 @@ class FastMCP(Provider, Generic[LifespanResultT]):
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 @@ class FastMCP(Provider, Generic[LifespanResultT]):
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())

View file

@ -24,7 +24,7 @@ class SimpleProvider(Provider):
super().__init__()
self._tools = tools or []
async def list_tools(self) -> list[Tool]:
async def _list_tools(self) -> list[Tool]:
return self._tools

View file

@ -578,7 +578,7 @@ class TestProviderToolTransformations:
# 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 @@ class TestProviderToolTransformations:
)
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 @@ class TestProviderToolTransformations:
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

View file

@ -377,8 +377,8 @@ class TestPromptEnabled:
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 @@ class TestPromptEnabled:
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 @@ class TestPromptEnabled:
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 @@ class TestPromptTags:
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"

View file

@ -159,7 +159,7 @@ class TestTransformReverseLookup:
# 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 @@ class TestTransformReverseLookup:
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 @@ class TestTransformReverseLookup:
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 @@ class TestTransformReverseLookup:
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

View file

@ -48,11 +48,11 @@ class SimpleToolProvider(Provider):
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 @@ class ListOnlyProvider(Provider):
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 @@ class TestProviderExecutionMethods:
"""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 @@ class TestProviderExecutionMethods:
"""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 @@ class TestProviderExecutionMethods:
"""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}!",

View file

@ -483,13 +483,13 @@ class TestVersionFilter:
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 @@ class TestVersionFilter:
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 @@ class TestVersionFilter:
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 @@ class TestMountedVersionFiltering:
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 @@ class TestMountedRangeFiltering:
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 @@ class TestMountedRangeFiltering:
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