Skip to main content
Glama

REPL MCP

A native Rust MCP server with a persistent CPython worker. Python remains the language of your cells; Rust owns the protocol, MCP broker, deadlines and process lifecycle. The worker starts on the first execution. An idle connection does not start Python or a multiprocessing resource tracker.

Execution has full access to the host filesystem, environment and network. The user explicitly chose this mode; the process boundary provides crash recovery, not a sandbox. Supported hosts: macOS and Linux. Windows job ownership is not implemented.

Installation

Download the wheel matching your host from the v3.1.0 GitHub release: macOS arm64, macOS x86_64 or Linux x86_64 (glibc 2.28+). Python 3.10+ is required; binary wheels do not require a Rust compiler. Install the downloaded wheel in a dedicated environment:

uv venv ~/.local/share/repl-mcp/venv --python 3.12
uv pip install --python ~/.local/share/repl-mcp/venv/bin/python /absolute/path/repl_mcp-3.1.0-*.whl
~/.local/share/repl-mcp/venv/bin/repl-mcp --help

Use that absolute executable path in your client registration. Releases are distributed through GitHub; an unqualified PyPI install is not the release path. Source installation requires Rust 1.88+ and CPython 3.10+:

uv sync --extra dev --locked
uv run repl-mcp --help
uv run repl-mcp --transport stdio --mcp-scope none

For a client launch configuration, use the absolute installed repl-mcp executable and an explicit project/config directory. The default working directory is the client's launch directory. --python /absolute/venv/bin/python selects a runtime; otherwise the binary prefers a sibling Python interpreter, then python3 on PATH. This makes wheel/uvx installations use their own environment. python_health reports the selected interpreter and, after lazy startup, its actual version.

The plugin launcher selects the published wheel for the host and runs it through uvx; its default launch does not compile Rust. It requires uv on PATH. The first launch downloads the wheel and Python dependencies, so allow enough startup time for that initial network setup. Subsequent launches reuse uv's cache. For a client with a short startup deadline, prewarm it with sh /path/to/plugin/scripts/repl-mcp-launch.sh --help, or register the installed binary above. Deliberate Git/source launches require Rust and a longer cold-start budget for compilation. Do not leave old MCP registrations enabled alongside the native server. The Python 2.x server and engine modules remain source regression fixtures, not the shipped entrypoint; the binary wheel contains the Rust executable with an embedded worker. For changes to broker discovery, result envelopes and helper APIs, follow the 3.0 migration checklist.

Related MCP server: mcp-python-repl

Execution tools

Tool

Purpose

execute_python(code, reset=False, timeout=120, session_id=None)

Execute one cell; top-level await works; state persists

python_start(code, reset=False, timeout=120, session_id=None)

Start a long cell and return its run ID immediately

python_health()

Inspect server/runtime/broker without starting Python or connecting servers

python_run(run_id=None)

Inspect active execution or the latest retained result

python_cancel(run_id=None)

Request cancellation of the active execution and linked MCP calls

python_session_open(name, project, python=None)

Open an independent namespace with an explicit project/environment

python_session_list()

Discover session IDs and environment metadata

python_session_inspect(session_id, include_namespace=False)

Inspect environment/state and optionally bounded variable names/types

python_session_close(session_id)

Stop one session and its owned work

python_execute_file(session_id, path, argv=[], mode="persistent")

Run saved source in a persistent namespace or a fresh owned worker

artifact_create(session_id, path, format)

Retain a file as JSON, text or binary without returning its whole body

artifact_read(session_id, id, offset=0, length=65536, encoding="text")

Read an explicit byte range; hex/base64 are opt-in

artifact_save(session_id, id, path, overwrite=False)

Save verified content without transferring it through agent context

artifact_forward(session_id, id, server, tool, argument, format="json")

Forward bounded content directly into one downstream tool argument

artifact_delete(session_id, id)

Release retained content

Results contain readable text and structured fields: run_id, success, stdout, stderr, return_value, error, elapsed_ms, truncated and state. Results also identify the session and generation. state describes the worker after execution; state_reset reports a fresh/reset/recovered namespace before that cell. Python failures set MCP isError; invalid tool arguments are protocol errors. Timing includes postprocessing. Normal interruption preserves variables; crashes or forced termination clear them. A reset restarts the worker and restores initial cwd/environment/loop.

Only one cell runs at a time per session; overlapping executions in that session fail promptly. Health and cancel remain available. For current MCP 2026-07-28 stateless requests, first call health, then pass its server.session_id to execution/start; a foreign session ID is rejected. Legacy clients can omit it. python_start avoids client request deadlines: poll python_run(run_id) until completion, or cancel it explicitly. History retains the last 64 results. Output is bounded during writes in UTF-8 bytes, not after accumulating an unlimited buffer. Safe previews inspect small builtin values; custom repr is not executed implicitly. Native fd output is drained separately and never mixed into MCP stdout.

Independent sessions and project environments

The follow-up design and historical acceptance evidence are recorded in the session design audit.

Parallel agents should open separate sessions and pass the returned ID to execution, polling and cancellation. Session names are labels; opening the same name twice does not share a namespace. Each session has its own variables, cwd, worker, active run and history. Closing or resetting one leaves the others running. The legacy default session remains available through python_health; close rejects this reserved session, whose state can instead be cleared with reset=True.

An explicit python selects that executable. Otherwise an existing project's .venv/bin/python is selected; projects without .venv use the reported server default. Invalid explicit interpreters or incomplete existing .venv directories fail instead of silently choosing another environment. Inspect environment metadata before installing dependencies; installations must target the selected interpreter.

Pass expected_generation to execute_python, python_start or python_execute_file when a cell depends on existing variables. A mismatch returns STATE_CHANGED before executing source. Session inspection includes generation and can list bounded variable names/types without evaluating their repr. This detects worker reset/recovery; it does not verify the meaning of variables changed by earlier successful cells. Server identity distinguishes a new server lifetime.

python_execute_file uses real source filenames and accepts an argv list without shell quoting. persistent runs inside the session namespace. fresh uses a separate owned worker in the same environment and leaves the persistent namespace intact; progress, results and cancellation remain attached to the owning session. It does not provide filesystem isolation or undo external effects.

import asyncio
import httpx
client = httpx.AsyncClient()
# A later cell can reuse the client on the persistent loop:
data = (await client.get(url)).json()

Cell-owned background tasks and delayed callbacks are cancelled at completion. Unjoined user threads or still-running executor work require termination of the worker and report cleared state; finished/default DNS executor threads may remain idle. Keep resource objects and await their work. Use top-level await instead of nested asyncio.run().

from pathlib import Path
Path('~/data.json').expanduser().read_text()  # open() does not expand ~
json_text = sh('gh pr list --json number,title')
print(json_text.returncode, json_text.stderr, json_text.ok)

sh() captures bounded stdout/stderr, follows Python's current cwd, and owns its process group. Standard subprocess.Popen children are tracked and polled to avoid retained zombies while preserving .wait() results. Explicit process detachment, os.fork, native process APIs and arbitrary custom signal handling are trusted code; they are not an OS containment guarantee.

checkpoint(path) writes a bounded, atomic JSON checkpoint of supported builtin namespace values and reports skipped values. restore(path) validates the complete checkpoint before loading it. Imports, functions, native objects and active resources are not serializable through this contract. No automatic code replay or pickle loading. repl_history() lists source IDs/line counts; repl_source(run_id) explicitly retrieves retained code. Source/linecache storage is capped at 64 runs and 1 MiB, never journalled.

MCP broker

The default registry is the launch project's .mcp.json. Use --config /path/registry.json for an independent broker registry, or --mcp-scope none to disable it. Client-managed cloud connectors cannot be discovered by a local server. Claude, Codex and t3 can all use the same explicit registry; another client's credentials and approvals are not inherited automatically.

{
  "mcpServers": {
    "service": {
      "type": "stdio",
      "command": "/absolute/path/service-mcp",
      "args": [],
      "allowedTools": ["read_record", "update_record"]
    }
  }
}

A dedicated registry is a grant to the broker. allowedTools narrows that grant and is checked on each call. Foreign Claude user/local scopes are opt-in and require independent brokerAllowed: true entries; plugin registries require an explicit exported configuration. This avoids reinterpreting a client's interactive approvals as blanket authorization. REPL_MCP_NO_BRIDGE=1 blocks recursive brokers.

print(mcp.help())                         # names only, connects nothing
print(mcp.help('service', 'read_record'))  # full input/output schema
response = mcp.call('service', 'read_record', arguments={'id': '123'})
print(response.get('structuredContent'))  # complete envelope retained
print(mcp.text(response))                 # all text blocks, explicit extraction
response = await mcp.acall('service', 'read_record', arguments={'id': '456'})

mcp.call is synchronous; mcp.acall supports concurrent async work. Legacy await mcp.call(...) succeeds after a single synchronous call, so it cannot fail only after applying a write. Use arguments={...} for tool arguments named timeout, server, tool or arguments. MCP errors raise ToolError with .result retaining the full envelope. Listing follows bounded pagination; failures are not empty success.

Sessions connect lazily, equivalent aliases share a transport, and broken transports are invalidated. Refresh does not retry writes. mcp.refresh() reloads configuration; mcp.journal() shows bounded call metadata and outcomes, including outcome_unknown when a dispatched call is abandoned. No arguments or response payloads enter this journal. --journal /private/path/journal.json optionally persists those metadata records atomically with an exclusive process lock. Prior unfinished dispatches become outcome_unknown on recovery. An execution is not a transaction: cancellation cannot undo a committed write. Reconcile an unknown outcome with the target before retrying.

mcp.explain(server, tool=None) reports configuration, grants, credential readiness, cached transport/catalogue state and suggested recovery actions without starting a server or making a network probe. Status distinguishes an absent registry/server, disabled broker, missing independent grant/credentials, disconnected transport, previous connection timeout and missing/stale catalogue. ready refers to observed cached state, not a fresh connectivity test. Host-client connector availability is reported as unknown: a local broker cannot discover those tools automatically.

Session brokers use the selected project's registry; an explicit launch --config remains an intentional shared registry override. Same-project sessions reuse their broker connections. Different projects have separate pools, and closing the last session of a project shuts down its broker. Per-call cancellation does not invalidate an otherwise healthy transport used by another session.

Large results and artifacts

Large supported trailing expression values return a bounded preview and an artifact reference containing id, size in bytes, format and sha256. Use the artifact tools to read a range, save it or forward it without copying the whole payload into conversation context. Inside Python, artifact(value) and artifact(path=..., format=...) explicitly retain JSON/text/bytes/files; matching artifact_read, artifact_save, artifact_forward and artifact_delete helpers use the same store. Custom objects are not implicitly serialized or repr'd.

Artifacts belong to their session, survive worker resets, and expire on session close, server exit or oldest-first eviction. Unknown/expired references return an explicit error. Storage retains at most 64 entries / 64 MiB, with 16 MiB per artifact; Unfinished uploads and bounded I/O copies can each temporarily retain up to another 64 MiB. Value uploads use chunks into unlinked Rust-owned files; cancellation and worker death discard unfinished uploads. Reads cap at 64 KiB per explicit range. Forwarding accepts artifacts up to 512 KiB and also enforces the broker's 256 KiB serialized argument budget and downstream grant/schema; larger payloads should be saved or split explicitly. Binary base64 conversion requires an explicit request. Cancellation cannot undo a completed save or downstream write; forwarding follows the same unknown-outcome journal contract. A hard server kill during an explicit save can leave an adjacent temporary file; the store's own retained and unfinished files are unlinked from creation.

Configuration errors omit secret values. ${VAR} must exist and be nonempty; transport mismatches, unknown fields and empty Bearer headers fail explicitly. HTTP uses Streamable HTTP, with explicit headers or independently configured OAuth. --oauth-login SERVER starts the configured broker's authorization flow; it does not borrow browser credentials from another client. Invalid broker configuration leaves health and ordinary Python available; broker operations fail closed until a successful refresh. See the configuration contract in migration design.

For an independent authorization-code grant:

{"mcpServers":{"service":{"type":"http","url":"https://service.example/mcp",
  "oauth":{"grantType":"authorization_code",
    "credentialFile":"/private/directory/service.json","redirectPort":0}}}}

Run repl-mcp --config /absolute/registry.json --oauth-login service, then open the printed authorization URL. Credential directories/files require private permissions; symlinks are rejected. redirectPort: 0 uses an ephemeral localhost port; configure a fixed port when the provider requires a pre-registered callback. Client-credentials grants use grantType: "client_credentials", clientId and clientSecret; environment substitutions keep secrets out of checked-in config.

Packages and resource limits

Runtime dependencies are pinned; Rust's transitive graph is committed in Cargo.lock, and Python's environment in uv.lock. To add a package to the actual running interpreter, use an explicit target:

import shlex, sys
sh('uv pip install --python ' + shlex.quote(sys.executable) + ' numpy==2.2.6')

Imports may need a worker reset after an install. Prefer a reproducible configured venv for long-lived use. openpyxl and httpx are included; scientific packages are not silently installed.

Limits include 256 KiB code, 1 MiB protocol/worker frames, 50,000-byte captured streams, 20,000-byte previews, bounded RPC concurrency and execution timeout up to 3600 seconds. --max-memory-mib defaults to 2048; zero disables RSS enforcement. This sampled worker RSS budget is not a hard whole-tree quota. A native C call may not process SIGINT; after the grace period Rust terminates the worker and reports cleared state. Large output should be written to an explicitly chosen file and summarized in the cell.

Verification

make verify
make wheel
make sdist

CI runs strict Rust/Python checks and the complete regression suite on macOS/Linux, Python 3.10/3.12/3.14 and MCP SDK 2.2/2.3. Stdio contract tests cover errors, schemas, cancellation late effects, fatal exits, asynchronous state and full broker envelopes. These fixtures verify wire behavior; they do not claim a live Claude/Codex/t3 UI test. See audit coverage for per-finding evidence and residual limits.

MIT license.

Available Tools

1 tool
execute_pythonExecute PythonA

Persistent Python REPL — use instead of python3 -c, heredocs or cmd | python3 via Bash. Also the way to BATCH MCP WORK: the injected mcp bridge reaches your project, global (user-scope) and plugin MCP servers, so one loop replaces N separate tool calls — for f in files: mcp.call('telegram-mcp', 'download_media', **f).

State (variables, imports, functions) survives across calls: a warm call takes ~0.1s vs ~3s for each fresh python3 Bash spawn. Full filesystem access — open(), absolute paths, and ~ all work. Top-level await is supported (e.g. await client.get(url) with httpx).

ParametersJSON Schema
NameRequiredDescriptionDefault
codeYesPython code to execute
resetNoClear namespace (keeps sh/mcp helpers)
timeoutNoMax execution seconds (default 120). Enforced for real: runaway code is interrupted (KeyboardInterrupt) with namespace state preserved.

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden, and it delivers: state persists across calls, performance characteristics are quantified, full filesystem access is disclosed, and top-level await is supported. The schema further clarifies that timeouts interrupt with KeyboardInterrupt and preserve namespace state.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Dense but purposeful: every sentence adds value, from purpose and alternatives to the MCP batch example and performance claims. Core concepts are front-loaded, and the inline example earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a complex tool, coverage is strong: persistence, MCP bridge, filesystem access, await, reset behavior, and timeout semantics are all covered. The only notable gap is that the description does not state what the tool returns (stdout, exceptions, etc.), which matters slightly more given no output schema is present.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, and the schema fully documents code, reset (clears namespace, keeps sh/mcp helpers), and timeout (real enforcement, KeyboardInterrupt, preserved state). The description enriches what code can do (await, mcp bridge) but adds no parameter-specific semantics, so the baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource: 'Persistent Python REPL'. It immediately differentiates itself from alternatives like `python3 -c`, heredocs, and `cmd | python3` via Bash, and highlights distinctive features (state persistence, MCP bridge, top-level await).

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It explicitly says 'use instead of' Bash Python invocations and explains why (persistence and ~0.1s warm vs ~3s spawn). It also declares itself the way to batch MCP work, giving a concrete example, so an agent has clear grounds to select it over alternatives.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

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

  1. 1 tool updatev2.1.1
    • First observedexecute_python

TDQS

A4.7/5.0

Scored across 1 tool

Disambiguation5/5

With only one tool, there is no possibility of confusion or overlap. The tool's purpose is unambiguous and clearly described.

Naming Consistency5/5

The single tool follows a clear verb_noun pattern (execute_python), and there are no other tools to create inconsistency.

Tool Count5/5

The server is purpose-built as a Python REPL, and a single tool fully satisfies that narrow scope. The tool is powerful and not trivial, so the count is appropriate.

Completeness5/5

The tool covers all expected REPL functionality: persistent state, filesystem access, top-level await, and MCP bridging. There are no obvious gaps for the stated domain.

Maintenance

ActivityMaintained
ResponsivenessUnresponsive

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    D
    maintenance
    Universal Python code execution MCP server that lets LLMs write and run Python for any task, with auto-install packages, streaming output, and automatic file display.
    9
    1
    MIT
  • A
    license
    A
    quality
    D
    maintenance
    A production-grade MCP server providing a persistent Python REPL with multi-session support, sandboxing, and timeout protection, enabling LLM agents to execute Python code across multiple turns with variables that persist between calls.
    12
    24 PyPI
    1
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    A secure, production-grade MCP server that provides filesystem operations, AST math evaluation, and system diagnostics for LLM agents.
    MIT
  • F
    license
    C
    quality
    D
    maintenance
    An MCP server that enables AI agents to execute terminal commands and Python code on the host system, leveraging Agent Zero's battle-tested implementation with session management and smart output handling.
    4
    -