mcp_run_isolated_python 0.1.0__tar.gz → 0.2.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_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/.github/actions/setup_python_environment/action.yaml +1 -1
- mcp_run_isolated_python-0.2.0/.github/workflows/publish.yml +73 -0
- {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/.pre-commit-config.yaml +2 -2
- {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/Dockerfile +8 -6
- {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/PKG-INFO +53 -14
- {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/README.md +50 -11
- {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/default_srt_settings.json +1 -1
- mcp_run_isolated_python-0.2.0/docker-compose.yml +12 -0
- mcp_run_isolated_python-0.2.0/docker-entrypoint.sh +16 -0
- {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/pyproject.toml +2 -2
- mcp_run_isolated_python-0.2.0/setup_host.sh +11 -0
- {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/src/mcp_run_isolated_python/code_executor.py +29 -7
- {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/tests/unit/code_executor/test_failure.py +3 -3
- {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/tests/unit/test_cli.py +1 -1
- {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/tests/unit/test_mcp_server.py +0 -2
- mcp_run_isolated_python-0.2.0/uv.lock +1954 -0
- mcp_run_isolated_python-0.1.0/.github/workflows/publish.yml +0 -31
- mcp_run_isolated_python-0.1.0/uv.lock +0 -1775
- {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/.github/FUNDING.yml +0 -0
- {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/.github/ISSUE_TEMPLATE/feature-form.yml +0 -0
- {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/.github/ISSUE_TEMPLATE/issue-form.yml +0 -0
- {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/.github/ISSUE_TEMPLATE/question-form.yml +0 -0
- {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/.github/pull_request_template.md +0 -0
- {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/.github/workflows/pytest.yml +0 -0
- {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/.github/workflows/ruff_format.yml +0 -0
- {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/.github/workflows/ruff_linter.yml +0 -0
- {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/.github/workflows/ty.yml +0 -0
- {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/.gitignore +0 -0
- {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/.intelij/Run CLI.run.xml +0 -0
- {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/.intelij/Run MCP server.run.xml +0 -0
- {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/.intelij/Run Tests.run.xml +0 -0
- {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/CONTRIBUTING.md +0 -0
- {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/LICENCE +0 -0
- {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/src/mcp_run_isolated_python/__init__.py +0 -0
- {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/src/mcp_run_isolated_python/cli.py +0 -0
- {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/src/mcp_run_isolated_python/direct_use.py +0 -0
- {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/src/mcp_run_isolated_python/mcp_server.py +0 -0
- {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/src/mcp_run_isolated_python/utils/__init__.py +0 -0
- {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/src/mcp_run_isolated_python/utils/logger.py +0 -0
- {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/src/mcp_run_isolated_python/utils/otel.py +0 -0
- {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/src/mcp_run_isolated_python/utils/settings.py +0 -0
- {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/tests/conftest.py +0 -0
- {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/tests/unit/code_executor/test_success.py +0 -0
- {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/tests/unit/test_direct_use.py +0 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
name: "Publish to PyPI"
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
# Publish on any tag starting with a `v`, e.g., v0.1.0
|
|
7
|
+
- v*
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
pypi:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
|
|
13
|
+
environment:
|
|
14
|
+
name: pypi
|
|
15
|
+
|
|
16
|
+
permissions:
|
|
17
|
+
id-token: write
|
|
18
|
+
contents: read
|
|
19
|
+
|
|
20
|
+
steps:
|
|
21
|
+
- name: Checkout
|
|
22
|
+
uses: actions/checkout@v6
|
|
23
|
+
|
|
24
|
+
- name: Setup Python Environment
|
|
25
|
+
uses: ./.github/actions/setup_python_environment
|
|
26
|
+
|
|
27
|
+
- name: Build
|
|
28
|
+
run: uv build
|
|
29
|
+
|
|
30
|
+
- name: Publish
|
|
31
|
+
run: uv publish
|
|
32
|
+
|
|
33
|
+
docker_hub:
|
|
34
|
+
runs-on: ubuntu-latest
|
|
35
|
+
|
|
36
|
+
permissions:
|
|
37
|
+
packages: write
|
|
38
|
+
contents: read
|
|
39
|
+
attestations: write
|
|
40
|
+
id-token: write
|
|
41
|
+
|
|
42
|
+
steps:
|
|
43
|
+
- name: Checkout
|
|
44
|
+
uses: actions/checkout@v6
|
|
45
|
+
|
|
46
|
+
- name: Log in to Docker Hub
|
|
47
|
+
uses: docker/login-action@v4
|
|
48
|
+
with:
|
|
49
|
+
username: ${{ secrets.DOCKER_USERNAME }}
|
|
50
|
+
password: ${{ secrets.DOCKER_PASSWORD }}
|
|
51
|
+
|
|
52
|
+
- name: Extract metadata (tags, labels) for Docker
|
|
53
|
+
id: meta
|
|
54
|
+
uses: docker/metadata-action@v6
|
|
55
|
+
with:
|
|
56
|
+
images: kigstn/mcp-run-isolated-python
|
|
57
|
+
|
|
58
|
+
- name: Build and push Docker image
|
|
59
|
+
id: push
|
|
60
|
+
uses: docker/build-push-action@v7
|
|
61
|
+
with:
|
|
62
|
+
context: .
|
|
63
|
+
file: ./Dockerfile
|
|
64
|
+
push: true
|
|
65
|
+
tags: ${{ steps.meta.outputs.tags }}
|
|
66
|
+
labels: ${{ steps.meta.outputs.labels }}
|
|
67
|
+
|
|
68
|
+
- name: Generate artifact attestation
|
|
69
|
+
uses: actions/attest@v4
|
|
70
|
+
with:
|
|
71
|
+
subject-name: index.docker.io/kigstn/mcp-run-isolated-python
|
|
72
|
+
subject-digest: ${{ steps.push.outputs.digest }}
|
|
73
|
+
push-to-registry: true
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
repos:
|
|
5
5
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
6
6
|
# Ruff version.
|
|
7
|
-
rev: v0.
|
|
7
|
+
rev: v0.16.6
|
|
8
8
|
hooks:
|
|
9
9
|
# Run the linter.
|
|
10
10
|
- id: ruff-check
|
|
@@ -19,7 +19,7 @@ repos:
|
|
|
19
19
|
|
|
20
20
|
# Gitleaks
|
|
21
21
|
- repo: https://github.com/gitleaks/gitleaks
|
|
22
|
-
rev: v8.30.
|
|
22
|
+
rev: v8.30.1
|
|
23
23
|
hooks:
|
|
24
24
|
- id: gitleaks
|
|
25
25
|
priority: 10
|
|
@@ -33,7 +33,7 @@ WORKDIR /code
|
|
|
33
33
|
COPY . .
|
|
34
34
|
|
|
35
35
|
# make sure the srt default config has the `enableWeakerNestedSandbox` set to true (needed for docker)
|
|
36
|
-
|
|
36
|
+
RUN sed -i 's/"enableWeakerNestedSandbox": *false/"enableWeakerNestedSandbox": true/' ./default_srt_settings.json
|
|
37
37
|
|
|
38
38
|
# Install project + deps into venv
|
|
39
39
|
RUN uv sync --frozen --no-editable --no-dev
|
|
@@ -41,7 +41,7 @@ RUN uv sync --frozen --no-editable --no-dev
|
|
|
41
41
|
# ----------------------------
|
|
42
42
|
# Final stage
|
|
43
43
|
# ----------------------------
|
|
44
|
-
# Then, use a final image
|
|
44
|
+
# Then, use a final image with uv and with node
|
|
45
45
|
FROM node:${ENVIROMENT}
|
|
46
46
|
LABEL authors="daniel.j"
|
|
47
47
|
|
|
@@ -52,7 +52,7 @@ RUN apt-get update \
|
|
|
52
52
|
&& rm -rf /var/lib/apt/lists/*
|
|
53
53
|
|
|
54
54
|
# install the srt sandbox
|
|
55
|
-
RUN npm install -g @anthropic-ai/sandbox-runtime
|
|
55
|
+
RUN npm install -g @anthropic-ai/sandbox-runtime@'<=0.0.64'
|
|
56
56
|
|
|
57
57
|
WORKDIR /code
|
|
58
58
|
|
|
@@ -63,6 +63,9 @@ RUN groupadd --system --gid 999 nonroot \
|
|
|
63
63
|
# Copy the Python version
|
|
64
64
|
COPY --from=builder --chown=python:python /python /python
|
|
65
65
|
|
|
66
|
+
# uv, so users of the prebuilt image can add packages at runtime via -e PYTHON_DEPENDENCIES
|
|
67
|
+
COPY --from=builder /usr/local/bin/uv /usr/local/bin/uv
|
|
68
|
+
|
|
66
69
|
# Copy project (with venv)
|
|
67
70
|
COPY --from=builder --chown=nonroot:nonroot /code /code
|
|
68
71
|
|
|
@@ -74,6 +77,5 @@ ENV PATH="/code/.venv/bin:$PATH"
|
|
|
74
77
|
|
|
75
78
|
EXPOSE 6400
|
|
76
79
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
# todo create custom settings maybe?
|
|
80
|
+
COPY --chmod=755 docker-entrypoint.sh /docker-entrypoint.sh
|
|
81
|
+
ENTRYPOINT ["/docker-entrypoint.sh"]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: mcp_run_isolated_python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: A secure and sandboxed python code interpreter for LLMs. Accessable via MCP, or directly in code
|
|
5
5
|
Project-URL: Repository, https://github.com/Kigstn/mcp-run-isolated-python
|
|
6
6
|
Author: Daniel J
|
|
@@ -14,7 +14,7 @@ License: Copyright © 2026 Daniel J
|
|
|
14
14
|
License-File: LICENCE
|
|
15
15
|
Requires-Python: >=3.10
|
|
16
16
|
Requires-Dist: cyclopts>=4.5.1
|
|
17
|
-
Requires-Dist: fastmcp>=
|
|
17
|
+
Requires-Dist: fastmcp>=4.0.3
|
|
18
18
|
Requires-Dist: filetype>=1.2.0
|
|
19
19
|
Requires-Dist: opentelemetry-api>=1.39.1
|
|
20
20
|
Requires-Dist: pydantic>=2.12.5
|
|
@@ -36,21 +36,57 @@ If you this, your LLM will always be able to count the number of "r" in strawber
|
|
|
36
36
|
|
|
37
37
|
### MCP - via Docker (recommended)
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
For this to run we have to set some security options on the container.
|
|
40
|
+
This is due to bubblewrap needing to create user, mount and pid namespaces, which the container has to be allowed to do:
|
|
41
|
+
- seccomp=unconfined
|
|
42
|
+
- apparmor=unconfined
|
|
43
|
+
- systempaths=unconfined
|
|
42
44
|
|
|
43
|
-
|
|
44
|
-
`docker run --privileged .`
|
|
45
|
+
This is mandatory - to my knowledge these are the miniumum needed permissions, but feel free to experiment :)
|
|
45
46
|
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
```
|
|
48
|
+
docker run -p 6400:6400 \
|
|
49
|
+
--security-opt seccomp=unconfined \
|
|
50
|
+
--security-opt apparmor=unconfined \
|
|
51
|
+
--security-opt systempaths=unconfined \
|
|
52
|
+
-e PYTHON_DEPENDENCIES="pydantic numpy" \
|
|
53
|
+
kigstn/mcp-run-isolated-python
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
You can pass your CLI settings directly after that, the dockerfile uses entrypoint to start the server and listens to all args.
|
|
48
57
|
|
|
49
58
|
Note:
|
|
50
59
|
|
|
60
|
+
- If you are running on Ubuntu 24 you will need to adapt your apparmor profile. We have a script, just run `setup_host.sh`. [More Info](https://github.com/anthropics/sandbox-runtime/blob/main/.github/workflows/integration-tests.yml#L152)
|
|
61
|
+
- These three options only loosen the *container*. The code your LLM runs is still sandboxed by `srt` inside it -
|
|
62
|
+
no network, no writes outside its temp dir, own pid namespace.
|
|
51
63
|
- Docker automatically creates a separate UV python interpreter for the runtime - so you dont have to pass that :)
|
|
52
|
-
- To control your
|
|
53
|
-
|
|
64
|
+
- To control your packages, use the env var `PYTHON_DEPENDENCIES` (space separated list). For greater control you need to build the image yourself
|
|
65
|
+
- We provide a sample docker-compose file, which you can use to run the server with a single command. Just adapt the env vars to your needs.
|
|
66
|
+
|
|
67
|
+
#### Configuration
|
|
68
|
+
|
|
69
|
+
To configure what packages are available to the LLM set `PYTHON_DEPENDENCIES` as an env var (space seperated list).
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
docker run ... -e PYTHON_DEPENDENCIES="numpy pandas" kigstn/mcp-run-isolated-python
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Or bake your own image on top, which is better if you restart often, deploy offline, or want a pinned set:
|
|
76
|
+
|
|
77
|
+
```dockerfile
|
|
78
|
+
FROM kigstn/mcp-run-isolated-python
|
|
79
|
+
RUN uv pip install --python /sandbox/.venv/bin/python numpy pandas
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
Build args, if you build the image yourself:
|
|
85
|
+
- `ENVIROMENT` - Default: "trixie-slim"
|
|
86
|
+
- `PYTHON_VERSION` - Default: "3.13"
|
|
87
|
+
- `PYTHON_DEPENDENCIES` - Default: "pydantic" (space separated list)
|
|
88
|
+
|
|
89
|
+
Consult the Dockerfile for detailed info
|
|
54
90
|
|
|
55
91
|
### MCP - via direct hosting
|
|
56
92
|
|
|
@@ -61,9 +97,10 @@ Then, just run the command to start the server:
|
|
|
61
97
|
|
|
62
98
|
### As a python package
|
|
63
99
|
|
|
64
|
-
This approached is generally discouraged for any production use, as it removes a lot of this projects security features.
|
|
100
|
+
This approached is generally discouraged for any production use, as it removes a lot of this projects security features.
|
|
65
101
|
|
|
66
|
-
|
|
102
|
+
TBH - it should be fine, as this is used by procts such claude code in production on your PC.
|
|
103
|
+
Just be warned :)
|
|
67
104
|
|
|
68
105
|
```py
|
|
69
106
|
from mcp_run_isolated_python import CodeSandbox, CodeSandboxSettings
|
|
@@ -81,6 +118,8 @@ async with CodeSandbox(settings=settings) as sandbox:
|
|
|
81
118
|
print(result)
|
|
82
119
|
```
|
|
83
120
|
|
|
121
|
+
When using the library like this, it will use your current python interpreter and environment, so you will have to make sure that the packages you need are installed there.
|
|
122
|
+
|
|
84
123
|
---
|
|
85
124
|
|
|
86
125
|
## Why this tool?
|
|
@@ -12,21 +12,57 @@ If you this, your LLM will always be able to count the number of "r" in strawber
|
|
|
12
12
|
|
|
13
13
|
### MCP - via Docker (recommended)
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
For this to run we have to set some security options on the container.
|
|
16
|
+
This is due to bubblewrap needing to create user, mount and pid namespaces, which the container has to be allowed to do:
|
|
17
|
+
- seccomp=unconfined
|
|
18
|
+
- apparmor=unconfined
|
|
19
|
+
- systempaths=unconfined
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
`docker run --privileged .`
|
|
21
|
+
This is mandatory - to my knowledge these are the miniumum needed permissions, but feel free to experiment :)
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
```
|
|
24
|
+
docker run -p 6400:6400 \
|
|
25
|
+
--security-opt seccomp=unconfined \
|
|
26
|
+
--security-opt apparmor=unconfined \
|
|
27
|
+
--security-opt systempaths=unconfined \
|
|
28
|
+
-e PYTHON_DEPENDENCIES="pydantic numpy" \
|
|
29
|
+
kigstn/mcp-run-isolated-python
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
You can pass your CLI settings directly after that, the dockerfile uses entrypoint to start the server and listens to all args.
|
|
24
33
|
|
|
25
34
|
Note:
|
|
26
35
|
|
|
36
|
+
- If you are running on Ubuntu 24 you will need to adapt your apparmor profile. We have a script, just run `setup_host.sh`. [More Info](https://github.com/anthropics/sandbox-runtime/blob/main/.github/workflows/integration-tests.yml#L152)
|
|
37
|
+
- These three options only loosen the *container*. The code your LLM runs is still sandboxed by `srt` inside it -
|
|
38
|
+
no network, no writes outside its temp dir, own pid namespace.
|
|
27
39
|
- Docker automatically creates a separate UV python interpreter for the runtime - so you dont have to pass that :)
|
|
28
|
-
- To control your
|
|
29
|
-
|
|
40
|
+
- To control your packages, use the env var `PYTHON_DEPENDENCIES` (space separated list). For greater control you need to build the image yourself
|
|
41
|
+
- We provide a sample docker-compose file, which you can use to run the server with a single command. Just adapt the env vars to your needs.
|
|
42
|
+
|
|
43
|
+
#### Configuration
|
|
44
|
+
|
|
45
|
+
To configure what packages are available to the LLM set `PYTHON_DEPENDENCIES` as an env var (space seperated list).
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
docker run ... -e PYTHON_DEPENDENCIES="numpy pandas" kigstn/mcp-run-isolated-python
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Or bake your own image on top, which is better if you restart often, deploy offline, or want a pinned set:
|
|
52
|
+
|
|
53
|
+
```dockerfile
|
|
54
|
+
FROM kigstn/mcp-run-isolated-python
|
|
55
|
+
RUN uv pip install --python /sandbox/.venv/bin/python numpy pandas
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
Build args, if you build the image yourself:
|
|
61
|
+
- `ENVIROMENT` - Default: "trixie-slim"
|
|
62
|
+
- `PYTHON_VERSION` - Default: "3.13"
|
|
63
|
+
- `PYTHON_DEPENDENCIES` - Default: "pydantic" (space separated list)
|
|
64
|
+
|
|
65
|
+
Consult the Dockerfile for detailed info
|
|
30
66
|
|
|
31
67
|
### MCP - via direct hosting
|
|
32
68
|
|
|
@@ -37,9 +73,10 @@ Then, just run the command to start the server:
|
|
|
37
73
|
|
|
38
74
|
### As a python package
|
|
39
75
|
|
|
40
|
-
This approached is generally discouraged for any production use, as it removes a lot of this projects security features.
|
|
76
|
+
This approached is generally discouraged for any production use, as it removes a lot of this projects security features.
|
|
41
77
|
|
|
42
|
-
|
|
78
|
+
TBH - it should be fine, as this is used by procts such claude code in production on your PC.
|
|
79
|
+
Just be warned :)
|
|
43
80
|
|
|
44
81
|
```py
|
|
45
82
|
from mcp_run_isolated_python import CodeSandbox, CodeSandboxSettings
|
|
@@ -57,6 +94,8 @@ async with CodeSandbox(settings=settings) as sandbox:
|
|
|
57
94
|
print(result)
|
|
58
95
|
```
|
|
59
96
|
|
|
97
|
+
When using the library like this, it will use your current python interpreter and environment, so you will have to make sure that the packages you need are installed there.
|
|
98
|
+
|
|
60
99
|
---
|
|
61
100
|
|
|
62
101
|
## Why this tool?
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# Lets people using the prebuilt image add packages without rebuilding:
|
|
3
|
+
# docker run -e PYTHON_DEPENDENCIES="numpy pandas" ...
|
|
4
|
+
set -eu
|
|
5
|
+
|
|
6
|
+
if [ -n "${PYTHON_DEPENDENCIES:-}" ]; then
|
|
7
|
+
echo "Installing extra python dependencies: ${PYTHON_DEPENDENCIES}"
|
|
8
|
+
# shellcheck disable=SC2086 # deliberate: space separated list of packages
|
|
9
|
+
uv pip install --python /sandbox/.venv/bin/python ${PYTHON_DEPENDENCIES}
|
|
10
|
+
fi
|
|
11
|
+
|
|
12
|
+
exec mcp-run-isolated-python \
|
|
13
|
+
--path_to_python=/sandbox/.venv/bin/python \
|
|
14
|
+
--user=nonroot \
|
|
15
|
+
--mcp_host=0.0.0.0 \
|
|
16
|
+
"$@"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "mcp_run_isolated_python"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.2.0"
|
|
4
4
|
description = "A secure and sandboxed python code interpreter for LLMs. Accessable via MCP, or directly in code"
|
|
5
5
|
authors = [{ name = "Daniel J" }]
|
|
6
6
|
license = { file = "LICENCE" }
|
|
@@ -10,7 +10,7 @@ requires-python = ">=3.10"
|
|
|
10
10
|
dependencies = [
|
|
11
11
|
"pydantic>=2.12.5",
|
|
12
12
|
"rich>=14.3.2",
|
|
13
|
-
"fastmcp>=
|
|
13
|
+
"fastmcp>=4.0.3",
|
|
14
14
|
"opentelemetry-api>=1.39.1",
|
|
15
15
|
"structlog>=25.5.0",
|
|
16
16
|
"filetype>=1.2.0",
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# Run this ON THE DOCKER HOST (the machine running dockerd)
|
|
3
|
+
#
|
|
4
|
+
# Only needed on Ubuntu 23.10+, which sets kernel.apparmor_restrict_unprivileged_userns=1. That lets
|
|
5
|
+
# bubblewrap create a user namespace but gives it zero capabilities, so bwrap dies with
|
|
6
|
+
# "setting up uid map: Permission denied". Same thing the sandbox-runtime CI does, see
|
|
7
|
+
# https://github.com/anthropics/sandbox-runtime/blob/main/.github/workflows/integration-tests.yml
|
|
8
|
+
set -eu
|
|
9
|
+
|
|
10
|
+
echo "kernel.apparmor_restrict_unprivileged_userns = 0" | sudo tee /etc/sysctl.d/99-bwrap.conf >/dev/null
|
|
11
|
+
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import shutil
|
|
3
|
-
import subprocess #
|
|
3
|
+
import subprocess # ruff: ignore[suspicious-subprocess-import]
|
|
4
4
|
import traceback
|
|
5
5
|
import uuid
|
|
6
|
+
from pathlib import Path
|
|
6
7
|
from typing import Annotated, Any, Literal
|
|
7
8
|
|
|
8
9
|
from fastmcp.utilities.types import Audio, File, Image
|
|
@@ -29,17 +30,38 @@ TypeReturnValue = list[CodeExecutionResult | EmbeddedResource | ImageContent | A
|
|
|
29
30
|
class CodeExecutor(BaseModel):
|
|
30
31
|
settings: CodeSandboxSettings
|
|
31
32
|
|
|
33
|
+
def _make_run_dir(self, *subdirs: str) -> Path:
|
|
34
|
+
path = self.settings.working_directory / uuid.uuid4().hex
|
|
35
|
+
for subdir in subdirs or ("",):
|
|
36
|
+
(path / subdir).mkdir(parents=True, exist_ok=True)
|
|
37
|
+
# make sure this is owned by the correct user
|
|
38
|
+
if self.settings.user:
|
|
39
|
+
shutil.chown(path, user=self.settings.user)
|
|
40
|
+
for subdir in subdirs:
|
|
41
|
+
shutil.chown(path / subdir, user=self.settings.user)
|
|
42
|
+
return path
|
|
43
|
+
|
|
32
44
|
def model_post_init(self, context: Any, /) -> None:
|
|
33
45
|
logger.info("Pre first run: Running pre-check to verify SRT CLI tool is available and working...")
|
|
34
46
|
|
|
35
47
|
# check that it actually works (all deps installed)
|
|
36
|
-
|
|
48
|
+
check_path = self._make_run_dir()
|
|
49
|
+
try:
|
|
50
|
+
p = subprocess.run( # ruff: ignore[subprocess-without-shell-equals-true]
|
|
51
|
+
("srt", "--settings", self.settings.path_to_srt_settings, "-c", "true"), # ruff: ignore[start-process-with-partial-path]
|
|
52
|
+
capture_output=True,
|
|
53
|
+
cwd=check_path,
|
|
54
|
+
check=False,
|
|
55
|
+
user=self.settings.user,
|
|
56
|
+
)
|
|
57
|
+
finally:
|
|
58
|
+
shutil.rmtree(check_path)
|
|
37
59
|
if p.returncode == 0:
|
|
38
60
|
logger.info("Pre-check for SRT CLI tool succeeded!")
|
|
39
61
|
else:
|
|
40
62
|
logger.error("Pre-check for SRT CLI tool failed", return_code=p.returncode, stderr=p.stderr.decode())
|
|
41
63
|
raise RuntimeError(
|
|
42
|
-
"Pre-check for SRT CLI tool failed. Please install it: `npm install -g @anthropic-ai/sandbox-runtime` & ensure it is working correctly"
|
|
64
|
+
"Pre-check for SRT CLI tool failed. Please install it: `npm install -g @anthropic-ai/sandbox-runtime@'<=0.0.64'` & ensure it is working correctly"
|
|
43
65
|
)
|
|
44
66
|
|
|
45
67
|
# Note: This is a sync function on purpose, to avoid the complexity of async subprocess. Uvicorn will spawn this in a thread
|
|
@@ -49,8 +71,7 @@ class CodeExecutor(BaseModel):
|
|
|
49
71
|
) -> TypeReturnValue:
|
|
50
72
|
try:
|
|
51
73
|
# create a temp working dir for the code to have write perms in
|
|
52
|
-
code_path = self.
|
|
53
|
-
(code_path / "output").mkdir(parents=True)
|
|
74
|
+
code_path = self._make_run_dir("output")
|
|
54
75
|
|
|
55
76
|
# write the code to a temp file
|
|
56
77
|
code_file_path = code_path / "code.py"
|
|
@@ -61,9 +82,10 @@ class CodeExecutor(BaseModel):
|
|
|
61
82
|
logger.info("Running python code...", code=python_code, settings=self.settings.model_dump())
|
|
62
83
|
try:
|
|
63
84
|
cmd = f""""{self.settings.path_to_python_interpreter}" "{code_file_path}" """
|
|
64
|
-
p = subprocess.run( #
|
|
65
|
-
("srt", "--settings", self.settings.path_to_srt_settings, cmd),
|
|
85
|
+
p = subprocess.run( # ruff: ignore[subprocess-without-shell-equals-true]
|
|
86
|
+
("srt", "--settings", self.settings.path_to_srt_settings, "-c", cmd), # ruff: ignore[start-process-with-partial-path]
|
|
66
87
|
cwd=code_path,
|
|
88
|
+
check=False,
|
|
67
89
|
capture_output=True,
|
|
68
90
|
timeout=self.settings.code_timeout_seconds,
|
|
69
91
|
user=self.settings.user,
|
|
@@ -57,11 +57,11 @@ from mcp_run_isolated_python.code_executor import CodeExecutionResult, CodeExecu
|
|
|
57
57
|
),
|
|
58
58
|
pytest.param(
|
|
59
59
|
"""
|
|
60
|
-
import
|
|
61
|
-
|
|
60
|
+
import httpx2
|
|
61
|
+
httpx2.get("https://www.google.com")
|
|
62
62
|
""",
|
|
63
63
|
"",
|
|
64
|
-
"""
|
|
64
|
+
"""httpx2.ProxyError: 403 Forbidden""",
|
|
65
65
|
id="network request",
|
|
66
66
|
),
|
|
67
67
|
],
|
{mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/tests/unit/test_mcp_server.py
RENAMED
|
@@ -18,8 +18,6 @@ async def test_start_mcp_server(settings: FullSettings):
|
|
|
18
18
|
# test that the MCP server is up by sending a request
|
|
19
19
|
try:
|
|
20
20
|
async with Client("http://localhost:6400/mcp") as client:
|
|
21
|
-
await client.ping()
|
|
22
|
-
|
|
23
21
|
tools = await client.list_tools()
|
|
24
22
|
assert len(tools) == 1
|
|
25
23
|
|