Commit068d3d3cRecorded4 Sep 2026Repositorysigil-mcp

test: prove the legacy handshake on stdio, not only over HTTP

Message

The dual-era initialize change affects every existing consumer -- courier, folio and apiary all speak stdio with a legacy handshake -- but the only coverage was over HTTP. A regression would have broken them at connect time, which is not somewhere a test would obviously look.

Adds stdio coverage: each supported legacy revision is echoed rather than normalised, an unrecognised request gets a legacy counter-offer and never the modern revision (which has no initialize handshake at all), and the full mcp-server-run loop answers a legacy handshake over real ports rather than through the dispatcher directly.

Verified against a real consumer with a control, which is the part that matters. Courier built against the RELEASED sigil-mcp answers a client asking for 2025-06-18 with "2025-11-25"; built against this branch it answers "2025-06-18". Courier's own claude/channel capability and serverInfo are unchanged, and its 120 tests pass.

So this is a fix for existing servers, not only groundwork for a new transport: every sigil-mcp server currently misreports its version to a pinned client, and Codex pins 2025-06-18 by default.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Changed
 test/test-server.sgl | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 60 insertions(+), 1 deletion(-)
Diff
test/test-server.sglmodified
@@ -94,10 +94,69 @@
94
(result (jsonrpc-response-result resp)))
95
(assert-true (jsonrpc-response? resp))
96
(assert-equal 1 (jsonrpc-response-id resp))
+97
;; A client that names no version gets our newest legacy revision.
98
(assert-equal "2025-11-25" (assoc-ref 'protocolVersion result))
99
(let ((info (assoc-ref 'serverInfo result)))
100
(assert-equal "test" (assoc-ref 'name info))
−100
(assert-equal "1.0" (assoc-ref 'version info))))))
+101
(assert-equal "1.0" (assoc-ref 'version info)))))
+102
+103
;; These cover the STDIO path specifically. Existing consumers -- courier,
+104
;; folio, apiary -- all speak stdio with a legacy handshake, so a
+105
;; regression here breaks them silently at connect time rather than
+106
;; anywhere a test would obviously look.
+107
+108
(test "a legacy client's requested version is echoed back"
+109
;; Answering with a fixed version regardless of the request is what
+110
;; makes a pinned client disconnect: it receives a version string it
+111
;; does not know and hangs up.
+112
(let* ((s (mcp-server name: "test" version: "1.0"))
+113
(msg (jsonrpc-request id: 1 method: "initialize"
+114
params: (dict protocolVersion: "2025-06-18")))
+115
(result (jsonrpc-response-result (mcp-server-handle-message s msg))))
+116
(assert-equal "2025-06-18" (assoc-ref 'protocolVersion result))))
+117
+118
(test "each supported legacy revision is echoed, not normalised"
+119
(let ((s (mcp-server name: "test" version: "1.0")))
+120
(for-each
+121
(lambda (v)
+122
(let* ((msg (jsonrpc-request id: 1 method: "initialize"
+123
params: (dict protocolVersion: v)))
+124
(result (jsonrpc-response-result
+125
(mcp-server-handle-message s msg))))
+126
(assert-equal v (assoc-ref 'protocolVersion result))))
+127
(list "2025-11-25" "2025-06-18" "2025-03-26" "2024-11-05"))))
+128
+129
(test "an unknown requested version gets a LEGACY counter-offer"
+130
;; Never the modern revision: it has no initialize handshake at all, so
+131
;; a client that just sent one could not speak it.
+132
(let* ((s (mcp-server name: "test" version: "1.0"))
+133
(msg (jsonrpc-request id: 1 method: "initialize"
+134
params: (dict protocolVersion: "1999-01-01")))
+135
(result (jsonrpc-response-result (mcp-server-handle-message s msg)))
+136
(answered (assoc-ref 'protocolVersion result)))
+137
(assert-equal "2025-11-25" answered)
+138
(assert-false (equal? mcp-current-version answered))))
+139
+140
(test "the full stdio loop answers a legacy handshake"
+141
;; Drives mcp-server-run over a real string port pair, the way a stdio
+142
;; consumer does, rather than calling the dispatcher directly.
+143
(let* ((s (mcp-server name: "test" version: "1.0"))
+144
(input (open-input-string
+145
(string-append
+146
(json-encode
+147
(dict jsonrpc: "2.0" id: 1 method: "initialize"
+148
params: (dict protocolVersion: "2025-06-18")))
+149
"\n")))
+150
(output (open-output-string)))
+151
(parameterize ((current-input-port input)
+152
(current-output-port output))
+153
(mcp-server-run s load-env?: #f))
+154
(let* ((line (car (filter (lambda (l) (not (string-empty? l)))
+155
(string-split (get-output-string output) "\n"))))
+156
(resp (json-decode line))
+157
(result (dict-ref resp result:)))
+158
(assert-equal "2025-06-18" (dict-ref result protocolVersion:))
+159
(assert-true (dict-ref result capabilities: #f))))))
160
161
;; ============================================================
162
;; Ping