返回 CodeWhale
test_support.rs
根目录 / crates / mcp / src / test_support.rs
1 //! Test-only helpers. Compiled under `#[cfg(all(test, unix))]` only, so
2 //! nothing here can be reached by a real `codewhale mcp-server` run, and
3 //! Windows — where the POSIX-sh fixture cannot run and its consumers are
4 //! `#[cfg(unix)]` — does not compile it as dead code.
5
6 use std::path::{Path, PathBuf};
7
8 /// A minimal POSIX-sh MCP server used to prove that responses come from a
9 /// spawned process rather than from an in-process stub.
10 ///
11 /// It is written in `sh` rather than Rust or Python so the test depends on
12 /// nothing beyond the shell every unix CI runner already has.
13 pub const FAKE_MCP_SERVER_SH: &str = r#"#!/bin/sh
14 while IFS= read -r line; do
15 id=$(printf '%s' "$line" | sed -n 's/.*"id":\([0-9][0-9]*\).*/\1/p')
16 method=$(printf '%s' "$line" | sed -n 's/.*"method":"\([^"]*\)".*/\1/p')
17 if [ -z "$id" ]; then
18 continue
19 fi
20 case "$method" in
21 initialize)
22 printf '{"jsonrpc":"2.0","id":%s,"result":{"protocolVersion":"2024-11-05","capabilities":{"tools":{}},"serverInfo":{"name":"fake-mcp","version":"0"}}}\n' "$id"
23 ;;
24 tools/list)
25 printf '{"jsonrpc":"2.0","id":%s,"result":{"tools":[{"name":"add","description":"add two numbers"}]}}\n' "$id"
26 ;;
27 tools/call)
28 name=$(printf '%s' "$line" | sed -n 's/.*"name":"\([^"]*\)".*/\1/p')
29 if [ "$name" = "add" ]; then
30 printf '{"jsonrpc":"2.0","id":%s,"result":{"content":[{"type":"text","text":"5"}]}}\n' "$id"
31 else
32 printf '{"jsonrpc":"2.0","id":%s,"error":{"code":-32602,"message":"unknown tool: '"$name"'"}}\n' "$id"
33 fi
34 ;;
35 *)
36 printf '{"jsonrpc":"2.0","id":%s,"error":{"code":-32601,"message":"unsupported method"}}\n' "$id"
37 ;;
38 esac
39 done
40 "#;
41
42 /// Owns a temporary directory holding [`FAKE_MCP_SERVER_SH`], removing it on
43 /// drop.
44 pub struct FakeServerScript {
45 dir: PathBuf,
46 script: PathBuf,
47 }
48
49 impl FakeServerScript {
50 pub fn path(&self) -> &Path {
51 &self.script
52 }
53 }
54
55 impl Drop for FakeServerScript {
56 fn drop(&mut self) {
57 let _ = std::fs::remove_dir_all(&self.dir);
58 }
59 }
60
61 /// Write the fake server to a unique temp directory and return a guard.
62 pub fn write_fake_mcp_server(label: &str) -> FakeServerScript {
63 let dir = std::env::temp_dir().join(format!(
64 "codewhale-mcp-{label}-{}-{:?}",
65 std::process::id(),
66 std::thread::current().id()
67 ));
68 std::fs::create_dir_all(&dir).expect("fake MCP server dir");
69 let script = dir.join("server.sh");
70 std::fs::write(&script, FAKE_MCP_SERVER_SH).expect("write fake MCP server");
71 FakeServerScript { dir, script }
72 }
73
73 lines RUST