diff --git a/clients/web/src/components/groups/ExperimentalFeaturesPanel/ExperimentalFeaturesPanel.stories.tsx b/clients/web/src/components/groups/ExperimentalFeaturesPanel/ExperimentalFeaturesPanel.stories.tsx index 37b133f5..1fddc9eb 100644 --- a/clients/web/src/components/groups/ExperimentalFeaturesPanel/ExperimentalFeaturesPanel.stories.tsx +++ b/clients/web/src/components/groups/ExperimentalFeaturesPanel/ExperimentalFeaturesPanel.stories.tsx @@ -1,16 +1,8 @@ import type { Meta, StoryObj } from "@storybook/react-vite"; import { fn } from "storybook/test"; -import type { - ClientExperimentalToggle, - RequestHistoryItem, -} from "./ExperimentalFeaturesPanel"; +import type { RequestHistoryItem } from "./ExperimentalFeaturesPanel"; import { ExperimentalFeaturesPanel } from "./ExperimentalFeaturesPanel"; -const defaultClientToggles: ClientExperimentalToggle[] = [ - { name: "experimental/customSampling", enabled: false }, - { name: "experimental/batchRequests", enabled: true }, -]; - const meta: Meta = { title: "Groups/ExperimentalFeaturesPanel", component: ExperimentalFeaturesPanel, @@ -23,7 +15,9 @@ const meta: Meta = { onHeaderChange: fn(), onCopyResponse: fn(), onTestCapability: fn(), - clientToggles: defaultClientToggles, + clientExperimental: { + "experimental/batchRequests": {}, + }, customHeaders: [], requestHistory: [], requestDraft: JSON.stringify( @@ -67,6 +61,30 @@ export const NoServerCaps: Story = { }, }; +export const NoneEnabled: Story = { + args: { + clientExperimental: undefined, + }, +}; + +export const AllKnownEnabled: Story = { + args: { + clientExperimental: { + "experimental/customSampling": {}, + "experimental/batchRequests": {}, + }, + }, +}; + +export const MixedKnownAndUnknown: Story = { + args: { + clientExperimental: { + "experimental/customSampling": {}, + "experimental/futureFeatureXyz": {}, + }, + }, +}; + export const WithResponse: Story = { args: { serverExperimental: { diff --git a/clients/web/src/components/groups/ExperimentalFeaturesPanel/ExperimentalFeaturesPanel.tsx b/clients/web/src/components/groups/ExperimentalFeaturesPanel/ExperimentalFeaturesPanel.tsx index 41406a27..81a58507 100644 --- a/clients/web/src/components/groups/ExperimentalFeaturesPanel/ExperimentalFeaturesPanel.tsx +++ b/clients/web/src/components/groups/ExperimentalFeaturesPanel/ExperimentalFeaturesPanel.tsx @@ -15,16 +15,12 @@ import { Title, } from "@mantine/core"; import type { + ClientCapabilities, JSONRPCErrorResponse, JSONRPCResponse, ServerCapabilities, } from "@modelcontextprotocol/sdk/types.js"; -export interface ClientExperimentalToggle { - name: string; - enabled: boolean; -} - export interface HeaderPair { key: string; value: string; @@ -39,7 +35,7 @@ export interface RequestHistoryItem { export interface ExperimentalFeaturesPanelProps { serverExperimental: ServerCapabilities["experimental"]; - clientToggles: ClientExperimentalToggle[]; + clientExperimental: ClientCapabilities["experimental"]; requestDraft: string; response?: JSONRPCResponse | JSONRPCErrorResponse; customHeaders: HeaderPair[]; @@ -54,6 +50,27 @@ export interface ExperimentalFeaturesPanelProps { onTestCapability: (name: string) => void; } +interface ClientToggleMetadata { + label: string; + description: string; +} + +const CLIENT_EXPERIMENTAL_TOGGLE_METADATA: Record< + string, + ClientToggleMetadata +> = { + "experimental/customSampling": { + label: "Custom sampling", + description: + "Allow servers to invoke client-defined sampling strategies via experimental/sampling.* methods.", + }, + "experimental/batchRequests": { + label: "Batch requests", + description: + "Send multiple JSON-RPC requests in a single call and receive a batched response.", + }, +}; + const HintText = Text.withProps({ size: "sm", c: "dimmed", @@ -128,9 +145,20 @@ function formatMethods(methods: string[]): string { return `Methods: ${methods.join(", ")}`; } +function getClientToggleNames( + clientExperimental: ClientCapabilities["experimental"], +): string[] { + const known = Object.keys(CLIENT_EXPERIMENTAL_TOGGLE_METADATA); + const recordKeys = Object.keys(clientExperimental ?? {}); + const unknown = recordKeys.filter( + (name) => !(name in CLIENT_EXPERIMENTAL_TOGGLE_METADATA), + ); + return [...known, ...unknown]; +} + export function ExperimentalFeaturesPanel({ serverExperimental, - clientToggles, + clientExperimental, requestDraft, response, customHeaders, @@ -183,16 +211,24 @@ export function ExperimentalFeaturesPanel({ Client Experimental Capabilities: - {clientToggles.map((toggle) => ( - - onToggleClientCapability(toggle.name, e.currentTarget.checked) - } - /> - ))} + {getClientToggleNames(clientExperimental).map((name) => { + const metadata = CLIENT_EXPERIMENTAL_TOGGLE_METADATA[name]; + const enabled = clientExperimental?.[name] !== undefined; + return ( + + + onToggleClientCapability(name, e.currentTarget.checked) + } + /> + {metadata?.description && ( + {metadata.description} + )} + + ); + })}