Skip to content

iota-uz/repl-mcp

Repository files navigation

Stateful Python REPL MCP Server

A Model Context Protocol (MCP) server giving AI agents a persistent Python REPL with honest execution semantics. Code runs in a subprocess kernel (Jupyter-style): variables survive across calls, runaway code is interruptible without losing state, and crashes never take the server down. Other MCP servers from your project's .mcp.json are callable in-code via a pre-injected mcp bridge.

Features

  • Persistent State: variables, imports, and functions survive across calls (~0.1s warm calls vs ~3s per fresh python3 spawn)
  • Real Timeouts: runaway code (sync or async) is interrupted at timeout seconds — KeyboardInterrupt, namespace state preserved. Cells that swallow the interrupt are killed and the kernel respawns with an explicit "variables cleared" notice
  • Crash Isolation: a segfault/OOM in REPL code kills only the kernel child; the server respawns it instantly
  • Top-level await: await client.get(url) directly — no asyncio.run() wrapper
  • Shell Composition: pre-injected sh() helper — json.loads(sh("gh pr view 1 --json title")) replaces cmd | python3 -c pipelines
  • Full Filesystem Access: open(), absolute paths, and ~ all work; cwd is your project
  • MCP Bridge: mcp.call("server", "tool", **args) reaches the servers in your project's .mcp.json — connected lazily on first use, with failures visible in mcp.failed / mcp.help()
  • Claude Code Plugin: one install bundles the server, a usage skill, and a Bash-nudge hook

Installation

Claude Code (plugin — recommended)

# In Claude Code:
/plugin marketplace add iota-uz/repl-mcp
/plugin install python-repl@repl-mcp

Restart the session and all three components are active. Portable across machines — nothing is hand-edited in ~/.claude.json.

Migrating from a claude mcp add install? Remove the old entry first: claude mcp remove python-repl -s user. Keeping both registers two REPL server processes with duplicate tools and can skew versions between them.

What the plugin bundles:

Component What it does
MCP server execute_python tool, launched via uvx pinned to the release tag (cached after first run; the REPL's working directory is your project, not the plugin cache)
Skill (python-repl) Teaches Claude when to reach for the REPL (instead of python3 -c / heredocs via Bash) and its gotchas — truncation limits, lazy mcp bridge, package installs
Nudge hook (PostToolUse) When Claude runs inline Python through Bash (python3 -c, python3 - <<EOF, cmd | python3), injects a non-blocking reminder to use execute_python. Silent on python3 script.py, python3 -m ..., pytest

To update later: /plugin marketplace update repl-mcp then /plugin update python-repl@repl-mcp.

Claude Code (MCP server only)

claude mcp add python-repl -- uvx --from git+https://github.com/iota-uz/repl-mcp@v2.0.0 repl-mcp

Pin to a tag (as above) so uvx caches the build instead of fetching GitHub on every session start.

Codex CLI

codex mcp add python-repl -- uvx --from git+https://github.com/iota-uz/repl-mcp@v2.0.0 repl-mcp

Claude Desktop

Add to ~/Library/Application Support/Claude/claude_desktop_config.json:

{
  "mcpServers": {
    "python-repl": {
      "command": "uvx",
      "args": ["--from", "git+https://github.com/iota-uz/repl-mcp@v2.0.0", "repl-mcp"]
    }
  }
}

Manual (development)

git clone https://github.com/iota-uz/repl-mcp && cd repl-mcp
uv sync --extra dev
uv run repl-mcp                  # stdio transport (the only transport)

Usage

One tool: execute_python(code, reset=False, timeout=120).

# State persists across calls
execute_python(code="import httpx; data = (await httpx.AsyncClient().get(url)).json()")
execute_python(code="len(data['items'])")          # → 42

# Shell composition
execute_python(code="prs = json.loads(sh('gh pr list --json number,title'))")

# MCP bridge (lazy-connects to your project's .mcp.json on first use)
execute_python(code="print(mcp.help())")
execute_python(code="mcp.call('github', 'create_issue', owner='me', repo='proj', title='Bug')")

# Runaway code? Interrupted at timeout, state survives:
execute_python(code="while True: pass", timeout=5)
# → KeyboardInterrupt: execution interrupted. Namespace state ... preserved.

# Missing package? Install into the running env:
execute_python(code="sh('uv pip install openpyxl')")

Notes:

  • The mcp bridge sees only the project's .mcp.json servers. Host-level connectors (claude.ai Notion/GitHub, user-scope claude mcp add servers) are not reachable — call those tools directly.
  • mcp.call arguments must be JSON-serializable (they cross the kernel process boundary).
  • Output truncates at 50KB (stdout) / 20KB (return values) — aggregate in-REPL.
  • reset=True clears variables but keeps sh/mcp.

Architecture (v2: subprocess kernel)

MCP client ── stdio ──► PARENT (FastMCP, pure async)        CHILD (owns namespace)
                          execute_python ── EXECUTE ──────►  exec / await cell
                                       ◄──── RESULT ──────   captured output
                          timeout: SIGINT ────────────────►  KeyboardInterrupt
                          crash: respawn + clear notice      (state survives)
                          MCP sessions (lazy)  ◄─ MCP_CALL ─ in-code mcp.* proxy

The server's event loop never blocks on REPL code; in-cell mcp.* calls are serviced on an independent channel while the cell runs. See CLAUDE.md for the full development guide.

v2.0.0 breaking changes

  • Removed (zero observed usage across real agent transcripts): workspace/git/ast_utils/code pre-injected utilities (use open()/pathlib/sh('git …')), %magic commands and object? queries, the inject parameter, mcp.tools.<server>.<tool> dot-style access and discover_tools() (use mcp.call/mcp.list_tools), SSE transport (stdio only)
  • Changed: execution moved to a subprocess kernel — timeout is now actually enforced; kernel restarts are reported explicitly
  • Added: top-level await, mcp.failed, lazy MCP connect
  • Install footprint dropped ~350MB (tree-sitter removed)

Development

uv run pytest tests/ -v          # full suite

See CLAUDE.md for architecture details, test map, gotchas, and the release process.

License

MIT

About

Stateful Python REPL MCP Server - Execute Python code persistently with MCP tool integration

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors