MCP client: let tool errors reach agent error handling, and keep structured-only results

Open
#2,362 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

@Sushmit404 is already working on this.

Since Oct 16, 2026.

  • #2363 by @Sushmit404 — open

Assessment

Difficulty
4/5
Estimated time
3-5 days
Newbie friendliness
25/100
Issue type
Feature
Clarity
Mostly clear
Activity status
Active
Tech stack
python

Research direction

Start with the mcp_client and per_user_mcp_client function groups, MCPToolClient.acall(), and convert_to_mcp_error, which the issue says turns tool error text into MCPAuthenticationError. Reproduce with a stdio server whose tool returns isError: true, and check how tool_call_max_retries and pass_tool_call_errors_to_agent respond. Done means an opt-in raise_on_tool_error path, a MCPToolError type, and the default behaviour unchanged; first settle the three open questions with maintainers.

Written by the indexing model from the issue text.

Description

feature request Needs Triage
Is this a new feature, an improvement, or a change to existing functionality?

Improvement

How would you describe the priority of this feature request

Medium

Please provide a clear description of problem this feature solves

When an MCP tool fails, the mcp_client function group returns the error message as normal tool output. This includes results the server marks with isError: true. Code downstream of the tool can't tell a failed call from a successful one, so:

  • ReAct tool_call_max_retries never retries a failed MCP tool call.
  • ReAct pass_tool_call_errors_to_agent: false cannot stop the run.

I reproduced this with a local stdio FastMCP server and a react_agent workflow that has pass_tool_call_errors_to_agent: false. The tool raised Order ORD-404 does not exist:

  • With nvidia/nemotron-3-super-120b-a12b, the run did not stop. The model read the error text and answered correctly, but the agent's error setting was ignored.
  • With a scripted LLM that ignores tool output, the run also did not stop, and the final answer was "Your order ORD-404 has shipped!". Nothing in the agent or the trace marked the tool call as failed.

In both cases, the function's FUNCTION_END step records the error text as a normal output.

A second issue: if a tool returns only structuredContent with no text block, MCPToolClient.acall() returns "" and the result is lost. The spec allows this, because mirroring structured content in a text block is a SHOULD. The ReAct agent then reports "The tool provided an empty response."

I understand that returning errors as text was intentional (#840): the LLM gets useful information instead of a hang. So I'm proposing an opt-in setting rather than a change to the default.

Describe your ideal solution
  1. A raise_on_tool_error: bool = False option on mcp_client and per_user_mcp_client. When it's true, failed tool calls raise instead of returning the error text. That covers isError results, timeouts, and connection errors. The default keeps returning the same text as today. langchain-mcp-adapters has a similar option, handle_tool_errors.
  2. A dedicated MCPToolError(MCPError) for isError results. Today the tool's own error text goes through convert_to_mcp_error, which matches on the message. So a tool error such as "GitHub API returned 403 Forbidden" becomes an MCPAuthenticationError saying authentication with the MCP server failed.
  3. When a result has no text content, return structuredContent serialized as JSON instead of "".
  4. Keep the server URL out of the exception message, because agents pass the message to the LLM and a URL can contain credentials. The URL stays on the exception as .url.
function_groups:
  orders:
    _type: mcp_client
    server: {transport: streamable-http, url: "http://localhost:9901/mcp"}
    raise_on_tool_error: true

I have a working implementation with unit tests and docs, and I'll open a draft PR linked to this issue so the design can be discussed alongside the code.

Additional context
  • Related: #2138 (closed as stale), which reported that the client exposes only text and drops structuredContent and isError. This proposal covers only the client-side result handling from that issue.
  • The CLI path already treats isError as an error: nat mcp client tool call raises (cli/commands.py).
  • Retrying a failed tool call runs the tool again with the same arguments. That may be unwanted for tools with side effects, so I documented it next to the option. Telling retryable failures (timeouts, connection errors) apart from tool errors could be a follow-up.
  • The MCP SDK already validates structuredContent against the tool's outputSchema in ClientSession.call_tool, so the client doesn't need to validate it again.

Questions for maintainers:

  1. Should raise_on_tool_error default to false (no behavior change) or true?
  2. Should the session wrapper's own failure returns ("Tool temporarily unavailable. Try again.", "User not authorized to call the tool") also raise when the option is on? My implementation raises them as MCPError with the CONNECTION and AUTHENTICATION categories.
  3. Is returning typed output based on outputSchema worth a follow-up, or should the client stay text-only?
Code of Conduct
  • I agree to follow this project's Code of Conduct
  • I have searched the open feature requests and have found no duplicates for this feature request
Dominant language
Python
Stars
2.7k
Forks
814
Avg merge
1d 7h
Merged PRs (30d)
131

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from NVIDIA/NeMo-Agent-Toolkit

All issues in NVIDIA/NeMo-Agent-Toolkit

Similar issues

More Python issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.