nurl-mcp — a local MCP (Model Context Protocol) server for the NURL toolchain. The LLM-facing counterpart of nurl-lsp: where nurl-lsp serves editors over LSP, nurl-mcp serves an LLM agent over MCP so it can drive the locally installed compiler — build, run, type-check, format NURL, compile it to wasm32-wasi (via the wasmbuilder package, no build service), and read the installed standard library — against the host's real filesystem.
Transport: newline-delimited JSON-RPC 2.0 over stdin/stdout (the transport every MCP client supports). Logs go to stderr; stdout is protocol-only. Wire it into an MCP client by pointing its command at the built binary, e.g. claude mcp add nurl -- nurl-mcp.
The build/run/check/fmt tools shell out to nurl / nurlc / nurlfmt (found on $PATH — the installed toolchain shims export $NURL_STDLIB so the compiler resolves stdlib imports). Running NURL is full code execution (libc FFI, no sandbox); over stdio that is safe because only the process that spawned this server talks to it. The --http transport serves the MCP Streamable-HTTP protocol via the stdlib facade (ext/mcp_http.nu: POST single/batch, GET SSE probe, Mcp-Session-Id echo, DELETE, CORS) with bearer-token auth layered on top; nurl_run stays off over HTTP unless --allow-run is given.
: ~ i g_read_only 0── Policy (set once in main, read by the dispatcher) ───────────────
g_read_only : 1 ⇒ expose only the read/analysis tools (check, fmt, list_stdlib, read_stdlib) — no build, no run. g_run_allowed : 1 ⇒ nurl_run is available. Defaults to 1 over stdio (only the spawning process can reach the server); over HTTP it is 0 unless --allow-run is passed, because run is unsandboxed code execution. g_token : non-empty ⇒ HTTP requests must carry Authorization: Bearer <g_token>.
: ~ i g_run_allowed 1: ~ s g_token ``@ nm_version → s@ nm_log_banner s suffix → vLog a startup banner "nurl-mcp <version> <suffix>" through mcp_log, building the line from the single-source version so the banners can never drift from the handshake version again.
: ~ i nm_seq 0@ nm_is_temp s path → bA resolved input path is a temp we own iff it sits under the "<tmpdir>/nurl-mcp-" prefix nm_tmp_path mints. Path-based (not a shared mutable flag) so it stays correct if requests are ever served on concurrent fibers.
@ nm_tmp_path s suffix → String@ nm_unlink s path → v@ nm_unlink_artifacts s base → vRemove a build's artifacts: the binary base and the base.ll IR that the build driver leaves beside it.
@ nm_input_path Json args → ?StringResolve a tool's input to a .nu file path (owned String) the compiler can read. Prefers an explicit path; otherwise writes inline source to a temp file. Returns None when neither argument is present.
@ nm_fail_from_output s prefix Output o b with_stdout → JsonTurn a non-zero-exit Output into an MCP error result carrying the exit code and captured diagnostics. with_stdout folds stdout in too (the build driver prints some diagnostics there; the front-end check prints IR there, so check passes F).
@ nm_proc_err ProcessErr e → Json@ nm_missing_input → Json@ nm_tool_check Json args → Json@ nm_tool_build Json args → Json@ nm_tool_build_wasm Json args → Json── Tool: nurl_build_wasm (compile to wasm32-wasi, fully local) ──────
Drives the wasmbuilder library in-process: nurlc → wasm32 IR rewrite → the toolchain's bundled zig cc. No build service, no network (a machine with no zig anywhere downloads it once — see the wasmbuilder README).
Output shape:
out argument given → module written there, text summarypath input, no out → written next to the input as<input minus .nu>.wasm
source, no out → JSON text with wasm_base64(mirrors the playground MCP tool)
@ nm_run_result Output o → Json@ nm_tool_run Json args → Json@ nm_tool_fmt Json args → Json@ nm_tool_list_stdlib Json args → Json@ nm_has_dotdot s p → b@ nm_tool_read_stdlib Json args → Json@ nm_stdlib_root → String@ nm_tool_api Json args → Json@ nm_tool_grep Json args → Json@ nm_schema_api → Json@ nm_schema_grep → Json@ nm_prop Json props s name s desc → v@ nm_schema_src_path → Json@ nm_schema_name → Json@ nm_schema_empty → Json@ nm_build_ok → bPolicy predicates (read the globals set in main).
@ nm_run_ok → b@ build_tools_list → ( Vec Json )@ dispatch_tool s name Json args → Json@ handle_initialize Json id → Json@ handle_ping Json id → Json@ handle_tools_list Json id → Json@ handle_tools_call Json id Json params → Json@ handle_unknown_method Json id s method → Json@ dispatch Json req → ?JsonTransport-agnostic dispatcher: request Json → Some(response) for a request, or None for a notification (the caller drops it / replies 202).
@ stdio_loop → v@ nm_cors HttpResponse r → v@ nm_authed HttpRequest req → b@ nm_is_loopback s host → b@ run_http s host i port → i@ main → i