Ensure proxy components forward meta dicts (#1282)

This commit is contained in:
Jeremiah Lowin 2025-07-29 07:34:41 -07:00 committed by GitHub
commit 0adb317361
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 47 additions and 4 deletions

View file

@ -255,6 +255,8 @@ class ProxyTool(Tool, MirroredComponent):
parameters=mcp_tool.inputSchema,
annotations=mcp_tool.annotations,
output_schema=mcp_tool.outputSchema,
meta=mcp_tool.meta,
tags=(mcp_tool.meta or {}).get("tags", []),
_mirrored=True,
)
@ -309,6 +311,8 @@ class ProxyResource(Resource, MirroredComponent):
name=mcp_resource.name,
description=mcp_resource.description,
mime_type=mcp_resource.mimeType or "text/plain",
meta=mcp_resource.meta,
tags=(mcp_resource.meta or {}).get("tags", []),
_mirrored=True,
)
@ -348,6 +352,8 @@ class ProxyTemplate(ResourceTemplate, MirroredComponent):
description=mcp_template.description,
mime_type=mcp_template.mimeType or "text/plain",
parameters={}, # Remote templates don't have local parameters
meta=mcp_template.meta,
tags=(mcp_template.meta or {}).get("tags", []),
_mirrored=True,
)
@ -380,6 +386,8 @@ class ProxyTemplate(ResourceTemplate, MirroredComponent):
name=self.name,
description=self.description,
mime_type=result[0].mimeType,
meta=self.meta,
tags=(self.meta or {}).get("tags", []),
_value=value,
)
@ -413,6 +421,8 @@ class ProxyPrompt(Prompt, MirroredComponent):
name=mcp_prompt.name,
description=mcp_prompt.description,
arguments=arguments,
meta=mcp_prompt.meta,
tags=(mcp_prompt.meta or {}).get("tags", []),
_mirrored=True,
)

View file

@ -18,6 +18,10 @@ from fastmcp.server.proxy import ProxyClient
def fastmcp_server():
mcp = FastMCP("TestServer")
@mcp.tool(tags={"echo"})
def echo(message: str) -> str:
return f"echo: {message}"
@mcp.tool
async def list_roots(context: Context) -> list[str]:
roots = await context.list_roots()
@ -80,6 +84,15 @@ async def proxy_server(fastmcp_server: FastMCP):
class TestProxyClient:
async def test_forward_tool_meta(self, proxy_server: FastMCP):
"""
Test that the proxy client correctly forwards the `echo` tool meta.
"""
async with Client(proxy_server) as client:
tools = await client.list_tools()
echo_tool = next(t for t in tools if t.name == "echo")
assert echo_tool.meta == {"tags": ["echo"]}
async def test_forward_error_response(self, proxy_server: FastMCP):
"""
Test that the proxy client correctly forwards an error response.

View file

@ -29,7 +29,7 @@ def fastmcp_server():
# --- Tools ---
@server.tool
@server.tool(tags={"greet"})
def greet(name: str) -> str:
"""Greet someone by name."""
return f"Hello, {name}!"
@ -50,7 +50,7 @@ def fastmcp_server():
# --- Resources ---
@server.resource(uri="resource://wave")
@server.resource(uri="resource://wave", tags={"wave"})
def wave() -> str:
return "👋"
@ -58,13 +58,13 @@ def fastmcp_server():
async def get_users() -> list[dict[str, Any]]:
return USERS
@server.resource(uri="data://user/{user_id}")
@server.resource(uri="data://user/{user_id}", tags={"users"})
async def get_user(user_id: str) -> dict[str, Any] | None:
return next((user for user in USERS if user["id"] == user_id), None)
# --- Prompts ---
@server.prompt
@server.prompt(tags={"welcome"})
def welcome(name: str) -> str:
return f"Welcome to FastMCP, {name}!"
@ -121,6 +121,11 @@ class TestTools:
assert "error_tool" in tools
assert "tool_without_description" in tools
async def test_get_tools_meta(self, proxy_server):
tools = await proxy_server.get_tools()
greet_tool = tools["greet"]
assert greet_tool.meta == {"tags": ["greet"]}
async def test_get_transformed_tools(
self, fastmcp_server: FastMCP, proxy_server: FastMCPProxy
):
@ -238,6 +243,11 @@ class TestResources:
)
assert [r.name for r in resources.values()] == Contains("get_users", "wave")
async def test_get_resources_meta(self, proxy_server):
resources = await proxy_server.get_resources()
wave_resource = resources["resource://wave"]
assert wave_resource.meta == {"tags": ["wave"]}
async def test_list_resources_same_as_original(self, fastmcp_server, proxy_server):
assert (
await proxy_server._mcp_list_resources()
@ -332,6 +342,11 @@ class TestResourceTemplates:
templates = await proxy_server.get_resource_templates()
assert [t.name for t in templates.values()] == Contains("get_user")
async def test_get_resource_templates_meta(self, proxy_server):
templates = await proxy_server.get_resource_templates()
get_user_template = templates["data://user/{user_id}"]
assert get_user_template.meta == {"tags": ["users"]}
async def test_list_resource_templates_same_as_original(
self, fastmcp_server, proxy_server
):
@ -431,6 +446,11 @@ class TestPrompts:
prompts = await proxy_server.get_prompts()
assert [p.name for p in prompts.values()] == Contains("welcome")
async def test_get_prompts_meta(self, proxy_server):
prompts = await proxy_server.get_prompts()
welcome_prompt = prompts["welcome"]
assert welcome_prompt.meta == {"tags": ["welcome"]}
async def test_list_prompts_same_as_original(self, fastmcp_server, proxy_server):
async with Client(fastmcp_server) as client:
result = await client.list_prompts()