@@ -14,6 +14,7 @@ export class StatelessClientScenario implements Scenario {
1414
1515 private server : http . Server | null = null ;
1616 private checks : ConformanceCheck [ ] = [ ] ;
17+ private negotiatedVersion : string | null = null ;
1718
1819 async start ( ) : Promise < ScenarioUrls > {
1920 return new Promise ( ( resolve , reject ) => {
@@ -57,7 +58,43 @@ export class StatelessClientScenario implements Scenario {
5758 req . on ( 'end' , ( ) => {
5859 const request = JSON . parse ( body ) ;
5960
60- // TEST 1: Verify client can call server/discover
61+ // Extract version and headers
62+ const meta = request . params ?. _meta ;
63+ const currentVersion = meta ?. [ 'io.modelcontextprotocol/protocolVersion' ] ;
64+ const headerVersion = req . headers [ 'mcp-protocol-version' ] ;
65+
66+ // [HTTP] Sends MCP-Protocol-Version header on every request, equal to _meta.protocolVersion
67+ if ( currentVersion ) {
68+ this . checks . push ( {
69+ id : 'client-sends-version-header' ,
70+ name : 'ClientSendsVersionHeader' ,
71+ description :
72+ 'Client sends MCP-Protocol-Version header equal to _meta.protocolVersion' ,
73+ status : headerVersion === currentVersion ? 'SUCCESS' : 'FAILURE' ,
74+ timestamp : new Date ( ) . toISOString ( ) ,
75+ specReferences : [ { id : 'SEP-2575' , url : '' } ]
76+ } ) ;
77+ }
78+
79+ // Sends a consistent protocolVersion once chosen
80+ if ( currentVersion ) {
81+ if ( ! this . negotiatedVersion ) {
82+ this . negotiatedVersion = currentVersion ;
83+ } else {
84+ this . checks . push ( {
85+ id : 'client-consistent-version' ,
86+ name : 'ClientConsistentVersion' ,
87+ description :
88+ 'Client sends a consistent protocolVersion once chosen' ,
89+ status :
90+ currentVersion === this . negotiatedVersion ? 'SUCCESS' : 'FAILURE' ,
91+ timestamp : new Date ( ) . toISOString ( ) ,
92+ specReferences : [ { id : 'SEP-2575' , url : '' } ]
93+ } ) ;
94+ }
95+ }
96+
97+ // Verify client can call server/discover
6198 if ( request . method === 'server/discover' ) {
6299 this . checks . push ( {
63100 id : 'client-calls-discover' ,
@@ -68,14 +105,14 @@ export class StatelessClientScenario implements Scenario {
68105 specReferences : [ { id : 'SEP-2575' , url : '' } ]
69106 } ) ;
70107
71- // Respond with valid discovery payload to keep client happy
108+ // Respond with valid discovery payload
72109 res . writeHead ( 200 , { 'Content-Type' : 'application/json' } ) ;
73110 res . end (
74111 JSON . stringify ( {
75112 jsonrpc : '2.0' ,
76113 id : request . id ,
77114 result : {
78- supportedVersions : [ '2026-06-18' ] ,
115+ supportedVersions : [ DRAFT_PROTOCOL_VERSION ] ,
79116 capabilities : { } ,
80117 serverInfo : { name : 'test' , version : '1.0' }
81118 }
@@ -84,8 +121,24 @@ export class StatelessClientScenario implements Scenario {
84121 return ;
85122 }
86123
87- // TEST 2: Verify inline _meta on every request
88- const meta = request . params ?. _meta ;
124+ // [STDIO] Cancels by sending notifications/cancelled with the request id
125+ if ( request . method === 'notifications/cancelled' ) {
126+ this . checks . push ( {
127+ id : 'client-cancels-by-notification' ,
128+ name : 'ClientCancelsByNotification' ,
129+ description :
130+ 'Client cancels by sending notifications/cancelled with the request id' ,
131+ status : 'SUCCESS' ,
132+ timestamp : new Date ( ) . toISOString ( ) ,
133+ specReferences : [ { id : 'SEP-2575' , url : '' } ]
134+ } ) ;
135+
136+ res . writeHead ( 200 , { 'Content-Type' : 'application/json' } ) ;
137+ res . end ( JSON . stringify ( { jsonrpc : '2.0' , id : request . id , result : { } } ) ) ;
138+ return ;
139+ }
140+
141+ // Verify inline _meta on every request
89142 const hasProtocolVersion =
90143 meta ?. [ 'io.modelcontextprotocol/protocolVersion' ] ;
91144 const hasClientInfo = meta ?. [ 'io.modelcontextprotocol/clientInfo' ] ;
@@ -106,6 +159,27 @@ export class StatelessClientScenario implements Scenario {
106159 details : { meta }
107160 } ) ;
108161
162+ // Handle long running task for cancellation testing
163+ if (
164+ request . method === 'tools/call' &&
165+ request . params ?. name === 'long_running_task'
166+ ) {
167+ // Do not respond immediately, wait for client to abort (req close) or send cancel notification
168+ req . on ( 'close' , ( ) => {
169+ if ( ! res . writableEnded ) {
170+ this . checks . push ( {
171+ id : 'client-cancels-by-closing-stream' ,
172+ name : 'ClientCancelsByClosingStream' ,
173+ description : 'Client cancels by closing the stream (request)' ,
174+ status : 'SUCCESS' ,
175+ timestamp : new Date ( ) . toISOString ( ) ,
176+ specReferences : [ { id : 'SEP-2575' , url : '' } ]
177+ } ) ;
178+ }
179+ } ) ;
180+ return ; // Keep request open
181+ }
182+
109183 // Return generic response to unblock client
110184 res . writeHead ( 200 , { 'Content-Type' : 'application/json' } ) ;
111185 res . end ( JSON . stringify ( { jsonrpc : '2.0' , id : request . id , result : { } } ) ) ;
0 commit comments