mcp-async-repl
Async Python REPL + Shell execution for MCP. Provides persistent state, background jobs, interactive input() bridging, and crash isolation in a single server.
README
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 callinput("prompt")and the server surfaces awaiting_inputstatus. Feed text viasend_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
.venvautomatically. 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 soinput()calls don't starve the command loop. - Input queue bridges
send_python_input()from the host tobuiltins.input()inside user code. When code callsinput("prompt"), the worker emits aninput_requestmessage 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
License
Apache License 2.0
Author
Jonathan Bankston (@jnotsknab)
Built for Mux-Swarm — a CLI-native multi-agent runtime.
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.