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:
35
apps/mcp-manager/build/README.md
Normal file
35
apps/mcp-manager/build/README.md
Normal 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`).
|
||||
37
apps/mcp-manager/build/build.sh
Normal file
37
apps/mcp-manager/build/build.sh
Normal 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"
|
||||
45
apps/mcp-manager/config.json
Normal file
45
apps/mcp-manager/config.json
Normal 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."
|
||||
}
|
||||
]
|
||||
}
|
||||
59
apps/mcp-manager/docker-compose.json
Normal file
59
apps/mcp-manager/docker-compose.json
Normal 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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
38
apps/mcp-manager/metadata/description.md
Normal file
38
apps/mcp-manager/metadata/description.md
Normal 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
|
||||
Reference in New Issue
Block a user