Catalog PyPI
PACKAGE EXPLORERView in registry

af-jupyterlab-mcp

PyPI

MCP Server for per-user JupyterLab server management on the ATLAS AF Kubernetes cluster

Versions collected1
Direct dependencies6From release 0.4.0
Last observed20 Oct, 21:07 UTC

README

Version 0.4.0 documentation

af-jupyterlab-mcp

MCP server that lets AF users create, inspect, and delete their own per-user JupyterLab servers on the UChicago ATLAS Analysis Facility Kubernetes cluster — the same notebooks af-portal deploys today, exposed as tools for LLMs.

Architecture

LLM <--MCP/HTTP--> af-jupyterlab-mcp <--k8s API--> notebook namespace (Pod/Service/Secret/Ingress)
                         ^
                         | Authorization: Bearer <broker-issued JWT>
                         |
              af-mcp-platform credential broker

This repo ships three groups of tools: six that manage the Pod/Service/ Secret/Ingress quadruple for a notebook, ported from af-portal's portal/jupyterlab.py and its four Jinja templates; seventeen nb_* tools that proxy calls into the Datalayer jupyter-mcp-server running inside the notebook itself (4ea435f), so a session can drive code execution inside the user's own notebook without the notebook token ever entering LLM context — see maniaclab/af-mcp-platform#189; and 52 nb_ui_* tools that drive the user's open JupyterLab tab through the jupyter-mcp-tools extension.

Project layout

src/af_jupyterlab_mcp/
├── cli.py               # argparse: `af-jupyterlab-mcp serve` (HTTP only)
├── config.py            # env-driven Settings: namespace, domain, image allowlist, quotas
├── server.py            # FastMCP setup, lifespan (k8s client + broker verifier), tool registration
├── auth/
│   └── broker.py        # extract_bearer(), get_broker_claims() -- broker-issued JWT verification
├── k8s/
│   ├── errors.py         # GuardrailError, NameConflictError, NotFoundOrNotYoursError, ...
│   ├── guardrails.py     # CPU/memory/duration range + image allowlist validation
│   ├── names.py          # sanitize_k8s_pod_name, name availability, name generation
│   ├── templates.py      # Jinja rendering of the four ported manifests
│   ├── notebooks.py      # create/get/list/delete notebook (ported portal logic)
│   ├── gpu.py            # get_gpu_availability (ported portal logic)
│   ├── proxy.py          # call_notebook_tool -- MCP client that proxies into jupyter-mcp-server
│   └── templates/        # pod.yaml.j2, service.yaml.j2, secret.yaml.j2, ingress.yaml.j2
│                          # (ported verbatim from af-portal/portal/templates/jupyterlab/)
└── tools/
    ├── _helpers.py        # format_error(), append_next_actions(), format_notebook[_list]()
    ├── jupyterlab.py      # the six CRD-management @mcp.tool() functions
    ├── nb_proxy.py        # the seventeen nb_* jupyter-mcp-server proxy @mcp.tool() functions
    └── nb_ui.py           # the 52 nb_ui_* JupyterLab frontend-command proxy tools

Tool surface

75 tools total. Every tool's MCP annotations declare its read-only/mutating/destructive status (see CLAUDE.md's "Tool registration pattern"); the column below mirrors that.

Notebook server management (k8s/notebooks.py, k8s/gpu.py)

ToolDoesKind
create_jupyter_serverCreate a per-user JupyterLab server (pod+service+secret+ingress)mutating
list_jupyter_serversList the caller's own JupyterLab serversread-only
get_jupyter_serverGet rich status for one of the caller's own JupyterLab serversread-only
delete_jupyter_serverDelete one of the caller's own JupyterLab servers (all four objects)destructive
get_gpu_availabilityGet cluster-wide GPU availability, optionally filtered by productread-only
list_supported_imagesList the CPU and GPU images allowed by create_jupyter_serverread-only

The owner of every server is always claims.unixname from the verified broker JWT — no tool takes an owner/username argument.

Notebook content proxy (k8s/proxy.py, upstream: jupyter-mcp-server)

Every nb_* tool takes notebook_server_id first, verifies the caller owns that pod, checks it is Ready, and forwards the call to the notebook's own jupyter-mcp-server with the notebook token injected server-side (never returned to the caller).

ToolDoesKind
nb_list_filesList files on the notebook server's filesystemread-only
nb_list_kernelsList all running kernels on the notebook serverread-only
nb_list_notebooksList notebooks open on the notebook serverread-only
nb_use_notebookConnect to or create a notebook on the notebook servermutating
nb_unuse_notebookDisconnect from a notebook on the notebook servermutating
nb_restart_notebookRestart a notebook's kernel on the notebook serverdestructive
nb_read_notebookRead a notebook's cells from the notebook serverread-only
nb_read_cellRead a cell from the active notebookread-only
nb_insert_cellInsert a cell at a given index in the active notebookmutating
nb_overwrite_cell_sourceOverwrite the source of a cell in the active notebookdestructive
nb_edit_cell_sourceEdit part of a cell's source in the active notebookmutating
nb_delete_cellDelete one or more cells from the active notebookdestructive
nb_move_cellMove a cell to a different index in the active notebookmutating
nb_execute_cellExecute a specific cell in the active notebookdestructive
nb_insert_execute_code_cellInsert a code cell and immediately execute itdestructive
nb_execute_codeExecute arbitrary code in the notebook server's kerneldestructive
nb_clear_cell_outputClear a code cell's outputs, keeping the celldestructive

The three code-execution tools (nb_execute_cell, nb_insert_execute_code_cell, nb_execute_code) are marked destructive even though "execute" isn't literally a delete: they can mutate anything the kernel can reach, which is what the annotation communicates to a client. Cell-addressing tools accept either a positional index or the cell's stable cell_id (given both, the id wins); prefer ids, since an index goes stale as soon as a cell is inserted above it.

JupyterLab UI proxy (tools/nb_ui.py, upstream: jupyter-mcp-tools)

Every nb_ui_* tool proxies one JupyterLab command through the same ownership/readiness/token path as the nb_* tools, but the command runs inside the user's open JupyterLab browser tab (relayed over a websocket by the jupyter-mcp-tools extension) and acts on the active notebook, console, or selection there. With no tab open, the call fails. The notebook image must allowlist each command (maniaclab/ml_platform#14).

Tool names are nb_ui_ + the upstream id with - → _ (upstream ids are JupyterLab command ids with : → _, e.g. notebook:run-all-cells → notebook_run-all-cells → nb_ui_notebook_run_all_cells). Tools that take arguments beyond notebook_server_id are noted.

AreaTools (nb_ui_ prefix omitted)Kind
Notebooknotebook_get_selected_cell, notebook_move_cursor_down, notebook_move_cursor_up, notebook_extend_marked_cells_below, notebook_extend_marked_cells_above, notebook_copy_cellread-only
Notebooknotebook_insert_cell_below, notebook_insert_cell_above, notebook_paste_cell_below, notebook_paste_cell_above, notebook_move_cell_up, notebook_move_cell_down, notebook_split_cell_at_cursor, notebook_change_cell_to_code, notebook_change_cell_to_markdown, notebook_change_cell_to_rawmutating
Notebooknotebook_delete_cell, notebook_cut_cell, notebook_merge_cell_above, notebook_merge_cell_below, notebook_run_all_cells, notebook_run_cell, notebook_run_cell_and_select_next, notebook_run_cell_and_insert_below, notebook_append_execute (source, cell_type)destructive
Consoleconsole_create (path, insert_mode, activate)mutating
Consoleconsole_clear, console_interrupt_kernel, console_inject (code, path, activate)destructive
Documentsdocmanager_open (path, factory), docmanager_new_untitled (content_type, path, ext), docmanager_save, docmanager_duplicatemutating
File browserfilebrowser_go_to_path (path), filebrowser_refresh, filebrowser_toggle_hidden_filesread-only
File browserfilebrowser_create_new_directorymutating
Kernelkernelmenu_reconnect_to_kernelmutating
Kernelkernelmenu_interrupt, kernelmenu_shutdowndestructive
UIapplication_toggle_left_area, application_toggle_right_area, application_toggle_presentation_mode, apputils_change_theme (theme), editmenu_open, filemenu_open, helpmenu_openread-only
Searchdocumentsearch_start (search_text), documentsearch_highlightNext, documentsearch_highlightPreviousread-only
Terminalterminal_create_new, terminal_refreshmutating

"Read-only" here includes commands that only change view state (cursor, selection, layout, search, theme). Commands that execute code are destructive, like the nb_execute_* tools.

Deliberately not proxied (and not allowlisted in the image):

  • filebrowser_upload / filebrowser_download — they open a file picker or save into the human's browser; no bytes ever reach the MCP client.
  • docmanager_delete / docmanager_rename / docmanager_save-as, kernelmenu_change, kernelmenu_restart, console_restart-kernel — they block on a modal dialog that needs a human click. Use nb_restart_notebook to restart a kernel without the UI.

Images with different tool sets

The tool list above is fixed, but the notebook behind a call can run any allowlisted image, and older images offer fewer tools (e.g. ml-platform:2026.3 has no jupyter-mcp-server at all; images with jupyter-mcp-server 1.x lack clear_cell_output and all but two UI commands). The chart value notebook.images.toolOverrides maps an exact image string to the upstream jupyter-mcp-server tool ids that image offers (e.g. read_cell, notebook_run-all-cells). Images without an entry are assumed to support every tool, matching the latest ml-platform image. Calling a tool a notebook's image does not offer returns a clear "not supported by image" error naming the image, without contacting the notebook.

On a jupyter-mcp-server 1.x image, the 2.x cell_id-style arguments are not understood: 1.x silently drops unknown arguments, so passing both cell_index and cell_id uses the index (2.x would use the id), and passing cell_id alone fails upstream because cell_index is required there.

Build and test commands

pixi run test          # quick tests
pixi run lint          # pre-commit + pylint
pixi run helm-lint      # lint + smoke-render the Helm chart

Downloads & popularity

Awaiting observations

No metric observations yet

Download counts and popularity will appear here as the collector receives them.

Versions 1

Collected releases
VersionPublishedDirect dependenciesOpen version
0.4.09 Oct 20266

Package advisories

0
No matching advisories collected

This describes our collected data; it is not a security assessment.

Repository

PENDING
maniaclab/af-jupyterlab-mcp

Shared repository data · GitHub

License detected in repository
Not collected

Metadata history

Meaningful changes
Current collected state Current

Observed since 20 Oct, 21:07 UTC

No superseded metadata states collected. Unchanged observations do not create new revisions.

Record identity

package:8120d36c8deee5143767b448e8e7940f48bb9204ec54337390cd8d608313b2fc