mcp-python-repl 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mcp_python_repl-0.1.0/.gitignore +6 -0
- mcp_python_repl-0.1.0/.idea/.gitignore +10 -0
- mcp_python_repl-0.1.0/.idea/inspectionProfiles/profiles_settings.xml +6 -0
- mcp_python_repl-0.1.0/.idea/mcp-python-repl.iml +19 -0
- mcp_python_repl-0.1.0/.idea/misc.xml +7 -0
- mcp_python_repl-0.1.0/.idea/modules.xml +8 -0
- mcp_python_repl-0.1.0/.idea/vcs.xml +6 -0
- mcp_python_repl-0.1.0/.idea/workspace.xml +106 -0
- mcp_python_repl-0.1.0/PKG-INFO +187 -0
- mcp_python_repl-0.1.0/README.md +158 -0
- mcp_python_repl-0.1.0/pyproject.toml +66 -0
- mcp_python_repl-0.1.0/src/__init__.py +0 -0
- mcp_python_repl-0.1.0/src/mcp_python_repl/config.py +82 -0
- mcp_python_repl-0.1.0/src/mcp_python_repl/executor.py +235 -0
- mcp_python_repl-0.1.0/src/mcp_python_repl/server.py +754 -0
- mcp_python_repl-0.1.0/src/mcp_python_repl/session.py +142 -0
- mcp_python_repl-0.1.0/tests/__init__.py +0 -0
- mcp_python_repl-0.1.0/tests/test_core.py +188 -0
- mcp_python_repl-0.1.0/uv.lock +915 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<module type="PYTHON_MODULE" version="4">
|
|
3
|
+
<component name="NewModuleRootManager">
|
|
4
|
+
<content url="file://$MODULE_DIR$">
|
|
5
|
+
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
|
6
|
+
<sourceFolder url="file://$MODULE_DIR$/src/mcp_python_repl" isTestSource="false" />
|
|
7
|
+
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
|
8
|
+
</content>
|
|
9
|
+
<orderEntry type="jdk" jdkName="Python 3.10 (mcp-python-repl)" jdkType="Python SDK" />
|
|
10
|
+
<orderEntry type="sourceFolder" forTests="false" />
|
|
11
|
+
</component>
|
|
12
|
+
<component name="PyDocumentationSettings">
|
|
13
|
+
<option name="format" value="PLAIN" />
|
|
14
|
+
<option name="myDocStringFormat" value="Plain" />
|
|
15
|
+
</component>
|
|
16
|
+
<component name="TestRunnerService">
|
|
17
|
+
<option name="PROJECT_TEST_RUNNER" value="py.test" />
|
|
18
|
+
</component>
|
|
19
|
+
</module>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<project version="4">
|
|
3
|
+
<component name="Black">
|
|
4
|
+
<option name="sdkName" value="Python 3.13" />
|
|
5
|
+
</component>
|
|
6
|
+
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10 (mcp-python-repl)" project-jdk-type="Python SDK" />
|
|
7
|
+
</project>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<project version="4">
|
|
3
|
+
<component name="ProjectModuleManager">
|
|
4
|
+
<modules>
|
|
5
|
+
<module fileurl="file://$PROJECT_DIR$/.idea/mcp-python-repl.iml" filepath="$PROJECT_DIR$/.idea/mcp-python-repl.iml" />
|
|
6
|
+
</modules>
|
|
7
|
+
</component>
|
|
8
|
+
</project>
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<project version="4">
|
|
3
|
+
<component name="AutoImportSettings">
|
|
4
|
+
<option name="autoReloadType" value="SELECTIVE" />
|
|
5
|
+
</component>
|
|
6
|
+
<component name="ChangeListManager">
|
|
7
|
+
<list default="true" id="e740e8cb-c5fc-42c9-902f-eeafbc67b973" name="Changes" comment="" />
|
|
8
|
+
<option name="SHOW_DIALOG" value="false" />
|
|
9
|
+
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
|
10
|
+
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
|
11
|
+
<option name="LAST_RESOLUTION" value="IGNORE" />
|
|
12
|
+
</component>
|
|
13
|
+
<component name="FileTemplateManagerImpl">
|
|
14
|
+
<option name="RECENT_TEMPLATES">
|
|
15
|
+
<list>
|
|
16
|
+
<option value="Python Script" />
|
|
17
|
+
</list>
|
|
18
|
+
</option>
|
|
19
|
+
</component>
|
|
20
|
+
<component name="Git.Settings">
|
|
21
|
+
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
|
22
|
+
</component>
|
|
23
|
+
<component name="ProjectColorInfo">{
|
|
24
|
+
"associatedIndex": 3
|
|
25
|
+
}</component>
|
|
26
|
+
<component name="ProjectId" id="39Iqh21oBUOTYzdMa6gzov8igkx" />
|
|
27
|
+
<component name="ProjectViewState">
|
|
28
|
+
<option name="hideEmptyMiddlePackages" value="true" />
|
|
29
|
+
<option name="showLibraryContents" value="true" />
|
|
30
|
+
</component>
|
|
31
|
+
<component name="PropertiesComponent"><![CDATA[{
|
|
32
|
+
"keyToString": {
|
|
33
|
+
"ModuleVcsDetector.initialDetectionPerformed": "true",
|
|
34
|
+
"Python.mcp-python-repl.executor": "Run",
|
|
35
|
+
"RunOnceActivity.ShowReadmeOnStart": "true",
|
|
36
|
+
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
|
|
37
|
+
"git-widget-placeholder": "main",
|
|
38
|
+
"last_opened_file_path": "/Users/soufiane/Documents/private_project/mcp-python-repl"
|
|
39
|
+
}
|
|
40
|
+
}]]></component>
|
|
41
|
+
<component name="RecentsManager">
|
|
42
|
+
<key name="CopyFile.RECENT_KEYS">
|
|
43
|
+
<recent name="$PROJECT_DIR$/src" />
|
|
44
|
+
<recent name="$PROJECT_DIR$/tests" />
|
|
45
|
+
<recent name="$PROJECT_DIR$" />
|
|
46
|
+
<recent name="pyproject.toml README.md" />
|
|
47
|
+
</key>
|
|
48
|
+
</component>
|
|
49
|
+
<component name="RunManager">
|
|
50
|
+
<configuration name="mcp-python-repl" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
|
|
51
|
+
<module name="mcp-python-repl" />
|
|
52
|
+
<option name="ENV_FILES" value="" />
|
|
53
|
+
<option name="INTERPRETER_OPTIONS" value="" />
|
|
54
|
+
<option name="PARENT_ENVS" value="true" />
|
|
55
|
+
<envs>
|
|
56
|
+
<env name="PYTHONUNBUFFERED" value="1" />
|
|
57
|
+
</envs>
|
|
58
|
+
<option name="SDK_HOME" value="" />
|
|
59
|
+
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/src" />
|
|
60
|
+
<option name="IS_MODULE_SDK" value="true" />
|
|
61
|
+
<option name="ADD_CONTENT_ROOTS" value="true" />
|
|
62
|
+
<option name="ADD_SOURCE_ROOTS" value="true" />
|
|
63
|
+
<EXTENSION ID="net.ashald.envfile">
|
|
64
|
+
<option name="IS_ENABLED" value="false" />
|
|
65
|
+
<option name="IS_SUBST" value="false" />
|
|
66
|
+
<option name="IS_PATH_MACRO_SUPPORTED" value="false" />
|
|
67
|
+
<option name="IS_IGNORE_MISSING_FILES" value="false" />
|
|
68
|
+
<option name="IS_ENABLE_EXPERIMENTAL_INTEGRATIONS" value="false" />
|
|
69
|
+
<ENTRIES>
|
|
70
|
+
<ENTRY IS_ENABLED="true" PARSER="runconfig" IS_EXECUTABLE="false" />
|
|
71
|
+
</ENTRIES>
|
|
72
|
+
</EXTENSION>
|
|
73
|
+
<option name="RUN_TOOL" value="" />
|
|
74
|
+
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/.venv/bin/mcp-python-repl" />
|
|
75
|
+
<option name="PARAMETERS" value="" />
|
|
76
|
+
<option name="SHOW_COMMAND_LINE" value="false" />
|
|
77
|
+
<option name="EMULATE_TERMINAL" value="false" />
|
|
78
|
+
<option name="MODULE_MODE" value="false" />
|
|
79
|
+
<option name="REDIRECT_INPUT" value="false" />
|
|
80
|
+
<option name="INPUT_FILE" value="" />
|
|
81
|
+
<method v="2" />
|
|
82
|
+
</configuration>
|
|
83
|
+
<recent_temporary>
|
|
84
|
+
<list>
|
|
85
|
+
<item itemvalue="Python.mcp-python-repl" />
|
|
86
|
+
</list>
|
|
87
|
+
</recent_temporary>
|
|
88
|
+
</component>
|
|
89
|
+
<component name="SharedIndexes">
|
|
90
|
+
<attachedChunks>
|
|
91
|
+
<set>
|
|
92
|
+
<option value="bundled-python-sdk-0978a7220b69-9867c7d4c0a4-com.jetbrains.pycharm.pro.sharedIndexes.bundled-PY-253.28294.336" />
|
|
93
|
+
</set>
|
|
94
|
+
</attachedChunks>
|
|
95
|
+
</component>
|
|
96
|
+
<component name="TaskManager">
|
|
97
|
+
<task active="true" id="Default" summary="Default task">
|
|
98
|
+
<changelist id="e740e8cb-c5fc-42c9-902f-eeafbc67b973" name="Changes" comment="" />
|
|
99
|
+
<created>1770394047623</created>
|
|
100
|
+
<option name="number" value="Default" />
|
|
101
|
+
<option name="presentableId" value="Default" />
|
|
102
|
+
<updated>1770394047623</updated>
|
|
103
|
+
</task>
|
|
104
|
+
<servers />
|
|
105
|
+
</component>
|
|
106
|
+
</project>
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mcp-python-repl
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A production-grade MCP server providing a persistent Python REPL with multi-session support, sandboxing, and timeout protection.
|
|
5
|
+
Project-URL: Homepage, https://github.com/soufiane-aazizi/mcp-python-repl
|
|
6
|
+
Project-URL: Repository, https://github.com/soufiane-aazizi/mcp-python-repl
|
|
7
|
+
Project-URL: Issues, https://github.com/soufiane-aazizi/mcp-python-repl/issues
|
|
8
|
+
Author: Soufiane Aazizi
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
Keywords: agent,interpreter,llm,mcp,model-context-protocol,python,repl
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
20
|
+
Classifier: Topic :: Software Development :: Interpreters
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Requires-Dist: mcp>=1.7.0
|
|
23
|
+
Requires-Dist: pydantic>=2.0
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
|
|
26
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
27
|
+
Requires-Dist: ruff>=0.8; extra == 'dev'
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
# 🐍 mcp-python-repl
|
|
31
|
+
|
|
32
|
+
A **production-grade** MCP server providing a persistent Python REPL with multi-session support, sandboxing, and timeout protection.
|
|
33
|
+
|
|
34
|
+
Built for LLM agents that need to execute Python code across multiple turns with **variables that persist between calls**.
|
|
35
|
+
|
|
36
|
+
## ✨ Features
|
|
37
|
+
|
|
38
|
+
| Feature | Description |
|
|
39
|
+
|---|---|
|
|
40
|
+
| **Multi-session** | Isolated sessions with unique IDs — run parallel workflows |
|
|
41
|
+
| **Persistent namespace** | Variables survive across calls within a session |
|
|
42
|
+
| **Timeout protection** | Configurable execution timeout (SIGALRM on Unix) |
|
|
43
|
+
| **Sandboxing** | Optional mode blocks dangerous modules (`subprocess`, `socket`, etc.) |
|
|
44
|
+
| **Package install** | Install pip packages on-the-fly (prefers `uv` for speed) |
|
|
45
|
+
| **File execution** | Run `.py` files inside the persistent session |
|
|
46
|
+
| **Dual transport** | stdio (local) and streamable-http (remote) |
|
|
47
|
+
| **Full introspection** | List variables, get history, check server status |
|
|
48
|
+
| **Env-based config** | All settings via `REPL_*` environment variables |
|
|
49
|
+
|
|
50
|
+
## 🚀 Quick Start
|
|
51
|
+
|
|
52
|
+
### With Claude Desktop / Cursor (stdio)
|
|
53
|
+
|
|
54
|
+
Add to your MCP config:
|
|
55
|
+
|
|
56
|
+
```json
|
|
57
|
+
{
|
|
58
|
+
"mcpServers": {
|
|
59
|
+
"python-repl": {
|
|
60
|
+
"command": "uvx",
|
|
61
|
+
"args": ["mcp-python-repl"]
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### With uv (local dev)
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
# Clone and run
|
|
71
|
+
git clone https://github.com/soufiane-aazizi/mcp-python-repl.git
|
|
72
|
+
cd mcp-python-repl
|
|
73
|
+
uv run mcp-python-repl
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### HTTP transport (remote / multi-client)
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
REPL_TRANSPORT=streamable-http REPL_PORT=8000 uv run mcp-python-repl
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## 🛠️ Tools
|
|
83
|
+
|
|
84
|
+
### Code Execution
|
|
85
|
+
|
|
86
|
+
| Tool | Description |
|
|
87
|
+
|---|---|
|
|
88
|
+
| `repl_run_code` | Execute Python code with persistent namespace |
|
|
89
|
+
| `repl_run_file` | Execute a `.py` file in the session |
|
|
90
|
+
| `repl_install_package` | Install a pip package (uses `uv` if available) |
|
|
91
|
+
|
|
92
|
+
### Namespace Management
|
|
93
|
+
|
|
94
|
+
| Tool | Description |
|
|
95
|
+
|---|---|
|
|
96
|
+
| `repl_list_namespace` | List all variables in a session |
|
|
97
|
+
| `repl_get_variable` | Get the full value of a variable |
|
|
98
|
+
| `repl_set_variable` | Inject a variable from JSON |
|
|
99
|
+
| `repl_delete_variable` | Delete a specific variable |
|
|
100
|
+
| `repl_clear_namespace` | Clear all variables in a session |
|
|
101
|
+
|
|
102
|
+
### Session Management
|
|
103
|
+
|
|
104
|
+
| Tool | Description |
|
|
105
|
+
|---|---|
|
|
106
|
+
| `repl_list_sessions` | List all active sessions |
|
|
107
|
+
| `repl_delete_session` | Delete a session and its data |
|
|
108
|
+
|
|
109
|
+
### Debugging
|
|
110
|
+
|
|
111
|
+
| Tool | Description |
|
|
112
|
+
|---|---|
|
|
113
|
+
| `repl_get_history` | Get execution history for a session |
|
|
114
|
+
| `repl_server_status` | Server config, Python version, session count |
|
|
115
|
+
|
|
116
|
+
## 🔄 How Persistence Works
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
Call 1: repl_run_code(code="data = [1,2,3]; total = sum(data); result = total")
|
|
120
|
+
→ returns: {"result": 6, "session_id": "a1b2c3d4e5f6", "new_variables": ["data", "total"]}
|
|
121
|
+
|
|
122
|
+
Call 2: repl_run_code(code="doubled = [x*2 for x in data]; result = doubled", session_id="a1b2c3d4e5f6")
|
|
123
|
+
→ returns: {"result": [2,4,6], "new_variables": ["doubled"]}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
> **Important:** The `result` variable is for returning output to the caller. It does **NOT** persist. Use named variables instead.
|
|
127
|
+
|
|
128
|
+
## ⚙️ Configuration
|
|
129
|
+
|
|
130
|
+
All settings are configurable via environment variables:
|
|
131
|
+
|
|
132
|
+
| Variable | Default | Description |
|
|
133
|
+
|---|---|---|
|
|
134
|
+
| `REPL_TIMEOUT` | `30` | Max execution time in seconds |
|
|
135
|
+
| `REPL_MAX_SESSIONS` | `50` | Maximum concurrent sessions |
|
|
136
|
+
| `REPL_SESSION_TTL` | `120` | Session expiry in minutes |
|
|
137
|
+
| `REPL_MAX_OUTPUT` | `1048576` | Max stdout/stderr capture (bytes) |
|
|
138
|
+
| `REPL_SANDBOX` | `false` | Enable sandboxing (`true`/`false`) |
|
|
139
|
+
| `REPL_TRANSPORT` | `stdio` | Transport: `stdio` or `streamable-http` |
|
|
140
|
+
| `REPL_HOST` | `127.0.0.1` | HTTP host (when using HTTP transport) |
|
|
141
|
+
| `REPL_PORT` | `8000` | HTTP port (when using HTTP transport) |
|
|
142
|
+
| `REPL_WORKDIR` | `cwd` | Working directory for executions |
|
|
143
|
+
|
|
144
|
+
### Sandbox Mode
|
|
145
|
+
|
|
146
|
+
When `REPL_SANDBOX=true`, the following modules are blocked:
|
|
147
|
+
|
|
148
|
+
`subprocess`, `shutil`, `ctypes`, `socket`, `http.server`, `xmlrpc`, `ftplib`, `smtplib`, `telnetlib`, `webbrowser`
|
|
149
|
+
|
|
150
|
+
And the following builtins are removed: `exec`, `eval`, `compile`, `__import__` (replaced with a restricted version).
|
|
151
|
+
|
|
152
|
+
## 🧪 Development
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
# Install dev dependencies
|
|
156
|
+
uv sync --extra dev
|
|
157
|
+
|
|
158
|
+
# Run tests
|
|
159
|
+
uv run pytest -v
|
|
160
|
+
|
|
161
|
+
# Lint
|
|
162
|
+
uv run ruff check src/ tests/
|
|
163
|
+
|
|
164
|
+
# Test with MCP Inspector
|
|
165
|
+
npx @modelcontextprotocol/inspector uv run mcp-python-repl
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## 📦 Project Structure
|
|
169
|
+
|
|
170
|
+
```
|
|
171
|
+
mcp-python-repl/
|
|
172
|
+
├── src/mcp_python_repl/
|
|
173
|
+
│ ├── __init__.py # Package metadata
|
|
174
|
+
│ ├── config.py # Env-based configuration
|
|
175
|
+
│ ├── session.py # Multi-session manager with TTL
|
|
176
|
+
│ ├── executor.py # Python code executor (timeout + sandbox)
|
|
177
|
+
│ └── server.py # MCP server with all tools
|
|
178
|
+
├── tests/
|
|
179
|
+
│ └── test_core.py # Unit + integration tests
|
|
180
|
+
├── pyproject.toml # uv/hatch project config
|
|
181
|
+
├── LICENSE # MIT
|
|
182
|
+
└── README.md
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## 📄 License
|
|
186
|
+
|
|
187
|
+
MIT — See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# 🐍 mcp-python-repl
|
|
2
|
+
|
|
3
|
+
A **production-grade** MCP server providing a persistent Python REPL with multi-session support, sandboxing, and timeout protection.
|
|
4
|
+
|
|
5
|
+
Built for LLM agents that need to execute Python code across multiple turns with **variables that persist between calls**.
|
|
6
|
+
|
|
7
|
+
## ✨ Features
|
|
8
|
+
|
|
9
|
+
| Feature | Description |
|
|
10
|
+
|---|---|
|
|
11
|
+
| **Multi-session** | Isolated sessions with unique IDs — run parallel workflows |
|
|
12
|
+
| **Persistent namespace** | Variables survive across calls within a session |
|
|
13
|
+
| **Timeout protection** | Configurable execution timeout (SIGALRM on Unix) |
|
|
14
|
+
| **Sandboxing** | Optional mode blocks dangerous modules (`subprocess`, `socket`, etc.) |
|
|
15
|
+
| **Package install** | Install pip packages on-the-fly (prefers `uv` for speed) |
|
|
16
|
+
| **File execution** | Run `.py` files inside the persistent session |
|
|
17
|
+
| **Dual transport** | stdio (local) and streamable-http (remote) |
|
|
18
|
+
| **Full introspection** | List variables, get history, check server status |
|
|
19
|
+
| **Env-based config** | All settings via `REPL_*` environment variables |
|
|
20
|
+
|
|
21
|
+
## 🚀 Quick Start
|
|
22
|
+
|
|
23
|
+
### With Claude Desktop / Cursor (stdio)
|
|
24
|
+
|
|
25
|
+
Add to your MCP config:
|
|
26
|
+
|
|
27
|
+
```json
|
|
28
|
+
{
|
|
29
|
+
"mcpServers": {
|
|
30
|
+
"python-repl": {
|
|
31
|
+
"command": "uvx",
|
|
32
|
+
"args": ["mcp-python-repl"]
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### With uv (local dev)
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# Clone and run
|
|
42
|
+
git clone https://github.com/soufiane-aazizi/mcp-python-repl.git
|
|
43
|
+
cd mcp-python-repl
|
|
44
|
+
uv run mcp-python-repl
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### HTTP transport (remote / multi-client)
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
REPL_TRANSPORT=streamable-http REPL_PORT=8000 uv run mcp-python-repl
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## 🛠️ Tools
|
|
54
|
+
|
|
55
|
+
### Code Execution
|
|
56
|
+
|
|
57
|
+
| Tool | Description |
|
|
58
|
+
|---|---|
|
|
59
|
+
| `repl_run_code` | Execute Python code with persistent namespace |
|
|
60
|
+
| `repl_run_file` | Execute a `.py` file in the session |
|
|
61
|
+
| `repl_install_package` | Install a pip package (uses `uv` if available) |
|
|
62
|
+
|
|
63
|
+
### Namespace Management
|
|
64
|
+
|
|
65
|
+
| Tool | Description |
|
|
66
|
+
|---|---|
|
|
67
|
+
| `repl_list_namespace` | List all variables in a session |
|
|
68
|
+
| `repl_get_variable` | Get the full value of a variable |
|
|
69
|
+
| `repl_set_variable` | Inject a variable from JSON |
|
|
70
|
+
| `repl_delete_variable` | Delete a specific variable |
|
|
71
|
+
| `repl_clear_namespace` | Clear all variables in a session |
|
|
72
|
+
|
|
73
|
+
### Session Management
|
|
74
|
+
|
|
75
|
+
| Tool | Description |
|
|
76
|
+
|---|---|
|
|
77
|
+
| `repl_list_sessions` | List all active sessions |
|
|
78
|
+
| `repl_delete_session` | Delete a session and its data |
|
|
79
|
+
|
|
80
|
+
### Debugging
|
|
81
|
+
|
|
82
|
+
| Tool | Description |
|
|
83
|
+
|---|---|
|
|
84
|
+
| `repl_get_history` | Get execution history for a session |
|
|
85
|
+
| `repl_server_status` | Server config, Python version, session count |
|
|
86
|
+
|
|
87
|
+
## 🔄 How Persistence Works
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
Call 1: repl_run_code(code="data = [1,2,3]; total = sum(data); result = total")
|
|
91
|
+
→ returns: {"result": 6, "session_id": "a1b2c3d4e5f6", "new_variables": ["data", "total"]}
|
|
92
|
+
|
|
93
|
+
Call 2: repl_run_code(code="doubled = [x*2 for x in data]; result = doubled", session_id="a1b2c3d4e5f6")
|
|
94
|
+
→ returns: {"result": [2,4,6], "new_variables": ["doubled"]}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
> **Important:** The `result` variable is for returning output to the caller. It does **NOT** persist. Use named variables instead.
|
|
98
|
+
|
|
99
|
+
## ⚙️ Configuration
|
|
100
|
+
|
|
101
|
+
All settings are configurable via environment variables:
|
|
102
|
+
|
|
103
|
+
| Variable | Default | Description |
|
|
104
|
+
|---|---|---|
|
|
105
|
+
| `REPL_TIMEOUT` | `30` | Max execution time in seconds |
|
|
106
|
+
| `REPL_MAX_SESSIONS` | `50` | Maximum concurrent sessions |
|
|
107
|
+
| `REPL_SESSION_TTL` | `120` | Session expiry in minutes |
|
|
108
|
+
| `REPL_MAX_OUTPUT` | `1048576` | Max stdout/stderr capture (bytes) |
|
|
109
|
+
| `REPL_SANDBOX` | `false` | Enable sandboxing (`true`/`false`) |
|
|
110
|
+
| `REPL_TRANSPORT` | `stdio` | Transport: `stdio` or `streamable-http` |
|
|
111
|
+
| `REPL_HOST` | `127.0.0.1` | HTTP host (when using HTTP transport) |
|
|
112
|
+
| `REPL_PORT` | `8000` | HTTP port (when using HTTP transport) |
|
|
113
|
+
| `REPL_WORKDIR` | `cwd` | Working directory for executions |
|
|
114
|
+
|
|
115
|
+
### Sandbox Mode
|
|
116
|
+
|
|
117
|
+
When `REPL_SANDBOX=true`, the following modules are blocked:
|
|
118
|
+
|
|
119
|
+
`subprocess`, `shutil`, `ctypes`, `socket`, `http.server`, `xmlrpc`, `ftplib`, `smtplib`, `telnetlib`, `webbrowser`
|
|
120
|
+
|
|
121
|
+
And the following builtins are removed: `exec`, `eval`, `compile`, `__import__` (replaced with a restricted version).
|
|
122
|
+
|
|
123
|
+
## 🧪 Development
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
# Install dev dependencies
|
|
127
|
+
uv sync --extra dev
|
|
128
|
+
|
|
129
|
+
# Run tests
|
|
130
|
+
uv run pytest -v
|
|
131
|
+
|
|
132
|
+
# Lint
|
|
133
|
+
uv run ruff check src/ tests/
|
|
134
|
+
|
|
135
|
+
# Test with MCP Inspector
|
|
136
|
+
npx @modelcontextprotocol/inspector uv run mcp-python-repl
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## 📦 Project Structure
|
|
140
|
+
|
|
141
|
+
```
|
|
142
|
+
mcp-python-repl/
|
|
143
|
+
├── src/mcp_python_repl/
|
|
144
|
+
│ ├── __init__.py # Package metadata
|
|
145
|
+
│ ├── config.py # Env-based configuration
|
|
146
|
+
│ ├── session.py # Multi-session manager with TTL
|
|
147
|
+
│ ├── executor.py # Python code executor (timeout + sandbox)
|
|
148
|
+
│ └── server.py # MCP server with all tools
|
|
149
|
+
├── tests/
|
|
150
|
+
│ └── test_core.py # Unit + integration tests
|
|
151
|
+
├── pyproject.toml # uv/hatch project config
|
|
152
|
+
├── LICENSE # MIT
|
|
153
|
+
└── README.md
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## 📄 License
|
|
157
|
+
|
|
158
|
+
MIT — See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "mcp-python-repl"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "A production-grade MCP server providing a persistent Python REPL with multi-session support, sandboxing, and timeout protection."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Soufiane Aazizi" },
|
|
14
|
+
]
|
|
15
|
+
keywords = ["mcp", "python", "repl", "interpreter", "llm", "agent", "model-context-protocol"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 4 - Beta",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.10",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Programming Language :: Python :: 3.13",
|
|
25
|
+
"Topic :: Software Development :: Interpreters",
|
|
26
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
dependencies = [
|
|
30
|
+
"mcp>=1.7.0",
|
|
31
|
+
"pydantic>=2.0",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[project.optional-dependencies]
|
|
35
|
+
dev = [
|
|
36
|
+
"pytest>=8.0",
|
|
37
|
+
"pytest-asyncio>=0.24",
|
|
38
|
+
"ruff>=0.8",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
[project.urls]
|
|
42
|
+
Homepage = "https://github.com/soufiane-aazizi/mcp-python-repl"
|
|
43
|
+
Repository = "https://github.com/soufiane-aazizi/mcp-python-repl"
|
|
44
|
+
Issues = "https://github.com/soufiane-aazizi/mcp-python-repl/issues"
|
|
45
|
+
|
|
46
|
+
[project.scripts]
|
|
47
|
+
mcp-python-repl = "mcp_python_repl.server:main"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
[tool.ruff]
|
|
51
|
+
target-version = "py310"
|
|
52
|
+
line-length = 100
|
|
53
|
+
|
|
54
|
+
[tool.ruff.lint]
|
|
55
|
+
select = ["E", "F", "I", "W", "UP"]
|
|
56
|
+
|
|
57
|
+
[tool.hatch.build.targets.wheel]
|
|
58
|
+
packages = ["src/mcp_python_repl"]
|
|
59
|
+
|
|
60
|
+
[tool.hatch.build.targets.wheel.sources]
|
|
61
|
+
"src" = ""
|
|
62
|
+
|
|
63
|
+
[tool.pytest.ini_options]
|
|
64
|
+
pythonpath = ["src"]
|
|
65
|
+
asyncio_mode = "auto"
|
|
66
|
+
testpaths = ["tests"]
|
|
File without changes
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configuration for mcp-python-repl.
|
|
3
|
+
|
|
4
|
+
All settings can be overridden via environment variables prefixed with REPL_.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
|
|
12
|
+
# ---------------------------------------------------------------------------
|
|
13
|
+
# Defaults
|
|
14
|
+
# ---------------------------------------------------------------------------
|
|
15
|
+
DEFAULT_TIMEOUT_SECONDS = 30
|
|
16
|
+
DEFAULT_MAX_SESSIONS = 50
|
|
17
|
+
DEFAULT_SESSION_TTL_MINUTES = 120
|
|
18
|
+
DEFAULT_MAX_OUTPUT_BYTES = 1_048_576 # 1 MB
|
|
19
|
+
DEFAULT_LOG_ENTRIES = 200
|
|
20
|
+
|
|
21
|
+
# Modules / builtins that are blocked in sandboxed mode
|
|
22
|
+
SANDBOXED_BLOCKED_MODULES = frozenset(
|
|
23
|
+
{
|
|
24
|
+
"subprocess",
|
|
25
|
+
"shutil",
|
|
26
|
+
"ctypes",
|
|
27
|
+
"socket",
|
|
28
|
+
"http.server",
|
|
29
|
+
"xmlrpc",
|
|
30
|
+
"ftplib",
|
|
31
|
+
"smtplib",
|
|
32
|
+
"telnetlib",
|
|
33
|
+
"webbrowser",
|
|
34
|
+
"antigravity",
|
|
35
|
+
}
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
SANDBOXED_BLOCKED_BUILTINS = frozenset(
|
|
39
|
+
{
|
|
40
|
+
"exec",
|
|
41
|
+
"eval",
|
|
42
|
+
"compile",
|
|
43
|
+
"__import__",
|
|
44
|
+
}
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass(frozen=True)
|
|
49
|
+
class Config:
|
|
50
|
+
"""Immutable server configuration resolved from environment."""
|
|
51
|
+
|
|
52
|
+
timeout_seconds: int = DEFAULT_TIMEOUT_SECONDS
|
|
53
|
+
max_sessions: int = DEFAULT_MAX_SESSIONS
|
|
54
|
+
session_ttl_minutes: int = DEFAULT_SESSION_TTL_MINUTES
|
|
55
|
+
max_output_bytes: int = DEFAULT_MAX_OUTPUT_BYTES
|
|
56
|
+
max_log_entries: int = DEFAULT_LOG_ENTRIES
|
|
57
|
+
sandbox_enabled: bool = False
|
|
58
|
+
working_directory: str = field(default_factory=os.getcwd)
|
|
59
|
+
transport: str = "stdio"
|
|
60
|
+
host: str = "127.0.0.1"
|
|
61
|
+
port: int = 8000
|
|
62
|
+
|
|
63
|
+
# ------------------------------------------------------------------
|
|
64
|
+
@classmethod
|
|
65
|
+
def from_env(cls) -> Config:
|
|
66
|
+
"""Build configuration from REPL_* environment variables."""
|
|
67
|
+
|
|
68
|
+
def _env(key: str, default: str) -> str:
|
|
69
|
+
return os.environ.get(f"REPL_{key}", default)
|
|
70
|
+
|
|
71
|
+
return cls(
|
|
72
|
+
timeout_seconds=int(_env("TIMEOUT", str(DEFAULT_TIMEOUT_SECONDS))),
|
|
73
|
+
max_sessions=int(_env("MAX_SESSIONS", str(DEFAULT_MAX_SESSIONS))),
|
|
74
|
+
session_ttl_minutes=int(_env("SESSION_TTL", str(DEFAULT_SESSION_TTL_MINUTES))),
|
|
75
|
+
max_output_bytes=int(_env("MAX_OUTPUT", str(DEFAULT_MAX_OUTPUT_BYTES))),
|
|
76
|
+
max_log_entries=int(_env("MAX_LOG_ENTRIES", str(DEFAULT_LOG_ENTRIES))),
|
|
77
|
+
sandbox_enabled=_env("SANDBOX", "false").lower() in ("1", "true", "yes"),
|
|
78
|
+
working_directory=_env("WORKDIR", os.getcwd()),
|
|
79
|
+
transport=_env("TRANSPORT", "stdio"),
|
|
80
|
+
host=_env("HOST", "127.0.0.1"),
|
|
81
|
+
port=int(_env("PORT", "8000")),
|
|
82
|
+
)
|