Metadata-Version: 2.4
Name: mcp-async-repl
Version: 0.1.0
Summary: Async Python REPL + Shell execution for MCP. Persistent state, background jobs, and interactive input() bridging.
Project-URL: Homepage, https://github.com/jnotsknab/mcp-async-repl
Project-URL: Repository, https://github.com/jnotsknab/mcp-async-repl
Project-URL: Issues, https://github.com/jnotsknab/mcp-async-repl/issues
Project-URL: Documentation, https://github.com/jnotsknab/mcp-async-repl#readme
Author-email: Jonathan Bankston <jnotsknab@users.noreply.github.com>
License-Expression: Apache-2.0
License-File: LICENSE
Keywords: ai-agent,async,background-jobs,claude,code-execution,llm-tools,mcp,model-context-protocol,persistent-session,python,repl,shell
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Interpreters
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.10
Requires-Dist: mcp>=1.0.0
Description-Content-Type: text/markdown

# 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

```bash
# 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:

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

With a custom venv path:

```json
{
  "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

```bash
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](https://docs.astral.sh/uv/) (for venv creation and package installs)
- [mcp](https://pypi.org/project/mcp/) (FastMCP)

## License

Apache License 2.0

## Author

Jonathan Bankston ([@jnotsknab](https://github.com/jnotsknab))

Built for [Mux-Swarm](https://github.com/jnotsknab/mux-swarm) — a CLI-native multi-agent runtime.