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.
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.
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.
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 roleComponents:
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.
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.
.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.
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.
See debian/README.Debian for more detail.
This package may not be indexed in our database yet. Please try again later or check the package repository directly.