diff --git a/clients/web/src/components/groups/ElicitationFormPanel/ElicitationFormPanel.tsx b/clients/web/src/components/groups/ElicitationFormPanel/ElicitationFormPanel.tsx
index 2ec2bdf9..fbe6e4f4 100644
--- a/clients/web/src/components/groups/ElicitationFormPanel/ElicitationFormPanel.tsx
+++ b/clients/web/src/components/groups/ElicitationFormPanel/ElicitationFormPanel.tsx
@@ -1,7 +1,7 @@
import { Alert, Button, Divider, Group, Stack, Text } from "@mantine/core";
import type { ElicitRequestFormParams } from "@modelcontextprotocol/sdk/types.js";
+import type { JsonSchemaType } from "../../../utils/jsonUtils";
import { SchemaForm } from "../SchemaForm/SchemaForm";
-import type { JsonSchema } from "../SchemaForm/SchemaForm";
export interface ElicitationFormPanelProps {
request: ElicitRequestFormParams;
@@ -38,7 +38,7 @@ export function ElicitationFormPanel({
{formatQuoted(request.message)}
diff --git a/clients/web/src/components/groups/ExperimentalFeaturesPanel/ExperimentalFeaturesPanel.stories.tsx b/clients/web/src/components/groups/ExperimentalFeaturesPanel/ExperimentalFeaturesPanel.stories.tsx
index 10bc307e..e2f04be8 100644
--- a/clients/web/src/components/groups/ExperimentalFeaturesPanel/ExperimentalFeaturesPanel.stories.tsx
+++ b/clients/web/src/components/groups/ExperimentalFeaturesPanel/ExperimentalFeaturesPanel.stories.tsx
@@ -1,7 +1,16 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import { fn } from "storybook/test";
+import type {
+ ClientExperimentalToggle,
+ 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,
@@ -14,13 +23,11 @@ const meta: Meta = {
onHeaderChange: fn(),
onCopyResponse: fn(),
onTestCapability: fn(),
- clientCapabilities: [
- { name: "experimental/customSampling", enabled: false },
- { name: "experimental/batchRequests", enabled: true },
- ],
+ clientExperimental: {},
+ clientToggles: defaultClientToggles,
customHeaders: [],
requestHistory: [],
- requestJson: JSON.stringify(
+ requestDraft: JSON.stringify(
{
jsonrpc: "2.0",
id: 1,
@@ -38,14 +45,12 @@ type Story = StoryObj;
export const WithServerCaps: Story = {
args: {
- serverCapabilities: [
- {
- name: "experimental/streaming",
+ serverExperimental: {
+ "experimental/streaming": {
description: "Supports streaming responses for long-running operations",
methods: ["experimental/stream.start", "experimental/stream.cancel"],
},
- {
- name: "experimental/caching",
+ "experimental/caching": {
description: "Server-side response caching with TTL support",
methods: [
"experimental/cache.get",
@@ -53,37 +58,31 @@ export const WithServerCaps: Story = {
"experimental/cache.clear",
],
},
- ],
+ },
},
};
export const NoServerCaps: Story = {
args: {
- serverCapabilities: [],
+ serverExperimental: undefined,
},
};
export const WithResponse: Story = {
args: {
- serverCapabilities: [
- {
- name: "experimental/echo",
+ serverExperimental: {
+ "experimental/echo": {
description: "Echoes back the request for testing",
- methods: ["experimental/echo"],
},
- ],
- responseJson: JSON.stringify(
- {
- jsonrpc: "2.0",
- id: 1,
- result: {
- echo: "Hello from experimental endpoint",
- timestamp: "2026-03-17T10:30:00Z",
- },
+ },
+ response: {
+ jsonrpc: "2.0" as const,
+ id: 1,
+ result: {
+ echo: "Hello from experimental endpoint",
+ timestamp: "2026-03-17T10:30:00Z",
},
- null,
- 2,
- ),
+ },
customHeaders: [
{ key: "X-Custom-Auth", value: "Bearer token123" },
{ key: "X-Request-Id", value: "req-abc-456" },
@@ -91,33 +90,50 @@ export const WithResponse: Story = {
},
};
+export const WithErrorResponse: Story = {
+ args: {
+ serverExperimental: {
+ "experimental/echo": {
+ description: "Echoes back the request for testing",
+ },
+ },
+ response: {
+ jsonrpc: "2.0" as const,
+ id: 1,
+ error: {
+ code: -32601,
+ message: "Method not found",
+ },
+ },
+ },
+};
+
export const WithHistory: Story = {
args: {
- serverCapabilities: [
- {
- name: "experimental/metrics",
+ serverExperimental: {
+ "experimental/metrics": {
description: "Exposes server metrics",
},
- ],
+ },
requestHistory: [
{
- timestamp: "2026-03-17 10:30:15",
+ timestamp: new Date("2026-03-17T10:30:15Z"),
method: "experimental/metrics.get",
status: "success",
durationMs: 42,
},
{
- timestamp: "2026-03-17 10:29:50",
+ timestamp: new Date("2026-03-17T10:29:50Z"),
method: "experimental/echo",
status: "success",
durationMs: 15,
},
{
- timestamp: "2026-03-17 10:28:30",
+ timestamp: new Date("2026-03-17T10:28:30Z"),
method: "experimental/unknown",
status: "error",
durationMs: 120,
},
- ],
+ ] satisfies RequestHistoryItem[],
},
};
diff --git a/clients/web/src/components/groups/ExperimentalFeaturesPanel/ExperimentalFeaturesPanel.tsx b/clients/web/src/components/groups/ExperimentalFeaturesPanel/ExperimentalFeaturesPanel.tsx
index 401fea6b..e245fc41 100644
--- a/clients/web/src/components/groups/ExperimentalFeaturesPanel/ExperimentalFeaturesPanel.tsx
+++ b/clients/web/src/components/groups/ExperimentalFeaturesPanel/ExperimentalFeaturesPanel.tsx
@@ -1,6 +1,7 @@
import {
ActionIcon,
Alert,
+ Badge,
Button,
Card,
Checkbox,
@@ -13,36 +14,37 @@ import {
Textarea,
Title,
} from "@mantine/core";
+import type {
+ ClientCapabilities,
+ JSONRPCErrorResponse,
+ JSONRPCResponse,
+ ServerCapabilities,
+} from "@modelcontextprotocol/sdk/types.js";
-export interface ExperimentalCapability {
- name: string;
- description?: string;
- methods?: string[];
-}
-
-export interface ClientExperimentalCapability {
+export interface ClientExperimentalToggle {
name: string;
enabled: boolean;
}
-export interface KeyValuePair {
+export interface HeaderPair {
key: string;
value: string;
}
export interface RequestHistoryItem {
- timestamp: string;
+ timestamp: Date;
method: string;
status: string;
durationMs: number;
}
export interface ExperimentalFeaturesPanelProps {
- serverCapabilities: ExperimentalCapability[];
- clientCapabilities: ClientExperimentalCapability[];
- requestJson: string;
- responseJson?: string;
- customHeaders: KeyValuePair[];
+ serverExperimental: ServerCapabilities["experimental"];
+ clientExperimental: ClientCapabilities["experimental"];
+ clientToggles: ClientExperimentalToggle[];
+ requestDraft: string;
+ response?: JSONRPCResponse | JSONRPCErrorResponse;
+ customHeaders: HeaderPair[];
requestHistory: RequestHistoryItem[];
onToggleClientCapability: (name: string, enabled: boolean) => void;
onRequestChange: (json: string) => void;
@@ -79,19 +81,38 @@ const RemoveIcon = ActionIcon.withProps({
color: "red",
});
-function formatMethods(methods: string[]): string {
- return `Methods: ${methods.join(", ")}`;
-}
-
function formatDuration(ms: number): string {
return `${ms}ms`;
}
+function formatTimestamp(date: Date): string {
+ return date.toLocaleString();
+}
+
+function formatResponse(
+ response: JSONRPCResponse | JSONRPCErrorResponse,
+): string {
+ return JSON.stringify(response, null, 2);
+}
+
+function isErrorResponse(
+ response: JSONRPCResponse | JSONRPCErrorResponse,
+): response is JSONRPCErrorResponse {
+ return "error" in response;
+}
+
+function getCapabilityEntries(
+ experimental: ServerCapabilities["experimental"],
+): [string, object][] {
+ if (!experimental) return [];
+ return Object.entries(experimental);
+}
+
export function ExperimentalFeaturesPanel({
- serverCapabilities,
- clientCapabilities,
- requestJson,
- responseJson,
+ serverExperimental,
+ clientToggles,
+ requestDraft,
+ response,
customHeaders,
requestHistory,
onToggleClientCapability,
@@ -103,6 +124,8 @@ export function ExperimentalFeaturesPanel({
onCopyResponse,
onTestCapability,
}: ExperimentalFeaturesPanelProps) {
+ const serverEntries = getCapabilityEntries(serverExperimental);
+
return (
@@ -111,19 +134,16 @@ export function ExperimentalFeaturesPanel({
Server Experimental Capabilities:
- {serverCapabilities.length === 0 ? (
+ {serverEntries.length === 0 ? (
No experimental capabilities
) : (
- serverCapabilities.map((cap) => (
-
+ serverEntries.map(([name, value]) => (
+
- {cap.name}
- {cap.description && {cap.description}}
- {cap.methods && cap.methods.length > 0 && (
- {formatMethods(cap.methods)}
- )}
+ {name}
+ {JSON.stringify(value)}
- onTestCapability(cap.name)}>
+ onTestCapability(name)}>
Test →
@@ -136,13 +156,13 @@ export function ExperimentalFeaturesPanel({
Client Experimental Capabilities:
- {clientCapabilities.map((clientCap) => (
+ {clientToggles.map((toggle) => (
- onToggleClientCapability(clientCap.name, e.currentTarget.checked)
+ onToggleClientCapability(toggle.name, e.currentTarget.checked)
}
/>
))}
@@ -182,7 +202,7 @@ export function ExperimentalFeaturesPanel({