This repository has no description
Rust 99%
Nix 1%
Shell <1%

README.md

beamdev #

A BEAM development tool for coding agents: a Model Context Protocol (MCP) server that connects to Erlang/BEAM nodes over the Erlang distribution protocol and runs code on them.

Introspection, tracing and debugging are not tools here — they are functions the node already has, and eval reaches all of them.

Development tool. Code runs unsandboxed, with the full authority of the target node. The cookie is the only boundary.

Install #

cargo build --release   # binary at target/release/beamdev
nix build               # or via the flake

Use #

Start a node, then point an MCP client at the binary:

{
  "mcpServers": {
    "erlang": {
      "command": "/path/to/beamdev",
      "args": ["--node", "myapp@localhost", "--mode", "elixir"]
    }
  }
}

--mode sets how terms are rendered: erlang (default), elixir, gleam, or lfe. --log-level takes the usual five. Both are fixed at startup.

--node is required and names the one node this server works against, for its whole life. The cookie comes from --cookie-file (default .erlang.cookie, relative to the working directory the client launches the server in); an unreadable cookie is fatal. Nothing is exposed to the caller for connecting or disconnecting, so an agent has no reason to go looking for a cookie.

The node itself need not be up. The server connects in the background and retries with backoff, and a tool call that finds the link down retries immediately rather than waiting out the timer, so restarting the node mid-session is unremarkable. A reconnect is logged on the node, which puts a marker in get_logs: everything before it — buffered events, loaded modules, process state — belongs to the previous incarnation.

Working against several nodes means running several servers. Reaching another node in an existing cluster does not: rpc:call/4 from the connected one already gets there.

Tools #

Tool Description
list_nodes Report the node and whether the connection to it is up
eval Evaluate Erlang or Elixir source, bounded by timeout_ms (default 15s)
load_module Compile module source on the node and load it
get_logs Read what the node has logged since we connected, filtered by level and regex

Nothing needs installing on the target: the tools drive the node's own scanner, parser, evaluator and compiler. The first eval loads one small runner module, which bounds each evaluation in time and memory and kills it on overrun.

get_logs needs a second module. A node's log stream does not cross the distribution link, so connecting installs a logger handler that buffers the last 1000 formatted events on the node. Only events logged after the connection are captured, and only those the node's primary logger level already lets through. Reports are rendered by the node's own inspect where it has Elixir, so redact: true and hand-written Inspect implementations apply.

eval(code: "erlang:process_info(whereis(user), [memory]).")
load_module(source: "-module(h).\n-export([n/0]).\nn() -> node().")
get_logs(level: "warning", grep: "timeout", tail: 20)

Erlang source passed to eval must end with a period; Elixir must not.

When Elixir code raises, the exception is rendered on the node with Exception.format/3, so the Inspect protocol applies and a struct's redacted fields stay redacted. Erlang has no equivalent: an Erlang exception's reason is rendered here, term for term, including anything it happens to be carrying.

Development #

cargo fmt && cargo clippy --all-targets --all-features -- -D warnings && cargo test

The integration tests start and stop their own BEAM nodes, so they need erl and elixir on PATH -- nix develop provides both.

License #

This project is licensed under the Apache License, Version 2.0 — see the LICENSE file for details.

Acknowledgements #