Jeremiah Lowin commited on
Commit
6cdad57
·
unverified ·
1 Parent(s): 4394198

Ensure proxy components forward meta dicts (#1282)

Browse files
src/fastmcp/server/proxy.py CHANGED
@@ -255,6 +255,8 @@ class ProxyTool(Tool, MirroredComponent):
255
  parameters=mcp_tool.inputSchema,
256
  annotations=mcp_tool.annotations,
257
  output_schema=mcp_tool.outputSchema,
 
 
258
  _mirrored=True,
259
  )
260
 
@@ -309,6 +311,8 @@ class ProxyResource(Resource, MirroredComponent):
309
  name=mcp_resource.name,
310
  description=mcp_resource.description,
311
  mime_type=mcp_resource.mimeType or "text/plain",
 
 
312
  _mirrored=True,
313
  )
314
 
@@ -348,6 +352,8 @@ class ProxyTemplate(ResourceTemplate, MirroredComponent):
348
  description=mcp_template.description,
349
  mime_type=mcp_template.mimeType or "text/plain",
350
  parameters={}, # Remote templates don't have local parameters
 
 
351
  _mirrored=True,
352
  )
353
 
@@ -380,6 +386,8 @@ class ProxyTemplate(ResourceTemplate, MirroredComponent):
380
  name=self.name,
381
  description=self.description,
382
  mime_type=result[0].mimeType,
 
 
383
  _value=value,
384
  )
385
 
@@ -413,6 +421,8 @@ class ProxyPrompt(Prompt, MirroredComponent):
413
  name=mcp_prompt.name,
414
  description=mcp_prompt.description,
415
  arguments=arguments,
 
 
416
  _mirrored=True,
417
  )
418
 
 
255
  parameters=mcp_tool.inputSchema,
256
  annotations=mcp_tool.annotations,
257
  output_schema=mcp_tool.outputSchema,
258
+ meta=mcp_tool.meta,
259
+ tags=(mcp_tool.meta or {}).get("tags", []),
260
  _mirrored=True,
261
  )
262
 
 
311
  name=mcp_resource.name,
312
  description=mcp_resource.description,
313
  mime_type=mcp_resource.mimeType or "text/plain",
314
+ meta=mcp_resource.meta,
315
+ tags=(mcp_resource.meta or {}).get("tags", []),
316
  _mirrored=True,
317
  )
318
 
 
352
  description=mcp_template.description,
353
  mime_type=mcp_template.mimeType or "text/plain",
354
  parameters={}, # Remote templates don't have local parameters
355
+ meta=mcp_template.meta,
356
+ tags=(mcp_template.meta or {}).get("tags", []),
357
  _mirrored=True,
358
  )
359
 
 
386
  name=self.name,
387
  description=self.description,
388
  mime_type=result[0].mimeType,
389
+ meta=self.meta,
390
+ tags=(self.meta or {}).get("tags", []),
391
  _value=value,
392
  )
393
 
 
421
  name=mcp_prompt.name,
422
  description=mcp_prompt.description,
423
  arguments=arguments,
424
+ meta=mcp_prompt.meta,
425
+ tags=(mcp_prompt.meta or {}).get("tags", []),
426
  _mirrored=True,
427
  )
428
 
tests/server/proxy/test_proxy_client.py CHANGED
@@ -18,6 +18,10 @@ from fastmcp.server.proxy import ProxyClient
18
  def fastmcp_server():
19
  mcp = FastMCP("TestServer")
20
 
 
 
 
 
21
  @mcp.tool
22
  async def list_roots(context: Context) -> list[str]:
23
  roots = await context.list_roots()
@@ -80,6 +84,15 @@ async def proxy_server(fastmcp_server: FastMCP):
80
 
81
 
82
  class TestProxyClient:
 
 
 
 
 
 
 
 
 
83
  async def test_forward_error_response(self, proxy_server: FastMCP):
84
  """
85
  Test that the proxy client correctly forwards an error response.
 
18
  def fastmcp_server():
19
  mcp = FastMCP("TestServer")
20
 
21
+ @mcp.tool(tags={"echo"})
22
+ def echo(message: str) -> str:
23
+ return f"echo: {message}"
24
+
25
  @mcp.tool
26
  async def list_roots(context: Context) -> list[str]:
27
  roots = await context.list_roots()
 
84
 
85
 
86
  class TestProxyClient:
87
+ async def test_forward_tool_meta(self, proxy_server: FastMCP):
88
+ """
89
+ Test that the proxy client correctly forwards the `echo` tool meta.
90
+ """
91
+ async with Client(proxy_server) as client:
92
+ tools = await client.list_tools()
93
+ echo_tool = next(t for t in tools if t.name == "echo")
94
+ assert echo_tool.meta == {"tags": ["echo"]}
95
+
96
  async def test_forward_error_response(self, proxy_server: FastMCP):
97
  """
98
  Test that the proxy client correctly forwards an error response.
tests/server/proxy/test_proxy_server.py CHANGED
@@ -29,7 +29,7 @@ def fastmcp_server():
29
 
30
  # --- Tools ---
31
 
32
- @server.tool
33
  def greet(name: str) -> str:
34
  """Greet someone by name."""
35
  return f"Hello, {name}!"
@@ -50,7 +50,7 @@ def fastmcp_server():
50
 
51
  # --- Resources ---
52
 
53
- @server.resource(uri="resource://wave")
54
  def wave() -> str:
55
  return "👋"
56
 
@@ -58,13 +58,13 @@ def fastmcp_server():
58
  async def get_users() -> list[dict[str, Any]]:
59
  return USERS
60
 
61
- @server.resource(uri="data://user/{user_id}")
62
  async def get_user(user_id: str) -> dict[str, Any] | None:
63
  return next((user for user in USERS if user["id"] == user_id), None)
64
 
65
  # --- Prompts ---
66
 
67
- @server.prompt
68
  def welcome(name: str) -> str:
69
  return f"Welcome to FastMCP, {name}!"
70
 
@@ -121,6 +121,11 @@ class TestTools:
121
  assert "error_tool" in tools
122
  assert "tool_without_description" in tools
123
 
 
 
 
 
 
124
  async def test_get_transformed_tools(
125
  self, fastmcp_server: FastMCP, proxy_server: FastMCPProxy
126
  ):
@@ -238,6 +243,11 @@ class TestResources:
238
  )
239
  assert [r.name for r in resources.values()] == Contains("get_users", "wave")
240
 
 
 
 
 
 
241
  async def test_list_resources_same_as_original(self, fastmcp_server, proxy_server):
242
  assert (
243
  await proxy_server._mcp_list_resources()
@@ -332,6 +342,11 @@ class TestResourceTemplates:
332
  templates = await proxy_server.get_resource_templates()
333
  assert [t.name for t in templates.values()] == Contains("get_user")
334
 
 
 
 
 
 
335
  async def test_list_resource_templates_same_as_original(
336
  self, fastmcp_server, proxy_server
337
  ):
@@ -431,6 +446,11 @@ class TestPrompts:
431
  prompts = await proxy_server.get_prompts()
432
  assert [p.name for p in prompts.values()] == Contains("welcome")
433
 
 
 
 
 
 
434
  async def test_list_prompts_same_as_original(self, fastmcp_server, proxy_server):
435
  async with Client(fastmcp_server) as client:
436
  result = await client.list_prompts()
 
29
 
30
  # --- Tools ---
31
 
32
+ @server.tool(tags={"greet"})
33
  def greet(name: str) -> str:
34
  """Greet someone by name."""
35
  return f"Hello, {name}!"
 
50
 
51
  # --- Resources ---
52
 
53
+ @server.resource(uri="resource://wave", tags={"wave"})
54
  def wave() -> str:
55
  return "👋"
56
 
 
58
  async def get_users() -> list[dict[str, Any]]:
59
  return USERS
60
 
61
+ @server.resource(uri="data://user/{user_id}", tags={"users"})
62
  async def get_user(user_id: str) -> dict[str, Any] | None:
63
  return next((user for user in USERS if user["id"] == user_id), None)
64
 
65
  # --- Prompts ---
66
 
67
+ @server.prompt(tags={"welcome"})
68
  def welcome(name: str) -> str:
69
  return f"Welcome to FastMCP, {name}!"
70
 
 
121
  assert "error_tool" in tools
122
  assert "tool_without_description" in tools
123
 
124
+ async def test_get_tools_meta(self, proxy_server):
125
+ tools = await proxy_server.get_tools()
126
+ greet_tool = tools["greet"]
127
+ assert greet_tool.meta == {"tags": ["greet"]}
128
+
129
  async def test_get_transformed_tools(
130
  self, fastmcp_server: FastMCP, proxy_server: FastMCPProxy
131
  ):
 
243
  )
244
  assert [r.name for r in resources.values()] == Contains("get_users", "wave")
245
 
246
+ async def test_get_resources_meta(self, proxy_server):
247
+ resources = await proxy_server.get_resources()
248
+ wave_resource = resources["resource://wave"]
249
+ assert wave_resource.meta == {"tags": ["wave"]}
250
+
251
  async def test_list_resources_same_as_original(self, fastmcp_server, proxy_server):
252
  assert (
253
  await proxy_server._mcp_list_resources()
 
342
  templates = await proxy_server.get_resource_templates()
343
  assert [t.name for t in templates.values()] == Contains("get_user")
344
 
345
+ async def test_get_resource_templates_meta(self, proxy_server):
346
+ templates = await proxy_server.get_resource_templates()
347
+ get_user_template = templates["data://user/{user_id}"]
348
+ assert get_user_template.meta == {"tags": ["users"]}
349
+
350
  async def test_list_resource_templates_same_as_original(
351
  self, fastmcp_server, proxy_server
352
  ):
 
446
  prompts = await proxy_server.get_prompts()
447
  assert [p.name for p in prompts.values()] == Contains("welcome")
448
 
449
+ async def test_get_prompts_meta(self, proxy_server):
450
+ prompts = await proxy_server.get_prompts()
451
+ welcome_prompt = prompts["welcome"]
452
+ assert welcome_prompt.meta == {"tags": ["welcome"]}
453
+
454
  async def test_list_prompts_same_as_original(self, fastmcp_server, proxy_server):
455
  async with Client(fastmcp_server) as client:
456
  result = await client.list_prompts()