#
tokens: 6352/50000 14/14 files
lines: off (toggle) GitHub
raw markdown copy
# 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:
--------------------------------------------------------------------------------

```
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
#  Usually these files are written by a python script from a template
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
.pybuilder/
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
#   For a library or package, you might want to ignore these files since the code is
#   intended to run in multiple environments; otherwise, check them in:
# .python-version

# pipenv
#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
#   However, in case of collaboration, if having platform-specific dependencies or dependencies
#   having no cross-platform support, pipenv may install dependencies that don't work, or not
#   install all needed dependencies.
#Pipfile.lock

# UV
#   Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
#   This is especially recommended for binary packages to ensure reproducibility, and is more
#   commonly ignored for libraries.
#uv.lock

# poetry
#   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
#   This is especially recommended for binary packages to ensure reproducibility, and is more
#   commonly ignored for libraries.
#   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock

# pdm
#   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
#   pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
#   in version control.
#   https://pdm.fming.dev/latest/usage/project/#working-with-version-control
.pdm.toml
.pdm-python
.pdm-build/

# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

# pytype static type analyzer
.pytype/

# Cython debug symbols
cython_debug/

# PyCharm
#  JetBrains specific template is maintained in a separate JetBrains.gitignore that can
#  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
#  and can be added to the global gitignore or merged into this file.  For a more nuclear
#  option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

# Abstra
# Abstra is an AI-powered process automation framework.
# Ignore directories containing user credentials, local state, and settings.
# Learn more at https://abstra.io/docs
.abstra/

# Visual Studio Code
#  Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore 
#  that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
#  and can be added to the global gitignore or merged into this file. However, if you prefer, 
#  you could uncomment the following to ignore the enitre vscode folder
# .vscode/

# Ruff stuff:
.ruff_cache/

# PyPI configuration file
.pypirc

# Cursor
#  Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
#  exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
#  refer to https://docs.cursor.com/context/ignore-files
.cursorignore
.cursorindexingignore
.vscode/
```

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

```markdown
# Code Sandbox MCP Server

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. 

![Code Sandbox MCP](./assets/code-sandbox-mcp.png)


**How It Works:**
1. Starts a container session (podman, docker, etc.) and ensures the session is open.
2. Writes the `code` to a temporary file on the host.
3. Copies this temporary file into the container at the configured `workdir`.
4. Executes the language-specific commands to run the code, e.g. python `python3 -u code.py` or javascript `node -u code.js`
5. Captures the output and error streams from the container.
6. Returns the output and error streams to the client.
7. Stops and removes the container. 

**Available Tools:**
- **run_python_code** - Executes a snippet of Python code in a secure, isolated sandbox.
  - `code` (string, required): The Python code to execute.
- **run_js_code** - Executes a snippet of JavaScript (Node.js) code in a secure, isolated sandbox.
  - `code` (string, required): The JavaScript code to execute.

## Installation

```bash
pip install git+https://github.com/philschmid/code-sandbox-mcp.git
```

## Getting Started: Usage with an MCP Client

Examples:
- [Local Client Python](./examples/test_local_client_python.py) example for running python code
- [Gemini SDK](./examples/test_gemini.py) example for running python code with the Gemini SDK
- [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
- [Local Client Javascript](./examples/test_local_client_js.py) example for running javascript code

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.

Add the following to your `mcpServers` configuration:

```json
{
  "mcpServers": {
    "code-sandbox": {
      "command": "code-sandbox-mcp",
    }
  }
}
```

### Provide Secrets and pass through environment variables

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

```json
{
  "mcpServers": {
    "code-sandbox": {
      "command": "code-sandbox-mcp",
      "args": ["--pass-through-env", "API_KEY,SECRET_TOKEN"]
      "env": {
        "API_KEY": "1234567890",
        "SECRET_TOKEN": "1234567890"
      }
    }
  }
}
```

### Provide a custom container image

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.

Note: When providing a custom container image both tools will use the same container image.

```json
{
  "mcpServers": {
    "code-sandbox": {
      "command": "code-sandbox-mcp",
      "env": {
        "CONTAINER_IMAGE": "your-own-image",
        "CONTAINER_LANGUAGE": "python" # or "javascript"
      }
    }
  }
}
```

### Use with Gemini SDK

The `code-sandbox-mcp` server can be used with the Gemini SDK by passing the `tools` parameter to the `generate_content` method.

```python
from fastmcp import Client
from google import genai
import asyncio


mcp_client = Client(
    {
        "local_server": {
            "transport": "stdio",
            "command": "code-sandbox-mcp",
        }
    }
)
gemini_client = genai.Client()


async def main():
    async with mcp_client:
        response = await gemini_client.aio.models.generate_content(
            model="gemini-2.5-flash",
            contents="Use Python to ping the google.com website and return the response time.",
            config=genai.types.GenerateContentConfig(
                temperature=0,
                tools=[mcp_client.session],  # Pass the FastMCP client session
            ),
        )
        print(response.text)

if __name__ == "__main__":
    asyncio.run(main())
```

### Use with Gemini CLI 

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.

![Gemini CLI Settings](./assets/gemini-cli.png)

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)

```json
{
  "mcpServers": {
    "code-sandbox": {
      "command": "code-sandbox-mcp",
    }
  }
}
```

## Customize/Build new Container Images

The repository comes with 2 container images, which are published on Docker Hub:

- `philschmi/code-sandbox-python:latest`
- `philschmi/code-sandbox-js:latest`

```bash
docker build -t philschmi/code-sandbox-python:latest -f containers/Dockerfile.python .
docker build -t philschmi/code-sandbox-js:latest -f containers/Dockerfile.nodejs .
```

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. 

To push the images to Docker Hub you need to retag the images to your own account and push them.

```bash
docker tag philschmi/code-sandbox-python:latest <your-account>/code-sandbox-python:latest
docker push <your-account>/code-sandbox-python:latest
```

To customize or install additional dependencies you can add them to the [Dockerfile](./Dockerfile) and build the image again. 


## Testing

### With MCP Inspector
Start the server with streamable-http and test your server using the MCP inspector. Alternatively start inspector and run the server with stdio.

```bash
npx @modelcontextprotocol/inspector
```

To run the test suite for `code-sandbox-mcp` and its components, clone the repository and run:

```bash
# You may need to install development dependencies first
pip install -e ".[dev]"

# Run the tests
pytest tests/
```

## License

Code Sandbox MCP Server is open source software licensed under the [MIT License](https://github.com/vndee/llm-sandbox/blob/main/LICENSE).

```

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

```python

```

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

```json
{
    "mcpServers": {
      "code-sandbox": {
        "command": "code-sandbox-mcp"
      }
    }
}
```

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

```python
from unittest.mock import MagicMock
from code_sandbox_mcp.server import mcp, main
import os
import pytest
from fastmcp import Client


@pytest.mark.asyncio
async def test_tools_added():
    # Check if the tools from the tools module are added to the mcp instance
    async with Client(mcp) as client:
        tools = await client.list_tools()
        tool_names = [tool.name for tool in tools]
        assert "run_python_code" in tool_names
        assert "run_javascript_code" in tool_names

```

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

```python
import os
from typing import Literal


EXECUTION_TIMEOUT: int = 30
DEFAULT_BACKEND: str = os.getenv("BACKEND", "podman")
VERBOSE: bool = os.getenv("VERBOSE", "false").lower() == "true"
DEFAULT_LANGUAGE: Literal["python", "javascript", "go"] = "python"


DEFAULT_ENVIRONMENT_MAP = {
    "python": {
        "image": "philschmi/code-sandbox-python:latest",
        "installed_libraries": "numpy, pandas, matplotlib, scikit-learn, requests, google-genai",
    },
    "javascript": {
        "image": "philschmi/code-sandbox-js:latest",
        "installed_libraries": "@google/genai",
    },
    # "bash": {
    #     "image": "bash:latest",
    #     "installed_libraries": "",
    # },
    # "go": {
    #     "image": "golang:1.22",
    #     "installed_libraries": DEFAULT_GO_LIBRARIES,
    # },
}

```

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

```python
from fastmcp import Client
from google import genai
import asyncio


mcp_client = Client(
    {
        "local_server": {
            "transport": "stdio",
            "command": "code-sandbox-mcp",
        }
    }
)
gemini_client = genai.Client()


async def main():
    async with mcp_client:
        response = await gemini_client.aio.models.generate_content(
            model="gemini-2.5-flash",
            contents="Use Python to ping the google.com website and return the response time.",
            config=genai.types.GenerateContentConfig(
                temperature=0,
                tools=[mcp_client.session],  # Pass the FastMCP client session
            ),
        )
        print(response.text)
        print("Tool Calls:")
        print(response.automatic_function_calling_history)


if __name__ == "__main__":
    asyncio.run(main())

```

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

```python
import os
import asyncio
import time
from mcp import ClientSession, StdioServerParameters, stdio_client

server_params = StdioServerParameters(
    command="code-sandbox-mcp",  # Executable
)


async def run():
    async with stdio_client(server_params) as (read, write):
        async with ClientSession(
            read,
            write,
        ) as session:
            await session.initialize()
            # Initialize conversation history using simple tuples
            tools = await session.list_tools()
            print([tool.name for tool in tools.tools])

            start_time = time.time()
            r = await session.call_tool(
                "run_python_code", arguments={"code": "print('Hello, World!')"}
            )
            print(r.content[0].text)
            print(f"Time taken: {time.time() - start_time} seconds")


if __name__ == "__main__":
    asyncio.run(run())

```

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

```python
import os
import asyncio
import time
from mcp import ClientSession, StdioServerParameters, stdio_client

server_params = StdioServerParameters(
    command="code-sandbox-mcp",  # Executable
)


async def run():
    async with stdio_client(server_params) as (read, write):
        async with ClientSession(
            read,
            write,
        ) as session:
            await session.initialize()
            # Initialize conversation history using simple tuples
            tools = await session.list_tools()
            print([tool.name for tool in tools.tools])

            start_time = time.time()
            r = await session.call_tool(
                "run_javascript_code",
                arguments={"code": "console.log('Hello, World!');"},
            )
            print(r.content[0].text)
            print(f"Time taken: {time.time() - start_time} seconds")


if __name__ == "__main__":
    asyncio.run(run())

```

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

```toml
[project]
name = "code-sandbox-mcp"
version = "0.1.0"
description = "A simple code sandbox MCP server"
readme = "README.md"
requires-python = ">=3.10"
authors = [{ name = "Philipp Schmid", email = "[email protected]" }]
license = { text = "MIT" }
dependencies = [
    "mcp",
    "fastmcp",
    "llm-sandbox[podman]",
]

[project.urls]
Homepage = "https://github.com/philschmid/code-sandbox-mcp"
Repository = "https://github.com/philschmid/code-sandbox-mcp"
"Bug Tracker" = "https://github.com/philschmid/code-sandbox-mcp/issues"

[project.optional-dependencies]
docker = ["docker>=7.1.0"]
k8s = ["kubernetes>=32.0.1"]
podman = ["docker>=7.1.0", "podman>=5.4.0.1"]
dev = [
    "pyright>=1.1.391",
    "pytest>=8.3.4",
    "ruff>=0.8.5",
    "pytest-asyncio",
    "pytest-mock",
]

[project.scripts]
code-sandbox-mcp = "code_sandbox_mcp.server:main"

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.hatch.build.targets.wheel]
packages = ["src/code_sandbox_mcp"]

[tool.pytest.ini_options]
pythonpath = ["src"]
```

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

```python
import os
import asyncio
import time
from mcp import ClientSession, StdioServerParameters, stdio_client

server_params = StdioServerParameters(
    command="code-sandbox-mcp",
    args=["--pass-through-env", "GEMINI_API_KEY"],
    env={"GEMINI_API_KEY": os.getenv("GEMINI_API_KEY")},
)


async def run():
    async with stdio_client(server_params) as (read, write):
        async with ClientSession(
            read,
            write,
        ) as session:
            await session.initialize()
            # Initialize conversation history using simple tuples
            tools = await session.list_tools()
            print([tool.name for tool in tools.tools])

            start_time = time.time()
            r = await session.call_tool(
                "run_python_code",
                arguments={
                    "code": f"""
from google import genai

client = genai.Client()

response = client.models.generate_content(
    model="gemini-2.5-flash",
    contents="How does AI work?"
)
print(response.text)
"""
                },
            )
            print(r.content[0].text)
            print(f"Time taken: {time.time() - start_time} seconds")


if __name__ == "__main__":
    asyncio.run(run())

```

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

```python
import pytest
from fastmcp import FastMCP, Client
from code_sandbox_mcp.server import mcp as code_sandbox_mcp_server
import os


@pytest.fixture(scope="module")
def mcp_stdio_server():
    """
    Fixture to provide the main code_sandbox_mcp server instance for testing.
    """
    # Set transport mode for testing
    os.environ["MCP_TRANSPORT_MODE"] = "stdio"

    yield code_sandbox_mcp_server


@pytest.mark.asyncio
async def test_run_python_code_integration(mcp_stdio_server: FastMCP):
    """
    Tests the run_python_code tool using an in-memory client.
    """
    query = "print('Hello, World!')"
    async with Client(mcp_stdio_server) as client:
        result = await client.call_tool("run_python_code", {"code": query})
        assert isinstance(result.content[0].text, str)
        assert len(result.content[0].text) > 0


@pytest.mark.asyncio
async def test_run_python_code_integration_with_error(mcp_stdio_server: FastMCP):
    """
    Tests the run_python_code tool using an in-memory client with an error
    """
    query = """from google.genai import genai
    client = genai.Client()
    response = client.generate_content("Hello, world!")
    print(response.text)
    """
    async with Client(mcp_stdio_server) as client:
        result = await client.call_tool("run_python_code", {"code": query})
        assert isinstance(result.content[0].text, str)
        assert "error" in result.content[0].text.lower()


@pytest.mark.asyncio
async def test_run_javascript_code_integration(mcp_stdio_server: FastMCP):
    """
    Tests the run_javascript_code tool using an in-memory client.
    """
    prompt = "console.log('Hello, World!');"
    async with Client(mcp_stdio_server) as client:
        result = await client.call_tool("run_javascript_code", {"code": prompt})
        assert isinstance(result.content[0].text, str)


@pytest.mark.asyncio
async def test_run_javascript_code_integration_with_error(mcp_stdio_server: FastMCP):
    """
    Tests the run_javascript_code tool using an in-memory client with an error
    """
    prompt = "const x = 1 / 0; console.log(y);"
    async with Client(mcp_stdio_server) as client:
        result = await client.call_tool("run_javascript_code", {"code": prompt})
        assert isinstance(result.content[0].text, str)
        assert "error" in result.content[0].text.lower()

```

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

```python
import os
from typing import Literal

from code_sandbox_mcp.const import (
    DEFAULT_BACKEND,
    DEFAULT_ENVIRONMENT_MAP,
    DEFAULT_LANGUAGE,
    EXECUTION_TIMEOUT,
    VERBOSE,
)
from llm_sandbox import (
    SandboxBackend,
    SandboxSession,
)
from llm_sandbox.session import _check_dependency


def _get_backend() -> SandboxBackend:
    """Get the backend to use for the sandbox session."""
    backend = SandboxBackend(DEFAULT_BACKEND)
    _check_dependency(backend)
    return backend


def run_code(
    code: str,
    language: Literal["python", "javascript"] = DEFAULT_LANGUAGE,
    image: str | None = None,
    libraries: list[str] | None = None,
    timeout: int = EXECUTION_TIMEOUT,
) -> str:
    """Execute code in a secure sandbox environment and automatic visualization capture.

    Args:
        code: The code to execute
        language: Programming language (python, javascript, go)
        libraries: List of libraries/packages to install
        image: Docker image to use for the sandbox session
        timeout: Execution timeout in seconds (default: 30)

    Returns:
        List of content items including execution results and any generated visualizations

    """
    if language not in DEFAULT_ENVIRONMENT_MAP:
        raise ValueError(f"Language {language} not supported")

    session_args = {
        "lang": language,
        "keep_template": True,
        "verbose": VERBOSE,
        "backend": _get_backend(),
        "session_timeout": timeout,
        "image": DEFAULT_ENVIRONMENT_MAP[language]["image"],
    }

    if os.getenv("PASSTHROUGH_ENV", None):
        env_vars = {}
        for var in os.getenv("PASSTHROUGH_ENV", None).split(","):
            env_vars[var] = os.getenv(var)
        session_args["runtime_configs"] = {"environment": env_vars}

    if os.getenv("CONTAINER_IMAGE", None) and os.getenv("CONTAINER_LANGUAGE", None):
        session_args["lang"] = os.getenv("CONTAINER_LANGUAGE")
        session_args["image"] = os.getenv("CONTAINER_IMAGE")

    if libraries:
        session_args["libraries"] = libraries

    with SandboxSession(**session_args) as session:
        result = session.run(
            code=code,
            libraries=libraries or [],
            timeout=timeout,
        )
    if result.exit_code != 0:
        raise Exception(result.stderr.strip())
    return result.stdout.strip()

```

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

```python
import argparse
import json
import os
from code_sandbox_mcp.const import DEFAULT_ENVIRONMENT_MAP
from fastmcp import FastMCP
from pydantic import Field
from typing import Annotated
from code_sandbox_mcp.utils import run_code

from mcp.types import TextContent
from llm_sandbox.data import ExecutionResult

from mcp.server.fastmcp import FastMCP

mcp = FastMCP(
    name="code-sandbox",
    instructions="This MCP server allows you to execute code in a secure sandbox environment and automatically capture visualizations.",
)


@mcp.tool()
def run_python_code(
    code: Annotated[
        str,
        Field(
            description=f"The Python code to execute, included libraries are {DEFAULT_ENVIRONMENT_MAP['python']['installed_libraries']}",
        ),
    ],
) -> TextContent:
    """Execute Python code in the sandbox environment and captures the standard output and error."""
    try:
        result = run_code(code, language="python")
        if len(result) == 0:
            result = ExecutionResult(
                exit_code=1, stderr="No output, forgot print()?"
            ).to_json()
        return TextContent(text=result, type="text")
    except Exception as e:
        result = ExecutionResult(exit_code=1, stderr=str(e)).to_json()
        return TextContent(text=result, type="text")


@mcp.tool()
def run_javascript_code(
    code: Annotated[
        str,
        Field(
            description=f"The JavaScript code to execute, included libraries are {DEFAULT_ENVIRONMENT_MAP['javascript']['installed_libraries']}",
        ),
    ],
) -> TextContent:
    """Execute JavaScript code in the sandbox environment and captures the standard output and error."""
    try:
        result = run_code(code, language="javascript")
        if len(result) == 0:
            result = ExecutionResult(
                exit_code=1, stderr="No output, forgot console.logs?"
            ).to_json()
        return TextContent(text=result, type="text")
    except Exception as e:
        result = ExecutionResult(exit_code=1, stderr=str(e)).to_json()
        return TextContent(text=result, type="text")


@mcp.resource("sandbox://environments")
def environment_details() -> str:
    """Resource containing detailed information about the environments.

    Returns:
        str: The details of the languages

    """
    return json.dumps(DEFAULT_ENVIRONMENT_MAP, indent=2)


def main():
    parser = argparse.ArgumentParser(description="Run Gemini MCP Server.")
    parser.add_argument(
        "--pass-through-env",
        help="Comma-separated list of environment variable keys to pass through to the sandbox (e.g., API_KEY,SECRET_TOKEN)",
        default=None,
        type=str,
        metavar="KEY1,KEY2,KEY3",
    )
    args = parser.parse_args()

    print(args.pass_through_env)

    if args.pass_through_env:
        os.environ["PASSTHROUGH_ENV"] = args.pass_through_env

    mcp.run(
        transport="stdio",
    )


if __name__ == "__main__":
    main()

```