inspector/test-servers/src/preset-registry.ts
cliffhall 6f47eaa143 fix: render a tool result's structuredContent in the Tools screen (#1908)
A tool declaring an `outputSchema` returns its real payload in
`structuredContent`; the `content[]` blocks usually only summarize it. v1
rendered that payload as its own inspectable JSON section — v2's
ToolResultPanel only ever walked `result.content`, so the payload was
dropped with no label, section, or hint that it existed.

Adds a `StructuredOutputPanel` group: a collapsible "Structured Output"
box (bordered like the existing "Resource Links" box) rendering the
payload as pretty-printed, syntax-highlighted, copyable JSON, bounded so
a large payload scrolls within itself. ToolResultPanel renders it below
the content blocks — and also when `content` is empty (where "No results
yet" was simply wrong) and below the alert on an error result, so it is
never silently dropped.

Adds a `list_items` test-server preset (nested payload, the issue's repro
shape) and a `structured-output-http.json` showcase config, documented in
the README.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SW1p8E2uiyyLx4RKwrwSrt
2026-08-11 09:23:43 -04:00

302 lines
8.8 KiB
TypeScript

/**
* Preset registry for config-driven composable server
* Maps preset names to fixture factory functions
*/
import type {
ToolDefinition,
TaskToolDefinition,
ResourceDefinition,
PromptDefinition,
ResourceTemplateDefinition,
} from "./composable-test-server.js";
import {
createEchoTool,
createGetEnvTool,
createAddTool,
createGetSumTool,
createGetWeatherTool,
createInvalidHeaderTool,
createSpecErrorTriggerTool,
createWriteToStderrTool,
createCollectSampleTool,
createListRootsTool,
createCollectFormElicitationTool,
createMrtrTool,
createMrtrMultiRoundTool,
createMrtrRootsTool,
createMrtrSamplingTool,
createMrtrLoopTool,
createMrtrEdgeCaseTool,
createCollectUrlElicitationTool,
createUrlElicitationFormTool,
createSendNotificationTool,
createGetAnnotatedMessageTool,
createGetTempTool,
createGetTempExtraTool,
createListItemsTool,
createAddResourceTool,
createRemoveResourceTool,
createAddToolTool,
createRemoveToolTool,
createAddPromptTool,
createRemovePromptTool,
createUpdateResourceTool,
createSendProgressTool,
createNumberedTools,
createSimpleTaskTool,
createProgressTaskTool,
createElicitationTaskTool,
createSamplingTaskTool,
createOptionalTaskTool,
createForbiddenTaskTool,
createImmediateReturnTaskTool,
createMcpAppDemoTool,
createMcpAppDemoResource,
createArchitectureResource,
createTestCwdResource,
createTestEnvResource,
createTestArgvResource,
createNumberedResources,
createFileResourceTemplate,
createUserResourceTemplate,
createNumberedResourceTemplates,
createSimplePrompt,
createArgsPrompt,
createNumberedPrompts,
} from "./test-server-fixtures.js";
export type PresetType = "tool" | "resource" | "resourceTemplate" | "prompt";
export type PresetResult =
| ToolDefinition
| TaskToolDefinition
| ResourceDefinition
| PromptDefinition
| ResourceTemplateDefinition
| (ToolDefinition | TaskToolDefinition)[]
| ResourceDefinition[]
| ResourceTemplateDefinition[]
| PromptDefinition[];
function resolveToolPreset(
name: string,
params?: Record<string, unknown>,
):
| ToolDefinition
| TaskToolDefinition
| (ToolDefinition | TaskToolDefinition)[] {
const p = params ?? {};
const get = (k: string) => p[k] as unknown;
switch (name) {
case "echo":
return createEchoTool();
case "get-env":
case "get_env":
return createGetEnvTool();
case "add":
return createAddTool();
case "get_sum":
return createGetSumTool();
case "get_weather":
return createGetWeatherTool();
case "invalid_header_tool":
return createInvalidHeaderTool();
case "trigger_header_mismatch":
return createSpecErrorTriggerTool(
"trigger_header_mismatch",
"Returns -32020 HeaderMismatch (HTTP 400)",
);
case "trigger_missing_capability":
return createSpecErrorTriggerTool(
"trigger_missing_capability",
"Returns -32021 MissingRequiredClientCapability (HTTP 400)",
);
case "trigger_unsupported_version":
return createSpecErrorTriggerTool(
"trigger_unsupported_version",
"Returns -32022 UnsupportedProtocolVersion (HTTP 400)",
);
case "trigger_method_not_found":
return createSpecErrorTriggerTool(
"trigger_method_not_found",
"Returns -32601 MethodNotFound (HTTP 404)",
);
case "trigger_invalid_params":
return createSpecErrorTriggerTool(
"trigger_invalid_params",
"Returns a generic -32602 Invalid params (not an unknown tool)",
);
case "write_to_stderr":
return createWriteToStderrTool();
case "collect_sample":
return createCollectSampleTool();
case "list_roots":
return createListRootsTool();
case "collect_elicitation":
return createCollectFormElicitationTool();
case "mrtr_confirm":
return createMrtrTool();
case "mrtr_two_step":
return createMrtrMultiRoundTool();
case "mrtr_roots":
return createMrtrRootsTool();
case "mrtr_sample":
return createMrtrSamplingTool();
case "mrtr_loop":
return createMrtrLoopTool();
case "mrtr_edge":
return createMrtrEdgeCaseTool();
case "collect_url_elicitation":
return createCollectUrlElicitationTool();
case "url_elicitation_form":
return createUrlElicitationFormTool();
case "send_notification":
return createSendNotificationTool();
case "get_annotated_message":
return createGetAnnotatedMessageTool();
case "get_temp":
return createGetTempTool();
case "get_temp_extra":
return createGetTempExtraTool();
case "list_items":
return createListItemsTool();
case "add_resource":
return createAddResourceTool();
case "remove_resource":
return createRemoveResourceTool();
case "add_tool":
return createAddToolTool();
case "remove_tool":
return createRemoveToolTool();
case "add_prompt":
return createAddPromptTool();
case "remove_prompt":
return createRemovePromptTool();
case "update_resource":
return createUpdateResourceTool();
case "send_progress":
return createSendProgressTool(get("name") as string | undefined);
case "numbered_tools":
return createNumberedTools(Number(get("count")) || 5);
case "simple_task":
return createSimpleTaskTool(
get("name") as string | undefined,
Number(get("delayMs")) || undefined,
);
case "progress_task":
return createProgressTaskTool(
get("name") as string | undefined,
Number(get("delayMs")) || undefined,
Number(get("progressUnits")) || undefined,
);
case "elicitation_task":
return createElicitationTaskTool(get("name") as string | undefined);
case "sampling_task":
return createSamplingTaskTool(
get("name") as string | undefined,
get("samplingText") as string | undefined,
);
case "optional_task":
return createOptionalTaskTool(
get("name") as string | undefined,
Number(get("delayMs")) || undefined,
);
case "forbidden_task":
return createForbiddenTaskTool(
get("name") as string | undefined,
Number(get("delayMs")) || undefined,
);
case "immediate_return_task":
return createImmediateReturnTaskTool(
get("name") as string | undefined,
Number(get("delayMs")) || undefined,
);
case "mcp_app_demo":
return createMcpAppDemoTool();
default:
throw new Error(`Unknown tool preset: ${name}`);
}
}
function resolveResourcePreset(
name: string,
params?: Record<string, unknown>,
): ResourceDefinition | ResourceDefinition[] {
const p = params ?? {};
const get = (k: string) => p[k] as unknown;
switch (name) {
case "architecture":
return createArchitectureResource();
case "test_cwd":
return createTestCwdResource();
case "test_env":
return createTestEnvResource();
case "test_argv":
return createTestArgvResource();
case "numbered_resources":
return createNumberedResources(Number(get("count")) || 3);
case "mcp_app_demo_widget":
return createMcpAppDemoResource();
default:
throw new Error(`Unknown resource preset: ${name}`);
}
}
function resolveResourceTemplatePreset(
name: string,
params?: Record<string, unknown>,
): ResourceTemplateDefinition | ResourceTemplateDefinition[] {
const p = params ?? {};
const get = (k: string) => p[k] as unknown;
switch (name) {
case "file":
return createFileResourceTemplate();
case "user":
return createUserResourceTemplate();
case "numbered_resource_templates":
return createNumberedResourceTemplates(Number(get("count")) || 3);
default:
throw new Error(`Unknown resource template preset: ${name}`);
}
}
function resolvePromptPreset(
name: string,
params?: Record<string, unknown>,
): PromptDefinition | PromptDefinition[] {
const p = params ?? {};
const get = (k: string) => p[k] as unknown;
switch (name) {
case "simple_prompt":
return createSimplePrompt();
case "args_prompt":
return createArgsPrompt();
case "numbered_prompts":
return createNumberedPrompts(Number(get("count")) || 3);
default:
throw new Error(`Unknown prompt preset: ${name}`);
}
}
/**
* Resolve a preset by type and name to definition(s)
*/
export function resolvePreset(
type: PresetType,
name: string,
params?: Record<string, unknown>,
): PresetResult {
switch (type) {
case "tool":
return resolveToolPreset(name, params);
case "resource":
return resolveResourcePreset(name, params);
case "resourceTemplate":
return resolveResourceTemplatePreset(name, params);
case "prompt":
return resolvePromptPreset(name, params);
default:
throw new Error(`Unknown preset type: ${type}`);
}
}