dbf5b45acd
- .mcp.json - bash/functions/pipelines/full_git_push.sh - python/pyproject.toml - python/uv.lock - bash/functions/infra/jupyter_mcp_serve.md - bash/functions/infra/jupyter_mcp_serve.sh - dev/issues/0166-app-to-app-dependencies-tracking.md Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
110 lines
3.7 KiB
Bash
Executable File
110 lines
3.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# jupyter_mcp_serve — arranca (o reusa) un Jupyter Lab colaborativo y lanza el
|
|
# Jupyter MCP server enganchado a el por stdio. Pensado para ser el `command` de
|
|
# la entrada "jupyter" en .mcp.json: garantiza que el MCP SIEMPRE tiene servidor.
|
|
#
|
|
# Por que existe: el MCP datalayer NO arranca jupyter, solo se conecta. Si la URL
|
|
# apunta a un puerto sin jupyter (en esta maquina 8888 = proxy VPN gluetun), el
|
|
# MCP nunca conecta. Este wrapper levanta su propio jupyter en un puerto propio.
|
|
#
|
|
# Env overrides:
|
|
# JUPYTER_MCP_ROOT raiz de notebooks (default: raiz del repo)
|
|
# JUPYTER_MCP_PORT puerto del jupyter gestionado (default: 8899)
|
|
# JUPYTER_MCP_VENV venv (default: <repo>/python/.venv)
|
|
# JUPYTER_MCP_TOKEN token (default: "" — solo escucha en 127.0.0.1)
|
|
#
|
|
# stdout esta RESERVADO al protocolo stdio del MCP. Todo log va a stderr + LOGFILE.
|
|
# Nunca hacer echo a stdout aqui.
|
|
#
|
|
# Uso directo / test:
|
|
# bash jupyter_mcp_serve.sh --dry-run # arranca jupyter, NO exec del MCP, loguea args
|
|
set -euo pipefail
|
|
|
|
DRY=0
|
|
[ "${1:-}" = "--dry-run" ] && DRY=1
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
# raiz del repo = tres niveles arriba de bash/functions/infra/
|
|
REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
|
|
|
|
VENV="${JUPYTER_MCP_VENV:-$REPO_ROOT/python/.venv}"
|
|
ROOT_DIR="${JUPYTER_MCP_ROOT:-$REPO_ROOT}"
|
|
PORT="${JUPYTER_MCP_PORT:-8899}"
|
|
HOST=127.0.0.1
|
|
TOKEN="${JUPYTER_MCP_TOKEN:-}"
|
|
LOGDIR="${HOME}/.fn_jupyter_mcp"
|
|
mkdir -p "$LOGDIR"
|
|
LOGFILE="$LOGDIR/wrapper.log"
|
|
JLOG="$LOGDIR/jupyterlab.log"
|
|
|
|
log(){ printf '[%s] %s\n' "$(date '+%H:%M:%S')" "$*" >>"$LOGFILE"; printf '%s\n' "$*" >&2; }
|
|
|
|
JUPYTER="$VENV/bin/jupyter"
|
|
MCP="$VENV/bin/jupyter-mcp-server"
|
|
|
|
if [ ! -x "$JUPYTER" ]; then
|
|
log "FATAL: $JUPYTER no existe. Instala: cd $REPO_ROOT/python && uv pip install --python .venv/bin/python3 jupyterlab jupyter-collaboration jupyter-mcp-server"
|
|
exit 1
|
|
fi
|
|
if [ ! -x "$MCP" ]; then
|
|
log "FATAL: $MCP no existe. Instala jupyter-mcp-server en el venv."
|
|
exit 1
|
|
fi
|
|
|
|
server_up(){
|
|
local code
|
|
code="$(curl -s -m 3 -o /dev/null -w '%{http_code}' "http://$HOST:$PORT/api/status?token=$TOKEN" 2>/dev/null || true)"
|
|
[ "$code" = "200" ]
|
|
}
|
|
|
|
if server_up; then
|
|
log "reuso jupyter existente en $HOST:$PORT"
|
|
else
|
|
log "arranco jupyter colaborativo en $HOST:$PORT (root=$ROOT_DIR)"
|
|
nohup "$JUPYTER" lab \
|
|
--no-browser \
|
|
--ServerApp.ip="$HOST" \
|
|
--ServerApp.port="$PORT" \
|
|
--ServerApp.root_dir="$ROOT_DIR" \
|
|
--IdentityProvider.token="$TOKEN" \
|
|
--ServerApp.disable_check_xsrf=True \
|
|
--ServerApp.allow_origin='*' \
|
|
>>"$JLOG" 2>&1 &
|
|
disown 2>/dev/null || true
|
|
# esperar hasta ~30s a que levante
|
|
for _ in $(seq 1 60); do
|
|
server_up && break
|
|
sleep 0.5
|
|
done
|
|
if ! server_up; then
|
|
log "FATAL: jupyter no levanto en 30s. Ver $JLOG"
|
|
exit 1
|
|
fi
|
|
log "jupyter arriba"
|
|
fi
|
|
|
|
BASE="http://$HOST:$PORT"
|
|
|
|
# Detectar el dialecto de CLI del MCP (cambia entre versiones de jupyter-mcp-server)
|
|
HELP="$("$MCP" --help 2>&1 || true)"
|
|
ARGS=(--transport stdio)
|
|
if printf '%s' "$HELP" | grep -q -- '--document-url'; then
|
|
ARGS+=(--document-url "$BASE" --runtime-url "$BASE")
|
|
printf '%s' "$HELP" | grep -q -- '--document-token' && ARGS+=(--document-token "$TOKEN" --runtime-token "$TOKEN")
|
|
elif printf '%s' "$HELP" | grep -q -- '--jupyter-url'; then
|
|
ARGS+=(--jupyter-url "$BASE" --jupyter-token "$TOKEN")
|
|
else
|
|
# fallback: variables de entorno que las distintas versiones reconocen
|
|
export DOCUMENT_URL="$BASE" RUNTIME_URL="$BASE" DOCUMENT_TOKEN="$TOKEN" RUNTIME_TOKEN="$TOKEN"
|
|
export JUPYTER_URL="$BASE" JUPYTER_TOKEN="$TOKEN"
|
|
fi
|
|
|
|
log "MCP cmd: $MCP ${ARGS[*]}"
|
|
|
|
if [ "$DRY" = "1" ]; then
|
|
log "--dry-run: no ejecuto el MCP. Jupyter sigue corriendo en $BASE"
|
|
exit 0
|
|
fi
|
|
|
|
exec "$MCP" "${ARGS[@]}"
|