Skip to content

Commit 2be42bc

Browse files
committed
Omit null fields from task result payloads
1 parent 494eb11 commit 2be42bc

2 files changed

Lines changed: 31 additions & 2 deletions

File tree

src/mcp/server/experimental/task_result_handler.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,9 +129,15 @@ async def handle(
129129
# The stored result contains the actual payload data
130130
# Per spec: tasks/result MUST include _meta with related-task metadata
131131
related_task = RelatedTaskMetadata(taskId=task_id)
132-
related_task_meta: dict[str, Any] = {RELATED_TASK_METADATA_KEY: related_task.model_dump(by_alias=True)}
132+
related_task_meta: dict[str, Any] = {
133+
RELATED_TASK_METADATA_KEY: related_task.model_dump(
134+
by_alias=True,
135+
mode="json",
136+
exclude_none=True,
137+
)
138+
}
133139
if result is not None:
134-
result_data = result.model_dump(by_alias=True)
140+
result_data = result.model_dump(by_alias=True, mode="json", exclude_none=True)
135141
existing_meta: dict[str, Any] = result_data.get("_meta") or {}
136142
result_data["_meta"] = {**existing_meta, **related_task_meta}
137143
return GetTaskPayloadResult.model_validate(result_data)

tests/experimental/tasks/server/test_task_result_handler.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,29 @@ async def test_handle_returns_result_for_completed_task(
6767
assert "io.modelcontextprotocol/related-task" in response.meta
6868

6969

70+
@pytest.mark.anyio
71+
async def test_handle_omits_none_fields_from_completed_task_payload(
72+
store: InMemoryTaskStore, queue: InMemoryTaskMessageQueue, handler: TaskResultHandler
73+
) -> None:
74+
"""Test task result payloads omit optional None fields instead of serializing null."""
75+
task = await store.create_task(TaskMetadata(ttl=60000), task_id="test-task")
76+
result = CallToolResult(content=[TextContent(type="text", text="Done!")])
77+
await store.store_result(task.taskId, result)
78+
await store.update_task(task.taskId, status="completed")
79+
80+
mock_session = Mock()
81+
mock_session.send_message = AsyncMock()
82+
83+
request = GetTaskPayloadRequest(params=GetTaskPayloadRequestParams(taskId=task.taskId))
84+
response = await handler.handle(request, mock_session, "req-1")
85+
86+
payload = response.model_dump(by_alias=True, mode="json")
87+
assert payload["content"] == [{"type": "text", "text": "Done!"}]
88+
assert "annotations" not in payload["content"][0]
89+
assert "_meta" not in payload["content"][0]
90+
assert "io.modelcontextprotocol/related-task" in payload["_meta"]
91+
92+
7093
@pytest.mark.anyio
7194
async def test_handle_raises_for_nonexistent_task(
7295
store: InMemoryTaskStore, queue: InMemoryTaskMessageQueue, handler: TaskResultHandler

0 commit comments

Comments
 (0)