09e9f3545f
CodeQL / Analyze (push) Waiting to run
dependency-audit / pip-audit (push) Waiting to run
Test / Code Quality (push) Has been cancelled
Test / Test (macos-latest, Python 3.10) (push) Has been cancelled
Test / Test (macos-latest, Python 3.11) (push) Has been cancelled
Test / Test (macos-latest, Python 3.12) (push) Has been cancelled
Test / Test (macos-latest, Python 3.13) (push) Has been cancelled
Test / Test (macos-latest, Python 3.14) (push) Has been cancelled
Test / Test (ubuntu-latest, Python 3.10) (push) Has been cancelled
Test / Test (ubuntu-latest, Python 3.11) (push) Has been cancelled
Test / Test (ubuntu-latest, Python 3.12) (push) Has been cancelled
Test / Test (ubuntu-latest, Python 3.13) (push) Has been cancelled
Test / Test (ubuntu-latest, Python 3.14) (push) Has been cancelled
Test / Test (windows-latest, Python 3.10) (push) Has been cancelled
Test / Test (windows-latest, Python 3.11) (push) Has been cancelled
Test / Test (windows-latest, Python 3.12) (push) Has been cancelled
Test / Test (windows-latest, Python 3.13) (push) Has been cancelled
Test / Test (windows-latest, Python 3.14) (push) Has been cancelled
114 lines
6.6 KiB
YAML
114 lines
6.6 KiB
YAML
# Remote notebooklm-mcp behind a tunnel. Pick ONE tunnel via a Compose profile:
|
|
#
|
|
# cp .env.example .env # set NOTEBOOKLM_MCP_TOKEN, NOTEBOOKLM_MCP_VERSION, and/or OAuth
|
|
# docker compose --profile cloudflare up -d # Cloudflare Tunnel (needs a domain)
|
|
# docker compose --profile tailscale up -d # Tailscale Funnel (no domain; *.ts.net)
|
|
# # (the Makefile wraps this: `make dev` / `make dev TUNNEL=tailscale`)
|
|
#
|
|
# The notebooklm-mcp service runs under either profile; exactly one tunnel sidecar
|
|
# is selected. No host ports are published — the ONLY way in is through the chosen
|
|
# tunnel, which terminates TLS at its edge (no CA cert for you to manage). See README.md.
|
|
services:
|
|
notebooklm-mcp:
|
|
# Pull the published image by default. `make up` / `make prod` require or
|
|
# inject a version; raw docker compose pull must set NOTEBOOKLM_MCP_VERSION.
|
|
# Override the namespace/tag via
|
|
# NOTEBOOKLM_MCP_IMAGE / NOTEBOOKLM_MCP_VERSION in .env. Contributors
|
|
# building from THIS checkout run `make dev`, which layers
|
|
# docker-compose.build.yml to add a `build:` section back (see that file).
|
|
image: ${NOTEBOOKLM_MCP_IMAGE:-tenglin/notebooklm-mcp}:${NOTEBOOKLM_MCP_VERSION:-set-version}
|
|
restart: unless-stopped
|
|
# Run as YOUR uid:gid so the mounted profile's files (owned by you on the
|
|
# host) are readable AND writable with NO chown — and your own `notebooklm`
|
|
# CLI keeps owning them. Defaults to 1000:1000; `make` fills these from
|
|
# `id -u`/`id -g` automatically, or set them in .env.
|
|
user: "${NOTEBOOKLM_UID:-1000}:${NOTEBOOKLM_GID:-1000}"
|
|
environment:
|
|
# Bearer token clients present (Authorization: Bearer ...). Env-only. OPTIONAL
|
|
# here so an OAuth-ONLY deploy (claude.ai, no Claude Code) works — set the bearer
|
|
# and/or the OAuth vars below. The server's own fail-closed guard refuses to start
|
|
# on this 0.0.0.0 bind if NEITHER is configured (clear message in the logs).
|
|
NOTEBOOKLM_MCP_TOKEN: ${NOTEBOOKLM_MCP_TOKEN:-}
|
|
# Binds 0.0.0.0 (for the cloudflared sidecar) → requires bearer and/or OAuth.
|
|
NOTEBOOKLM_MCP_ALLOW_EXTERNAL_BIND: "1"
|
|
# Optional self-hosted OAuth (for claude.ai). Unset → bearer-only; set both
|
|
# together (partial → refuses to start). See .env.example. The OAuth state
|
|
# (registered clients + tokens) persists under the mounted profile volume.
|
|
NOTEBOOKLM_MCP_OAUTH_PASSWORD: ${NOTEBOOKLM_MCP_OAUTH_PASSWORD:-}
|
|
NOTEBOOKLM_MCP_OAUTH_BASE_URL: ${NOTEBOOKLM_MCP_OAUTH_BASE_URL:-}
|
|
# Optional (Cloudflare profile only): trust the tunnel's CF-Connecting-IP header as
|
|
# the per-IP login-throttle key. Default off → throttle keys on the socket peer (the
|
|
# tunnel egress), i.e. one global bucket. Tailscale Funnel does not set this header.
|
|
NOTEBOOKLM_MCP_TRUST_PROXY: ${NOTEBOOKLM_MCP_TRUST_PROXY:-}
|
|
# Point the app at the mounted profile, independent of the (arbitrary) uid's
|
|
# home. The host dir is always mounted as the profile literally named
|
|
# "server"; NOTEBOOKLM_HOME/profiles/server is where the app reads+writes.
|
|
NOTEBOOKLM_HOME: /data
|
|
NOTEBOOKLM_PROFILE: server
|
|
# HOME points at the writable mount too, so an arbitrary uid with no
|
|
# /etc/passwd entry never trips a `~` / getpwuid lookup.
|
|
HOME: /data/profiles/server
|
|
volumes:
|
|
# READ-WRITE: keepalive/recovery rotates cookies into storage_state.json
|
|
# (+ its .lock). A read-only mount kills the session ~1h in.
|
|
# Defaults to your local "default" profile; override with NOTEBOOKLM_PROFILE_DIR
|
|
# in .env to point at a different profile dir (e.g. a dedicated/throwaway one).
|
|
# Treat the dir as a secret — master_token.json is a full-account credential.
|
|
- "${NOTEBOOKLM_PROFILE_DIR:-${HOME}/.notebooklm/profiles/default}:/data/profiles/server:rw"
|
|
# No `ports:` — not reachable except via the tunnel.
|
|
|
|
# --- Tunnel option A: Cloudflare (needs a domain in your Cloudflare account) ----
|
|
cloudflared:
|
|
profiles: ["cloudflare"]
|
|
# Pinnable for reproducibility: set CLOUDFLARED_VERSION in .env to a release
|
|
# from github.com/cloudflare/cloudflared/releases. Defaults to `latest` (the
|
|
# tunnel connector is backward-compatible and is what Cloudflare recommends
|
|
# running), so the template works without choosing a tag.
|
|
image: cloudflare/cloudflared:${CLOUDFLARED_VERSION:-latest}
|
|
restart: unless-stopped
|
|
command: tunnel run
|
|
environment:
|
|
# From the Cloudflare Zero Trust dashboard (Networks → Tunnels). Configure
|
|
# the tunnel's Public Hostname to point at http://notebooklm-mcp:9420.
|
|
# `:-` not `:?` — Compose interpolates the WHOLE file before profile filtering,
|
|
# so a hard-required `:?` here would error even under `--profile tailscale`.
|
|
# cloudflared fails loudly in its own logs if this is empty when the profile runs.
|
|
TUNNEL_TOKEN: ${CF_TUNNEL_TOKEN:-}
|
|
depends_on:
|
|
# Wait until the MCP server passes its HEALTHCHECK (port listening), not
|
|
# merely until its container starts, before bringing the tunnel up.
|
|
notebooklm-mcp:
|
|
condition: service_healthy
|
|
|
|
# --- Tunnel option B: Tailscale Funnel (NO domain; free stable *.ts.net HTTPS) --
|
|
# tailscale/tailscale Funnel-in-Docker pattern (see Tailscale's Docker docs). It
|
|
# publishes the WHOLE host root → notebooklm-mcp:9420 (so the OAuth routes at / AND
|
|
# /mcp are reachable). Your BASE_URL becomes https://<TS_HOSTNAME>.<tailnet>.ts.net.
|
|
# PREREQS (one-time, in the Tailscale admin console): enable MagicDNS + HTTPS certs,
|
|
# and grant the `funnel` node attribute in the tailnet policy (Access controls →
|
|
# Funnel → "Add Funnel to policy"). TS_AUTHKEY is a normal auth key — Funnel comes
|
|
# from the policy, not the key. NOTE: not live-verified in this repo; check
|
|
# `docker compose --profile tailscale logs tailscale` for the served URL.
|
|
tailscale:
|
|
profiles: ["tailscale"]
|
|
image: tailscale/tailscale:latest
|
|
restart: unless-stopped
|
|
environment:
|
|
# `:-` not `:?` (see the cloudflared note) — required only when the tailscale
|
|
# profile actually runs; the container errors clearly if it's empty then.
|
|
TS_AUTHKEY: ${TS_AUTHKEY:-}
|
|
TS_HOSTNAME: notebooklm-mcp # → https://notebooklm-mcp.<your-tailnet>.ts.net
|
|
TS_STATE_DIR: /var/lib/tailscale
|
|
TS_USERSPACE: "true" # no NET_ADMIN / /dev/net/tun needed
|
|
TS_SERVE_CONFIG: /config/funnel.json
|
|
volumes:
|
|
- tailscale-state:/var/lib/tailscale
|
|
# MUST mount the serve-config DIR (not the file) so tailscaled detects/reads it.
|
|
- ./tailscale:/config:ro
|
|
depends_on:
|
|
notebooklm-mcp:
|
|
condition: service_healthy
|
|
|
|
volumes:
|
|
tailscale-state:
|