Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
cleanup. The cluster-side `[jupyter]` extra and `aexp jupyter setup`
extension recipe are unchanged.

### Fixed

- **`aexp install --with-jupyter` now pins the `.mcp.json` `jupyter`
entry to `jupyter-mcp-server==0.23.0`.** The entry was unpinned, so
`uvx` resolved it to "latest" — currently the v1.0.x line, which
(since v1.0.0, 2026-04-03) makes server-startup auth mandatory:
`jupyter-mcp-server` reads `JUPYTER_URL` / `JUPYTER_TOKEN` /
`MCP_TOKEN` from the environment when the process starts. Claude Code
spawns the server over stdio with no such env block, so on v1.0.x the
process comes up but never completes the MCP handshake — the
`jupyter` server hangs at "connecting" and exposes no tools. 0.23.0 is
the last pre-auth release and supports the runtime
`connect_to_jupyter(jupyter_url, jupyter_token)` call this integration
is built on (the cluster URL/token rotate per session, so a
startup-env model does not fit). An unpinned entry therefore shipped
broken-by-default. Existing consumer `.mcp.json` files keep whatever
`jupyter` entry they already have (the merge is additive) — if yours
predates this fix, add `==0.23.0` to that entry's `args` by hand.

## [0.4.0] - 2026-05-20

### Added
Expand Down
30 changes: 29 additions & 1 deletion src/aexp/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -599,11 +599,39 @@ def _jupyter_mcp_entries() -> dict[str, Any]:
No ``.mcp.json`` edit, no MCP restart. That runtime retargeting is
what makes the multi-node workflow (``/aexp-jupyter-connect`` /
``/aexp-jupyter-discover``) work.

**Why ``jupyter-mcp-server`` is pinned to ``==0.23.0``.**
``jupyter-mcp-server`` v1.0.0 (released 2026-04-03) made
server-startup auth mandatory: it reads ``JUPYTER_URL`` /
``JUPYTER_TOKEN`` / ``MCP_TOKEN`` from the *environment when the
process starts*. Claude Code spawns this server over stdio with no
such env block, so on v1.0.x the process comes up but never
completes the MCP handshake — Claude Code shows the ``jupyter``
server stuck "connecting" forever, exposing no tools.

Moving *forward* to v1.0.x is not a fix here: the cluster JupyterLab
URL + token rotate every compute-node session, so baking them into
``.mcp.json`` as static startup env vars is the wrong model. This
integration is built on the *runtime* ``connect_to_jupyter(
jupyter_url, jupyter_token)`` call, which the pre-auth 0.23.0 line
supports cleanly. 0.23.0 is the last release before the
mandatory-auth change and is the version verified against the
electricrag deployment (2026-05-15).

The pin is load-bearing: an *unpinned* ``jupyter-mcp-server``
resolves to "latest" via ``uvx`` — currently v1.0.x — so an unpinned
entry ships broken. Revisit the pin only when v1.0.x grows a
runtime-retarget path (or stdio-spawn stops requiring startup env);
if you bump it, also update the ``.mcp.json`` example and
"Environment reference" in ``docs/setup/jupyter-mcp.md``.
"""
return {
"jupyter": {
"command": "uvx",
"args": ["jupyter-mcp-server"],
# Pinned deliberately -- v1.0.x's mandatory startup-env auth
# hangs the MCP stdio handshake. Full rationale in the
# docstring above; do not unpin without re-verifying.
"args": ["jupyter-mcp-server==0.23.0"],
},
}

Expand Down
21 changes: 18 additions & 3 deletions src/aexp/vendor/limina/docs/setup/jupyter-mcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ pip install uv

# Verify uvx can fetch jupyter-mcp-server (does NOT support --help cleanly,
# but a non-error exit means it's installed)
uvx jupyter-mcp-server --help # may exit with usage; that's fine
uvx jupyter-mcp-server==0.23.0 --help # may exit with usage; that's fine
```

After `aexp install --with-jupyter` your `.mcp.json` will include the
Expand All @@ -185,7 +185,7 @@ After `aexp install --with-jupyter` your `.mcp.json` will include the
"mcpServers": {
"jupyter": {
"command": "uvx",
"args": ["jupyter-mcp-server"]
"args": ["jupyter-mcp-server==0.23.0"]
}
}
}
Expand All @@ -195,6 +195,20 @@ No token or URL is baked into the entry — the agent supplies them at
runtime via `connect_to_jupyter` (see "Per-session: connect from the
laptop" below).

> **Why the `==0.23.0` pin?** `jupyter-mcp-server` v1.0.0 (2026-04-03)
> made server-startup auth mandatory — the process reads `JUPYTER_URL` /
> `JUPYTER_TOKEN` / `MCP_TOKEN` from the environment *when it starts*.
> Claude Code spawns this server over stdio with no such env block, so
> on v1.0.x the process comes up but never completes the MCP handshake:
> the `jupyter` server hangs at "connecting" and exposes no tools.
> 0.23.0 is the last pre-auth release, and it supports the runtime
> `connect_to_jupyter(jupyter_url, jupyter_token)` call this whole
> integration is built around (the cluster URL/token rotate per
> session, so a startup-env model is the wrong fit). The pin is
> load-bearing — an *unpinned* entry resolves to latest = v1.0.x =
> broken. `aexp install --with-jupyter` writes the pin for you; don't
> drop it without re-verifying the handshake against a newer release.

## Per-session: launch JupyterLab on the cluster

Use whatever batch launcher you have for JupyterLab. The launcher should
Expand Down Expand Up @@ -349,6 +363,7 @@ additive follow-up — open an issue.

| Symptom | Likely cause | Fix |
|---|---|---|
| The `jupyter` MCP server hangs at "connecting" in Claude Code and never exposes any tools | `jupyter-mcp-server` resolved to v1.0.x, which requires startup-env auth (`JUPYTER_URL`/`JUPYTER_TOKEN`/`MCP_TOKEN`) that a stdio spawn can't supply — the MCP handshake never completes | Pin the `.mcp.json` `jupyter` entry to `jupyter-mcp-server==0.23.0` (the last pre-auth release). `aexp install --with-jupyter` writes this pin by default — seeing this symptom means the pin was removed, or the `.mcp.json` predates it. |
| **"File ID error: ... cannot be opened because its file ID could not be retrieved"** when opening any notebook in JupyterLab UI. Server log shows `404 POST /api/fileid/index` and `404 GET /jupyter-server-documents/get-example` | Frontend labextension `@jupyter-ai-contrib/server-documents` is calling Datalayer-private routes that no longer exist after we disabled the server-side `jupyter_server_documents` | `jupyter labextension disable @jupyter-ai-contrib/server-documents`, then restart the lab process |
| `execute_cell` returns `jupyter_server_nbmodel extension not found. Please install it.` | `jupyter_server_nbmodel` is disabled (you may have over-corrected if you were following an older draft of this doc) | `jupyter server extension enable jupyter_server_nbmodel`, then restart the lab process |
| `404 Not Found for url: http://.../api/collaboration/session/...` | `jupyter_server_ydoc` extension disabled | `jupyter server extension enable jupyter_server_ydoc`, then restart the lab process |
Expand Down Expand Up @@ -536,7 +551,7 @@ snapshot.
| Tool | Version | Notes |
|---|---|---|
| `uv` | latest | Used by `uvx jupyter-mcp-server` for the laptop-side MCP server |
| `jupyter-mcp-server` | 1.0.2+ | fetched ephemerally by `uvx`; not permanently installed |
| `jupyter-mcp-server` | 0.23.0 (pinned) | fetched ephemerally by `uvx`; pinned in `.mcp.json` — v1.0.x's mandatory startup-env auth hangs the MCP stdio handshake |

### Cluster server endpoint (when Jupyter is running)

Expand Down
4 changes: 3 additions & 1 deletion tests/test_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -770,7 +770,9 @@ def test_install_with_jupyter_writes_mcp_entries(fresh_git_repo: Path) -> None:
assert "aexp" in servers
assert "jupyter" in servers
assert servers["jupyter"]["command"] == "uvx"
assert "jupyter-mcp-server" in servers["jupyter"]["args"]
# jupyter-mcp-server is pinned: v1.0.x's mandatory startup-env auth
# hangs the MCP stdio handshake (see _jupyter_mcp_entries docstring).
assert servers["jupyter"]["args"] == ["jupyter-mcp-server==0.23.0"]
assert "jupyter-compute" not in servers


Expand Down
Loading