Skip to main content
Glama
README.md
# ipynb-mcp

An agent-agnostic MCP server that lets any AI agent (Claude Code, OpenAI Codex, etc.) drive a local **JupyterLab** session via the [Model Context Protocol](https://modelcontextprotocol.io).

## Getting started with an AI agent

After installation, use one of the following prompts to kick off a session.

**Fresh start (no server running):**
> Start a new notebook project using ipynb-mcp. Venv is at `~/lib/python/uv-osm`, notebook dir is `/path/to/notebooks`. Project goal: [one sentence description].

**Reconnect to an already-running JupyterLab:**
> Continue a notebook project using ipynb-mcp. JupyterLab is already running on port 8888 with token `[token]`. Notebook dir is `/path/to/notebooks`. Continue working on `[notebook.ipynb]`.

The token is printed to the terminal when JupyterLab starts — look for a line like:
```
http://localhost:8888/lab?token=abc123...
```

### Known limitation: browser reload required

When the agent writes or updates cells via the REST API, JupyterLab's browser tab does not auto-refresh. After the agent makes changes, reload the notebook manually with **File → Reload Notebook from Disk** to see the latest cells and outputs.

---

## How it works

```
┌─────────────────────┐  stdio (MCP)  ┌───────────────────┐
│   AI agent          │◄─────────────►│   ipynb-mcp       │
│  (Claude Code /     │               │   (this server)   │
│   OpenAI Codex /    │               │         │         │
│   any MCP client)   │               │  Jupyter REST API │
└─────────────────────┘               │  + kernel WS      │
                                      │         │         │
                                      │  JupyterLab       │
                                      │  (subprocess)     │
                                      └───────────────────┘
```

Unlike colab-mcp (which proxies tools from a remote browser extension), **ipynb-mcp talks directly to the Jupyter REST API and kernel WebSocket** — no browser extension required.

## Installation

This project uses the shared `uv-osm` virtual environment. Always activate it before running any commands.

### Prerequisites

```bash
source ~/lib/python/uv-osm/bin/activate && uv pip install jupyterlab
```

### Install ipynb-mcp

```bash
source ~/lib/python/uv-osm/bin/activate && cd ipynb-mcp && uv pip install -e .
```

> **Note:** Use `uv pip install` (not plain `pip install`). Never install packages globally.

## Configuring your agent

Use the full venv binary path in all agent configs so the server always runs inside the correct environment regardless of the shell's active venv.

### Claude Code

Register the server once, **from your project root directory** (not from inside the `ipynb-mcp` source folder — Claude Code scopes MCP servers to the directory where the command is run):

```bash
cd /path/to/your/project
claude mcp add ipynb-mcp -- /home/<user>/lib/python/uv-osm/bin/ipynb-mcp
```

Verify it registered correctly by running `/mcp` in the Claude Code prompt — you should see `ipynb-mcp · ✔ connected` in the server list. If the server doesn't appear, check that you ran `claude mcp add` from the right directory. The server starts on demand when a tool is called; no manual pre-launch needed.

Or add to `.claude/mcp.json` in your project:

```json
{
  "mcpServers": {
    "ipynb-mcp": {
      "command": "/home/<user>/lib/python/uv-osm/bin/ipynb-mcp",
      "args": []
    }
  }
}
```

Replace `/home/<user>` with your actual home directory (or use the `~` expansion if your client supports it).

### Generic `mcp.json` (OpenAI Codex, Cursor, Zed, etc.)

Most MCP-compatible clients read a configuration file. Create or extend `mcp.json`:

```json
{
  "mcpServers": {
    "ipynb-mcp": {
      "command": "/home/<user>/lib/python/uv-osm/bin/ipynb-mcp",
      "args": [],
      "env": {
        "IPYNB_MCP_NOTEBOOK_DIR": "/path/to/your/notebooks",
        "IPYNB_MCP_PORT": "8888"
      }
    }
  }
}
```

Using the full venv path ensures the server finds all its dependencies without needing the shell to be activated first.

### Environment variables

| Variable | Default | Description |
|---|---|---|
| `IPYNB_MCP_NOTEBOOK_DIR` | `$PWD` | Root directory for notebooks |
| `IPYNB_MCP_PORT` | `8888` | Port for JupyterLab |

## Available tools

| Tool | Description |
|---|---|
| `start_jupyter` | Launch JupyterLab and open the browser tab |
| `open_notebook` | Create or open a notebook; opens it in the browser |
| `get_cells` | List all cells (id, type, source, outputs) |
| `add_code_cell` | Insert a Python code cell |
| `add_text_cell` | Insert a Markdown cell |
| `update_cell` | Replace a cell's source (clears outputs) |
| `delete_cell` | Remove a cell |
| `move_cell` | Reorder a cell |
| `run_code_cell` | Execute a code cell on a live kernel |

## Typical agent workflow

```
agent → start_jupyter()           # launches JupyterLab, opens browser
agent → open_notebook("work.ipynb")
agent → add_text_cell(path, "# My analysis")
agent → add_code_cell(path, "import pandas as pd\ndf = pd.read_csv('data.csv')")
agent → run_code_cell(path, cell_id)   # executes, returns outputs
agent → get_cells(path)                # inspect outputs
agent → update_cell(path, cell_id, "...fixed code...")
agent → run_code_cell(path, cell_id)
```

## Differences from colab-mcp

| | colab-mcp | ipynb-mcp |
|---|---|---|
| Runtime | Google Colab (remote) | Local JupyterLab |
| Architecture | Proxy → browser extension → Colab | Direct Jupyter REST + kernel WS |
| Browser extension | Required | Not required |
| Internet | Required | Optional (fully offline capable) |
| Agent compatibility | Any MCP client | Any MCP client |
| GPU / TPU | Via Colab (free tier limits) | Via local hardware |
| Persistence | Session-based | Standard filesystem |