import type { ClientCapabilities, InitializeResult, } from "@modelcontextprotocol/client"; import type { Meta, StoryObj } from "@storybook/react-vite"; import { expect, userEvent, within } from "storybook/test"; import { ConnectionInfoContent, SERVER_INFO_NOT_REPORTED_LABEL, } from "./ConnectionInfoContent"; const fullResult: InitializeResult = { protocolVersion: "2025-03-26", serverInfo: { name: "Everything Server", version: "2.1.0" }, capabilities: { tools: { listChanged: true }, resources: { subscribe: true, listChanged: true }, prompts: { listChanged: true }, logging: {}, completions: {}, }, instructions: "This server provides access to the project management system. Use the list_projects tool first to discover available projects before querying tasks. Rate limiting applies: max 60 requests per minute.", }; const fullClientCaps: ClientCapabilities = { roots: { listChanged: true }, sampling: {}, elicitation: {}, experimental: {}, }; const meta: Meta = { title: "Groups/ConnectionInfoContent", component: ConnectionInfoContent, }; export default meta; type Story = StoryObj; export const FullCapabilities: Story = { args: { initializeResult: fullResult, clientCapabilities: fullClientCaps, transport: "stdio", }, }; export const ModernEra: Story = { args: { initializeResult: { protocolVersion: "2026-07-28", serverInfo: { name: "Modern Server", version: "2.0.0" }, capabilities: { tools: { listChanged: true }, resources: { subscribe: true }, // The Server Extensions row reads the negotiated server capabilities // (era-transparent), not discoverResult — carry it here too so the // modern story demonstrates the extension. (#1740) extensions: { "io.modelcontextprotocol/tasks": {}, }, }, }, clientCapabilities: fullClientCaps, transport: "streamable-http", protocolEra: "modern", discoverResult: { supportedVersions: ["2026-07-28", "2025-11-25"], serverInfo: { name: "Modern Server", version: "2.0.0" }, capabilities: { tools: { listChanged: true }, resources: { subscribe: true }, extensions: { "io.modelcontextprotocol/tasks": {}, }, }, }, }, // SEP-2663 moved task support to the extension map, so the Tasks capability // row must read it there on a modern connection — otherwise a tasks-capable // server shows ✗ while listing the extension below (#1887). play: async ({ canvasElement }) => { const canvas = within(canvasElement); const tasksRow = canvas.getByText("Tasks"); await expect(tasksRow.previousElementSibling).toHaveTextContent("✓"); }, }; // A modern server that omitted the optional `_meta` serverInfo stamp (#1772). // `initializeResult.serverInfo` here is App's client-side catalog fallback, and // `serverInfoReported: false` tells the modal not to present it as server-sent — // Name and Version read "— (not reported by server)". export const ServerInfoNotReported: Story = { args: { initializeResult: { protocolVersion: "2026-07-28", // The catalog name App synthesizes; must NOT surface as the reported name. serverInfo: { name: "my-catalog-name", version: "" }, capabilities: { tools: { listChanged: true } }, }, serverInfoReported: false, clientCapabilities: { roots: { listChanged: true } }, transport: "streamable-http", protocolEra: "modern", // A real modern connection that skipped the `_meta` serverInfo stamp still // has a discover result — with `supportedVersions` + capabilities, just no // `serverInfo`. Including it makes the story accurate and shows the Discovery // section doesn't leak a name either. discoverResult: { supportedVersions: ["2026-07-28", "2025-11-25"], capabilities: { tools: { listChanged: true } }, }, }, play: async ({ canvasElement }) => { const canvas = within(canvasElement); expect(canvas.queryByText("my-catalog-name")).not.toBeInTheDocument(); expect(canvas.getAllByText(SERVER_INFO_NOT_REPORTED_LABEL)).toHaveLength(2); }, }; export const MinimalCapabilities: Story = { args: { initializeResult: { protocolVersion: "2025-03-26", serverInfo: { name: "Simple Server", version: "1.0.0" }, capabilities: { tools: { listChanged: false }, }, }, clientCapabilities: { roots: { listChanged: true }, }, transport: "streamable-http", }, }; export const WithInstructions: Story = { args: { initializeResult: fullResult, clientCapabilities: { roots: { listChanged: true }, sampling: {}, }, transport: "stdio", }, }; export const WithOAuth: Story = { args: { initializeResult: { protocolVersion: "2025-03-26", serverInfo: { name: "Authenticated Server", version: "3.0.0" }, capabilities: { tools: { listChanged: true }, resources: { subscribe: true }, }, }, clientCapabilities: { roots: { listChanged: true }, }, transport: "streamable-http", oauth: { protocol: "standard", authorized: true, authUrl: "https://auth.example.com/oauth2/authorize", scopes: ["read", "write", "admin"], accessToken: "eyJhbGciOiJSUzI1NiIs...truncated", }, }, }; // An authorization server that also returned an `id_token` (#2019). Shown for // inspection only — decoding it is display-only, and it is never used as a // credential. export const WithOAuthIdToken: Story = { args: { initializeResult: { protocolVersion: "2025-03-26", serverInfo: { name: "OIDC-fronted Server", version: "3.0.0" }, capabilities: { tools: { listChanged: true } }, }, clientCapabilities: { roots: { listChanged: true } }, transport: "streamable-http", oauth: { protocol: "standard", authorized: true, authUrl: "https://auth.example.com/oauth2/authorize", scopes: ["openid", "email", "read"], accessToken: "opaque-access-token", // header {"alg":"none"} / payload {"sub":"user-42","email":"a@b.test"} idToken: "eyJhbGciOiJub25lIn0.eyJzdWIiOiJ1c2VyLTQyIiwiZW1haWwiOiJhQGIudGVzdCJ9.", }, }, play: async ({ canvasElement }) => { const canvas = within(canvasElement); expect(canvas.getByText("ID Token")).toBeInTheDocument(); // Only the ID token is a JWT here, so exactly one decode toggle is offered. await userEvent.click( canvas.getByRole("button", { name: "Decode JWT for ID Token" }), ); expect(canvas.getByText(/"sub": "user-42"/)).toBeInTheDocument(); }, };