repl-mcp
This server provides a persistent, stateful Python REPL that AI agents can call to run code, keep variables across calls, and orchestrate other MCP servers from within Python.
Run Python code with
execute_python(code); variables, imports, and functions persist between calls.Execute async code with top-level
await(e.g.await client.get(url)).Enforce real timeouts: runaway code gets interrupted with
KeyboardInterrupt, preserving namespace state.Survive crashes: segfaults/OOM kill only the kernel, which respawns with a clear notice.
Use the built-in
sh()helper to run shell commands and parse output directly, replacingcmd | python3 -cpipelines.Access the full filesystem with
open(), absolute paths, and~; cwd is the project directory.Call any discovered MCP server in-code via
mcp.call('server', 'tool', **args)— project, global, and plugin scopes, connected on demand.Inspect available MCP servers without connecting:
print(mcp.help())ormcp.servers.Reset the namespace with
reset=True(keepssh/mcphelpers).Install missing packages into the running environment with
sh('uv pip install ...').Aggregate large results in-REPL to stay under output truncation limits (50KB stdout / 20KB return values).
Restrict bridge scope via
--mcp-scopeif needed, or disable it with--mcp-scope none.
Provides tools for interacting with GitHub's API via the MCP bridge, enabling actions such as creating issues.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@repl-mcpimport requests and fetch the JSON from the API"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
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 --helpUse 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 noneFor 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 one cell; top-level await works; state persists |
| Start a long cell and return its run ID immediately |
| Inspect server/runtime/broker without starting Python or connecting servers |
| Inspect active execution or the latest retained result |
| Request cancellation of the active execution and linked MCP calls |
| Open an independent namespace with an explicit project/environment |
| Discover session IDs and environment metadata |
| Inspect environment/state and optionally bounded variable names/types |
| Stop one session and its owned work |
| Run saved source in a persistent namespace or a fresh owned worker |
| Retain a file as JSON, text or binary without returning its whole body |
| Read an explicit byte range; hex/base64 are opt-in |
| Save verified content without transferring it through agent context |
| Forward bounded content directly into one downstream tool argument |
| 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 sdistCI 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.
Available Tools
1 toolexecute_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).
| Name | Required | Description | Default |
|---|---|---|---|
| code | Yes | Python code to execute | |
| reset | No | Clear namespace (keeps sh/mcp helpers) | |
| timeout | No | Max execution seconds (default 120). Enforced for real: runaway code is interrupted (KeyboardInterrupt) with namespace state preserved. |
TDQS
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.
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.
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.
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.
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.
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 tool update
v2.1.1- First observed
execute_python
TDQS
Scored across 1 tool
With only one tool, there is no possibility of confusion or overlap. The tool's purpose is unambiguous and clearly described.
The single tool follows a clear verb_noun pattern (execute_python), and there are no other tools to create inconsistency.
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.
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
Related MCP Connectors
Nifty's MCP server — exposes tasks, projects, messages, and files as tools for AI agents.
MCP server for your apps' tools and custom tools, plus hosted AI agents and approval-gated workflows
Remote MCP server for supportsheep: run AI interviews and manage support content for your blog.
MCP server for building and testing AI agents with multi-model experimentation and insights.
Related MCP Servers
- AlicenseAqualityDmaintenanceUniversal 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.91MIT
- AlicenseAqualityDmaintenanceA 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.1224 PyPI1MIT
- AlicenseNot gradedqualityDmaintenanceA secure, production-grade MCP server that provides filesystem operations, AST math evaluation, and system diagnostics for LLM agents.MIT
- FlicenseCqualityDmaintenanceAn 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-