Jeremiah Lowin commited on
Commit
3de056b
·
1 Parent(s): d662e59

use pydantic_core.to_json

Browse files
src/fastmcp/prompts/prompt.py CHANGED
@@ -3,7 +3,6 @@
3
  from __future__ import annotations as _annotations
4
 
5
  import inspect
6
- import json
7
  from collections.abc import Awaitable, Callable, Sequence
8
  from typing import TYPE_CHECKING, Annotated, Any, Literal
9
 
@@ -192,7 +191,9 @@ class Prompt(BaseModel):
192
  content = TextContent(type="text", text=msg)
193
  messages.append(Message(role="user", content=content))
194
  else:
195
- content = json.dumps(pydantic_core.to_jsonable_python(msg))
 
 
196
  messages.append(Message(role="user", content=content))
197
  except Exception:
198
  raise ValueError(
 
3
  from __future__ import annotations as _annotations
4
 
5
  import inspect
 
6
  from collections.abc import Awaitable, Callable, Sequence
7
  from typing import TYPE_CHECKING, Annotated, Any, Literal
8
 
 
191
  content = TextContent(type="text", text=msg)
192
  messages.append(Message(role="user", content=content))
193
  else:
194
+ content = pydantic_core.to_json(
195
+ msg, fallback=str, indent=2
196
+ ).decode()
197
  messages.append(Message(role="user", content=content))
198
  except Exception:
199
  raise ValueError(
src/fastmcp/resources/types.py CHANGED
@@ -97,15 +97,12 @@ class FunctionResource(Resource):
97
 
98
  if isinstance(result, Resource):
99
  return await result.read(context=context)
100
- if isinstance(result, bytes):
101
  return result
102
- if isinstance(result, str):
103
  return result
104
- try:
105
- return json.dumps(pydantic_core.to_jsonable_python(result))
106
- except (TypeError, pydantic_core.PydanticSerializationError):
107
- # If JSON serialization fails, try str()
108
- return str(result)
109
  except Exception as e:
110
  raise ValueError(f"Error reading resource {self.uri}: {e}")
111
 
 
97
 
98
  if isinstance(result, Resource):
99
  return await result.read(context=context)
100
+ elif isinstance(result, bytes):
101
  return result
102
+ elif isinstance(result, str):
103
  return result
104
+ else:
105
+ return pydantic_core.to_json(result, fallback=str, indent=2).decode()
 
 
 
106
  except Exception as e:
107
  raise ValueError(f"Error reading resource {self.uri}: {e}")
108
 
src/fastmcp/server/server.py CHANGED
@@ -14,7 +14,6 @@ from typing import TYPE_CHECKING, Any, Generic, Literal
14
 
15
  import anyio
16
  import httpx
17
- import pydantic_core
18
  import uvicorn
19
  from mcp.server.lowlevel.helper_types import ReadResourceContents
20
  from mcp.server.lowlevel.server import LifespanResultT
@@ -27,6 +26,7 @@ from mcp.types import (
27
  EmbeddedResource,
28
  GetPromptResult,
29
  ImageContent,
 
30
  TextContent,
31
  )
32
  from mcp.types import Prompt as MCPPrompt
@@ -435,7 +435,12 @@ class FastMCP(Generic[LifespanResultT]):
435
  messages = await self._prompt_manager.render_prompt(
436
  name, arguments=arguments or {}, context=context
437
  )
438
- return GetPromptResult(messages=pydantic_core.to_jsonable_python(messages))
 
 
 
 
 
439
  else:
440
  for server in self._mounted_servers.values():
441
  if server.match_prompt(name):
 
14
 
15
  import anyio
16
  import httpx
 
17
  import uvicorn
18
  from mcp.server.lowlevel.helper_types import ReadResourceContents
19
  from mcp.server.lowlevel.server import LifespanResultT
 
26
  EmbeddedResource,
27
  GetPromptResult,
28
  ImageContent,
29
+ PromptMessage,
30
  TextContent,
31
  )
32
  from mcp.types import Prompt as MCPPrompt
 
435
  messages = await self._prompt_manager.render_prompt(
436
  name, arguments=arguments or {}, context=context
437
  )
438
+
439
+ return GetPromptResult(
440
+ messages=[
441
+ PromptMessage(role=m.role, content=m.content) for m in messages
442
+ ]
443
+ )
444
  else:
445
  for server in self._mounted_servers.values():
446
  if server.match_prompt(name):
src/fastmcp/tools/tool.py CHANGED
@@ -1,7 +1,6 @@
1
  from __future__ import annotations
2
 
3
  import inspect
4
- import json
5
  from collections.abc import Callable
6
  from typing import TYPE_CHECKING, Annotated, Any
7
 
@@ -166,23 +165,7 @@ def _convert_to_content(
166
 
167
  return other_content + mcp_types
168
 
169
- # if the result is a bytes object, convert it to a text content object
170
  if not isinstance(result, str):
171
- try:
172
- jsonable_result = pydantic_core.to_jsonable_python(result)
173
- if jsonable_result is None:
174
- return [TextContent(type="text", text="null")]
175
- elif isinstance(jsonable_result, bool):
176
- return [
177
- TextContent(
178
- type="text", text="true" if jsonable_result else "false"
179
- )
180
- ]
181
- elif isinstance(jsonable_result, str | int | float):
182
- return [TextContent(type="text", text=str(jsonable_result))]
183
- else:
184
- return [TextContent(type="text", text=json.dumps(jsonable_result))]
185
- except Exception:
186
- result = str(result)
187
 
188
  return [TextContent(type="text", text=result)]
 
1
  from __future__ import annotations
2
 
3
  import inspect
 
4
  from collections.abc import Callable
5
  from typing import TYPE_CHECKING, Annotated, Any
6
 
 
165
 
166
  return other_content + mcp_types
167
 
 
168
  if not isinstance(result, str):
169
+ result = pydantic_core.to_json(result, fallback=str, indent=2).decode()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
170
 
171
  return [TextContent(type="text", text=result)]
tests/resources/test_function_resources.py CHANGED
@@ -95,7 +95,7 @@ class TestFunctionResource:
95
  fn=lambda: MyModel(name="test"),
96
  )
97
  content = await resource.read()
98
- assert content == '{"name": "test"}'
99
 
100
  async def test_custom_type_conversion(self):
101
  """Test handling of custom types."""
 
95
  fn=lambda: MyModel(name="test"),
96
  )
97
  content = await resource.read()
98
+ assert content == '{\n "name": "test"\n}'
99
 
100
  async def test_custom_type_conversion(self):
101
  """Test handling of custom types."""
tests/resources/test_resource_template.py CHANGED
@@ -295,7 +295,7 @@ class TestResourceTemplate:
295
 
296
  assert isinstance(resource, FunctionResource)
297
  content = await resource.read()
298
- assert content == "hello"
299
 
300
  async def test_wildcard_param_can_create_resource(self):
301
  """Test that wildcard parameters are valid."""
 
295
 
296
  assert isinstance(resource, FunctionResource)
297
  content = await resource.read()
298
+ assert content == '"hello"'
299
 
300
  async def test_wildcard_param_can_create_resource(self):
301
  """Test that wildcard parameters are valid."""
tests/server/test_mount.py CHANGED
@@ -227,7 +227,7 @@ class TestResourcesAndTemplates:
227
  async with Client(main_app) as client:
228
  resource = await client.read_resource("data+data://users")
229
  assert isinstance(resource[0], TextResourceContents)
230
- assert resource[0].text == '["user1", "user2"]'
231
 
232
  async def test_mount_with_resource_templates(self):
233
  """Test mounting a server with resource templates."""
 
227
  async with Client(main_app) as client:
228
  resource = await client.read_resource("data+data://users")
229
  assert isinstance(resource[0], TextResourceContents)
230
+ assert resource[0].text == '[\n "user1",\n "user2"\n]'
231
 
232
  async def test_mount_with_resource_templates(self):
233
  """Test mounting a server with resource templates."""
tests/server/test_server_interactions.py CHANGED
@@ -104,7 +104,7 @@ class TestTools:
104
  async with Client(tool_server) as client:
105
  result = await client.call_tool("list_tool", {})
106
  assert isinstance(result[0], TextContent)
107
- assert result[0].text == '["x", 2]'
108
 
109
 
110
  class TestToolReturnTypes:
@@ -130,7 +130,7 @@ class TestToolReturnTypes:
130
  async with Client(mcp) as client:
131
  result = await client.call_tool("bytes_tool", {})
132
  assert isinstance(result[0], TextContent)
133
- assert result[0].text == "Hello, world!"
134
 
135
  async def test_uuid(self):
136
  mcp = FastMCP()
@@ -144,7 +144,7 @@ class TestToolReturnTypes:
144
  async with Client(mcp) as client:
145
  result = await client.call_tool("uuid_tool", {})
146
  assert isinstance(result[0], TextContent)
147
- assert result[0].text == str(test_uuid)
148
 
149
  async def test_path(self):
150
  mcp = FastMCP()
@@ -158,7 +158,7 @@ class TestToolReturnTypes:
158
  async with Client(mcp) as client:
159
  result = await client.call_tool("path_tool", {})
160
  assert isinstance(result[0], TextContent)
161
- assert result[0].text == str(test_path)
162
 
163
  async def test_datetime(self):
164
  mcp = FastMCP()
@@ -172,7 +172,7 @@ class TestToolReturnTypes:
172
  async with Client(mcp) as client:
173
  result = await client.call_tool("datetime_tool", {})
174
  assert isinstance(result[0], TextContent)
175
- assert result[0].text == dt.isoformat()
176
 
177
  async def test_image(self, tmp_path: Path):
178
  mcp = FastMCP()
 
104
  async with Client(tool_server) as client:
105
  result = await client.call_tool("list_tool", {})
106
  assert isinstance(result[0], TextContent)
107
+ assert result[0].text == '[\n "x",\n 2\n]'
108
 
109
 
110
  class TestToolReturnTypes:
 
130
  async with Client(mcp) as client:
131
  result = await client.call_tool("bytes_tool", {})
132
  assert isinstance(result[0], TextContent)
133
+ assert result[0].text == '"Hello, world!"'
134
 
135
  async def test_uuid(self):
136
  mcp = FastMCP()
 
144
  async with Client(mcp) as client:
145
  result = await client.call_tool("uuid_tool", {})
146
  assert isinstance(result[0], TextContent)
147
+ assert result[0].text == f'"{test_uuid}"'
148
 
149
  async def test_path(self):
150
  mcp = FastMCP()
 
158
  async with Client(mcp) as client:
159
  result = await client.call_tool("path_tool", {})
160
  assert isinstance(result[0], TextContent)
161
+ assert result[0].text == f'"{test_path}"'
162
 
163
  async def test_datetime(self):
164
  mcp = FastMCP()
 
172
  async with Client(mcp) as client:
173
  result = await client.call_tool("datetime_tool", {})
174
  assert isinstance(result[0], TextContent)
175
+ assert result[0].text == f'"{dt.isoformat()}"'
176
 
177
  async def test_image(self, tmp_path: Path):
178
  mcp = FastMCP()
tests/tools/test_tool_manager.py CHANGED
@@ -387,18 +387,7 @@ class TestCallTools:
387
  assert isinstance(result, list)
388
  assert len(result) == 1
389
  assert isinstance(result[0], TextContent)
390
- assert result[0].text == '["rex", "gertrude"]'
391
- assert json.loads(result[0].text) == ["rex", "gertrude"]
392
-
393
- result = await manager.call_tool(
394
- "name_shrimp",
395
- {"tank": '{"x": null, "shrimp": [{"name": "rex"}, {"name": "gertrude"}]}'},
396
- )
397
- assert isinstance(result, list)
398
- assert len(result) == 1
399
- assert isinstance(result[0], TextContent)
400
- assert result[0].text == '["rex", "gertrude"]'
401
- assert json.loads(result[0].text) == ["rex", "gertrude"]
402
 
403
 
404
  class TestToolSchema:
 
387
  assert isinstance(result, list)
388
  assert len(result) == 1
389
  assert isinstance(result[0], TextContent)
390
+ assert result[0].text == '[\n "rex",\n "gertrude"\n]'
 
 
 
 
 
 
 
 
 
 
 
391
 
392
 
393
  class TestToolSchema: