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_imagereturns 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-servertimeoutin.mcp.json. Progress does not extend these, so if you set one, keep it above your longest cell, e.g.MCP_TOOL_TIMEOUT=36000000for 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 get403. That is deliberate — an unauthenticated server is open to cross-site attacks. Set a token instead. - Send the token in the
Authorizationheader, 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_pathto 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 functionregister_tools(tools)- Register multiple functions (list or dict)list_tools()- Get list of registered toolsstart_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 withmcp_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
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Ensure all tests pass:
pytest tests/ - 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)
| File | Size | Uploaded | |
|---|---|---|---|
| jupyter_mcp_live-0.6.0.tar.gz | 166.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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
|