Jeremiah Lowin commited on
Commit
36d15c2
·
unverified ·
2 Parent(s): 1c062149dc2a8a

Merge pull request #283 from jlowin/openapi-response

Browse files
src/fastmcp/server/openapi.py CHANGED
@@ -10,12 +10,12 @@ from re import Pattern
10
  from typing import TYPE_CHECKING, Any, Literal
11
 
12
  import httpx
13
- from mcp.types import TextContent
14
  from pydantic.networks import AnyUrl
15
 
16
  from fastmcp.resources import Resource, ResourceTemplate
17
  from fastmcp.server.server import FastMCP
18
- from fastmcp.tools.tool import Tool
19
  from fastmcp.utilities import openapi
20
  from fastmcp.utilities.func_metadata import func_metadata
21
  from fastmcp.utilities.logging import get_logger
@@ -239,9 +239,14 @@ class OpenAPITool(Tool):
239
  # Handle request errors (connection, timeout, etc.)
240
  raise ValueError(f"Request error: {str(e)}")
241
 
242
- async def run(self, arguments: dict[str, Any], context: Any = None) -> Any:
 
 
 
 
243
  """Run the tool with arguments and optional context."""
244
- return await self._execute_request(**arguments, context=context)
 
245
 
246
 
247
  class OpenAPIResource(Resource):
@@ -605,13 +610,4 @@ class FastMCPOpenAPI(FastMCP):
605
 
606
  context = self.get_context()
607
  result = await self._tool_manager.call_tool(name, arguments, context=context)
608
-
609
- # For other tools, ensure the response is wrapped in TextContent
610
- if isinstance(result, dict | str):
611
- if isinstance(result, dict):
612
- result_text = json.dumps(result)
613
- else:
614
- result_text = result
615
- return [TextContent(text=result_text, type="text")]
616
-
617
  return result
 
10
  from typing import TYPE_CHECKING, Any, Literal
11
 
12
  import httpx
13
+ from mcp.types import EmbeddedResource, ImageContent, TextContent
14
  from pydantic.networks import AnyUrl
15
 
16
  from fastmcp.resources import Resource, ResourceTemplate
17
  from fastmcp.server.server import FastMCP
18
+ from fastmcp.tools.tool import Tool, _convert_to_content
19
  from fastmcp.utilities import openapi
20
  from fastmcp.utilities.func_metadata import func_metadata
21
  from fastmcp.utilities.logging import get_logger
 
239
  # Handle request errors (connection, timeout, etc.)
240
  raise ValueError(f"Request error: {str(e)}")
241
 
242
+ async def run(
243
+ self,
244
+ arguments: dict[str, Any],
245
+ context: Context[ServerSessionT, LifespanContextT] | None = None,
246
+ ) -> list[TextContent | ImageContent | EmbeddedResource]:
247
  """Run the tool with arguments and optional context."""
248
+ response = await self._execute_request(**arguments, context=context)
249
+ return _convert_to_content(response)
250
 
251
 
252
  class OpenAPIResource(Resource):
 
610
 
611
  context = self.get_context()
612
  result = await self._tool_manager.call_tool(name, arguments, context=context)
 
 
 
 
 
 
 
 
 
613
  return result
src/fastmcp/server/proxy.py CHANGED
@@ -61,7 +61,7 @@ class ProxyTool(Tool):
61
  self,
62
  arguments: dict[str, Any],
63
  context: Context[ServerSessionT, LifespanContextT] | None = None,
64
- ) -> Any:
65
  # the client context manager will swallow any exceptions inside a TaskGroup
66
  # so we return the raw result and raise an exception ourselves
67
  async with self._client:
 
61
  self,
62
  arguments: dict[str, Any],
63
  context: Context[ServerSessionT, LifespanContextT] | None = None,
64
+ ) -> list[TextContent | ImageContent | EmbeddedResource]:
65
  # the client context manager will swallow any exceptions inside a TaskGroup
66
  # so we return the raw result and raise an exception ourselves
67
  async with self._client:
tests/server/test_openapi.py CHANGED
@@ -19,6 +19,8 @@ from fastmcp.server.openapi import (
19
  OpenAPIResource,
20
  OpenAPIResourceTemplate,
21
  OpenAPITool,
 
 
22
  )
23
 
24
 
@@ -268,6 +270,32 @@ class TestTools:
268
  user = json.loads(response_text)
269
  assert user == expected_data
270
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
271
 
272
  class TestResources:
273
  async def test_list_resources(self, fastmcp_openapi_server: FastMCPOpenAPI):
 
19
  OpenAPIResource,
20
  OpenAPIResourceTemplate,
21
  OpenAPITool,
22
+ RouteMap,
23
+ RouteType,
24
  )
25
 
26
 
 
270
  user = json.loads(response_text)
271
  assert user == expected_data
272
 
273
+ async def test_call_tool_return_list(
274
+ self,
275
+ fastapi_app: FastAPI,
276
+ api_client: httpx.AsyncClient,
277
+ users_db: dict[int, User],
278
+ ):
279
+ """
280
+ The tool created by the OpenAPI server should return a list of content.
281
+ """
282
+ openapi_spec = fastapi_app.openapi()
283
+ mcp_server = FastMCPOpenAPI(
284
+ openapi_spec=openapi_spec,
285
+ client=api_client,
286
+ route_maps=[
287
+ RouteMap(methods=["GET"], pattern=r".*", route_type=RouteType.TOOL)
288
+ ],
289
+ )
290
+ async with Client(mcp_server) as client:
291
+ tool_response = await client.call_tool("get_users_users_get", {})
292
+ assert isinstance(tool_response, list)
293
+ assert isinstance(tool_response[0], TextContent)
294
+ assert json.loads(tool_response[0].text) == [
295
+ user.model_dump()
296
+ for user in sorted(users_db.values(), key=lambda x: x.id)
297
+ ]
298
+
299
 
300
  class TestResources:
301
  async def test_list_resources(self, fastmcp_openapi_server: FastMCPOpenAPI):