VS Code Jupyter MCP Server
by Happypig375
README.md
# Jupyter MCP Server
[](https://marketplace.visualstudio.com/items?itemName=Happypig375.vscode-jupyter-mcp-server)
[](https://marketplace.visualstudio.com/items?itemName=Happypig375.vscode-jupyter-mcp-server)
[](https://github.com/Happypig375/vscode-jupyter-mcp-server)
A notebook-specific MCP server that runs inside VS Code. An external MCP client can inspect, edit, execute, create, export, and manage notebooks open in the VS Code windows the user is working in. It has no dependency on Copilot Chat or Cursor. Notebook document operations use VS Code's native API; kernel operations require the installed `ms-toolsai.jupyter` extension.
## Install and connect
1. Install **Jupyter MCP Server** (publisher `Happypig375`) from the [Marketplace](https://marketplace.visualstudio.com/items?itemName=Happypig375.vscode-jupyter-mcp-server), or run `code --install-extension Happypig375.vscode-jupyter-mcp-server`.
2. Check the `$(notebook) MCP` status item and copy its URL, or use the output channel. The default endpoint is `http://127.0.0.1:51303/mcp`.
3. Configure the external client to use Streamable HTTP and that copied endpoint.
The tool contract below describes 0.5.0; see the [changelog](CHANGELOG.md) for earlier versions. After an upgrade, refresh or reconnect the client's MCP tool catalog if it still advertises old parameters. The connected server's `tools/list` response is authoritative.
## How this project differs
The design is an in-extension broker over the user's live, shared in-memory `NotebookDocument`. Windows register as peers, duplicate URIs are disambiguated with short opaque `notebookRef` handles, and one broker port is taken over safely if its owner closes. The decisions below explain which documented ideas we adopted and which remain outside this project's boundary.
| Design or workflow | This project's decision, benefit, and tradeoff | Relevant comparison |
|---|---|---|
| Shared live notebook document | **Adopted.** Read and edit VS Code's current document, including unsaved changes. The agent and user share notebook state; the notebook must be open in VS Code. | [vscode-inmemory-notebook-mcp](https://github.com/vatsapatel/vscode-inmemory-notebook-mcp) also supports live notebooks and external clients. [mcp-jupyter-complete](https://github.com/tofunori/mcp-jupyter-complete) uses file edits and reloads; [Datalayer](https://github.com/datalayer/jupyter-mcp-server) supports standalone Jupyter Server workflows. |
| Window routing | **Implemented inside the extension host.** One window owns the broker, peers can take over its port, and short refs distinguish the same URI in different windows. This avoids a separate daemon process. | [vscode-inmemory-notebook-mcp](https://github.com/vatsapatel/vscode-inmemory-notebook-mcp) uses a daemon with per-window bridge workers. |
| Long-running execution | **Adopted for 0.4.0.** `run_cells` returns an `executionId`; `get_execution` follows that same run after a wait budget expires or `waitMs: 0` returns immediately. | The operation-ID and polling pattern comes from [vscode-inmemory-notebook-mcp](https://github.com/vatsapatel/vscode-inmemory-notebook-mcp). |
| Pushed output streaming | **Not implemented.** Bounded polling serves request/response clients. Pushed events would add client notification support and event-history management that this workflow does not require. | [vscode-inmemory-notebook-mcp](https://github.com/vatsapatel/vscode-inmemory-notebook-mcp) documents polling and streaming. |
| Cell locking | **Not implemented.** The server checks explicit targets and execution freshness without imposing an edit-ownership policy on the user and agent. Locking would require a separate coordination policy. | [vscode-inmemory-notebook-mcp](https://github.com/vatsapatel/vscode-inmemory-notebook-mcp) exposes cell-lock tools. |
| Per-run interruption | **Not implemented as an isolated cancellation tool.** `interrupt_kernels` explicitly requests a kernel-wide interrupt; stopping observation of a run does not prove its code stopped. | This server's [interrupt implementation](src/notebookOps.ts) uses the notebook kernel command. |
| Provider startup and administration | **Colab initialization and runtime verification in 0.5.0.** MCP handles CPU/server setup through VS Code's native Chromium driver, lists actual kernel choices for explicit selection, starts and mounts the runtime, then proves kernel/filesystem identity. Requires the supported local Colab extension and a native debugging connection; authentication may require the user. Terminal inspection and explicit closure handle preparation blockers; Drive and general resource administration are not implemented. | The [Colab VS Code guide](https://github.com/googlecolab/colab-vscode/wiki/User-Guide) documents its provider-owned connection and administration flow. |
Whole-notebook reads, cell anchors, and export were inspired by [vscode-inmemory-notebook-mcp](https://github.com/vatsapatel/vscode-inmemory-notebook-mcp); the execution-wait and output-capture pattern came from [vscode-runtime-notebook-mcp](https://github.com/olavocarvalho/vscode-runtime-notebook-mcp).
Sources and repository documentation were checked on 2026-09-06. The Colab setup row was rechecked on 2026-10-19 against the guide, installed 0.9.7 bundle and corresponding source linked below; its preparation-terminal scope was rechecked against the guide and implemented tools on 2026-10-20 (Hong Kong time). This is a bounded, documentation-scoped comparison; it does not claim competitor installation or behavioral testing.
## Tools
Existing-notebook tools use `notebookRef`, or `notebookRefs` for a batch. Prefer the short ref from `list_notebooks`; a plain URI is also accepted when it identifies one open notebook. `open_notebooks` accepts file URIs in `uris` and an optional `windowId`; `create_notebook` accepts `title` and an optional `windowId`.
Cell references accept zero-based numeric indices or the string `cell_id` returned by a read. String references are matched as IDs first, so a native ID containing only digits (for example `"40025805"`) remains an ID; send a JSON number to request an index. Numeric strings still fall back to indices when no exact ID matches. Prefer real cell IDs; an `index:N` fallback can change after insertion, deletion, or movement. Notebook refs remain valid across cell edits and resolve only against currently open notebooks.
| Tool | Category | Description |
|---|---|---|
| `inspect_colab_environment_preflight` | Read | Aggregate bounded preparation blockers, native-listener candidates and sanitized app identity/count corroboration without changing editor or runtime state |
| `list_colab_terminals` | Read | Inspect bounded terminal metadata and short-lived references in one window; never reads commands or history |
| `close_colab_terminals` | Manage | Close only explicitly selected, authorized terminals from that window's latest inventory; reports uncertain or partial closure |
| `prepare_colab_environment` | Manage | Prepare a local workspace and native connection, optionally preserving one strictly verified clean configured Workspace across a tracked normal Windows restart |
| `get_colab_environment_preparation` | Read | Follow durable preparation after restart and freshly verify the exact workspace/native connection |
| `create_notebook` | Create | Create a workspace file or an untitled notebook in an empty window and open it |
| `list_notebooks` | Read | List grouped connected windows, including empty windows, with `windowId`, `windowLabel`, URI, and routable `notebookRef` |
| `read_notebook` | Read | Read an `outline` by default, or select `source`, `outputs`, or `all`; optionally target `cellIds` |
| `search_cells` | Read | Search cell source and output text with match locations |
| `get_kernel_info` | Read | Report observed active runtime fields and explicit unavailable reasons |
| `get_execution` | Read | Inspect a tracked `executionId`, or recover the notebook's latest run; optionally wait for its result |
| `list_kernels` | Read | List exact registered kernel/controller IDs; read-only |
| `configure_kernel` | Manage | Generic Jupyter setup, tracked Colab CPU initialization, or exact prior Colab server reuse with runtime verification |
| `mount_colab_server` | Manage | Recover the exact MCP-created Colab server mount for an already active idle Python kernel, then verify it |
| `list_setup_kernels` | Read | List actual kernel choices from an exact active Colab setup; fresh listing invalidates earlier choice IDs |
| `select_setup_kernel` | Manage | Select one exact ID from that setup's latest listing, then continue startup and verification |
| `get_kernel_setup` / `cancel_kernel_setup` | Manage | Poll or cancel Colab initialization, including cleanup and retained-server/cell status |
| `check_colab` | Diagnose | Prove the active idle kernel matches a Colab filesystem root through a fresh nonce and confirm cleanup |
| `edit_cells` | Write | Insert, edit, delete, or exact-text replace cells; optional explicit rerun |
| `move_cells` | Write | Move cells while preserving content, outputs, and metadata |
| `clear_cell_outputs` | Write | Clear outputs and execution state |
| `run_cells` | Execute | Start a tracked, ordered run; `waitMs` bounds the caller's wait, and observation continues independently |
| `select_kernel` | Manage | Select an exact ID from `list_kernels`; no fallback or start |
| `restart_kernels` | Manage | Request notebook-kernel restarts; provider confirmation may be required |
| `interrupt_kernels` | Manage | Request kernel interrupts; completion cannot be confirmed |
| `open_notebooks` | Manage | Open file URIs, preserving an existing live model |
| `save_notebooks` | Manage | Persist file-backed notebooks, including remote execution state |
| `upload_file` / `download_file` | File transfer | Hashed transfer between `hostPath` and `kernelPath`; native Colab Contents transport for files above 4 MiB |
| `export_notebook` | Manage | Export as markdown, Python, or HTML |
### Editing cell metadata
`edit_cells.edits[].metadata` represents the cell's nbformat metadata, such as `tags`, `jupyter.source_hidden`, or presentation fields. Inserted cells store that map; `edit` and `replace` shallow-merge it with existing nbformat metadata. Omitting it preserves existing metadata. VS Code's native cell ID, attachments, and execution-count fields remain separate and are preserved. The saved `.ipynb` receives the supplied fields inside each cell's `metadata` object.
For `insert`, `cellId` is an anchor: a numeric index or stable ID inserts the new cell immediately after that cell. `TOP` inserts before the first cell and `BOTTOM` inserts after the last. This preserves the existing insertion behavior while making the position explicit.
This follows the ipynb extension's [serializer](https://github.com/microsoft/vscode/blob/main/extensions/ipynb/src/serializers.ts) and [native cell envelope](https://github.com/microsoft/vscode/blob/main/extensions/ipynb/src/common.ts), checked on 2026-10-12. Standalone metadata regressions exercise serialized fields and reopening with a bounded serializer fixture. Setting `NOTEBOOK_SERIALIZER_BUNDLE` to an installed ipynb bundle additionally checks its pure conversion function without activating that extension; this is a serialization check, not a live editor/save integration test.
### Reading without excess output
`read_notebook` returns notebook and cell metadata in every view. `source` adds source text, `outputs` adds saved output, and `all` adds both within the requested limits. Source slicing uses 1-based inclusive `startLine` and `endLine`; `maxSourceChars` defaults to 12,000 per cell, with `0` requesting unbounded source explicitly. Truncation is reported.
For outputs, `outputMode` selects `summary`, preferred `text`, or `full` textual representations. `maxOutputChars` bounds each cell's returned output. Binary images are summarized rather than decoded. Saved output can be stale after an edit; saving does not refresh it.
### Jupyter and providers
The package declares `ms-toolsai.jupyter` as a dependency. Kernel-backed tools (`list_kernels`, `configure_kernel`, `mount_colab_server`, `check_colab`, `select_kernel`, `run_cells`, restart/interrupt, and file transfer) are registered when that extension is present; presence does not guarantee every runtime API is available. `get_kernel_info` and `get_execution` remain exposed for read-only diagnostics.
`list_kernels` lists currently registered controllers, including those contributed by other extensions; it does not discover every dormant provider. `configure_kernel` defaults to generic Jupyter setup and may display a picker, authentication, or consent UI. Its JSON response distinguishes an observed active kernel, pending setup, and no ready kernel; `ready` remains false because observation alone does not prove responsiveness or provider identity. `select_kernel` requires an exact listed ID and passes the actual target editor to the public selection command. Inspection, listing, and file transfer do not start or select a kernel. File transfer requires the public API of an active idle Python kernel; see Microsoft's [kernel execution and authorization sample](https://github.com/microsoft/vscode-extension-samples/tree/main/jupyter-kernel-execution-sample).
### Colab setup and checking
**23 October source update:** environment recovery and window correspondence
now target the exact Windows 1.143.0 build identified below. Independent source
review, structural checks and deterministic suites pass. An isolated native
picker fixture passes; the separate environment fixture passed its behavior
assertions but failed cleanup. A reviewed cleanup-only correction then closed
both owned development windows normally, with independent process and port
checks. The failed full fixture remains recorded. This change has not yet been
installed or validated by a default-profile 1.143.0 restore or real Colab setup.
See the [bounded validation record](TRANSFER_DIAGNOSIS.md#23-october-vs-code-11430-source-and-isolated-validation).
The dated 1.142.0 checks below remain historical evidence. Normal Colab runtime
verification is separate from recovery compatibility.
If VS Code initially suggests local kernels for the notebook, setup chooses **Select Another Kernel... → Colab**. If the source picker opens directly, setup chooses **Colab** there. VS Code can reopen the source picker during a provider handoff; setup allows one additional Colab selection after observing a new empty prompt. It does not accept a suggested local kernel as a default.
Before an input action, MCP brings its exact bound VS Code page to the foreground through Chromium and verifies focus. Binding and kernel listing remain read-only; input guards check focus again before typing or submitting.
Colab activation is checked before opening any picker, even if VS Code reports the extension as active. A cached activation failure returns `colab_activation_failed`; inspect Colab's Output log, restore connectivity and reload the window before retrying. Failed activation can leave Google's authentication provider unregistered until that reload.
`configure_kernel({ notebookRef, runtime: "colab" })` performs Colab initialization inside MCP. It uses the installed Colab 0.9.7 extension and a loopback Chromium debugging connection to operate its normal picker through fixed, bounded DOM projections. **Orca and VS Code's smoke-test driver are not required or used by this path.** A local, trusted VS Code window with an open folder/workspace is required. Remote extension hosts and unsupported provider versions return a blocker before setup.
From an ordinary empty VS Code window, call `prepare_colab_environment({ windowId, folderUri })` before `open_notebooks`. Use the file URI of the notebook's existing local project folder; a folder already trusted by the user retains its normal trust. The tool does not change files or trust settings in that folder. Omitting `folderUri` creates an isolated folder, which may require user trust before MCP and Jupyter extensions start. If a native connection already works, preparation opens the requested or generated folder directly. Otherwise, the default route currently supports the default installed Windows VS Code **1.143.0** profile with exactly one clean empty local window. It checks the installed CLI, main process identity and all window evidence, arms a bounded helper, then requests normal VS Code quit. It never forces termination or saves/discards user work. Open documents, notebooks, terminals, other windows and unsupported installations block that automatic path. While still active, the extension host disarms an unconfirmed quit after five seconds; deactivation allows a normal slow shutdown to finish, and a new host in the same original app disarms a stale attempt. VS Code does not expose the quit-veto result. Concurrent extension disabling/reloading or unrelated exits cannot always be distinguished from the requested shutdown; the original deadline remains bounded.
`prepare_colab_environment({ windowId, restartMode: "restore-clean-workspace" })` selects a separate, narrower route for one already configured Workspace. It supports only the default local Windows VS Code 1.143.0 build at commit `2d0b7506a36c1d6db9a466440131a8e01eab2289`, exactly one corroborated native window, and a trusted local file-backed or untitled Workspace with 1–64 local roots. It first captures and revalidates only that exact Workspace URI and ordered root identities around the marker-bound native probe and pinned install/process/window checks. If the binding already works, dirty or untitled documents, arbitrary tab contents, terminals, debugging, quit/restore policy, kernels and tracked background work do not block this no-restart result; launch arguments and saved-state metadata are not inspected, no restart authorization is created, and the minimal receipt reports `restartRequired: false`. If the marker is unavailable and an actual restart is needed, `window.restoreWindows` must resolve to `all` or `preserve`, `window.confirmBeforeClose` must resolve to `never`, every open notebook must be saved and file-backed, and dirty or untitled text documents, dirty tabs, terminals, debugging, webviews, terminal tabs, virtual tab resources and unknown tab inputs block the route. The actual restart also requires an affirmative no-kernel result and no active or unsettled notebook execution, Colab setup, or file transfer.
Before a restored restart, MCP snapshots the exact Workspace URI, ordered roots and names, file-backed text/notebook/custom/diff tab identities, tab multiplicity and group membership, and document/notebook identities and versions. Document identities and versions are an exact pre-quit mutation guard; they are not a post-launch loaded-editor requirement. MCP also reads bounded metadata from the default profile's `Code/Workspaces` directory and `User/globalStorage/storage.json` backup registry. It never reads backup payloads, deletes stale state, or writes VS Code configuration, trust or private state. Another untitled Workspace or a registered empty-window backup blocks the route. The snapshot is revalidated before and after helper arming and immediately before normal quit under a process-local mutation exclusion. A per-request token holds the same exclusion continuously while the request cleans an older active receipt, reserves a port, and builds its new receipt; only that internal request may transfer provisional ownership to a durable preparation ID. Polling and initialization cannot adopt the request's unbound handle. The complete receipt reserves space for helper and failure fields before the active slot is claimed. Only the owning receipt after it is proven durably failed or ready can relinquish its durable ID; the in-progress request keeps the handle until it transfers to a new pending receipt or exits safely. Expiry must first be recorded and read back as failure, and polling an older receipt cannot release or replace a newer block. A failed final check durably disarms the helper, and an unconfirmed disarm keeps the owning exclusion held.
For this restored route, the helper relaunches the pinned application with the same default user-data and extensions directories plus the owned debugging port, with no positional Workspace argument and no `--new-window`; VS Code's last-session restoration must recreate the approved Workspace, ordered roots and tab-group resource multiplicity. Readiness is withheld until a distinct new main process is bounded by a persisted pre-spawn timestamp, the pinned installation/profile/version/commit and sole window are reverified, the current untitled-Workspace identity/no-other-untitled invariant and zero-empty-window-backup invariant hold, and the exact marker-bound native probe succeeds. Workspace/folder backup counts may be pruned by VS Code at startup and are not required to remain equal. Once a receipt is ready, polling checks only the Workspace identity, trust and a fresh marker-bound native connection; it does not reapply the old editor, terminal, kernel or saved-state checks. The receipt never claims runtime readiness. `folderUri`, when supplied, may only identify an existing root and never converts or opens a different folder.
`inspect_colab_environment_preflight({})` is the read-only companion to that workflow. It queries every connected broker peer with a bounded 30-second per-peer deadline and returns counts and booleans for workspace shape, notebooks and their active/visible editors, text documents and their active/visible editors, tabs, terminals, debugging, quit/restore policy and native-listener candidates. Rejected, timed-out, oversized or invalid peer responses are reported as unavailable instead of failing the whole inspection. It also reports sanitized default-install/version/main-process corroboration and compares the connected-peer count with the independently observed app-window count. For the exact default local Windows VS Code 1.143.0 build at commit `2d0b7506a36c1d6db9a466440131a8e01eab2289`, it can derive a candidate numeric window ID from this extension's public `ExtensionContext.logUri`. It validates the exact extension ID and default-profile path shape without reading or enumerating any log, then corroborates the ID against one unique sanitized `code --status` row under the verified main process. The broker reports a mapping only when every peer responds, all main-process/commit/native-row evidence agrees, every derived ID is unique, and the derived set exactly equals the observed native-window set. Count equality alone remains insufficient; unsupported versions or commits, remote or malformed contexts, duplicate IDs, extra windows, timeouts and mismatches remain unmapped. This mapping is point-in-time read-only evidence, not restart permission, and does not change preparation recommendations or prove restored-session, native-binding or runtime readiness. Native endpoint inspection reads only the bounded loopback target catalog; it does not create a marker, open a debugger WebSocket, evaluate renderer code, focus a window or establish exact window binding. `prepare_colab_environment` retains its own marker-bound check. An automatic next-operation recommendation requires exactly one valid responding peer, one corroborated app window, consistent verified application evidence and no unhandled blocker for that route. This recommendation rule is intentionally narrower than the existing native preparation path: an observed shared listener in a larger app remains evidence only unless `prepare_colab_environment` independently establishes the exact marker binding. The same conservative rule reuses the automatic-restart launch-argument allowlist, so an app previously bootstrapped with debug-port and workspace arguments can report `startup_arguments_unsupported` and suppress an automatic recommendation even while the native target catalog is reachable; that result does not prove native readiness failed. Window labels are shortened to 256 Unicode code points for broker display instead of rejecting a valid long multi-root label. The preflight never returns document contents, terminal commands/arguments/history/environment, authentication state, target titles/URLs, broad process arguments, log paths or filesystem paths. It does not save, close, reload, restart or select a runtime. Results are point-in-time only. The preflight retains no reusable authorization; only an explicit restored-mode preparation owns its bounded durable snapshot and mutation exclusion.
If a terminal blocks preparation, `list_colab_terminals({ windowId })` returns opaque references with bounded name, shell PID, shell type, interaction and exit metadata. These fields cannot prove a terminal is idle or establish its ownership. For terminals whose closure is authorized, call `close_colab_terminals({ windowId, inventoryId, terminalRefs })` with explicit references from the latest inventory, then inspect the closure result before retrying preparation. Inventories expire after 60 seconds and replacement inventories invalidate earlier references. Closure uses the public `Terminal.dispose()` API; it can terminate commands and lose scrollback. A `closed` result confirms removal from the VS Code window, not completion or exit of the shell process. There is no implicit close-all or automatic closure of restored terminals, and the tool never reads terminal arguments, environment, history or commands.
The default preparation route also converts a single folder into an untitled Workspace through VS Code's public [`updateWorkspaceFolders`](https://code.visualstudio.com/api/references/vscode-api#workspace.updateWorkspaceFolders) API, preserving the root URI and giving its display name an owned Colab suffix. This refresh happens **before** opening the notebook or creating its runtime, so Colab can later add its filesystem without the single-folder-to-workspace refresh. Existing configured Workspaces are reused. Restored notebooks, documents, terminals or debugging block conversion; the tool does not close or save them. The conversion is recorded before dispatch and never replayed if acknowledgement is missing. No internal workspace configuration or trust settings are written. The restored-workspace mode never invokes this conversion. This API behavior was checked against VS Code 1.142.0 and the public reference on 20 October 2026.
VS Code may ask whether to save the untitled Workspace when it is later closed or the app quits. Preparation leaves that normal save decision and the user's close preferences intact.
The receipt survives both restarts and includes `preparationId`, `workspaceUri` and `expectedWindowLabel`; after conversion, `folderUri` identifies the original root and `workspaceUri` identifies the untitled Workspace. Reconnect to the same MCP endpoint, re-list windows with `list_notebooks`, then call `get_colab_environment_preparation({ preparationId, windowId })` on the current owner. Polling does not dispatch workspace changes. `environmentReady: true` requires the exact trusted workspace, original root/name, a fresh native connection and, for a generated folder, its owned marker; it is not Colab runtime readiness. Continue with the existing `open_notebooks({ uris, windowId })` or `create_notebook({ title, windowId })`, then the explicit Colab setup sequence below. No second notebook-opening tool is introduced.
The helper relaunches the visible editor, enables a loopback debugging port for that app launch and returns it through the app environment. It does not modify `argv.json`, use Orca or enable the smoke driver. For other supported desktop environments, an existing `--remote-debugging-port=<port>` launch can still be configured with machine-scoped `jupyterMcp.nativeDebugPort` or `JUPYTER_MCP_NATIVE_DEBUG_PORT`. The adapter connects only to `127.0.0.1` and rejects redirected hosts, ports and non-page targets. Reloading an already running window cannot add that listener. Fixed DOM writes recheck marker, widget, input, focus, row and CPU state synchronously. An already verified Colab runtime can be reused without native UI setup.
The workflow reuses an already active runtime only after a fresh Colab check. Without an alias it otherwise selects **Colab → New Colab Server → CPU**, chooses **Standard** and **Latest** if those prompts appear, and supplies a unique alias. To rediscover a prior MCP-created server after a reload, pass its exact receipt value as `serverAlias`, for example `configure_kernel({ notebookRef, runtime: "colab", serverAlias: "m0123abcd" })`. That path selects only the exact live server row and never creates a fallback server or enters another alias. If the exact alias already has one matching mounted root and the current kernel verifies against it, no picker is needed. An absent, ambiguous, disabled, or changed alias fails safely.
Both paths then pause at `awaiting_kernel_selection`. Call `list_setup_kernels({ notebookRef, setupId })`, inspect the returned `choices`, and call `select_setup_kernel({ notebookRef, setupId, kernelChoiceId })` with one exact returned ID. There is no default Python label or first-item selection. This listing is distinct from `list_kernels`, which reports already registered controllers. Filtered, incomplete, duplicate-label or changed picker contents fail safely. A new listing invalidates old choice IDs.
After that explicit selection and VS Code's confirmation, MCP executes only an inserted temporary `pass` cell, removes it after confirmed completion, invokes Colab's server-mount command, selects the same alias if needed, and verifies the kernel against the mounted filesystem. It does not choose GPU/TPU, purchase an upgrade, install packages, mount Google Drive, or delete existing servers. Google's sign-in, consent and MFA remain normal security interactions; an unhandled prompt stops setup with a blocker.
Mounting the first server can convert a single-folder window into a multi-folder workspace and restart its extension host. Keep the receipt's `serverAlias`: the MCP connection, window/notebook handles and in-memory `setupId` can be replaced during that refresh. Reconnect, call `list_notebooks`, and use the new notebook reference with `configure_kernel({ notebookRef, runtime: "colab", serverAlias })`. Follow the new explicit kernel listing/selection if required and require fresh verification. The interrupted receipt does not prove readiness. Notebook-specific picker matching includes the owning folder name in a multi-folder workspace.
Every Colab setup receipt includes its generated `serverAlias` once the alias exists, even if a later startup or mount step fails. If that exact server was selected successfully and the same notebook now has its active idle Python kernel, `mount_colab_server({ notebookRef, serverAlias })` can recover only the missing workspace mount. It accepts only an MCP alias of the form `m` plus eight lowercase hexadecimal characters. It does not select or start a kernel, execute or edit cells, or create or remove a server. An already mounted folder is reused only when exactly one `colab` workspace folder has that exact alias. Otherwise the tool invokes the normal mount command, accepts only that alias, requires one newly added matching root, and completes only after a fresh same-kernel nonce check. The operation has its own tracked `setupId`; poll or cancel it with the normal setup tools.
The request returns a `setupId`. Poll `get_kernel_setup({ notebookRef, setupId, waitMs: 1000 })`; its `actionRequired` field identifies the explicit kernel-selection step. Continue polling after selection until `status` is `ready`, `failed`, or `cancelled`. Only `ready: true` with completed verification permits a readiness claim. `waitMs` is 0–60000 ms (setup/selection default 1000, polling default 0); request expiry does not cancel the five-minute background workflow. The deadline includes time awaiting a choice. `cancel_kernel_setup` cancels explicitly. Setup records are bounded to sixteen and last only for the extension-window lifetime. A transient unique marker binds actions to the owning window, and a machine-wide lease prevents overlapping setup. MCP notebook mutations are refused while that notebook's setup is active. Only bounded kernel labels and opaque choice IDs are returned by listing; no DOM tree, notebook text or screenshot is exposed.
A cancelled provider assignment may still create a server; MCP does not delete it automatically. A queued/running or edited startup cell is retained and reported, and unsettled commands retain the setup lease until they settle. Inspect `failedStage`, `serverMayRemain`, `startupCellRetained`, and `cleanupPending` after a failure. Server naming is confirmed by input readback and the following picker displaying the exact generated alias. Picker completion alone never proves readiness. Exact-ID selection requires VS Code's observed boolean confirmation and fails closed if a future version stops supplying it.
Before submitting a server name, MCP also requires the provider's alias prompt to identify **Colab CPU**. Cancelling setup keeps notebook mutations blocked until pending commands and runtime verification finish. If a command remains stuck after cancellation, use VS Code's normal interrupt control for the owned startup cell, or reload the owning window to recover the extension state; a reload does not prove remote work stopped. A persistent desktop lock after every owning VS Code process has exited needs inspection of the task-specific `jupyter-mcp-colab-setup-*` temporary directory; do not remove a live owner's lock. These are exceptional recovery paths, not steps in normal initialization.
Colab setup also refuses an unsettled `run_cells` command, even when an interrupt has made its execution record `unavailable` or that record has expired. Wait for the underlying VS Code/Jupyter command to settle before recovery. During provider selection, receipts expose `providerStep`; a failed receipt retains `failedProviderStep`, distinguishing the initial kernel picker, Colab source, server prompt, optional repeated handoff, and exact existing/new server selection. These bounded step names contain no captured picker text and do not establish a provider or authentication failure by themselves.
`check_colab({ notebookRef })` also remains available independently. It requires an active idle Python kernel and one registered Colab filesystem root. Supply `colabFileUri: "colab://<server-id>/"` if multiple roots exist; this is a root URI without a query or fragment. The check creates one exclusive non-hidden nonce file in `/content`, validates bounded Linux/Python/module diagnostics, and requires the same nonce through the native Colab filesystem. It removes only its owned probe and confirms absence through both surfaces before returning `verified: true`. Labels, an installed module, or a mounted folder alone cannot pass. The receipt includes the root, process ID, Python version, timestamp and individual checks. The check reads no user files and edits no notebook cells.
If kernel-side removal succeeds but the filesystem is unavailable, check the current server/root before retrying. If execution or cleanup has not settled, retain that uncertainty. Verification applies only at that moment; check again after kernel changes. Standalone transfer/check request cancellation is not forwarded through cross-window routing; their internal deadlines still apply.
The flow was checked on 2026-10-19 against the installed Colab 0.9.7 bundle, its [user guide](https://github.com/googlecolab/colab-vscode/wiki/User-Guide), a [corresponding source snapshot](https://github.com/googlecolab/colab-vscode/blob/d9b4965b4c56d00a85b4cf58c2ea9ca6cda48bc5/src/jupyter/provider.ts), VS Code's [exact-editor command mapping](https://github.com/microsoft/vscode/blob/1.142.0/src/vs/workbench/api/common/extHostNotebookKernels.ts), and [native driver](https://github.com/microsoft/vscode/blob/1.142.0/src/vs/workbench/services/driver/browser/driver.ts). The source snapshot is not a tagged 0.9.7 release. An isolated real VS Code 1.142.0 fixture passed every provider-like prompt, read-only listing, explicit second-kernel selection and alias mounting with no Orca. That fixture is not a real Colab runtime test. Run it explicitly with `node src/test/colabNative.live.cjs <absolute-Code-executable> <new-evidence-directory>`. Routine setup actions are performed by the MCP adapter; authentication and unsupported UI remain explicit blockers.
### File transfer and runtime management
Files above 4 MiB require Colab's registered filesystem provider, which uses Jupyter's Contents API. This is the same file service used by **Upload to Colab** and Colab's native download command. The provider buffers a whole file, so native transfers have a hard 64 MiB cap even if `maxBytes` is larger. Smaller files use 256 KiB kernel chunks unless `colabFileUri` explicitly selects the native service. Each chunk request carries its payload once as a base64 literal, zlib-compressed when that saves at least 10%. IPython keeps the source of every executed request in `linecache` for the kernel's lifetime, so each transfer request is tagged and removes earlier tagged sources; payload-bearing requests are not retained. A kernel object carries at most 64 MiB of chunk traffic; beyond that, transfers are refused before dispatch with guidance to use the native service or restart the kernel. Kernel-side exceptions are reported by class name only.
For native transfer, open a file from the existing runtime's Colab Contents view. Alternatively, **Colab: Mount Server to Workspace** is available when Colab's experimental `colab.serverMounting` setting is enabled. `get_kernel_info` then lists `colabRootCandidates`. A transfer creates a random nonce file in the selected kernel's `/content` and requires the candidate filesystem to read the same nonce before any payload is sent or retrieved. If multiple roots are exposed, supply `colabFileUri` explicitly; they are not probed in sequence. Alternatively, pass an exact `colabFileUri`, such as `colab://<server-id>/content/data.bin`, whose path must equal `kernelPath`. No runtime is started, selected or guessed. Missing or mismatched native routes fail without falling back to a large kernel transfer.
Uploads use the native service only for a random temporary file, then verify size/SHA-256 and atomically promote it in the original kernel. Downloads verify the returned bytes against an independent kernel-side hash before local promotion. No-overwrite remains the default. Kernel lookups/executions have a 60-second deadline, native payload steps two minutes, nonce reads fifteen seconds, and best-effort remote cleanup five seconds. Only one transfer may use a notebook or kernel at a time; each step rechecks the original active idle kernel. An observed restart invalidates the operation. Pending kernel executions or native payload requests quarantine that kernel until they settle. A timed-out read-only nonce probe does not quarantine the selected kernel, so a stale root cannot block a corrected explicit URI. The filesystem API cannot cancel in-flight I/O: a timeout, busy kernel or lost runtime may leave a random `.part` or nonce file, and unconfirmed promotion remains uncertain. MCP client cancellation is still not forwarded across window routing. See the [transfer diagnosis and regression scope](TRANSFER_DIAGNOSIS.md).
`restart_kernels` and `interrupt_kernels` request state changes through Jupyter's provider commands. `restart_kernels` refuses to dispatch when the public Jupyter API explicitly reports no active kernel; it never selects or starts a replacement. Provider command failures are returned as failures. After a command returns, the tool reports the current active-kernel observation and still does not claim completion, because a provider can display confirmation UI or acknowledge before the kernel state changes. Use `get_kernel_info` before `run_cells` to verify the intended runtime where available.
The optional Colab VS Code extension contributes controllers through Jupyter. On 2026-09-06, MCP server 0.3.0 with `google.colab` 0.9.3 passed live checks for targeted execution, nonadjacent output clearing, and a 524,425-byte upload/download round trip with matching SHA-256 hashes in an existing Colab Python session. Initial discovery used the normal Colab picker. This is evidence for that tested integration, not a claim that every Colab feature is MCP-integrated.
On 2026-10-14, Marketplace 0.4.3 with `google.colab` 0.9.7 passed a live 65,568,700-byte native upload/download round trip and a separate 62,572,000-byte upload. Hashes matched and the same Colab process remained responsive through subsequent notebook execution. See [the validation record](TRANSFER_DIAGNOSIS.md#14-october-live-colab-verification) for timings, scope and remaining limits.
Jupyter's [public kernel API](https://github.com/microsoft/vscode-jupyter/blob/v2025.9.1/src/standalone/api/kernels/index.ts) returns already-started kernels for open notebooks, without provider identity. Colab's [0.9.7 activation](https://github.com/googlecolab/colab-vscode/blob/d9b4965b4c56d00a85b4cf58c2ea9ca6cda48bc5/src/extension.ts) exports no connection API. The 0.5.0 setup flow therefore uses the normal notebook picker and a separate filesystem proof, rather than private provider registries or authentication state.
## Recommended flow
1. Call `list_notebooks` and select a `notebookRef`.
2. Read its outline, then request `view: "source"` for the cells needed. Edit cells and refresh indices or fallback anchors after structural changes.
3. If a kernel must be chosen, list its registered controllers and select an exact ID. Configure the provider explicitly when necessary. For Colab, use `runtime: "colab"`, list and explicitly select an actual setup kernel when requested, then require a successful nonce verification before workload execution.
4. Call `run_cells`. `waitMs` defaults to 1,000; zero returns immediately, and larger values can wait for long jobs. This is a request wait budget, not an execution time limit. A returned ID or dispatch status does not prove execution started.
5. If the run is unfinished, call `get_execution` with that ID. Its `waitMs` defaults to zero and also supports long waits. A client may impose a shorter request timeout; neither that timeout nor an expired wait budget interrupts execution. Omit the ID to recover the latest tracked run after a lost response.
6. Read results and save edits or export the notebook. Completed background runs persist file-backed outputs, including remote results; untitled notebooks are not force-saved.
Runs submit cells in order and stop after an observed failure. Only one tracked run is active per notebook, with at most 16 active runs per window and 256 cells per run. The registry retains at most 32 notebooks and eight runs per notebook; terminal records expire after one hour and can be evicted earlier at capacity. Active runs have no one-hour expiry. Returned execution output is capped at 12,000 characters per cell and 48,000 per receipt. `run_cells` includes output by default; `get_execution` includes it only with `includeOutputs: true`. Lookup requires an open notebook, and records do not survive an extension reload. `saveBeforeExecute` pre-saves dirty notebooks before edit, move, or run; use `save_notebooks` to persist subsequent edits.
Tracking observes cell identity, source, execution summaries, and restart/interrupt requests made through MCP. Restarts initiated outside those tools are detected only when VS Code exposes corresponding cell or lifecycle changes. An `unavailable` result means completion could not be confirmed; it does not establish that the kernel stopped.
Execution receipts also report `notebookCommandPending`: whether any tracked cell command for that notebook has yet to settle in VS Code/Jupyter. This can remain true after completion is observed or tracking is invalidated. Another run and automatic Colab setup are blocked until those commands resolve or reject; pending notebooks continue to count toward the 16-notebook active-work limit independently of retained execution history. This is command-settlement evidence, not proof of remote kernel idleness.
Tracked runs watch the public Jupyter kernel's lifecycle and check its identity before each cell and once per second while waiting. A death, restart, termination, disappearance or replacement ends tracking as `unavailable`, with a bounded `reasonCode` and, when observed, `kernelStatusAtStop`; later cells are `not-run`. This also covers Colab automatically restarting without completing the old cell's execution summary. Once a kernel is observed, a transient lookup failure or `unknown` status is tolerated; three consecutive unsuccessful observations end tracking as uncertain. Each lookup is bounded to two seconds, and a new cell waits for a usable observation before dispatch. Initial lookup failure blocks dispatch. None of these observation failures proves a crash or its cause. The monitor runs no heartbeat code and never restarts or interrupts the runtime. It preserves unsettled-command protection, and a return to idle does not clear an earlier confirmed lifecycle failure. Use a fresh `check_colab` after recovery; neither an old setup receipt nor the new kernel's idle status confirms that the interrupted work completed.
If the command never settles, a normal reload of the owning VS Code window clears this in-memory guard and tracking state; it does not prove remote work stopped. Verify the intended runtime before further execution.
### Updating clients from 0.3.0
- Replace `inspect_notebooks`, `read_cells`, and `read_cell_outputs` with the appropriate `read_notebook` view.
- Replace `run_cells.wait` and `timeoutMs` with the single `waitMs` budget, then follow `executionId` with `get_execution`.
- Replace file transfer's `localPath` with `hostPath`, which names a path on the VS Code extension host.
Obsolete keys and tools are rejected; refresh the client's tool catalog after upgrading.
## Multi-window routing
All windows share the configured loopback HTTP URL. The first window binds the broker; others register private loopback peers. If the broker closes, a peer can take over the same port. A URI that is ambiguous across windows fails; use the listed ref. Multi-notebook operations are grouped by owner.
## Configuration
| Setting | Default | Description |
|---|---|---|
| `jupyterMcp.enabled` | `true` | Enable the server |
| `jupyterMcp.transport` | `http` | Streamable HTTP on loopback; `stdio` is scoped to the extension host and has no standalone Node launcher |
| `jupyterMcp.port` | `51303` | Shared machine-scoped broker port |
| `jupyterMcp.saveBeforeExecute` | `true` | Pre-save dirty notebooks before edit, move, or run |
| `jupyterMcp.nativeDebugPort` | `0` | Exact loopback Chromium debugging port; `0` uses the launch environment's `JUPYTER_MCP_NATIVE_DEBUG_PORT`, otherwise native setup is unavailable |
## Development and testing
Install dependencies with `npm ci`, then run `npm run typecheck` and `npm run compile`. Press **F5** to open an Extension Development Host.
`npm test` compiles the extension and runs the MCP boundary, Jupyter integration, broker, kernel-file, desktop-adapter and Colab-setup suites. `npm run coverage` merges their c8 coverage and enforces statements/lines >=75%, branches >=55%, and functions >=85%. The tests use shims without downloading VS Code; live provider checks are separate. CI runs on Ubuntu, Windows, and macOS.
## License
MIT
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues