Jeremiah Lowin commited on
Commit
54bdf2c
·
1 Parent(s): e97c2b8

Updated list_tools

Browse files
src/fastmcp/server/middleware.py CHANGED
@@ -144,6 +144,14 @@ class MCPMiddleware:
144
  handler = partial(self.on_read_resource, call_next=handler)
145
  case "prompts/get":
146
  handler = partial(self.on_get_prompt, call_next=handler)
 
 
 
 
 
 
 
 
147
 
148
  match context.type:
149
  case "request":
@@ -203,3 +211,26 @@ class MCPMiddleware:
203
  call_next: CallNext[mt.ListToolsRequest, ListToolsResult],
204
  ) -> ListToolsResult:
205
  return await call_next(context)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
  handler = partial(self.on_read_resource, call_next=handler)
145
  case "prompts/get":
146
  handler = partial(self.on_get_prompt, call_next=handler)
147
+ case "tools/list":
148
+ handler = partial(self.on_list_tools, call_next=handler)
149
+ case "resources/list":
150
+ handler = partial(self.on_list_resources, call_next=handler)
151
+ case "resource-templates/list":
152
+ handler = partial(self.on_list_resource_templates, call_next=handler)
153
+ case "prompts/list":
154
+ handler = partial(self.on_list_prompts, call_next=handler)
155
 
156
  match context.type:
157
  case "request":
 
211
  call_next: CallNext[mt.ListToolsRequest, ListToolsResult],
212
  ) -> ListToolsResult:
213
  return await call_next(context)
214
+
215
+ async def on_list_resources(
216
+ self,
217
+ context: MiddlewareContext[mt.ListResourcesRequest],
218
+ call_next: CallNext[mt.ListResourcesRequest, ListResourcesResult],
219
+ ) -> ListResourcesResult:
220
+ return await call_next(context)
221
+
222
+ async def on_list_resource_templates(
223
+ self,
224
+ context: MiddlewareContext[mt.ListResourceTemplatesRequest],
225
+ call_next: CallNext[
226
+ mt.ListResourceTemplatesRequest, ListResourceTemplatesResult
227
+ ],
228
+ ) -> ListResourceTemplatesResult:
229
+ return await call_next(context)
230
+
231
+ async def on_list_prompts(
232
+ self,
233
+ context: MiddlewareContext[mt.ListPromptsRequest],
234
+ call_next: CallNext[mt.ListPromptsRequest, ListPromptsResult],
235
+ ) -> ListPromptsResult:
236
+ return await call_next(context)
src/fastmcp/server/server.py CHANGED
@@ -341,7 +341,8 @@ class FastMCP(Generic[LifespanResultT]):
341
 
342
  async def get_tools(self) -> dict[str, Tool]:
343
  """Get all registered tools, indexed by registered key."""
344
- return await self._list_tools(apply_middleware=False)
 
345
 
346
  async def get_tool(self, key: str) -> Tool:
347
  tools = await self.get_tools()
@@ -515,7 +516,7 @@ class FastMCP(Generic[LifespanResultT]):
515
  tools = await self._middleware_list_tools()
516
  return [tool.to_mcp_tool(name=tool.name) for tool in tools]
517
 
518
- async def _middleware_list_tools(self) -> dict[str, Tool]:
519
  """
520
  List all available tools, in the format expected by the low-level MCP
521
  server.
@@ -524,13 +525,13 @@ class FastMCP(Generic[LifespanResultT]):
524
 
525
  async def _handler(
526
  context: MiddlewareContext[mcp.types.ListToolsRequest],
527
- ) -> list[MCPTool]:
528
  tools = await self._list_tools()
529
 
530
- mcp_tools: list[MCPTool] = []
531
- for key, tool in tools.items():
532
  if self._should_enable_component(tool):
533
- mcp_tools.append(tool.to_mcp_tool(name=key))
534
 
535
  return mcp_tools
536
 
@@ -547,13 +548,13 @@ class FastMCP(Generic[LifespanResultT]):
547
  # Apply the middleware chain.
548
  return await self._apply_middleware(mw_context, _handler)
549
 
550
- async def _list_tools(self, apply_middleware: bool = True) -> dict[str, Tool]:
551
  """
552
  List all available tools.
553
  """
554
 
555
  if (tools := self._cache.get("tools")) is self._cache.NOT_FOUND:
556
- tools: dict[str, Tool] = {}
557
 
558
  # iterate such that new mounts overwrite older ones
559
  for mounted_server in self._mounted_servers:
@@ -566,18 +567,17 @@ class FastMCP(Generic[LifespanResultT]):
566
  server_tools = await mounted_server.server._list_tools()
567
  # Apply prefix to each tool key if prefix exists and is not empty
568
  if mounted_server.prefix:
569
- for tool in server_tools.values():
570
  tool = tool.with_key(f"{mounted_server.prefix}_{tool.key}")
571
- tools[tool.key] = tool
572
  else:
573
- tools.update(server_tools)
574
- tools.update(server_tools)
575
  except Exception as e:
576
  logger.warning(
577
  f"Failed to get tools from mounted server '{mounted_server.prefix}': {e}"
578
  )
579
  continue
580
- tools.update(self._tool_manager.get_tools())
581
  self._cache.set("tools", tools)
582
  return tools
583
 
 
341
 
342
  async def get_tools(self) -> dict[str, Tool]:
343
  """Get all registered tools, indexed by registered key."""
344
+ tools = await self._list_tools(apply_middleware=False)
345
+ return {tool.key: tool for tool in tools}
346
 
347
  async def get_tool(self, key: str) -> Tool:
348
  tools = await self.get_tools()
 
516
  tools = await self._middleware_list_tools()
517
  return [tool.to_mcp_tool(name=tool.name) for tool in tools]
518
 
519
+ async def _middleware_list_tools(self) -> list[Tool]:
520
  """
521
  List all available tools, in the format expected by the low-level MCP
522
  server.
 
525
 
526
  async def _handler(
527
  context: MiddlewareContext[mcp.types.ListToolsRequest],
528
+ ) -> list[Tool]:
529
  tools = await self._list_tools()
530
 
531
+ mcp_tools: list[Tool] = []
532
+ for tool in tools:
533
  if self._should_enable_component(tool):
534
+ mcp_tools.append(tool)
535
 
536
  return mcp_tools
537
 
 
548
  # Apply the middleware chain.
549
  return await self._apply_middleware(mw_context, _handler)
550
 
551
+ async def _list_tools(self, apply_middleware: bool = True) -> list[Tool]:
552
  """
553
  List all available tools.
554
  """
555
 
556
  if (tools := self._cache.get("tools")) is self._cache.NOT_FOUND:
557
+ tools: list[Tool] = []
558
 
559
  # iterate such that new mounts overwrite older ones
560
  for mounted_server in self._mounted_servers:
 
567
  server_tools = await mounted_server.server._list_tools()
568
  # Apply prefix to each tool key if prefix exists and is not empty
569
  if mounted_server.prefix:
570
+ for tool in server_tools:
571
  tool = tool.with_key(f"{mounted_server.prefix}_{tool.key}")
572
+ tools.append(tool)
573
  else:
574
+ tools.extend(server_tools)
 
575
  except Exception as e:
576
  logger.warning(
577
  f"Failed to get tools from mounted server '{mounted_server.prefix}': {e}"
578
  )
579
  continue
580
+ tools.extend(self._tool_manager.get_tools().values())
581
  self._cache.set("tools", tools)
582
  return tools
583
 
tests/server/middleware/test_middleware.py CHANGED
@@ -7,7 +7,7 @@ import pytest
7
 
8
  from fastmcp import Client, FastMCP
9
  from fastmcp.server.context import Context
10
- from fastmcp.server.middleware.middleware import MCPMiddleware, MiddlewareContext
11
 
12
 
13
  @dataclass
@@ -194,3 +194,43 @@ class TestMiddlewareHooks:
194
  assert recording_middleware.assert_called(hook="on_message", times=1)
195
  assert recording_middleware.assert_called(hook="on_request", times=1)
196
  assert recording_middleware.assert_called(hook="on_list_tools", times=1)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
 
8
  from fastmcp import Client, FastMCP
9
  from fastmcp.server.context import Context
10
+ from fastmcp.server.middleware import MCPMiddleware, MiddlewareContext
11
 
12
 
13
  @dataclass
 
194
  assert recording_middleware.assert_called(hook="on_message", times=1)
195
  assert recording_middleware.assert_called(hook="on_request", times=1)
196
  assert recording_middleware.assert_called(hook="on_list_tools", times=1)
197
+
198
+ async def test_list_resources(
199
+ self, mcp_server: FastMCP, recording_middleware: RecordingMiddleware
200
+ ):
201
+ async with Client(mcp_server) as client:
202
+ await client.list_resources()
203
+
204
+ assert recording_middleware.assert_called(times=3)
205
+ assert recording_middleware.assert_called(method="resources/list", times=3)
206
+ assert recording_middleware.assert_called(hook="on_message", times=1)
207
+ assert recording_middleware.assert_called(hook="on_request", times=1)
208
+ assert recording_middleware.assert_called(hook="on_list_resources", times=1)
209
+
210
+ async def test_list_resource_templates(
211
+ self, mcp_server: FastMCP, recording_middleware: RecordingMiddleware
212
+ ):
213
+ async with Client(mcp_server) as client:
214
+ await client.list_resource_templates()
215
+
216
+ assert recording_middleware.assert_called(times=3)
217
+ assert recording_middleware.assert_called(
218
+ method="resource-templates/list", times=3
219
+ )
220
+ assert recording_middleware.assert_called(hook="on_message", times=1)
221
+ assert recording_middleware.assert_called(hook="on_request", times=1)
222
+ assert recording_middleware.assert_called(
223
+ hook="on_list_resource_templates", times=1
224
+ )
225
+
226
+ async def test_list_prompts(
227
+ self, mcp_server: FastMCP, recording_middleware: RecordingMiddleware
228
+ ):
229
+ async with Client(mcp_server) as client:
230
+ await client.list_prompts()
231
+
232
+ assert recording_middleware.assert_called(times=3)
233
+ assert recording_middleware.assert_called(method="prompts/list", times=3)
234
+ assert recording_middleware.assert_called(hook="on_message", times=1)
235
+ assert recording_middleware.assert_called(hook="on_request", times=1)
236
+ assert recording_middleware.assert_called(hook="on_list_prompts", times=1)