From 5c889a1d47858b64f24bb0986f2282360ba847a5 Mon Sep 17 00:00:00 2001 From: Bryan Frimin Date: Fri, 13 Mar 2026 19:43:19 +0100 Subject: [PATCH] Fall back to StructuredContent in MCP tool results When an MCP server sets StructuredContent without populating Content with TextContent entries, extractMCPContent returned an empty string, making successful tool calls look empty to the agent. Now the function serializes StructuredContent as JSON when no text parts are found. Signed-off-by: Bryan Frimin --- pkg/agent/mcp.go | 15 +++++++++++++-- pkg/agent/mcp_test.go | 44 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/pkg/agent/mcp.go b/pkg/agent/mcp.go index be5aef13f..520532150 100644 --- a/pkg/agent/mcp.go +++ b/pkg/agent/mcp.go @@ -167,7 +167,7 @@ func (t *mcpTool) Execute(ctx context.Context, arguments string) (ToolResult, er } func extractMCPContent(result *mcp.CallToolResult) string { - if result == nil || len(result.Content) == 0 { + if result == nil { return "" } @@ -178,5 +178,16 @@ func extractMCPContent(result *mcp.CallToolResult) string { } } - return strings.Join(parts, "\n") + if len(parts) > 0 { + return strings.Join(parts, "\n") + } + + if result.StructuredContent != nil { + data, err := json.Marshal(result.StructuredContent) + if err == nil { + return string(data) + } + } + + return "" } diff --git a/pkg/agent/mcp_test.go b/pkg/agent/mcp_test.go index 86df974c6..dd231976e 100644 --- a/pkg/agent/mcp_test.go +++ b/pkg/agent/mcp_test.go @@ -379,4 +379,48 @@ func TestExtractMCPContent(t *testing.T) { assert.Equal(t, "", extractMCPContent(result)) }, ) + + t.Run( + "falls back to structured content when no text content", + func(t *testing.T) { + t.Parallel() + result := &mcp.CallToolResult{ + StructuredContent: map[string]any{ + "status": "ok", + "count": float64(42), + }, + } + got := extractMCPContent(result) + assert.Contains(t, got, `"status":"ok"`) + assert.Contains(t, got, `"count":42`) + }, + ) + + t.Run( + "text content takes precedence over structured content", + func(t *testing.T) { + t.Parallel() + result := &mcp.CallToolResult{ + Content: []mcp.Content{ + &mcp.TextContent{Text: "text wins"}, + }, + StructuredContent: map[string]any{"key": "value"}, + } + assert.Equal(t, "text wins", extractMCPContent(result)) + }, + ) + + t.Run( + "structured content used when content has only non-text", + func(t *testing.T) { + t.Parallel() + result := &mcp.CallToolResult{ + Content: []mcp.Content{ + &mcp.ImageContent{Data: []byte("img"), MIMEType: "image/png"}, + }, + StructuredContent: map[string]any{"fallback": true}, + } + assert.Equal(t, `{"fallback":true}`, extractMCPContent(result)) + }, + ) }