Skip to content

Commit 1143014

Browse files
committed
fix: hotfix for MCP parsing
1 parent e8dda33 commit 1143014

File tree

2 files changed

+87
-46
lines changed

2 files changed

+87
-46
lines changed

nerve/generation/__init__.py

Lines changed: 83 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,45 @@ def _get_unknown_tool_response(self, tool_call_id: str, tool_name: str) -> dict[
119119
"content": f"The tool {tool_name} is not available.",
120120
}
121121

122+
def _responses_for(self, tool_call_id: str, tool_name: str, response: t.Any) -> t.Any:
123+
if isinstance(response, str):
124+
return [
125+
{
126+
"tool_call_id": tool_call_id,
127+
"role": "tool",
128+
"name": tool_name,
129+
"content": response,
130+
}
131+
]
132+
else:
133+
"""
134+
Handle:
135+
136+
Invalid 'messages[3]'. Image URLs are only allowed for messages with role 'user', but this message with role 'tool' contains an image URL.", 'type': 'invalid_request_error', 'param': 'messages[3]', 'code': 'invalid_value'}}
137+
138+
And:
139+
140+
An assistant message with 'tool_calls' must be followed by tool messages responding to each 'tool_call_id'.
141+
"""
142+
return [
143+
{
144+
"tool_call_id": tool_call_id,
145+
"role": "tool",
146+
"name": tool_name,
147+
"content": "",
148+
},
149+
{
150+
"role": "user",
151+
"content": [
152+
{
153+
"type": "text",
154+
"text": f"{tool_name} returned the following response:",
155+
},
156+
response,
157+
],
158+
},
159+
]
160+
122161
async def _get_tool_response(
123162
self, tool_call_id: str, tool_name: str, tool_fn: t.Callable[..., t.Any], tool_args: dict[str, t.Any]
124163
) -> list[dict[str, t.Any]]:
@@ -141,53 +180,55 @@ async def _get_tool_response(
141180
tool_response = f"ERROR while executing tool {tool_name}: {e}"
142181

143182
generated_responses = get_tool_response(tool_response)
144-
if not isinstance(generated_responses, list):
145-
generated_responses = [generated_responses]
146-
147-
ret_responses: list[t.Any] = []
148-
149-
for generated_response in generated_responses:
150-
if isinstance(generated_response, str):
151-
ret_responses.append(
152-
{
153-
"tool_call_id": tool_call_id,
154-
"role": "tool",
155-
"name": tool_name,
156-
"content": generated_response,
157-
}
158-
)
159-
else:
160-
"""
161-
Handle:
183+
if isinstance(generated_responses, str):
184+
# simple case, just set content
185+
return [
186+
{
187+
"tool_call_id": tool_call_id,
188+
"role": "tool",
189+
"name": tool_name,
190+
"content": generated_responses,
191+
}
192+
]
193+
elif isinstance(generated_responses, list):
194+
logger.debug("merging multiple responses from MCP: {}", generated_responses)
195+
# multiple response, probably from MCP, merge
196+
return [
197+
{
198+
"tool_call_id": tool_call_id,
199+
"role": "tool",
200+
"name": tool_name,
201+
"content": "\n".join(generated_responses),
202+
}
203+
]
204+
else:
205+
"""
206+
Handle:
162207
163-
Invalid 'messages[3]'. Image URLs are only allowed for messages with role 'user', but this message with role 'tool' contains an image URL.", 'type': 'invalid_request_error', 'param': 'messages[3]', 'code': 'invalid_value'}}
208+
Invalid 'messages[3]'. Image URLs are only allowed for messages with role 'user', but this message with role 'tool' contains an image URL.", 'type': 'invalid_request_error', 'param': 'messages[3]', 'code': 'invalid_value'}}
164209
165-
And:
210+
And:
166211
167-
An assistant message with 'tool_calls' must be followed by tool messages responding to each 'tool_call_id'.
168-
"""
169-
ret_responses.extend(
170-
[
171-
{
172-
"tool_call_id": tool_call_id,
173-
"role": "tool",
174-
"name": tool_name,
175-
"content": "",
176-
},
212+
An assistant message with 'tool_calls' must be followed by tool messages responding to each 'tool_call_id'.
213+
"""
214+
return [
215+
{
216+
"tool_call_id": tool_call_id,
217+
"role": "tool",
218+
"name": tool_name,
219+
"content": "",
220+
},
221+
{
222+
"role": "user",
223+
"content": [
177224
{
178-
"role": "user",
179-
"content": [
180-
{
181-
"type": "text",
182-
"text": f"{tool_name} returned the following response:",
183-
},
184-
generated_response,
185-
],
225+
"type": "text",
226+
"text": f"{tool_name} returned the following response:",
186227
},
187-
]
188-
)
189-
190-
return ret_responses
228+
generated_responses,
229+
],
230+
},
231+
]
191232

192233
async def _process_tool_call(
193234
self, call_id: str, tool_name: str, args: str | dict[str, t.Any], extra_tools: dict[str, t.Callable[..., t.Any]]

nerve/tools/mcp/client.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,14 +72,14 @@ async def tools(self) -> list[Tool]:
7272

7373
return self._tools
7474

75-
async def call_tool(self, name: str, **kwargs: t.Any) -> list[t.Any]:
75+
async def call_tool(self, mcp_tool_name: str, **kwargs: t.Any) -> t.Any:
7676
if not self._session:
7777
await self.connect()
7878
if not self._session:
7979
raise Exception("failed to connect to MCP server")
8080

81-
logger.debug("calling mcp tool: {}", name)
82-
ret = await self._session.call_tool(name, kwargs)
81+
logger.debug("calling mcp tool: {}", mcp_tool_name)
82+
ret = await self._session.call_tool(mcp_tool_name, kwargs)
8383
logger.debug("mcp tool call result: {}", ret)
8484

8585
if ret.isError:
@@ -101,4 +101,4 @@ async def call_tool(self, name: str, **kwargs: t.Any) -> list[t.Any]:
101101

102102
logger.debug("tool call responses: {}", responses)
103103

104-
return responses
104+
return responses[0] if len(responses) == 1 else responses

0 commit comments

Comments
 (0)