Skip to main content
Glama
nodejs-repl-mcp

nodejs-repl-mcp

by unfallenwill

nodejs-repl-mcp

Session-stateful Node.js REPL exposed as an MCP server, packaged as a Docker image.

Each named session keeps a real node:repl context alive in its own child process: let/const declarations, top-level await, required modules, and per-session npm packages persist across tool calls until the session is removed. Designed for AI agents and automation that need to build up state across many small evaluations — no terminal UI, no stdin scraping.

Architecture

MCP client ──stdio / streamable HTTP──> MCP server (this process)
                          │  session table (in-memory)
                          ├── worker: node child process ── repl.REPLServer (own context)
                          ├── worker: node child process ── repl.REPLServer (own context)
                          └── ...
  • One worker process per session — crash isolation, independent require cache, killable individually.

  • Native REPL semantics — the worker drives the server's own default evaluator (REPLServer.prototype.eval), so let/const redeclaration, multi-line continuation (Recoverable), on-demand core-module loading, and top-level await behave exactly like the interactive node REPL.

  • Runtime errors — the REPL's internal domain prints Uncaught ... without invoking the eval callback; the worker taps its output stream and still returns a structured error, so the session survives bugs in evaluated code.

  • Per-session workspace — each session gets ~/.nodejs-repl/sessions/<name> (override with NODEJS_REPL_SESSIONS_DIR); the worker's cwd and require resolution root live there. install_packages runs npm install into that directory only.

Related MCP server: jupyter-kernel-mcp

Tools

Tool

Arguments

Description

create_session

name

Create a persistent session (^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$)

exec

name, code

Evaluate JS in the session; syntax-incomplete input is buffered and continues on the next exec. Returns { ok, output, error?, incomplete?, console? }

history

name, limit?

Evaluation history (inputs, outputs, errors, captured console)

list_sessions

—

All sessions with pid, workspace, liveness

remove_session

name

Kill the worker. Workspace dir is kept (deps/files survive)

install_packages

name, packages[], saveDev?

npm install into that session's workspace

Usage

The prebuilt image on GHCR (multi-arch: amd64 + arm64) needs no local build. MCP stdio transport needs stdin, so run with -i:

{
  "mcpServers": {
    "nodejs-repl": {
      "command": "docker",
      "args": ["run", "-i", "--rm", "ghcr.io/unfallenwill/nodejs-repl-mcp:latest"],
      "volumes": ["nodejs-repl-data:/data"]
    }
  }
}

Or with Claude Code:

claude mcp add nodejs-repl -- docker run -i --rm -v nodejs-repl-data:/data ghcr.io/unfallenwill/nodejs-repl-mcp:latest

Pin a release with :X.Y.Z instead of :latest for reproducible setups.

Build From Source

git clone https://github.com/unfallenwill/nodejs-repl-mcp.git
cd nodejs-repl-mcp
docker build -t nodejs-repl-mcp .

Then use the config above with the local tag, e.g. args: ["run", "-i", "--rm", "nodejs-repl-mcp"]. A different Playwright version can be baked in with --build-arg PLAYWRIGHT_VERSION=x.y.z.

Releases

Pushing a tag vX.Y.Z triggers .github/workflows/publish.yml and publishes ghcr.io/unfallenwill/nodejs-repl-mcp:{X.Y.Z, X.Y, latest} (pre-release tags get the exact version only):

git tag v0.1.0 && git push origin v0.1.0

The first push creates the package as private; flip it to public in the package's visibility settings for unauthenticated pulls.

Preinstalled Packages

The image ships with Playwright — package, headless Chromium build, and the system libraries it needs — so browser automation works out of the box, no install_packages required:

create_session { "name": "browser" }
exec { "name": "browser", "code": "const { chromium } = require('playwright'); const b = await chromium.launch(); const p = await b.newPage(); await p.setContent('<h1>hi</h1>'); await p.textContent('h1')" }   // 'hi'

The preinstalled set lives at /opt/session-deps and is exposed to sessions via NODE_PATH, so packages installed with install_packages (session-local node_modules) still take precedence. Pin a different version at build time with --build-arg PLAYWRIGHT_VERSION=x.y.z.

Local

npm install
npm run build
node dist/index.js          # MCP stdio server
claude mcp add nodejs-repl -- node /path/to/nodejs-repl-mcp/dist/index.js

HTTP Transport

Serve streamable HTTP instead of stdio — for remote workbenches, shared hosts, or clients that can't spawn a child process. REPL sessions live in the server's SessionManager, not in the MCP connection, so state survives across independent HTTP requests even though each request is served by a fresh MCP server instance.

node dist/index.js --transport http --host 127.0.0.1 --port 3000 --token my-secret

Option

Env fallback

Default

Notes

--transport <stdio|http>

MCP_TRANSPORT

stdio

stdio keeps existing configs working unchanged

--host <address>

MCP_HOST

127.0.0.1

Loopback binds get the SDK's Host/Origin DNS-rebinding guards

--port <number>

MCP_PORT / PORT

3000

--token <secret>

MCP_TOKEN

none

Requires Authorization: Bearer <secret> on every request

With Docker (note --host 0.0.0.0 — loopback inside the container is unreachable from outside):

docker run --rm -p 3000:3000 -v nodejs-repl-data:/data \
  ghcr.io/unfallenwill/nodejs-repl-mcp:latest \
  node dist/index.js --transport http --host 0.0.0.0 --port 3000 --token my-secret

Connect any streamable-HTTP MCP client to http://127.0.0.1:3000/mcp; with a token configured, send it as a bearer header:

claude mcp add --transport http nodejs-repl http://127.0.0.1:3000/mcp --header "Authorization: Bearer my-secret"

Use --token whenever the server is reachable beyond loopback. The server executes arbitrary code by design; a missing token on a non-loopback bind prints a warning at startup.

Example session

create_session { "name": "analysis" }
exec { "name": "analysis", "code": "let data = [3,1,2]; data.sort()" }
exec { "name": "analysis", "code": "data" }                        // state persists: [1,2,3]
exec { "name": "analysis", "code": "const r = await fetch('https://example.com').then(r => r.status); r" }
install_packages { "name": "analysis", "packages": ["lodash@^4"] }
exec { "name": "analysis", "code": "require('lodash').chunk([1,2,3,4], 2)" }
history { "name": "analysis" }
remove_session { "name": "analysis" }

Design notes

Session-state persistence for REPLs has four known approaches (see research in .firecrawl/):

  1. History replay (.save/.load, Nesh) — simple, but replaying Math.random() or side-effecting statements yields wrong state.

  2. Runtime serialization — impractical in JS: closures are opaque; sockets/native handles can't be serialized.

  3. Process snapshotting (VM/CRIU, the RunKit/Tonic approach) — perfect semantics ("time travel") but heavy infrastructure.

  4. Session residency — keep the session process alive; state persists naturally. This is what nodejs-repl-mcp implements: the MCP server is already a long-lived process, so the coordinator layer of designs like node-repl-cli collapses into the server itself, and each session is a detached worker with a real REPL context.

Limitations

  • Sessions are process state: they do not survive server restarts or container restarts (workspace directories do, via the /data volume).

  • exec has no built-in timeout by design (long evaluations are legitimate). A wedged session can be killed with remove_session.

  • Workers run with full Node.js capabilities — no sandboxing. Do not expose the server to untrusted input; for untrusted code use a container/gVM-level boundary (the vm module is not a security boundary — vm2 escape). In HTTP mode, treat the endpoint as RCE-by-design: keep it on loopback or require --token.

Development

npm run build
node scripts/smoke.mjs                                  # local stdio smoke test (22 assertions)
node scripts/smoke-http.mjs                             # local streamable HTTP smoke test (22 assertions)
SMOKE_CMD=docker SMOKE_ARGS="run -i --rm nodejs-repl-mcp" node scripts/smoke.mjs   # against the container
SMOKE_PLAYWRIGHT=1 SMOKE_CMD=docker SMOKE_ARGS="run -i --rm nodejs-repl-mcp" node scripts/smoke.mjs   # + preinstalled Playwright check
SMOKE_CMD=docker SMOKE_ARGS="run --rm -p 31007:31007 -v nodejs-repl-data:/data nodejs-repl-mcp node dist/index.js --transport http --host 0.0.0.0 --port 31007 --token smoke-secret" node scripts/smoke-http.mjs   # HTTP against the container

The HTTP smoke test covers bearer-token rejection (401 + WWW-Authenticate), the JSON-RPC/SSE stateless leg, state persistence across independent requests, the SDK client leg (skipped when devDependencies are absent, e.g. inside the image), and clean SIGTERM shutdown.

Available Tools

6 tools
create_sessionCreate REPL sessionA

Create a named, persistent Node.js REPL session. Each session runs in its own child process with an isolated context: variables, let/const declarations, required modules and top-level await state persist across exec calls until the session is removed.

Inputs: name (required).

execEvaluate code in sessionA

Evaluate JavaScript in a named REPL session with full node:repl semantics. Syntax-incomplete input is buffered and continues on the next exec (multi-line). Returns the inspected result, captured console output, and any error. State persists between execs.

Inputs: code (required), name (required).

historySession historyA

Return the evaluation history of a session (inputs, outputs, errors, captured console) — useful for reviewing what state was built up.

Inputs: name (required), limit.

install_packagesInstall npm packages into sessionA

Run npm install in the session workspace so subsequent exec calls can require/import the packages. Installs into that session only.

Inputs: name (required), packages (required), saveDev.

list_sessionsList REPL sessionsA

List all REPL sessions with pid, workspace and liveness.

Inputs: no named parameters.

remove_sessionRemove REPL sessionA

Stop and remove a session (kills its worker process). The session workspace directory is kept, so installed dependencies and written files survive.

Inputs: name (required).

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

    TDQS

    A4.3/5.0

    Scored across 6 tools

    Disambiguation5/5

    Each tool maps to a distinct operation: session lifecycle (create/list/remove), code execution, history retrieval, and package installation. There is no meaningful overlap or risk of selecting the wrong tool.

    Naming Consistency4/5

    Most tools follow a clear verb_noun pattern (create_session, list_sessions, remove_session, install_packages), but exec and history are terse single-word names that break the pattern. Overall still readable and predictable.

    Tool Count5/5

    Six tools is a well-scoped set for managing persistent Node.js REPL sessions. Each tool serves a clear need without redundancy or bloat.

    Completeness5/5

    The surface covers the full lifecycle: create a session, execute code in it, inspect history, install dependencies, list sessions, and remove sessions. No obvious dead ends or missing critical operations.

    Maintenance

    ActivityStale
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • F
      license
      A
      quality
      D
      maintenance
      A secure Node.js execution environment that allows coding agents and LLMs to run JavaScript dynamically, install NPM packages, and retrieve results while adhering to the Model Control Protocol.
      7
      115 npm
      4
      -
    • A
      license
      A
      quality
      D
      maintenance
      Enables AI agents to execute Python, TypeScript, and JavaScript code in persistent Jupyter kernels with stateful variables and imports across interactions.
      7
      79 PyPI
      4
      MIT
    • A
      license
      Not graded
      quality
      D
      maintenance
      Enables creation and management of persistent REPL sessions for various languages and shells, with web-based monitoring and session recovery capabilities.
      32 npm
      3
      MIT
    • F
      license
      A
      quality
      D
      maintenance
      Enables AI agents to start and manage pseudo-terminal sessions, run shell commands and interact with REPLs programmatically.
      7
      -