fix(web): name an empty tool result instead of "No results yet" (#1860)
The Tools screen mounts the Results panel only once a call has produced a result, but the panel's zero-content branch rendered "No results yet" — the same wording it would use before anything had run. An empty `content` array with no `structuredContent` is a legal `CallToolResult`, so a call that had plainly completed reported itself as a call that never happened. That reads worst after a multi-round-trip (MRTR) sequence, which is how it was reported: the user answers one or more embedded elicitations, watches the Protocol tab group the exchange and mark it COMPLETE, and is then told there are no results yet. Nothing was lost — the result was stored and rendered; the panel simply had no wording for "completed, returned nothing". The panel now renders an "Empty result" alert saying the call completed and returned no content. This is the remaining half of the same gap #1908 closed for `structuredContent`-only results. test-servers: new `mrtr_empty` preset (`createMrtrEmptyResultTool`) — one elicitation round, then a complete result with `content: []` and no `structuredContent` — added to `mrtr-showcase-http.json` so the shape is reproducible by hand. README documents it. Signed-off-by: Cliff Hall <cliff@futurescale.com> Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
parent
6bc355df63
commit
b3aedf62cd
6 changed files with 87 additions and 6 deletions
15
README.md
15
README.md
|
|
@ -140,7 +140,7 @@ Each config below is a ready-made server for exercising one feature by hand. Loa
|
|||
| ----------------------------------------- | -------------------------------------------------- | ---------------------------------------------------------------------- |
|
||||
| `mcp-app-http.json` **(legacy era)** | An MCP App (UI resource + app tool) in the Apps tab | [#1859](https://github.com/modelcontextprotocol/inspector/issues/1859) |
|
||||
| `modern-mrtr-http.json` | A single MRTR round-trip | — |
|
||||
| `mrtr-showcase-http.json` | Every MRTR preset in one server | — |
|
||||
| `mrtr-showcase-http.json` | Every MRTR preset in one server | [#1860](https://github.com/modelcontextprotocol/inspector/issues/1860) |
|
||||
| `modern-network-http.json` | Network tab: `Mcp-*` headers + error taxonomy | [#1628](https://github.com/modelcontextprotocol/inspector/issues/1628) |
|
||||
| `xmcpheader-modern-http.json` | Tools tab: `x-mcp-header` mirroring and exclusions | [#1632](https://github.com/modelcontextprotocol/inspector/issues/1632) |
|
||||
| `pagination-http.json` | Page-by-page list fetching | [#1721](https://github.com/modelcontextprotocol/inspector/issues/1721) |
|
||||
|
|
@ -175,8 +175,21 @@ The Inspector drives MRTR manually (`inputRequired: { autoFulfill: false }`), so
|
|||
| `mrtr_sample` | Embedded sampling → the Sampling panel |
|
||||
| `mrtr_roots` | Embedded `roots/list`, auto-answered silently from configured roots (no modal) |
|
||||
| `mrtr_edge` | An `inputRequests`-only round, then a `requestState`-only round |
|
||||
| `mrtr_empty` | Completes with an empty result — no `content`, no `structuredContent` |
|
||||
| `mrtr_loop` | Never completes → trips the `MRTR_MAX_ROUNDS` bound |
|
||||
|
||||
Run `mrtr_empty` and answer its single elicitation: the Protocol tab groups the
|
||||
exchange as an MRTR conversation ending **COMPLETE**, and the Results panel says
|
||||
**"Empty result — The tool call completed successfully and returned no content."**
|
||||
On the broken build that same result rendered as **"No results yet"**, the panel's
|
||||
pre-run placeholder ([#1860](https://github.com/modelcontextprotocol/inspector/issues/1860)) —
|
||||
so a call the user had just watched succeed read as a call that never ran. An
|
||||
empty `content` array with no `structuredContent` is a legal `CallToolResult`,
|
||||
and the panel only ever mounts once a result exists, so the placeholder wording
|
||||
could not be true there. (The neighbouring half of the same gap — a result whose
|
||||
payload lives only in `structuredContent` — was closed by
|
||||
[#1908](https://github.com/modelcontextprotocol/inspector/issues/1908).)
|
||||
|
||||
> The legacy `collect_elicitation` preset calls `server.elicitInput`, which errors on the 2026-07-28 leg — server→client requests aren't allowed there. MRTR is the modern replacement.
|
||||
|
||||
#### Network tab — standardized headers and error taxonomy
|
||||
|
|
|
|||
|
|
@ -35,11 +35,20 @@ describe("ToolResultPanel", () => {
|
|||
expect(screen.getByText("boom")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders the empty state when content is empty", () => {
|
||||
// #1860: this panel only mounts once a result exists, so an empty result must
|
||||
// read as "the call completed and returned nothing" — never as the pre-run
|
||||
// "No results yet" placeholder, which denies a call the user watched succeed.
|
||||
it("names the outcome when a completed result has no content", () => {
|
||||
renderWithMantine(
|
||||
<ToolResultPanel result={emptyResult} onClear={() => {}} />,
|
||||
);
|
||||
expect(screen.getByText("No results yet")).toBeInTheDocument();
|
||||
expect(screen.getByText("Empty result")).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(
|
||||
"The tool call completed successfully and returned no content.",
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.queryByText("No results yet")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("groups resource_link blocks in a scrollable Resource Links box", async () => {
|
||||
|
|
@ -141,7 +150,7 @@ describe("ToolResultPanel", () => {
|
|||
onClear={() => {}}
|
||||
/>,
|
||||
);
|
||||
expect(screen.queryByText("No results yet")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("Empty result")).not.toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByRole("heading", { name: "Structured Output" }),
|
||||
).toBeInTheDocument();
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import {
|
|||
Paper,
|
||||
ScrollArea,
|
||||
Stack,
|
||||
Text,
|
||||
Title,
|
||||
} from "@mantine/core";
|
||||
import type {
|
||||
|
|
@ -176,6 +175,16 @@ const ErrorAlert = Alert.withProps({
|
|||
title: "Tool Error",
|
||||
});
|
||||
|
||||
// A completed call whose result renders nothing. `content: []` with no
|
||||
// `structuredContent` is a legal CallToolResult, and this panel is only mounted
|
||||
// once a result exists — so saying "no results yet" here would deny a call the
|
||||
// user just watched succeed (#1860). Name the outcome instead.
|
||||
const EmptyResultAlert = Alert.withProps({
|
||||
color: "gray",
|
||||
variant: "light",
|
||||
title: "Empty result",
|
||||
});
|
||||
|
||||
function ResourceLinksGroup({
|
||||
links,
|
||||
onReadResource,
|
||||
|
|
@ -277,7 +286,9 @@ export function ToolResultPanel({
|
|||
</ResultScroll>
|
||||
) : result.content.length === 0 && !structuredNode ? (
|
||||
<ResultScroll>
|
||||
<Text c="dimmed">No results yet</Text>
|
||||
<EmptyResultAlert>
|
||||
The tool call completed successfully and returned no content.
|
||||
</EmptyResultAlert>
|
||||
</ResultScroll>
|
||||
) : hasLinks ? (
|
||||
<FillStack>
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
{ "preset": "mrtr_sample" },
|
||||
{ "preset": "mrtr_roots" },
|
||||
{ "preset": "mrtr_edge" },
|
||||
{ "preset": "mrtr_empty" },
|
||||
{ "preset": "mrtr_loop" }
|
||||
],
|
||||
"transport": {
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import {
|
|||
createMrtrSamplingTool,
|
||||
createMrtrLoopTool,
|
||||
createMrtrEdgeCaseTool,
|
||||
createMrtrEmptyResultTool,
|
||||
createCollectUrlElicitationTool,
|
||||
createUrlElicitationFormTool,
|
||||
createSendNotificationTool,
|
||||
|
|
@ -151,6 +152,8 @@ function resolveToolPreset(
|
|||
return createMrtrLoopTool();
|
||||
case "mrtr_edge":
|
||||
return createMrtrEdgeCaseTool();
|
||||
case "mrtr_empty":
|
||||
return createMrtrEmptyResultTool();
|
||||
case "collect_url_elicitation":
|
||||
return createCollectUrlElicitationTool();
|
||||
case "url_elicitation_form":
|
||||
|
|
|
|||
|
|
@ -755,6 +755,50 @@ export function createMrtrEdgeCaseTool(): ToolDefinition {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* An MRTR tool whose FINAL (complete) result carries an empty `content` array
|
||||
* and no `structuredContent` — a legal `CallToolResult` that renders nothing.
|
||||
*
|
||||
* This is the shape behind #1860: the sequence completes (the Protocol tab shows
|
||||
* the conversation COMPLETE), a real result is stored, and the Results panel had
|
||||
* no way to say so — it fell through to the same "No results yet" placeholder it
|
||||
* shows before anything has run, so a successful call read as a call that never
|
||||
* happened. Kept as an MRTR tool rather than a plain one because the multi-round
|
||||
* case is where the ambiguity actually misleads: the user has answered prompts
|
||||
* and watched rounds complete, so "no results yet" is flatly contradicted by
|
||||
* what they just did.
|
||||
*/
|
||||
export function createMrtrEmptyResultTool(): ToolDefinition {
|
||||
return {
|
||||
name: "mrtr_empty",
|
||||
description:
|
||||
"MRTR tool that completes with an empty result (no content, no structuredContent).",
|
||||
inputSchema: {},
|
||||
handler: async (
|
||||
_params: Record<string, unknown>,
|
||||
_context?: TestServerContext,
|
||||
extra?: HandlerExtra,
|
||||
) => {
|
||||
if (extra?.inputResponses?.ack === undefined) {
|
||||
return inputRequired({
|
||||
inputRequests: {
|
||||
ack: inputRequired.elicit({
|
||||
message: "Acknowledge to finish (the result will be empty)",
|
||||
requestedSchema: {
|
||||
type: "object",
|
||||
properties: { ack: { type: "boolean", title: "Acknowledge" } },
|
||||
required: ["ack"],
|
||||
},
|
||||
}),
|
||||
},
|
||||
requestState: `mrtr-empty:${++mrtrMintCount}`,
|
||||
});
|
||||
}
|
||||
return { content: [] } satisfies CallToolResult;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a "url_elicitation_form" tool that spins up a simple HTTP server on a dynamic
|
||||
* port with a form page, sends that URL via URL elicitation, and on form submit collects
|
||||
|
|
|
|||
Loading…
Reference in a new issue