fix: salvage a list result's valid entries when one is malformed (#1909)

The SDK validates a list result as a whole, so a single non-conforming entry
rejected the entire response and the panel showed "Couldn't load resources"
with zero items — the exact opposite of what a debugging tool should do with a
server that is 90% right. Reported against a PHP server whose empty
`annotations` object reaches the wire as `[]` rather than `{}`.

`listAll*` now falls back to a lenient re-walk when — and only when — the SDK
rejects a result it received (`InvalidResult` / `UnsupportedResultType`), keeps
the entries that validate, and reports the rest. A conforming server takes the
strict, cache-aware path exactly as before.

Salvaging is not accepting: the dropped entries are named with their reason in
a warning above the list, and the Protocol entry is still marked rejected, so
the wire truth survives. When every entry validates, the original error is
rethrown rather than swallowed — the rejection was about something the
per-item pass cannot explain.

`isClientDecodeRejection` moves to the new module and is now shared with
managedListState, which had the only copy.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HDdo1rNRVQnVRRdsqSrvG3
Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
cliffhall 2026-08-15 16:52:11 -04:00
commit fe5b622fd1

View file

@ -15,6 +15,8 @@ import type { Tool } from "@modelcontextprotocol/client";
import type { ExcludedTool } from "@inspector/core/mcp/types.js";
import { ListChangedIndicator } from "../../elements/ListChangedIndicator/ListChangedIndicator";
import { ListLoadError } from "../../elements/ListLoadError/ListLoadError";
import { MalformedItemsWarning } from "../../elements/MalformedItemsWarning/MalformedItemsWarning";
import type { MalformedListItem } from "@inspector/core/mcp";
import {
ListPaginationControls,
type ListPaginationControlsProps,
@ -37,6 +39,12 @@ export interface ToolControlsProps {
* A failed list load, surfaced above the list instead of leaving the panel
* empty (which reads as "this server has none") (#1953).
*/
/**
* Entries the client dropped from this list because they failed the MCP
* schema. Rendered as a warning above the list, which still shows the rest
* (#1909).
*/
malformedListItems?: MalformedListItem[];
loadError?: Error | null;
/** Pagination controls (#1721). */
pagination: ListPaginationControlsProps;
@ -127,6 +135,7 @@ export function ToolControls({
searchText = "",
listChanged,
onRefreshList,
malformedListItems = [],
loadError,
pagination,
onSearchChange,
@ -163,6 +172,11 @@ export function ToolControls({
/>
<ListPaginationControls {...pagination} />
<ListLoadError error={loadError} what="tools" onRetry={onRefreshList} />
<MalformedItemsWarning
items={malformedListItems}
method="tools/list"
what="tools"
/>
<SidebarScroll viewportRef={viewportRef}>
<Stack gap="xs">
{filteredTools.map(({ tool, key }) => (