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.
Files changed (45) hide show
  1. {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/.github/actions/setup_python_environment/action.yaml +1 -1
  2. mcp_run_isolated_python-0.2.0/.github/workflows/publish.yml +73 -0
  3. {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/.pre-commit-config.yaml +2 -2
  4. {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/Dockerfile +8 -6
  5. {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/PKG-INFO +53 -14
  6. {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/README.md +50 -11
  7. {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/default_srt_settings.json +1 -1
  8. mcp_run_isolated_python-0.2.0/docker-compose.yml +12 -0
  9. mcp_run_isolated_python-0.2.0/docker-entrypoint.sh +16 -0
  10. {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/pyproject.toml +2 -2
  11. mcp_run_isolated_python-0.2.0/setup_host.sh +11 -0
  12. {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/src/mcp_run_isolated_python/code_executor.py +29 -7
  13. {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/tests/unit/code_executor/test_failure.py +3 -3
  14. {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/tests/unit/test_cli.py +1 -1
  15. {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/tests/unit/test_mcp_server.py +0 -2
  16. mcp_run_isolated_python-0.2.0/uv.lock +1954 -0
  17. mcp_run_isolated_python-0.1.0/.github/workflows/publish.yml +0 -31
  18. mcp_run_isolated_python-0.1.0/uv.lock +0 -1775
  19. {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/.github/FUNDING.yml +0 -0
  20. {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  21. {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/.github/ISSUE_TEMPLATE/feature-form.yml +0 -0
  22. {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/.github/ISSUE_TEMPLATE/issue-form.yml +0 -0
  23. {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/.github/ISSUE_TEMPLATE/question-form.yml +0 -0
  24. {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/.github/pull_request_template.md +0 -0
  25. {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/.github/workflows/pytest.yml +0 -0
  26. {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/.github/workflows/ruff_format.yml +0 -0
  27. {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/.github/workflows/ruff_linter.yml +0 -0
  28. {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/.github/workflows/ty.yml +0 -0
  29. {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/.gitignore +0 -0
  30. {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/.intelij/Run CLI.run.xml +0 -0
  31. {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/.intelij/Run MCP server.run.xml +0 -0
  32. {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/.intelij/Run Tests.run.xml +0 -0
  33. {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/CONTRIBUTING.md +0 -0
  34. {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/LICENCE +0 -0
  35. {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/src/mcp_run_isolated_python/__init__.py +0 -0
  36. {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/src/mcp_run_isolated_python/cli.py +0 -0
  37. {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/src/mcp_run_isolated_python/direct_use.py +0 -0
  38. {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/src/mcp_run_isolated_python/mcp_server.py +0 -0
  39. {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/src/mcp_run_isolated_python/utils/__init__.py +0 -0
  40. {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/src/mcp_run_isolated_python/utils/logger.py +0 -0
  41. {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/src/mcp_run_isolated_python/utils/otel.py +0 -0
  42. {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/src/mcp_run_isolated_python/utils/settings.py +0 -0
  43. {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/tests/conftest.py +0 -0
  44. {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/tests/unit/code_executor/test_success.py +0 -0
  45. {mcp_run_isolated_python-0.1.0 → mcp_run_isolated_python-0.2.0}/tests/unit/test_direct_use.py +0 -0
@@ -26,5 +26,5 @@ runs:
26
26
  - name: Install srt & deps
27
27
  shell: bash
28
28
  run: |
29
- npm install -g @anthropic-ai/sandbox-runtime
29
+ npm install -g @anthropic-ai/sandbox-runtime@'<=0.0.64'
30
30
  sudo apt-get install ripgrep socat bubblewrap
@@ -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.15.0
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.0
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
- # RUN sed -i 's/"enableWeakerNestedSandbox": *false/"enableWeakerNestedSandbox": true/' ./default_srt_settings.json
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 without uv, but with node
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
- ENTRYPOINT ["mcp-run-isolated-python", "--path_to_python='/sandbox/.venv/bin/python'", "--user=nonroot"]
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.4
1
+ Metadata-Version: 2.5
2
2
  Name: mcp_run_isolated_python
3
- Version: 0.1.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>=3.0.1
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
- WIP - support is suboptimal, as the anthropic sandbox runtime does currently only run in privileged docker running
40
- configs.
41
- Waiting on them to add proper support for containers, the existing options does not seem to do anything :D
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
- If you still want to use it, here is the command:
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
- You can pass your CLI settings directly after that, the dockerfile uses entrypoint to start the server and listens to
47
- all args.
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 python version & packages, use the docker build args `PYTHON_VERSION` and `PYTHON_DEPENDENCIES` (space
53
- separated list)
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
- But if you like to live dangerously, or you are the only one using this, or you are building a super quick prototype - this will be fine & should be safe, as it is still using sandboxed code execution.
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
- WIP - support is suboptimal, as the anthropic sandbox runtime does currently only run in privileged docker running
16
- configs.
17
- Waiting on them to add proper support for containers, the existing options does not seem to do anything :D
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
- If you still want to use it, here is the command:
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
- You can pass your CLI settings directly after that, the dockerfile uses entrypoint to start the server and listens to
23
- all args.
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 python version & packages, use the docker build args `PYTHON_VERSION` and `PYTHON_DEPENDENCIES` (space
29
- separated list)
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
- But if you like to live dangerously, or you are the only one using this, or you are building a super quick prototype - this will be fine & should be safe, as it is still using sandboxed code execution.
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?
@@ -6,7 +6,7 @@
6
6
  "allowLocalBinding": false
7
7
  },
8
8
  "filesystem": {
9
- "denyRead": ["~/.ssh"],
9
+ "denyRead": [],
10
10
  "allowWrite": ["."],
11
11
  "denyWrite": []
12
12
  },
@@ -0,0 +1,12 @@
1
+ services:
2
+ mcp-run-isolated-python:
3
+ build:
4
+ context: .
5
+ environment:
6
+ PYTHON_DEPENDENCIES: "pydantic"
7
+ ports:
8
+ - "6400:6400"
9
+ security_opt:
10
+ - seccomp=unconfined
11
+ - apparmor=unconfined
12
+ - systempaths=unconfined
@@ -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.1.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>=3.0.1",
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 # noqa: S404
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
- p = subprocess.run(("srt", "python -c '1+1'"), capture_output=True)
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.settings.working_directory / uuid.uuid4().hex
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( # noqa: S603
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 requests
61
- requests.get("https://www.google.com")
60
+ import httpx2
61
+ httpx2.get("https://www.google.com")
62
62
  """,
63
63
  "",
64
- """OSError: Tunnel connection failed""",
64
+ """httpx2.ProxyError: 403 Forbidden""",
65
65
  id="network request",
66
66
  ),
67
67
  ],
@@ -13,7 +13,7 @@ async def test_cli_starts_mcp_server():
13
13
  # test that the MCP server is up by sending a request
14
14
  try:
15
15
  async with Client("http://localhost:6400/mcp") as client:
16
- await client.ping()
16
+ await client.list_tools()
17
17
 
18
18
  # stop MCP server
19
19
  finally:
@@ -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