# Directory Structure
```
├── .gitattributes
├── .gitignore
├── .VSCodeCounter
│ ├── 2025-03-28_08-28-01
│ │ ├── details.md
│ │ ├── diff-details.md
│ │ ├── diff.csv
│ │ ├── diff.md
│ │ ├── diff.txt
│ │ ├── results.csv
│ │ ├── results.json
│ │ ├── results.md
│ │ └── results.txt
│ └── 2025-05-01_18-42-30
│ ├── details.md
│ ├── diff-details.md
│ ├── diff.csv
│ ├── diff.md
│ ├── diff.txt
│ ├── results.csv
│ ├── results.json
│ ├── results.md
│ └── results.txt
├── Dockerfile
├── LICENSE
├── mcp_server.py
├── pyproject.toml
├── README.md
├── smithery.yaml
└── tests
├── test_local_python_executor.py
└── test_mcp_server.py
```
# Files
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
```
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
```
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
1 | # Python
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 | *.so
6 | .Python
7 | build/
8 | develop-eggs/
9 | dist/
10 | downloads/
11 | eggs/
12 | .eggs/
13 | lib/
14 | lib64/
15 | parts/
16 | sdist/
17 | var/
18 | wheels/
19 | *.egg-info/
20 | .installed.cfg
21 | *.egg
22 |
23 | # Virtual Environment
24 | .env
25 | .venv
26 | env/
27 | venv/
28 | ENV/
29 |
30 | # IDE
31 | .idea/
32 | .vscode/
33 | *.swp
34 | *.swo
35 |
36 | # Testing
37 | .coverage
38 | htmlcov/
39 | .pytest_cache/
40 | .mypy_cache/
41 |
42 | # Distribution
43 | dist/
44 | build/
45 | *.egg-info/
46 |
47 | uv.lock
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
1 | # Safe Local Python Executor
2 |
3 | An MCP server (stdio transport) that wraps Hugging Face's [`LocalPythonExecutor`](https://github.com/huggingface/smolagents/blob/main/src/smolagents/local_python_executor.py)
4 | (from the [`smolagents`](https://huggingface.co/docs/smolagents/en/index) framework). It is a custom Python runtime that
5 | provides basic isolation/security when running Python code generated by LLMs locally. It does not require Docker or VM.
6 | This package allows to expose the Python executor via MCP (Model Context Protocol) as a tool for LLM apps like Claude Desktop, Cursor or any other MCP compatible client.
7 | In case of Claude Desktop this tool is an easy way to add a missing Code Interpreter (available as a plugin in ChatGPT for quite a while already).
8 |
9 | <img width="1032" alt="image" src="https://github.com/user-attachments/assets/3b820bfc-970a-4315-8f2d-970591c6fdae" />
10 |
11 | ## Features
12 |
13 | - Exposes `run_python` tool
14 | - Safer execution of Python code compared to direct use of Python `eva()l`
15 | - Ran via uv in Python venv
16 | - No file I/O ops are allowed
17 | - Restricted list of imports
18 | - collections
19 | - datetime
20 | - itertools
21 | - math
22 | - queue
23 | - random
24 | - re
25 | - stat
26 | - statistics
27 | - time
28 | - unicodedata
29 |
30 | ## Security
31 |
32 | Be careful with execution of code produced by LLM on your machine, stay away from MCP servers that run Python via command line or using `eval()`. The safest option is using a VM or a docker container, though it requires some effort to set-up, consumes resources/slower. There're 3rd party servcices providing Python runtime, though they require registration, API keys etc.
33 |
34 | `LocalPythonExecutor` provides a good balance between direct use of local Python environment (which is easier to set-up) AND remote execution in Dokcer container or a VM/3rd party service (which is safe). Hugginng Face team has invested time into creating a quick and safe option to run LLM generated code used by their code agents. This MCP server builds upon it:
35 |
36 | >To add a first layer of security, code execution in smolagents is not performed by the vanilla Python interpreter. We have re-built a more secure LocalPythonExecutor from the ground up.
37 |
38 | Read more [here](https://huggingface.co/docs/smolagents/en/tutorials/secure_code_execution#local-code-execution).
39 |
40 | ## Installation and Execution
41 |
42 | ### Installing via Smithery
43 |
44 | To install Safe Local Python Executor for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@maxim-saplin/mcp_safe_local_python_executor):
45 |
46 | ```bash
47 | npx -y @smithery/cli install @maxim-saplin/mcp_safe_local_python_executor --client claude
48 | ```
49 |
50 | ### Installing Manually
51 | 1. Install `uv` (e.h. `brew install uv` on macOS or use [official docs](https://docs.astral.sh/uv/getting-started/installation/#__tabbed_1_2))
52 | 2. Clone the repo, change the directory `cd mcp_safe_local_python_executor`
53 | 3. The server can be started via command line `uv run mcp_server.py`, venv will be created automatically, depedencies (smollagents, mcp) will be installed
54 |
55 |
56 | ## Configuring Claude Desktop
57 |
58 | 1. Make sure you have Claude for Desktop installed (download from [claude.ai](https://claude.ai/desktop))
59 | 2. Edit your Claude for Desktop configuration file:
60 | - macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
61 | - Windows: `%APPDATA%\Claude\claude_desktop_config.json`
62 | - Or open Claude Desktop -> Settings -> Developer -> click "Edit Config" button
63 |
64 | 3. Add the following configuration:
65 |
66 | ```json
67 | {
68 | "mcpServers": {
69 | "safe-local-python-executor": {
70 | "command": "uv",
71 | "args": [
72 | "--directory",
73 | "/path/to/mcp_local_python_executor/",
74 | "run",
75 | "mcp_server.py"
76 | ]
77 | }
78 | }
79 | }
80 | ```
81 |
82 | 4. Restart Claude for Desktop
83 | 5. The Python executor tool will now be available in Claude (you'll see hammer icon in the message input field)
84 |
85 | ## Example Prompts
86 |
87 | Once configured, you can use prompts like:
88 |
89 | - "Calculate the factorial of 5 using Python"
90 | - "Create a list of prime numbers up to 100"
91 | - "Solve this equation (use Python): x^2 + 5x + 6 = 0"
92 |
93 |
94 | ## Development
95 |
96 | Clone the repo. Use `uv` to create venv, install dev dependencies, run tests:
97 |
98 | ```
99 | uv venv .venv
100 | uv sync --group dev
101 | python -m pytest tests/
102 | ```
103 |
104 | -----------------------
105 |
106 | <a href="https://glama.ai/mcp/servers/@maxim-saplin/mcp_safe_local_python_executor">
107 | <img width="380" height="200" src="https://glama.ai/mcp/servers/@maxim-saplin/mcp_safe_local_python_executor/badge" />
108 | </a>
109 |
110 | [](https://smithery.ai/server/@maxim-saplin/mcp_safe_local_python_executor)
111 |
112 | [](https://mseep.ai/app/maxim-saplin-mcp-safe-local-python-executor)
113 |
114 |
```
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
```dockerfile
1 | # Generated by https://smithery.ai. See: https://smithery.ai/docs/build/project-config
2 | FROM ghcr.io/astral-sh/uv:alpine
3 |
4 | # Install build dependencies
5 | WORKDIR /app
6 |
7 | # Copy project files
8 | COPY . .
9 |
10 | # Pre-create virtual environment and install dependencies to avoid runtime downloads
11 | RUN uv venv .venv \
12 | && uv sync --no-dev
13 |
14 | # Default command to run the MCP server
15 | CMD ["uv", "run", "mcp_server.py"]
16 |
```
--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------
```yaml
1 | # Smithery configuration file: https://smithery.ai/docs/build/project-config
2 |
3 | startCommand:
4 | type: stdio
5 | commandFunction:
6 | # A JS function that produces the CLI command based on the given config to start the MCP on stdio.
7 | |-
8 | (config) => ({ command: 'uv', args: ['run', 'mcp_server.py'], env: {} })
9 | configSchema:
10 | # JSON Schema defining the configuration options for the MCP.
11 | type: object
12 | properties: {}
13 | exampleConfig: {}
14 |
```
--------------------------------------------------------------------------------
/.VSCodeCounter/2025-05-01_18-42-30/results.csv:
--------------------------------------------------------------------------------
```
1 | "filename", "language", "Markdown", "Python", "comment", "blank", "total"
2 | "/Users/admin/src/mcp_safe_local_python_executor/README.md", "Markdown", 73, 0, 0, 27, 100
3 | "/Users/admin/src/mcp_safe_local_python_executor/mcp_server.py", "Python", 0, 59, 3, 18, 80
4 | "/Users/admin/src/mcp_safe_local_python_executor/tests/test_local_python_executor.py", "Python", 0, 66, 2, 24, 92
5 | "/Users/admin/src/mcp_safe_local_python_executor/tests/test_mcp_server.py", "Python", 0, 32, 4, 10, 46
6 | "Total", "-", 73, 157, 9, 79, 318
```
--------------------------------------------------------------------------------
/.VSCodeCounter/2025-05-01_18-42-30/results.json:
--------------------------------------------------------------------------------
```json
1 | {"file:///Users/admin/src/mcp_safe_local_python_executor/README.md":{"language":"Markdown","code":73,"comment":0,"blank":27},"file:///Users/admin/src/mcp_safe_local_python_executor/mcp_server.py":{"language":"Python","code":59,"comment":3,"blank":18},"file:///Users/admin/src/mcp_safe_local_python_executor/tests/test_local_python_executor.py":{"language":"Python","code":66,"comment":2,"blank":24},"file:///Users/admin/src/mcp_safe_local_python_executor/tests/test_mcp_server.py":{"language":"Python","code":32,"comment":4,"blank":10}}
```
--------------------------------------------------------------------------------
/.VSCodeCounter/2025-05-01_18-42-30/results.md:
--------------------------------------------------------------------------------
```markdown
1 | # Summary
2 |
3 | Date : 2025-05-01 18:42:30
4 |
5 | Directory /Users/admin/src/mcp_safe_local_python_executor
6 |
7 | Total : 4 files, 230 codes, 9 comments, 79 blanks, all 318 lines
8 |
9 | Summary / [Details](details.md) / [Diff Summary](diff.md) / [Diff Details](diff-details.md)
10 |
11 | ## Languages
12 | | language | files | code | comment | blank | total |
13 | | :--- | ---: | ---: | ---: | ---: | ---: |
14 | | Python | 3 | 157 | 9 | 52 | 218 |
15 | | Markdown | 1 | 73 | 0 | 27 | 100 |
16 |
17 | ## Directories
18 | | path | files | code | comment | blank | total |
19 | | :--- | ---: | ---: | ---: | ---: | ---: |
20 | | . | 4 | 230 | 9 | 79 | 318 |
21 | | . (Files) | 2 | 132 | 3 | 45 | 180 |
22 | | tests | 2 | 98 | 6 | 34 | 138 |
23 |
24 | Summary / [Details](details.md) / [Diff Summary](diff.md) / [Diff Details](diff-details.md)
```
--------------------------------------------------------------------------------
/.VSCodeCounter/2025-05-01_18-42-30/details.md:
--------------------------------------------------------------------------------
```markdown
1 | # Details
2 |
3 | Date : 2025-05-01 18:42:30
4 |
5 | Directory /Users/admin/src/mcp_safe_local_python_executor
6 |
7 | Total : 4 files, 230 codes, 9 comments, 79 blanks, all 318 lines
8 |
9 | [Summary](results.md) / Details / [Diff Summary](diff.md) / [Diff Details](diff-details.md)
10 |
11 | ## Files
12 | | filename | language | code | comment | blank | total |
13 | | :--- | :--- | ---: | ---: | ---: | ---: |
14 | | [README.md](/README.md) | Markdown | 73 | 0 | 27 | 100 |
15 | | [mcp\_server.py](/mcp_server.py) | Python | 59 | 3 | 18 | 80 |
16 | | [tests/test\_local\_python\_executor.py](/tests/test_local_python_executor.py) | Python | 66 | 2 | 24 | 92 |
17 | | [tests/test\_mcp\_server.py](/tests/test_mcp_server.py) | Python | 32 | 4 | 10 | 46 |
18 |
19 | [Summary](results.md) / Details / [Diff Summary](diff.md) / [Diff Details](diff-details.md)
```
--------------------------------------------------------------------------------
/.VSCodeCounter/2025-03-28_08-28-01/results.md:
--------------------------------------------------------------------------------
```markdown
1 | # Summary
2 |
3 | Date : 2025-03-28 08:28:01
4 |
5 | Directory /Users/admin/src/mcp_safe_local_python_executor
6 |
7 | Total : 9 files, 859 codes, 45 comments, 158 blanks, all 1062 lines
8 |
9 | Summary / [Details](details.md) / [Diff Summary](diff.md) / [Diff Details](diff-details.md)
10 |
11 | ## Languages
12 | | language | files | code | comment | blank | total |
13 | | :--- | ---: | ---: | ---: | ---: | ---: |
14 | | Python | 7 | 753 | 45 | 122 | 920 |
15 | | Markdown | 1 | 99 | 0 | 36 | 135 |
16 | | pip requirements | 1 | 7 | 0 | 0 | 7 |
17 |
18 | ## Directories
19 | | path | files | code | comment | blank | total |
20 | | :--- | ---: | ---: | ---: | ---: | ---: |
21 | | . | 9 | 859 | 45 | 158 | 1,062 |
22 | | . (Files) | 5 | 677 | 13 | 110 | 800 |
23 | | examples | 3 | 163 | 24 | 37 | 224 |
24 | | tests | 1 | 19 | 8 | 11 | 38 |
25 |
26 | Summary / [Details](details.md) / [Diff Summary](diff.md) / [Diff Details](diff-details.md)
```
--------------------------------------------------------------------------------
/.VSCodeCounter/2025-03-28_08-28-01/diff.md:
--------------------------------------------------------------------------------
```markdown
1 | # Diff Summary
2 |
3 | Date : 2025-03-28 08:28:01
4 |
5 | Directory /Users/admin/src/mcp_safe_local_python_executor
6 |
7 | Total : 11 files, 583 codes, 27 comments, 76 blanks, all 686 lines
8 |
9 | [Summary](results.md) / [Details](details.md) / Diff Summary / [Diff Details](diff-details.md)
10 |
11 | ## Languages
12 | | language | files | code | comment | blank | total |
13 | | :--- | ---: | ---: | ---: | ---: | ---: |
14 | | Python | 9 | 540 | 27 | 60 | 627 |
15 | | Markdown | 1 | 36 | 0 | 16 | 52 |
16 | | pip requirements | 1 | 7 | 0 | 0 | 7 |
17 |
18 | ## Directories
19 | | path | files | code | comment | blank | total |
20 | | :--- | ---: | ---: | ---: | ---: | ---: |
21 | | . | 11 | 583 | 27 | 76 | 686 |
22 | | . (Files) | 5 | 555 | 10 | 72 | 637 |
23 | | examples | 3 | 163 | 24 | 37 | 224 |
24 | | tests | 3 | -135 | -7 | -33 | -175 |
25 |
26 | [Summary](results.md) / [Details](details.md) / Diff Summary / [Diff Details](diff-details.md)
```
--------------------------------------------------------------------------------
/.VSCodeCounter/2025-05-01_18-42-30/diff.md:
--------------------------------------------------------------------------------
```markdown
1 | # Diff Summary
2 |
3 | Date : 2025-05-01 18:42:30
4 |
5 | Directory /Users/admin/src/mcp_safe_local_python_executor
6 |
7 | Total : 10 files, -629 codes, -36 comments, -79 blanks, all -744 lines
8 |
9 | [Summary](results.md) / [Details](details.md) / Diff Summary / [Diff Details](diff-details.md)
10 |
11 | ## Languages
12 | | language | files | code | comment | blank | total |
13 | | :--- | ---: | ---: | ---: | ---: | ---: |
14 | | pip requirements | 1 | -7 | 0 | 0 | -7 |
15 | | Markdown | 1 | -26 | 0 | -9 | -35 |
16 | | Python | 8 | -596 | -36 | -70 | -702 |
17 |
18 | ## Directories
19 | | path | files | code | comment | blank | total |
20 | | :--- | ---: | ---: | ---: | ---: | ---: |
21 | | . | 10 | -629 | -36 | -79 | -744 |
22 | | . (Files) | 5 | -545 | -10 | -65 | -620 |
23 | | examples | 3 | -163 | -24 | -37 | -224 |
24 | | tests | 2 | 79 | -2 | 23 | 100 |
25 |
26 | [Summary](results.md) / [Details](details.md) / Diff Summary / [Diff Details](diff-details.md)
```
--------------------------------------------------------------------------------
/.VSCodeCounter/2025-03-28_08-28-01/results.csv:
--------------------------------------------------------------------------------
```
1 | "filename", "language", "Python", "Markdown", "pip requirements", "comment", "blank", "total"
2 | "/Users/admin/src/mcp_safe_local_python_executor/README.md", "Markdown", 0, 99, 0, 0, 36, 135
3 | "/Users/admin/src/mcp_safe_local_python_executor/examples/run_executor.py", "Python", 24, 0, 0, 4, 7, 35
4 | "/Users/admin/src/mcp_safe_local_python_executor/examples/run_mcp_server.py", "Python", 58, 0, 0, 6, 11, 75
5 | "/Users/admin/src/mcp_safe_local_python_executor/examples/test_mcp_client.py", "Python", 81, 0, 0, 14, 19, 114
6 | "/Users/admin/src/mcp_safe_local_python_executor/executor.py", "Python", 486, 0, 0, 1, 51, 538
7 | "/Users/admin/src/mcp_safe_local_python_executor/mcp_server.py", "Python", 56, 0, 0, 12, 21, 89
8 | "/Users/admin/src/mcp_safe_local_python_executor/requirements.txt", "pip requirements", 0, 0, 7, 0, 0, 7
9 | "/Users/admin/src/mcp_safe_local_python_executor/setup.py", "Python", 29, 0, 0, 0, 2, 31
10 | "/Users/admin/src/mcp_safe_local_python_executor/tests/test_mcp_server.py", "Python", 19, 0, 0, 8, 11, 38
11 | "Total", "-", 753, 99, 7, 45, 158, 1062
```
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
```toml
1 | [build-system]
2 | requires = ["setuptools>=42", "wheel"]
3 | build-backend = "setuptools.build_meta"
4 |
5 | [project]
6 | name = "mcp_safe_local_python_executor"
7 | version = "0.1.0"
8 | description = "MCP server exposing tool for a safe local Python code execution"
9 | readme = "README.md"
10 | authors = [
11 | {name = "Maxim Saplin"}
12 | ]
13 | license = {text = "MIT"}
14 | requires-python = ">=3.10"
15 | classifiers = [
16 | "Development Status :: 3 - Alpha",
17 | "Intended Audience :: Developers",
18 | "Topic :: Software Development :: Libraries :: Python Modules",
19 | "Programming Language :: Python :: 3.10",
20 | "Programming Language :: Python :: 3.11",
21 | "Programming Language :: Python :: 3.12",
22 | "Programming Language :: Python :: 3.13",
23 | "Operating System :: OS Independent",
24 | ]
25 | dependencies = [
26 | "mcp[cli]>=1.5.0",
27 | "smolagents==1.12.0",
28 | ]
29 |
30 | [dependency-groups]
31 | dev = [
32 | "pytest>=7.0.0",
33 | "pytest-asyncio>=0.21.0",
34 | ]
35 |
36 | [project.urls]
37 | "Homepage" = "https://github.com/maxim-saplin/mcp_safe_local_python_executor/tree/main"
38 |
39 | [tool.setuptools.packages.find]
40 | where = ["."]
41 |
```
--------------------------------------------------------------------------------
/.VSCodeCounter/2025-03-28_08-28-01/details.md:
--------------------------------------------------------------------------------
```markdown
1 | # Details
2 |
3 | Date : 2025-03-28 08:28:01
4 |
5 | Directory /Users/admin/src/mcp_safe_local_python_executor
6 |
7 | Total : 9 files, 859 codes, 45 comments, 158 blanks, all 1062 lines
8 |
9 | [Summary](results.md) / Details / [Diff Summary](diff.md) / [Diff Details](diff-details.md)
10 |
11 | ## Files
12 | | filename | language | code | comment | blank | total |
13 | | :--- | :--- | ---: | ---: | ---: | ---: |
14 | | [README.md](/README.md) | Markdown | 99 | 0 | 36 | 135 |
15 | | [examples/run\_executor.py](/examples/run_executor.py) | Python | 24 | 4 | 7 | 35 |
16 | | [examples/run\_mcp\_server.py](/examples/run_mcp_server.py) | Python | 58 | 6 | 11 | 75 |
17 | | [examples/test\_mcp\_client.py](/examples/test_mcp_client.py) | Python | 81 | 14 | 19 | 114 |
18 | | [executor.py](/executor.py) | Python | 486 | 1 | 51 | 538 |
19 | | [mcp\_server.py](/mcp_server.py) | Python | 56 | 12 | 21 | 89 |
20 | | [requirements.txt](/requirements.txt) | pip requirements | 7 | 0 | 0 | 7 |
21 | | [setup.py](/setup.py) | Python | 29 | 0 | 2 | 31 |
22 | | [tests/test\_mcp\_server.py](/tests/test_mcp_server.py) | Python | 19 | 8 | 11 | 38 |
23 |
24 | [Summary](results.md) / Details / [Diff Summary](diff.md) / [Diff Details](diff-details.md)
```
--------------------------------------------------------------------------------
/.VSCodeCounter/2025-05-01_18-42-30/diff.csv:
--------------------------------------------------------------------------------
```
1 | "filename", "language", "Markdown", "Python", "pip requirements", "comment", "blank", "total"
2 | "/Users/admin/src/mcp_safe_local_python_executor/README.md", "Markdown", -26, 0, 0, 0, -9, -35
3 | "/Users/admin/src/mcp_safe_local_python_executor/examples/run_executor.py", "Python", 0, -24, 0, -4, -7, -35
4 | "/Users/admin/src/mcp_safe_local_python_executor/examples/run_mcp_server.py", "Python", 0, -58, 0, -6, -11, -75
5 | "/Users/admin/src/mcp_safe_local_python_executor/examples/test_mcp_client.py", "Python", 0, -81, 0, -14, -19, -114
6 | "/Users/admin/src/mcp_safe_local_python_executor/executor.py", "Python", 0, -486, 0, -1, -51, -538
7 | "/Users/admin/src/mcp_safe_local_python_executor/mcp_server.py", "Python", 0, 3, 0, -9, -3, -9
8 | "/Users/admin/src/mcp_safe_local_python_executor/requirements.txt", "pip requirements", 0, 0, -7, 0, 0, -7
9 | "/Users/admin/src/mcp_safe_local_python_executor/setup.py", "Python", 0, -29, 0, 0, -2, -31
10 | "/Users/admin/src/mcp_safe_local_python_executor/tests/test_local_python_executor.py", "Python", 0, 66, 0, 2, 24, 92
11 | "/Users/admin/src/mcp_safe_local_python_executor/tests/test_mcp_server.py", "Python", 0, 13, 0, -4, -1, 8
12 | "Total", "-", -26, -596, -7, -36, -79, -744
```
--------------------------------------------------------------------------------
/.VSCodeCounter/2025-03-28_08-28-01/results.json:
--------------------------------------------------------------------------------
```json
1 | {"file:///Users/admin/src/mcp_safe_local_python_executor/mcp_server.py":{"language":"Python","code":56,"comment":12,"blank":21},"file:///Users/admin/src/mcp_safe_local_python_executor/executor.py":{"language":"Python","code":486,"comment":1,"blank":51},"file:///Users/admin/src/mcp_safe_local_python_executor/examples/run_executor.py":{"language":"Python","code":24,"comment":4,"blank":7},"file:///Users/admin/src/mcp_safe_local_python_executor/examples/run_mcp_server.py":{"language":"Python","code":58,"comment":6,"blank":11},"file:///Users/admin/src/mcp_safe_local_python_executor/examples/test_mcp_client.py":{"language":"Python","code":81,"comment":14,"blank":19},"file:///Users/admin/src/mcp_safe_local_python_executor/setup.py":{"language":"Python","code":29,"comment":0,"blank":2},"file:///Users/admin/src/mcp_safe_local_python_executor/README.md":{"language":"Markdown","code":99,"comment":0,"blank":36},"file:///Users/admin/src/mcp_safe_local_python_executor/requirements.txt":{"language":"pip requirements","code":7,"comment":0,"blank":0},"file:///Users/admin/src/mcp_safe_local_python_executor/tests/test_mcp_server.py":{"language":"Python","code":19,"comment":8,"blank":11}}
```
--------------------------------------------------------------------------------
/tests/test_mcp_server.py:
--------------------------------------------------------------------------------
```python
1 | """
2 | Tests for the MCP server.
3 | """
4 |
5 | import os
6 | import sys
7 | import pytest
8 |
9 | # Add the parent directory to the path
10 | sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
11 |
12 | # Import the MCP server module
13 | import mcp_server
14 |
15 |
16 | @pytest.mark.asyncio
17 | async def test_run_python():
18 | """Test that the run_python tool works correctly."""
19 | # Test basic arithmetic
20 | result = await mcp_server.run_python("result = 2 + 2")
21 | assert result["result"] == 4
22 |
23 | # Test math module functionality
24 | result = await mcp_server.run_python("import math\nresult = math.sqrt(16)")
25 | assert result["result"] == 4.0
26 |
27 |
28 | @pytest.mark.asyncio
29 | async def test_prime_numbers():
30 | """Test generating a list of prime numbers."""
31 | code = """
32 | def is_prime(n):
33 | if n < 2:
34 | return False
35 | for i in range(2, int(n**0.5) + 1):
36 | if n % i == 0:
37 | return False
38 | return True
39 |
40 | primes = [num for num in range(2, 101) if is_prime(num)]
41 | result = primes"""
42 |
43 | result = await mcp_server.run_python(code)
44 | expected_primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41,
45 | 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]
46 | assert result["result"] == expected_primes
```
--------------------------------------------------------------------------------
/.VSCodeCounter/2025-03-28_08-28-01/diff.csv:
--------------------------------------------------------------------------------
```
1 | "filename", "language", "Python", "Markdown", "pip requirements", "comment", "blank", "total"
2 | "/Users/admin/src/mcp_safe_local_python_executor/README.md", "Markdown", 0, 36, 0, 0, 16, 52
3 | "/Users/admin/src/mcp_safe_local_python_executor/examples/run_executor.py", "Python", 24, 0, 0, 4, 7, 35
4 | "/Users/admin/src/mcp_safe_local_python_executor/examples/run_mcp_server.py", "Python", 58, 0, 0, 6, 11, 75
5 | "/Users/admin/src/mcp_safe_local_python_executor/examples/test_mcp_client.py", "Python", 81, 0, 0, 14, 19, 114
6 | "/Users/admin/src/mcp_safe_local_python_executor/executor.py", "Python", 486, 0, 0, 1, 51, 538
7 | "/Users/admin/src/mcp_safe_local_python_executor/mcp_server.py", "Python", -3, 0, 0, 9, 3, 9
8 | "/Users/admin/src/mcp_safe_local_python_executor/requirements.txt", "pip requirements", 0, 0, 7, 0, 0, 7
9 | "/Users/admin/src/mcp_safe_local_python_executor/setup.py", "Python", 29, 0, 0, 0, 2, 31
10 | "/Users/admin/src/mcp_safe_local_python_executor/tests/run_executor.py", "Python", -24, 0, 0, -4, -7, -35
11 | "/Users/admin/src/mcp_safe_local_python_executor/tests/test_local_python_executor.py", "Python", -98, 0, 0, -7, -27, -132
12 | "/Users/admin/src/mcp_safe_local_python_executor/tests/test_mcp_server.py", "Python", -13, 0, 0, 4, 1, -8
13 | "Total", "-", 540, 36, 7, 27, 76, 686
```
--------------------------------------------------------------------------------
/.VSCodeCounter/2025-05-01_18-42-30/diff-details.md:
--------------------------------------------------------------------------------
```markdown
1 | # Diff Details
2 |
3 | Date : 2025-05-01 18:42:30
4 |
5 | Directory /Users/admin/src/mcp_safe_local_python_executor
6 |
7 | Total : 10 files, -629 codes, -36 comments, -79 blanks, all -744 lines
8 |
9 | [Summary](results.md) / [Details](details.md) / [Diff Summary](diff.md) / Diff Details
10 |
11 | ## Files
12 | | filename | language | code | comment | blank | total |
13 | | :--- | :--- | ---: | ---: | ---: | ---: |
14 | | [README.md](/README.md) | Markdown | -26 | 0 | -9 | -35 |
15 | | [examples/run\_executor.py](/examples/run_executor.py) | Python | -24 | -4 | -7 | -35 |
16 | | [examples/run\_mcp\_server.py](/examples/run_mcp_server.py) | Python | -58 | -6 | -11 | -75 |
17 | | [examples/test\_mcp\_client.py](/examples/test_mcp_client.py) | Python | -81 | -14 | -19 | -114 |
18 | | [executor.py](/executor.py) | Python | -486 | -1 | -51 | -538 |
19 | | [mcp\_server.py](/mcp_server.py) | Python | 3 | -9 | -3 | -9 |
20 | | [requirements.txt](/requirements.txt) | pip requirements | -7 | 0 | 0 | -7 |
21 | | [setup.py](/setup.py) | Python | -29 | 0 | -2 | -31 |
22 | | [tests/test\_local\_python\_executor.py](/tests/test_local_python_executor.py) | Python | 66 | 2 | 24 | 92 |
23 | | [tests/test\_mcp\_server.py](/tests/test_mcp_server.py) | Python | 13 | -4 | -1 | 8 |
24 |
25 | [Summary](results.md) / [Details](details.md) / [Diff Summary](diff.md) / Diff Details
```
--------------------------------------------------------------------------------
/.VSCodeCounter/2025-03-28_08-28-01/diff-details.md:
--------------------------------------------------------------------------------
```markdown
1 | # Diff Details
2 |
3 | Date : 2025-03-28 08:28:01
4 |
5 | Directory /Users/admin/src/mcp_safe_local_python_executor
6 |
7 | Total : 11 files, 583 codes, 27 comments, 76 blanks, all 686 lines
8 |
9 | [Summary](results.md) / [Details](details.md) / [Diff Summary](diff.md) / Diff Details
10 |
11 | ## Files
12 | | filename | language | code | comment | blank | total |
13 | | :--- | :--- | ---: | ---: | ---: | ---: |
14 | | [README.md](/README.md) | Markdown | 36 | 0 | 16 | 52 |
15 | | [examples/run\_executor.py](/examples/run_executor.py) | Python | 24 | 4 | 7 | 35 |
16 | | [examples/run\_mcp\_server.py](/examples/run_mcp_server.py) | Python | 58 | 6 | 11 | 75 |
17 | | [examples/test\_mcp\_client.py](/examples/test_mcp_client.py) | Python | 81 | 14 | 19 | 114 |
18 | | [executor.py](/executor.py) | Python | 486 | 1 | 51 | 538 |
19 | | [mcp\_server.py](/mcp_server.py) | Python | -3 | 9 | 3 | 9 |
20 | | [requirements.txt](/requirements.txt) | pip requirements | 7 | 0 | 0 | 7 |
21 | | [setup.py](/setup.py) | Python | 29 | 0 | 2 | 31 |
22 | | [tests/run\_executor.py](/tests/run_executor.py) | Python | -24 | -4 | -7 | -35 |
23 | | [tests/test\_local\_python\_executor.py](/tests/test_local_python_executor.py) | Python | -98 | -7 | -27 | -132 |
24 | | [tests/test\_mcp\_server.py](/tests/test_mcp_server.py) | Python | -13 | 4 | 1 | -8 |
25 |
26 | [Summary](results.md) / [Details](details.md) / [Diff Summary](diff.md) / Diff Details
```
--------------------------------------------------------------------------------
/mcp_server.py:
--------------------------------------------------------------------------------
```python
1 | #!/usr/bin/env python3
2 | """
3 | MCP Server for LocalPythonExecutor
4 |
5 | This module implements a Model Context Protocol (MCP) server that exposes
6 | the LocalPythonExecutor as a tool for AI assistants.
7 | """
8 |
9 | import logging
10 | from typing import Dict, Any
11 |
12 | from mcp.server.fastmcp import FastMCP
13 | # from executor import LocalPythonExecutor
14 | from smolagents.local_python_executor import LocalPythonExecutor
15 |
16 | # Configure logging
17 | logging.basicConfig(
18 | level=logging.INFO,
19 | format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
20 | )
21 |
22 | logger = logging.getLogger(__name__)
23 |
24 | mcp = FastMCP("python-executor")
25 |
26 | executor = LocalPythonExecutor(additional_authorized_imports=[])
27 | executor.send_tools({})
28 |
29 |
30 | @mcp.tool()
31 | async def run_python(
32 | code: str
33 | ) -> Dict[str, Any]:
34 | """Execute Python code in a secure sandbox environment.
35 |
36 | This tool allows running simple Python code for calculations and data manipulations.
37 | The execution environment is restricted for security purposes. Make sure you create a single file
38 | that can be executed in one go and it returns a result.
39 |
40 | Default allowed imports:
41 | - math
42 | - random
43 | - datetime
44 | - time
45 | - json
46 | - re
47 | - string
48 | - collections
49 | - itertools
50 | - functools
51 | - operator
52 |
53 | Args:
54 | code: The Python code to execute. Must be valid Python 3 code. The result must be stored in a variable called `result`. E.g.:
55 | ```python
56 | import math
57 | result = math.sqrt(16)
58 | ```
59 |
60 | Returns:
61 | A dictionary with execution results containing:
62 | - result: The final value or None if no value is returned
63 | - logs: Any output from print statements
64 | """
65 | logger.info(f"Executing Python code: {code}")
66 |
67 | result, logs, _ = executor(code)
68 |
69 | response = {
70 | "result": result,
71 | "logs": logs
72 | }
73 |
74 | logger.info(f"Execution result: {result}")
75 | return response
76 |
77 |
78 | if __name__ == "__main__":
79 | logger.info("Starting MCP server for Python executor")
80 | mcp.run(transport='stdio')
```
--------------------------------------------------------------------------------
/tests/test_local_python_executor.py:
--------------------------------------------------------------------------------
```python
1 | """
2 | Smoke tests for the LocalPythonExecutor.
3 | """
4 |
5 | import os
6 | import sys
7 | import pytest
8 |
9 | # Add the parent directory to the path
10 | sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
11 |
12 | # Import the executor
13 | from smolagents.local_python_executor import LocalPythonExecutor
14 |
15 |
16 | def test_simple_arithmetic():
17 | """Test that simple arithmetic works correctly."""
18 | executor = LocalPythonExecutor(additional_authorized_imports=[])
19 |
20 | code = "2 + 2"
21 | result, logs, _ = executor(code)
22 |
23 | assert result == 4
24 | assert logs == ""
25 |
26 |
27 | def test_variable_assignment():
28 | """Test that variable assignment works correctly."""
29 | executor = LocalPythonExecutor(additional_authorized_imports=[])
30 |
31 | code = """
32 | x = 10
33 | y = 20
34 | result = x + y
35 | """
36 | result, logs, _ = executor(code)
37 |
38 | assert result == 30
39 | assert logs == ""
40 |
41 |
42 | def test_expression_result():
43 | """Test that the last expression is returned as result."""
44 | executor = LocalPythonExecutor(additional_authorized_imports=[])
45 |
46 | code = """
47 | x = 5
48 | x * 2
49 | """
50 | result, logs, _ = executor(code)
51 |
52 | assert result == 10
53 | assert logs == ""
54 |
55 |
56 | def test_array_operations():
57 | """Test that array operations work correctly."""
58 | executor = LocalPythonExecutor(additional_authorized_imports=[])
59 |
60 | code = """
61 | numbers = [1, 2, 3, 4, 5]
62 | total = 0
63 | for num in numbers:
64 | total += num
65 | total
66 | """
67 | result, logs, _ = executor(code)
68 |
69 | assert result == 15
70 | assert logs == ""
71 |
72 |
73 | def test_filesystem_access_fails():
74 | """Test that filesystem access is blocked"""
75 | print("\nTest: Filesystem access attempt")
76 | code = """
77 | import os
78 | files = os.listdir('.')
79 | files
80 | """
81 | executor = LocalPythonExecutor(additional_authorized_imports=[])
82 | with pytest.raises(Exception) as exc_info:
83 | executor(code)
84 | assert 'InterpreterError: Import of os is not allowed' in str(exc_info.value)
85 |
86 |
87 |
88 | if __name__ == "__main__":
89 | if len(sys.argv) > 1 and sys.argv[1] == "--smoke":
90 | run_smoke_tests()
91 | else:
92 | pytest.main(["-v", __file__])
```
--------------------------------------------------------------------------------
/.VSCodeCounter/2025-05-01_18-42-30/results.txt:
--------------------------------------------------------------------------------
```
1 | Date : 2025-05-01 18:42:30
2 | Directory : /Users/admin/src/mcp_safe_local_python_executor
3 | Total : 4 files, 230 codes, 9 comments, 79 blanks, all 318 lines
4 |
5 | Languages
6 | +----------+------------+------------+------------+------------+------------+
7 | | language | files | code | comment | blank | total |
8 | +----------+------------+------------+------------+------------+------------+
9 | | Python | 3 | 157 | 9 | 52 | 218 |
10 | | Markdown | 1 | 73 | 0 | 27 | 100 |
11 | +----------+------------+------------+------------+------------+------------+
12 |
13 | Directories
14 | +-------------------------------------------------------------------------------------+------------+------------+------------+------------+------------+
15 | | path | files | code | comment | blank | total |
16 | +-------------------------------------------------------------------------------------+------------+------------+------------+------------+------------+
17 | | . | 4 | 230 | 9 | 79 | 318 |
18 | | . (Files) | 2 | 132 | 3 | 45 | 180 |
19 | | tests | 2 | 98 | 6 | 34 | 138 |
20 | +-------------------------------------------------------------------------------------+------------+------------+------------+------------+------------+
21 |
22 | Files
23 | +-------------------------------------------------------------------------------------+----------+------------+------------+------------+------------+
24 | | filename | language | code | comment | blank | total |
25 | +-------------------------------------------------------------------------------------+----------+------------+------------+------------+------------+
26 | | /Users/admin/src/mcp_safe_local_python_executor/README.md | Markdown | 73 | 0 | 27 | 100 |
27 | | /Users/admin/src/mcp_safe_local_python_executor/mcp_server.py | Python | 59 | 3 | 18 | 80 |
28 | | /Users/admin/src/mcp_safe_local_python_executor/tests/test_local_python_executor.py | Python | 66 | 2 | 24 | 92 |
29 | | /Users/admin/src/mcp_safe_local_python_executor/tests/test_mcp_server.py | Python | 32 | 4 | 10 | 46 |
30 | | Total | | 230 | 9 | 79 | 318 |
31 | +-------------------------------------------------------------------------------------+----------+------------+------------+------------+------------+
```
--------------------------------------------------------------------------------
/.VSCodeCounter/2025-03-28_08-28-01/results.txt:
--------------------------------------------------------------------------------
```
1 | Date : 2025-03-28 08:28:01
2 | Directory : /Users/admin/src/mcp_safe_local_python_executor
3 | Total : 9 files, 859 codes, 45 comments, 158 blanks, all 1062 lines
4 |
5 | Languages
6 | +------------------+------------+------------+------------+------------+------------+
7 | | language | files | code | comment | blank | total |
8 | +------------------+------------+------------+------------+------------+------------+
9 | | Python | 7 | 753 | 45 | 122 | 920 |
10 | | Markdown | 1 | 99 | 0 | 36 | 135 |
11 | | pip requirements | 1 | 7 | 0 | 0 | 7 |
12 | +------------------+------------+------------+------------+------------+------------+
13 |
14 | Directories
15 | +-----------------------------------------------------------------------------+------------+------------+------------+------------+------------+
16 | | path | files | code | comment | blank | total |
17 | +-----------------------------------------------------------------------------+------------+------------+------------+------------+------------+
18 | | . | 9 | 859 | 45 | 158 | 1,062 |
19 | | . (Files) | 5 | 677 | 13 | 110 | 800 |
20 | | examples | 3 | 163 | 24 | 37 | 224 |
21 | | tests | 1 | 19 | 8 | 11 | 38 |
22 | +-----------------------------------------------------------------------------+------------+------------+------------+------------+------------+
23 |
24 | Files
25 | +-----------------------------------------------------------------------------+------------------+------------+------------+------------+------------+
26 | | filename | language | code | comment | blank | total |
27 | +-----------------------------------------------------------------------------+------------------+------------+------------+------------+------------+
28 | | /Users/admin/src/mcp_safe_local_python_executor/README.md | Markdown | 99 | 0 | 36 | 135 |
29 | | /Users/admin/src/mcp_safe_local_python_executor/examples/run_executor.py | Python | 24 | 4 | 7 | 35 |
30 | | /Users/admin/src/mcp_safe_local_python_executor/examples/run_mcp_server.py | Python | 58 | 6 | 11 | 75 |
31 | | /Users/admin/src/mcp_safe_local_python_executor/examples/test_mcp_client.py | Python | 81 | 14 | 19 | 114 |
32 | | /Users/admin/src/mcp_safe_local_python_executor/executor.py | Python | 486 | 1 | 51 | 538 |
33 | | /Users/admin/src/mcp_safe_local_python_executor/mcp_server.py | Python | 56 | 12 | 21 | 89 |
34 | | /Users/admin/src/mcp_safe_local_python_executor/requirements.txt | pip requirements | 7 | 0 | 0 | 7 |
35 | | /Users/admin/src/mcp_safe_local_python_executor/setup.py | Python | 29 | 0 | 2 | 31 |
36 | | /Users/admin/src/mcp_safe_local_python_executor/tests/test_mcp_server.py | Python | 19 | 8 | 11 | 38 |
37 | | Total | | 859 | 45 | 158 | 1,062 |
38 | +-----------------------------------------------------------------------------+------------------+------------+------------+------------+------------+
```
--------------------------------------------------------------------------------
/.VSCodeCounter/2025-05-01_18-42-30/diff.txt:
--------------------------------------------------------------------------------
```
1 | Date : 2025-05-01 18:42:30
2 | Directory : /Users/admin/src/mcp_safe_local_python_executor
3 | Total : 10 files, -629 codes, -36 comments, -79 blanks, all -744 lines
4 |
5 | Languages
6 | +------------------+------------+------------+------------+------------+------------+
7 | | language | files | code | comment | blank | total |
8 | +------------------+------------+------------+------------+------------+------------+
9 | | pip requirements | 1 | -7 | 0 | 0 | -7 |
10 | | Markdown | 1 | -26 | 0 | -9 | -35 |
11 | | Python | 8 | -596 | -36 | -70 | -702 |
12 | +------------------+------------+------------+------------+------------+------------+
13 |
14 | Directories
15 | +-------------------------------------------------------------------------------------+------------+------------+------------+------------+------------+
16 | | path | files | code | comment | blank | total |
17 | +-------------------------------------------------------------------------------------+------------+------------+------------+------------+------------+
18 | | . | 10 | -629 | -36 | -79 | -744 |
19 | | . (Files) | 5 | -545 | -10 | -65 | -620 |
20 | | examples | 3 | -163 | -24 | -37 | -224 |
21 | | tests | 2 | 79 | -2 | 23 | 100 |
22 | +-------------------------------------------------------------------------------------+------------+------------+------------+------------+------------+
23 |
24 | Files
25 | +-------------------------------------------------------------------------------------+------------------+------------+------------+------------+------------+
26 | | filename | language | code | comment | blank | total |
27 | +-------------------------------------------------------------------------------------+------------------+------------+------------+------------+------------+
28 | | /Users/admin/src/mcp_safe_local_python_executor/README.md | Markdown | -26 | 0 | -9 | -35 |
29 | | /Users/admin/src/mcp_safe_local_python_executor/examples/run_executor.py | Python | -24 | -4 | -7 | -35 |
30 | | /Users/admin/src/mcp_safe_local_python_executor/examples/run_mcp_server.py | Python | -58 | -6 | -11 | -75 |
31 | | /Users/admin/src/mcp_safe_local_python_executor/examples/test_mcp_client.py | Python | -81 | -14 | -19 | -114 |
32 | | /Users/admin/src/mcp_safe_local_python_executor/executor.py | Python | -486 | -1 | -51 | -538 |
33 | | /Users/admin/src/mcp_safe_local_python_executor/mcp_server.py | Python | 3 | -9 | -3 | -9 |
34 | | /Users/admin/src/mcp_safe_local_python_executor/requirements.txt | pip requirements | -7 | 0 | 0 | -7 |
35 | | /Users/admin/src/mcp_safe_local_python_executor/setup.py | Python | -29 | 0 | -2 | -31 |
36 | | /Users/admin/src/mcp_safe_local_python_executor/tests/test_local_python_executor.py | Python | 66 | 2 | 24 | 92 |
37 | | /Users/admin/src/mcp_safe_local_python_executor/tests/test_mcp_server.py | Python | 13 | -4 | -1 | 8 |
38 | | Total | | -629 | -36 | -79 | -744 |
39 | +-------------------------------------------------------------------------------------+------------------+------------+------------+------------+------------+
```
--------------------------------------------------------------------------------
/.VSCodeCounter/2025-03-28_08-28-01/diff.txt:
--------------------------------------------------------------------------------
```
1 | Date : 2025-03-28 08:28:01
2 | Directory : /Users/admin/src/mcp_safe_local_python_executor
3 | Total : 11 files, 583 codes, 27 comments, 76 blanks, all 686 lines
4 |
5 | Languages
6 | +------------------+------------+------------+------------+------------+------------+
7 | | language | files | code | comment | blank | total |
8 | +------------------+------------+------------+------------+------------+------------+
9 | | Python | 9 | 540 | 27 | 60 | 627 |
10 | | Markdown | 1 | 36 | 0 | 16 | 52 |
11 | | pip requirements | 1 | 7 | 0 | 0 | 7 |
12 | +------------------+------------+------------+------------+------------+------------+
13 |
14 | Directories
15 | +-------------------------------------------------------------------------------------+------------+------------+------------+------------+------------+
16 | | path | files | code | comment | blank | total |
17 | +-------------------------------------------------------------------------------------+------------+------------+------------+------------+------------+
18 | | . | 11 | 583 | 27 | 76 | 686 |
19 | | . (Files) | 5 | 555 | 10 | 72 | 637 |
20 | | examples | 3 | 163 | 24 | 37 | 224 |
21 | | tests | 3 | -135 | -7 | -33 | -175 |
22 | +-------------------------------------------------------------------------------------+------------+------------+------------+------------+------------+
23 |
24 | Files
25 | +-------------------------------------------------------------------------------------+------------------+------------+------------+------------+------------+
26 | | filename | language | code | comment | blank | total |
27 | +-------------------------------------------------------------------------------------+------------------+------------+------------+------------+------------+
28 | | /Users/admin/src/mcp_safe_local_python_executor/README.md | Markdown | 36 | 0 | 16 | 52 |
29 | | /Users/admin/src/mcp_safe_local_python_executor/examples/run_executor.py | Python | 24 | 4 | 7 | 35 |
30 | | /Users/admin/src/mcp_safe_local_python_executor/examples/run_mcp_server.py | Python | 58 | 6 | 11 | 75 |
31 | | /Users/admin/src/mcp_safe_local_python_executor/examples/test_mcp_client.py | Python | 81 | 14 | 19 | 114 |
32 | | /Users/admin/src/mcp_safe_local_python_executor/executor.py | Python | 486 | 1 | 51 | 538 |
33 | | /Users/admin/src/mcp_safe_local_python_executor/mcp_server.py | Python | -3 | 9 | 3 | 9 |
34 | | /Users/admin/src/mcp_safe_local_python_executor/requirements.txt | pip requirements | 7 | 0 | 0 | 7 |
35 | | /Users/admin/src/mcp_safe_local_python_executor/setup.py | Python | 29 | 0 | 2 | 31 |
36 | | /Users/admin/src/mcp_safe_local_python_executor/tests/run_executor.py | Python | -24 | -4 | -7 | -35 |
37 | | /Users/admin/src/mcp_safe_local_python_executor/tests/test_local_python_executor.py | Python | -98 | -7 | -27 | -132 |
38 | | /Users/admin/src/mcp_safe_local_python_executor/tests/test_mcp_server.py | Python | -13 | 4 | 1 | -8 |
39 | | Total | | 583 | 27 | 76 | 686 |
40 | +-------------------------------------------------------------------------------------+------------------+------------+------------+------------+------------+
```