Skip to content
Back to skills

Marimo Serve

BSecurity

Use when several marimo notebooks should be reachable at once rather than one `marimo run` per file — 'serve my notebooks', 'host the marimo apps', 'run all the notebooks', 'give me a URL for these notebooks', 'share these dashboards', 'put this on the LAN', 'expose marimo on the tailnet', 'JupyterLab-but-for-marimo', 'let my coauthor click through these'. Also covers the editor-on-a-directory case. NOT for authoring or debugging one notebook (marimo) and NOT for pairing on a live kernel (mar...

  • 22 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 19, 2026
datapythonshellbashdebugging

Works with

  • cli

Security analysis

B75/100
  • criticalModifies startup scripts or system services for persistence

Pro scans all 2 files and shows the line behind each finding

Scanned September 19, 2026

npx -y skills add edwinhu/workflows --skill marimo-serve --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Marimo Serve?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Marimo Serve
[![Security: B — Skills Directory](https://www.skillsdirectory.com/api/skills/edwinhu-marimo-serve/badge)](https://www.skillsdirectory.com/skills/edwinhu-marimo-serve)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: marimo-serve
description: "Use when several marimo notebooks should be reachable at once rather than one `marimo run` per file — 'serve my notebooks', 'host the marimo apps', 'run all the notebooks', 'give me a URL for these notebooks', 'share these dashboards', 'put this on the LAN', 'expose marimo on the tailnet', 'JupyterLab-but-for-marimo', 'let my coauthor click through these'. Also covers the editor-on-a-directory case. NOT for authoring or debugging one notebook (marimo) and NOT for pairing on a live kernel (marimo-pair)."
user-invocable: true
---

# marimo-serve

**What this skill carries** — grep `references/` for any subject the names below miss:
!`d=${CLAUDE_SKILL_DIR}; command -v skill-toc >/dev/null 2>&1 && exec skill-toc "$d"; s=$HOME/.claude/skills/plugin-utils/bin/skill-toc; [ -x "$s" ] && exec "$s" "$d"; echo "(skill-toc unavailable: references and scripts are NOT listed here — install the plugin-utils plugin, or start a new session so its bin/ reaches PATH)"`

Runs one uvicorn process that auto-mounts every `*.py` notebook in a directory under `http://host:port/<mount>/<stem>`. Add or remove files — the URL list updates without restart. This is marimo's closest equivalent to "JupyterLab for many notebooks."

**Default is read-only Run mode.** Pass `--edit` to launch `marimo edit DIRECTORY --watch` instead (full editor, saves on disk; picks up external `.py` edits without restart).

## Quick usage

```bash
# Read-only Run mode (default)
pixi run python ${CLAUDE_SKILL_DIR}/scripts/serve.py [DIRECTORY] \
    [--host 127.0.0.1] [--port 2718] [--mount /<project>] [--include-code]

# Edit mode — launches marimo's built-in multi-session editor
pixi run python ${CLAUDE_SKILL_DIR}/scripts/serve.py [DIRECTORY] --edit \
    [--host 127.0.0.1] [--port 2718]
```

Defaults:
- `DIRECTORY` = `./notebooks`
- Bind = `127.0.0.1:2718`
- Mode = **read-only Run mode**. Users interact with UI widgets and see outputs but cannot edit cells. `marimo.create_asgi_app()` only supports Run mode.
- Mount = `/<project>` (the parent directory's name — e.g. running from `~/projects/mirror` with `./notebooks` → `/mirror/<stem>`). marimo requires a non-empty prefix; pass `--mount` to override. **Run mode only** — edit mode serves at `/`.
- Source code hidden (`--include-code` to reveal; read-only regardless)

## Expose on tailnet

Keep the server on `127.0.0.1` and let Tailscale Serve handle the tailnet + HTTPS. Because marimo requires a non-empty mount prefix (`/apps`), the upstream URL **must include the same prefix** or tailscale will strip it and marimo returns 404:

```bash
# mounts /<project> on the tailnet AND forwards /<project> upstream (path preserved)
tailscale serve --bg --https=443 --set-path=/<project> http://127.0.0.1:2718/<project>
# browse: https://<machine>.<tailnet>.ts.net/<project>/<notebook_stem>
tailscale serve status   # see active config
tailscale serve --https=443 --set-path=/<project> off   # remove just this path
tailscale serve reset    # tear down all paths
```

Use `--set-path` if `/` is already mapped to another service — tailscale serve supports multiple path prefixes on the same hostname.

### Service-worker conflict (separate port)

If an app already mapped at `/` (e.g. a PWA) registers a service worker with root scope, that SW will intercept requests to `/<project>/*` on the **same** origin and return its own cached shell — marimo never gets the request. Fix: put marimo on a different HTTPS port so it's a separate origin (SWs cannot cross origins):

```bash
tailscale serve --bg --https=8443 --set-path=/<project> http://127.0.0.1:2718/<project>
# browse: https://<machine>.<tailnet>.ts.net:8443/<project>/<notebook_stem>
```

`tailscale serve --https=443 off` removes ALL paths on :443 — use `--set-path=/<path> off` to remove a single prefix.

## Persistence (macOS launchd)

For always-on serving, wrap in `~/Library/LaunchAgents/com.user.marimo-serve.plist` pointing at the `serve.py` command with `KeepAlive=true`. Tailscale Serve config survives reboots automatically.

## Dependencies

Project must have `marimo` and `uvicorn` installed. With pixi:

```bash
pixi add marimo uvicorn
```

## When to use another approach

- **Editing, not serving**: pass `--edit` (this wraps `marimo edit <dir>`) or call `marimo edit <dir>` directly.
- **Fully static hosting (no Python)**: `marimo export html-wasm` → drop on any static host.
- **Single notebook, one-off**: `marimo run notebook.py` is simpler.

## Implementation notes

Run mode uses `marimo.create_asgi_app().with_dynamic_directory()` — the officially documented pattern for multi-notebook ASGI serving. Filenames starting with `_` are skipped (treat as private/helper modules). `create_asgi_app()`'s docstring states it "only works for application that are in Run mode" — that's why edit mode delegates to the `marimo edit` CLI (`os.execv`) instead.

Edit mode passes `--watch` by default so that edits made to the `.py` file from outside the browser (e.g. by an agent using Edit/Write) are picked up by the running session without a manual reload. This pairs with the marimo-pair skill: one agent edits the file on disk, the user (or another agent) runs cells in the browser.

Files in this skill

  • SKILL.md5.1 KB
  • scripts/serve.py3.9 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…