From 4cb8e3cd2c569c1c37a7072223625ede54807544 Mon Sep 17 00:00:00 2001 From: alexz Date: Fri, 29 May 2026 07:04:51 -0700 Subject: [PATCH] Add mcp-manager app: MCP server control panel w/ HTTPS SSE bridge Single-container FastAPI + React UI plus mongo:7. Image bundles Node 20 + uv/uvx so npx/uvx MCP servers run; package caches persisted under app-data. 127.0.0.1 /api healthcheck; admin email/password form fields + auto-generated JWT secret. --- apps/mcp-manager/build/README.md | 35 ++++++++++++++ apps/mcp-manager/build/build.sh | 37 +++++++++++++++ apps/mcp-manager/config.json | 45 ++++++++++++++++++ apps/mcp-manager/docker-compose.json | 59 ++++++++++++++++++++++++ apps/mcp-manager/metadata/description.md | 38 +++++++++++++++ 5 files changed, 214 insertions(+) create mode 100644 apps/mcp-manager/build/README.md create mode 100644 apps/mcp-manager/build/build.sh create mode 100644 apps/mcp-manager/config.json create mode 100644 apps/mcp-manager/docker-compose.json create mode 100644 apps/mcp-manager/metadata/description.md diff --git a/apps/mcp-manager/build/README.md b/apps/mcp-manager/build/README.md new file mode 100644 index 0000000..8e03f3d --- /dev/null +++ b/apps/mcp-manager/build/README.md @@ -0,0 +1,35 @@ +# MCP Manager image build + +The Dockerfile and full source for the **MCP Manager** image live in a separate Gitea repo: + +- Source: +- Published image: `git.alexzaw.dev/alexz/mcp-manager:` + +This folder holds only a thin helper script (`build.sh`) that clones the source +repo and pushes the resulting image to the in-house Docker registry. + +## Rebuild + +```bash +cd build/ +./build.sh # builds + pushes :latest and the version tag +IMAGE_TAG=1.0.1 ./build.sh # override the tag +``` + +Requires `docker login git.alexzaw.dev` to already be done (or `~/.docker/config.json` +to have the registry credentials, which is the case on this host). + +## Notes + +- Two-stage build: `node:20-bookworm-slim` builds the React frontend, then a + `python:3.11-slim` runtime stage serves the API + static UI on port `8001`. +- The runtime image bundles **Node 20** (copied from the bookworm node stage, so + it's glibc-compatible — no NodeSource/apt) and **uv / uvx** (`pip install uv`), + so the npx- and uvx-based MCP servers the app manages can actually run. +- All package caches/installs are pinned under `/data` via env vars + (`NPM_CONFIG_CACHE`, `npm_config_prefix`, `UV_CACHE_DIR`, `UV_TOOL_DIR`, pip cache). + Runtipi mounts `/data` to app-data, so MCP servers download once and persist. +- `amd64` only. Add `--platform linux/arm64` to `build.sh` args to cross-build + (Node is copied from a multi-arch base, so this works). +- Upstream ships no `frontend/yarn.lock`; the Dockerfile resolves dependencies + fresh at build time (no `--frozen-lockfile`). diff --git a/apps/mcp-manager/build/build.sh b/apps/mcp-manager/build/build.sh new file mode 100644 index 0000000..cf91dcc --- /dev/null +++ b/apps/mcp-manager/build/build.sh @@ -0,0 +1,37 @@ +#!/bin/bash +# Build and push the MCP Manager Docker image. +# Clones the source repo (or reuses an existing checkout) and tags/pushes +# both :latest and the version tag. + +set -euo pipefail + +IMAGE_NAME="${IMAGE_NAME:-git.alexzaw.dev/alexz/mcp-manager}" +IMAGE_TAG="${IMAGE_TAG:-1.0.0}" +SOURCE_REPO="${SOURCE_REPO:-https://git.alexzaw.dev/alexz/mcp-manager.git}" +WORKDIR="${WORKDIR:-/tmp/mcp-manager-build}" + +echo "==> Preparing source at ${WORKDIR}" +if [[ -d "${WORKDIR}/.git" ]]; then + git -C "${WORKDIR}" fetch --quiet origin + git -C "${WORKDIR}" reset --hard --quiet origin/HEAD +else + rm -rf "${WORKDIR}" + git clone --quiet "${SOURCE_REPO}" "${WORKDIR}" +fi + +echo "==> Building ${IMAGE_NAME}:${IMAGE_TAG} (and :latest)" +cd "${WORKDIR}" +docker build "$@" \ + -t "${IMAGE_NAME}:${IMAGE_TAG}" \ + -t "${IMAGE_NAME}:latest" \ + . + +echo "==> Pushing ${IMAGE_NAME}:${IMAGE_TAG}" +docker push "${IMAGE_NAME}:${IMAGE_TAG}" +echo "==> Pushing ${IMAGE_NAME}:latest" +docker push "${IMAGE_NAME}:latest" + +echo "" +echo "Done. To install on Runtipi:" +echo " sudo /etc/runtipi/runtipi-cli appstore update" +echo " sudo /etc/runtipi/runtipi-cli app install mcp-manager:runtipi" diff --git a/apps/mcp-manager/config.json b/apps/mcp-manager/config.json new file mode 100644 index 0000000..34ceda5 --- /dev/null +++ b/apps/mcp-manager/config.json @@ -0,0 +1,45 @@ +{ + "$schema": "https://schemas.runtipi.io/app-info.json", + "name": "MCP Manager", + "id": "mcp-manager", + "available": true, + "short_desc": "Manage MCP servers behind a login and expose them to Claude over HTTPS/SSE.", + "author": "alexz", + "port": 8501, + "categories": ["ai", "utilities"], + "description": "A control panel for your Model Context Protocol (MCP) servers. Add, edit, run and monitor stdio / SSE / HTTP servers, then export a ready-to-paste Claude configuration. stdio servers (npx- and uvx-based) are launched inside the container and exposed over HTTPS through the built-in SSE bridge. Node 20 and uv/uvx ship in the image, and every package cache is persisted to app-data so servers download once and survive restarts.", + "tipi_version": 1, + "version": "1.0.0", + "source": "https://git.alexzaw.dev/alexz/mcp-manager", + "website": "https://git.alexzaw.dev/alexz/mcp-manager", + "exposable": true, + "dynamic_config": true, + "no_gui": false, + "min_tipi_version": "4.0.0", + "supported_architectures": ["amd64"], + "form_fields": [ + { + "type": "text", + "label": "Admin email", + "env_variable": "ADMIN_EMAIL", + "required": true, + "hint": "Email used to log in to the dashboard." + }, + { + "type": "password", + "label": "Admin password", + "env_variable": "ADMIN_PASSWORD", + "required": true, + "min": 8, + "hint": "Password for the admin account." + }, + { + "type": "random", + "label": "JWT secret", + "env_variable": "JWT_SECRET", + "min": 64, + "encoding": "hex", + "hint": "Auto-generated signing secret for login sessions." + } + ] +} diff --git a/apps/mcp-manager/docker-compose.json b/apps/mcp-manager/docker-compose.json new file mode 100644 index 0000000..d94d0b4 --- /dev/null +++ b/apps/mcp-manager/docker-compose.json @@ -0,0 +1,59 @@ +{ + "$schema": "https://schemas.runtipi.io/dynamic-compose.json", + "schemaVersion": 2, + "services": [ + { + "name": "mcp-manager", + "image": "git.alexzaw.dev/alexz/mcp-manager:1.0.0", + "isMain": true, + "internalPort": 8001, + "dependsOn": { + "mcp-manager-db": { "condition": "service_healthy" } + }, + "environment": [ + { "key": "TZ", "value": "${TZ}" }, + { "key": "MONGO_URL", "value": "mongodb://mcp-manager-db:27017" }, + { "key": "DB_NAME", "value": "mcp_manager" }, + { "key": "DATA_DIR", "value": "/data" }, + { "key": "STATIC_DIR", "value": "/app/static" }, + { "key": "JWT_SECRET", "value": "${JWT_SECRET}" }, + { "key": "ADMIN_EMAIL", "value": "${ADMIN_EMAIL}" }, + { "key": "ADMIN_PASSWORD", "value": "${ADMIN_PASSWORD}" }, + { "key": "FRONTEND_URL", "value": "${APP_PROTOCOL}://${APP_DOMAIN}" } + ], + "volumes": [ + { + "hostPath": "${APP_DATA_DIR}/data/app", + "containerPath": "/data", + "readOnly": false + } + ], + "healthCheck": { + "test": "curl -fsS http://127.0.0.1:8001/api/ || exit 1", + "interval": "30s", + "timeout": "5s", + "retries": 3, + "startPeriod": "30s" + } + }, + { + "name": "mcp-manager-db", + "image": "mongo:7", + "isMain": false, + "volumes": [ + { + "hostPath": "${APP_DATA_DIR}/data/db", + "containerPath": "/data/db", + "readOnly": false + } + ], + "healthCheck": { + "test": "mongosh --quiet --eval 'db.runCommand({ ping: 1 }).ok' || exit 1", + "interval": "15s", + "timeout": "10s", + "retries": 5, + "startPeriod": "20s" + } + } + ] +} diff --git a/apps/mcp-manager/metadata/description.md b/apps/mcp-manager/metadata/description.md new file mode 100644 index 0000000..2649765 --- /dev/null +++ b/apps/mcp-manager/metadata/description.md @@ -0,0 +1,38 @@ +# MCP Manager + +A self-hosted control panel for your **Model Context Protocol (MCP) servers**. Add, edit, run and monitor servers from one dashboard, then export a ready-to-paste Claude configuration. + +## Why this exists + +MCP servers are scattered across `npx`, `uvx`, and one-off HTTP/SSE endpoints, and wiring them into Claude by hand is tedious. MCP Manager keeps them in one place behind a login and turns any local **stdio** server into an HTTPS-reachable **SSE** endpoint you can drop straight into a Claude config. + +## Features + +- Add **stdio**, **SSE**, and **HTTP** MCP servers with custom command, args, and env +- Launch and stop stdio servers as managed processes, with live status +- Built-in **SSE bridge** — exposes each stdio server over HTTPS: + - `GET https:///api/mcp//sse?token=` + - `POST https:///api/mcp//messages?session_id=...&token=` +- Per-server auto-generated `access_token`; unauthenticated requests get `401` +- **Claude Config** tab emits copy-paste-ready stdio and HTTPS/SSE blocks +- Admin login (JWT cookie); config snapshot + running state persisted to disk +- Single container + MongoDB; the React UI and the `/api` are served on one port + +## Runtime + +- **Node 20** and **uv / uvx** ship inside the image, so the common npx- and uvx-based MCP servers run out of the box. +- All package caches and installs (`npm`, `npx`, `uv` tools, global npm prefix, pip) live under `/data`, which is mounted to Runtipi **app-data** — so servers download **once** and survive container restarts and updates. + +## First run + +1. Install and set **Admin email** + **Admin password** (the JWT secret is generated). +2. Toggle **Expose app** and assign a domain — Traefik issues TLS automatically. +3. Add your MCP servers, start the stdio ones, and copy the generated Claude config. + +## Backup + +Everything needed to rebuild lives under app-data: + +- `servers.json` — full server configuration snapshot (re-seeded if the DB is empty on boot) +- `running_state.json` — which stdio servers were running (auto-restarted on boot) +- the MongoDB volume — accounts and live server records