Internal refactor of MCP handlers (#2005)

This commit is contained in:
Jeremiah Lowin 2025-10-05 08:45:10 -04:00 committed by GitHub
commit 91af3cd2ae
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 247 additions and 192 deletions

View file

@ -104,7 +104,7 @@ async def get_server_details():
print(f" - Imported from news app: {news_resources}")
# Let's try to access resources using the prefixed URI
weather_data = await app._mcp_read_resource(uri="weather://weather/forecast")
weather_data = await app._read_resource_mcp(uri="weather://weather/forecast")
print(f"\nWeather data from prefixed URI: {weather_data}")

View file

@ -21,7 +21,7 @@ def get_example_data() -> dict:
async def example_usage():
result = await server._mcp_call_tool("get_example_data", {})
result = await server._call_tool_mcp("get_example_data", {})
print("Tool Result:")
print(result)
print("This is an example of using a custom serializer with FastMCP.")

View file

@ -46,21 +46,23 @@ class PromptManager:
"""Adds a mounted server as a source for prompts."""
self._mounted_servers.append(server)
async def _load_prompts(self, *, via_server: bool = False) -> dict[str, Prompt]:
async def _load_prompts(
self, *, apply_filtering: bool = False
) -> dict[str, Prompt]:
"""
The single, consolidated recursive method for fetching prompts. The 'via_server'
The single, consolidated recursive method for fetching prompts. The 'apply_filtering'
parameter determines the communication path.
- via_server=False: Manager-to-manager path for complete, unfiltered inventory
- via_server=True: Server-to-server path for filtered MCP requests
- apply_filtering=False: Manager-to-manager path for complete, unfiltered inventory
- apply_filtering=True: Server-to-server path for filtered MCP requests
"""
all_prompts: dict[str, Prompt] = {}
for mounted in self._mounted_servers:
try:
if via_server:
if apply_filtering:
# Use the server-to-server filtered path
child_results = await mounted.server._list_prompts()
child_results = await mounted.server._list_prompts_middleware()
else:
# Use the manager-to-manager unfiltered path
child_results = await mounted.server._prompt_manager.list_prompts()
@ -104,13 +106,13 @@ class PromptManager:
"""
Gets the complete, unfiltered inventory of all prompts.
"""
return await self._load_prompts(via_server=False)
return await self._load_prompts(apply_filtering=False)
async def list_prompts(self) -> list[Prompt]:
"""
Lists all prompts, applying protocol filtering.
"""
prompts_dict = await self._load_prompts(via_server=True)
prompts_dict = await self._load_prompts(apply_filtering=True)
return list(prompts_dict.values())
def add_prompt_from_fn(
@ -196,7 +198,9 @@ class PromptManager:
else:
continue
try:
return await mounted.server._get_prompt(prompt_key, arguments)
return await mounted.server._get_prompt_middleware(
prompt_key, arguments
)
except NotFoundError:
continue

View file

@ -63,27 +63,31 @@ class ResourceManager:
async def get_resources(self) -> dict[str, Resource]:
"""Get all registered resources, keyed by URI."""
return await self._load_resources(via_server=False)
return await self._load_resources(apply_filtering=False)
async def get_resource_templates(self) -> dict[str, ResourceTemplate]:
"""Get all registered templates, keyed by URI template."""
return await self._load_resource_templates(via_server=False)
return await self._load_resource_templates(apply_filtering=False)
async def _load_resources(self, *, via_server: bool = False) -> dict[str, Resource]:
async def _load_resources(
self, *, apply_filtering: bool = False
) -> dict[str, Resource]:
"""
The single, consolidated recursive method for fetching resources. The 'via_server'
The single, consolidated recursive method for fetching resources. The 'apply_filtering'
parameter determines the communication path.
- via_server=False: Manager-to-manager path for complete, unfiltered inventory
- via_server=True: Server-to-server path for filtered MCP requests
- apply_filtering=False: Manager-to-manager path for complete, unfiltered inventory
- apply_filtering=True: Server-to-server path for filtered MCP requests
"""
all_resources: dict[str, Resource] = {}
for mounted in self._mounted_servers:
try:
if via_server:
if apply_filtering:
# Use the server-to-server filtered path
child_resources_list = await mounted.server._list_resources()
child_resources_list = (
await mounted.server._list_resources_middleware()
)
child_resources = {
resource.key: resource for resource in child_resources_list
}
@ -123,22 +127,24 @@ class ResourceManager:
return all_resources
async def _load_resource_templates(
self, *, via_server: bool = False
self, *, apply_filtering: bool = False
) -> dict[str, ResourceTemplate]:
"""
The single, consolidated recursive method for fetching templates. The 'via_server'
The single, consolidated recursive method for fetching templates. The 'apply_filtering'
parameter determines the communication path.
- via_server=False: Manager-to-manager path for complete, unfiltered inventory
- via_server=True: Server-to-server path for filtered MCP requests
- apply_filtering=False: Manager-to-manager path for complete, unfiltered inventory
- apply_filtering=True: Server-to-server path for filtered MCP requests
"""
all_templates: dict[str, ResourceTemplate] = {}
for mounted in self._mounted_servers:
try:
if via_server:
if apply_filtering:
# Use the server-to-server filtered path
child_templates = await mounted.server._list_resource_templates()
child_templates = (
await mounted.server._list_resource_templates_middleware()
)
else:
# Use the manager-to-manager unfiltered path
child_templates = (
@ -179,14 +185,14 @@ class ResourceManager:
"""
Lists all resources, applying protocol filtering.
"""
resources_dict = await self._load_resources(via_server=True)
resources_dict = await self._load_resources(apply_filtering=True)
return list(resources_dict.values())
async def list_resource_templates(self) -> list[ResourceTemplate]:
"""
Lists all templates, applying protocol filtering.
"""
templates_dict = await self._load_resource_templates(via_server=True)
templates_dict = await self._load_resource_templates(apply_filtering=True)
return list(templates_dict.values())
def add_resource_or_template_from_fn(
@ -492,7 +498,7 @@ class ResourceManager:
continue
try:
result = await mounted.server._read_resource(key)
result = await mounted.server._read_resource_middleware(key)
return result[0].content
except NotFoundError:
continue

View file

@ -205,7 +205,7 @@ class Context:
"""
if self.fastmcp is None:
raise ValueError("Context is not available outside of a request")
return await self.fastmcp._mcp_read_resource(uri)
return await self.fastmcp._read_resource_mcp(uri)
async def log(
self,

View file

@ -386,13 +386,13 @@ class FastMCP(Generic[LifespanResultT]):
def _setup_handlers(self) -> None:
"""Set up core MCP protocol handlers."""
self._mcp_server.list_tools()(self._mcp_list_tools)
self._mcp_server.list_resources()(self._mcp_list_resources)
self._mcp_server.list_resource_templates()(self._mcp_list_resource_templates)
self._mcp_server.list_prompts()(self._mcp_list_prompts)
self._mcp_server.call_tool()(self._mcp_call_tool)
self._mcp_server.read_resource()(self._mcp_read_resource)
self._mcp_server.get_prompt()(self._mcp_get_prompt)
self._mcp_server.list_tools()(self._list_tools_mcp)
self._mcp_server.list_resources()(self._list_resources_mcp)
self._mcp_server.list_resource_templates()(self._list_resource_templates_mcp)
self._mcp_server.list_prompts()(self._list_prompts_mcp)
self._mcp_server.call_tool()(self._call_tool_mcp)
self._mcp_server.read_resource()(self._read_resource_mcp)
self._mcp_server.get_prompt()(self._get_prompt_mcp)
async def _apply_middleware(
self,
@ -519,11 +519,15 @@ class FastMCP(Generic[LifespanResultT]):
return routes
async def _mcp_list_tools(self) -> list[MCPTool]:
async def _list_tools_mcp(self) -> list[MCPTool]:
"""
List all available tools, in the format expected by the low-level MCP
server.
"""
logger.debug(f"[{self.name}] Handler called: list_tools")
async with fastmcp.server.context.Context(fastmcp=self):
tools = await self._list_tools()
tools = await self._list_tools_middleware()
return [
tool.to_mcp_tool(
name=tool.key,
@ -532,24 +536,11 @@ class FastMCP(Generic[LifespanResultT]):
for tool in tools
]
async def _list_tools(self) -> list[Tool]:
async def _list_tools_middleware(self) -> list[Tool]:
"""
List all available tools, in the format expected by the low-level MCP
server.
List all available tools, applying MCP middleware.
"""
async def _handler(
context: MiddlewareContext[mcp.types.ListToolsRequest],
) -> list[Tool]:
tools = await self._tool_manager.list_tools() # type: ignore[reportPrivateUsage]
mcp_tools: list[Tool] = []
for tool in tools:
if self._should_enable_component(tool):
mcp_tools.append(tool)
return mcp_tools
async with fastmcp.server.context.Context(fastmcp=self) as fastmcp_ctx:
# Create the middleware context.
mw_context = MiddlewareContext(
@ -561,13 +552,33 @@ class FastMCP(Generic[LifespanResultT]):
)
# Apply the middleware chain.
return await self._apply_middleware(mw_context, _handler)
return await self._apply_middleware(mw_context, self._list_tools)
async def _mcp_list_resources(self) -> list[MCPResource]:
async def _list_tools(
self,
context: MiddlewareContext[mcp.types.ListToolsRequest],
) -> list[Tool]:
"""
List all available tools
"""
tools = await self._tool_manager.list_tools() # type: ignore[reportPrivateUsage]
mcp_tools: list[Tool] = []
for tool in tools:
if self._should_enable_component(tool):
mcp_tools.append(tool)
return mcp_tools
async def _list_resources_mcp(self) -> list[MCPResource]:
"""
List all available resources, in the format expected by the low-level MCP
server.
"""
logger.debug(f"[{self.name}] Handler called: list_resources")
async with fastmcp.server.context.Context(fastmcp=self):
resources = await self._list_resources()
resources = await self._list_resources_middleware()
return [
resource.to_mcp_resource(
uri=resource.key,
@ -576,25 +587,11 @@ class FastMCP(Generic[LifespanResultT]):
for resource in resources
]
async def _list_resources(self) -> list[Resource]:
async def _list_resources_middleware(self) -> list[Resource]:
"""
List all available resources, in the format expected by the low-level MCP
server.
List all available resources, applying MCP middleware.
"""
async def _handler(
context: MiddlewareContext[dict[str, Any]],
) -> list[Resource]:
resources = await self._resource_manager.list_resources() # type: ignore[reportPrivateUsage]
mcp_resources: list[Resource] = []
for resource in resources:
if self._should_enable_component(resource):
mcp_resources.append(resource)
return mcp_resources
async with fastmcp.server.context.Context(fastmcp=self) as fastmcp_ctx:
# Create the middleware context.
mw_context = MiddlewareContext(
@ -606,13 +603,33 @@ class FastMCP(Generic[LifespanResultT]):
)
# Apply the middleware chain.
return await self._apply_middleware(mw_context, _handler)
return await self._apply_middleware(mw_context, self._list_resources)
async def _mcp_list_resource_templates(self) -> list[MCPResourceTemplate]:
async def _list_resources(
self,
context: MiddlewareContext[dict[str, Any]],
) -> list[Resource]:
"""
List all available resources
"""
resources = await self._resource_manager.list_resources() # type: ignore[reportPrivateUsage]
mcp_resources: list[Resource] = []
for resource in resources:
if self._should_enable_component(resource):
mcp_resources.append(resource)
return mcp_resources
async def _list_resource_templates_mcp(self) -> list[MCPResourceTemplate]:
"""
List all available resource templates, in the format expected by the low-level MCP
server.
"""
logger.debug(f"[{self.name}] Handler called: list_resource_templates")
async with fastmcp.server.context.Context(fastmcp=self):
templates = await self._list_resource_templates()
templates = await self._list_resource_templates_middleware()
return [
template.to_mcp_template(
uriTemplate=template.key,
@ -621,25 +638,12 @@ class FastMCP(Generic[LifespanResultT]):
for template in templates
]
async def _list_resource_templates(self) -> list[ResourceTemplate]:
async def _list_resource_templates_middleware(self) -> list[ResourceTemplate]:
"""
List all available resource templates, in the format expected by the low-level MCP
server.
List all available resource templates, applying MCP middleware.
"""
async def _handler(
context: MiddlewareContext[dict[str, Any]],
) -> list[ResourceTemplate]:
templates = await self._resource_manager.list_resource_templates()
mcp_templates: list[ResourceTemplate] = []
for template in templates:
if self._should_enable_component(template):
mcp_templates.append(template)
return mcp_templates
async with fastmcp.server.context.Context(fastmcp=self) as fastmcp_ctx:
# Create the middleware context.
mw_context = MiddlewareContext(
@ -651,13 +655,35 @@ class FastMCP(Generic[LifespanResultT]):
)
# Apply the middleware chain.
return await self._apply_middleware(mw_context, _handler)
return await self._apply_middleware(
mw_context, self._list_resource_templates
)
async def _mcp_list_prompts(self) -> list[MCPPrompt]:
async def _list_resource_templates(
self,
context: MiddlewareContext[dict[str, Any]],
) -> list[ResourceTemplate]:
"""
List all available resource templates
"""
templates = await self._resource_manager.list_resource_templates() # type: ignore[reportPrivateUsage]
mcp_templates: list[ResourceTemplate] = []
for template in templates:
if self._should_enable_component(template):
mcp_templates.append(template)
return mcp_templates
async def _list_prompts_mcp(self) -> list[MCPPrompt]:
"""
List all available prompts, in the format expected by the low-level MCP
server.
"""
logger.debug(f"[{self.name}] Handler called: list_prompts")
async with fastmcp.server.context.Context(fastmcp=self):
prompts = await self._list_prompts()
prompts = await self._list_prompts_middleware()
return [
prompt.to_mcp_prompt(
name=prompt.key,
@ -666,25 +692,12 @@ class FastMCP(Generic[LifespanResultT]):
for prompt in prompts
]
async def _list_prompts(self) -> list[Prompt]:
async def _list_prompts_middleware(self) -> list[Prompt]:
"""
List all available prompts, in the format expected by the low-level MCP
server.
List all available prompts, applying MCP middleware.
"""
async def _handler(
context: MiddlewareContext[mcp.types.ListPromptsRequest],
) -> list[Prompt]:
prompts = await self._prompt_manager.list_prompts() # type: ignore[reportPrivateUsage]
mcp_prompts: list[Prompt] = []
for prompt in prompts:
if self._should_enable_component(prompt):
mcp_prompts.append(prompt)
return mcp_prompts
async with fastmcp.server.context.Context(fastmcp=self) as fastmcp_ctx:
# Create the middleware context.
mw_context = MiddlewareContext(
@ -696,9 +709,25 @@ class FastMCP(Generic[LifespanResultT]):
)
# Apply the middleware chain.
return await self._apply_middleware(mw_context, _handler)
return await self._apply_middleware(mw_context, self._list_prompts)
async def _mcp_call_tool(
async def _list_prompts(
self,
context: MiddlewareContext[mcp.types.ListPromptsRequest],
) -> list[Prompt]:
"""
List all available prompts
"""
prompts = await self._prompt_manager.list_prompts() # type: ignore[reportPrivateUsage]
mcp_prompts: list[Prompt] = []
for prompt in prompts:
if self._should_enable_component(prompt):
mcp_prompts.append(prompt)
return mcp_prompts
async def _call_tool_mcp(
self, key: str, arguments: dict[str, Any]
) -> list[ContentBlock] | tuple[list[ContentBlock], dict[str, Any]]:
"""
@ -719,29 +748,22 @@ class FastMCP(Generic[LifespanResultT]):
async with fastmcp.server.context.Context(fastmcp=self):
try:
result = await self._call_tool(key, arguments)
result = await self._call_tool_middleware(key, arguments)
return result.to_mcp_result()
except DisabledError:
raise NotFoundError(f"Unknown tool: {key}")
except NotFoundError:
raise NotFoundError(f"Unknown tool: {key}")
async def _call_tool(self, key: str, arguments: dict[str, Any]) -> ToolResult:
async def _call_tool_middleware(
self,
key: str,
arguments: dict[str, Any],
) -> ToolResult:
"""
Applies this server's middleware and delegates the filtered call to the manager.
"""
async def _handler(
context: MiddlewareContext[mcp.types.CallToolRequestParams],
) -> ToolResult:
tool = await self._tool_manager.get_tool(context.message.name)
if not self._should_enable_component(tool):
raise NotFoundError(f"Unknown tool: {context.message.name!r}")
return await self._tool_manager.call_tool(
key=context.message.name, arguments=context.message.arguments or {}
)
mw_context = MiddlewareContext[CallToolRequestParams](
message=mcp.types.CallToolRequestParams(name=key, arguments=arguments),
source="client",
@ -749,9 +771,24 @@ class FastMCP(Generic[LifespanResultT]):
method="tools/call",
fastmcp_context=fastmcp.server.dependencies.get_context(),
)
return await self._apply_middleware(mw_context, _handler)
return await self._apply_middleware(mw_context, self._call_tool)
async def _mcp_read_resource(self, uri: AnyUrl | str) -> list[ReadResourceContents]:
async def _call_tool(
self,
context: MiddlewareContext[mcp.types.CallToolRequestParams],
) -> ToolResult:
"""
Call a tool
"""
tool = await self._tool_manager.get_tool(context.message.name)
if not self._should_enable_component(tool):
raise NotFoundError(f"Unknown tool: {context.message.name!r}")
return await self._tool_manager.call_tool(
key=context.message.name, arguments=context.message.arguments or {}
)
async def _read_resource_mcp(self, uri: AnyUrl | str) -> list[ReadResourceContents]:
"""
Handle MCP 'readResource' requests.
@ -761,7 +798,7 @@ class FastMCP(Generic[LifespanResultT]):
async with fastmcp.server.context.Context(fastmcp=self):
try:
return await self._read_resource(uri)
return await self._read_resource_middleware(uri)
except DisabledError:
# convert to NotFoundError to avoid leaking resource presence
raise NotFoundError(f"Unknown resource: {str(uri)!r}")
@ -769,26 +806,14 @@ class FastMCP(Generic[LifespanResultT]):
# standardize NotFound message
raise NotFoundError(f"Unknown resource: {str(uri)!r}")
async def _read_resource(self, uri: AnyUrl | str) -> list[ReadResourceContents]:
async def _read_resource_middleware(
self,
uri: AnyUrl | str,
) -> list[ReadResourceContents]:
"""
Applies this server's middleware and delegates the filtered call to the manager.
"""
async def _handler(
context: MiddlewareContext[mcp.types.ReadResourceRequestParams],
) -> list[ReadResourceContents]:
resource = await self._resource_manager.get_resource(context.message.uri)
if not self._should_enable_component(resource):
raise NotFoundError(f"Unknown resource: {str(context.message.uri)!r}")
content = await self._resource_manager.read_resource(context.message.uri)
return [
ReadResourceContents(
content=content,
mime_type=resource.mime_type,
)
]
# Convert string URI to AnyUrl if needed
if isinstance(uri, str):
uri_param = AnyUrl(uri)
@ -802,9 +827,28 @@ class FastMCP(Generic[LifespanResultT]):
method="resources/read",
fastmcp_context=fastmcp.server.dependencies.get_context(),
)
return await self._apply_middleware(mw_context, _handler)
return await self._apply_middleware(mw_context, self._read_resource)
async def _mcp_get_prompt(
async def _read_resource(
self,
context: MiddlewareContext[mcp.types.ReadResourceRequestParams],
) -> list[ReadResourceContents]:
"""
Read a resource
"""
resource = await self._resource_manager.get_resource(context.message.uri)
if not self._should_enable_component(resource):
raise NotFoundError(f"Unknown resource: {str(context.message.uri)!r}")
content = await self._resource_manager.read_resource(context.message.uri)
return [
ReadResourceContents(
content=content,
mime_type=resource.mime_type,
)
]
async def _get_prompt_mcp(
self, name: str, arguments: dict[str, Any] | None = None
) -> GetPromptResult:
"""
@ -820,7 +864,7 @@ class FastMCP(Generic[LifespanResultT]):
async with fastmcp.server.context.Context(fastmcp=self):
try:
return await self._get_prompt(name, arguments)
return await self._get_prompt_middleware(name, arguments)
except DisabledError:
# convert to NotFoundError to avoid leaking prompt presence
raise NotFoundError(f"Unknown prompt: {name}")
@ -828,24 +872,13 @@ class FastMCP(Generic[LifespanResultT]):
# standardize NotFound message
raise NotFoundError(f"Unknown prompt: {name}")
async def _get_prompt(
async def _get_prompt_middleware(
self, name: str, arguments: dict[str, Any] | None = None
) -> GetPromptResult:
"""
Applies this server's middleware and delegates the filtered call to the manager.
"""
async def _handler(
context: MiddlewareContext[mcp.types.GetPromptRequestParams],
) -> GetPromptResult:
prompt = await self._prompt_manager.get_prompt(context.message.name)
if not self._should_enable_component(prompt):
raise NotFoundError(f"Unknown prompt: {context.message.name!r}")
return await self._prompt_manager.render_prompt(
name=context.message.name, arguments=context.message.arguments
)
mw_context = MiddlewareContext(
message=mcp.types.GetPromptRequestParams(name=name, arguments=arguments),
source="client",
@ -853,7 +886,19 @@ class FastMCP(Generic[LifespanResultT]):
method="prompts/get",
fastmcp_context=fastmcp.server.dependencies.get_context(),
)
return await self._apply_middleware(mw_context, _handler)
return await self._apply_middleware(mw_context, self._get_prompt)
async def _get_prompt(
self,
context: MiddlewareContext[mcp.types.GetPromptRequestParams],
) -> GetPromptResult:
prompt = await self._prompt_manager.get_prompt(context.message.name)
if not self._should_enable_component(prompt):
raise NotFoundError(f"Unknown prompt: {context.message.name!r}")
return await self._prompt_manager.render_prompt(
name=context.message.name, arguments=context.message.arguments
)
def add_tool(self, tool: Tool) -> Tool:
"""Add a tool to the server.

View file

@ -52,21 +52,21 @@ class ToolManager:
"""Adds a mounted server as a source for tools."""
self._mounted_servers.append(server)
async def _load_tools(self, *, via_server: bool = False) -> dict[str, Tool]:
async def _load_tools(self, *, apply_filtering: bool = False) -> dict[str, Tool]:
"""
The single, consolidated recursive method for fetching tools. The 'via_server'
The single, consolidated recursive method for fetching tools. The 'apply_filtering'
parameter determines the communication path.
- via_server=False: Manager-to-manager path for complete, unfiltered inventory
- via_server=True: Server-to-server path for filtered MCP requests
- apply_filtering=False: Manager-to-manager path for complete, unfiltered inventory
- apply_filtering=True: Server-to-server path for filtered MCP requests
"""
all_tools: dict[str, Tool] = {}
for mounted in self._mounted_servers:
try:
if via_server:
if apply_filtering:
# Use the server-to-server filtered path
child_results = await mounted.server._list_tools()
child_results = await mounted.server._list_tools_middleware()
else:
# Use the manager-to-manager unfiltered path
child_results = await mounted.server._tool_manager.list_tools()
@ -116,13 +116,13 @@ class ToolManager:
"""
Gets the complete, unfiltered inventory of all tools.
"""
return await self._load_tools(via_server=False)
return await self._load_tools(apply_filtering=False)
async def list_tools(self) -> list[Tool]:
"""
Lists all tools, applying protocol filtering.
"""
tools_dict = await self._load_tools(via_server=True)
tools_dict = await self._load_tools(apply_filtering=True)
return list(tools_dict.values())
@property
@ -247,7 +247,7 @@ class ToolManager:
else:
continue
try:
return await mounted.server._call_tool(tool_key, arguments)
return await mounted.server._call_tool_middleware(tool_key, arguments)
except NotFoundError:
continue

View file

@ -19,7 +19,7 @@ class TestCustomRoutes:
return server
def test_custom_routes_via_server_http_app(self, server_with_custom_route):
def test_custom_routes_apply_filtering_http_app(self, server_with_custom_route):
"""Test that custom routes are included when using server.http_app()."""
# Get the app via server.http_app()
app = server_with_custom_route.http_app()

View file

@ -163,7 +163,7 @@ async def test_integration_array_path_parameter(array_path_spec, mock_client):
mcp = FastMCP.from_openapi(array_path_spec, client=mock_client)
# Call the tool with a single value
await mcp._mcp_call_tool("test_operation", {"days": ["monday"]})
await mcp._call_tool_mcp("test_operation", {"days": ["monday"]})
# Check the request was made correctly
mock_client.request.assert_called_with(
@ -177,7 +177,7 @@ async def test_integration_array_path_parameter(array_path_spec, mock_client):
mock_client.request.reset_mock()
# Call the tool with multiple values
await mcp._mcp_call_tool("test_operation", {"days": ["monday", "tuesday"]})
await mcp._call_tool_mcp("test_operation", {"days": ["monday", "tuesday"]})
# Check the request was made correctly
mock_client.request.assert_called_with(

View file

@ -173,15 +173,15 @@ class TestTools:
async def test_list_tools_same_as_original(self, fastmcp_server, proxy_server):
assert (
await proxy_server._mcp_list_tools()
== await fastmcp_server._mcp_list_tools()
await proxy_server._list_tools_mcp()
== await fastmcp_server._list_tools_mcp()
)
async def test_call_tool_result_same_as_original(
self, fastmcp_server: FastMCP, proxy_server: FastMCPProxy
):
result = await fastmcp_server._mcp_call_tool("greet", {"name": "Alice"})
proxy_result = await proxy_server._mcp_call_tool("greet", {"name": "Alice"})
result = await fastmcp_server._call_tool_mcp("greet", {"name": "Alice"})
proxy_result = await proxy_server._call_tool_mcp("greet", {"name": "Alice"})
assert result == proxy_result
@ -267,8 +267,8 @@ class TestResources:
async def test_list_resources_same_as_original(self, fastmcp_server, proxy_server):
assert (
await proxy_server._mcp_list_resources()
== await fastmcp_server._mcp_list_resources()
await proxy_server._list_resources_mcp()
== await fastmcp_server._list_resources_mcp()
)
async def test_read_resource(self, proxy_server: FastMCPProxy):
@ -367,8 +367,8 @@ class TestResourceTemplates:
async def test_list_resource_templates_same_as_original(
self, fastmcp_server, proxy_server
):
result = await fastmcp_server._mcp_list_resource_templates()
proxy_result = await proxy_server._mcp_list_resource_templates()
result = await fastmcp_server._list_resource_templates_mcp()
proxy_result = await proxy_server._list_resource_templates_mcp()
assert proxy_result == result
@pytest.mark.parametrize("id", [1, 2, 3])

View file

@ -74,7 +74,7 @@ def tools(mcp: FastMCP, test_dir: Path) -> FastMCP:
async def test_list_resources(mcp: FastMCP):
resources = await mcp._mcp_list_resources()
resources = await mcp._list_resources_mcp()
assert len(resources) == 4
assert [str(r.uri) for r in resources] == [
@ -86,7 +86,7 @@ async def test_list_resources(mcp: FastMCP):
async def test_read_resource_dir(mcp: FastMCP):
res_iter = await mcp._mcp_read_resource("dir://test_dir")
res_iter = await mcp._read_resource_mcp("dir://test_dir")
res_list = list(res_iter)
assert len(res_list) == 1
res = res_list[0]
@ -102,7 +102,7 @@ async def test_read_resource_dir(mcp: FastMCP):
async def test_read_resource_file(mcp: FastMCP):
res_iter = await mcp._mcp_read_resource("file://test_dir/example.py")
res_iter = await mcp._read_resource_mcp("file://test_dir/example.py")
res_list = list(res_iter)
assert len(res_list) == 1
res = res_list[0]
@ -110,17 +110,17 @@ async def test_read_resource_file(mcp: FastMCP):
async def test_delete_file(mcp: FastMCP, test_dir: Path):
await mcp._mcp_call_tool(
await mcp._call_tool_mcp(
"delete_file", arguments=dict(path=str(test_dir / "example.py"))
)
assert not (test_dir / "example.py").exists()
async def test_delete_file_and_check_resources(mcp: FastMCP, test_dir: Path):
await mcp._mcp_call_tool(
await mcp._call_tool_mcp(
"delete_file", arguments=dict(path=str(test_dir / "example.py"))
)
res_iter = await mcp._mcp_read_resource("file://test_dir/example.py")
res_iter = await mcp._read_resource_mcp("file://test_dir/example.py")
res_list = list(res_iter)
assert len(res_list) == 1
res = res_list[0]

View file

@ -76,7 +76,7 @@ class TestTools:
def fn(x: int) -> int:
return x + 1
mcp_tools = await mcp._mcp_list_tools()
mcp_tools = await mcp._list_tools_mcp()
assert len(mcp_tools) == 1
assert mcp_tools[0].name == "fn"
@ -89,7 +89,7 @@ class TestTools:
def fn(x: int) -> int:
return x + 1
mcp_tools = await mcp._mcp_list_tools()
mcp_tools = await mcp._list_tools_mcp()
assert len(mcp_tools) == 1
assert mcp_tools[0].name == "custom_name"
@ -110,7 +110,7 @@ class TestTools:
assert "adder" not in mcp_tools
with pytest.raises(NotFoundError, match="Unknown tool: adder"):
await mcp._mcp_call_tool("adder", {"a": 1, "b": 2})
await mcp._call_tool_mcp("adder", {"a": 1, "b": 2})
async def test_add_tool_at_init(self):
def f(x: int) -> int:
@ -136,7 +136,7 @@ class TestToolDecorator:
mcp = FastMCP()
with pytest.raises(NotFoundError, match="Unknown tool: add"):
await mcp._mcp_call_tool("add", {"x": 1, "y": 2})
await mcp._call_tool_mcp("add", {"x": 1, "y": 2})
async def test_tool_decorator(self):
mcp = FastMCP()
@ -185,7 +185,7 @@ class TestToolDecorator:
def add(x: int, y: int) -> int:
return x + y
tools = await mcp._mcp_list_tools()
tools = await mcp._list_tools_mcp()
assert len(tools) == 1
tool = tools[0]
assert tool.description == "Add two numbers"

View file

@ -47,7 +47,7 @@ async def test_tool_annotations_in_mcp_protocol():
return message
# Check via MCP protocol
mcp_tools = await mcp._mcp_list_tools()
mcp_tools = await mcp._list_tools_mcp()
assert len(mcp_tools) == 1
assert mcp_tools[0].annotations is not None
assert mcp_tools[0].annotations.title == "Echo Tool"

View file

@ -29,14 +29,14 @@ async def test_transformed_tool_filtering():
"""Echo back the message provided."""
return message
tools = list(await mcp._list_tools())
tools = list(await mcp._list_tools_middleware())
assert len(tools) == 0
mcp.add_tool_transformation(
"echo", ToolTransformConfig(name="echo_transformed", tags={"enabled_tools"})
)
tools = list(await mcp._list_tools())
tools = list(await mcp._list_tools_middleware())
assert len(tools) == 1