Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 62 additions & 8 deletions packages/python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,19 @@ A Model Context Protocol server for running code in a secure sandbox by [E2B](ht

## Development

Install dependencies:
Install dependencies with Poetry:
```
uv install
cd packages/python
poetry install
```

Or install directly with pip (from project root):
```
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
cd packages/python
python -m pip install --force-reinstall .
```

## Installation
Expand All @@ -30,14 +40,58 @@ On Windows: `%APPDATA%/Claude/claude_desktop_config.json`

### Debugging

Since MCP servers communicate over stdio, debugging can be challenging. We recommend using the [MCP Inspector](https://github.com/modelcontextprotocol/inspector), which is available as a package script:
Since MCP servers communicate over stdio, debugging can be challenging. We recommend using the [MCP Inspector](https://github.com/modelcontextprotocol/inspector). You can point it at the Poetry command directly, for example:

```
npx @modelcontextprotocol/inspector \
uv \
--directory . \
run \
e2b-mcp-server \
cd packages/python
npx @modelcontextprotocol/inspector -- poetry run e2b-mcp-server
```

The Inspector will provide a URL to access debugging tools in your browser.

## HTTP Server (Official SDK - Stateless)

Uses the official MCP Python SDK's stateless HTTP transport (no session persistence, no SSE stream).

### Run (HTTP)

With Poetry:
```
cd packages/python
poetry run e2b-mcp-server-http
```

With pip (after following development setup above):
```
e2b-mcp-server-http
```

### Authentication

The server requires an `Authorization: Bearer <E2B_API_KEY>` header for all requests. The token is passed through to the E2B SDK for sandbox operations.

### Examples

The server endpoints and behavior are managed by the official MCP SDK. Check the server logs for the actual URL when it starts.

List tools:

```
curl -s \
-H "Authorization: Bearer YOUR_E2B_API_KEY" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' \
http://localhost:8000/mcp
```

Call tool:

```
curl -s \
-H "Authorization: Bearer YOUR_E2B_API_KEY" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"run_code","arguments":{"code":"print(\"hello world\")"}}}' \
http://localhost:8000/mcp
```

The server runs in stateless mode with regular JSON responses (no SSE streaming). The actual host, port, and mount path are determined by the SDK's HTTP transport configuration.
115 changes: 115 additions & 0 deletions packages/python/e2b_mcp_server/http_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
"""
Run the E2B MCP server with streamable HTTP transport using the official SDK.
"""

import contextvars
import json
import logging
from collections.abc import Sequence

from dotenv import load_dotenv
from mcp.server.fastmcp import FastMCP
from mcp.server.auth.provider import AccessToken, TokenVerifier
from mcp.server.auth.settings import AuthSettings
from mcp.types import TextContent, ImageContent, EmbeddedResource, Tool
from e2b_code_interpreter import Sandbox
from pydantic import BaseModel, AnyHttpUrl

# Load environment variables
load_dotenv()

# Suppress known harmless errors from MCP SDK streamable HTTP transport
logging.getLogger("mcp.server.streamable_http").setLevel(logging.CRITICAL)

# Store the token for the current request
current_token: contextvars.ContextVar[str | None] = contextvars.ContextVar("current_token", default=None)


# Tool schema (copied from server.py for HTTP version)
class ToolSchema(BaseModel):
code: str


class E2BTokenVerifier(TokenVerifier):
"""Token verifier that stores the E2B API key for use in tools."""

async def verify_token(self, token: str) -> AccessToken | None:
"""Verify token and store it for the current request."""
# Store the token in context for use in tools
current_token.set(token)

# Return a valid access token - we don't validate, just pass through
return AccessToken(
token=token,
scopes=["e2b"], # Dummy scope
client_id="e2b-mcp-client", # Required field
)


# Create FastMCP server instance with token verifier and auth settings
mcp = FastMCP(
"e2b-mcp-server",
stateless_http=True,
json_response=True,
token_verifier=E2BTokenVerifier(),
auth=AuthSettings(
issuer_url=AnyHttpUrl("https://e2b.dev"), # E2B as issuer
resource_server_url=AnyHttpUrl("http://localhost:8000"), # This server's URL
required_scopes=["e2b"],
),
)

def tool_response(exec_obj):
"""Convert Execution object to a serializable dict"""
result = {}

# Logs
if hasattr(exec_obj, "logs"):
result["stdout"] = exec_obj.logs.stdout if hasattr(exec_obj.logs, "stdout") else []
result["stderr"] = exec_obj.logs.stderr if hasattr(exec_obj.logs, "stderr") else []

# Results (if any)
if hasattr(exec_obj, "results"):
result["results"] = exec_obj.results

# Error
if hasattr(exec_obj, "error") and exec_obj.error:
err = exec_obj.error
result["error"] = {
"name": getattr(err, "name", None),
"value": getattr(err, "value", None),
"traceback": getattr(err, "traceback", None)
}

return result

@mcp.tool()
async def run_code(code: str) -> Sequence[TextContent | ImageContent | EmbeddedResource]:
"""Run python code in a secure sandbox by E2B. Using the Jupyter Notebook syntax."""
# Get the token from the current request context - mandatory for HTTP mode
api_key = current_token.get()
if not api_key:
raise ValueError("Authorization header with Bearer token is required")

sandbox = Sandbox(api_key=api_key)
execution = sandbox.run_code(code)

result = tool_response(execution)

return [
TextContent(
type="text",
text=json.dumps(result, indent=2)
)
]


def main() -> None:
"""Run server with streamable HTTP transport."""
mcp.run(transport="streamable-http")


if __name__ == "__main__":
main()


6 changes: 5 additions & 1 deletion packages/python/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,15 @@ homepage = "https://e2b.dev/"
repository = "https://github.com/e2b-dev/mcp-server/tree/main/packages/python"
packages = [{ include = "e2b_mcp_server" }]

[tool.poetry.scripts]
e2b-mcp-server = "e2b_mcp_server:main"
e2b-mcp-server-http = "e2b_mcp_server.http_server:main"

[tool.poetry.dependencies]
python = ">=3.10,<4.0"

e2b-code-interpreter = "^1.0.2"
mcp = "^1.0.0"
mcp = ">=1.12.0"
pydantic = "^2.10.2"
python-dotenv = "1.0.1"

Expand Down