#
tokens: 8606/50000 14/14 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .gemini
│   └── settings.json
├── .gitignore
├── assets
│   ├── code-sandbox-mcp.png
│   └── gemini-cli.png
├── containers
│   ├── Dockerfile.nodejs
│   └── Dockerfile.python
├── examples
│   ├── test_client_gemini_call.py
│   ├── test_gemini.py
│   ├── test_local_client_js.py
│   └── test_local_client_python.py
├── license
├── pyproject.toml
├── README.md
├── src
│   └── code_sandbox_mcp
│       ├── __init__.py
│       ├── const.py
│       ├── server.py
│       └── utils.py
└── tests
    ├── test_integration.py
    └── test_server.py
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
  1 | # Byte-compiled / optimized / DLL files
  2 | __pycache__/
  3 | *.py[cod]
  4 | *$py.class
  5 | 
  6 | # C extensions
  7 | *.so
  8 | 
  9 | # Distribution / packaging
 10 | .Python
 11 | build/
 12 | develop-eggs/
 13 | dist/
 14 | downloads/
 15 | eggs/
 16 | .eggs/
 17 | lib/
 18 | lib64/
 19 | parts/
 20 | sdist/
 21 | var/
 22 | wheels/
 23 | share/python-wheels/
 24 | *.egg-info/
 25 | .installed.cfg
 26 | *.egg
 27 | MANIFEST
 28 | 
 29 | # PyInstaller
 30 | #  Usually these files are written by a python script from a template
 31 | #  before PyInstaller builds the exe, so as to inject date/other infos into it.
 32 | *.manifest
 33 | *.spec
 34 | 
 35 | # Installer logs
 36 | pip-log.txt
 37 | pip-delete-this-directory.txt
 38 | 
 39 | # Unit test / coverage reports
 40 | htmlcov/
 41 | .tox/
 42 | .nox/
 43 | .coverage
 44 | .coverage.*
 45 | .cache
 46 | nosetests.xml
 47 | coverage.xml
 48 | *.cover
 49 | *.py,cover
 50 | .hypothesis/
 51 | .pytest_cache/
 52 | cover/
 53 | 
 54 | # Translations
 55 | *.mo
 56 | *.pot
 57 | 
 58 | # Django stuff:
 59 | *.log
 60 | local_settings.py
 61 | db.sqlite3
 62 | db.sqlite3-journal
 63 | 
 64 | # Flask stuff:
 65 | instance/
 66 | .webassets-cache
 67 | 
 68 | # Scrapy stuff:
 69 | .scrapy
 70 | 
 71 | # Sphinx documentation
 72 | docs/_build/
 73 | 
 74 | # PyBuilder
 75 | .pybuilder/
 76 | target/
 77 | 
 78 | # Jupyter Notebook
 79 | .ipynb_checkpoints
 80 | 
 81 | # IPython
 82 | profile_default/
 83 | ipython_config.py
 84 | 
 85 | # pyenv
 86 | #   For a library or package, you might want to ignore these files since the code is
 87 | #   intended to run in multiple environments; otherwise, check them in:
 88 | # .python-version
 89 | 
 90 | # pipenv
 91 | #   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
 92 | #   However, in case of collaboration, if having platform-specific dependencies or dependencies
 93 | #   having no cross-platform support, pipenv may install dependencies that don't work, or not
 94 | #   install all needed dependencies.
 95 | #Pipfile.lock
 96 | 
 97 | # UV
 98 | #   Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
 99 | #   This is especially recommended for binary packages to ensure reproducibility, and is more
100 | #   commonly ignored for libraries.
101 | #uv.lock
102 | 
103 | # poetry
104 | #   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105 | #   This is especially recommended for binary packages to ensure reproducibility, and is more
106 | #   commonly ignored for libraries.
107 | #   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108 | #poetry.lock
109 | 
110 | # pdm
111 | #   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
112 | #pdm.lock
113 | #   pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
114 | #   in version control.
115 | #   https://pdm.fming.dev/latest/usage/project/#working-with-version-control
116 | .pdm.toml
117 | .pdm-python
118 | .pdm-build/
119 | 
120 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
121 | __pypackages__/
122 | 
123 | # Celery stuff
124 | celerybeat-schedule
125 | celerybeat.pid
126 | 
127 | # SageMath parsed files
128 | *.sage.py
129 | 
130 | # Environments
131 | .env
132 | .venv
133 | env/
134 | venv/
135 | ENV/
136 | env.bak/
137 | venv.bak/
138 | 
139 | # Spyder project settings
140 | .spyderproject
141 | .spyproject
142 | 
143 | # Rope project settings
144 | .ropeproject
145 | 
146 | # mkdocs documentation
147 | /site
148 | 
149 | # mypy
150 | .mypy_cache/
151 | .dmypy.json
152 | dmypy.json
153 | 
154 | # Pyre type checker
155 | .pyre/
156 | 
157 | # pytype static type analyzer
158 | .pytype/
159 | 
160 | # Cython debug symbols
161 | cython_debug/
162 | 
163 | # PyCharm
164 | #  JetBrains specific template is maintained in a separate JetBrains.gitignore that can
165 | #  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
166 | #  and can be added to the global gitignore or merged into this file.  For a more nuclear
167 | #  option (not recommended) you can uncomment the following to ignore the entire idea folder.
168 | #.idea/
169 | 
170 | # Abstra
171 | # Abstra is an AI-powered process automation framework.
172 | # Ignore directories containing user credentials, local state, and settings.
173 | # Learn more at https://abstra.io/docs
174 | .abstra/
175 | 
176 | # Visual Studio Code
177 | #  Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore 
178 | #  that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
179 | #  and can be added to the global gitignore or merged into this file. However, if you prefer, 
180 | #  you could uncomment the following to ignore the enitre vscode folder
181 | # .vscode/
182 | 
183 | # Ruff stuff:
184 | .ruff_cache/
185 | 
186 | # PyPI configuration file
187 | .pypirc
188 | 
189 | # Cursor
190 | #  Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
191 | #  exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
192 | #  refer to https://docs.cursor.com/context/ignore-files
193 | .cursorignore
194 | .cursorindexingignore
195 | .vscode/
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Code Sandbox MCP Server
  2 | 
  3 | The Code Sandbox MCP Server is a lightweight, STDIO-based Model Context Protocol (MCP) Server, allowing AI assistants and LLM applications to safely execute code snippets using containerized environments. It is uses the [llm-sandbox](https://github.com/vndee/llm-sandbox) package to execute the code snippets. 
  4 | 
  5 | ![Code Sandbox MCP](./assets/code-sandbox-mcp.png)
  6 | 
  7 | 
  8 | **How It Works:**
  9 | 1. Starts a container session (podman, docker, etc.) and ensures the session is open.
 10 | 2. Writes the `code` to a temporary file on the host.
 11 | 3. Copies this temporary file into the container at the configured `workdir`.
 12 | 4. Executes the language-specific commands to run the code, e.g. python `python3 -u code.py` or javascript `node -u code.js`
 13 | 5. Captures the output and error streams from the container.
 14 | 6. Returns the output and error streams to the client.
 15 | 7. Stops and removes the container. 
 16 | 
 17 | **Available Tools:**
 18 | - **run_python_code** - Executes a snippet of Python code in a secure, isolated sandbox.
 19 |   - `code` (string, required): The Python code to execute.
 20 | - **run_js_code** - Executes a snippet of JavaScript (Node.js) code in a secure, isolated sandbox.
 21 |   - `code` (string, required): The JavaScript code to execute.
 22 | 
 23 | ## Installation
 24 | 
 25 | ```bash
 26 | pip install git+https://github.com/philschmid/code-sandbox-mcp.git
 27 | ```
 28 | 
 29 | ## Getting Started: Usage with an MCP Client
 30 | 
 31 | Examples:
 32 | - [Local Client Python](./examples/test_local_client_python.py) example for running python code
 33 | - [Gemini SDK](./examples/test_gemini.py) example for running python code with the Gemini SDK
 34 | - [Calling Gemini from a client](./examples/test_client_gemini_call.py) example for running python code that uses the Gemini SDK and passes through the Gemini API key
 35 | - [Local Client Javascript](./examples/test_local_client_js.py) example for running javascript code
 36 | 
 37 | To use the Code Sandbox MCP server, you need to add it to your MCP client's configuration file (e.g., in your AI assistant's settings). The server is designed to be launched on-demand by the client.
 38 | 
 39 | Add the following to your `mcpServers` configuration:
 40 | 
 41 | ```json
 42 | {
 43 |   "mcpServers": {
 44 |     "code-sandbox": {
 45 |       "command": "code-sandbox-mcp",
 46 |     }
 47 |   }
 48 | }
 49 | ```
 50 | 
 51 | ### Provide Secrets and pass through environment variables
 52 | 
 53 | You can pass through environment variables to the sandbox by setting the `--pass-through-env` flag when starting the MCP server and providing the env when starting the server
 54 | 
 55 | ```json
 56 | {
 57 |   "mcpServers": {
 58 |     "code-sandbox": {
 59 |       "command": "code-sandbox-mcp",
 60 |       "args": ["--pass-through-env", "API_KEY,SECRET_TOKEN"]
 61 |       "env": {
 62 |         "API_KEY": "1234567890",
 63 |         "SECRET_TOKEN": "1234567890"
 64 |       }
 65 |     }
 66 |   }
 67 | }
 68 | ```
 69 | 
 70 | ### Provide a custom container image
 71 | 
 72 | You can provide a custom container image by setting the `CONTAINER_IMAGE` and `CONTAINER_LANGUAGE` environment variables when starting the MCP server. Both variables are required as the `CONTAINER_LANGUAGE` is used to determine the commands to run in the container and the `CONTAINER_IMAGE` is used to determine the image to use.
 73 | 
 74 | Note: When providing a custom container image both tools will use the same container image.
 75 | 
 76 | ```json
 77 | {
 78 |   "mcpServers": {
 79 |     "code-sandbox": {
 80 |       "command": "code-sandbox-mcp",
 81 |       "env": {
 82 |         "CONTAINER_IMAGE": "your-own-image",
 83 |         "CONTAINER_LANGUAGE": "python" # or "javascript"
 84 |       }
 85 |     }
 86 |   }
 87 | }
 88 | ```
 89 | 
 90 | ### Use with Gemini SDK
 91 | 
 92 | The `code-sandbox-mcp` server can be used with the Gemini SDK by passing the `tools` parameter to the `generate_content` method.
 93 | 
 94 | ```python
 95 | from fastmcp import Client
 96 | from google import genai
 97 | import asyncio
 98 | 
 99 | 
100 | mcp_client = Client(
101 |     {
102 |         "local_server": {
103 |             "transport": "stdio",
104 |             "command": "code-sandbox-mcp",
105 |         }
106 |     }
107 | )
108 | gemini_client = genai.Client()
109 | 
110 | 
111 | async def main():
112 |     async with mcp_client:
113 |         response = await gemini_client.aio.models.generate_content(
114 |             model="gemini-2.5-flash",
115 |             contents="Use Python to ping the google.com website and return the response time.",
116 |             config=genai.types.GenerateContentConfig(
117 |                 temperature=0,
118 |                 tools=[mcp_client.session],  # Pass the FastMCP client session
119 |             ),
120 |         )
121 |         print(response.text)
122 | 
123 | if __name__ == "__main__":
124 |     asyncio.run(main())
125 | ```
126 | 
127 | ### Use with Gemini CLI 
128 | 
129 | The `code-sandbox-mcp` server can be used with the Gemini CLI. You can configure MCP servers at the global level in the `~/.gemini/settings.json` file or in your project's root directory, create or open the `.gemini/settings.json` file. Within the file, add the mcpServers configuration block.
130 | 
131 | ![Gemini CLI Settings](./assets/gemini-cli.png)
132 | 
133 | See [settings.json](.gemini/settings.json) for an example and read more about the [Gemini CLI](https://github.com/google-gemini/gemini-cli/blob/main/docs/tools/mcp-server.md)
134 | 
135 | ```json
136 | {
137 |   "mcpServers": {
138 |     "code-sandbox": {
139 |       "command": "code-sandbox-mcp",
140 |     }
141 |   }
142 | }
143 | ```
144 | 
145 | ## Customize/Build new Container Images
146 | 
147 | The repository comes with 2 container images, which are published on Docker Hub:
148 | 
149 | - `philschmi/code-sandbox-python:latest`
150 | - `philschmi/code-sandbox-js:latest`
151 | 
152 | ```bash
153 | docker build -t philschmi/code-sandbox-python:latest -f containers/Dockerfile.python .
154 | docker build -t philschmi/code-sandbox-js:latest -f containers/Dockerfile.nodejs .
155 | ```
156 | 
157 | The script will build the image using the current user's account. To update the images you want to use you can either pass the --python-image or --js-image flags when starting the MCP server or update the [const.py](./src//code_sandbox_mcp/const.py) file. 
158 | 
159 | To push the images to Docker Hub you need to retag the images to your own account and push them.
160 | 
161 | ```bash
162 | docker tag philschmi/code-sandbox-python:latest <your-account>/code-sandbox-python:latest
163 | docker push <your-account>/code-sandbox-python:latest
164 | ```
165 | 
166 | To customize or install additional dependencies you can add them to the [Dockerfile](./Dockerfile) and build the image again. 
167 | 
168 | 
169 | ## Testing
170 | 
171 | ### With MCP Inspector
172 | Start the server with streamable-http and test your server using the MCP inspector. Alternatively start inspector and run the server with stdio.
173 | 
174 | ```bash
175 | npx @modelcontextprotocol/inspector
176 | ```
177 | 
178 | To run the test suite for `code-sandbox-mcp` and its components, clone the repository and run:
179 | 
180 | ```bash
181 | # You may need to install development dependencies first
182 | pip install -e ".[dev]"
183 | 
184 | # Run the tests
185 | pytest tests/
186 | ```
187 | 
188 | ## License
189 | 
190 | Code Sandbox MCP Server is open source software licensed under the [MIT License](https://github.com/vndee/llm-sandbox/blob/main/LICENSE).
191 | 
```

--------------------------------------------------------------------------------
/src/code_sandbox_mcp/__init__.py:
--------------------------------------------------------------------------------

```python
1 | 
```

--------------------------------------------------------------------------------
/.gemini/settings.json:
--------------------------------------------------------------------------------

```json
1 | {
2 |     "mcpServers": {
3 |       "code-sandbox": {
4 |         "command": "code-sandbox-mcp"
5 |       }
6 |     }
7 | }
```

--------------------------------------------------------------------------------
/tests/test_server.py:
--------------------------------------------------------------------------------

```python
 1 | from unittest.mock import MagicMock
 2 | from code_sandbox_mcp.server import mcp, main
 3 | import os
 4 | import pytest
 5 | from fastmcp import Client
 6 | 
 7 | 
 8 | @pytest.mark.asyncio
 9 | async def test_tools_added():
10 |     # Check if the tools from the tools module are added to the mcp instance
11 |     async with Client(mcp) as client:
12 |         tools = await client.list_tools()
13 |         tool_names = [tool.name for tool in tools]
14 |         assert "run_python_code" in tool_names
15 |         assert "run_javascript_code" in tool_names
16 | 
```

--------------------------------------------------------------------------------
/src/code_sandbox_mcp/const.py:
--------------------------------------------------------------------------------

```python
 1 | import os
 2 | from typing import Literal
 3 | 
 4 | 
 5 | EXECUTION_TIMEOUT: int = 30
 6 | DEFAULT_BACKEND: str = os.getenv("BACKEND", "podman")
 7 | VERBOSE: bool = os.getenv("VERBOSE", "false").lower() == "true"
 8 | DEFAULT_LANGUAGE: Literal["python", "javascript", "go"] = "python"
 9 | 
10 | 
11 | DEFAULT_ENVIRONMENT_MAP = {
12 |     "python": {
13 |         "image": "philschmi/code-sandbox-python:latest",
14 |         "installed_libraries": "numpy, pandas, matplotlib, scikit-learn, requests, google-genai",
15 |     },
16 |     "javascript": {
17 |         "image": "philschmi/code-sandbox-js:latest",
18 |         "installed_libraries": "@google/genai",
19 |     },
20 |     # "bash": {
21 |     #     "image": "bash:latest",
22 |     #     "installed_libraries": "",
23 |     # },
24 |     # "go": {
25 |     #     "image": "golang:1.22",
26 |     #     "installed_libraries": DEFAULT_GO_LIBRARIES,
27 |     # },
28 | }
29 | 
```

--------------------------------------------------------------------------------
/examples/test_gemini.py:
--------------------------------------------------------------------------------

```python
 1 | from fastmcp import Client
 2 | from google import genai
 3 | import asyncio
 4 | 
 5 | 
 6 | mcp_client = Client(
 7 |     {
 8 |         "local_server": {
 9 |             "transport": "stdio",
10 |             "command": "code-sandbox-mcp",
11 |         }
12 |     }
13 | )
14 | gemini_client = genai.Client()
15 | 
16 | 
17 | async def main():
18 |     async with mcp_client:
19 |         response = await gemini_client.aio.models.generate_content(
20 |             model="gemini-2.5-flash",
21 |             contents="Use Python to ping the google.com website and return the response time.",
22 |             config=genai.types.GenerateContentConfig(
23 |                 temperature=0,
24 |                 tools=[mcp_client.session],  # Pass the FastMCP client session
25 |             ),
26 |         )
27 |         print(response.text)
28 |         print("Tool Calls:")
29 |         print(response.automatic_function_calling_history)
30 | 
31 | 
32 | if __name__ == "__main__":
33 |     asyncio.run(main())
34 | 
```

--------------------------------------------------------------------------------
/examples/test_local_client_python.py:
--------------------------------------------------------------------------------

```python
 1 | import os
 2 | import asyncio
 3 | import time
 4 | from mcp import ClientSession, StdioServerParameters, stdio_client
 5 | 
 6 | server_params = StdioServerParameters(
 7 |     command="code-sandbox-mcp",  # Executable
 8 | )
 9 | 
10 | 
11 | async def run():
12 |     async with stdio_client(server_params) as (read, write):
13 |         async with ClientSession(
14 |             read,
15 |             write,
16 |         ) as session:
17 |             await session.initialize()
18 |             # Initialize conversation history using simple tuples
19 |             tools = await session.list_tools()
20 |             print([tool.name for tool in tools.tools])
21 | 
22 |             start_time = time.time()
23 |             r = await session.call_tool(
24 |                 "run_python_code", arguments={"code": "print('Hello, World!')"}
25 |             )
26 |             print(r.content[0].text)
27 |             print(f"Time taken: {time.time() - start_time} seconds")
28 | 
29 | 
30 | if __name__ == "__main__":
31 |     asyncio.run(run())
32 | 
```

--------------------------------------------------------------------------------
/examples/test_local_client_js.py:
--------------------------------------------------------------------------------

```python
 1 | import os
 2 | import asyncio
 3 | import time
 4 | from mcp import ClientSession, StdioServerParameters, stdio_client
 5 | 
 6 | server_params = StdioServerParameters(
 7 |     command="code-sandbox-mcp",  # Executable
 8 | )
 9 | 
10 | 
11 | async def run():
12 |     async with stdio_client(server_params) as (read, write):
13 |         async with ClientSession(
14 |             read,
15 |             write,
16 |         ) as session:
17 |             await session.initialize()
18 |             # Initialize conversation history using simple tuples
19 |             tools = await session.list_tools()
20 |             print([tool.name for tool in tools.tools])
21 | 
22 |             start_time = time.time()
23 |             r = await session.call_tool(
24 |                 "run_javascript_code",
25 |                 arguments={"code": "console.log('Hello, World!');"},
26 |             )
27 |             print(r.content[0].text)
28 |             print(f"Time taken: {time.time() - start_time} seconds")
29 | 
30 | 
31 | if __name__ == "__main__":
32 |     asyncio.run(run())
33 | 
```

--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------

```toml
 1 | [project]
 2 | name = "code-sandbox-mcp"
 3 | version = "0.1.0"
 4 | description = "A simple code sandbox MCP server"
 5 | readme = "README.md"
 6 | requires-python = ">=3.10"
 7 | authors = [{ name = "Philipp Schmid", email = "[email protected]" }]
 8 | license = { text = "MIT" }
 9 | dependencies = [
10 |     "mcp",
11 |     "fastmcp",
12 |     "llm-sandbox[podman]",
13 | ]
14 | 
15 | [project.urls]
16 | Homepage = "https://github.com/philschmid/code-sandbox-mcp"
17 | Repository = "https://github.com/philschmid/code-sandbox-mcp"
18 | "Bug Tracker" = "https://github.com/philschmid/code-sandbox-mcp/issues"
19 | 
20 | [project.optional-dependencies]
21 | docker = ["docker>=7.1.0"]
22 | k8s = ["kubernetes>=32.0.1"]
23 | podman = ["docker>=7.1.0", "podman>=5.4.0.1"]
24 | dev = [
25 |     "pyright>=1.1.391",
26 |     "pytest>=8.3.4",
27 |     "ruff>=0.8.5",
28 |     "pytest-asyncio",
29 |     "pytest-mock",
30 | ]
31 | 
32 | [project.scripts]
33 | code-sandbox-mcp = "code_sandbox_mcp.server:main"
34 | 
35 | [build-system]
36 | requires = ["hatchling"]
37 | build-backend = "hatchling.build"
38 | 
39 | [tool.hatch.build.targets.wheel]
40 | packages = ["src/code_sandbox_mcp"]
41 | 
42 | [tool.pytest.ini_options]
43 | pythonpath = ["src"]
```

--------------------------------------------------------------------------------
/examples/test_client_gemini_call.py:
--------------------------------------------------------------------------------

```python
 1 | import os
 2 | import asyncio
 3 | import time
 4 | from mcp import ClientSession, StdioServerParameters, stdio_client
 5 | 
 6 | server_params = StdioServerParameters(
 7 |     command="code-sandbox-mcp",
 8 |     args=["--pass-through-env", "GEMINI_API_KEY"],
 9 |     env={"GEMINI_API_KEY": os.getenv("GEMINI_API_KEY")},
10 | )
11 | 
12 | 
13 | async def run():
14 |     async with stdio_client(server_params) as (read, write):
15 |         async with ClientSession(
16 |             read,
17 |             write,
18 |         ) as session:
19 |             await session.initialize()
20 |             # Initialize conversation history using simple tuples
21 |             tools = await session.list_tools()
22 |             print([tool.name for tool in tools.tools])
23 | 
24 |             start_time = time.time()
25 |             r = await session.call_tool(
26 |                 "run_python_code",
27 |                 arguments={
28 |                     "code": f"""
29 | from google import genai
30 | 
31 | client = genai.Client()
32 | 
33 | response = client.models.generate_content(
34 |     model="gemini-2.5-flash",
35 |     contents="How does AI work?"
36 | )
37 | print(response.text)
38 | """
39 |                 },
40 |             )
41 |             print(r.content[0].text)
42 |             print(f"Time taken: {time.time() - start_time} seconds")
43 | 
44 | 
45 | if __name__ == "__main__":
46 |     asyncio.run(run())
47 | 
```

--------------------------------------------------------------------------------
/tests/test_integration.py:
--------------------------------------------------------------------------------

```python
 1 | import pytest
 2 | from fastmcp import FastMCP, Client
 3 | from code_sandbox_mcp.server import mcp as code_sandbox_mcp_server
 4 | import os
 5 | 
 6 | 
 7 | @pytest.fixture(scope="module")
 8 | def mcp_stdio_server():
 9 |     """
10 |     Fixture to provide the main code_sandbox_mcp server instance for testing.
11 |     """
12 |     # Set transport mode for testing
13 |     os.environ["MCP_TRANSPORT_MODE"] = "stdio"
14 | 
15 |     yield code_sandbox_mcp_server
16 | 
17 | 
18 | @pytest.mark.asyncio
19 | async def test_run_python_code_integration(mcp_stdio_server: FastMCP):
20 |     """
21 |     Tests the run_python_code tool using an in-memory client.
22 |     """
23 |     query = "print('Hello, World!')"
24 |     async with Client(mcp_stdio_server) as client:
25 |         result = await client.call_tool("run_python_code", {"code": query})
26 |         assert isinstance(result.content[0].text, str)
27 |         assert len(result.content[0].text) > 0
28 | 
29 | 
30 | @pytest.mark.asyncio
31 | async def test_run_python_code_integration_with_error(mcp_stdio_server: FastMCP):
32 |     """
33 |     Tests the run_python_code tool using an in-memory client with an error
34 |     """
35 |     query = """from google.genai import genai
36 |     client = genai.Client()
37 |     response = client.generate_content("Hello, world!")
38 |     print(response.text)
39 |     """
40 |     async with Client(mcp_stdio_server) as client:
41 |         result = await client.call_tool("run_python_code", {"code": query})
42 |         assert isinstance(result.content[0].text, str)
43 |         assert "error" in result.content[0].text.lower()
44 | 
45 | 
46 | @pytest.mark.asyncio
47 | async def test_run_javascript_code_integration(mcp_stdio_server: FastMCP):
48 |     """
49 |     Tests the run_javascript_code tool using an in-memory client.
50 |     """
51 |     prompt = "console.log('Hello, World!');"
52 |     async with Client(mcp_stdio_server) as client:
53 |         result = await client.call_tool("run_javascript_code", {"code": prompt})
54 |         assert isinstance(result.content[0].text, str)
55 | 
56 | 
57 | @pytest.mark.asyncio
58 | async def test_run_javascript_code_integration_with_error(mcp_stdio_server: FastMCP):
59 |     """
60 |     Tests the run_javascript_code tool using an in-memory client with an error
61 |     """
62 |     prompt = "const x = 1 / 0; console.log(y);"
63 |     async with Client(mcp_stdio_server) as client:
64 |         result = await client.call_tool("run_javascript_code", {"code": prompt})
65 |         assert isinstance(result.content[0].text, str)
66 |         assert "error" in result.content[0].text.lower()
67 | 
```

--------------------------------------------------------------------------------
/src/code_sandbox_mcp/utils.py:
--------------------------------------------------------------------------------

```python
 1 | import os
 2 | from typing import Literal
 3 | 
 4 | from code_sandbox_mcp.const import (
 5 |     DEFAULT_BACKEND,
 6 |     DEFAULT_ENVIRONMENT_MAP,
 7 |     DEFAULT_LANGUAGE,
 8 |     EXECUTION_TIMEOUT,
 9 |     VERBOSE,
10 | )
11 | from llm_sandbox import (
12 |     SandboxBackend,
13 |     SandboxSession,
14 | )
15 | from llm_sandbox.session import _check_dependency
16 | 
17 | 
18 | def _get_backend() -> SandboxBackend:
19 |     """Get the backend to use for the sandbox session."""
20 |     backend = SandboxBackend(DEFAULT_BACKEND)
21 |     _check_dependency(backend)
22 |     return backend
23 | 
24 | 
25 | def run_code(
26 |     code: str,
27 |     language: Literal["python", "javascript"] = DEFAULT_LANGUAGE,
28 |     image: str | None = None,
29 |     libraries: list[str] | None = None,
30 |     timeout: int = EXECUTION_TIMEOUT,
31 | ) -> str:
32 |     """Execute code in a secure sandbox environment and automatic visualization capture.
33 | 
34 |     Args:
35 |         code: The code to execute
36 |         language: Programming language (python, javascript, go)
37 |         libraries: List of libraries/packages to install
38 |         image: Docker image to use for the sandbox session
39 |         timeout: Execution timeout in seconds (default: 30)
40 | 
41 |     Returns:
42 |         List of content items including execution results and any generated visualizations
43 | 
44 |     """
45 |     if language not in DEFAULT_ENVIRONMENT_MAP:
46 |         raise ValueError(f"Language {language} not supported")
47 | 
48 |     session_args = {
49 |         "lang": language,
50 |         "keep_template": True,
51 |         "verbose": VERBOSE,
52 |         "backend": _get_backend(),
53 |         "session_timeout": timeout,
54 |         "image": DEFAULT_ENVIRONMENT_MAP[language]["image"],
55 |     }
56 | 
57 |     if os.getenv("PASSTHROUGH_ENV", None):
58 |         env_vars = {}
59 |         for var in os.getenv("PASSTHROUGH_ENV", None).split(","):
60 |             env_vars[var] = os.getenv(var)
61 |         session_args["runtime_configs"] = {"environment": env_vars}
62 | 
63 |     if os.getenv("CONTAINER_IMAGE", None) and os.getenv("CONTAINER_LANGUAGE", None):
64 |         session_args["lang"] = os.getenv("CONTAINER_LANGUAGE")
65 |         session_args["image"] = os.getenv("CONTAINER_IMAGE")
66 | 
67 |     if libraries:
68 |         session_args["libraries"] = libraries
69 | 
70 |     with SandboxSession(**session_args) as session:
71 |         result = session.run(
72 |             code=code,
73 |             libraries=libraries or [],
74 |             timeout=timeout,
75 |         )
76 |     if result.exit_code != 0:
77 |         raise Exception(result.stderr.strip())
78 |     return result.stdout.strip()
79 | 
```

--------------------------------------------------------------------------------
/src/code_sandbox_mcp/server.py:
--------------------------------------------------------------------------------

```python
 1 | import argparse
 2 | import json
 3 | import os
 4 | from code_sandbox_mcp.const import DEFAULT_ENVIRONMENT_MAP
 5 | from fastmcp import FastMCP
 6 | from pydantic import Field
 7 | from typing import Annotated
 8 | from code_sandbox_mcp.utils import run_code
 9 | 
10 | from mcp.types import TextContent
11 | from llm_sandbox.data import ExecutionResult
12 | 
13 | from mcp.server.fastmcp import FastMCP
14 | 
15 | mcp = FastMCP(
16 |     name="code-sandbox",
17 |     instructions="This MCP server allows you to execute code in a secure sandbox environment and automatically capture visualizations.",
18 | )
19 | 
20 | 
21 | @mcp.tool()
22 | def run_python_code(
23 |     code: Annotated[
24 |         str,
25 |         Field(
26 |             description=f"The Python code to execute, included libraries are {DEFAULT_ENVIRONMENT_MAP['python']['installed_libraries']}",
27 |         ),
28 |     ],
29 | ) -> TextContent:
30 |     """Execute Python code in the sandbox environment and captures the standard output and error."""
31 |     try:
32 |         result = run_code(code, language="python")
33 |         if len(result) == 0:
34 |             result = ExecutionResult(
35 |                 exit_code=1, stderr="No output, forgot print()?"
36 |             ).to_json()
37 |         return TextContent(text=result, type="text")
38 |     except Exception as e:
39 |         result = ExecutionResult(exit_code=1, stderr=str(e)).to_json()
40 |         return TextContent(text=result, type="text")
41 | 
42 | 
43 | @mcp.tool()
44 | def run_javascript_code(
45 |     code: Annotated[
46 |         str,
47 |         Field(
48 |             description=f"The JavaScript code to execute, included libraries are {DEFAULT_ENVIRONMENT_MAP['javascript']['installed_libraries']}",
49 |         ),
50 |     ],
51 | ) -> TextContent:
52 |     """Execute JavaScript code in the sandbox environment and captures the standard output and error."""
53 |     try:
54 |         result = run_code(code, language="javascript")
55 |         if len(result) == 0:
56 |             result = ExecutionResult(
57 |                 exit_code=1, stderr="No output, forgot console.logs?"
58 |             ).to_json()
59 |         return TextContent(text=result, type="text")
60 |     except Exception as e:
61 |         result = ExecutionResult(exit_code=1, stderr=str(e)).to_json()
62 |         return TextContent(text=result, type="text")
63 | 
64 | 
65 | @mcp.resource("sandbox://environments")
66 | def environment_details() -> str:
67 |     """Resource containing detailed information about the environments.
68 | 
69 |     Returns:
70 |         str: The details of the languages
71 | 
72 |     """
73 |     return json.dumps(DEFAULT_ENVIRONMENT_MAP, indent=2)
74 | 
75 | 
76 | def main():
77 |     parser = argparse.ArgumentParser(description="Run Gemini MCP Server.")
78 |     parser.add_argument(
79 |         "--pass-through-env",
80 |         help="Comma-separated list of environment variable keys to pass through to the sandbox (e.g., API_KEY,SECRET_TOKEN)",
81 |         default=None,
82 |         type=str,
83 |         metavar="KEY1,KEY2,KEY3",
84 |     )
85 |     args = parser.parse_args()
86 | 
87 |     print(args.pass_through_env)
88 | 
89 |     if args.pass_through_env:
90 |         os.environ["PASSTHROUGH_ENV"] = args.pass_through_env
91 | 
92 |     mcp.run(
93 |         transport="stdio",
94 |     )
95 | 
96 | 
97 | if __name__ == "__main__":
98 |     main()
99 | 
```