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