Skip to main content

Releases

Latest Release

See the latest release notes on the GitHub Releases page.

Jupyter MCP Server 2.1

Most of 2.1 is about the same thing: a server does work a client cannot see, and the failures that follow are quiet ones. Nothing here changes an existing tool call, and the command line, the configuration and the transports are unchanged.

A dead kernel is now reported, not replaced

When a kernel died the server quietly started another. Every variable, import and definition of the session went with the old one, the notebook's state was not what the agent believed it was, and nothing said so — the next execute_cell simply behaved as though the session had always been empty.

A fresh kernel is often exactly what somebody wants, so the behaviour is not gone; it is off by default and named:

The kernel of 'analysis.ipynb' is gone, and its session — every variable,
import and definition — went with it. Call restart_notebook to start a fresh
one, or enable the 'kernel.auto-restart' capability to have replacements
started automatically.

To keep the old behaviour:

jupyter-mcp-server start --capability kernel.auto-restart

Attaching a kernel for the first time is not a restart and is never refused, and restart_notebook is never blocked — it is a caller asking for exactly this. See Capabilities.

Addressing a cell that has moved

A cell index is a position, and a position stops being true the moment anyone inserts a cell above it. In a notebook a person and an agent are both working in, that is constantly — and the failure is silent, because an index stays valid; it just stops meaning the same cell.

Every result now says which cell it acted on:

{ "_meta": { "io.jupyter-mcp/cell_id": "a1b2c3d4" } }

and every cell tool accepts that id in place of an index:

overwrite_cell_source(cell_id: "a1b2c3d4", cell_source: "…")

Given both, the id wins. An id that is no longer in the notebook is an error naming it, never a quiet fall back to the index it arrived with. For delete_cell, every id is checked before any cell is deleted, so one bad id fails the call rather than half-deleting a notebook.

note

Cell ids arrived in nbformat 4.5. A notebook saved before that has none, and the server reports none rather than inventing one — an id generated on read differs on the next read, so it would name a cell only until somebody looked again. Addressing by index still works.

See Cell ids.

Tools say whether they are safe to repeat

All tools now answer idempotentHint and openWorldHint. The direction that matters is the unsafe one: idempotentHint: true on insert_cell or delete_cell would invite a client to retry a timed-out call and insert the cell twice, or delete a second, different cell. Positional edits and restarts say false; setting a value says true; only the three tools that run arbitrary code are open-world.

tools/list is also sorted by name now, so a client that caches the list stops seeing changes that are not changes.

Results carry data as well as text

Every tool result now carries structuredContent beside its text, built in one place. A tab-separated listing also comes back as rows keyed by its header, so an agent wanting one field stops splitting text and hoping the columns have not moved. Text is never dropped: a result with structure and no content is invisible to a client that has not adopted the former.

The three listings carry cache hints (SEP-2549). Nothing that changes carries one — a cell read or an edit answers something that has just moved, and a stale hint is worse than none.

Every tool also advertises an output schema, so a client can learn what a tool returns without calling it, and the server validates what it built against that schema on every call. See Results for the shapes, the kind of every tool, and what arrives in _meta.

Auditing, for deployments that need it

JUPYTER_MCP_AUDIT_SINK_CLASS names a class and every tool call reaches it. A sink that fails never fails the call it was describing, and says so at ERROR every time — a silently dropped audit record looks exactly like nothing having happened. See Audit.

For extension authors

Two things changed, and both were bugs you may have worked around.

Extensions register after the server is configured, not at import. They used to register while the command line was still being parsed, so an extension asking what the server was pointed at got the default however it had been invoked. If you read sys.argv to find out, you can stop.

Extensions register in name order. importlib.metadata returns entry points in whatever order the installation produced — it varies between a wheel and an editable install — and registration is not independent: the SDK keeps the original when a tool name is registered twice, so an extension replacing another's tool silently did nothing if it happened to run first.

Extensions can also declare capabilities now. See Extensions.

jupyter-mcp-spaces no longer ships from this repository. The extension for the datalayer document provider now lives in the Datalayer gateway that serves it, where the code it talks to already lives. Nothing about how it is loaded changed — it is the same jupyter_mcp_server.extensions entry-point group — so a deployment that gets it from the gateway sees the same tools. Only pip install ./ext/spaces against a checkout of this repository has gone away; there is no replacement package to install here.

Conformance

The server is tested against the MCP conformance suite on every pull request, at each protocol version it serves. Known gaps are listed by name in tests/conformance-baseline.yaml; a new failure blocks the merge.

Jupyter MCP Server 2.0: Built on MCP 2

Version 2.0.0 moves the server to the MCP Python SDK 2, the SDK's first major release, and requires mcp >= 2, < 3. The major version bump is about that dependency: the server itself keeps its command line, its configuration, its tools and its behaviour.

Your jupyter-mcp-serverInstall
>= 2.0.0mcp >= 2 (automatic)
< 2.0.0mcp < 2 (automatic)
# Move to 2.0
pip install -U "jupyter-mcp-server>=2.0.0"

# Stay on the 1.x line, for an environment that still needs mcp 1
pip install "jupyter-mcp-server<2"
Nothing changes for users and MCP clients

jupyter-mcp-server start, every option, every environment variable, the Jupyter Server extension and its /mcp endpoint, the 22 tools and the jupyter_cite prompt are all as they were. MCP clients keep connecting the same way: the protocol version is negotiated with each client, and older clients are still served. Tool errors still carry their reason — "notebook not connected", "cell index out of range" — rather than the generic Error executing tool message the new SDK would return on its own.

Who Is Affected

  • Anyone whose environment holds another package pinning mcp<2: pip will refuse to install jupyter-mcp-server>=2 next to it. Stay on jupyter-mcp-server<2 until that package moves.
  • Authors of extensions or custom token verifiers that import from the SDK directly, and anyone embedding the server's streamable_http_app() in their own ASGI application.
  • Clients written on the SDK against the server's test helpers or wire models.

What Changed for Integrators

mcp 1mcp 2
from mcp.server.fastmcp import FastMCP, Contextfrom mcp.server import MCPServer, from mcp.server.mcpserver import Context
FastMCP(..., json_response=..., stateless_http=...)MCPServer(...), transport options on streamable_http_app(...)
from mcp.server.fastmcp.prompts.base import UserMessagefrom mcp.server.mcpserver import UserMessage
tool.inputSchema, content.mimeType, result.structuredContenttool.input_schema, content.mime_type, result.structured_content (model_dump(by_alias=True) for the wire form)
mcp.call_tool() returns (content, structured)returns a CallToolResult
Clients pass an httpx.AsyncClientClients pass an httpx2.AsyncClient; streamable_http_client() yields two streams
Exceptions raised in a tool reach the client as Error executing tool <name>: <reason>The SDK sends only Error executing tool <name>; the server's call_tool override restores the reason, so on this server the message is unchanged

The SDK's own list is in its migration guide.

The pydantic-ai Example

The examples/cli agent needs pydantic-ai 2.35 or later with its mcp extra on fastmcp-slim 4.x, the line of fastmcp built on mcp 2 — see examples/cli/requirements.txt. That is the one place a fastmcp package is involved; the server itself is on the MCP SDK alone.

Hot Fix: Pin code-sandboxes to Match Your Version

The sandbox variant named jupyter was renamed to jupyter-server in code-sandboxes 1.1.1, so the two packages have to agree on the name. Which version of code-sandboxes you need depends on which jupyter-mcp-server you are on:

Your jupyter-mcp-serverInstall
>= 1.5.0code-sandboxes >= 1.1.1
< 1.5.0code-sandboxes <= 1.0.9
# On 1.5.0 or later
pip install "jupyter-mcp-server>=1.5.0" "code-sandboxes>=1.1.1"

# Staying on an earlier jupyter-mcp-server
pip install "jupyter-mcp-server<1.5.0" "code-sandboxes<=1.0.9"
How a mismatch shows up

From 1.5.0, jupyter-mcp-server requires code-sandboxes >= 1.1.1, so pip refuses that pairing outright. The one it does not catch is the other direction: earlier releases asked only for code-sandboxes >= 0.17.0, which a 1.1.1 install satisfies. Nothing fails at install time — it fails the first time code runs:

ValueError: Unknown sandbox variant: jupyter.
Supported variants: datalayer, daytona, docker, eval, google-colab, jupyter-server,
kaggle, modal, monty

If you see that, you are on jupyter-mcp-server < 1.5.0 with code-sandboxes >= 1.1.1. Either upgrade the server or pin code-sandboxes <= 1.0.9.

What Changed

code-sandboxes 1.1.1 made every canonical variant name spell out what it is: jupyter-server rather than jupyter, and google-colab rather than google_colab. From 1.5.0 jupyter-mcp-server uses those names, and SANDBOX_VARIANT=jupyter-server is the default.

Spelling is read leniently from 1.5.0 onward — google_colab, google-colab and GOOGLE-COLAB all name the same variant — but jupyter is not an alias for jupyter-server. It names nothing.

If You Set SANDBOX_VARIANT

BeforeFrom 1.5.0
jupyterjupyter-server
google_colabgoogle-colab
colabgoogle-colab

Everything else — datalayer, kaggle, monty, modal, docker, eval — is unchanged. Leaving SANDBOX_VARIANT unset needs no action: the default moved with the rename.

Migration Guide to 1.3.2

Version 1.3.2 renames provider to document_provider, so that the name says what the option actually selects.

It only ever chose where the notebook documents livejupyter for the collaboration API of a Jupyter Server, datalayer for the Datalayer spacer. Where the code runs is a separate choice, made with --sandbox-variant. The old name, and its help text, suggested one option governed both.

Nothing breaks in 1.3.2

The former names are deprecated, not removed. --provider is still accepted as an alias, PROVIDER is still read, and a /connect payload carrying "provider" is still understood. Migrate when convenient.

Who Should Migrate

  • End users passing --provider or setting PROVIDER, which in practice means anyone pointing the server at Datalayer-hosted documents.
  • Operators whose manifests, wrapper scripts or MCP client configurations carry PROVIDER.
  • Anyone calling the /connect endpoint or the connect_to_jupyter tool with a provider field.

Change Summary

  • Use document_provider names everywhere instead of provider names.
  • The accepted values are unchanged: jupyter (default) and datalayer.
  • Execution backends keep their own option, --sandbox-variant / SANDBOX_VARIANT.
  • Nothing has to change immediately: the old names still work in this version.

Rename Matrix

Old (pre-1.3.2)New (1.3.2+)
providerdocument_provider
PROVIDERDOCUMENT_PROVIDER
--provider--document-provider
"provider" in the /connect payload"document_provider"
provider argument of connect_to_jupyterdocument_provider

End User Migration

1. Update environment variables

Before:

export PROVIDER=datalayer

After:

export DOCUMENT_PROVIDER=datalayer

2. Update CLI commands

Before:

jupyter mcp start \
--provider datalayer \
--document-url <url> \
--document-token <token>

After:

jupyter mcp start \
--document-provider datalayer \
--document-url <url> \
--document-token <token>

3. Update MCP client configurations

An MCP client passing the variable through its env block needs the new key:

{
"env": {
"DOCUMENT_PROVIDER": "datalayer",
"DOCUMENT_URL": "<url>",
"DOCUMENT_TOKEN": "<token>"
}
}

4. Check that document and execution are configured separately

The two axes are independent, and a Datalayer document store does not imply a Datalayer sandbox. State each one:

jupyter mcp start \
--document-provider datalayer \
--sandbox-variant datalayer

Known Migration Pitfalls

  • Assuming --provider datalayer also moved execution to Datalayer; it never did, and --sandbox-variant is what does.
  • Both PROVIDER and DOCUMENT_PROVIDER set to different values in the same environment. The new name wins, which may not be what the older one intended.
  • Wrapper scripts updated while CI secrets still carry the old key name.
  1. Replace PROVIDER with DOCUMENT_PROVIDER in environments, manifests and CI secrets.
  2. Replace --provider with --document-provider in commands and wrappers.
  3. Rename provider to document_provider in /connect payloads and tool calls.
  4. State --sandbox-variant explicitly wherever execution is not the default Jupyter one.
  5. Validate a notebook opens and a cell executes before removing the old names.

For full reference values, see Configuration.

Migration Guide to 1.2.0

Version 1.2.0 completes the terminology migration from runtime to code sandbox.

This is primarily a naming and configuration migration. Execution behavior is the same goal as before: notebooks are stored on a document server and code is executed by a kernel backend. The backend is now consistently called code sandbox across CLI, environment variables, config model, and docs.

Who Should Migrate

  • End users running jupyter-mcp-server locally or in CI.
  • Operators maintaining shared MCP server deployments (containers, services, platform templates, runbooks).

Breaking Change Summary

  • Use code_sandbox names everywhere instead of runtime names.
  • Use CODE_SANDBOX_* environment variables and matching CLI flags.
  • Use start_new_code_sandbox naming for bootstrap behavior.
  • Any custom scripts or wrappers using old runtime names must be updated.

Rename Matrix

Old (pre-1.2.0)New (1.2.0+)
runtimecode sandbox
RUNTIME_URLCODE_SANDBOX_URL
RUNTIME_TOKENCODE_SANDBOX_TOKEN
RUNTIME_PASSWORDCODE_SANDBOX_PASSWORD
RUNTIME_IDCODE_SANDBOX_ID
START_NEW_RUNTIMESTART_NEW_CODE_SANDBOX
--runtime-url--code-sandbox-url
--runtime-token--code-sandbox-token
--runtime-password--code-sandbox-password
--runtime-id--code-sandbox-id
--start-new-runtime--start-new-code-sandbox

End User Migration

1. Update environment variables

Before:

export RUNTIME_URL=http://localhost:8888
export RUNTIME_TOKEN=MY_TOKEN
export START_NEW_RUNTIME=true

After:

export CODE_SANDBOX_URL=http://localhost:8888
export CODE_SANDBOX_TOKEN=MY_TOKEN
export START_NEW_CODE_SANDBOX=true

2. Update CLI commands

Before:

jupyter-mcp-server \
--runtime-url http://localhost:8888 \
--runtime-token MY_TOKEN

After:

jupyter-mcp-server \
--code-sandbox-url http://localhost:8888 \
--code-sandbox-token MY_TOKEN

3. Validate split document + code sandbox setups

If you run document storage and execution on different servers, ensure both sides are configured explicitly:

export DOCUMENT_URL=http://notebook-storage:8888
export DOCUMENT_TOKEN=DOC_TOKEN

export CODE_SANDBOX_URL=http://compute-cluster:8888
export CODE_SANDBOX_TOKEN=EXEC_TOKEN

4. Validate auth mode

Token and password auth still work the same way, just with updated names:

  • CODE_SANDBOX_TOKEN or CODE_SANDBOX_PASSWORD
  • DOCUMENT_TOKEN or DOCUMENT_PASSWORD

When both token and password are supplied, password authentication takes precedence.

Operator Migration

1. Update deployment manifests and templates

Update references in:

  • Kubernetes manifests
  • Helm values/templates
  • Docker Compose files
  • systemd units
  • CI/CD secrets and variable groups

Recommended approach:

  1. Add new CODE_SANDBOX_* variables.
  2. Remove old RUNTIME_* variables.
  3. Restart workloads and verify MCP health checks.

2. Update startup wrappers and runbooks

Search and replace in operational scripts:

  • runtime -> code_sandbox for internal names
  • runtime-oriented flags -> --code-sandbox-*

Also update on-call docs and incident runbooks so diagnostics use current naming.

3. Validate service behavior post-cutover

Run these checks after rollout:

  1. MCP health endpoint returns healthy status.
  2. Kernel listing works (list_kernels).
  3. Notebook operations work (use_notebook, read_cell, execute_cell).
  4. Reconnect behavior remains as expected (RECONNECT_INTERVAL).

Configuration Examples (1.2.0+)

Single Jupyter server for both document and code sandbox

export JUPYTER_URL=http://localhost:8888
export JUPYTER_TOKEN=MY_TOKEN

Explicit dual-server topology

export DOCUMENT_URL=http://notebook-storage:8888
export DOCUMENT_TOKEN=DOC_TOKEN
export DOCUMENT_ID=shared/analysis.ipynb

export CODE_SANDBOX_URL=http://compute-cluster:8888
export CODE_SANDBOX_TOKEN=EXEC_TOKEN
export START_NEW_CODE_SANDBOX=false

Sandbox variant execution

export SANDBOX_VARIANT=kaggle
export CODE_SANDBOX_CHANNELS_URL=wss://.../api/kernels/.../channels?...
export SANDBOX_GPU=T4

Known Migration Pitfalls

  • Old RUNTIME_* names left in environment but ignored by new startup paths.
  • Mixed old/new names across microservices causing partial misconfiguration.
  • Wrapper scripts updated, but CI secrets still use old key names.
  • Documentation drift in internal playbooks and customer onboarding docs.
  1. Replace all RUNTIME_* env vars with CODE_SANDBOX_*.
  2. Replace all runtime CLI flags with code sandbox flags.
  3. Update deployment manifests, secrets, and runbooks.
  4. Validate health plus core notebook execution flows.
  5. Remove old naming from internal docs to prevent regressions.

For full reference values, see Configuration.

Older Releases

0.16.x - 13 Oct 2025

0.15.x - 08 Oct 2025

0.14.0 - 03 Oct 2025

0.13.0 - 25 Sep 2025

0.11.0 - 01 Aug 2025

0.10.2 - 17 Jul 2025

0.10.1 - 11 Jul 2025

0.10.0 - 07 Jul 2025

  • More fixes issues for nbclient stop.

0.9.0 - 02 Jul 2025

  • Fix issues with nbmodel stops.

0.6.0 - 01 Jul 2025