Skip to content

fix(connectors): return tool errors as results so the model can self-correct (#427) - #519

Open
hari (Mr-Neutr0n) wants to merge 1 commit into
langchain-ai:mainfrom
Mr-Neutr0n:fix/427-connector-tool-errors-as-results
Open

fix(connectors): return tool errors as results so the model can self-correct (#427)#519
hari (Mr-Neutr0n) wants to merge 1 commit into
langchain-ai:mainfrom
Mr-Neutr0n:fix/427-connector-tool-errors-as-results

Conversation

@Mr-Neutr0n

Copy link
Copy Markdown

Fixes #427.

The connector tools threw on error instead of returning the message as the tool result. A thrown tool error aborts the agent run, and even when the run survives it, the model never sees the text — which matters because these messages are written for the model. callMcpTool answers a wrong tool name with:

MCP tool notion-get-page-content was not returned by tools/list for notion. Run openwiki_list_mcp_tools first and use an exact discovered name.

That is precisely the hint needed to retry correctly, and the model never received it.

Verified before/after on that exact path

before:  >>> REJECTED (model never sees the hint)
after:   >>> RETURNED: Tool error: notion MCP connector is not enabled.

Errors are surfaced, not swallowed — the result is prefixed Tool error: so a failure is never mistaken for data.

⚠️ Contract change that needs your eye

Three tests in test/raw-connector-tools.test.ts asserted that symlink rejections throw:

).rejects.toThrow(/symbolic links/u);

They now assert the same refusal on the result channel instead. The security property is unchanged — the symlink is still refused before any read, and the reason still reaches the caller — but this is a real change to what those tests pin, and they are security tests, so I did not want to quietly rewrite them and move on.

If you would rather security refusals keep hard-failing while ordinary tool errors are returned, say so and I will exempt the raw-file tools from the wrapper. That is a two-line change.

Scope

LangChain rejects schema violations before func runs, so those remain rejections — a different layer. This covers errors thrown inside the tool body, which is what the issue is about.

Tests

test/connector-tool-errors.test.ts — 6 cases: thrown Error, rejected promise, non-Error rejection, success passthrough untouched, the issue's MCP tool-name case end to end, and a successful connector call still returning parseable JSON.

Full suite 775 passed, typecheck and lint:check clean.


Reviewed and tested locally; drafted with AI assistance.

…correct (langchain-ai#427)

The connector tools threw on error instead of returning the message as the tool
result. A thrown tool error aborts the agent run, and even when the run survives
it, the model never sees the text - which matters because these messages are
written FOR the model. callMcpTool answers a wrong tool name with

  MCP tool notion-get-page-content was not returned by tools/list for notion.
  Run openwiki_list_mcp_tools first and use an exact discovered name.

which is exactly the hint needed to retry correctly. Verified before/after on
that path: invoke() rejected, and now returns
"Tool error: notion MCP connector is not enabled."

Errors are surfaced, not swallowed: the result is prefixed `Tool error:` so a
failure is never mistaken for data.

CONTRACT CHANGE worth a maintainer's eye: three tests in
test/raw-connector-tools.test.ts asserted that symlink rejections *throw*
(`.rejects.toThrow(/symbolic links/u)`). They now assert the same refusal on the
result channel instead. The security property is unchanged - the symlink is
still refused before any read, and the reason still reaches the caller - but if
you would rather security refusals keep hard-failing while ordinary tool errors
are returned, say so and I will exempt the raw-file tools from the wrapper.

Scope note: LangChain rejects schema violations before `func` runs, so those
remain rejections. This covers errors thrown inside the tool body, which is what
the issue is about.

Tests: test/connector-tool-errors.test.ts - 6 cases (thrown Error, rejected
promise, non-Error rejection, success passthrough untouched, the issue's MCP
tool-name case end to end, and a successful connector call).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Connector tools throw instead of returning errors as tool results, so the model can't self-correct (e.g. wrong MCP tool name)

1 participant