@jupyter-kit/executor-jupyter
Runs cells against a remote Jupyter kernel — talks to a running Jupyter Server (or JupyterHub / BinderHub / Enterprise Gateway) over the standard kernel WebSocket protocol.
Because it speaks the protocol — not Python directly — it is language-
agnostic. Any installed Jupyter kernel works (Python via ipykernel,
R via IRkernel, Julia via IJulia, TypeScript via ITypescript, …).
| runtime | location | |
|---|---|---|
executor-pyodide | Python (CPython → WASM) | browser |
executor-webr | R (compiled → WASM) | browser |
executor-jupyter | any Jupyter kernel | remote server |
Exports createJupyterExecutor(options) returning an
Executor ready to pass to <Notebook executor={…} />.
JupyterExecutorOptions
| Option | Type | Default | Notes |
|---|---|---|---|
baseUrl (required) | string | — | Jupyter Server base URL, e.g. http://localhost:8888. No trailing slash. |
token | string | — | API token. Sent as Authorization: token <value> for REST and ?token=<value> on the WebSocket. |
kernelName | string | 'python3' | Kernel spec name. Ignored when kernelId is given. |
kernelId | string | — | Reuse a pre-existing kernel instead of starting one. |
shutdownOnDispose | boolean | true | Shut down the kernel on dispose() (only when this executor started it). |
onStatus | (status, detail?) => void | — | Lifecycle callback. status is one of 'idle' | 'connecting' | 'starting-kernel' | 'busy' | 'ready' | 'error' | 'disconnected'. |
The returned executor also exposes a dispose() method — call it when the
<Notebook> unmounts to shut down the kernel and close the WebSocket.
Usage
import { Notebook } from '@jupyter-kit/react';import { createJupyterExecutor } from '@jupyter-kit/executor-jupyter';import { createEditorPlugin } from '@jupyter-kit/editor-codemirror';import { python as pythonEditor } from '@codemirror/lang-python';import { useEffect, useMemo } from 'react';
function MyNotebook({ ipynb }) { const executor = useMemo( () => createJupyterExecutor({ baseUrl: 'http://localhost:8888', token: import.meta.env.VITE_JUPYTER_TOKEN, kernelName: 'python3', onStatus: (s, d) => console.log('jupyter:', s, d), }), [], );
// Shut down the kernel on unmount. useEffect(() => () => { executor.dispose(); }, [executor]);
return ( <Notebook executor={executor} plugins={[createEditorPlugin({ languages: { python: pythonEditor() } })]} ipynb={ipynb} language="python" /> );}Server setup
The Jupyter Server must allow the renderer’s origin in CORS. Minimum dev config:
jupyter server \ --ServerApp.token=YOUR_TOKEN \ --ServerApp.allow_origin='http://localhost:6006' \ --ServerApp.allow_credentials=True \ --ServerApp.disable_check_xsrf=TrueOr put the same in ~/.jupyter/jupyter_server_config.py:
c.ServerApp.allow_origin = 'http://localhost:6006'c.ServerApp.allow_credentials = Truec.ServerApp.disable_check_xsrf = TrueFor production, restrict allow_origin to your renderer’s actual origin.
allow_origin='*' disables allow_credentials per W3C — acceptable here
because auth rides on the ?token= query param, not on cookies.
How it works
createJupyterExecutor({...})returns an executor object. No network traffic yet.- On the first
execute()call:POST /api/kernelsstarts a fresh kernel (unlesskernelIdwas given); a WebSocket to/api/kernels/<id>/channelsopens. - Each cell execute sends an
execute_requeston the shell channel;iopubmessages are collected (stream, display_data, execute_result, error) until a matchingstatus: idlearrives. dispose()closes the WS andDELETE /api/kernels/<id>shuts the kernel down (when this executor owns it).
Comm support
commProvider is implemented, so @jupyter-kit/widgets
works end-to-end with ipywidgets running in the remote kernel. comm_open
messages from the kernel are routed to handlers registered via
commProvider.onCommOpen(...); frontend→kernel traffic is forwarded on the
shell channel.
Limitations
allow_stdin: false— interactiveinput()calls are not supported.- v1 binary message framing is not implemented; Jupyter Server defaults to JSON over WebSocket so this is rarely an issue.
- Reconnect-on-drop is not built in. If the WebSocket closes, the next
execute()call re-establishes a fresh connection but in-flight requests are lost.
Security
- Token = full execution rights. Anyone with the
tokencan run anything the server user can. Treat it like an SSH key: never commit it, never put it in a client-side bundle that ships to untrusted users. - Kernel state is shared across cells. Variables / imports / file handles persist for the lifetime of the kernel. If you expose this to multiple visitors, they share a kernel (and each other’s state) unless you create a new executor per session.
- Filesystem & network access. The kernel can read any file the server
user can read, open outbound connections, run
subprocess, etc. Put the server inside an isolated container or limited-permission VM. - CORS configuration leaks auth in dev.
disable_check_xsrf=Trueremoves the CSRF protection Jupyter normally applies — fine for local dev, but don’t deploy that flag to production. - Consider a per-user proxy. JupyterHub spawns a kernel per authenticated user and handles isolation. If your renderer is on a public page, reverse-proxy through JupyterHub rather than exposing a single shared Jupyter Server.
Unlike executor-pyodide / executor-webr, the renderer itself is not
the attack surface — your server is. The usual web-app threat model
(token hygiene, CSRF, rate limiting, resource quotas) applies.