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.
This commit is contained in:
2026-05-29 07:04:51 -07:00
parent e4cadf08dc
commit 4cb8e3cd2c
5 changed files with 214 additions and 0 deletions

View File

@@ -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: <https://git.alexzaw.dev/alexz/mcp-manager>
- Published image: `git.alexzaw.dev/alexz/mcp-manager:<tag>`
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`).

View File

@@ -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"

View File

@@ -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."
}
]
}

View File

@@ -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"
}
}
]
}

View File

@@ -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://<domain>/api/mcp/<name>/sse?token=<token>`
- `POST https://<domain>/api/mcp/<name>/messages?session_id=...&token=<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