Skip to main content

Jupyter MCP Live

A powerful MCP (Model Context Protocol) server for executing code in Jupyter notebooks. This extension enables AI assistants and other MCP clients to programmatically create, modify, and execute code across multiple kernel types (Python, R, Julia, and more) through a running Jupyter Server.

Overview

Jupyter MCP Live provides a comprehensive toolkit for AI-assisted coding workflows. It enables AI assistants to:

  • Execute code in any Jupyter kernel (Python, R, Julia, etc.)
  • Create and modify notebook cells programmatically
  • Manage kernels - start, stop, switch between different kernel types
  • Query notebooks - read cells, view outputs, check execution status
  • Install packages directly into kernel environments

Perfect for building AI-powered data analysis tools, automated notebook workflows, and interactive coding assistants.

Key Features

  • Multi-Kernel Support: Execute code in Python, R, Julia, and any Jupyter-supported kernel
  • Collaborative Editing: Full support for YDoc - works seamlessly when notebooks are open in JupyterLab
  • Live Output Streaming: Outputs render in JupyterLab as the kernel emits them - watch long-running cells progress in real time instead of waiting for the tool call to finish
  • Progress Notifications: Long-running cells send MCP progress updates to the client (elapsed time, output count, latest output line), including heartbeats for silent cells
  • Kernel Management: Automatic kernel reuse, smart kernel resolution, and session management
  • Complete Notebook Control: Create, read, update, and delete cells programmatically
  • Output Filtering: Intelligent filtering of large outputs to optimize token usage
  • Plots the Model Can See: view_cell_image returns a cell's PNG/JPEG figures, or a markdown cell's pasted images, as MCP image content, also from inside Code Mode scripts
  • MCP Protocol: Standard MCP interface for AI assistant integration
  • Authenticated by Jupyter: The MCP endpoint lives on the Jupyter Server itself and accepts the same token as Jupyter — works on a local server and on JupyterHub
  • Production Ready: Comprehensive test suite, modular architecture, and detailed documentation

Quick Start

1. Installation

Install into the environment that runs Jupyter:

pip install jupyter-mcp-live

For the latest unreleased changes, install main from GitHub:

uv pip install "git+https://github.com/zqfang/jupyter-mcp-live.git"

Or, for development, from a clone:

git clone https://github.com/zqfang/jupyter-mcp-live
cd jupyter-mcp-live
uv sync --extra test

See INSTALL.md for detailed instructions.

2. Basic Configuration

Create a jupyter_config.py file:

c = get_config()

# Basic MCP server settings
c.MCPExtensionApp.mcp_name = "My Jupyter MCP Server"

# Jupyter's token also authenticates MCP clients. Jupyter reads it from the
# JUPYTER_TOKEN environment variable (or generates one and prints it at
# startup), so it never has to be written into this file.

# Register tools from existing packages
c.MCPExtensionApp.mcp_tools = [
    # Notebook execution and kernel management tools
    "jupyter_mcp_live.execution_tools:list_notebook_sessions",
    "jupyter_mcp_live.execution_tools:list_notebook_kernels",
    "jupyter_mcp_live.execution_tools:execute_cell",
    "jupyter_mcp_live.execution_tools:setup_notebook",
    "jupyter_mcp_live.execution_tools:switch_notebook_kernel",
    "jupyter_mcp_live.execution_tools:shutdown_notebook",
    "jupyter_mcp_live.execution_tools:interrupt_notebook_kernel",
    "jupyter_mcp_live.execution_tools:restart_notebook_kernel",
    "jupyter_mcp_live.execution_tools:add_code_cell",
    "jupyter_mcp_live.execution_tools:edit_code_cell",
    "jupyter_mcp_live.execution_tools:add_markdown_cell",
    "jupyter_mcp_live.execution_tools:edit_markdown_cell",
    "jupyter_mcp_live.execution_tools:delete_cell",
    "jupyter_mcp_live.execution_tools:view_notebook",
    "jupyter_mcp_live.execution_tools:read_cell",
    "jupyter_mcp_live.execution_tools:view_cell_image"
]

3. Start Jupyter Server

export JUPYTER_TOKEN=<your Jupyter token>
jupyter lab --config jupyter_config.py
## or use example config here
# jupyter lab --config examples/jupyter_config_with_execution_tools.py

The MCP endpoint is served by Jupyter itself at {base_url}mcp on the Jupyter port, e.g. http://localhost:8888/mcp. Every request must carry the Jupyter token (Authorization: token <TOKEN> or Authorization: Bearer <TOKEN>); requests without it get 403.

4. Connect MCP Clients

Claude Code Configuration:

Set the following configuration:

"mcpServers": {
  "jupyter-mcp": {
    "type": "http",
    "url": "http://localhost:8888/mcp",
    "headers": { "Authorization": "Bearer <your Jupyter token>" }
  }
}

Or use the claude CLI:

claude mcp add --transport http jupyter-mcp http://localhost:8888/mcp \
  --header "Authorization: Bearer <your Jupyter token>"

Codex Configuration (CLI and IDE extension):

Add the following to ~/.codex/config.toml (shared by Codex clients), or to .codex/config.toml for a trusted project:

[mcp_servers.jupyter-mcp-local]
url = "http://localhost:8888/mcp"
bearer_token_env_var = "JUPYTER_TOKEN"
# Optional: allow tool calls to run for up to 10 hours (default: 60 seconds).
tool_timeout_sec = 36000

Make the same Jupyter token available in the environment that launches Codex:

export JUPYTER_TOKEN='<your Jupyter token>'
codex

For the IDE extension, ensure the IDE process inherits JUPYTER_TOKEN, then restart the extension after saving the configuration. Codex sends the token as Authorization: Bearer <token>; the token itself stays out of the TOML file.

Run codex mcp list to check the configured server, or use /mcp in the Codex CLI to inspect active servers. For JupyterHub, replace url with https://<hub-host>/user/<username>/mcp and set JUPYTER_TOKEN to your Hub API token.

See the official OpenAI MCP documentation for configuration details.

Gemini CLI Configuration:

Add the following to .gemini/settings.json:

{
  "mcpServers": {
    "jupyter-mcp": {
      "httpUrl": "http://localhost:8888/mcp",
      "headers": { "Authorization": "Bearer <your Jupyter token>" }
    }
  }
}

JupyterHub

Install jupyter-mcp-live in the single-user server environment (the user image). Each user's server then exposes its own endpoint through the Hub proxy — no extra ports or proxy configuration:

https://<hub-host>/user/<username>/mcp

Authenticate with a JupyterHub API token: create one on the Hub's Token page (https://<hub-host>/hub/token). A token you create for yourself can reach your own server by default; if you restrict its permissions, keep the scope access:servers!user=<username>. Send it as Authorization: Bearer <token>:

claude mcp add --transport http jupyter-hub https://<hub-host>/user/<username>/mcp \
  --header "Authorization: Bearer <hub-api-token>"

Each user's MCP endpoint can only reach that user's kernels and files.

For the full step-by-step setup (user image, tool config, restart, token, verification, troubleshooting), see JUPYTERHUB.md.

Long-running cells

A cell may run for up to 10 hours (EXECUTION_CONFIG.default_timeout). While it runs, the server sends MCP progress notifications: one per output line (at most one per second) and a heartbeat every 30s.

Your MCP client has its own limits. In Claude Code:

  • Idle timeout: a tool call that receives neither a response nor progress for the idle window is aborted (CLAUDE_CODE_MCP_TOOL_IDLE_TIMEOUT, 5 minutes for HTTP servers). The 30s heartbeat keeps long, silent cells from hitting it.
  • Wall-clock timeout: MCP_TOOL_TIMEOUT (milliseconds, default about 28 hours) or a per-server timeout in .mcp.json. Progress does not extend these, so if you set one, keep it above your longest cell, e.g. MCP_TOOL_TIMEOUT=36000000 for 10 hours.

To stop a cell, call interrupt_notebook_kernel (like JupyterLab's stop button; variables are kept) or, if it ignores the interrupt, restart_notebook_kernel (clears all state). A cell that hits the timeout, or whose MCP request is cancelled, is interrupted automatically. On Windows, an interrupt only takes effect between Python statements, so a single long call into C code (e.g. time.sleep(600) or one huge native operation) finishes before it stops. JupyterLab's stop button behaves the same. Use restart_notebook_kernel if a cell must stop immediately.

See Claude Code's MCP docs and environment variables.

Security notes

  • MCP tools execute arbitrary code; anyone holding the token can run code as the Jupyter user. Treat the token like a password.
  • If Jupyter runs without authentication (empty token and no password), Jupyter treats every request as anonymous and requires an XSRF cookie on POST, so MCP clients get 403. That is deliberate — an unauthenticated server is open to cross-site attacks. Set a token instead.
  • Send the token in the Authorization header, not as ?token= in the URL (URLs end up in logs and shell history).
  • The endpoint is meant for MCP clients (Claude Code, Gemini CLI, …), not browser pages: CORS preflight requests are not answered.
  • Use c.MCPExtensionApp.mcp_endpoint_path to move the endpoint if another extension already serves /mcp.

Upgrading from 0.3.x

0.4.0 serves MCP on the Jupyter port by default and no longer starts the separate listener on mcp_port. Point clients at http://<jupyter>/mcp with the Jupyter token, as above. To keep the old unauthenticated listener on a single-user machine, set:

c.MCPExtensionApp.mcp_standalone = True  # listens on mcp_port, NO authentication
c.MCPExtensionApp.mcp_port = 8080

The standalone listener now validates the Host and Origin headers (421 / 403 for foreign values), which blocks DNS-rebinding attacks from web pages, but it still has no authentication — anyone who can connect can run code.

Architecture

Core Components

MCPServer (jupyter_mcp_live.mcp_server.MCPServer)

A simplified LoggingConfigurable class that manages FastMCP integration:

from jupyter_mcp_live.mcp_server import MCPServer

# Create server
server = MCPServer(name="My Server", port=8080)

# Register functions
def my_tool(message: str) -> str:
    return f"Hello, {message}!"

server.register_tool(my_tool)

# Start server
await server.start_server()

Key Methods:

  • register_tool(func, name=None, description=None) - Register a Python function
  • register_tools(tools) - Register multiple functions (list or dict)
  • list_tools() - Get list of registered tools
  • start_server(host=None) - Start the HTTP MCP server

MCPExtensionApp (jupyter_mcp_live.extension.MCPExtensionApp)

Jupyter Server extension that manages the MCP server lifecycle:

Configuration Traits:

  • mcp_name - Server name (default: "Jupyter MCP Server")
  • mcp_jupyter_endpoint - Serve the authenticated endpoint on the Jupyter port (default: True)
  • mcp_endpoint_path - Endpoint path relative to Jupyter's base_url (default: "mcp")
  • mcp_standalone - Also run the separate, unauthenticated listener (default: False)
  • mcp_port - Port of the standalone listener (default: 3001; only used with mcp_standalone)
  • mcp_tools - List of tools to register (format: "module:function")
  • use_tool_discovery - Enable automatic tool discovery via entrypoints (default: True)

Tool Registration

Tools can be registered in two ways:

1. Manual Configuration

Specify tools directly in your Jupyter configuration using module:function format:

c.MCPExtensionApp.mcp_tools = [
    "os:getcwd",
    # from another package, installed separately (pip install jupyter-ai-tools)
    "jupyter_ai_tools.toolkits.notebook:read_notebook",
]

2. Automatic Discovery via Entrypoints

Python packages can expose tools automatically using the jupyter_mcp_live.tools entrypoint group.

In your package's pyproject.toml:

[project.entry-points."jupyter_mcp_live.tools"]
my_package_tools = "my_package.tools:TOOLS"

In my_package/tools.py:

# Option 1: Define as a list
TOOLS = [
    "my_package.operations:create_file",
    "my_package.operations:delete_file",
]

# Option 2: Define as a function
def get_tools():
    return [
        "my_package.operations:create_file",
        "my_package.operations:delete_file",
    ]

Tools from entrypoints are discovered automatically when the extension starts. To disable automatic discovery:

c.MCPExtensionApp.use_tool_discovery = False

Configuration Examples

Minimal Setup

c = get_config()
# Nothing else is required: start Jupyter with JUPYTER_TOKEN set (it also
# authenticates MCP clients) and register tools as shown above.

Full Configuration

c = get_config()

# MCP Server Configuration
c.MCPExtensionApp.mcp_name = "Advanced Jupyter MCP Server"
c.MCPExtensionApp.mcp_endpoint_path = "mcp"  # served at {base_url}mcp
# Third-party toolkits are not dependencies of this package; install the ones
# you list: pip install jupyter-ai-tools jupyterlab-commands-toolkit
c.MCPExtensionApp.mcp_tools = [
    # File system operations (jupyter-ai-tools)
    "jupyter_ai_tools.toolkits.file_system:read",
    "jupyter_ai_tools.toolkits.file_system:write", 
    "jupyter_ai_tools.toolkits.file_system:edit",
    "jupyter_ai_tools.toolkits.file_system:ls",
    "jupyter_ai_tools.toolkits.file_system:glob",
  
    # Notebook operations (jupyter-ai-tools)
    "jupyter_ai_tools.toolkits.notebook:read_notebook",
    "jupyter_ai_tools.toolkits.notebook:edit_cell",
    "jupyter_ai_tools.toolkits.notebook:add_cell", 
    "jupyter_ai_tools.toolkits.notebook:delete_cell",
    "jupyter_ai_tools.toolkits.notebook:create_notebook",
  
    # Git operations (jupyter-ai-tools)
    "jupyter_ai_tools.toolkits.git:git_status",
    "jupyter_ai_tools.toolkits.git:git_add",
    "jupyter_ai_tools.toolkits.git:git_commit",
    "jupyter_ai_tools.toolkits.git:git_push",
  
    # JupyterLab operations (jupyterlab-commands-toolkit)
    "jupyterlab_commands_toolkit.tools:clear_all_outputs_in_notebook",
    "jupyterlab_commands_toolkit.tools:open_document",
    "jupyterlab_commands_toolkit.tools:open_markdown_file_in_preview_mode",
    "jupyterlab_commands_toolkit.tools:show_diff_of_current_notebook",
  
    # Utility functions  
    "os:getcwd",
    "time:time",
    "platform:system",
]

Running Tests

# Install test dependencies (pytest and plugins; the [dev] extra is only ruff)
pip install -e ".[test]"

# Unit tests (mocked; no kernels or servers started)
pytest -m "not integration"

# Integration tests (start a real Jupyter Server and kernels)
pytest -m integration

# Run with coverage
pytest --cov=jupyter_mcp_live

Project Structure

jupyter-mcp-live/
├── jupyter_mcp_live/
│   │  # Server and endpoint
│   ├── extension.py           # Jupyter Server extension (MCPExtensionApp)
│   ├── mcp_handler.py         # Authenticated {base_url}mcp endpoint
│   ├── asgi_bridge.py         # Runs the FastMCP app inside Tornado
│   ├── mcp_server.py          # FastMCP server (Code Mode, tool registry)
│   ├── code_mode_images.py    # Forwards images from Code Mode tool calls
│   │  # MCP tools
│   ├── execution_tools.py     # The 16 public tools
│   │  # Services
│   ├── service_container.py   # Service singletons
│   ├── kernel_manager.py      # Kernel lifecycle and reuse
│   ├── execution_engine.py    # Runs cells, streams outputs live
│   ├── cell_operations.py     # Add / edit / delete cells
│   ├── query_service.py       # Read-only notebook queries
│   │  # Notebook adapters
│   ├── notebook_adapter.py    # Adapter protocol + factory
│   ├── ydoc_adapter.py        # Open notebooks (live YDoc edits)
│   ├── ydoc_helpers.py        # YDoc room lookup and lifecycle
│   ├── nbformat_adapter.py    # Closed notebooks
│   ├── notebook_store.py      # Load/save via the contents manager
│   │  # Utilities
│   ├── execution_utils.py     # Kernel execute + output collection
│   ├── execution_progress.py  # MCP progress notifications
│   ├── output_filter.py       # Trims large outputs in responses
│   ├── output_converter.py    # Output format conversion
│   ├── cell_images.py         # Images for view_cell_image
│   ├── kernel_utils.py        # Kernel name resolution
│   ├── kernel_tracker.py      # Kernel sessions per notebook
│   ├── notebook_config.py     # Configuration constants
│   ├── notebook_models.py     # Dataclasses
│   ├── path_utils.py          # Path validation (.ipynb, inside root)
│   └── server_context.py      # Server context
├── tests/
│   ├── test_*.py              # Unit tests (mocked)
│   └── integration/           # Real Jupyter Server and kernels
├── examples/
│   └── jupyter_config_with_execution_tools.py  # Example configuration
├── jupyter-config/            # Enables the server extension on install
├── INSTALL.md                 # Installation guide
├── JUPYTERHUB.md              # JupyterHub setup
└── pyproject.toml             # Package configuration

Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Add tests for new functionality
  4. Ensure all tests pass: pytest tests/
  5. Submit a pull request

Metadata

Release files for jupyter-mcp-live 0.6.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for jupyter-mcp-live 0.6.0
File Size Uploaded
jupyter_mcp_live-0.6.0.tar.gz 166.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for jupyter-mcp-live 0.6.0
File Interpreter ABI Platform
jupyter_mcp_live-0.6.0-py3-none-any.whl Python 3 none any Details

Total release size: 263.6 kB

Release files / jupyter_mcp_live-0.6.0.tar.gz

Download URL jupyter_mcp_live-0.6.0.tar.gz
Size 166.1 kB
Tags Source
SHA-256 checksum
How to use checksums
6ec410230dcb8edbcd8e10b6f2a3be2cbf9c8b0464a2f22f387941cb30700c20
BLAKE2b-256 checksum
How to use checksums
a8817ea23ef0f191bc71e4cf11f18951496878756509b541b863fdae961da650
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.3

Release files / jupyter_mcp_live-0.6.0-py3-none-any.whl

Download URL jupyter_mcp_live-0.6.0-py3-none-any.whl
Size 97.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
9cf5891d6e14588c058b6d20e2fd243c58d2ba62b1a7de5e41cd1bffd1ca813e
BLAKE2b-256 checksum
How to use checksums
d4820bcc36b2bd978dbd114cd3fc4bb6ca09b5459a2e8f781e672b6131f592e3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.3

Release history Release notifications | RSS feed

This release

0.6.0 This release

2 release files

0.0.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page