LocalRun

The MCP server

@mindantic/localrun-mcp exposes LocalRun to MCP clients. Give a folder a .test address, start it, read its logs, fix its env, from a chat window instead of the tray.

text
You:     Give this folder a .test address and start it
Claude:  Added notes at https://notes.test, running on port 3000

Requirements

macOS, and the LocalRun app. The app owns the DNS responder, the proxy, and the certificate; the package is a thin wrapper around the daemon's unix socket. The package declares "os": ["darwin"] so npm refuses to install it elsewhere; on Windows a socket path means a named pipe, and it could not connect even in principle.

Install

The MCP tab in LocalRun's menu-bar panel is the shortest path. It writes the client config, and repairs it later if you move or rename the app.

By hand, for Claude Code:

bash
claude mcp add localrun --scope user -- npx -y @mindantic/localrun-mcp

For Claude Desktop, add this to ~/Library/Application Support/Claude/claude_desktop_config.json and restart the app:

json
{
  "mcpServers": {
    "localrun": { "command": "npx", "args": ["-y", "@mindantic/localrun-mcp"] }
  }
}
Connect writes absolute paths

The app ships the MCP bundle inside itself, so the Connect button registers two absolute paths into LocalRun.app rather than an npx command. That is faster and works before the package is on your machine, but it breaks if you move the app, rename it, or reinstall to ~/Applications. LocalRun checks every path in the entry at launch, rewrites the broken ones, and turns the row's button into Reconnect when it cannot. An npx entry has no paths to break.

Tools

Nineteen tools, against the daemon's 38 JSON-RPC methods. The gap is deliberate: the tool list and the set of RPCs the server is allowed to reach are both pinned by a test, so widening either is a conscious edit rather than a side effect.

Looking around

ToolWhat it does
list_sitesEvery site, its address, and whether it is running
get_siteOne site in full: address, folder, command, port
read_logsRecent output from a site, to find out why it is failing
check_setupWhether .test addresses and HTTPS are working
find_running_serversDev servers already running that LocalRun does not manage
preview_folderWhat LocalRun would do with a folder, without adding it

Changing things

ToolWhat it does
add_siteGive a folder an address
add_site_for_portGive an address to something already on a port
adopt_running_serverTake over a server you started yourself
start_site / stop_site / restart_siteRun control
rebuild_siteRun the build command, then restart
update_siteName, start command, build command, port, https
set_envEnvironment variables, merged, never replaced
set_keep_runningManual, with LocalRun, or always
remove_siteRemove from LocalRun
run_setupFix the system setup (macOS asks for your password)
import_from_caddyMove sites out of an existing Caddyfile

Tools that act on an existing site accept a name, an id, or any path inside the project folder. The daemon is inconsistent about which it wants (update and remove need the exact id, everything else takes either), so the MCP server resolves it for you.

What it deliberately cannot reach

Some RPCs have no safe agent-facing shape, so the server has no route to them at all:

BlockedWhy
projects.upsertWrites a whole project record; update_site has a field allowlist instead
projects.openTerminalOpens a window and types a command into your shell
projects.startAllOne call, arbitrary blast radius
settings.set, autostart.*Machine-level configuration, not project work
caddy.trust, caddy.provision, caddy.syncRaw proxy taps; run_setup is the supervised version

No confirmation prompts

There are none, on purpose.

A token handed to the agent is not a gate

An earlier build issued a confirmation token the agent had to echo back before a destructive call went through. A real Claude session defeated it in one turn, by echoing it. Anything the agent can read, the agent can return, so that guard was friction that proved nothing, and it is gone.

What replaced it is honesty about consequences plus a few hard limits:

  • remove_site never touches your files. It removes the entry; the folder and everything in it is left alone, and add_site puts it straight back. The action is reversible, so it does not need a gate.
  • start_site will not take a port from another program. If something else holds the port it reports what, and does nothing, unless you pass takeOver: true.
  • set_env merges. Variables you do not mention are kept, and values are never read back.
  • update_site has a hard field allowlist. It cannot reach the folder, the working directory, env, or the autostart tier.
  • import_from_caddy inverts the daemon's defaults. Hostnames are required rather than inferred, and removeOriginal defaults to false, so your Caddyfile is left alone unless you ask.

Destructive tools carry the MCP destructiveHint annotation, so a client that prompts before such calls still can.

Not a security boundary

The daemon listens on an unauthenticated unix socket. Any process running as you can drive it directly, with or without this package. Treat the MCP server as a convenience layer, not a sandbox.

Configuration

VariableDefaultPurpose
LOCALRUN_DIR~/.localrunWhere LocalRun keeps its state
LOCALRUN_DAEMON_SOCK$LOCALRUN_DIR/daemon.sockSocket path override
LOCALRUN_MCP_NO_LAUNCHunset1 stops it opening LocalRun.app automatically

If LocalRun is not running, the server opens it and waits. If it cannot, it returns instructions rather than failing silently.

When the tools are not there

The client shows the server as connected but no LocalRun tools appear, or every call errors. Check, in order:

bash
claude mcp list                        # is it registered at all?
npx -y @mindantic/localrun-mcp         # does it start standalone? (Ctrl-C to exit)
ls -l ~/.localrun/daemon.sock          # is the daemon actually up?

A missing socket means LocalRun is not running and could not be launched. A registration pointing at a moved app bundle shows up in the MCP tab as Reconnect.


npm: @mindantic/localrun-mcp.