Repository navigation
agentgateway.call_mcp_tool flattens CallToolResult to str, dropping structuredContent and other MCP fields #214
Description
Describe the Bug
sap_cloud_sdk.agentgateway.AgentGatewayClient.call_mcp_tool is typed -> str and both internal flow paths (_customer.py::call_mcp_tool_customer and _lob.py::call_mcp_tool_lob) discard almost everything on the returned MCP CallToolResult before crossing the SDK boundary:
# src/sap_cloud_sdk/agentgateway/_customer.py (main branch, lines 571-576)
# Same shape in _lob.py at lines 480-484.
result = await session.call_tool(tool.name, kwargs)
if not result.content:
logger.warning("Tool '%s' returned empty content", tool.name)
return ""
first = result.content[0]
return str(getattr(first, "text", ""))Dropped fields:
structuredContent— the MCP-native channel for schema-typed structured tool output. Servers set this alongsidecontentso consumers can distinguish human-readable text from typed data.content[1:]— every content block after the first (multi-block responses lose everything but block 0).isError— success and error results are both returned asstr, indistinguishable at the type level._meta— arbitrary MCP metadata.
The reference converter converters.py::mcp_tool_to_langchain builds a LangChain StructuredTool whose coroutine returns this flattened str directly to LangChain, so downstream consumers using the reference converter also lose these fields.
Why it matters: langchain_mcp_adapters (a sibling library in the LangChain ecosystem) preserves structuredContent on the LangChain side by wrapping it into MCPToolArtifact on ToolMessage.artifact via response_format="content_and_artifact". Consumers that go through sap_cloud_sdk.agentgateway for its auth / mTLS / destination-resolution / tenant-routing plumbing pay for it by losing the structured channel. Concrete downstream impacts:
- Agents emitting structured tool payloads as A2A
Part(root=DataPart(...))cannot recoverstructuredContentfrom astrreturn. The workaround is tojson.loadsthe returned string, which only succeeds when the MCP server happens to duplicate its structured payload intocontent[0].text. - Any MCP tool that returns multiple
contentblocks (text + image, or multiple text blocks) is truncated to block 0. - Callers cannot check
isErrorat the type level — success and error results have the same return type.
Steps to Reproduce
- Configure an MCP server that returns a
CallToolResultwith bothcontent(text) andstructuredContent(dict) populated. This is the standard MCP shape for schema-typed tool output. - Invoke via
agent_gateway_client.call_mcp_tool(tool, ...). - Observe that the return value is
str— the first content block'stext.structuredContent,content[1:],isError, and_metaare unrecoverable from the return.
Expected Behavior
call_mcp_tool (or an equivalent method exposed by the SDK) allows consumers to access the full CallToolResult, preserving content, structuredContent, isError, and _meta. This lets consumers implement the same content_and_artifact split that langchain_mcp_adapters provides for the direct-MCP path.
Two possible shapes for the fix (maintainers know the compatibility surface best):
Option A — additive, non-breaking. Add a new method returning the raw CallToolResult, keep the existing call_mcp_tool unchanged:
async def call_mcp_tool_raw(
self,
tool: MCPTool,
user_token: str | Callable[[], str] | None = None,
app_tid: str | None = None,
**kwargs,
) -> mcp.types.CallToolResult:
...Update converters.py::mcp_tool_to_langchain (or ship a second reference converter) to call call_mcp_tool_raw and build a StructuredTool with response_format="content_and_artifact", matching the langchain_mcp_adapters shape.
Option B — breaking, cleaner long-term. Change call_mcp_tool to return CallToolResult; update the reference converter accordingly. Requires a major version bump and a migration note.
Option A seems preferable given call_mcp_tool is a documented public API with a stable signature and existing consumers would need to migrate.
Used Versions
- Python version:
3.14.3(bug is Python-version-independent — logic is in the SDK) - SAP Cloud SDK for Python version:
0.29.1observed. Verified the same flattening logic is still onmainat time of filing:agw_client.py:485declares-> str, and_customer.py:571-576/_lob.py:480-484contain the flatten-to-content[0].textcode. - Framework version:
langchain-mcp-adapters==0.2.2(for cross-reference with the sibling library's_convert_call_tool_resultbehavior)
Code Examples
# Consumer-side workaround currently in use — reconstructs a partial
# CallToolResult by parsing the flattened string. Works when the MCP
# server duplicates its payload into content[0].text; loses information
# when it doesn't.
raw_string = await agw_client.call_mcp_tool(tool, ...)
try:
parsed = json.loads(raw_string)
structured = parsed if isinstance(parsed, dict) else None
except json.JSONDecodeError:
structured = None
result = CallToolResult(
content=[TextContent(type="text", text=raw_string)],
structuredContent=structured,
isError=False, # Cannot actually determine — SDK dropped it
)Affected Development Phase
Development
Impact
Impaired
Related
- MCP protocol spec on
CallToolResult/structuredContent: https://modelcontextprotocol.io/specification/ langchain_mcp_adapters._convert_call_tool_result— how the sibling library preserves the split: https://github.com/langchain-ai/langchain-mcp-adapters/blob/main/langchain_mcp_adapters/tools.py
Activity
patrickmlr commentedon Jul 8, 2026
I'd be happy to open a PR for this if it's a direction you'd like to pursue — either the additive (Option A) or breaking (Option B) shape, whichever the team prefers.
betinacosta commentedon Sep 10, 2026
Hi @patrickmlr,
I apologize for the delayed response; we have been experiencing a significant increase in demand recently.
We believe we should proceed with Option A and subsequently deprecate the old method. Additionally, we think it would be advantageous to create our own wrapper rather than directly returning the type from the mcp library, which would prevent us from being dependent on their library.
Would you still be interested in opening this PR? If so, we would be delighted to have your contribution.
patrickmlr commentedon Sep 15, 2026
Hi @betinacosta,
sure! I'll let you know as soon as I'm ready with the PR.
Kind regards
Patrick