@@ -54,23 +54,10 @@ const JsonRpcEnvelopeSchema = v.looseObject({
5454 error : v . optional ( v . looseObject ( { code : v . number ( ) , message : v . string ( ) } ) ) ,
5555} ) ;
5656
57- const InitializeResultSchema = v . looseObject ( {
58- instructions : v . optional ( v . string ( ) ) ,
59- } ) ;
60-
6157const ToolListResultSchema = v . looseObject ( {
6258 tools : v . optional ( v . array ( McpToolDescriptorSchema ) ) ,
6359} ) ;
6460
65- export type McpServerMetadata = {
66- instructions ?: string ;
67- } ;
68-
69- export type McpToolList = {
70- tools : McpToolDescriptor [ ] ;
71- serverMetadata : McpServerMetadata ;
72- } ;
73-
7461/** Forward an MCP `tools/call` JSON-RPC request to a local Storybook MCP server. */
7562export async function callMcpTool (
7663 record : StorybookInstanceRecord ,
@@ -92,23 +79,14 @@ export async function listMcpTools(
9279 record : StorybookInstanceRecord ,
9380 fetchImpl : typeof fetch = fetch
9481) : Promise < McpToolDescriptor [ ] > {
95- const { tools } = await listMcpToolsWithServerMetadata ( record , fetchImpl ) ;
96- return tools ;
97- }
98-
99- /** List tools and include server metadata from the preceding MCP `initialize` response. */
100- export async function listMcpToolsWithServerMetadata (
101- record : StorybookInstanceRecord ,
102- fetchImpl : typeof fetch = fetch
103- ) : Promise < McpToolList > {
104- const { result, serverMetadata } = await sendJsonRpcRequest (
82+ const { result } = await sendJsonRpcRequest (
10583 record ,
10684 'tools/list' ,
10785 { } ,
10886 ToolListResultSchema ,
10987 fetchImpl
11088 ) ;
111- return { tools : result . tools ?? [ ] , serverMetadata } ;
89+ return result . tools ?? [ ] ;
11290}
11391
11492const REQUEST_HEADERS = {
@@ -117,29 +95,21 @@ const REQUEST_HEADERS = {
11795 [ STORYBOOK_MCP_PROXY_HEADER ] : STORYBOOK_MCP_PROXY_HEADER_VALUE ,
11896} ;
11997
120- type InitializeMcpSessionResult = {
121- sessionId : string | null ;
122- serverMetadata : McpServerMetadata ;
123- } ;
124-
12598/**
12699 * Send a minimal MCP `initialize` request carrying {@link MCP_CLIENT_INFO} and return the session
127- * id plus any best-effort server metadata from the initialize response .
100+ * id when the server assigns one .
128101 *
129102 * The session id is MCP Streamable HTTP spec behavior, not a tmcp implementation detail: the
130103 * server assigns it during initialization, returns it in the `Mcp-Session-Id` response header,
131104 * and associates the session's clientInfo with later requests echoing that header. The same
132- * initialize result can also carry server instructions, so metadata is parsed from the response
133- * body even when no session id is assigned .
105+ * initialize response body is still consumed so the follow-up request cannot race ahead of the
106+ * server storing clientInfo for telemetry segmentation .
134107 *
135108 * The handshake is best-effort — when it fails (or a future server ignores sessions), the actual
136- * request proceeds without a session and keeps working; only the telemetry segmentation and
137- * initialize metadata are lost, and error reporting stays anchored on the real call. It shares the
138- * full {@link REQUEST_TIMEOUT_MS} budget rather than a tighter one: `storybook ai --help` renders the
139- * server instructions carried here, and the only thing that slows the handshake is the dev server
140- * still starting up — which the command must wait through anyway. A tighter budget would just drop
141- * the instructions on that first slow request while the command list (sent right after) comes back
142- * fine.
109+ * request proceeds without a session and keeps working; only telemetry segmentation is lost, and
110+ * error reporting stays anchored on the real call. It shares the full {@link REQUEST_TIMEOUT_MS}
111+ * budget rather than a tighter one because the only thing that slows the handshake is the dev
112+ * server still starting up, which the command must wait through anyway.
143113 *
144114 * Sessions are deliberately one-shot: each JSON-RPC request gets its own handshake and the session
145115 * is never reused or closed. A CLI invocation makes one request on the happy path (two on error
@@ -153,7 +123,7 @@ type InitializeMcpSessionResult = {
153123async function initializeMcpSession (
154124 target : string ,
155125 fetchImpl : typeof fetch
156- ) : Promise < InitializeMcpSessionResult > {
126+ ) : Promise < string | null > {
157127 try {
158128 const response = await fetchImpl ( target , {
159129 method : 'POST' ,
@@ -173,22 +143,18 @@ async function initializeMcpSession(
173143 const sessionId = response . headers . get ( 'mcp-session-id' ) ;
174144 if ( ! response . ok ) {
175145 await response . body ?. cancel ( ) ;
176- return { sessionId : null , serverMetadata : { } } ;
146+ return null ;
177147 }
178148
179- let serverMetadata : McpServerMetadata = { } ;
180149 try {
181- serverMetadata = parseInitializeServerMetadata (
182- await readJsonRpcResponse ( response , target ) ,
183- target
184- ) ;
150+ await response . text ( ) ;
185151 } catch {
186- // The initialize request is best-effort metadata ; a malformed response must not block the
152+ // The initialize request is best-effort telemetry setup ; a malformed body must not block the
187153 // actual tools/list or tools/call request from preserving its existing behavior.
188154 }
189- return { sessionId, serverMetadata } ;
155+ return sessionId ;
190156 } catch {
191- return { sessionId : null , serverMetadata : { } } ;
157+ return null ;
192158 }
193159}
194160
@@ -212,15 +178,15 @@ async function sendJsonRpcRequest<TResult>(
212178 params : unknown ,
213179 resultSchema : v . GenericSchema < unknown , TResult > ,
214180 fetchImpl : typeof fetch
215- ) : Promise < { result : TResult ; serverMetadata : McpServerMetadata } > {
181+ ) : Promise < { result : TResult } > {
216182 const endpoint = record . mcp . endpoint ;
217183 if ( ! endpoint ) {
218184 throw new Error ( `The Storybook instance at ${ record . cwd } has no server endpoint registered` ) ;
219185 }
220186
221187 const target = new URL ( endpoint , record . url ) . href ;
222188
223- const { sessionId, serverMetadata } = await initializeMcpSession ( target , fetchImpl ) ;
189+ const sessionId = await initializeMcpSession ( target , fetchImpl ) ;
224190
225191 const response = await fetchImpl ( target , {
226192 method : 'POST' ,
@@ -254,7 +220,7 @@ async function sendJsonRpcRequest<TResult>(
254220 if ( ! result . success ) {
255221 throw unexpectedShapeError ( target ) ;
256222 }
257- return { result : result . output , serverMetadata } ;
223+ return { result : result . output } ;
258224}
259225
260226function unexpectedShapeError ( target : string ) : Error {
@@ -263,8 +229,7 @@ function unexpectedShapeError(target: string): Error {
263229
264230/**
265231 * Unwrap a parsed JSON-RPC payload into its `result`, or report why it isn't usable. The command
266- * path throws on the reported error; the best-effort initialize-metadata parse falls back to empty
267- * — sharing this keeps the envelope handling in one place.
232+ * path throws on the reported error.
268233 */
269234function unwrapJsonRpcResult (
270235 payload : unknown ,
@@ -286,21 +251,6 @@ function unwrapJsonRpcResult(
286251 return { ok : true , result : envelope . output . result } ;
287252}
288253
289- function parseInitializeServerMetadata ( payload : unknown , target : string ) : McpServerMetadata {
290- const unwrapped = unwrapJsonRpcResult ( payload , target ) ;
291- if ( ! unwrapped . ok ) {
292- return { } ;
293- }
294-
295- const result = v . safeParse ( InitializeResultSchema , unwrapped . result ) ;
296- if ( ! result . success ) {
297- return { } ;
298- }
299-
300- const instructions = result . output . instructions ?. trim ( ) ;
301- return instructions ? { instructions } : { } ;
302- }
303-
304254async function readJsonRpcResponse ( response : Response , endpoint : string ) : Promise < unknown > {
305255 const contentType = ( response . headers . get ( 'content-type' ) ?? '' ) . toLowerCase ( ) ;
306256 const body = await response . text ( ) ;
0 commit comments