OpenHands / OpenHands/software-agent-sdk
[Bug]: MCPToolObservation silently discards structuredContent and result _meta
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 1.1k
- Forks
- 539
- Avg merge
- 1d 19h
- Merged PRs (30d)
- 137
Description
Is there an existing issue for the same bug?
- I have searched existing issues and this is not a duplicate.
Bug Description
When an MCP tool result carries structuredContent or result _meta, both are silently discarded at conversion: MCPToolObservation.from_call_tool_result keeps only text/image blocks, is_error, and tool_name. Once dropped there, no downstream consumer (event persistence, websocket delivery, protocol-aware clients such as Canvas) can recover them. MCP servers use structuredContent for machine-readable result data and _meta for protocol metadata.
A draft PR with a candidate fix: #4775.
Expected Behavior
The observation preserves structuredContent and result _meta so they serialize with the event, and registered secrets in those fields are masked before persistence or client delivery.
Actual Behavior
Converting a real mcp.types.CallToolResult that carries both fields yields an observation with neither. Reproduced on main at 9d143aac3 (v1.44.1):
$ python repro.py
COMMIT 9d143aac3
PYTHON 3.13.12
STRUCTURED_PRESERVED False
META_PRESERVED False
repro.py
import mcp.types
from openhands.sdk.mcp.definition import MCPToolObservation
result = mcp.types.CallToolResult(
_meta={"ui": {"hint": "Bearer token"}},
structuredContent={"rows": [{"token": "abc"}], "count": 1},
content=[mcp.types.TextContent(type="text", text="ok")],
isError=False,
)
obs = MCPToolObservation.from_call_tool_result(tool_name="probe", result=result)
print("STRUCTURED_PRESERVED", getattr(obs, "structured_content", None) is not None)
print("META_PRESERVED", getattr(obs, "result_meta", None) is not None)
Steps to Reproduce
- Check out
mainat9d143aac3and install withuv sync - Run the script above with the repo interpreter
- Both lines print
False: the converted observation carries neitherstructuredContentnor_meta, andmodel_dumploses them
Acceptance Criteria
-
structuredContentand result_metasurvive conversion and event serialization - Observations serialized before the fields existed still load
- Registered secrets in the new fields are masked, including nested keys, nested values, and JSON scalars
- The fields stay out of the content sent to the model
Installation Method
pip install -e . from a git checkout of main
SDK Version
main branch, commit 9d143aac3 (v1.44.1)
Python Version
3.13.12
Operating System
Linux
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start at MCPToolObservation.from_call_tool_result and run the repro.py example against the stated commit to confirm the fields are dropped. Trace the observation's event serialization and secret-masking paths; done means structuredContent and result _meta survive conversion and loading, nested secrets are masked, and neither field reaches model content.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- api, backend, security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100