Skip to content

Commit 95572d6

Browse files
committed
Reduce code duplicated between tool invoke methods
1 parent 4f48d7c commit 95572d6

1 file changed

Lines changed: 17 additions & 30 deletions

File tree

  • integrations/mcp/src/haystack_integrations/tools/mcp

integrations/mcp/src/haystack_integrations/tools/mcp/mcp_tool.py

Lines changed: 17 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,21 @@ def _resolve_headers(headers: dict[str, str | Secret] | None) -> dict[str, str]
6363
return resolved_headers
6464

6565

66+
def extract_first_text(result: str) -> str:
67+
# Per MCP spec, content[] may contain TextContent, ImageContent, AudioContent, etc.
68+
# Parse only first TextContent block (ToolInvoker requires dict, not list).
69+
parsed: dict = json.loads(result)
70+
content: list = parsed.get("content", [])
71+
for block in content:
72+
if isinstance(block, dict) and block.get("type") == "text":
73+
text = block.get("text", "")
74+
try:
75+
return json.loads(text)
76+
except (json.JSONDecodeError, TypeError):
77+
# No TextContent found, return full parsed response as fallback
78+
return text
79+
80+
6681
class AsyncExecutor:
6782
"""Thread-safe event loop executor for running async code from sync contexts."""
6883

@@ -1088,21 +1103,7 @@ async def invoke() -> Any:
10881103
# Parse JSON to dict only when outputs_to_state is configured.
10891104
# ToolInvoker requires dict for _merge_tool_outputs(); ToolCallResult.result expects str otherwise.
10901105
if self.outputs_to_state:
1091-
parsed = json.loads(result)
1092-
1093-
# Per MCP spec, content[] may contain TextContent, ImageContent, AudioContent, etc.
1094-
# Parse only first TextContent block (ToolInvoker requires dict, not list).
1095-
content = parsed.get("content", [])
1096-
for block in content:
1097-
if isinstance(block, dict) and block.get("type") == "text":
1098-
text = block.get("text", "")
1099-
try:
1100-
return json.loads(text)
1101-
except (json.JSONDecodeError, TypeError):
1102-
return text
1103-
1104-
# No TextContent found, return full parsed response as fallback
1105-
return parsed
1106+
return self.extract_first_text(result)
11061107

11071108
return result
11081109
except (MCPError, TimeoutError) as e:
@@ -1133,21 +1134,7 @@ async def ainvoke(self, **kwargs: Any) -> str | dict[str, Any]:
11331134
# Parse JSON to dict only when outputs_to_state is configured.
11341135
# ToolInvoker requires dict for _merge_tool_outputs(); ToolCallResult.result expects str otherwise.
11351136
if self.outputs_to_state:
1136-
parsed = json.loads(result)
1137-
1138-
# Per MCP spec, content[] may contain TextContent, ImageContent, AudioContent, etc.
1139-
# Parse only first TextContent block (ToolInvoker requires dict, not list).
1140-
content = parsed.get("content", [])
1141-
for block in content:
1142-
if isinstance(block, dict) and block.get("type") == "text":
1143-
text = block.get("text", "")
1144-
try:
1145-
return json.loads(text)
1146-
except (json.JSONDecodeError, TypeError):
1147-
return text
1148-
1149-
# No TextContent found, return full parsed response as fallback
1150-
return parsed
1137+
return extract_first_text(result)
11511138

11521139
return result
11531140
except asyncio.TimeoutError as e:

0 commit comments

Comments
 (0)