fix: omit null optional fields from task result payloads (#2809)
Co-authored-by: Sheldon <neooosky@gmail.com>
This commit is contained in:
parent
494eb11d36
commit
2309e5ef97
2 changed files with 31 additions and 2 deletions
|
|
@ -129,9 +129,15 @@ class TaskResultHandler:
|
||||||
# The stored result contains the actual payload data
|
# The stored result contains the actual payload data
|
||||||
# Per spec: tasks/result MUST include _meta with related-task metadata
|
# Per spec: tasks/result MUST include _meta with related-task metadata
|
||||||
related_task = RelatedTaskMetadata(taskId=task_id)
|
related_task = RelatedTaskMetadata(taskId=task_id)
|
||||||
related_task_meta: dict[str, Any] = {RELATED_TASK_METADATA_KEY: related_task.model_dump(by_alias=True)}
|
related_task_meta: dict[str, Any] = {
|
||||||
|
RELATED_TASK_METADATA_KEY: related_task.model_dump(
|
||||||
|
by_alias=True,
|
||||||
|
mode="json",
|
||||||
|
exclude_none=True,
|
||||||
|
)
|
||||||
|
}
|
||||||
if result is not None:
|
if result is not None:
|
||||||
result_data = result.model_dump(by_alias=True)
|
result_data = result.model_dump(by_alias=True, mode="json", exclude_none=True)
|
||||||
existing_meta: dict[str, Any] = result_data.get("_meta") or {}
|
existing_meta: dict[str, Any] = result_data.get("_meta") or {}
|
||||||
result_data["_meta"] = {**existing_meta, **related_task_meta}
|
result_data["_meta"] = {**existing_meta, **related_task_meta}
|
||||||
return GetTaskPayloadResult.model_validate(result_data)
|
return GetTaskPayloadResult.model_validate(result_data)
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,29 @@ async def test_handle_returns_result_for_completed_task(
|
||||||
assert "io.modelcontextprotocol/related-task" in response.meta
|
assert "io.modelcontextprotocol/related-task" in response.meta
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.anyio
|
||||||
|
async def test_handle_omits_none_fields_from_completed_task_payload(
|
||||||
|
store: InMemoryTaskStore, queue: InMemoryTaskMessageQueue, handler: TaskResultHandler
|
||||||
|
) -> None:
|
||||||
|
"""Test task result payloads omit optional None fields instead of serializing null."""
|
||||||
|
task = await store.create_task(TaskMetadata(ttl=60000), task_id="test-task")
|
||||||
|
result = CallToolResult(content=[TextContent(type="text", text="Done!")])
|
||||||
|
await store.store_result(task.taskId, result)
|
||||||
|
await store.update_task(task.taskId, status="completed")
|
||||||
|
|
||||||
|
mock_session = Mock()
|
||||||
|
mock_session.send_message = AsyncMock()
|
||||||
|
|
||||||
|
request = GetTaskPayloadRequest(params=GetTaskPayloadRequestParams(taskId=task.taskId))
|
||||||
|
response = await handler.handle(request, mock_session, "req-1")
|
||||||
|
|
||||||
|
payload = response.model_dump(by_alias=True, mode="json")
|
||||||
|
assert payload["content"] == [{"type": "text", "text": "Done!"}]
|
||||||
|
assert "annotations" not in payload["content"][0]
|
||||||
|
assert "_meta" not in payload["content"][0]
|
||||||
|
assert "io.modelcontextprotocol/related-task" in payload["_meta"]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.anyio
|
@pytest.mark.anyio
|
||||||
async def test_handle_raises_for_nonexistent_task(
|
async def test_handle_raises_for_nonexistent_task(
|
||||||
store: InMemoryTaskStore, queue: InMemoryTaskMessageQueue, handler: TaskResultHandler
|
store: InMemoryTaskStore, queue: InMemoryTaskMessageQueue, handler: TaskResultHandler
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue