AgentStack
MCP verified MIT Self-run

Mcpelevator

mcp-pacnpal-mcpelevator · by pacnpal

Elevate MCP servers into authenticated HTTP endpoints. Self-hosted, in one container.

No reviews yet
0 installs
18 views
0.0% view→install

Install

$ agentstack add mcp-pacnpal-mcpelevator

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access Used
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README — it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/mcp-pacnpal-mcpelevator)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
13d ago

Declared compatibility

Claude CodeClaude DesktopCursorWindsurf

Compatibility is declared by the source manifest. End-to-end runtime verification is coming — see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps — measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Mcpelevator? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

mcpelevator

Elevate MCP servers into authenticated HTTP endpoints. Self-hosted, in one container.

Most MCP servers ship as stdio programs (npx -y …, uvx …, a command, a docker image). Stdio only works when the client can spawn the process locally, which phones and most "any device" setups can't do. mcpelevator runs those servers for you and exposes each one as a remote Streamable HTTP endpoint (the transport Claude mobile, Flutter clients, etc. connect to). Add a server, press start, copy the URL into your client. (A per-server REST/OpenAPI surface is on the roadmap, not yet served.)

Already-remote servers work too: point mcpelevator at an existing Streamable-HTTP/SSE MCP URL (the remote runner) and it proxies that upstream behind the same auth, supervision, and per-client copy menu as a local one — handy for putting bearer auth in front of a remote server, or giving every client one consistent endpoint.

The protocol bridging is done by FastMCP; mcpelevator is the control plane: a clean UI, process supervision, security, and onboarding around it.

How it works

[Claude mobile / any MCP client] ──Streamable HTTP──┐
                                                    ▼
  FastAPI ─ /            SvelteKit SPA              (one container, one port)
          ─ /api/*       control plane (SSOT in SQLite)
          ─ /s//mcp   reverse-proxy ─┐  auth + Host/Origin enforced here
                                           ▼
  per enabled server: 1 supervised bridge process (own uvicorn on a loopback port)
      FastMCP proxy(stdio command — or a remote HTTP/SSE URL) → Streamable HTTP   ← fault-isolated, real PID/logs

A reconciler converges running processes to the desired state in SQLite (Kubernetes-style), so the system is idempotent and survives restarts.

Screenshots

Add a server, press start, copy the URL. The dashboard supervises each one — live state, per-server errors, and a copy menu for the endpoint.

Browse the registry — search public MCP directories. Search & install — pick a version, install with one review.

Add a server — npx, uvx, a command, or a remote URL. Settings — access tokens, bind mode, and the LAN gate.

Mobile

Quickstart (Docker)

docker compose up --build
# open http://127.0.0.1:8080

The image is batteries-included (Node/npx + Python/uv preinstalled), so npx/uvx servers run with no extra setup. Data (SQLite + package caches) persists in the mcpe-data volume. By default the port is published to host loopback only. See Security.

Quickstart (Unraid)

A Community Applications template lives in pacnpal/unraid-templates (mcpelevator.xml). It ships the recommended self-hosted-box setup out of the box: host networking (so the LAN gate sees real client IPs), appdata persisted to /mnt/user/appdata/mcpelevator, and MCPE_ALLOW_PRIVATE_LAN=true so the headless box is reachable from your LAN on first boot — the admin token is printed once to the container log for you to log in with. Full walkthrough (install, first login, updating, troubleshooting): [docs/unraid.md](docs/unraid.md).

Quickstart (local dev)

# backend (control plane) on http://127.0.0.1:8080
cd backend && uv sync && uv run uvicorn app.main:app --reload

# frontend (HMR) on http://localhost:5173, proxies /api and /s to :8080
cd frontend && npm install && npm run dev

Or use the Makefile: make dev-backend, make dev-frontend, make build, make test, make docker.

Adding a server

Via the API (the UI add-flow wraps this):

curl -X POST http://127.0.0.1:8080/api/servers -H 'content-type: application/json' -d '{
  "name": "Memory", "runner": "npx", "command": "npx",
  "args": ["-y", "@modelcontextprotocol/server-memory"], "enabled": true
}'

Then point any MCP client at http://127.0.0.1:8080/s/memory/mcp.

Already remote? Use the remote runner to proxy an existing Streamable-HTTP/SSE MCP URL — no local process. The launch spec reuses the same fields: command is the upstream URL, args[0] is the transport (streamable-http or sse), and env is the upstream HTTP headers.

curl -X POST http://127.0.0.1:8080/api/servers -H 'content-type: application/json' -d '{
  "name": "Remote MCP", "runner": "remote",
  "command": "https://example.com/mcp", "args": ["streamable-http"],
  "env": {"Authorization": "Bearer "}, "enabled": true
}'

Pasting an mcpServers config with remote entries now imports them as remote servers instead of skipping: a url (or Gemini CLI's httpUrl) becomes the upstream, and a type / transport field selects the transport (defaulting to streamable-http).

Install from a registry (catalog)

Don't know the package name? Browse finds servers for you. The catalog searches public MCP directories and resolves a chosen server into a launch spec you review and install — no hand-typing npx -y ….

  • MCP Registry (registry.modelcontextprotocol.io) — the official directory. Its

servers carry structured packages, so npm → npx and pypi → uvx are derived automatically and pinned to the latest version (a per-card dropdown picks an older one): one-click install.

  • Glama (glama.ai) — a larger, curated directory for discovery. It publishes no

launch command, so installs open the review form pre-filled with the name + required env-var keys + a repo link for you to complete.

Filter by type — narrow the browse list to one or more package/registry types (npm, pypi, oci, nuget, mcpb, remote) with the type chips. A server that publishes a remote (HTTP/SSE) endpoint is installable as a proxied remote server: install carries the endpoint's declared headers into the review form (required ones flagged) so you can fill in upstream auth before starting.

Open Browse in the header (or /catalog). The backend proxies the directories (GET /api/catalog/servers, GET /api/catalog/server) so the SPA stays same-origin; installing posts the reviewed draft to POST /api/servers tagged source=catalog:.

Adding another directory is a small plugin: one Source module + one line in the source registry — see [backend/app/catalog/README.md](backend/app/catalog/README.md).

Configuration (env vars, prefix MCPE_)

| Var | Default | Meaning | |---|---|---| | MCPE_HOST | 127.0.0.1 | Control-plane bind (Docker sets 0.0.0.0) | | MCPE_PORT | 8080 | Control-plane port | | MCPE_PUBLIC_BASE_URL | (derived) | Absolute URL clients use (set behind a tunnel) | | MCPE_TRUSTED_PROXIES | (none) | CIDRs whose peer IPs count as loopback for the Host guard (reverse proxy / Docker bridge gateway) | | MCPE_TRUST_DOCKER_HOST | false | Auto-detect the container's default gateway (the Docker host) and trust it for the Host guard, without hardcoding the gateway CIDR. Opt-in: safe only with a loopback-published port (-p 127.0.0.1:8080:8080) — under userland-proxy a 0.0.0.0 publish presents the gateway as the peer too, so enabling it there trusts LAN traffic. Loopback allowance only; the bearer-token gate still applies | | MCPE_ALLOWED_HOSTS | (none) | Comma-separated extra hostnames the Host/Origin guard always trusts (like the MCPE_PUBLIC_BASE_URL host, for additional origins). Setting it turns control-plane auth on under auto (the box is reachable off-host via that hostname) | | MCPE_ADMIN_TOKEN | (none) | Break-glass control-plane token, always accepted on /api | | MCPE_MINT_ADMIN_TOKEN | false | Force-mint a fresh admin token on boot and print it (recovery for a lost token); unset after grabbing it | | MCPE_ALLOW_PRIVATE_LAN | false | First-boot seed for the LAN-access setting (headless bootstrap); see Security | | MCPE_DOCKER_RUNNER | false | First-boot seed for the (root-equivalent) docker-runner setting; needs the Docker socket mounted or a dind sidecar | | MCPE_DATA_DIR | ./data | SQLite + caches | | MCPE_FRONTEND_DIR | ../frontend/build | Built SPA to serve | | MCPE_PORT_RANGE_START / _END | 49200 / 49400 | Loopback ports for bridge processes | | MCPE_MAX_RUNNING | 50 | Cap on concurrent running servers | | MCPE_START_TIMEOUT_S | 120 | Readiness timeout (covers npx/uvx cold start) | | MCPE_VERSION | (image: release tag) | Version the instance reports (/api/health, UI badge). Set by the published image from the release tag; you don't normally set it. Unset → derived from the adjacent pyproject.toml (source tree), else installed package metadata |

Security

> Want to expose this over the internet — e.g. to reach it from Claude? See > [docs/claude-web-exposure.md](docs/claude-web-exposure.md) > for two concrete, secure recipes: Path A (claude.ai web/mobile) — > a Cloudflare Tunnel plus a Cloudflare Access self-hosted app with Managed OAuth, > since web/mobile and Desktop's account-UI remote connectors are OAuth-only and > can't send a bearer; and Path B (Claude Code / locally-configured > Desktop) — a public HTTPS tunnel plus mcpelevator's built-in bearer auth. > The guide has the exact cloudflared/Access steps, curl checks, and the > connector caveats to test before relying on web/mobile.

> Full threat model. For the trust boundaries, attacker stories, and how > findings are triaged by severity, see [docs/security.md](docs/security.md).

Two independent layers guard the system, and a request must pass both.

Host/Origin allowlist (DNS-rebinding defense), enforced on every request in every mode. A loopback Host is trusted only when the request's peer actually connects from loopback, so an off-host bind can't spoof Host: localhost. expose mode adds the hosts you allowlist, and the host in MCPE_PUBLIC_BASE_URL is always trusted. Behind a local reverse proxy or Docker's bridge gateway (where the peer is the forwarder, not the real client), set MCPE_TRUSTED_PROXIES (CIDRs) to trust it. The default docker-compose.yml does this for the bridge range, which is safe only with a loopback-published port.

Local network (LAN) access — for a self-hosted box (Unraid, a NAS, a home server) you want to reach from your phone or laptop on the same network, turn on Allow access from devices on your local network (the allow_private_lan setting — Settings page or PATCH /api/settings). It lets a request whose Host is a private-IP literal (e.g. http://192.168.1.50:8080) through the guard when the connecting peer is itself on a private network — no per-host allowlisting, and no DNS-rebinding hole, because a rebinding attack delivers the attacker's domain in the Host header, never a bare private-IP literal. Only IP literals qualify; a hostname that resolves to a LAN address is still rejected. Bind the socket off-host for this: the Docker image already binds 0.0.0.0, but a source install must launch uvicorn with --host 0.0.0.0 (uvicorn app.main:app --host 0.0.0.0) — MCPE_HOST only feeds derived URLs there, it doesn't move the dev-server bind. Because the instance is now reachable off-host, enabling LAN access turns on control-plane auth under auto (so /api requires an admin token). It is off by default.

Getting in the first time (the token-vs-access chicken-and-egg): on a fresh install /api is open from loopback with no token, so the simplest path is to mint the admin token on the box itself — or over an SSH tunnel (ssh -L 8080:127.0.0.1:8080 you@box, then open http://localhost:8080) — which logs that browser in, and then turn LAN access on. For a headless box with no loopback browser, set MCPE_ALLOW_PRIVATE_LAN=true (and optionally MCPE_ADMIN_TOKEN): it seeds the setting on first boot, and because that turns control-plane auth on, the startup bootstrap mints an admin token and prints it once to the container logs (docker compose logs, or Unraid's log viewer) for you to log in with from the LAN. The env var only seeds the initial value — the Settings toggle is authoritative afterwards.

Per-request bearer auth, on both planes:

  • The proxy data plane (/s) uses a pluggable per-server auth provider. v1 ships none and bearer (SHA-256-hashed tokens); a server set to bearer needs a token in Authorization: Bearer . A token authorizes every bearer-protected server by default, or you can scope it to a single server when you create it.
  • The control plane (/api) requires an admin token with the control scope. Enforcement follows the control_plane_auth setting: auto (the default) requires it when bind_mode=expose or MCPE_PUBLIC_BASE_URL is set (either way the instance is reachable off-host), so a plain local install stays zero-config; always requires it even on loopback. /api/health (control-plane liveness), /api/health/{slug} and /api/health/summary (per-server readiness, for load balancers), and /api/auth/status stay public.

When control-plane auth is enforced, the SPA shows a login screen. The admin token is printed once to the container logs on first boot (look for "control-plane auth is ON"), and the Settings page can generate one (which logs you in immediately). To switch to expose or always from the UI you have to generate an admin token first, so you can't lock yourself out.

MCPE_ADMIN_TOKEN is a break-glass credential: when set, it's always accepted on /api. Use it to recover a lost token, or for CI and automation. A minted token is shown only once (only its hash is stored), so if you lose it and haven't set MCPE_ADMIN_TOKEN, set that var and restart to get back in, then generate a fresh token. Alternatively, set MCPE_MINT_ADMIN_TOKEN=true and restart: the bootstrap mints a fresh control token and prints it to the logs (existing tokens keep working) — unset the var afterwards so it doesn't mint a new one on every restart.

Docker runner — launch MCP servers packaged as Docker/OCI images (e.g. ghcr.io/github/github-mcp-server). It is opt-in and root-equivalent: OFF by default behind a Settings toggle (docker_runner, or MCPE_DOCKER_RUNNER=true to seed it headless), because it runs arbitrary images on a Docker daemon. Enable it, then paste an mcpServers docker config or install an OCI catalog entry. mcpelevator stores the canonical shape (image + container args + env) and synthesizes a hardened docker run (--rm --init --cap-drop ALL --security-opt no-new-privileges --pids-limit + a memory cap, secrets passed by name so a value never enters mcpelevator's own argv/ps (Docker still resolves it into the container env, readable via docker inspect by anyone with daemon access), and a label the supervisor uses to reap orphaned containers). Two isolation models, selected by docker-compose.yml config only (identical runner code): sibling containers via the mounted host socket (simplest, hands the host daemon to containers) or an isolated docker:dind sidecar via DOCKER_HOST (blast-radius isolation, privileged sidecar). Networking and the root filesystem stay at Docker's defaults so egress-needing servers work.

Project layout

backend/app/   FastAPI control plane, supervisor, bridge host, runners, auth, proxy, catalog
frontend/      SvelteKit (Svelte 5) SPA, adapter-static
Dockerfile     multi-stage: build SPA → python+node+uv runtime

Status / roadmap

Working today: add a server (guided form, paste an mcpServers config — stdio or remote, or browse a registry and install with one review), supervise it, and use it over Streamable HTTP from any MCP client. Per-server detail with live log streaming, config, and discovered tools; edit / clone / delete / start / stop. Clone a server to spin up a like-configured copy in one click, and rename a server's slug to re-point its `/

Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.