Skip to content

Commit 1f10d05

Browse files
committed
Create BATS tests for limactl-mcp
Signed-off-by: Jan Dubois <[email protected]>
1 parent 6ef4970 commit 1f10d05

File tree

1 file changed

+123
-0
lines changed

1 file changed

+123
-0
lines changed

hack/bats/tests/mcp.bats

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
# SPDX-FileCopyrightText: Copyright The Lima Authors
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
load "../helpers/load"
5+
6+
NAME=bats
7+
8+
# TODO Move helper functions to shared location
9+
run_yq() {
10+
run -0 --separate-stderr limactl yq "$@"
11+
}
12+
13+
json_edit() {
14+
limactl yq --input-format json --output-format json --indent 0 "$@"
15+
}
16+
17+
# TODO The reusable Lima instance setup is copied from preserve-env.bats
18+
# TODO and should be factored out into helper functions.
19+
local_setup_file() {
20+
if [[ -n "${LIMA_BATS_REUSE_INSTANCE:-}" ]]; then
21+
run limactl list --format '{{.Status}}' "$NAME"
22+
[[ $status == 0 ]] && [[ $output == "Running" ]] && return
23+
fi
24+
limactl unprotect "$NAME" || :
25+
limactl delete --force "$NAME" || :
26+
# Make sure that the host agent doesn't inherit file handles 3 or 4.
27+
# Otherwise bats will not finish until the host agent exits.
28+
limactl start --yes --name "$NAME" template://default 3>&- 4>&-
29+
}
30+
31+
local_teardown_file() {
32+
if [[ -z "${LIMA_BATS_REUSE_INSTANCE:-}" ]]; then
33+
limactl delete --force "$NAME"
34+
fi
35+
}
36+
37+
local_setup() {
38+
coproc MCP { limactl mcp serve "$NAME"; }
39+
PID=$!
40+
ID=0
41+
42+
mcp initialize '{"protocolVersion":"2025-03-26"}'
43+
44+
run_yq .serverInfo.name <<<"$output"
45+
assert_output lima
46+
}
47+
48+
local_teardown() {
49+
kill "${PID:?}" 2>&1 >/dev/null || :
50+
}
51+
52+
mcp() {
53+
local method=$1
54+
local params=${2:-}
55+
56+
local request
57+
printf -v request '{"jsonrpc":"2.0","id":%d,"method":"%s"}' "$((++ID))" "$method"
58+
if [[ -n $params ]]; then
59+
request=$(json_edit ".params=${params}" <<<"$request")
60+
fi
61+
62+
# send request to MCP server stdin
63+
echo "$request" >&"${MCP[1]}"
64+
65+
# read response from MCP server stdout with 5s timeout
66+
local json
67+
read -t 5 -r json <&"${MCP[0]}"
68+
69+
# verify that the response matches the request; also validates the output is valid JSON
70+
run_yq .id <<<"$json"
71+
assert_output "$ID"
72+
73+
# there must be no error object in the response
74+
run_yq .error <<<"$json"
75+
assert_output "null"
76+
77+
# set $output to .result
78+
run_yq .result <<<"$json"
79+
}
80+
81+
tools_call() {
82+
local name=$1
83+
local args=${2:-}
84+
85+
local params
86+
printf -v params '{"name":"%s"}' "$name"
87+
if [[ -n $args ]]; then
88+
params=$(json_edit ".arguments=${args}" <<<"$params")
89+
fi
90+
mcp tools/call "$params"
91+
}
92+
93+
@test 'list tools' {
94+
mcp tools/list
95+
run_yq '.tools[].name' <<<"$output"
96+
assert_line glob
97+
assert_line list_directory
98+
assert_line read_file
99+
assert_line run_shell_command
100+
assert_line search_file_content
101+
assert_line write_file
102+
}
103+
104+
@test 'run shell command' {
105+
run -0 limactl shell "$NAME" cat /etc/os-release
106+
expected=$output
107+
108+
tools_call run_shell_command '{"directory":"/etc","command":["cat","os-release"]}'
109+
json=$output
110+
111+
run_yq '.content[0].type' <<<"$json"
112+
assert_output "text"
113+
114+
# The text property is a string encoding of an embedded JSON object
115+
run_yq '.content[0].text' <<<"$json"
116+
text=$output
117+
118+
run_yq '.exit_code' <<<"$text"
119+
assert_output 0
120+
121+
run_yq '.stdout' <<<"$text"
122+
assert_output "$expected"
123+
}

0 commit comments

Comments
 (0)