Stop reporting a rejected credential as a missing tool - #1586
Open
GeiserX wants to merge 2 commits into
Open
Conversation
Discovery can come back empty for reasons that have nothing to do with the tool being asked for. The case that surfaced this: a credential the upstream rejects breaks discovery, the catalog comes back empty, the address resolves to nothing, and the caller is told the TOOL does not exist. That sends someone looking for a renamed or removed tool, which is the one thing that is not wrong. The address really does not resolve, so ToolNotFoundError stays the right error. What was missing is the fact that separates a mistyped tool name from a connection that produced nothing, so the error now carries it when the connection exists but has no tools, and points at health, which does report the underlying cause. `reason` is optional, so an ordinary unknown tool name reads exactly as before. Both directions are pinned: the new assertion failed before this change, and emitting the reason unconditionally reddens the populated-catalog control.
This was referenced Aug 13, 2026
The test helper tripped four of the repo's own lint rules at once — manual _tag inspection, a raw throw, a built-in Error constructor, and stringifying an unknown — so this branch would have gone red the first time fork CI ran on it. Effect.flip removes the need for all four: the failure becomes the value, already typed, and an unexpected success fails the test by itself. The assertions now pin `reason`, the field this change actually adds, rather than regex-matching a rendered message. Mutation-checked: emitting no reason fails the empty-catalog test and leaves the populated-catalog control green.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Ask an MCP connection for a tool while its credential is being rejected upstream, and Executor tells
you the tool does not exist:
So you go looking for a renamed or removed tool. That is the one thing that is not wrong. Discovery
failed, the catalog came back empty, and the address therefore resolved to nothing — the credential
is the problem, and nothing in the error points there.
The address really does not resolve, so
ToolNotFoundErroris the right error and this does notchange that. What was missing is the fact that separates a mistyped tool name from a connection that
produced nothing at all:
Health already reports the underlying cause (
expiredfor a credential the upstream rejects,degradedotherwise, both with detail), so once the error names theconnection there is somewhere to go.
Scope
reasonis an optional field, set only when the address does not resolve and the connectionexists and it has no tools. An ordinary unknown tool name is untouched — same message, same
suggestions — so no existing consumer sees a difference.
Tests
packages/core/sdk/src/tool-not-found-empty-catalog.test.ts, pinned in both directions:the reason is emitted unconditionally — so it is guarding rather than passing for free.
Found while testing a credential provider that returns values Executor does not validate: a value
with a stray newline breaks discovery, and this is how that surfaces. It is the same class as #1582,
where health reported
healthyfor a connection whose credential was missing — the error named thewrong thing rather than failing to fire.
Part of the review in #1585; see #1564 for the first of this series.