Skip to main content

A Jupyter-based MCP server for code interpretation

Project description

Jupyter Interpreter MCP

A remote Jupyter-based MCP (Model Context Protocol) server for code interpretation. This server connects to a remote Jupyter server (e.g. running in a Docker container or cloud instance) and provides a persistent, sandboxed code execution environment similar to Jupyter notebooks. Supports both Python and bash command execution.

Architecture

MCP Server → RemoteJupyterClient → Jupyter REST API → Remote Kernel
                                          ↓
                              WebSocket Connection
                                          ↓
                           Jupyter server Filesystem

All code executes within the remote Jupyter server. Session history files are stored in the server's filesystem, not on the host. You can execute both Python code and bash commands (e.g., ls, pwd, cat file.txt). Requirements

Requirements

  • Python 3.10 or higher
  • uv package manager
  • Network access to a Jupyter server

Quick Start

1. (Optional) Start Jupyter Container

This is only necessary if you don't use any other remote instance of Jupyter. Run a Jupyter container with the required port mappings, e.g.:

docker run -d \
  --name jupyter-notebook \
  -p 8889:8888 \
  jupyter/minimal-notebook:latest

2. Get Authentication Token

Create a new token for accessing the Jupyter server or use an existing token.

3. Run the MCP server

Using uvx

Start the server using uvx:

uvx jupyter-interpreter-mcp --jupyter-base-url http://localhost:8889 --jupyter-token abc123def456... --sessions-dir /home/jovyan/sessions --session-ttl 3600

or to add it to e.g. Claude Code:

{
  "mcpServers": {
    "jupyter-interpreter-mcp": {
      "command": "uvx",
      "args": [
        "jupyter-interpreter-mcp",
        "--jupyter-base-url",
        "http://localhost:8889",
        "--jupyter-token",
        "abc123def456...",
        "--sessions-dir",
        "/home/jovyan/sessions",
        "--session-ttl",
        "3600",
        "--restore-sessions-on-startup"
      ]
    }
  }
}

From source

Create a .env file in the project root:

JUPYTER_BASE_URL=http://localhost:8889
JUPYTER_TOKEN=abc123def456...
SESSIONS_DIR=/home/jovyan/sessions
SESSION_TTL=3600  # Optional: session expiry in seconds (0 = never expire)
RESTORE_SESSIONS_ON_STARTUP=false  # Optional: eager restore all sessions on startup

See .env.example for full configuration options and Docker setup instructions.

You can then install and run the server using uv:

uv pip install .
uv run jupyter-interpreter-mcp

The server will validate the connection to Jupyter on startup and fail with a clear error message if the connection cannot be established.

Session-Based Workflow

The server uses a session-based architecture where each session has:

  • A unique UUID-based session ID
  • An isolated directory on the Jupyter server at {sessions-dir}/{session-id}/
  • A persistent Jupyter kernel that maintains execution state (variables, imports)
  • On-demand restoration on access after restart (with optional eager restore at startup)

Typical Workflow

  1. Create a session using create_session
  2. Execute code in the session using execute_code
  3. Upload/download files within the session directory using upload_file_path and download_file
  4. List files in the session directory using list_dir
  5. Read/write/edit text files directly using read_file, write_file, and edit_file

Sessions automatically expire after the configured TTL (time-to-live) period.

Tools

create_session

Creates a new isolated session with a dedicated directory and Jupyter kernel.

Parameters: None

Returns: A dictionary containing:

  • session_id (string): UUID identifier for the session

Example usage:

result = create_session()
# Returns: {
#   "session_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
# }

execute_code

Executes code (Python or bash) within a persistent session, retaining past results (e.g., variables, imports). Similar to a Jupyter notebook.

Parameters:

  • code (string, required): The code to execute (Python or bash commands)
  • session_id (string, required): The session ID from create_session

Returns: A dictionary containing:

  • result (list of strings): Output from the code execution
  • error (list of strings): Any errors that occurred during execution
  • session_id (string): The session ID used

Example usage:

# Create a session first
session = create_session()
session_id = session["session_id"]

# Execute code in the session
result = execute_code(code="x = 42\nprint(x)", session_id=session_id)
# Returns: {"result": ["42"], "error": [], "session_id": "a1b2c3d4-..."}

# Subsequent execution - reuses the session state
result = execute_code(code="print(x * 2)", session_id=session_id)
# Returns: {"result": ["84"], "error": [], "session_id": "a1b2c3d4-..."}

# Bash commands
result = execute_code(code="ls -la", session_id=session_id)

download_file

Download a file from the session directory.

Parameters:

  • session_id (string, required): The session ID
  • path (string, required): Relative path within session directory

Returns: A dictionary containing:

  • content (string): File content (base64-encoded for binary, plain text otherwise)
  • encoding (string): Either "base64" or "text"
  • filename (string): The basename of the downloaded file

Example usage:

# Download text file
result = download_file(session_id=session_id, path="script.py")
# Returns: {"content": "print('Hello, World!')", "encoding": "text", "filename": "script.py"}

# Download binary file
result = download_file(session_id=session_id, path="images/logo.png")
# Returns: {"content": "iVBORw0KGgo...", "encoding": "base64", "filename": "logo.png"}

list_dir

List files and directories within the session directory.

Parameters:

  • session_id (string, required): The session ID
  • path (string, optional): Subdirectory path within session (defaults to session root)

Returns: A dictionary containing:

  • error (string): Empty string on success, error message on failure
  • result (list of strings): Formatted file/directory listing

Example usage:

# List session root
result = list_dir(session_id=session_id)

# List subdirectory
result = list_dir(session_id=session_id, path="images")

read_file

Reads a text file from the session directory and returns its content with 1-indexed line numbers. Use offset and limit to page through large files. Binary files are not supported; use download_file for those.

Parameters:

  • session_id (string, required): The session ID
  • path (string, required): File path relative to the session directory
  • offset (integer, optional): 1-indexed line number to start reading from (default: 1)
  • limit (integer, optional): Maximum number of lines to return (default: 200)

Returns: A dictionary containing:

  • lines (list of strings): Lines prefixed with their line number (e.g. "42: content")
  • total_lines (integer): Total number of lines in the file
  • offset (integer): The offset that was used
  • limit (integer): The limit that was used
  • truncated (boolean): Whether the result was truncated
  • path (string): The file path that was read

Example usage:

# Read first 200 lines
result = read_file(session_id=session_id, path="script.py")
# Returns: {"lines": ["1: import os", "2: ..."], "total_lines": 42, "offset": 1, "limit": 200, "truncated": False, "path": "script.py"}

# Page through a large file
result = read_file(session_id=session_id, path="data.csv", offset=201, limit=200)

write_file

Writes text content to a file in the session directory, creating or overwriting it. Parent directories are created automatically. Use upload_file_path for binary files.

Parameters:

  • session_id (string, required): The session ID
  • path (string, required): Destination file path relative to the session directory
  • content (string, required): Text content to write

Returns: A dictionary containing:

  • status (string): "ok" on success
  • path (string): The file path that was written
  • bytes_written (integer): Number of bytes written

Example usage:

result = write_file(
    session_id=session_id,
    path="scripts/hello.py",
    content="print('Hello, World!')\n"
)
# Returns: {"status": "ok", "path": "scripts/hello.py", "bytes_written": 23}

edit_file

Edits a text file in the session directory by replacing an exact substring (old_string) with new_string. Three matching strategies are tried in order: exact, line-trimmed, and indentation-flexible. By default the match must be unique; set replace_all=True to replace every occurrence. Use read_file first to confirm the exact content.

Parameters:

  • session_id (string, required): The session ID
  • path (string, required): File path relative to the session directory
  • old_string (string, required): The text to find and replace (must be non-empty)
  • new_string (string, required): The replacement text
  • replace_all (boolean, optional): Replace all occurrences instead of requiring a unique match (default: false)

Returns: A dictionary containing:

  • status (string): "ok" on success
  • path (string): The file path that was edited
  • replacements (integer): Number of replacements made

Example usage:

result = edit_file(
    session_id=session_id,
    path="scripts/hello.py",
    old_string="Hello, World!",
    new_string="Hello, Python!",
)
# Returns: {"status": "ok", "path": "scripts/hello.py", "replacements": 1}

# Replace all occurrences
result = edit_file(
    session_id=session_id,
    path="data.txt",
    old_string="foo",
    new_string="bar",
    replace_all=True,
)

upload_file_path

Upload a file from the host filesystem to the session directory by providing its absolute path. Only files within allowed directories are permitted, and sensitive files (.env, .ssh/, credentials, etc.) are blocked.

Parameters:

  • session_id (string, required): The session ID
  • host_path (string, required): Absolute path to the file on the host filesystem
  • destination_path (string, required): Relative path within session directory
  • overwrite (boolean, optional): Whether to overwrite an existing file (default: true)

Returns: A dictionary containing:

  • status (string): "success" if the upload succeeded
  • sandbox_path (string): Absolute path inside the sandbox
  • size (string): File size in bytes

Example usage:

result = upload_file_path(
    session_id=session_id,
    host_path="/home/user/data/dataset.csv",
    destination_path="data/dataset.csv",
    overwrite=False
)

Path Security Configuration

The upload_file_path tool restricts which host filesystem paths can be uploaded for security. You can configure allowed directories using either a command-line argument or an environment variable.

Configuration Methods (in order of precedence)

1. --allowed-dir CLI Argument

Pass one or more --allowed-dir arguments when starting the server:

# Allow uploads from a single directory
jupyter-interpreter-mcp --allowed-dir /home/user/projects

# Allow uploads from multiple directories
jupyter-interpreter-mcp \
  --allowed-dir /home/user/projects \
  --allowed-dir /home/user/data

2. ALLOWED_UPLOAD_DIRS Environment Variable

Set the ALLOWED_UPLOAD_DIRS environment variable to a colon-separated list of absolute directory paths:

# Allow uploads from multiple directories
export ALLOWED_UPLOAD_DIRS=/home/user/projects:/home/user/data

# Or in your .env file
ALLOWED_UPLOAD_DIRS=/home/user/projects:/home/user/data

3. Allow all

Using the --allow-all flag will allow uploads from any directory on the host filesystem.

4. Default Behavior

When neither --allowed-dir nor ALLOWED_UPLOAD_DIRS is set, uploads are allowed only from the current working directory on the host filesystem. Sensitive file protection (see below) is always active regardless of this setting.

MCP Client Configuration Examples

OpenCode

In your opencode.jsonc (global config at ~/.config/opencode/opencode.json or per-project):

{
  "mcp": {
    "jupyter-interpreter": {
      "type": "local",
      "command": [
        "uv", "run", "--project", "/path/to/jupyter-interpreter-mcp",
        "jupyter-interpreter-mcp",
        "--allowed-dir", "/home/user/projects",
        "--jupyter-base-url", "http://localhost:8888",
        // ... other args
      ],
      // Or use environment variables:
      "environment": {
        "ALLOWED_UPLOAD_DIRS": "/home/user/projects:/home/user/data"
      }
    }
  }
}

For most use cases, either use the environment variable approach with hardcoded paths, or rely on the CWD-only default with sensitive file protection.

Claude Desktop / Cursor

In claude_desktop_config.json or .cursor/mcp.json:

{
  "mcpServers": {
    "jupyter-interpreter": {
      "command": "jupyter-interpreter-mcp",
      "args": [
        "--allowed-dir", "/home/user/projects",
        "--jupyter-base-url", "http://localhost:8888"
      ],
      "env": {
        "JUPYTER_TOKEN": "your-token-here"
      }
    }
  }
}

Sensitive File Protection

Regardless of allowed directories, the following file patterns are always blocked from upload:

  • .env files (e.g., .env, .env.local)
  • SSH keys and configuration (.ssh/)
  • GPG keys (.gnupg/)
  • AWS credentials (.aws/)
  • Docker credentials (.docker/config.json)
  • Generic credential files (credentials.json, credentials.yaml)
  • Netrc files (.netrc)
  • NPM/PyPI tokens (.npmrc, .pypirc)
  • Secret/token files (secret.json, tokens.yaml, etc.)
  • Git credentials (.git-credentials)

Development

Installing Development Dependencies

uv pip install -e ".[dev,test]"

Testing

Tests can be run using pytest. If you're using mcpo you can start the server using e.g. the following command:

uvx mcpo --port 8000 -- uv run --directory /path/to/jupyter-interpreter-mcp jupyter-interpreter-mcp

For this, a configured .env file is required. You can then test the MCP server endpoint at http://localhost:8000/docs.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

jupyter_interpreter_mcp-0.4.0.tar.gz (60.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

jupyter_interpreter_mcp-0.4.0-py3-none-any.whl (32.2 kB view details)

Uploaded Python 3

File details

Details for the file jupyter_interpreter_mcp-0.4.0.tar.gz.

File metadata

  • Download URL: jupyter_interpreter_mcp-0.4.0.tar.gz
  • Upload date:
  • Size: 60.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for jupyter_interpreter_mcp-0.4.0.tar.gz
Algorithm Hash digest
SHA256 e04e4967fdbeaab24fa71eb1ef2765b36bc5c8146275ec7edce7e009ed2edfa8
MD5 c8794a25fb4ef29c514b96a60a60a4f7
BLAKE2b-256 94d234fdf153bfe9024abe59bc9a2b78d204eac1fef07ebb9f030290c6b3ac4e

See more details on using hashes here.

Provenance

The following attestation bundles were made for jupyter_interpreter_mcp-0.4.0.tar.gz:

Publisher: release.yml on lmseidler/jupyter-interpreter-mcp

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file jupyter_interpreter_mcp-0.4.0-py3-none-any.whl.

File metadata

File hashes

Hashes for jupyter_interpreter_mcp-0.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 443d352f4c231204bb6b73fe6e230e0ffe75e663dc14c6ca9ed6d3273a694555
MD5 9b8eb97d9bdaa909bf77d86f0a82b4b0
BLAKE2b-256 3180d2d8fb3dd54403f8d35c7237660d8117351068b3f62b5fdf3cc6d5c7cc7f

See more details on using hashes here.

Provenance

The following attestation bundles were made for jupyter_interpreter_mcp-0.4.0-py3-none-any.whl:

Publisher: release.yml on lmseidler/jupyter-interpreter-mcp

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page