message/send JSON-RPC result is wrapped in the SendMessageResponse oneof instead of returning the Task/Message directly

Open Beginner friendly
#1,192 3 comments 0 reactions 1 assignee View on GitHub

@rohityan is already working on this.

Since Aug 19, 2026.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
70/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Domain
api

Research direction

The issue is in a2a/server/routes/jsonrpc_dispatcher.py in the _handle_send_message function. Compare it with _handle_get_task and _handle_cancel_task to see the difference in how they serialize the return value. The fix is to remove the SendMessageResponse wrapper and return the Task or Message directly via MessageToDict. Run the existing tests for the JSON-RPC routes to verify the change produces the expected JSON shape.

Written by the indexing model from the issue text.

Description

component: server

Environment

  • a2a-sdk 1.0.3
  • Python 3.13
  • Transport: JSON-RPC (create_jsonrpc_routes, DefaultRequestHandlerV2), enable_v0_3_compat=True

Summary

For a 1.x message/send call, the JSON-RPC result comes back as {"task": {…}} (or {"message": {…}}) rather than the Task or Message object itself. A client that reads result.status and result.artifacts, which the A2A 1.0 spec describes for the unary send, finds nothing, because the payload sits one level deeper under result.task.

The task/message oneof belongs to the streaming StreamResponse. The unary message/send should return the Task or Message directly, the way tasks/get and tasks/cancel already do.

Expected

{ "jsonrpc": "2.0", "id": "1", "result": { "id": "…", "contextId": "…", "status": {…}, "artifacts": [ … ] } }

Actual

{ "jsonrpc": "2.0", "id": "1", "result": { "task": { "id": "…", "contextId": "…", "status": {…}, "artifacts": [ … ] } } }

Root cause

In a2a/server/routes/jsonrpc_dispatcher.py, _handle_send_message wraps the return value in a SendMessageResponse before serializing:

async def _handle_send_message(self, request_obj, context):
    task_or_message = await self.request_handler.on_message_send(request_obj, context)
    if isinstance(task_or_message, Task):
        return MessageToDict(SendMessageResponse(task=task_or_message))
    return MessageToDict(SendMessageResponse(message=task_or_message))

MessageToDict on a SendMessageResponse emits the oneof field name (task or message) as a key, which becomes the JSON-RPC result. The sibling handlers serialize the object directly and do not show this wrapper:

async def _handle_get_task(self, request_obj, context):
    task = await self.request_handler.on_get_task(request_obj, context)
    return MessageToDict(task, preserving_proto_field_name=False)

Impact

A2A 1.0 clients that follow the spec read the Task at result, not at result.task. Against this SDK they see an empty result and report the call as incomplete. The two shapes also disagree within one SDK: tasks/get returns the Task un-nested, message/send nests it, so a client cannot use one code path for both.

Reproduce

  1. Stand up any agent with create_jsonrpc_routes(...).
  2. POST a 1.x message/send (method: "SendMessage").
  3. Read the response: result has a single key, task.

Suggested fix

Return the Task or Message directly from _handle_send_message, matching _handle_get_task and _handle_cancel_task:

async def _handle_send_message(self, request_obj, context):
    task_or_message = await self.request_handler.on_message_send(request_obj, context)
    return MessageToDict(task_or_message, preserving_proto_field_name=False)

The v0.3 compat path is unaffected. It already returns the Task/Message directly through the v0.3 adapter.

Dominant language
Python
Stars
2.2k
Forks
496
Avg merge
1d 23h
Merged PRs (30d)
16

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from a2aproject/a2a-python

All issues in a2aproject/a2a-python

Similar issues

More Python issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.