@@ -178,7 +178,19 @@ export class McpClient {
178178 } ,
179179 timeoutMs
180180 )
181- . then ( ( ) => {
181+ . then ( ( result ) => {
182+ // Validate protocol version from server response (per MCP spec §4.2.1.2)
183+ const initResult = result as { protocolVersion ?: string } | undefined ;
184+ const serverVersion = initResult ?. protocolVersion ;
185+ if ( serverVersion && serverVersion !== "2025-03-26" && serverVersion !== "2024-11-05" ) {
186+ reject (
187+ new Error (
188+ `Unsupported MCP protocol version "${ serverVersion } " from server "${ this . serverName } ". ` +
189+ `Client supports 2025-03-26 and 2024-11-05.`
190+ )
191+ ) ;
192+ return ;
193+ }
182194 // Send initialized notification
183195 this . sendNotification ( "notifications/initialized" ) ;
184196 resolve ( ) ;
@@ -302,36 +314,54 @@ export class McpClient {
302314 try {
303315 const parsed : unknown = JSON . parse ( line ) ;
304316
305- // Handle notifications (no id field — server-initiated)
306- if ( parsed && typeof parsed === "object" && ! ( "id" in parsed ) ) {
307- const notification = parsed as JsonRpcNotification ;
308- if ( this . notificationHandler && typeof notification . method === "string" ) {
309- try {
310- this . notificationHandler ( notification . method , notification . params ) ;
311- } catch {
312- // Swallow handler errors to avoid crashing the reader loop
317+ // Handle JSON-RPC batch (array of requests/notifications/responses)
318+ // Per MCP 2025-03-26 §4.1.1.3: implementations MUST support receiving batches.
319+ if ( Array . isArray ( parsed ) ) {
320+ for ( const item of parsed ) {
321+ if ( item && typeof item === "object" ) {
322+ this . handleSingleMessage ( item ) ;
313323 }
314324 }
315325 return ;
316326 }
317327
318- // Handle responses to our requests
319- const message = parsed as JsonRpcResponse ;
320- if ( message . id !== undefined && this . pendingRequests . has ( message . id ) ) {
321- const pending = this . pendingRequests . get ( message . id ) ! ;
322- this . pendingRequests . delete ( message . id ) ;
323- clearTimeout ( pending . timer ) ;
324- if ( message . error ) {
325- pending . reject ( this . withStderr ( `MCP error: ${ message . error . message } ` ) ) ;
326- } else {
327- pending . resolve ( message . result ) ;
328- }
328+ // Handle single message
329+ if ( parsed && typeof parsed === "object" ) {
330+ this . handleSingleMessage ( parsed ) ;
329331 }
330332 } catch {
331333 // Ignore unparseable lines
332334 }
333335 }
334336
337+ private handleSingleMessage ( msg : object ) : void {
338+ // Handle notifications (no id field — server-initiated)
339+ if ( ! ( "id" in msg ) ) {
340+ const notification = msg as unknown as JsonRpcNotification ;
341+ if ( this . notificationHandler && typeof notification . method === "string" ) {
342+ try {
343+ this . notificationHandler ( notification . method , notification . params ) ;
344+ } catch {
345+ // Swallow handler errors to avoid crashing the reader loop
346+ }
347+ }
348+ return ;
349+ }
350+
351+ // Handle responses to our requests
352+ const message = msg as unknown as JsonRpcResponse ;
353+ if ( message . id !== undefined && this . pendingRequests . has ( message . id ) ) {
354+ const pending = this . pendingRequests . get ( message . id ) ! ;
355+ this . pendingRequests . delete ( message . id ) ;
356+ clearTimeout ( pending . timer ) ;
357+ if ( message . error ) {
358+ pending . reject ( this . withStderr ( `MCP error: ${ message . error . message } ` ) ) ;
359+ } else {
360+ pending . resolve ( message . result ) ;
361+ }
362+ }
363+ }
364+
335365 private withNpxYesArg ( command : string , args : string [ ] ) : string [ ] {
336366 const executable = path
337367 . basename ( command )
0 commit comments