Merge multiple instances of the same MCP server across environments into a single endpoint with a unified env parameter.
Project description
mcp-env-mux
Merge multiple instances of the same MCP server across environments into a single endpoint with a unified env parameter.
Problem
When you connect the same MCP server (e.g., Coralogix, Datadog) from multiple environments (prod, staging, dev), each instance exposes identical tool definitions. Your MCP client sees every tool duplicated per environment, wasting context window tokens and creating a confusing tool list.
Solution
mcp-env-mux sits in front of your MCP backends as a proxy. It discovers tools from each backend, merges identical definitions into a single tool with an injected env parameter, and routes calls to the correct backend based on the caller's env selection. One tool instead of N copies.
Quick Start
Without auth
# Install
pip install -e .
# Set your API keys as environment variables
export BACKEND_PROD_API_KEY="your-prod-key"
export BACKEND_STAGING_API_KEY="your-staging-key"
# Create a config file (see Configuration below)
# Validate your config and check for schema mismatches
mcp-env-mux --config config.json --test-schema
# Start the proxy
mcp-env-mux --config config.json
With auth enabled
# Optional: set Azure client secret via env var
export AZURE_CLIENT_SECRET="your-azure-client-secret"
# Start the proxy with an auth-enabled config
mcp-env-mux --config config.json
When the auth block is present in the config, the proxy enables JWT Bearer auth, OAuth login routes, RBAC enforcement for tool calls, and a UI for minting long-lived bot tokens.
The proxy also exposes operational HTTP endpoints:
/metrics(or your configured metrics path) for Prometheus metrics/healthzfor process liveness/readyzfor startup readiness
For Azure / Microsoft Entra ID setup from scratch, including both required redirect URIs (
/auth/callbackand/ui/callback), seeAZURE_SETUP.md.
Configuration
The config file is JSON with a required environments object and an optional auth object.
Header values and selected auth values support $VAR substitution -- any value matching $SOME_NAME is resolved from the OS environment at startup.
Basic config
{
"environments": {
"Prod": {
"description": "Production environment",
"url": "https://api.example.com/mcp",
"headers": {
"Authorization": "$PROD_API_KEY"
}
},
"Staging": {
"description": "Staging environment",
"url": "https://api-staging.example.com/mcp",
"headers": {
"Authorization": "$STAGING_API_KEY"
}
}
}
}
Auth-enabled config
{
"environments": {
"Prod": {
"description": "Production environment",
"url": "https://api.example.com/mcp",
"headers": {
"Authorization": "$PROD_API_KEY"
}
},
"Staging": {
"description": "Staging environment",
"url": "https://api-staging.example.com/mcp",
"headers": {
"Authorization": "$STAGING_API_KEY"
}
}
},
"auth": {
"azure": {
"tenant_id": "your-tenant-id",
"client_id": "your-client-id",
"client_secret": "$AZURE_CLIENT_SECRET"
},
"signing_key_file": ".keys/mcp-env-mux.pem",
"roles": {
"admin": {
"allowed_envs": {
"*": ["*"]
}
},
"staging-reader": {
"allowed_envs": {
"Staging": ["*"]
}
},
"prod-logs": {
"allowed_envs": {
"Prod": ["logs*"]
}
}
},
"token_minting_roles": ["admin"],
"token_max_expiry_days": 180
}
}
Fields
Top-level fields:
environments(required) -- Map of environment name to backend configuration.metrics(optional) -- Enables Prometheus metrics exposition and optional user-level call metrics.auth(optional) -- Enables authentication, OAuth routes, RBAC, and token minting UI. If omitted, the proxy remains unauthenticated for backward compatibility.
Environment fields:
url(required) -- The MCP backend's HTTP endpoint.description(required) -- Human-readable label shown in the merged tool'senvparameter description.headers(optional) -- HTTP headers sent to this backend. Use$ENV_VARfor secrets.
Metrics fields:
enabled(optional, defaulttrue) -- Enables metrics collection and exposition.path(optional, default/metrics) -- HTTP path exposing Prometheus metrics.user_level_metrics(optional, defaultfalse) -- Enables per-user/per-bot total tool-call counts using the principal identity as a label.
Metrics include request counts, success/error counts, latency, in-flight requests, and response-size histograms at both tool and environment level. Response-size metrics use lightweight best-effort estimation for common response types rather than full JSON serialization.
Auth fields:
azure(required whenauthis present) -- Azure AD OAuth client config.azure.tenant_id(required) -- Azure tenant ID.azure.client_id(required) -- Azure app client ID.azure.client_secret(required) -- Azure app client secret. Supports$ENV_VARsubstitution.signing_key_file(required) -- Path to the RSA private key used to sign local JWTs. Resolution order: if the file exists it is loaded; otherwise, ifMCP_ENV_MUX_SIGNING_KEY_PEMis set, that PEM content is written to this path and loaded; otherwise a new key is generated on first start and written here.roles(required) -- Map of role name to RBAC config.roles.<role>.allowed_envs-- Map of environment glob pattern to list of tool glob patterns.token_minting_roles(required) -- Roles allowed to access the token minting UI.token_max_expiry_days(optional, default180) -- Maximum bot token lifetime allowed by the UI.
How It Works
- Discovery -- Connects to each backend and calls
list_tools()to collect tool definitions. - Merge -- Groups tools by name across environments. For each group:
- Descriptions must match across environments (mismatch is an error).
- Parameter types must match (mismatch is an error).
- Tools available in only a subset of environments get a constrained
envenum. - Extra parameters present in some environments but not others are included as optional, with warnings.
- Proxy -- Builds a FastMCP server with one handler per merged tool. Each handler extracts the
envargument, strips parameters not supported by the target backend, and forwards the call. - Optional auth -- If
authis configured:- MCP requests require a valid JWT Bearer token.
- OAuth 2.1 routes are exposed for Azure AD login with PKCE.
- Tool calls are checked against role-based allowlists for environment and tool name.
- A web UI can mint long-lived bot tokens for approved users.
Horizontal scaling
The proxy runs FastMCP HTTP transport with stateless_http=True.
This matters for multi-replica deployments behind a load balancer:
- requests do not depend on replica-local MCP session affinity
list_tools()andcall_tool()can be served by any replica- per-user tool visibility is derived from the presented token and in-memory merged tool metadata, not from sticky server-side MCP session state
Each replica still performs backend discovery at startup and keeps its own backend client connections, so replicas should be started with the same config and auth/signing setup.
CLI Reference
mcp-env-mux [OPTIONS]
| Flag | Default | Description |
|---|---|---|
--config PATH |
(required) | Path to the JSON configuration file. |
--host HOST |
0.0.0.0 |
Host to bind the proxy server to. |
--port PORT |
8080 |
Port to bind the proxy server to. |
--test-schema |
off | Validate config and tool schemas, print diagnostics, then exit. Also validates auth config shape if present. |
Schema Validation (CI Mode)
Use --test-schema to check for configuration errors and tool schema mismatches without starting the server. This connects to all backends, runs the merge, and reports errors and warnings to stdout.
mcp-env-mux --config config.json --test-schema
Exit code 0 means all tools merged cleanly. Exit code 1 means there are merge errors (description or type mismatches). Integrate this into your CI pipeline to catch schema drift between environments.
Authentication and Authorization
Authentication is optional and fully config-driven.
What gets enabled
When the auth block is present:
/mcprequires a valid Bearer token./.well-known/oauth-protected-resourceis exposed for resource metadata./.well-known/oauth-authorization-serveris exposed for auth server metadata./auth/login,/auth/callback, and/auth/tokenimplement an Azure AD OAuth 2.1 PKCE flow for MCP/OAuth clients./ui/login,/ui/callback, and/ui/tokensimplement a browser-based login flow and token minting UI for human operators. The UI uses Azure authentication together with an app-controlled short-lived UI session cookie.
When the auth block is absent, requests pass through without auth.
RBAC model
RBAC is allowlist-only and role-based:
- Users can have multiple roles.
- Each role maps environment glob patterns to tool glob patterns.
- Access is allowed if any assigned role matches both the requested environment and tool.
- Unknown roles grant no access.
- There are no explicit deny rules.
Examples:
"*": ["*"]allows all tools in all environments."Prod": ["logs*"]allows only tools matchinglogs*inProd.
Token types
The auth module works with two token types:
- User tokens -- Azure-issued tokens used for interactive OAuth/MCP access.
- Bot tokens -- Longer-lived locally-signed tokens created via the
/ui/tokenspage by users with minting permission.
Both token types carry roles and are accepted by the MCP endpoint.
Key management
signing_key_file points to the RSA private key used to sign local JWTs.
Resolution order:
- If the file exists, it is loaded.
- Else if
MCP_ENV_MUX_SIGNING_KEY_PEMis set, that PEM content is written to the configured file path and loaded. - Else a new 2048-bit RSA key is generated and written automatically.
The server logs where it sourced the signing key from in all cases:
- existing file
MCP_ENV_MUX_SIGNING_KEY_PEM- newly generated key
When a new signing key is generated automatically, the server logs warning-level messages loudly so the event is visible to operators. The warning explicitly calls out that horizontal scaling and rolling restarts will not work correctly unless all replicas share the same signing key.
Protect this file appropriately in production. The private PEM is a secret. Prefer a mounted secret file or injecting MCP_ENV_MUX_SIGNING_KEY_PEM in environments where baking keys into images is undesirable.
OAuth Endpoints
When auth is enabled, the proxy exposes:
GET /.well-known/oauth-protected-resourceGET /.well-known/oauth-authorization-serverGET /auth/loginGET /auth/callbackPOST /auth/tokenGET /ui/loginGET /ui/callbackGET /ui/tokensPOST /ui/tokensGET /ui/logout
The OAuth flow uses Azure AD / Microsoft Entra ID as the identity provider.
Operational Endpoints
-
GET /healthz-- Liveness endpoint. Returns200when the process is up. -
GET /readyz-- Readiness endpoint. Returns200after successful startup and503while startup is incomplete. -
GET /metrics-- Prometheus metrics endpoint when metrics are enabled. -
/auth/*is the OAuth/API-facing surface used by MCP/OAuth clients. -
/ui/*is a browser-session-based flow used by humans to log in and mint bot tokens. -
/ui/loginand/ui/callbackuse Azure authentication with a dedicated/ui/callbackredirect URI and create an app-controlled UI session for the token-minting experience.
See AZURE_SETUP.md for the full Azure app-registration setup from scratch.
Development
# Install with dev dependencies
pip install -e ".[dev]"
# Run all tests
pytest tests/ -v --timeout=120
# Run unit tests only
pytest tests/ -v --ignore=tests/test_e2e.py --ignore=tests/test_auth_e2e.py
# Run core E2E tests only
pytest tests/test_e2e.py -v --timeout=120
# Run auth unit tests
pytest tests/test_auth_config.py tests/test_hybrid.py tests/test_ui_session.py -v
# Run auth E2E tests
pytest tests/test_auth_e2e.py -v --timeout=120
Requires Python >= 3.11.
Architecture
The package is organized into six top-level areas under src/mcp_env_mux/: cli, config, discovery, merge, proxy, and auth.
The main data flow is:
- CLI parses args.
- Config is loaded and environment variables are resolved.
- If auth is configured, signing keys are loaded/generated.
- Backends are discovered.
- Schemas are merged.
- The proxy server is built and started.
- If auth is enabled, OAuth routes, RBAC middleware, and token UI routes are registered.
For detailed module documentation, see src/mcp_env_mux/mcp_env_mux_docs.md, src/mcp_env_mux/auth/auth_docs.md, and the per-module *_docs.md files. For test documentation and coverage mapping, see tests/tests_docs.md and the auth-specific test docs in tests/test_auth_config_docs.md, tests/test_auth_e2e_docs.md, and tests/test_oauth_routes_docs.md.
Project Structure
mcp-env-mux/
pyproject.toml -- Package metadata, dependencies, CLI entrypoint
config.json -- Example configuration file
src/mcp_env_mux/
__init__.py -- Package init
cli.py -- CLI entrypoint and startup orchestration
config.py -- Config loading, validation, env var resolution
discovery.py -- Backend connection and tool discovery
merge.py -- Schema diffing and tool merging
proxy.py -- FastMCP server construction and call routing
auth/
keys.py -- RSA key loading/generation and public key extraction
tokens.py -- User and bot JWT creation
rbac.py -- Role-based allowlist checks
middleware.py -- RBAC enforcement on MCP tool calls
oauth.py -- Azure AD OAuth 2.1 PKCE endpoints
ui.py -- Bot token minting web UI
auth_docs.md -- Auth package overview
mcp_env_mux_docs.md -- Detailed package documentation
tests/
conftest.py -- Shared fixtures: mock backends, proxy lifecycle
test_config.py -- Unit tests for config loading and env var substitution
test_merge.py -- Unit tests for schema merging logic
test_proxy.py -- Unit tests for call routing and parameter stripping
test_e2e.py -- End-to-end tests with real backends and proxy
test_auth_config.py -- Unit tests for auth config parsing and validation
test_oauth_routes.py -- Unit tests for PKCE verification and OAuth route registration
test_auth_e2e.py -- End-to-end auth tests over HTTP
tests_docs.md -- Test suite documentation
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file mcp_env_mux-0.3.5.tar.gz.
File metadata
- Download URL: mcp_env_mux-0.3.5.tar.gz
- Upload date:
- Size: 138.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a4a8277f31bcac3db4906c310b374aac1b6c7066b1649f64611a39b351e9c7b5
|
|
| MD5 |
aa8195de9b3c2bbe44c09b64d3aeb5bc
|
|
| BLAKE2b-256 |
4f7167a2d746d6c16786ece6078d045a82938cc0de35f4bd45daedc4154a224d
|
File details
Details for the file mcp_env_mux-0.3.5-py3-none-any.whl.
File metadata
- Download URL: mcp_env_mux-0.3.5-py3-none-any.whl
- Upload date:
- Size: 52.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8f7e55ffd3c2bdef543b8ce82585b015c4b77e5f696ba1c4cd1214094ae64d27
|
|
| MD5 |
b82c7dd18d357b73b494543f74c402b0
|
|
| BLAKE2b-256 |
5c3fcd7c86590218b70d6e6e0230b928099967e011d647f98fc59e60ddf34961
|