Skip to main content

mcp-async-repl

Async Python REPL + Shell execution for MCP. Persistent state, background jobs, and interactive input() bridging — in a single server.

The only MCP server that combines a crash-isolated persistent Python worker, true async shell execution, stdin interaction, and job lifecycle management in one package.

What makes this different

Every other Python REPL MCP server runs exec() in the server process and blocks on long-running code. This one doesn't.

  • Crash-isolated Python worker — runs in a separate subprocess with a JSON protocol over stdin/stdout. If your code segfaults, the MCP server stays alive.
  • True async from the start — both shell and Python execution return job IDs immediately if they take >2s. Poll with status tools. Never blocks the server.
  • input() bridging — user code can call input("prompt") and the server surfaces a waiting_input status. Feed text via send_python_input() to unblock it. No other REPL MCP has this.
  • Persistent state — variables survive across execute_python() calls until you restart the worker.
  • Shell + REPL in one server — async shell commands with stdin support alongside the persistent Python worker. No need for two separate MCP servers.
  • Auto venv via uv — creates and manages a .venv automatically. No Docker required.

Install

# Run directly with uvx (no install needed)
uvx mcp-async-repl

# Or install from PyPI
pip install mcp-async-repl

Configuration

Add to your MCP client config:

{
  "mcpServers": {
    "async-repl": {
      "command": "uvx",
      "args": ["mcp-async-repl"]
    }
  }
}

With a custom venv path:

{
  "mcpServers": {
    "async-repl": {
      "command": "uvx",
      "args": ["mcp-async-repl"],
      "env": {
        "REPL_VENV_DIR": "/path/to/your/venv"
      }
    }
  }
}

Claude Desktop

Add to claude_desktop_config.json:

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
  • Windows: %APPDATA%\Claude\claude_desktop_config.json

Claude Code

claude mcp add async-repl uvx mcp-async-repl

Cursor

Add to .cursor/mcp.json or ~/.cursor/mcp.json.

Tools (9 total)

Python REPL

Tool Description
execute_python Execute code in a persistent worker. Returns immediately if >2s. Supports input().
send_python_input Feed text to a pending input() call in the worker.
check_python_status Poll status, stdout, stderr, and input prompts for the current job.
list_variables Inspect all variables in the persistent session.
restart_python_worker Kill and restart the worker. Clears all state.

Async Shell

Tool Description
execute_command_async Run any shell command in the background. Returns a job ID.
send_command_input Send stdin text to a running shell job (e.g. y\n, passwords).
check_job_status Poll status and output of a background shell job.

Package Management

Tool Description
install_package_async Install a pip package via uv in the background.

Example session

Full 8-step test run, executed by an LLM agent via Mux-Swarm:

> Test the Python REPL MCP by running these steps in order:
>
> 1. Execute: x = 42; print(f"x is {x}")
> 2. Execute: y = x * 2; print(f"y is {y}")
> 3. List variables
> 4. Execute: name = input("Enter your name: ")
> 5. Send python input: Jonathan
> 6. Execute: print(f"Hello {name}, x={x}")
> 7. Execute: import time; [print(f"tick {i}") for i in range(5) if not time.sleep(1)]
> 8. Restart the python worker, then list variables

── MuxAgent ────────────────────────────────────────────────────────────

## Step 1: Execute initial code with state and stdout
✅ Step 1 passed — State persisted, stdout captured: `x is 42`

## Step 2: Verify x persisted from step 1
✅ Step 2 passed — `x` persisted correctly: `y is 84`

## Step 3: List variables
✅ Step 3 passed — Variables `x` and `y` are present

## Step 4: Execute code with input() — should return waiting_input status
✅ Step 4 passed — Correctly returned `waiting_input` status with prompt: `'Enter your name: '`

## Step 5: Send python input to unblock
✅ Step 5 passed — Input delivered, status changed to `completed`

## Step 6: Verify name persisted alongside x and y
✅ Step 6 passed — All variables persisted: `Hello Jonathan, x=42`

## Step 7: Execute long-running code — should timeout to background
✅ Step 7a passed — Timed out to background as expected.
✅ Step 7b passed — Background job completed with all ticks: `tick 0` through `tick 4`

## Step 8: Restart python worker, then list variables
✅ Step 8 passed — Worker restarted, variables cleared (empty list returned)

───────────────────────────────────────────────────────────────────────

Summary: All Tests Passed ✅

| Step | Test                                      | Result                            |
|------|-------------------------------------------|-----------------------------------|
| 1    | State persistence + stdout capture        | ✅ `x is 42`                       |
| 2    | Variable persistence across calls         | ✅ `y is 84` (x=42 persisted)      |
| 3    | List variables                            | ✅ `x`, `y` found                  |
| 4    | input() returns waiting_input             | ✅ Status: `waiting_input`          |
| 5    | Send input unblocks                       | ✅ Status: `completed`              |
| 6    | All variables persist together            | ✅ `Hello Jonathan, x=42`           |
| 7    | Long-running → background + status check  | ✅ All ticks captured               |
| 8    | Restart clears state                      | ✅ Empty variable list              |

Architecture

┌─────────────────────────────────────────────────────┐
│                   MCP Server                        │
│               (mcp-async-repl)                      │
│                                                     │
│  ┌─────────────────┐    ┌────────────────────────┐  │
│  │  Shell Jobs      │    │  Python Worker Manager │  │
│  │                  │    │                        │  │
│  │  execute_command │    │  ┌──────────────────┐  │  │
│  │  send_input      │    │  │ Worker Subprocess│  │  │
│  │  check_status    │    │  │                  │  │  │
│  │                  │    │  │  Main Thread:    │  │  │
│  │  Each command    │    │  │   stdin loop     │  │  │
│  │  runs as an      │    │  │   (never blocks) │  │  │
│  │  async subprocess│    │  │                  │  │  │
│  │  with job ID     │    │  │  Exec Thread:    │  │  │
│  │                  │    │  │   runs exec()    │  │  │
│  │                  │    │  │   in daemon      │  │  │
│  │                  │    │  │   thread         │  │  │
│  │                  │    │  │                  │  │  │
│  │                  │    │  │  Input Queue:    │  │  │
│  │                  │    │  │   bridges input()│  │  │
│  │                  │    │  │   calls to host  │  │  │
│  └─────────────────┘    │  └──────────────────┘  │  │
│                          └────────────────────────┘  │
└─────────────────────────────────────────────────────┘

The Python worker runs in a separate subprocess communicating via JSON over stdin/stdout. Inside the worker:

  • Main thread reads commands from stdin and dispatches them. Never blocks.
  • Exec thread runs exec() in a daemon thread so input() calls don't starve the command loop.
  • Input queue bridges send_python_input() from the host to builtins.input() inside user code. When code calls input("prompt"), the worker emits an input_request message and blocks on the queue. The host pushes text onto the queue to unblock it.

This means:

  • Long-running code doesn't block the MCP server
  • input() works without a PTY
  • A crash in user code kills the worker, not the server
  • The worker can be restarted cleanly at any time

Environment variables

Variable Default Description
REPL_VENV_DIR .venv Path to the virtual environment. Created automatically via uv if it doesn't exist.

How it compares

Feature mcp-async-repl mcp-python (hdresearch) mcp-python-repl (soufiane-aazizi) mcp-background-job (dylan-gluck)
Persistent Python state ✅ ✅ ✅ ❌
Async (non-blocking) execution ✅ ❌ ❌ ✅
Background job polling ✅ ❌ ❌ ✅
input() bridging ✅ ❌ ❌ ❌
Shell command execution ✅ ❌ ❌ ✅
Shell stdin interaction ✅ ❌ ❌ ✅
Crash-isolated worker ✅ ❌ ❌ N/A
Variable introspection ✅ ✅ ✅ ❌
Package install tool ✅ ✅ ❌ ❌
Auto venv (no Docker) ✅ ❌ ✅ ✅

Requirements

  • Python >= 3.10
  • uv (for venv creation and package installs)
  • mcp (FastMCP)

License

Apache License 2.0

Author

Jonathan Bankston (@jnotsknab)

Built for Mux-Swarm — a CLI-native multi-agent runtime.

Metadata

Release files for mcp-async-repl 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for mcp-async-repl 0.1.0
File Size Uploaded Provenance
mcp_async_repl-0.1.0.tar.gz 15.0 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for mcp-async-repl 0.1.0
File Interpreter ABI Platform
mcp_async_repl-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 28.9 kB

Release files / mcp_async_repl-0.1.0.tar.gz

Download URL mcp_async_repl-0.1.0.tar.gz
Size 15.0 kB
Tags Source
SHA-256 checksum
How to use checksums
d3b890eb78262b41d85ffd17cf14bca2c35d6ee835474987e68f4d707d21f03c
BLAKE2b-256 checksum
How to use checksums
e9ad37a244ebae3c0f74ab8eeebd7c791a5f91e8b89d269e6a5ed89205a08b5e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.10

Release files / mcp_async_repl-0.1.0-py3-none-any.whl

Download URL mcp_async_repl-0.1.0-py3-none-any.whl
Size 13.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
bdeb1399ac99c74eab8820a9ccc97d2bbef90abf57c2faccf40a3a601b2b33a4
BLAKE2b-256 checksum
How to use checksums
4ac162a764e7f246df4b5a35db1ceaa7d812ca47de905d2535c27e637e088638
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.10

Security for mcp-async-repl 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page