Skip to content

Commit d951b51

Browse files
authored
claude: Add MCP accses to sandbox environment (#13149)
Viral was complaining that Claude was inefficient at doing Yggdrasil work, to which I said "Skill issue". In particular, Claude does not have the capability to interactively inspect the sandbox in order to diagnose failures - it must modify the script and re-run it. This takes forever. This PR attempts to fix that by providing a (vibe-coded) repo-specific MCP server that simply launches a the provided script with `--debug` and proxies inputs and outputs to claude in the MCP format that it understands. The tool structure deliberately mirrors the ordinary claude bash/edit tools, to make sure that claude's tuning applies. The implementaiton is extremely jank, but does seem to basically work. I may do some more work to clean it up, but I also expect that Claude will gain the ability to natively interact with stdin at some point at which point it could just call `--debug` itself, so if that happens first we can delete this again.
1 parent f58aece commit d951b51

File tree

5 files changed

+202
-50
lines changed

5 files changed

+202
-50
lines changed

.ci/Manifest.toml

Lines changed: 36 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# This file is machine-generated - editing it directly is not advised
22

3-
julia_version = "1.12.4"
3+
julia_version = "1.12.5"
44
manifest_format = "2.0"
5-
project_hash = "307630b5433268691f0cd1ef7f1c6c820417465f"
5+
project_hash = "3d57187bd139cceee67cd1da87ff1247dbf9c5ef"
66

77
[[deps.ArgParse]]
88
deps = ["Logging", "TextWrap"]
@@ -57,9 +57,9 @@ version = "1.43.0"
5757

5858
[[deps.Binutils_jll]]
5959
deps = ["Artifacts", "JLLWrappers", "Libdl", "Zlib_jll", "Zstd_jll"]
60-
git-tree-sha1 = "d35dde1c7201e7a1d7ca4ce4d62499a3a220539a"
60+
git-tree-sha1 = "603339925f4f0e6d3a3969a7aa391ec80f8a3ad5"
6161
uuid = "489e263e-5428-50b0-a723-147a141b401e"
62-
version = "2.45.1+0"
62+
version = "2.45.0+0"
6363

6464
[[deps.BitFlags]]
6565
git-tree-sha1 = "0691e34b3bb8be9307330f88d1a3c3f25466c24d"
@@ -72,6 +72,14 @@ git-tree-sha1 = "1b96ea4a01afe0ea4090c5c8039690672dd13f2e"
7272
uuid = "6e34b625-4abd-537c-b88f-471c36dfa7a0"
7373
version = "1.0.9+0"
7474

75+
[[deps.ClaudeMCPTools]]
76+
deps = ["Base64", "JSON", "Random", "Sockets", "UUIDs"]
77+
git-tree-sha1 = "9f9878794e6ff7faa086aa980aa8b15a139e3b09"
78+
repo-rev = "master"
79+
repo-url = "https://github.com/JuliaBench/ClaudeMCPTools.jl.git"
80+
uuid = "b9bb1685-6a70-41d7-9793-2f9fb633d966"
81+
version = "0.1.0"
82+
7583
[[deps.ChunkCodecCore]]
7684
git-tree-sha1 = "1a3ad7e16a321667698a19e77362b35a1e94c544"
7785
uuid = "0b6fb165-00bc-4d37-ab8b-79f91016dbe1"
@@ -193,22 +201,17 @@ git-tree-sha1 = "5e6fe50ae7f23d171f44e311c2960294aaa0beb5"
193201
uuid = "cd3eb016-35fb-5094-929b-558a96fad6f3"
194202
version = "1.10.19"
195203

196-
[[deps.HashArrayMappedTries]]
197-
git-tree-sha1 = "2eaa69a7cab70a52b9687c8bf950a5a93ec895ae"
198-
uuid = "076d061b-32b6-4027-95e0-9a2c6f6d7e74"
199-
version = "0.2.0"
200-
201204
[[deps.Hiccup]]
202205
deps = ["MacroTools", "Test"]
203206
git-tree-sha1 = "6187bb2d5fcbb2007c39e7ac53308b0d371124bd"
204207
uuid = "9fb69e20-1954-56bb-a84f-559cc56a8ff7"
205208
version = "0.2.2"
206209

207210
[[deps.HistoricalStdlibVersions]]
208-
deps = ["Pkg", "PrecompileTools"]
209-
git-tree-sha1 = "abdd6437ede003ee3c412b7da655a8dde356a573"
211+
deps = ["Pkg"]
212+
git-tree-sha1 = "7e72d0ce251105cebeab1e156977af2346cbe558"
210213
uuid = "6df8b67a-e8a0-4029-b4b7-ac196fe72102"
211-
version = "2.0.6"
214+
version = "2.0.2"
212215

213216
[[deps.InlineStrings]]
214217
git-tree-sha1 = "8f3d257792a522b4601c24a577954b0a8cd7334d"
@@ -280,11 +283,6 @@ git-tree-sha1 = "1c602b1127f4751facb671441ca72715cc95938a"
280283
uuid = "dd4b983a-f0e5-5f8d-a1b7-129d4a5fb1ac"
281284
version = "2.10.3+0"
282285

283-
[[deps.LazilyInitializedFields]]
284-
git-tree-sha1 = "0f2da712350b020bc3957f269c9caad516383ee0"
285-
uuid = "0e77f7df-68c5-4e49-93ce-4cd80f5598bf"
286-
version = "1.3.0"
287-
288286
[[deps.LibCURL]]
289287
deps = ["LibCURL_jll", "MozillaCACerts_jll"]
290288
uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21"
@@ -395,10 +393,10 @@ uuid = "d8793406-e978-5875-9003-1fc021f44a92"
395393
version = "0.4.4"
396394

397395
[[deps.OpenSSL]]
398-
deps = ["BitFlags", "Dates", "MozillaCACerts_jll", "NetworkOptions", "OpenSSL_jll", "Sockets"]
399-
git-tree-sha1 = "1d1aaa7d449b58415f97d2839c318b70ffb525a0"
396+
deps = ["BitFlags", "Dates", "MozillaCACerts_jll", "OpenSSL_jll", "Sockets"]
397+
git-tree-sha1 = "f1a7e086c677df53e064e0fdd2c9d0b0833e3f6e"
400398
uuid = "4d8831e6-92b7-49fb-bdf8-b643e874388c"
401-
version = "1.6.1"
399+
version = "1.5.0"
402400

403401
[[deps.OpenSSL_jll]]
404402
deps = ["Artifacts", "Libdl"]
@@ -411,9 +409,9 @@ uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
411409
version = "1.8.1"
412410

413411
[[deps.OutputCollectors]]
414-
git-tree-sha1 = "d5e1ff7302df52d4bae4115b2dc093856f216e99"
412+
git-tree-sha1 = "5d3f2b3b2e2a9d7d6f1774c78e94530ac7f360cc"
415413
uuid = "6c11c7d4-943b-4e2b-80de-f2cfc2930a8c"
416-
version = "0.1.2"
414+
version = "0.1.1"
417415

418416
[[deps.Parsers]]
419417
deps = ["Dates", "PrecompileTools", "UUIDs"]
@@ -450,15 +448,15 @@ version = "0.2.0"
450448

451449
[[deps.PrecompileTools]]
452450
deps = ["Preferences"]
453-
git-tree-sha1 = "07a921781cab75691315adc645096ed5e370cb77"
451+
git-tree-sha1 = "5aa36f7049a63a1528fe8f7c3f2113413ffd4e1f"
454452
uuid = "aea7be01-6a6a-4083-8856-8a6e6704d82a"
455-
version = "1.3.3"
453+
version = "1.2.1"
456454

457455
[[deps.Preferences]]
458456
deps = ["TOML"]
459-
git-tree-sha1 = "522f093a29b31a93e34eaea17ba055d850edea28"
457+
git-tree-sha1 = "0f27480397253da18fe2c12a4ba4eb9eb208bf3d"
460458
uuid = "21216c6a-2e73-6563-6e65-726566657250"
461-
version = "1.5.1"
459+
version = "1.5.0"
462460

463461
[[deps.Printf]]
464462
deps = ["Unicode"]
@@ -467,9 +465,9 @@ version = "1.11.0"
467465

468466
[[deps.ProgressMeter]]
469467
deps = ["Distributed", "Printf"]
470-
git-tree-sha1 = "fbb92c6c56b34e1a2c4c36058f68f332bec840e7"
468+
git-tree-sha1 = "13c5103482a8ed1536a54c08d0e742ae3dca2d42"
471469
uuid = "92933f4c-e287-5a05-a399-4b506db050ca"
472-
version = "1.11.0"
470+
version = "1.10.4"
473471

474472
[[deps.REPL]]
475473
deps = ["InteractiveUtils", "JuliaSyntaxHighlighting", "Markdown", "Sockets", "StyledStrings", "Unicode"]
@@ -492,17 +490,11 @@ git-tree-sha1 = "3e66fa3f5110ad24f2ce227d4bf2d86af116b963"
492490
uuid = "4418983a-e44d-11e8-3aec-9789530b3b3e"
493491
version = "1.11.0"
494492

495-
[[deps.RegistryInstances]]
496-
deps = ["LazilyInitializedFields", "Pkg", "TOML", "Tar"]
497-
git-tree-sha1 = "ffd19052caf598b8653b99404058fce14828be51"
498-
uuid = "2792f1a3-b283-48e8-9a74-f99dce5104f3"
499-
version = "0.1.0"
500-
501493
[[deps.RegistryTools]]
502494
deps = ["LibGit2", "Pkg", "SHA", "UUIDs"]
503-
git-tree-sha1 = "3ccf9d64e95b4a02a707f57f4032a4c3d58f23ba"
495+
git-tree-sha1 = "c0ac28884fab9ae94ed8cf3448aa950afc2ff9c1"
504496
uuid = "d1eb7eb1-105f-429d-abf5-b0f65cb9e2c4"
505-
version = "2.4.2"
497+
version = "2.3.0"
506498

507499
[[deps.Requires]]
508500
deps = ["UUIDs"]
@@ -514,17 +506,11 @@ version = "1.3.1"
514506
uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"
515507
version = "0.7.0"
516508

517-
[[deps.ScopedValues]]
518-
deps = ["HashArrayMappedTries", "Logging"]
519-
git-tree-sha1 = "c3b2323466378a2ba15bea4b2f73b081e022f473"
520-
uuid = "7e506255-f358-4e82-b7e4-beb19740aa63"
521-
version = "1.5.0"
522-
523509
[[deps.Scratch]]
524510
deps = ["Dates"]
525-
git-tree-sha1 = "9b81b8393e50b7d4e6d0a9f14e192294d3b7c109"
511+
git-tree-sha1 = "3bac05bc7e74a75fd9cba4295cde4045d9fe2386"
526512
uuid = "6c6a2e73-6563-6170-7368-637461726353"
527-
version = "1.3.0"
513+
version = "1.2.1"
528514

529515
[[deps.Serialization]]
530516
uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
@@ -612,9 +598,9 @@ version = "0.3.0"
612598

613599
[[deps.TimeZones]]
614600
deps = ["Artifacts", "Dates", "Downloads", "InlineStrings", "Mocking", "Printf", "Scratch", "TZJData", "Unicode", "p7zip_jll"]
615-
git-tree-sha1 = "d422301b2a1e294e3e4214061e44f338cafe18a2"
601+
git-tree-sha1 = "06f4f1f3e8ff09e42e59b043a747332e88e01aba"
616602
uuid = "f269a46b-ccf7-5d73-abea-4c690281aa53"
617-
version = "1.22.2"
603+
version = "1.22.1"
618604

619605
[deps.TimeZones.extensions]
620606
TimeZonesRecipesBaseExt = "RecipesBase"
@@ -643,15 +629,15 @@ version = "1.11.0"
643629

644630
[[deps.XZ_jll]]
645631
deps = ["Artifacts", "JLLWrappers", "Libdl"]
646-
git-tree-sha1 = "9cce64c0fdd1960b597ba7ecda2950b5ed957438"
632+
git-tree-sha1 = "fee71455b0aaa3440dfdd54a9a36ccef829be7d4"
647633
uuid = "ffd25f8a-64ca-5728-b0f7-c24cf3aae800"
648-
version = "5.8.2+0"
634+
version = "5.8.1+0"
649635

650636
[[deps.ZMQ]]
651637
deps = ["FileWatching", "PrecompileTools", "Printf", "Sockets", "ZeroMQ_jll"]
652-
git-tree-sha1 = "5f1c7008e2258c61af0eafef8c1f536b9fffbbd2"
638+
git-tree-sha1 = "c398a0a905ed975308b433f013af388b65b10cb6"
653639
uuid = "c2297ded-f4af-51ae-bb23-16f91089e4e1"
654-
version = "1.5.1"
640+
version = "1.5.0"
655641

656642
[[deps.ZeroMQ_jll]]
657643
deps = ["Artifacts", "JLLWrappers", "Libdl", "libsodium_jll"]

.ci/Project.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
[deps]
22
BinaryBuilder = "12aac903-9f7c-5d81-afc2-d9565ea332ae"
33
BinaryBuilderBase = "7f725544-6523-48cd-82d1-3fa08ff4056e"
4+
ClaudeMCPTools = "b9bb1685-6a70-41d7-9793-2f9fb633d966"
45
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
56
Downloads = "f43a241f-c20a-4ad4-852c-f6b1247861c6"
67
HistoricalStdlibVersions = "6df8b67a-e8a0-4029-b4b7-ac196fe72102"
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#!/usr/bin/env julia
2+
#
3+
# Helper script to launch an interactive BinaryBuilder sandbox shell.
4+
# Called by the MCP server as a subprocess with piped IO.
5+
#
6+
# Usage: julia --project=.ci run_shell.jl <platform> [workdir]
7+
#
8+
# The sandbox's stdin/stdout are inherited from this process,
9+
# so the MCP server can communicate with the shell via pipes.
10+
11+
using BinaryBuilder
12+
using BinaryBuilderBase
13+
14+
platform_str = ARGS[1]
15+
workdir = length(ARGS) >= 2 ? ARGS[2] : pwd()
16+
17+
platform = parse(BinaryBuilderBase.Platform, platform_str)
18+
cd(workdir)
19+
20+
BinaryBuilder.runshell(platform)

.claude/mcp-bb-sandbox/server.jl

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
#!/usr/bin/env julia
2+
#
3+
# BinaryBuilder Sandbox MCP Server for Yggdrasil
4+
#
5+
# Thin wrapper around ClaudeMCPTools that registers a sessioned bash tool
6+
# configured to launch BinaryBuilder sandbox sessions.
7+
#
8+
# Dependencies: ClaudeMCPTools (from .ci project, activated via --project flag in .mcp.json)
9+
10+
using ClaudeMCPTools
11+
12+
# ═══════════════════════════════════════════════════════════════
13+
# Constants
14+
# ═══════════════════════════════════════════════════════════════
15+
16+
const SERVER_DIR = @__DIR__
17+
const PROJECT_ROOT = dirname(dirname(SERVER_DIR)) # .claude/mcp-bb-sandbox -> .claude -> root
18+
const CI_PROJECT = joinpath(PROJECT_ROOT, ".ci")
19+
const RUN_SHELL_SCRIPT = joinpath(SERVER_DIR, "run_shell.jl")
20+
21+
const JULIA_CMD = ["julia", "+1.12"]
22+
23+
function log_msg(msg)
24+
println(stderr, "[bb-sandbox] ", msg)
25+
flush(stderr)
26+
end
27+
28+
# ═══════════════════════════════════════════════════════════════
29+
# BinaryBuilder-specific session start command
30+
# ═══════════════════════════════════════════════════════════════
31+
32+
function make_sandbox_cmd(params::AbstractDict)
33+
platform = get(params, "platform", "x86_64-linux-gnu")
34+
bt_path = get(params, "build_tarballs_path", nothing)
35+
debug_mode = get(params, "debug_mode", "end")
36+
37+
metadata = Dict{String,Any}("platform" => platform)
38+
39+
local cmd
40+
if bt_path !== nothing
41+
bt_path = abspath(bt_path)
42+
isfile(bt_path) || error("build_tarballs.jl not found: $bt_path")
43+
bt_dir = dirname(bt_path)
44+
metadata["build_tarballs_path"] = bt_path
45+
cmd = Cmd(`$(JULIA_CMD) --project=$CI_PROJECT $bt_path --debug=$debug_mode $platform`; dir=bt_dir)
46+
log_msg("Using build_tarballs: $bt_path (debug=$debug_mode)")
47+
else
48+
cmd = Cmd(`$(JULIA_CMD) --project=$CI_PROJECT $RUN_SHELL_SCRIPT $platform $PROJECT_ROOT`; dir=PROJECT_ROOT)
49+
end
50+
51+
return (cmd, metadata)
52+
end
53+
54+
# ═══════════════════════════════════════════════════════════════
55+
# Session formatting
56+
# ═══════════════════════════════════════════════════════════════
57+
58+
function format_sandbox_session(session::BashSession)
59+
uptime = round(Int, time() - session.created_at)
60+
running = process_running(session.process)
61+
platform = get(session.metadata, "platform", "unknown")
62+
bt = get(session.metadata, "build_tarballs_path", nothing)
63+
bt_str = bt !== nothing ? " | Build: $bt" : ""
64+
return "- ID: $(session.id) | Platform: $platform | Status: $(running ? "running" : "exited") | Uptime: $(uptime)s$bt_str"
65+
end
66+
67+
# ═══════════════════════════════════════════════════════════════
68+
# Server setup
69+
# ═══════════════════════════════════════════════════════════════
70+
71+
manager = SessionManager(make_sandbox_cmd;
72+
log=log_msg,
73+
format_session=format_sandbox_session,
74+
ready_timeout_s=300.0)
75+
76+
server = MCPServer(;
77+
name="bb-sandbox",
78+
version="1.0.0",
79+
instructions="BinaryBuilder sandbox manager for Yggdrasil. " *
80+
"Use sandbox_start to launch an interactive cross-compilation environment, " *
81+
"sandbox_exec to run commands inside it, and sandbox_stop when done.")
82+
83+
# Register sessioned bash tools for sandbox management
84+
register_sessioned_bash!(server, manager;
85+
prefix="sandbox",
86+
start_description="""Launch a new BinaryBuilder sandbox session for interactive cross-compilation debugging.
87+
88+
Returns a session_id to use with sandbox_exec. The sandbox provides a full BinaryBuilder environment with cross-compilation toolchains, compilers (CC, CXX, FC), and standard environment variables (prefix, WORKSPACE, target, libdir, bindir, etc.).
89+
90+
The session is persistent: working directory changes, environment variable modifications, and other shell state carry over between sandbox_exec calls.
91+
92+
Starting a session may take 1-3 minutes on first run as compiler shards are downloaded and mounted.""",
93+
exec_description="""Execute a bash command inside a running BinaryBuilder sandbox session.
94+
95+
The session is persistent - working directory and environment changes carry over between commands. Both stdout and stderr are captured and returned interleaved.
96+
97+
Inside the sandbox you have access to cross-compilers (\$CC, \$CXX, \$FC), the target platform triplet (\$target), install prefix (\$prefix), and all standard BinaryBuilder environment variables.""",
98+
stop_description="Stop a running BinaryBuilder sandbox session and clean up resources.",
99+
list_description="List all active BinaryBuilder sandbox sessions with their IDs, platforms, and uptime.",
100+
start_extra_properties=Dict{String,Any}(
101+
"platform" => Dict{String,Any}(
102+
"type" => "string",
103+
"description" => "Target platform triplet. Examples: 'x86_64-linux-gnu', 'aarch64-linux-musl', 'x86_64-w64-mingw32', 'x86_64-apple-darwin14', 'aarch64-apple-darwin20'. Defaults to 'x86_64-linux-gnu'.",
104+
),
105+
"build_tarballs_path" => Dict{String,Any}(
106+
"type" => "string",
107+
"description" => "Optional absolute or relative path to a build_tarballs.jl file. When specified, the build script runs with the selected debug_mode and then drops into an interactive shell. Sources are downloaded and extracted into the workspace.",
108+
),
109+
"debug_mode" => Dict{String,Any}(
110+
"type" => "string",
111+
"description" => "Debug mode when build_tarballs_path is set. 'end' (default): run the full build script, then drop into a shell to inspect results. 'begin': drop into a shell before the build script runs, so you can execute build steps manually.",
112+
"enum" => Any["begin", "end"],
113+
),
114+
))
115+
116+
# Register sessioned str_replace_editor for editing files inside the sandbox
117+
register_tool!(server, "sandbox_str_replace_editor",
118+
SessionedStrReplaceEditorTool(manager, "sandbox_str_replace_editor",
119+
"""View, create, and edit files inside a running BinaryBuilder sandbox session.
120+
121+
Uses the same command interface as str_replace_editor (view/str_replace/create) but operates on files inside the sandbox filesystem rather than the host. Requires a session_id from sandbox_start.
122+
123+
Paths should be absolute paths inside the sandbox (e.g. /workspace/srcdir/..., \$prefix/lib/...)."""))
124+
125+
# Clean shutdown
126+
atexit() do
127+
stop_all_sessions(manager)
128+
log_msg("MCP server shut down.")
129+
end
130+
131+
# Start
132+
log_msg("MCP server starting (pid=$(getpid()))")
133+
log_msg("Project root: $PROJECT_ROOT")
134+
run_stdio_server(server)

.mcp.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"mcpServers": {
3+
"bb-sandbox": {
4+
"command": "julia",
5+
"args": [
6+
"--project=.ci",
7+
".claude/mcp-bb-sandbox/server.jl"
8+
]
9+
}
10+
}
11+
}

0 commit comments

Comments
 (0)