MCP Self-Service Server Catalog & Config Generator
web applicationWebApplicationNetworkSystemapt install mcprackmcprack is a centralized platform for managing and distributing Model Context Protocol (MCP) server configurations across your organization. It solves the problem of how to securely provision AI clients with access to multiple backend services without hardcoding secrets or requiring manual configuration on each machine.
Key features:
Users log in with local account or Active Directory credentials, select which servers they need, choose their target client, and download a ready-to-use configuration. Credentials are managed centrally in Vaultwarden using the same secure pattern as the mcp_rack Ansible role.


Model Context Protocol (MCP) Self-Service Catalog & Config Generator
mcprack is a centralized platform for managing and distributing MCP (Model Context Protocol) server configurations across your organization. It solves the problem of how to securely provision AI clients (Claude Desktop, GitHub Copilot, and other MCP-compatible tools) with access to multiple backend services β without hardcoding secrets or requiring manual configuration on each machine.
π Try the live demo (username/password prefilled: demo / demo)

You have multiple MCP servers (tools that connect AI clients to your services: databases, APIs, knowledge bases, etc.). You want users to:
mcprack provides:
.json or .env config file tailored to each user with their chosen serversAdmin registers a new server (e.g., mastodon-mcp):
/usr/bin/mastodon-mcpMASTODON_INSTANCE, MASTODON_ACCESS_TOKENMCP-mastodon-mcpUser logs into mcprack (local account or Active Directory):
claude_desktop_config.json with only those serversAI Client (Claude) loads the config:
Credentials are never exposed to the user or stored insecurely β they come from Vaultwarden at runtime.
Non-technical users don't have to do any of this themselves. An admin
can configure a user's entire server access, config selection, and
individual credentials on their behalf β from the web UI or scripted via
the CLI β so the user never has to log in or make a single choice. See
doc/ADMIN-USER-CONFIG.md.
Local accounts are always available. LDAP/Active Directory is optional and disabled by default β enable it during installation if you want users to authenticate with AD credentials instead.
A logged-in local user can change their own password from the user menu ("Change Password"). LDAP-authenticated users can't β their password is managed by the directory, not mcprack.
The "Forgot your password?" link on the login page only works if outbound
SMTP is configured in /etc/mcprack/env β otherwise it just tells the user
to ask an admin. To enable it:
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USERNAME=notifications@example.com
SMTP_PASSWORD=...
SMTP_USE_TLS=true
SMTP_FROM=mcprack <noreply@example.com>
Reset links are signed (SECRET_KEY) and expire after PASSWORD_RESET_MAX_AGE_SECONDS
(default 3600). Only local accounts with an email on file get a reset link;
the response is identical either way so the endpoint can't be used to
enumerate which usernames/emails exist.
Set DEMO_MODE=true in /etc/mcprack/env for a public-facing instance (like the
live demo).
Everything works normally β browsing the catalog, selecting servers, downloading
configs, and viewing a registered server's configuration (Admin β Servers β
edit) β except actually registering, editing, or deleting one, which is
disabled outright (the edit page renders read-only instead). That's a deliberate restriction, not a
missing feature: a server's command is executed as-is whenever any user
connects to it, so letting an untrusted public admin account change it would
be full code execution as the mcprack service account. Restart the service
after changing this.
mcprack ships hardened by default (CSRF protection, rate-limited login,
secure session cookies, security headers, a SECRET_KEY startup guard) and
debian/mcprack.service/debian/apache-mcprack.conf add systemd sandboxing
and a sample TLS-terminating reverse proxy β see debian/README.Debian's
"Exposing mcprack to the public internet" section for the concrete
configuration. Two risks remain that configuration alone doesn't close:
catalog.py's _make_proxy_token) are signed, time-limited bearer
tokens (24h) with no revocation list β a leaked token (e.g. a shared
config file) stays valid until it expires or an admin manually stops that
proxy instance from Admin β Proxy instances. Treat a downloaded client
config file as a credential.command runs as-is (see Demo mode above) as the mcprack OS account, so
a compromised admin account is equivalent to arbitrary code execution as
that account. Use strong admin passwords, rotate/delete the
installer-generated /etc/mcprack/admin-credentials password after first
login, and rely on the systemd hardening in debian/mcprack.service as
the containment boundary β it isn't a full per-command sandbox.Future improvement worth considering: optional OAuth/OIDC login for
users (e.g. via authlib) alongside the existing local/LDAP auth would
further reduce brute-force exposure and enable SSO. It's a larger, separate
feature (client registration, redirect flow, mapping external identities
onto the User model) and isn't implemented yet.
MCP-<server>-user-<username> in Vaultwarden, or locally encrypted if Vaultwarden isn't configured)bw-cli / Secure Note pattern used by the mcp_rack Ansible role/api/v1 API (servers, users, selections, overrides, client configs, audit log) for scripts/CI, authenticated with session cookies or personal API tokens, documented with an OpenAPI 3 specComponents:
secret_store.pyScenario 1: Team with shared MCP servers
Scenario 2: Enterprise deployment
Scenario 3: Remote teams
Scenario 4: Multi-client support
Local accounts are always available. LDAP/Active Directory is optional and disabled by default β enable it during installation if you want users to authenticate with AD credentials instead.
python3 -m venv .venv
. .venv/bin/activate
pip install -r requirements.txt -r requirements-dev.txt
cp .env.example .env # edit as needed
export $(grep -v '^#' .env | xargs)
flask db upgrade
flask create-admin
flask run
Open http://127.0.0.1:5000, log in with the admin account you just created, register a server under Servers, then visit the catalog to select it and download a config.
mcprack is also published on PyPI as a standalone package, for trying it out or running it outside of Debian/Ubuntu:
python3 -m venv .venv && . .venv/bin/activate
pip install mcprack
export SECRET_KEY=$(python3 -c "import secrets; print(secrets.token_urlsafe(32))")
mcprack db upgrade # bootstraps the schema on first run
mcprack create-admin
mcprack --host 0.0.0.0 --port 8913
The mcprack console script forwards to the same Flask management CLI as
the Debian package (mcprack user list, mcprack server show ..., etc. β
see "Command-line administration" below) and to the same /api/v1 REST API.
For production use, prefer the Debian package below β it wires up gunicorn,
systemd hardening, and dbconfig-common, none of which pip installs for you.
Besides the web UI, users, MCP catalog servers, and per-server credentials
can all be managed from the command line via cli.py, registered onto
mcprack's Flask CLI. Use the mcprack launcher (e.g. mcprack user list);
in development, invoking the same commands via flask <command> still works.
On a Debian install, the mcprack launcher script forwards any non-flag
first argument the same way, so
mcprack user list works too (see "Installation (Debian/Ubuntu package)"
below) β run it as root (e.g. sudo mcprack user list), since it needs to
read the real SECRET_KEY out of /etc/mcprack/env, which is 0640 root:mcprack and unreadable by other users. With no arguments, or
arguments starting with -, mcprack runs
the dev server instead (mcprack --host 0.0.0.0 --port 8913).
user β manage mcprack accounts:
mcprack user list
mcprack user create --username alice --admin # prompts for password if omitted
mcprack user passwd alice
mcprack user enable alice
mcprack user disable alice # blocks login without deleting
mcprack user promote alice
mcprack user demote alice
mcprack user delete alice --yes
server β manage MCP catalog servers:
mcprack server list
mcprack server show jenkins # non-secret config only
mcprack server edit jenkins --label "Jenkins CI" --disabled
mcprack server enable jenkins
mcprack server disable jenkins
mcprack server delete jenkins --yes # also clears stored secrets
# edit env/key metadata in one command
mcprack server edit multiflexi \
--set-env MULTIFLEXI_HOST=https://flexibee-dev.spoje.net:5434/api/VitexSoftware/MultiFlexi/1.0.0 \
--set-env MULTIFLEXI_USERNAME=admin \
--add-secret-key MULTIFLEXI_PASSWORD \
--add-required-key MULTIFLEXI_HOST
secret β manage a server's credential (secret env var) values:
mcprack secret backend # Vaultwarden or local encrypted fallback?
mcprack secret list jenkins
mcprack secret set jenkins JENKINS_TOKEN
mcprack secret unset jenkins JENKINS_TOKEN
Every command and option has --help (e.g. mcprack user create --help),
generated automatically, so it's not duplicated here in full β on a
Debian install, man mcprack also covers the full command reference.
user server, user override, user config, and
template β let an admin fully configure a non-technical user's
server access, config selection, individual credentials, and generated
config without that user ever logging in; see
doc/ADMIN-USER-CONFIG.md for the full guide
(web UI and CLI side by side).
This CLI does not cover the pip/npm/docker server installer subsystem
below, which stays UI-only; server show only surfaces a server's
install_method/installed_version read-only.
For debugging a per-user proxy URL end to end β rather than administering
the catalog β see mcprack-mcp-probe under "Diagnosing a proxy URL" below.
Besides the web UI and CLI, mcprack exposes a JSON REST API at /api/v1,
for scripts, CI pipelines, or any external integration. It's authenticated
the same way as the rest of the app β a session cookie from /login β plus
a new bearer-token option for callers that can't hold a browser session:
# session-cookie flow (browser-like)
curl -c cookies.txt -d "username=admin&password=..." http://localhost:5000/login
curl -b cookies.txt http://localhost:5000/api/v1/me
# personal API token flow (scripts/CI β mint once, reuse anywhere)
TOKEN=$(curl -b cookies.txt -X POST http://localhost:5000/api/v1/tokens \
-H 'Content-Type: application/json' -d '{"name":"ci-script"}' | jq -r .data.token)
curl -H "Authorization: Bearer $TOKEN" http://localhost:5000/api/v1/servers
curl -H "Authorization: Bearer $TOKEN" http://localhost:5000/api/v1/me/config/claude
The raw token is shown exactly once, at creation β only its hash is stored,
so it can't be recovered later. Revoke a token with
DELETE /api/v1/tokens/{id}, done as the same user who created it.
Coverage: /me (profile), /tokens (self-service API tokens),
/servers + /admin/servers (catalog, admin CRUD), /me/selections,
/me/overrides/{serverId}, /me/config/{client}, /admin/users, and
/audit-log (admin, same filters as the web UI). Every response is a JSON
envelope β {"data": ...} on success, {"error": {"code", "message"}} on
failure β and list endpoints are paginated (?page=, ?per_page=).
Admin-managed user configuration: everything an admin can do for a
non-technical user from Admin β Users or the mcprack CLI (see
doc/ADMIN-USER-CONFIG.md) is also available
as JSON API calls, so third-party applications can drive mcprack directly:
/admin/users/{userId}/selections,
/admin/users/{userId}/overrides/{serverId},
/admin/users/{userId}/config/{client}, /admin/templates (+
/admin/templates/{templateId}), and
/admin/users/{userId}/apply-template.
OpenAPI 3 spec: served live at /api/v1/openapi.json (source in
openapi/openapi.yaml), so it can be imported into Postman, fed to a
codegen tool (e.g. openapi-generator-cli generate -i http://localhost:5000/api/v1/openapi.json -g <language>-client to build a
client SDK), or validated with openapi-spec-validator. Every operation
declares a stable operationId specifically so codegen tools produce
clean method names.
SQLALCHEMY_DATABASE_URI defaults to SQLite but PostgreSQL
(postgresql+psycopg2://..., needs psycopg2-binary /
python3-psycopg2) and MySQL (mysql+pymysql://..., needs PyMySQL /
python3-pymysql) both work β see requirements-db.txt.
In the server edit form, each environment variable row has a "citlivΓ©" (sensitive) checkbox. Only rows marked sensitive β API keys, tokens, passwords, and the HTTP auth token key, which is always forced sensitive β ever leave mcprack's own database. Non-sensitive config (base URLs, regions, log levels, ...) stays directly in the DB and never touches Vaultwarden.
Sensitive values (a server's defaults, and any personal override a user
sets) live in Vaultwarden β mcprack talks to it the same way the mcp_rack
Ansible role does (bw-cli, Secure Notes named MCP-<server-name> /
MCP-<server-name>-user-<username>, plain KEY=value lines) β when
Vaultwarden is configured. If BW_SERVER is unset, the same sensitive
values are stored instead in a local Fernet-encrypted column, keyed off a
subkey derived from SECRET_KEY. Which backend is authoritative is decided
purely by configuration, never by live reachability β an unreachable but
configured Vaultwarden is a hard error, not a silent fallback. Admins can
move data between the two deliberately from Admin β Vaultwarden diagnostics
(e.g. after configuring Vaultwarden for the first time, or before a planned
Vaultwarden outage).
mcp_rack already uses, such as
https://vaultwarden-dev.proxy.spojenet.cz).Set these four (plus optionally BW_ITEM_PREFIX, default MCP-) in your
environment β .env for local development, /etc/mcprack/env in
production (see debian/README.Debian):
BW_SERVER=https://vaultwarden-dev.proxy.spojenet.cz
BW_CLIENTID=user.xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
BW_CLIENTSECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
BW_PASSWORD=your-vaultwarden-account-master-password
BW_BIN (default /usr/bin/bw) and BITWARDENCLI_APPDATA_DIR (where bw
keeps its login/session state) rarely need changing from their defaults.
Log in as an admin and open Vaultwarden diagnostics in the nav bar (or
go straight to /admin/vaultwarden/wizard). It checks each prerequisite in
order β bw installed, BW_SERVER set and reachable, API key valid,
master password unlocks the vault β and stops at the first failing step
with a specific fix, instead of just showing bw's raw error text. Re-run it
after editing /etc/mcprack/env and restarting mcprack.service so the
updated environment is loaded.
Once every step is green, admins can save server credentials (they're
written straight to a MCP-<server-name> Secure Note) and users can
download/view configs (which read those notes back and merge in any
personal override).
mcprack keeps an append-only audit trail so a problem (a broken server, a credential issue, an unexpected proxy) can be traced back to which MCP server and which user caused it. It is always on β there is no opt-in/opt-out flag, since this is a security feature, not an optional convenience.
Each event records: timestamp (UTC), the acting user (if any β some events
are system-initiated, e.g. idle proxy cleanup), the MCP server involved, the
action, whether it succeeded, a short error code/message on failure, the
client's source IP/hostname, how long it took, and a request_id UUID that
correlates multiple events from a single incoming request (e.g. a
credential lookup followed by a proxy start).
Events currently logged:
login / login_failed β every login attempt (auth.py)config_download β a user downloading their client config (catalog.py)credential_access β a server's secrets being resolved from Vaultwarden
(vaultwarden.py) β logs that access happened, never the valuesproxy_start / proxy_stop β a per-user FastMCP proxy instance starting,
being stopped by an admin, or reaped for being idle (user_proxy.py)admin_change β admin actions: server/user create/edit/delete, and audit
archival runs themselves (admin.py, flask audit-archive)Request or response bodies, tool call arguments/results, and credential
values are never written to the audit log β only the fact that an access or
call happened. error_message is always a short, fixed string, never raw
request content.
Admins can browse the trail at Admin β Audit Log
(/admin/audit-log), filterable by server, user, time window, and
errors-only. Click the π on a row to see every event sharing its
request_id. There is no way to edit or delete individual entries from the
UI β the table is append-only by design.
AUDIT_RETENTION_DAYS (default 90) controls how long entries are kept.
Nothing deletes automatically β run the CLI command periodically (e.g. from
cron) to export old entries and then purge them:
flask audit-archive # archive entries older than AUDIT_RETENTION_DAYS, as JSON
flask audit-archive --format csv # export as CSV instead
flask audit-archive --days 30 # override the retention window for this run
flask audit-archive --output /path/to.json
flask audit-archive --dry-run # just report how many entries would be archived
The export always happens before anything is deleted, and the archival run
itself creates one more admin_change audit entry (who/when ran it, how
many rows were purged) β so even the cleanup of old rows leaves a trace.
mcprack can export distributed traces and metrics over OTLP to any
self-hosted OpenTelemetry Collector (Grafana Alloy, the vanilla OTEL
Collector, Jaeger, Tempo, ...). Like the audit log, this is about an
intranet deployment being able to see what's happening β but unlike the
audit log, it is opt-in: with OTEL_ENABLED unset/false, telemetry.py
is a complete no-op (no spans, no metrics, no network calls), and none of
the opentelemetry-* packages even need to be installed.
mcprack (Flask + SQLAlchemy + FastMCP proxy)
β OTLP (grpc or http/protobuf)
βΌ
OTEL Collector / Grafana Alloy
β
ββββΆ Jaeger / Tempo (traces)
ββββΆ Prometheus / Loki (metrics)
Install the optional dependencies (pip install -r requirements-otel.txt,
or the python3-opentelemetry-* Debian packages β see debian/control),
then set:
| Variable | Default | Notes |
|---|---|---|
OTEL_ENABLED |
false |
Master switch. Everything below is ignored while this is off. |
OTEL_EXPORTER_OTLP_ENDPOINT |
(empty) | Base URL of the Collector, e.g. http://10.11.56.226:4318. |
OTEL_SERVICE_NAME |
mcprack |
service.name resource attribute. |
OTEL_EXPORTER_OTLP_PROTOCOL |
http/protobuf |
See table below. |
OTEL_TRACES_SAMPLER |
parentbased_always_on |
Passed through as a resource/env hint; not itself validated. |
OTEL_TRACE_UI_URL_TEMPLATE |
(empty) | URL template for the "View trace" link on an audit log entry's detail page β see below. |
OTEL_EXPORTER_OTLP_PROTOCOL values| Value | Supported? | Notes |
|---|---|---|
http/protobuf |
β (default) | Uses opentelemetry-exporter-otlp-proto-http, typically port 4318. |
grpc |
β | Uses opentelemetry-exporter-otlp-proto-grpc, typically port 4317. Whatever port is in OTEL_EXPORTER_OTLP_ENDPOINT is used as-is β mcprack never rewrites it. |
http/json |
β | Not supported. The Python OTLP SDK, unlike the JS SDK, ships no JSON-over-HTTP exporter β only protobuf-over-HTTP and gRPC exist. Setting this logs a startup warning and mcprack transparently falls back to http/protobuf; it never fails to start over this. |
| anything else | β | Same fallback-with-warning behavior as http/json. |
Check Admin β OTEL Diagnostics (/admin/otel/wizard) to see the
effective (post-fallback) protocol, whether a fallback happened, and to
fire a one-off test span/metric at the configured endpoint.
If multiple mcprack instances share one Collector, that test signal tags
itself server_name="otel-diagnostics@<hostname>" (plus explicit
mcprack.diagnostics.hostname / mcprack.diagnostics.service_name
attributes) so you can tell which instance it came from directly from the
signal itself β it deliberately does not rely on the Collector correctly
propagating OTLP resource attributes (service.name, host.name), since
a prometheus exporter without resource_to_telemetry_conversion enabled
never turns service.name into a per-series label, and a Collector's own
resourcedetection processor stamps host.name with its own hostname,
not the origin's.
.env β testing against the shared 10.11.56.226 stackOTEL_ENABLED=true
OTEL_SERVICE_NAME=mcprack-demo
OTEL_EXPORTER_OTLP_ENDPOINT=http://10.11.56.226:4318
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
Grafana itself is at http://10.11.56.226:3000; 4318 is the OTLP
http/protobuf receiver in front of it (Alloy or an OTEL Collector β verify
with your platform team which signals it currently forwards to
Loki/Tempo).
docker-compose.otel.yml brings up a local Jaeger (with its own built-in
OTLP receiver) so you can see traces end to end without touching the shared
instance:
docker compose -f docker-compose.otel.yml up -d
# then run mcprack with OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318
# Jaeger UI: http://localhost:16686
Add --profile collector --profile grafana to also get a Collector, Prometheus,
and a provisioned local Grafana (http://localhost:3001, anonymous
viewer access) for trying out the metrics dashboard below β see the
comments at the top of docker-compose.otel.yml for the exact ports and
OTEL_EXPORTER_OTLP_ENDPOINT to use in that mode.
grafana/mcprack-otel-dashboard.json is a ready-to-import dashboard
covering the three custom metrics above: MCP call rate (success vs error)
and p50/p95/p99 duration by server, config downloads by client type, and a
trace search panel scoped to mcprack's service name. Import it via
Dashboards β Import in any Grafana that has a Prometheus datasource
fed by the OTEL Collector's metrics exporter (e.g. the shared
http://10.11.56.226:3000 instance, or the local one from
--profile grafana above) β you'll be prompted to pick that datasource
for ${DS_PROMETHEUS} (and, optionally, a Tempo datasource for
${DS_TRACES}, if your Collector forwards traces there).
To jump straight from one specific audit log entry to its trace instead of
browsing the dashboard, use the "View trace" button described below β
that's the audit.request_id-correlated path, not this dashboard.
Automatic: every Flask request/response (via
opentelemetry-instrumentation-flask) and every SQLAlchemy query (via
opentelemetry-instrumentation-sqlalchemy).
Custom spans around the operations that actually matter for troubleshooting an MCP problem:
mcp.server.call β a request forwarded through a user's per-user proxy to
a stdio MCP server (server_name, transport attributes)vaultwarden.lookup β a Vaultwarden Secure Note read (secret_name_hash
attribute only β a one-way hash of the item name, never the name or the
secret value)catalog.generate_config β building a client config for download/viewmcp.proxy.start / mcp.proxy.stop β per-user FastMCP proxy lifecycleMetrics (OTEL Meter, not just traces):
mcp_server_calls_total (counter; labels server_name, result)mcp_server_call_duration_seconds (histogram; label server_name)config_downloads_total (counter; label client_type)Credential and audit content never reaches a span or metric β the same rule the audit log follows (see above). Span/metric attributes are limited to identifiers (server names, hashed secret names), outcomes, and durations; request/response bodies and secret values are never attached.
The audit trail already generates a per-request UUID (audit.py,
flask.g) to tie together everything that happened during one incoming
request. mcprack does not overwrite OpenTelemetry's own (SDK-generated)
trace ID with that UUID β instead, every custom span tags itself with the
UUID as the audit.request_id span attribute, so you look a trace up
by that attribute rather than by trace ID. On the audit log's request
detail page (/admin/audit-log/request/<id>), the "View trace" button
builds its link from OTEL_TRACE_UI_URL_TEMPLATE, substituting
{request_id} (and the identical {trace_id} alias, for templates that
read more naturally that way) β point it at a saved search/query in your
trace UI that filters on that attribute, e.g. for Grafana/Tempo using
TraceQL:
OTEL_TRACE_UI_URL_TEMPLATE="http://10.11.56.226:3000/explore?left=%7B%22datasource%22:%22tempo%22,%22queries%22:%5B%7B%22query%22:%22%7B%20.audit.request_id%20%3D%20%5C%22{request_id}%5C%22%20%7D%22%7D%5D%7D"
Leave it unset and the button just says "not configured" instead of linking anywhere.
pytest
mcprack is published as a .deb on the VitexSoftware APT repository.
sudo apt install lsb-release wget
sudo wget -O /usr/share/keyrings/vitexsoftware.gpg https://repo.vitexsoftware.com/KEY.gpg
sudo wget -O /etc/apt/sources.list.d/vitexsoftware.sources https://repo.vitexsoftware.com/vitexsoftware.sources
sudo apt update
sudo apt install mcprack
The postinst wizard sets up the database via dbconfig-common (SQLite,
PostgreSQL, or MySQL), writes /etc/mcprack/env, runs migrations, and
creates an initial admin account (random password saved to
/etc/mcprack/admin-credentials). See debian/README.Debian for the
full post-install configuration steps, and repo.vitexsoftware.com
for other available packages.
For unattended/repeatable deployments, mcprack can also be installed and
managed with the vitexsoftware.mcprack
Ansible collection instead of running apt install by hand.
It provides:
vitexsoftware.mcprack.app β a role that installs the .deb
package above, configures /etc/mcprack/env (database, LDAP,
Vaultwarden, DEMO_MODE/STRICT_SERVER_PERMISSIONS, ...), optionally
fronts it with an nginx reverse proxy and UFW firewall rules, waits for
/health, and provisions local admin accounts and catalog servers.mcprack_server, mcprack_user, mcprack_secret, mcprack_template,
mcprack_template_apply, and the mcprack_user_access /
mcprack_user_selection / mcprack_user_override / mcprack_user_config
family for per-user server access, pre-selection, credential overrides,
and generated client configs.ansible-galaxy collection install vitexsoftware.mcprack
- hosts: mcprack_servers
become: true
roles:
- role: vitexsoftware.mcprack.app
vars:
mcprack_domain: mcprack.example.com
mcprack_admin_users: [alice]
mcprack_catalog_servers:
- name: netbox
label: NetBox
transport: http
url: "https://netbox.example.com/mcp"
category: infra
Note this collection manages the mcprack application itself β it does not install or run individual MCP servers' own runtime (e.g. as systemd services); see Remote access to stdio MCP servers below for that.
See debian/ β builds a .deb following the same conventions as other
VitexSoftware Flask apps (e.g. abraflexi-yearend): system Python packages,
no virtualenv, gunicorn + systemd. See debian/README.Debian for the
post-install configuration steps.
Users always connect remotely β mcprack never assumes a user's Claude/Copilot
client runs on the same machine as mcprack itself. So a stdio server is never
handed to a client as a raw local spawn command: the downloaded config always
points at a per-user proxy URL (/proxy/mcp/<token>/<server_id>), served by
mcprack.service itself β no separate proxy service to install or enable.
The first time a user's client connects to that URL, mcprack spawns a
dedicated fastmcp subprocess for that (user, server) pair on demand,
resolving that user's credentials at that exact moment. Each user gets their
own isolated instance, even for a server several users have selected at once;
idle instances are cleaned up automatically after 15 minutes. This needs
python3-fastmcp installed (apt install python3-fastmcp) β see Admin β
Proxy instances for a live list of what's running.
Two env vars in /etc/mcprack/env tune the cold-spawn path, if the defaults
don't fit your hardware:
MCP_PROXY_HANDSHAKE_TIMEOUT (default 15 seconds) β how long to wait for
a freshly spawned fastmcp process to bind its port and answer a real MCP
initialize request before giving up and reporting it as broken. Cold
starts (fresh Python interpreter, heavy imports) commonly take several
seconds; if your proxy instances routinely fail with "failed its startup
handshake" even though the registered command is fine, raise this.MCP_PROXY_LOCK_WAIT_TIMEOUT (default 22 seconds) β how long a second
concurrent request for the same (user, server) pair waits for the first
one's spawn to finish before giving up, instead of racing it. Should stay
comfortably above MCP_PROXY_HANDSHAKE_TIMEOUT plus spawn overhead, and
under gunicorn's own 30s worker timeout (--timeout 30 in
debian/mcprack.service).Separately from those two, each already-established request forwarded to a
running proxy instance has a fixed 10s ceiling (_PROXY_REQUEST_TIMEOUT in
mcprack/catalog.py), so one slow backend can't tie up a gunicorn worker.
It is not currently configurable, which is worth knowing when a backend does
genuinely slow work per call (a large IMAP mailbox scan, say): the client
sees Upstream MCP server did not respond within 10s even though the
backend would have answered, which can mask the backend's own error.
See debian/README.Debian for more detail.
mcprack-mcp-probeWhen a client reports a broken server it usually says only something like
Transport creation error: Unexpected status code: 502 Bad Gateway. That
hides the useful part: mcprack sends a JSON-RPC error body along with its
502s, naming the actual reason (a failed startup handshake, credentials that
couldn't be resolved, missing required config) β and MCP clients discard it.
mcprack-mcp-probe performs the same handshake a real client does
(initialize, session negotiation, notifications/initialized) and reports
which step failed and why:
# does this URL work at all?
mcprack-mcp-probe 'https://mcprack.example.com/proxy/mcp/<token>/7'
# what does the server actually expose?
mcprack-mcp-probe --tools 'https://mcprack.example.com/proxy/mcp/<token>/7'
# exercise a real tool call end to end
mcprack-mcp-probe --call list_dir --args '{"path": "."}' URL
# anything else in the protocol
mcprack-mcp-probe --method resources/list URL
Take the URL straight from a downloaded client config (/view/claude,
/view/copilot): it already carries that user's signed token, so the probe
exercises exactly what that user's client would do, with their credentials
and permissions.
The token embedded in the URL is redacted from all output, so probe results
can be pasted into a bug report as-is. Exit status is 0 only when every
step succeeded, so it also works as a smoke test from monitoring or CI. It's
stdlib-only, so it runs on any mcprack host with nothing extra installed.
The default per-request timeout is 120s β deliberately well above
MCP_PROXY_HANDSHAKE_TIMEOUT, so a slow cold spawn shows up as a real
result instead of as a probe timeout; override it with --timeout.
Reading the outcome:
initialize: OK but the tool call returns a JSON-RPC error or
isError: true β mcprack and the spawned backend are both fine; the
failure is inside that backend server's own logic.FAIL initialize: server returned a JSON-RPC error β the transport works,
but mcprack could not start or reach the backend. The same message is in
Admin β Audit log as a proxy_start failure, and the backend's own output
is at /var/lib/mcprack/user-proxies/u<user>-s<server>.log.FAIL initialize: HTTP 403 β the token expired (they're valid 24h), or
the user no longer has that server selected or permitted. Download a fresh
config.FAIL initialize: HTTP 404 β the server was disabled, deleted, or is no
longer a stdio server (a server with its own URL isn't proxied).FAIL initialize: could not connect β nothing answered at that host, so
the problem is DNS, TLS, or the reverse proxy in front of mcprack, not
mcprack itself.To verify a fresh mcprack install actually spawns, proxies, and tears down
stdio MCP servers correctly, register
mcp-server-filesystem
as a test server. It has no external dependencies or credentials to wire
up β just python3-fastmcp β and is read-only by default (FS_READONLY=true),
so it's safe to point at any directory:
sudo apt install mcp-server-filesystem # from repo.vitexsoftware.com
Register it with:
mcp-server-filesystemFS_ROOT=/path/to/sandbox (optional; defaults to the spawned
process's cwd), FS_READONLY=false only if you also want to exercise
the write/delete/move/copy toolsOnce registered, download its config from the catalog and confirm your
client can list its tools (list_dir, read_file, stat, exists,
glob_search, plus the gated mutating ones) through the per-user proxy β
this exercises the same cold-spawn/handshake/proxy path as any other
stdio server, without needing a real backend to configure first.
Admin β Install (/admin/install) lets an admin install a new MCP server
straight from PyPI, npm, or a Docker image β no shell access to the host
needed. This is on top of, not instead of, autodetection and manual
registration: once installed, a server behaves exactly like any other
registered McpServer row (same catalog, health checks, per-user proxy).
/var/lib/mcprack/installs/<name>/venv β never a shared venv, so
one server's dependencies can never conflict with another's.-g) install directory
under /var/lib/mcprack/installs/<name>/npm β same isolation rationale.docker run --rm -i <image>
once per user session β there is no persistent container to manage. The
only "install" step is docker pull plus a sanity check.For pip and npm you must supply the exact expected binary/entry-point
name the package installs (e.g. a package named foo-mcp-server might
install a console script called foo-mcp) β mcprack verifies this exact
name exists after install and fails loudly, with the full install log
shown, rather than guessing at an alternate name. Guessing wrong here has
been a real, documented problem for this app's autodetection in the past
(see detection.py), so the installer deliberately never does it.
Because pip install/npm install/docker pull can each take well over
the 30s gunicorn worker timeout, installs always run as a detached
background process; the Install page polls install status every few
seconds until it reaches success or failed.
Env vars configured at install time work exactly like a manually
registered server's β non-secret values in mcprack's own database, secret
ones in Vaultwarden (or the encrypted local fallback). For Docker servers
specifically, docker run does not forward the host process's
environment into the container on its own β mcprack computes -e <NAME>
flags for each configured env var automatically, every time the container
starts, so adding/removing an env var later via the credentials form takes
effect on the next session with nothing else to keep in sync.
Uninstalling a pip/npm-installed server stops any running per-user proxy
first, then deletes its venv/npm directory from disk. Docker images
pulled during install are left in place on uninstall (they may be shared
or reused, and Docker has its own garbage collection via
docker image prune).
Docker security note: running the docker CLI from the unprivileged
mcprack service account requires that account to be a member of the
host's docker group (or otherwise have access to the Docker socket) β
this is well-known to be equivalent to root access, since a docker group
member can trivially bind-mount the host filesystem. mcprack never grants
this itself; it only detects whether it's already usable (docker ps
succeeds) and shows a clear diagnostic with the exact manual command
otherwise:
sudo usermod -aG docker mcprack && systemctl restart mcprack
Treat this as a deliberate, opt-in tradeoff for hosts that specifically want Docker-based MCP servers β not something to enable by default.
python3-venv, npm, nodejs, and docker.io are Suggests-level
dependencies in debian/control (not hard Depends), since not every
mcprack deployment needs every installer backend.
Separately from the Admin β Install runtime installer above, an MCP server
that ships as its own Debian package can self-register in mcprack at
apt install time, with no manual admin step: the server's own repo
builds a small companion package (mcprack-mcp-server-<name>) whose
postinst/prerm call mcprack server create/mcprack server delete.
See doc/PACKAGING-MCP-SERVERS.md for the
full convention: package naming, the postinst/prerm template, the
mcprack server create CLI reference, a pybuild pitfall that silently ships
an empty package if missed, and how to rename an existing package into this
convention with a clean apt upgrade path.
Debian-packaged MCP servers that self-register in mcprack via the
mcprack-mcp-server-<name> companion-package convention above
(Suggests: in debian/control):
mcp-server-netbox pending on GitHub)This package may not be indexed in our database yet. Please try again later or check the package repository directly.