The Cancel button POSTed `notifications/cancelled` on every transport. On a 2026-07-28 Streamable HTTP connection that is the wrong signal — the spec makes closing the request's SSE response stream the cancellation signal there and says the notification is neither required nor expected — so a spec-compliant server acknowledged it 202 and dropped it while the tool ran to completion. The SDK already implements the fork, off `transport.hasPerRequestStream`. Every Inspector connection is wrapped in `MessageTrackingTransport`, which did not forward that flag, so the SDK read `undefined` and took the stdio branch on every client — the CLI and TUI included, whose real `StreamableHTTPClientTransport` advertises it correctly. The web client needed three more links: its browser transport answers for the upstream transport that lives on the backend, so it advertises the flag for `streamable-http` and applies the SDK's `requestSignal` to its `POST /api/mcp/send`; and `/api/mcp/send`, held open for the whole call, forwards the browser's disconnect to the upstream `transport.send`. The test fixture's `toWebRequest` built a Web Request with no `signal`, so no fixture server could observe a disconnect at all — without that, a test asserting the cancel reaches the server passes vacuously. stdio and SSE keep `notifications/cancelled`: they multiplex every request over one shared channel and have no per-request stream to close. So does the legacy era, which predates the mechanism. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013hzwzS8UvBsr4yZm7o7sev Signed-off-by: cliffhall <cliff@futurescale.com>
327 lines
9.6 KiB
TypeScript
327 lines
9.6 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,
|
|
createNullableFieldsTool,
|
|
createGetEnvTool,
|
|
createAddTool,
|
|
createGetSumTool,
|
|
createGetWeatherTool,
|
|
createInvalidHeaderTool,
|
|
createSpecErrorTriggerTool,
|
|
createWriteToStderrTool,
|
|
createCollectSampleTool,
|
|
createListRootsTool,
|
|
createCollectFormElicitationTool,
|
|
createMrtrTool,
|
|
createMrtrMultiRoundTool,
|
|
createMrtrRootsTool,
|
|
createMrtrSamplingTool,
|
|
createMrtrLoopTool,
|
|
createMrtrEdgeCaseTool,
|
|
createMrtrEmptyResultTool,
|
|
createCollectUrlElicitationTool,
|
|
createUrlElicitationFormTool,
|
|
createSendNotificationTool,
|
|
createSlowTaskTool,
|
|
createGetAnnotatedMessageTool,
|
|
createGetTempTool,
|
|
createGetTempExtraTool,
|
|
createListItemsTool,
|
|
createAddResourceTool,
|
|
createRemoveResourceTool,
|
|
createAddToolTool,
|
|
createRemoveToolTool,
|
|
createAddPromptTool,
|
|
createRemovePromptTool,
|
|
createUpdateResourceTool,
|
|
createSendProgressTool,
|
|
createNumberedTools,
|
|
createSimpleTaskTool,
|
|
createProgressTaskTool,
|
|
createElicitationTaskTool,
|
|
createSamplingTaskTool,
|
|
createOptionalTaskTool,
|
|
createForbiddenTaskTool,
|
|
createImmediateReturnTaskTool,
|
|
createMcpAppDemoTool,
|
|
createMcpAppDemoResource,
|
|
createAppElicitationTool,
|
|
createAppElicitationResource,
|
|
createMrtrAppElicitationTool,
|
|
createArchitectureResource,
|
|
createTestCwdResource,
|
|
createTestEnvResource,
|
|
createTestArgvResource,
|
|
createNumberedResources,
|
|
createFileResourceTemplate,
|
|
createUserResourceTemplate,
|
|
createNumberedResourceTemplates,
|
|
createRfc6570ResourceTemplates,
|
|
createRfc6570BaseResource,
|
|
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 "record_shipment":
|
|
case "nullable_fields":
|
|
return createNullableFieldsTool();
|
|
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 "mrtr_empty":
|
|
return createMrtrEmptyResultTool();
|
|
case "collect_url_elicitation":
|
|
return createCollectUrlElicitationTool();
|
|
case "url_elicitation_form":
|
|
return createUrlElicitationFormTool();
|
|
case "send_notification":
|
|
return createSendNotificationTool();
|
|
case "slow_task":
|
|
return createSlowTaskTool();
|
|
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();
|
|
case "app_choose_option":
|
|
return createAppElicitationTool();
|
|
case "mrtr_app_choose_option":
|
|
return createMrtrAppElicitationTool();
|
|
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(get("domain") as string | undefined);
|
|
case "choose_option_app":
|
|
return createAppElicitationResource();
|
|
case "rfc6570_base":
|
|
return createRfc6570BaseResource();
|
|
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);
|
|
case "rfc6570_templates":
|
|
return createRfc6570ResourceTemplates();
|
|
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}`);
|
|
}
|
|
}
|