Skip to content

Stop reporting a rejected credential as a missing tool - #1586

Open
GeiserX wants to merge 2 commits into
UsefulSoftwareCo:mainfrom
GeiserX:fix/tool-not-found-empty-catalog
Open

Stop reporting a rejected credential as a missing tool#1586
GeiserX wants to merge 2 commits into
UsefulSoftwareCo:mainfrom
GeiserX:fix/tool-not-found-empty-catalog

Conversation

@GeiserX

@GeiserX GeiserX commented Aug 13, 2026

Copy link
Copy Markdown

Ask an MCP connection for a tool while its credential is being rejected upstream, and Executor tells
you the tool does not exist:

ToolNotFoundError: Tool not found: tools.acme.org.main.whoami
suggestions: []

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 ToolNotFoundError is the right error and this does not
change that. What was missing is the fact that separates a mistyped tool name from a connection that
produced nothing at all:

ToolNotFoundError: Tool not found: tools.acme.org.main.whoami —
  connection "acme/main" has no tools; check its health for why discovery produced none

Health already reports the underlying cause (expired for a credential the upstream rejects, degraded otherwise, both with detail), so once the error names the
connection there is somewhere to go.

Scope

reason is an optional field, set only when the address does not resolve and the connection
exists 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 empty-catalog assertion fails without the change and passes with it;
  • a populated-catalog control asserts the connection note is not added, and that control fails if
    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 healthy for a connection whose credential was missing — the error named the
wrong thing rather than failing to fire.

Part of the review in #1585; see #1564 for the first of this series.

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.
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.
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.

1 participant