|
211 | 211 | Base.close(timer) |
212 | 212 | end |
213 | 213 |
|
214 | | - @testset "Protocol Version Validation" begin |
| 214 | + @testset "Protocol Version Negotiation" begin |
| 215 | + # Per MCP spec: version negotiation happens at JSON-RPC level. |
| 216 | + # Server MUST respond with a version it supports (not error). |
| 217 | + # Client then decides if it can work with that version. |
| 218 | + |
215 | 219 | port = 15090 + rand(1:1000) |
216 | | - |
| 220 | + |
217 | 221 | transport = HttpTransport(port=port, protocol_version="2025-06-18") |
218 | | - |
| 222 | + |
219 | 223 | server = mcp_server( |
220 | 224 | name = "version-test", |
221 | 225 | version = "1.0.0" |
222 | 226 | ) |
223 | | - |
| 227 | + |
224 | 228 | server.transport = transport |
225 | 229 | ModelContextProtocol.connect(transport) |
226 | | - |
| 230 | + |
227 | 231 | server_task = @async start!(server) |
228 | 232 | sleep(2) |
229 | | - |
230 | | - # First, properly initialize the server |
| 233 | + |
| 234 | + # Test 1: Client requests newer version, server responds with its version |
231 | 235 | init_response = HTTP.post( |
232 | 236 | "http://127.0.0.1:$port/", |
233 | 237 | ["Content-Type" => "application/json", |
234 | | - "MCP-Protocol-Version" => "2025-06-18", |
| 238 | + "MCP-Protocol-Version" => "2025-11-25", # Client sends newer version |
235 | 239 | "Accept" => "application/json, text/event-stream"], |
236 | 240 | JSON3.write(Dict( |
237 | 241 | "jsonrpc" => "2.0", |
238 | 242 | "method" => "initialize", |
239 | 243 | "params" => Dict( |
240 | | - "protocolVersion" => "2025-06-18", |
| 244 | + "protocolVersion" => "2025-11-25", # Client requests newer version |
241 | 245 | "capabilities" => Dict(), |
242 | 246 | "clientInfo" => Dict("name" => "test", "version" => "1.0") |
243 | 247 | ), |
244 | | - "id" => 0 |
| 248 | + "id" => 1 |
245 | 249 | )) |
246 | 250 | ) |
247 | 251 | @test init_response.status == 200 |
| 252 | + result = JSON3.read(String(init_response.body)) |
| 253 | + @test haskey(result, "result") |
| 254 | + # Server responds with its supported version (not error) |
| 255 | + @test result["result"]["protocolVersion"] == "2025-06-18" |
| 256 | + |
248 | 257 | session_id = HTTP.header(init_response, "Mcp-Session-Id", "") |
249 | | - |
250 | | - # Request with wrong protocol version header - should fail |
251 | | - try |
252 | | - response = HTTP.post( |
253 | | - "http://127.0.0.1:$port/", |
254 | | - ["Content-Type" => "application/json", |
255 | | - "MCP-Protocol-Version" => "2024-11-05", # Wrong version |
256 | | - "Accept" => "application/json, text/event-stream"], |
257 | | - JSON3.write(Dict( |
258 | | - "jsonrpc" => "2.0", |
259 | | - "method" => "initialize", |
260 | | - "params" => Dict( |
261 | | - "protocolVersion" => "2025-06-18", |
262 | | - "capabilities" => Dict(), |
263 | | - "clientInfo" => Dict("name" => "test", "version" => "1.0") |
264 | | - ), |
265 | | - "id" => 1 |
266 | | - )) |
267 | | - ) |
268 | | - @test false # Should not succeed |
269 | | - catch e |
270 | | - if e isa HTTP.ExceptionRequest.StatusError |
271 | | - @test e.response.status == 400 # Should return 400 for wrong protocol version |
272 | | - # Verify the error message |
273 | | - body = String(e.response.body) |
274 | | - msg = JSON3.read(body) |
275 | | - @test msg["error"]["code"] == -32602 |
276 | | - @test contains(msg["error"]["message"], "protocol version") |
277 | | - else |
278 | | - rethrow(e) |
279 | | - end |
280 | | - end |
281 | | - |
282 | | - # Request with wrong protocol version in params (but correct header) |
283 | | - # This is a different case - the transport accepts it but the server should reject |
| 258 | + |
| 259 | + # Test 2: Client requests older version, server still responds with its version |
284 | 260 | response = HTTP.post( |
285 | 261 | "http://127.0.0.1:$port/", |
286 | 262 | ["Content-Type" => "application/json", |
287 | | - "MCP-Protocol-Version" => "2025-06-18", |
288 | | - "Mcp-Session-Id" => session_id, |
| 263 | + "MCP-Protocol-Version" => "2024-11-05", # Older version header |
289 | 264 | "Accept" => "application/json, text/event-stream"], |
290 | 265 | JSON3.write(Dict( |
291 | 266 | "jsonrpc" => "2.0", |
292 | 267 | "method" => "initialize", |
293 | 268 | "params" => Dict( |
294 | | - "protocolVersion" => "2024-11-05", # Wrong version |
| 269 | + "protocolVersion" => "2024-11-05", # Older version |
295 | 270 | "capabilities" => Dict(), |
296 | 271 | "clientInfo" => Dict("name" => "test", "version" => "1.0") |
297 | 272 | ), |
298 | 273 | "id" => 2 |
299 | 274 | )) |
300 | 275 | ) |
301 | | - |
302 | | - # Should return error |
303 | | - @test response.status == 200 # Still 200 for JSON-RPC errors |
| 276 | + @test response.status == 200 |
304 | 277 | result = JSON3.read(String(response.body)) |
305 | | - @test haskey(result, "error") |
306 | | - @test result["error"]["code"] == -32602 # Invalid params |
307 | | - |
| 278 | + @test haskey(result, "result") |
| 279 | + # Server responds with its version, client decides compatibility |
| 280 | + @test result["result"]["protocolVersion"] == "2025-06-18" |
| 281 | + |
308 | 282 | # Clean up |
309 | 283 | server.active = false |
310 | 284 | ModelContextProtocol.close(transport) |
311 | | - |
| 285 | + |
312 | 286 | timer = Timer(2) |
313 | 287 | while !istaskdone(server_task) && isopen(timer) |
314 | 288 | sleep(0.1) |
|
0 commit comments