Metadata-Version: 2.5
Name: af-jupyterlab-mcp
Version: 0.1.3
Summary: MCP Server for per-user JupyterLab server management on the ATLAS AF Kubernetes cluster
Project-URL: Homepage, https://github.com/maniaclab/af-jupyterlab-mcp
Project-URL: Bug Tracker, https://github.com/maniaclab/af-jupyterlab-mcp/issues
Author-email: Giordon Stark <kratsg@gmail.com>
License-Expression: Apache-2.0
License-File: LICENSE
Classifier: Development Status :: 1 - Planning
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Scientific/Engineering
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: af-credentials[mcp]>=0.2.0
Requires-Dist: jinja2>=3.1
Requires-Dist: kubernetes>=30
Requires-Dist: mcp<3,>=2.0.0
Requires-Dist: pyyaml>=6
Description-Content-Type: text/markdown

# af-jupyterlab-mcp

<!-- --8<-- [start:intro] -->

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](https://github.com/maniaclab/af-portal) deploys
today, exposed as tools for LLMs.
<!-- --8<-- [end:intro] -->

<!-- --8<-- [start:architecture] -->

## 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
```

Phase 1 (this repo, today) ships six tools that manage the Pod/Service/
Secret/Ingress quadruple for a notebook, ported from af-portal's
`portal/jupyterlab.py` and its four Jinja templates. Phase 2 (tracked, not yet
built) adds a typed proxy to the Datalayer `jupyter-mcp-server` running inside
the notebook itself — see
[maniaclab/af-mcp-platform#189](https://github.com/maniaclab/af-mcp-platform/issues/189).
<!-- --8<-- [end:architecture] -->

## 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)
│   └── templates/        # pod.yaml.j2, service.yaml.j2, secret.yaml.j2, ingress.yaml.j2
│                          # (ported verbatim from af-portal/portal/templates/jupyterlab/)
└── tools/
    └── jupyterlab.py     # the six @mcp.tool() functions
```

<!-- --8<-- [start:tool-surface] -->

## Tool surface

- `create_jupyter_server`
- `list_jupyter_servers`
- `get_jupyter_server`
- `delete_jupyter_server`
- `get_gpu_availability`
- `list_supported_images`

The owner of every server is always `claims.unixname` from the verified broker
JWT — no tool takes an owner/username argument.
<!-- --8<-- [end:tool-surface] -->

## Build and test commands

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