Phase 2: Refactor Sampling/Elicitation cluster to MCP SDK types

Replace ad hoc local types with SDK schema types across the Sampling
and Elicitation group components.

- SamplingRequestPanel: accept `request: CreateMessageRequestParams` +
  `draftResult: CreateMessageResult` instead of 16 flat props; derive
  messages, model preferences, and parameters from the request object;
  drop `tools`/`toolChoice` (not in MCP 2025-11-25 CreateMessageRequest)
- InlineSamplingRequest: accept `request: CreateMessageRequestParams`;
  derive messagePreview and modelHints from the request
- ElicitationFormPanel: accept `request: ElicitRequest['params']` +
  `serverName`; pass requestedSchema directly to SchemaForm
- ElicitationUrlPanel: rename `elicitationId` to `requestId`
- InlineElicitationRequest: accept `request: ElicitRequest['params']`;
  discriminate form vs URL mode on request shape instead of `mode` prop
- PendingClientRequests: unchanged (children-based wrapper)
- Delete local `SamplingTool` type
- Update all stories with real SDK type fixtures

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
cliffhall 2026-04-20 19:57:58 -04:00
commit c1a63cb37d
2 changed files with 326 additions and 299 deletions

View file

@ -1,12 +1,31 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import type { ElicitRequest } from "@modelcontextprotocol/sdk/types.js";
import { fn } from "storybook/test";
import { InlineElicitationRequest } from "./InlineElicitationRequest";
const formRequest = {
message: "Please provide your database connection details.",
requestedSchema: {
type: "object" as const,
properties: {
host: { type: "string" as const, title: "Host" },
port: { type: "string" as const, title: "Port" },
database: { type: "string" as const, title: "Database" },
},
},
} satisfies ElicitRequest["params"];
const urlRequest: ElicitRequest["params"] = {
mode: "url",
message: "Please authenticate via the external URL.",
url: "https://example.com/auth/callback?session=abc123",
elicitationId: "elicit-abc-123",
};
const meta: Meta<typeof InlineElicitationRequest> = {
title: "Groups/InlineElicitationRequest",
component: InlineElicitationRequest,
args: {
message: "Please provide your database connection details.",
queuePosition: "1 of 1",
onChange: fn(),
onSubmit: fn(),
@ -19,32 +38,21 @@ type Story = StoryObj<typeof InlineElicitationRequest>;
export const FormMode: Story = {
args: {
mode: "form",
schema: {
type: "object",
properties: {
host: { type: "string", title: "Host" },
port: { type: "integer", title: "Port" },
database: { type: "string", title: "Database" },
},
required: ["host", "port"],
},
request: formRequest,
values: {},
},
};
export const UrlMode: Story = {
args: {
mode: "url",
url: "https://example.com/auth/callback?session=abc123",
request: urlRequest,
isWaiting: false,
},
};
export const UrlWaiting: Story = {
args: {
mode: "url",
url: "https://example.com/auth/callback?session=abc123",
request: urlRequest,
isWaiting: true,
},
};

View file

@ -8,16 +8,14 @@ import {
Stack,
Text,
} from "@mantine/core";
import type { JsonSchema } from "../SchemaForm/SchemaForm";
import type { ElicitRequest } from "@modelcontextprotocol/sdk/types.js";
import { SchemaForm } from "../SchemaForm/SchemaForm";
import type { JsonSchema } from "../SchemaForm/SchemaForm";
export interface InlineElicitationRequestProps {
mode: "form" | "url";
message: string;
request: ElicitRequest["params"];
queuePosition: string;
schema?: JsonSchema;
values?: Record<string, unknown>;
url?: string;
isWaiting?: boolean;
onChange: (values: Record<string, unknown>) => void;
onSubmit: () => void;
@ -49,45 +47,52 @@ const CompactButton = Button.withProps({
variant: "light",
});
function getBadgeLabel(mode: "form" | "url"): string {
return mode === "form"
function isFormMode(
request: ElicitRequest["params"],
): request is ElicitRequest["params"] & {
requestedSchema: Record<string, unknown>;
} {
return "requestedSchema" in request;
}
function getBadgeLabel(request: ElicitRequest["params"]): string {
return isFormMode(request)
? "elicitation/create (form)"
: "elicitation/create (url)";
}
export function InlineElicitationRequest({
mode,
message,
request,
queuePosition,
schema,
values,
url,
isWaiting,
onChange,
onSubmit,
onCancel,
}: InlineElicitationRequestProps) {
const formMode = isFormMode(request);
return (
<RequestContainer>
<Stack gap="sm">
<Group justify="space-between">
<Badge color="violet">{getBadgeLabel(mode)}</Badge>
<Badge color="violet">{getBadgeLabel(request)}</Badge>
<QueueLabel>{queuePosition}</QueueLabel>
</Group>
<ItalicMessage>{message}</ItalicMessage>
<ItalicMessage>{request.message}</ItalicMessage>
{mode === "form" && schema && (
{formMode && (
<SchemaForm
schema={schema}
schema={request.requestedSchema as JsonSchema}
values={values ?? {}}
onChange={onChange}
/>
)}
{mode === "url" && url && (
{!formMode && "url" in request && (
<>
<Code block>{url}</Code>
<Code block>{(request as { url: string }).url}</Code>
{isWaiting && (
<Group>
<Loader size="xs" />
@ -99,7 +104,7 @@ export function InlineElicitationRequest({
<ActionsRow>
<CompactButton onClick={onCancel}>Cancel</CompactButton>
{mode === "form" && (
{formMode && (
<Button size="xs" onClick={onSubmit}>
Submit
</Button>