@@ -211,5 +211,131 @@ if [ "$VERSION_LINES" -ne 1 ]; then
211211fi
212212echo " OK: version output is clean single line"
213213
214+ echo " "
215+ echo " === Phase 5: MCP stdio transport (agent handshake) ==="
216+
217+ # Test the actual MCP protocol as an agent (Claude Code, OpenCode, etc.) would use it.
218+ # Uses background process + kill instead of timeout (portable across macOS/Linux).
219+
220+ # Helper: run binary in background with input, wait up to N seconds, collect output
221+ mcp_run () {
222+ local input_file=" $1 " output_file=" $2 " max_wait=" ${3:- 10} "
223+ " $BINARY " < " $input_file " > " $output_file " 2> /dev/null &
224+ local pid=$!
225+ local waited=0
226+ while kill -0 " $pid " 2> /dev/null && [ " $waited " -lt " $max_wait " ]; do
227+ sleep 1
228+ waited=$(( waited + 1 ))
229+ done
230+ kill " $pid " 2> /dev/null || true
231+ wait " $pid " 2> /dev/null || true
232+ }
233+
234+ MCP_INPUT=$( mktemp)
235+ MCP_OUTPUT=$( mktemp)
236+ cat > " $MCP_INPUT " << 'MCPEOF '
237+ {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"smoke-test","version":"1.0"}}}
238+ {"jsonrpc":"2.0","method":"notifications/initialized"}
239+ {"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}
240+ MCPEOF
241+
242+ mcp_run " $MCP_INPUT " " $MCP_OUTPUT " 10
243+
244+ # 5a: Verify initialize response (id:1)
245+ if ! grep -q ' "id":1' " $MCP_OUTPUT " ; then
246+ echo " FAIL: no initialize response (id:1) in MCP output"
247+ echo " Output was:"
248+ cat " $MCP_OUTPUT "
249+ rm -f " $MCP_INPUT " " $MCP_OUTPUT "
250+ exit 1
251+ fi
252+ echo " OK: initialize response received (id:1)"
253+
254+ # 5b: Verify tools/list response (id:2) with tool names
255+ if ! grep -q ' "id":2' " $MCP_OUTPUT " ; then
256+ echo " FAIL: no tools/list response (id:2) in MCP output"
257+ echo " Output was:"
258+ cat " $MCP_OUTPUT "
259+ rm -f " $MCP_INPUT " " $MCP_OUTPUT "
260+ exit 1
261+ fi
262+ echo " OK: tools/list response received (id:2)"
263+
264+ # 5c: Verify expected tools are present
265+ for TOOL in index_repository search_graph trace_call_path get_code_snippet search_code; do
266+ if ! grep -q " \" $TOOL \" " " $MCP_OUTPUT " ; then
267+ echo " FAIL: tool '$TOOL ' not found in tools/list response"
268+ rm -f " $MCP_INPUT " " $MCP_OUTPUT "
269+ exit 1
270+ fi
271+ done
272+ echo " OK: all 5 core MCP tools present in tools/list"
273+
274+ # 5d: Verify protocol version in initialize response
275+ if ! grep -q ' "protocolVersion"' " $MCP_OUTPUT " ; then
276+ echo " FAIL: protocolVersion missing from initialize response"
277+ rm -f " $MCP_INPUT " " $MCP_OUTPUT "
278+ exit 1
279+ fi
280+ echo " OK: protocolVersion present in initialize response"
281+
282+ rm -f " $MCP_INPUT " " $MCP_OUTPUT "
283+
284+ # 5e: MCP tool call via JSON-RPC (index + search round-trip)
285+ echo " "
286+ echo " --- Phase 5e: MCP tool call round-trip ---"
287+ MCP_TOOL_INPUT=$( mktemp)
288+ MCP_TOOL_OUTPUT=$( mktemp)
289+
290+ cat > " $MCP_TOOL_INPUT " << TOOLEOF
291+ {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"smoke-test","version":"1.0"}}}
292+ {"jsonrpc":"2.0","method":"notifications/initialized"}
293+ {"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"index_repository","arguments":{"repo_path":"$TMPDIR ","mode":"fast"}}}
294+ {"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"search_graph","arguments":{"name_pattern":"compute"}}}
295+ TOOLEOF
296+
297+ mcp_run " $MCP_TOOL_INPUT " " $MCP_TOOL_OUTPUT " 30
298+
299+ if ! grep -q ' "id":2' " $MCP_TOOL_OUTPUT " ; then
300+ echo " FAIL: no index_repository response (id:2)"
301+ cat " $MCP_TOOL_OUTPUT "
302+ rm -f " $MCP_TOOL_INPUT " " $MCP_TOOL_OUTPUT "
303+ exit 1
304+ fi
305+
306+ if ! grep -q ' "id":3' " $MCP_TOOL_OUTPUT " ; then
307+ echo " FAIL: no search_graph response (id:3)"
308+ cat " $MCP_TOOL_OUTPUT "
309+ rm -f " $MCP_TOOL_INPUT " " $MCP_TOOL_OUTPUT "
310+ exit 1
311+ fi
312+ echo " OK: MCP tool call round-trip (index + search) succeeded"
313+
314+ # 5f: Content-Length framing (OpenCode compatibility)
315+ echo " "
316+ echo " --- Phase 5f: Content-Length framing ---"
317+ MCP_CL_INPUT=$( mktemp)
318+ MCP_CL_OUTPUT=$( mktemp)
319+
320+ INIT_MSG=' {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"cl-test","version":"1.0"}}}'
321+ INIT_LEN=${# INIT_MSG}
322+ printf " Content-Length: %d\r\n\r\n%s" " $INIT_LEN " " $INIT_MSG " > " $MCP_CL_INPUT "
323+
324+ TOOLS_MSG=' {"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'
325+ TOOLS_LEN=${# TOOLS_MSG}
326+ printf " Content-Length: %d\r\n\r\n%s" " $TOOLS_LEN " " $TOOLS_MSG " >> " $MCP_CL_INPUT "
327+
328+ mcp_run " $MCP_CL_INPUT " " $MCP_CL_OUTPUT " 10
329+
330+ if ! grep -q ' "id":1' " $MCP_CL_OUTPUT " || ! grep -q ' "id":2' " $MCP_CL_OUTPUT " ; then
331+ echo " FAIL: Content-Length framed handshake did not produce both responses"
332+ cat " $MCP_CL_OUTPUT "
333+ rm -f " $MCP_CL_INPUT " " $MCP_CL_OUTPUT "
334+ exit 1
335+ fi
336+ echo " OK: Content-Length framing works (OpenCode compatible)"
337+
338+ rm -f " $MCP_CL_INPUT " " $MCP_CL_OUTPUT " " $MCP_TOOL_INPUT " " $MCP_TOOL_OUTPUT "
339+
214340echo " "
215341echo " === smoke-test: ALL PASSED ==="
0 commit comments