@@ -391,14 +391,33 @@ test_api_openai_reasoning_effort() {
391391
392392# ── MCP server ─────────────────────────────────────────────────────────────────
393393
394+ _MCP_ACCEPT=" Accept: application/json, text/event-stream"
395+ _MCP_INIT=' {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}'
396+
397+ # init MCP session: sets MCP_SESSION var
398+ _mcp_init () {
399+ local url=" $1 "
400+ MCP_SESSION=$( curl -s -D - -X POST " $url " \
401+ -H " Content-Type: application/json" -H " $_MCP_ACCEPT " \
402+ -d " $_MCP_INIT " | grep -i " mcp-session-id" | awk ' {print $2}' | tr -d ' \r\n' )
403+ }
404+
405+ # send MCP JSON-RPC with session
406+ _mcp_call () {
407+ local url=" $1 " data=" $2 "
408+ curl -s -X POST " $url " \
409+ -H " Content-Type: application/json" -H " $_MCP_ACCEPT " \
410+ -H " mcp-session-id: $MCP_SESSION " \
411+ -d " $data "
412+ }
413+
394414test_api_mcp_init () {
395415 _api_start " ${API_CONTAINER} -mcp" || return 1
396416
397- # MCP streamable HTTP: POST JSON-RPC initialize to /mcp
398- local out code
399- code=$( curl -s -o /dev/null -w " %{http_code}" -X POST " $API_BASE /mcp" \
400- -H " Content-Type: application/json" \
401- -d ' {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}' )
417+ local code
418+ code=$( curl -s -o /dev/null -w " %{http_code}" -X POST " $API_BASE /mcp/" \
419+ -H " Content-Type: application/json" -H " $_MCP_ACCEPT " \
420+ -d " $_MCP_INIT " )
402421 assert_eq " $code " " 200" " mcp initialize returns 200" || { _api_stop " ${API_CONTAINER} -mcp" ; return 1; }
403422
404423 echo " OK: api_mcp_init"
@@ -408,10 +427,11 @@ test_api_mcp_init() {
408427test_api_mcp_tools_list () {
409428 _api_start " ${API_CONTAINER} -mcp-tl" || return 1
410429
430+ _mcp_init " $API_BASE /mcp/"
431+ assert_not_empty " $MCP_SESSION " " mcp session id" || { _api_stop " ${API_CONTAINER} -mcp-tl" ; return 1; }
432+
411433 local out
412- out=$( curl -sf -X POST " $API_BASE /mcp" \
413- -H " Content-Type: application/json" \
414- -d ' {"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' )
434+ out=$( _mcp_call " $API_BASE /mcp/" ' {"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' )
415435 assert_contains " $out " " claude_run" " mcp tools/list has claude_run" || { _api_stop " ${API_CONTAINER} -mcp-tl" ; return 1; }
416436 assert_contains " $out " " list_files" " mcp tools/list has list_files" || { _api_stop " ${API_CONTAINER} -mcp-tl" ; return 1; }
417437 assert_contains " $out " " read_file" " mcp tools/list has read_file" || { _api_stop " ${API_CONTAINER} -mcp-tl" ; return 1; }
@@ -425,10 +445,11 @@ test_api_mcp_tools_list() {
425445test_api_mcp_claude_run () {
426446 _api_start " ${API_CONTAINER} -mcp-run" || return 1
427447
448+ _mcp_init " $API_BASE /mcp/"
449+
428450 local out
429- out=$( curl -sf -X POST " $API_BASE /mcp" \
430- -H " Content-Type: application/json" \
431- -d ' {"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"claude_run","arguments":{"prompt":"respond with exactly MCPTEST","model":"' " $TEST_MODEL " ' ","no_continue":true}}}' )
451+ out=$( _mcp_call " $API_BASE /mcp/" \
452+ ' {"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"claude_run","arguments":{"prompt":"respond with exactly MCPTEST","model":"' " $TEST_MODEL " ' ","no_continue":true}}}' )
432453 assert_contains " $out " " MCPTEST" " mcp claude_run returns response" || { _api_stop " ${API_CONTAINER} -mcp-run" ; return 1; }
433454
434455 echo " OK: api_mcp_claude_run"
@@ -438,30 +459,28 @@ test_api_mcp_claude_run() {
438459test_api_mcp_file_ops () {
439460 _api_start " ${API_CONTAINER} -mcp-f" || return 1
440461
462+ _mcp_init " $API_BASE /mcp/"
463+
441464 local out
442465
443466 # write
444- out=$( curl -sf -X POST " $API_BASE /mcp" \
445- -H " Content-Type: application/json" \
446- -d ' {"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"write_file","arguments":{"path":"mcptest.txt","content":"hello mcp"}}}' )
467+ out=$( _mcp_call " $API_BASE /mcp/" \
468+ ' {"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"write_file","arguments":{"path":"mcptest.txt","content":"hello mcp"}}}' )
447469 assert_contains " $out " " ok" " mcp write_file ok" || { _api_stop " ${API_CONTAINER} -mcp-f" ; return 1; }
448470
449471 # read
450- out=$( curl -sf -X POST " $API_BASE /mcp" \
451- -H " Content-Type: application/json" \
452- -d ' {"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"read_file","arguments":{"path":"mcptest.txt"}}}' )
472+ out=$( _mcp_call " $API_BASE /mcp/" \
473+ ' {"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"read_file","arguments":{"path":"mcptest.txt"}}}' )
453474 assert_contains " $out " " hello mcp" " mcp read_file returns content" || { _api_stop " ${API_CONTAINER} -mcp-f" ; return 1; }
454475
455476 # list
456- out=$( curl -sf -X POST " $API_BASE /mcp" \
457- -H " Content-Type: application/json" \
458- -d ' {"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"list_files","arguments":{}}}' )
477+ out=$( _mcp_call " $API_BASE /mcp/" \
478+ ' {"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"list_files","arguments":{}}}' )
459479 assert_contains " $out " " mcptest.txt" " mcp list_files shows written file" || { _api_stop " ${API_CONTAINER} -mcp-f" ; return 1; }
460480
461481 # delete
462- out=$( curl -sf -X POST " $API_BASE /mcp" \
463- -H " Content-Type: application/json" \
464- -d ' {"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"delete_file","arguments":{"path":"mcptest.txt"}}}' )
482+ out=$( _mcp_call " $API_BASE /mcp/" \
483+ ' {"jsonrpc":"2.0","id":5,"method":"tools/call","params":{"name":"delete_file","arguments":{"path":"mcptest.txt"}}}' )
465484 assert_contains " $out " " ok" " mcp delete_file ok" || { _api_stop " ${API_CONTAINER} -mcp-f" ; return 1; }
466485
467486 echo " OK: api_mcp_file_ops"
@@ -473,17 +492,23 @@ test_api_mcp_auth() {
473492
474493 # no token → 401
475494 local code
476- code=$( curl -s -o /dev/null -w " %{http_code}" -X POST " $API_BASE /mcp" \
477- -H " Content-Type: application/json" \
478- -d ' {"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}} ' )
495+ code=$( curl -s -o /dev/null -w " %{http_code}" -X POST " $API_BASE /mcp/ " \
496+ -H " Content-Type: application/json" -H " $_MCP_ACCEPT " \
497+ -d " $_MCP_INIT " )
479498 assert_eq " $code " " 401" " mcp no token returns 401" || { _api_stop " ${API_CONTAINER} -mcp-auth" ; return 1; }
480499
481- # correct token → 200
482- code=$( curl -s -o /dev/null -w " %{http_code}" -X POST " $API_BASE /mcp" \
483- -H " Content-Type: application/json" \
500+ # correct token via header → 200
501+ code=$( curl -s -o /dev/null -w " %{http_code}" -X POST " $API_BASE /mcp/ " \
502+ -H " Content-Type: application/json" -H " $_MCP_ACCEPT " \
484503 -H " Authorization: Bearer mcpsecret" \
485- -d ' {"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' )
486- assert_eq " $code " " 200" " mcp correct token returns 200" || { _api_stop " ${API_CONTAINER} -mcp-auth" ; return 1; }
504+ -d " $_MCP_INIT " )
505+ assert_eq " $code " " 200" " mcp correct token via header returns 200" || { _api_stop " ${API_CONTAINER} -mcp-auth" ; return 1; }
506+
507+ # correct token via query param → 200
508+ code=$( curl -s -o /dev/null -w " %{http_code}" -X POST " $API_BASE /mcp/?apiToken=mcpsecret" \
509+ -H " Content-Type: application/json" -H " $_MCP_ACCEPT " \
510+ -d " $_MCP_INIT " )
511+ assert_eq " $code " " 200" " mcp correct token via query param returns 200" || { _api_stop " ${API_CONTAINER} -mcp-auth" ; return 1; }
487512
488513 echo " OK: api_mcp_auth"
489514 _api_stop " ${API_CONTAINER} -mcp-auth"
0 commit comments