Skip to content

Commit c3117b6

Browse files
committed
Add JSON serialization tests for _meta attributes
Adds tests to verify that _meta attributes are correctly serialized with the underscore prefix in JSON output, not as "meta". This ensures the implementation conforms to the MCP specification where metadata fields use the _meta key. Tests verify: - Resource serialization includes "_meta" key in JSON - Response content serialization includes "_meta" key - JSON strings contain the literal "_meta" key - No "meta" key appears in serialized output
1 parent 218839f commit c3117b6

File tree

2 files changed

+95
-0
lines changed

2 files changed

+95
-0
lines changed

tests/server/fastmcp/resources/test_resource_meta.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,3 +143,51 @@ def get_complex() -> str:
143143
assert contents[0].meta["config"]["nested"]["value"] == 42
144144
assert contents[0].meta["config"]["list"] == [1, 2, 3]
145145
assert contents[0].meta["tags"] == ["tag1", "tag2"]
146+
147+
148+
@pytest.mark.anyio
149+
async def test_resource_meta_json_serialization():
150+
"""Test that _meta is correctly serialized as '_meta' in JSON output."""
151+
mcp = FastMCP()
152+
153+
def get_widget() -> str:
154+
return "widget content"
155+
156+
resource = FunctionResource.from_function(
157+
fn=get_widget,
158+
uri="resource://widget",
159+
**{"_meta": {"widgetDomain": "example.com", "version": "1.0"}},
160+
)
161+
mcp.add_resource(resource)
162+
163+
# First check the resource itself serializes correctly
164+
resource_json = resource.model_dump(by_alias=True, mode="json")
165+
assert "_meta" in resource_json, "Expected '_meta' key in resource JSON"
166+
assert resource_json["_meta"]["widgetDomain"] == "example.com"
167+
168+
# Get the full response through the handler
169+
handler = mcp._mcp_server.request_handlers[types.ReadResourceRequest]
170+
request = types.ReadResourceRequest(
171+
params=types.ReadResourceRequestParams(uri=AnyUrl("resource://widget")),
172+
)
173+
result = await handler(request)
174+
175+
# Serialize to JSON with aliases
176+
result_json = result.model_dump(by_alias=True, mode="json")
177+
178+
# Verify _meta is in the JSON output (not "meta")
179+
content_json = result_json["root"]["contents"][0]
180+
assert "_meta" in content_json, "Expected '_meta' key in content JSON output"
181+
assert "meta" not in content_json or content_json.get("meta") is None, "Should not have 'meta' key in JSON output"
182+
assert content_json["_meta"]["widgetDomain"] == "example.com"
183+
assert content_json["_meta"]["version"] == "1.0"
184+
185+
# Also verify in the JSON string
186+
result_json_str = result.model_dump_json(by_alias=True)
187+
assert '"_meta"' in result_json_str, "Expected '_meta' string in JSON output"
188+
189+
# Verify the full structure matches expected MCP format
190+
assert content_json["uri"] == "resource://widget"
191+
assert content_json["text"] == "widget content"
192+
assert content_json["mimeType"] == "text/plain"
193+
assert content_json["_meta"]["widgetDomain"] == "example.com"

tests/server/test_resource_meta.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,3 +174,50 @@ async def read_resource(uri: AnyUrl) -> Iterable[ReadResourceContents]:
174174
assert content1.meta is not None
175175
assert content1.meta["index"] == 1
176176
assert content1.meta["type"] == "body"
177+
178+
179+
@pytest.mark.anyio
180+
async def test_read_resource_meta_json_serialization(temp_file: Path):
181+
"""Test that _meta is correctly serialized as '_meta' in JSON output."""
182+
server = Server("test")
183+
184+
@server.read_resource()
185+
async def read_resource(uri: AnyUrl) -> Iterable[ReadResourceContents]:
186+
return [
187+
ReadResourceContents(
188+
content="Test content",
189+
mime_type="text/plain",
190+
meta={"widgetDomain": "example.com", "version": "1.0"},
191+
)
192+
]
193+
194+
# Get the handler
195+
handler = server.request_handlers[types.ReadResourceRequest]
196+
197+
# Create a request
198+
request = types.ReadResourceRequest(
199+
params=types.ReadResourceRequestParams(uri=FileUrl(temp_file.as_uri())),
200+
)
201+
202+
# Call the handler
203+
result = await handler(request)
204+
205+
# Serialize to JSON with aliases
206+
result_json = result.model_dump(by_alias=True, mode="json")
207+
208+
# Verify structure
209+
assert "root" in result_json
210+
assert "contents" in result_json["root"]
211+
assert len(result_json["root"]["contents"]) == 1
212+
213+
# Verify _meta is in the JSON output (not "meta")
214+
content_json = result_json["root"]["contents"][0]
215+
assert "_meta" in content_json, "Expected '_meta' key in JSON output"
216+
assert "meta" not in content_json or content_json.get("meta") is None, "Should not have 'meta' key in JSON output"
217+
assert content_json["_meta"]["widgetDomain"] == "example.com"
218+
assert content_json["_meta"]["version"] == "1.0"
219+
220+
# Also verify in the JSON string
221+
result_json_str = result.model_dump_json(by_alias=True)
222+
assert '"_meta"' in result_json_str, "Expected '_meta' string in JSON output"
223+
assert content_json["_meta"]["widgetDomain"] == "example.com"

0 commit comments

Comments
 (0)