Skip to content

Commit 85da090

Browse files
committed
Add MCP stdio transport smoke tests (Phase 5)
Tests the actual agent handshake path that Claude Code, OpenCode, etc. use: - 5a-d: initialize → notifications/initialized → tools/list via bare JSONL - 5e: Full tool call round-trip (index + search) via JSON-RPC - 5f: Content-Length framing (OpenCode compatibility) Uses portable background process + kill instead of timeout command (not available on all macOS configurations).
1 parent 64144a2 commit 85da090

File tree

1 file changed

+126
-0
lines changed

1 file changed

+126
-0
lines changed

scripts/smoke-test.sh

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,5 +211,131 @@ if [ "$VERSION_LINES" -ne 1 ]; then
211211
fi
212212
echo "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+
214340
echo ""
215341
echo "=== smoke-test: ALL PASSED ==="

0 commit comments

Comments
 (0)