Jeremiah Lowin commited on
Commit
6ad13fe
·
unverified ·
2 Parent(s): 9cdc3fbf4bfd8f

Merge pull request #290 from jlowin/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
 
@@ -195,7 +194,9 @@ class Prompt(BaseModel):
195
  content = TextContent(type="text", text=msg)
196
  messages.append(Message(role="user", content=content))
197
  else:
198
- content = json.dumps(pydantic_core.to_jsonable_python(msg))
 
 
199
  messages.append(Message(role="user", content=content))
200
  except Exception:
201
  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
 
 
194
  content = TextContent(type="text", text=msg)
195
  messages.append(Message(role="user", content=content))
196
  else:
197
+ content = pydantic_core.to_json(
198
+ msg, fallback=str, indent=2
199
+ ).decode()
200
  messages.append(Message(role="user", content=content))
201
  except Exception:
202
  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
 
@@ -170,23 +169,7 @@ def _convert_to_content(
170
 
171
  return other_content + mcp_types
172
 
173
- # if the result is a bytes object, convert it to a text content object
174
  if not isinstance(result, str):
175
- try:
176
- jsonable_result = pydantic_core.to_jsonable_python(result)
177
- if jsonable_result is None:
178
- return [TextContent(type="text", text="null")]
179
- elif isinstance(jsonable_result, bool):
180
- return [
181
- TextContent(
182
- type="text", text="true" if jsonable_result else "false"
183
- )
184
- ]
185
- elif isinstance(jsonable_result, str | int | float):
186
- return [TextContent(type="text", text=str(jsonable_result))]
187
- else:
188
- return [TextContent(type="text", text=json.dumps(jsonable_result))]
189
- except Exception:
190
- result = str(result)
191
 
192
  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
 
 
169
 
170
  return other_content + mcp_types
171
 
 
172
  if not isinstance(result, str):
173
+ result = pydantic_core.to_json(result, fallback=str, indent=2).decode()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
 
175
  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
@@ -298,7 +298,7 @@ class TestResourceTemplate:
298
 
299
  assert isinstance(resource, FunctionResource)
300
  content = await resource.read()
301
- assert content == "hello"
302
 
303
  async def test_wildcard_param_can_create_resource(self):
304
  """Test that wildcard parameters are valid."""
 
298
 
299
  assert isinstance(resource, FunctionResource)
300
  content = await resource.read()
301
+ assert content == '"hello"'
302
 
303
  async def test_wildcard_param_can_create_resource(self):
304
  """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
@@ -6,6 +6,7 @@ from enum import Enum
6
  from pathlib import Path
7
  from typing import Annotated, Literal
8
 
 
9
  import pytest
10
  from mcp.types import (
11
  BlobResourceContents,
@@ -104,7 +105,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 +131,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 +145,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 +159,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 +173,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()
 
6
  from pathlib import Path
7
  from typing import Annotated, Literal
8
 
9
+ import pydantic_core
10
  import pytest
11
  from mcp.types import (
12
  BlobResourceContents,
 
105
  async with Client(tool_server) as client:
106
  result = await client.call_tool("list_tool", {})
107
  assert isinstance(result[0], TextContent)
108
+ assert result[0].text == '[\n "x",\n 2\n]'
109
 
110
 
111
  class TestToolReturnTypes:
 
131
  async with Client(mcp) as client:
132
  result = await client.call_tool("bytes_tool", {})
133
  assert isinstance(result[0], TextContent)
134
+ assert result[0].text == '"Hello, world!"'
135
 
136
  async def test_uuid(self):
137
  mcp = FastMCP()
 
145
  async with Client(mcp) as client:
146
  result = await client.call_tool("uuid_tool", {})
147
  assert isinstance(result[0], TextContent)
148
+ assert result[0].text == pydantic_core.to_json(test_uuid).decode()
149
 
150
  async def test_path(self):
151
  mcp = FastMCP()
 
159
  async with Client(mcp) as client:
160
  result = await client.call_tool("path_tool", {})
161
  assert isinstance(result[0], TextContent)
162
+ assert result[0].text == pydantic_core.to_json(test_path).decode()
163
 
164
  async def test_datetime(self):
165
  mcp = FastMCP()
 
173
  async with Client(mcp) as client:
174
  result = await client.call_tool("datetime_tool", {})
175
  assert isinstance(result[0], TextContent)
176
+ assert result[0].text == pydantic_core.to_json(dt).decode()
177
 
178
  async def test_image(self, tmp_path: Path):
179
  mcp = FastMCP()
tests/tools/test_tool_manager.py CHANGED
@@ -390,18 +390,7 @@ class TestCallTools:
390
  assert isinstance(result, list)
391
  assert len(result) == 1
392
  assert isinstance(result[0], TextContent)
393
- assert result[0].text == '["rex", "gertrude"]'
394
- assert json.loads(result[0].text) == ["rex", "gertrude"]
395
-
396
- result = await manager.call_tool(
397
- "name_shrimp",
398
- {"tank": '{"x": null, "shrimp": [{"name": "rex"}, {"name": "gertrude"}]}'},
399
- )
400
- assert isinstance(result, list)
401
- assert len(result) == 1
402
- assert isinstance(result[0], TextContent)
403
- assert result[0].text == '["rex", "gertrude"]'
404
- assert json.loads(result[0].text) == ["rex", "gertrude"]
405
 
406
 
407
  class TestToolSchema:
 
390
  assert isinstance(result, list)
391
  assert len(result) == 1
392
  assert isinstance(result[0], TextContent)
393
+ assert result[0].text == '[\n "rex",\n "gertrude"\n]'
 
 
 
 
 
 
 
 
 
 
 
394
 
395
 
396
  class TestToolSchema: