Model Context Protocol gateway & proxy - unify REST, MCP, and A2A with federation, virtual servers, retries, security, and an optional admin UI.
ContextForge MCP Gateway is a feature-rich gateway, proxy and MCP Registry that federates MCP and REST services - unifying discovery, auth, rate-limiting, observability, virtual servers, multi-transport protocols, and an optional Admin UI into one clean endpoint for your AI clients. It runs as a fully compliant MCP server, deployable via PyPI or Docker, and scales to multi-cluster environments on Kubernetes with Redis-backed federation and caching.
ContextForge MCP Gateway is a production-grade gateway, registry, and proxy that sits in front of any Model Context Protocol (MCP) server or REST API-exposing a unified endpoint for all your AI clients.
It supports:
For a list of upcoming features, check out the ContextForge MCP Gateway Roadmap
π Gateway Layer with Protocol Flexibility2025-03-26
)Adapts REST APIs into tools with:
MCP Gateway is published on PyPI as mcp-contextforge-gateway
.
# 1οΈβ£ Isolated env + install from pypi mkdir mcpgateway && cd mcpgateway python3 -m venv .venv && source .venv/bin/activate pip install --upgrade pip pip install mcp-contextforge-gateway # 2οΈβ£ Launch on all interfaces with custom creds & secret key # Enable the Admin API endpoints (true/false) - disabled by default export MCPGATEWAY_UI_ENABLED=true export MCPGATEWAY_ADMIN_API_ENABLED=true BASIC_AUTH_PASSWORD=pass JWT_SECRET_KEY=my-test-key \ mcpgateway --host 0.0.0.0 --port 4444 & # admin/pass # 3οΈβ£ Generate a bearer token & smoke-test the API export MCPGATEWAY_BEARER_TOKEN=$(python3 -m mcpgateway.utils.create_jwt_token \ --username admin --exp 10080 --secret my-test-key) curl -s -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ http://127.0.0.1:4444/version | jqWindows (PowerShell) quick-start
# 1οΈβ£ Isolated env + install from PyPI mkdir mcpgateway ; cd mcpgateway python3 -m venv .venv ; .\.venv\Scripts\Activate.ps1 pip install --upgrade pip pip install mcp-contextforge-gateway # 2οΈβ£ Environment variables (session-only) $Env:MCPGATEWAY_UI_ENABLED = "true" $Env:MCPGATEWAY_ADMIN_API_ENABLED = "true" $Env:BASIC_AUTH_PASSWORD = "changeme" # admin/changeme $Env:JWT_SECRET_KEY = "my-test-key" # 3οΈβ£ Launch the gateway mcpgateway.exe --host 0.0.0.0 --port 4444 # Optional: background it # Start-Process -FilePath "mcpgateway.exe" -ArgumentList "--host 0.0.0.0 --port 4444" # 4οΈβ£ Bearer token and smoke-test $Env:MCPGATEWAY_BEARER_TOKEN = python3 -m mcpgateway.utils.create_jwt_token ` --username admin --exp 10080 --secret my-test-key curl -s -H "Authorization: Bearer $Env:MCPGATEWAY_BEARER_TOKEN" ` http://127.0.0.1:4444/version | jqMore configuration
Copy .env.example to .env
and tweak any of the settings (or use them as env variables).
# 1οΈβ£ Spin up the sample GO MCP time server using mcpgateway.translate & docker python3 -m mcpgateway.translate \ --stdio "docker run --rm -i -p 8888:8080 ghcr.io/ibm/fast-time-server:latest -transport=stdio" \ --port 8003 # Or using the official mcp-server-git using uvx: pip install uv # to install uvx, if not already installed python3 -m mcpgateway.translate --stdio "uvx mcp-server-git" --port 9000 # Alternative: running the local binary # cd mcp-servers/go/fast-time-server; make build # python3 -m mcpgateway.translate --stdio "./dist/fast-time-server -transport=stdio" --port 8002 # 2οΈβ£ Register it with the gateway curl -s -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name":"fast_time","url":"http://localhost:9000/sse"}' \ http://localhost:4444/gateways # 3οΈβ£ Verify tool catalog curl -s -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" http://localhost:4444/tools | jq # 4οΈβ£ Create a *virtual server* bundling those tools. Use the ID of tools from the tool catalog (Step #3) and pass them in the associatedTools list. curl -s -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name":"time_server","description":"Fast time tools","associatedTools":[<ID_OF_TOOLS>]}' \ http://localhost:4444/servers | jq # Example curl curl -s -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" -H "Content-Type: application/json" -d '{"name":"time_server","description":"Fast time tools","associatedTools":["6018ca46d32a4ac6b4c054c13a1726a2"]}' \ http://localhost:4444/servers | jq # 5οΈβ£ List servers (should now include the UUID of the newly created virtual server) curl -s -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" http://localhost:4444/servers | jq # 6οΈβ£ Client SSE endpoint. Inspect it interactively with the MCP Inspector CLI (or use any MCP client) npx -y @modelcontextprotocol/inspector # Transport Type: SSE, URL: http://localhost:4444/servers/UUID_OF_SERVER_1/sse, Header Name: "Authorization", Bearer Tokenπ§ Using the stdio wrapper (mcpgateway-wrapper)
export MCP_AUTH_TOKEN=$MCPGATEWAY_BEARER_TOKEN export MCP_SERVER_CATALOG_URLS=http://localhost:4444/servers/UUID_OF_SERVER_1 python3 -m mcpgateway.wrapper # Ctrl-C to exit
You can also run it with uv
or inside Docker/Podman - see the Containers section above.
In MCP Inspector, define MCP_AUTH_TOKEN
and MCP_SERVER_CATALOG_URLS
env variables, and select python3
as the Command, and -m mcpgateway.wrapper
as Arguments.
echo $PWD/.venv/bin/python3 # Using the Python3 full path ensures you have a working venv export MCP_SERVER_CATALOG_URLS='http://localhost:4444/servers/UUID_OF_SERVER_1' export MCP_AUTH_TOKEN=${MCPGATEWAY_BEARER_TOKEN} npx -y @modelcontextprotocol/inspector
When using a MCP Client such as Claude with stdio:
{ "mcpServers": { "mcpgateway-wrapper": { "command": "python", "args": ["-m", "mcpgateway.wrapper"], "env": { "MCP_AUTH_TOKEN": "your-token-here", "MCP_SERVER_CATALOG_URLS": "http://localhost:4444/servers/UUID_OF_SERVER_1", "MCP_TOOL_CALL_TIMEOUT": "120" } } } }
Use the official OCI image from GHCR with Docker or Podman.
docker run -d --name mcpgateway \ -p 4444:4444 \ -e MCPGATEWAY_UI_ENABLED=true \ -e MCPGATEWAY_ADMIN_API_ENABLED=true \ -e HOST=0.0.0.0 \ -e JWT_SECRET_KEY=my-test-key \ -e BASIC_AUTH_USER=admin \ -e BASIC_AUTH_PASSWORD=changeme \ -e AUTH_REQUIRED=true \ -e DATABASE_URL=sqlite:///./mcp.db \ ghcr.io/ibm/mcp-context-forge:0.3.1 # Tail logs (Ctrl+C to quit) docker logs -f mcpgateway # Generating an API key docker run --rm -it ghcr.io/ibm/mcp-context-forge:0.3.1 \ python3 -m mcpgateway.utils.create_jwt_token --username admin --exp 0 --secret my-test-key
Browse to http://localhost:4444/admin (user admin
/ pass changeme
).
mkdir -p $(pwd)/data touch $(pwd)/data/mcp.db sudo chown -R :docker $(pwd)/data chmod 777 $(pwd)/data docker run -d --name mcpgateway \ --restart unless-stopped \ -p 4444:4444 \ -v $(pwd)/data:/data \ -e MCPGATEWAY_UI_ENABLED=true \ -e MCPGATEWAY_ADMIN_API_ENABLED=true \ -e DATABASE_URL=sqlite:////data/mcp.db \ -e HOST=0.0.0.0 \ -e JWT_SECRET_KEY=my-test-key \ -e BASIC_AUTH_USER=admin \ -e BASIC_AUTH_PASSWORD=changeme \ ghcr.io/ibm/mcp-context-forge:0.3.1
SQLite now lives on the host at ./data/mcp.db
.
mkdir -p $(pwd)/data touch $(pwd)/data/mcp.db sudo chown -R :docker $(pwd)/data chmod 777 $(pwd)/data docker run -d --name mcpgateway \ --network=host \ -e MCPGATEWAY_UI_ENABLED=true \ -e MCPGATEWAY_ADMIN_API_ENABLED=true \ -e HOST=0.0.0.0 \ -e PORT=4444 \ -e DATABASE_URL=sqlite:////data/mcp.db \ -v $(pwd)/data:/data \ ghcr.io/ibm/mcp-context-forge:0.3.1
Using --network=host
allows Docker to access the local network, allowing you to add MCP servers running on your host. See Docker Host network driver documentation for more details.
podman run -d --name mcpgateway \ -p 4444:4444 \ -e HOST=0.0.0.0 \ -e DATABASE_URL=sqlite:///./mcp.db \ ghcr.io/ibm/mcp-context-forge:0.3.1
mkdir -p $(pwd)/data touch $(pwd)/data/mcp.db sudo chown -R :docker $(pwd)/data chmod 777 $(pwd)/data podman run -d --name mcpgateway \ --restart=on-failure \ -p 4444:4444 \ -v $(pwd)/data:/data \ -e DATABASE_URL=sqlite:////data/mcp.db \ ghcr.io/ibm/mcp-context-forge:0.3.13 - Host networking (rootless)
mkdir -p $(pwd)/data touch $(pwd)/data/mcp.db sudo chown -R :docker $(pwd)/data chmod 777 $(pwd)/data podman run -d --name mcpgateway \ --network=host \ -v $(pwd)/data:/data \ -e DATABASE_URL=sqlite:////data/mcp.db \ ghcr.io/ibm/mcp-context-forge:0.3.1βοΈ Docker/Podman tips
.env files - Put all the -e FOO=
lines into a file and replace them with --env-file .env
. See the provided .env.example for reference.
Pinned tags - Use an explicit version (e.g. v0.3.1
) instead of latest
for reproducible builds.
JWT tokens - Generate one in the running container:
docker exec mcpgateway python3 -m mcpgateway.utils.create_jwt_token -u admin -e 10080 --secret my-test-key
Upgrades - Stop, remove, and rerun with the same -v $(pwd)/data:/data
mount; your DB and config stay intact.
curl -s -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ http://localhost:4444/health | jq curl -s -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ http://localhost:4444/tools | jq curl -s -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ http://localhost:4444/version | jqπ§ Running the MCP Gateway stdio wrapper
The mcpgateway.wrapper
lets you connect to the gateway over stdio while keeping JWT authentication. You should run this from the MCP Client. The example below is just for testing.
# Set environment variables export MCPGATEWAY_BEARER_TOKEN=$(python3 -m mcpgateway.utils.create_jwt_token --username admin --exp 10080 --secret my-test-key) export MCP_AUTH_TOKEN=${MCPGATEWAY_BEARER_TOKEN} export MCP_SERVER_CATALOG_URLS='http://localhost:4444/servers/UUID_OF_SERVER_1' export MCP_TOOL_CALL_TIMEOUT=120 export MCP_WRAPPER_LOG_LEVEL=DEBUG # or OFF to disable logging docker run --rm -i \ -e MCP_AUTH_TOKEN=$MCPGATEWAY_BEARER_TOKEN \ -e MCP_SERVER_CATALOG_URLS=http://host.docker.internal:4444/servers/UUID_OF_SERVER_1 \ -e MCP_TOOL_CALL_TIMEOUT=120 \ -e MCP_WRAPPER_LOG_LEVEL=DEBUG \ ghcr.io/ibm/mcp-context-forge:0.3.1 \ python3 -m mcpgateway.wrapperTesting
mcpgateway.wrapper
by hand:
Because the wrapper speaks JSON-RPC over stdin/stdout, you can interact with it using nothing more than a terminal or pipes.
# Start the MCP Gateway Wrapper export MCP_AUTH_TOKEN=${MCPGATEWAY_BEARER_TOKEN} export MCP_SERVER_CATALOG_URLS=http://localhost:4444/servers/YOUR_SERVER_UUID python3 -m mcpgateway.wrapperInitialize the protocol
# Initialize the protocol {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"demo","version":"0.0.1"}}} # Then after the reply: {"jsonrpc":"2.0","method":"notifications/initialized","params":{}} # Get prompts {"jsonrpc":"2.0","id":4,"method":"prompts/list"} {"jsonrpc":"2.0","id":5,"method":"prompts/get","params":{"name":"greeting","arguments":{"user":"Bob"}}} # Get resources {"jsonrpc":"2.0","id":6,"method":"resources/list"} {"jsonrpc":"2.0","id":7,"method":"resources/read","params":{"uri":"https://example.com/some.txt"}} # Get / call tools {"jsonrpc":"2.0","id":2,"method":"tools/list"} {"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"get_system_time","arguments":{"timezone":"Europe/Dublin"}}}Expected responses from mcpgateway.wrapper
{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2025-03-26","capabilities":{"experimental":{},"prompts":{"listChanged":false},"resources":{"subscribe":false,"listChanged":false},"tools":{"listChanged":false}},"serverInfo":{"name":"mcpgateway-wrapper","version":"0.3.0"}}} # When there's no tools {"jsonrpc":"2.0","id":2,"result":{"tools":[]}} # After you add some tools and create a virtual server {"jsonrpc":"2.0","id":2,"result":{"tools":[{"annotations":{"readOnlyHint":false,"destructiveHint":true,"idempotentHint":false,"openWorldHint":true},"description":"Convert time between different timezones","inputSchema":{"properties":{"source_timezone":{"description":"Source IANA timezone name","type":"string"},"target_timezone":{"description":"Target IANA timezone name","type":"string"},"time":{"description":"Time to convert in RFC3339 format or common formats like '2006-01-02 15:04:05'","type":"string"}},"required":["time","source_timezone","target_timezone"],"type":"object"},"name":"convert_time"},{"annotations":{"readOnlyHint":false,"destructiveHint":true,"idempotentHint":false,"openWorldHint":true},"description":"Get current system time in specified timezone","inputSchema":{"properties":{"timezone":{"description":"IANA timezone name (e.g., 'America/New_York', 'Europe/London'). Defaults to UTC","type":"string"}},"type":"object"},"name":"get_system_time"}]}} # Running the time tool: {"jsonrpc":"2.0","id":3,"result":{"content":[{"type":"text","text":"2025-07-09T00:09:45+01:00"}]}}π§© Running from an MCP Client (
mcpgateway.wrapper
)
The mcpgateway.wrapper
exposes everything your Gateway knows about over stdio, so any MCP client that can't (or shouldn't) open an authenticated SSE stream still gets full tool-calling power.
π³ Docker / PodmanRemember to substitute your real Gateway URL (and server ID) for
http://localhost:4444/servers/UUID_OF_SERVER_1
. When inside Docker/Podman, that often becomeshttp://host.docker.internal:4444/servers/UUID_OF_SERVER_1
(macOS/Windows) or the gateway container's hostname (Linux).
docker run -i --rm \ --network=host \ -e MCP_SERVER_CATALOG_URLS=http://localhost:4444/servers/UUID_OF_SERVER_1 \ -e MCP_AUTH_TOKEN=${MCPGATEWAY_BEARER_TOKEN} \ -e MCP_TOOL_CALL_TIMEOUT=120 \ ghcr.io/ibm/mcp-context-forge:0.3.1 \ python3 -m mcpgateway.wrapperπ¦ pipx (one-liner install & run)
# Install gateway package in its own isolated venv pipx install --include-deps mcp-contextforge-gateway # Run the stdio wrapper MCP_AUTH_TOKEN=${MCPGATEWAY_BEARER_TOKEN} \ MCP_SERVER_CATALOG_URLS=http://localhost:4444/servers/UUID_OF_SERVER_1 \ python3 -m mcpgateway.wrapper # Alternatively with uv uv run --directory . -m mcpgateway.wrapper
Claude Desktop JSON (uses the host Python that pipx injected):
{ "mcpServers": { "mcpgateway-wrapper": { "command": "python3", "args": ["-m", "mcpgateway.wrapper"], "env": { "MCP_AUTH_TOKEN": "<your-token>", "MCP_SERVER_CATALOG_URLS": "http://localhost:4444/servers/UUID_OF_SERVER_1", "MCP_TOOL_CALL_TIMEOUT": "120" } } } }β‘ uv / uvx (light-speed venvs) 1 - Install
uv
(uvx
is an alias it provides)
# (a) official one-liner curl -Ls https://astral.sh/uv/install.sh | sh # (b) or via pipx pipx install uv2 - Create an on-the-spot venv & run the wrapper
# Create venv in ~/.venv/mcpgateway (or current dir if you prefer) uv venv ~/.venv/mcpgateway source ~/.venv/mcpgateway/bin/activate # Install the gateway package using uv uv pip install mcp-contextforge-gateway # Launch wrapper MCP_AUTH_TOKEN=${MCPGATEWAY_BEARER_TOKEN} \ MCP_SERVER_CATALOG_URLS=http://localhost:4444/servers/UUID_OF_SERVER_1 \ uv run --directory . -m mcpgateway.wrapper # Use this just for testing, as the Client will run the uv commandClaude Desktop JSON (runs through uvx)
{ "mcpServers": { "mcpgateway-wrapper": { "command": "uvx", "args": [ "run", "--", "python", "-m", "mcpgateway.wrapper" ], "env": { "MCP_AUTH_TOKEN": "<your-token>", "MCP_SERVER_CATALOG_URLS": "http://localhost:4444/servers/UUID_OF_SERVER_1" } } }π Using with Claude Desktop (or any GUI MCP client)
File βΈ Settings βΈ Developer βΈ Edit Config
mcpgateway-wrapper
started and listed your tools.Need help? See:
Spin up a fully-loaded dev environment (Python 3.11, Docker/Podman CLI, all project dependencies) in just two clicks.
π Prerequisitesgit clone https://github.com/ibm/mcp-context-forge.git cd mcp-context-forge code .
VS Code will detect the .devcontainer
and prompt: "Reopen in Container" or manually run: Ctrl/Cmd β§ P β Dev Containers: Reopen in Container
The container build will:
make install-dev
to pull all dependenciesYou'll land in /workspace
ready to develop.
Common tasks inside the container:
# Start dev server (hot reload) make dev # http://localhost:4444 # Run tests & linters make test make lint
Optional:
make bash
- drop into an interactive shellmake clean
- clear build artefacts & caches.devcontainer/devcontainer.json
)No local Docker? Use Codespaces:
What it does:
.venv
in your home folder ~/.venv/mcpgateway
For development, you can use:
make install-dev # Install development dependencies, ex: linters and test harness make lint # optional: run style checks (ruff, mypy, etc.)Containerised (self-signed TLS)
You can use docker or podman, ex:
make podman # build production image make podman-run-ssl # run at https://localhost:4444 # or listen on port 4444 on your host directly, adds --network=host to podman make podman-run-ssl-host
curl -k -sX GET \ -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ https://localhost:4444/tools | jq
You should receive []
until you register a tool.
make venv install # create .venv + install deps make serve # gunicorn on :4444
uv venv && source .venv/bin/activate uv pip install -e '.[dev]' # IMPORTANT: in zsh, quote to disable glob expansion!
python3 -m venv .venv && source .venv/bin/activate pip install -e ".[dev]"Optional (PostgreSQL adapter)
You can configure the gateway with SQLite, PostgreSQL (or any other compatible database) in .env.
When using PostgreSQL, you need to install psycopg2
driver.
uv pip install psycopg2-binary # dev convenience # or uv pip install psycopg2 # production build
docker run --name mcp-postgres \ -e POSTGRES_USER=postgres \ -e POSTGRES_PASSWORD=mysecretpassword \ -e POSTGRES_DB=mcp \ -p 5432:5432 -d postgres
A make compose-up
target is provided along with a docker-compose.yml file to make this process simpler.
.env
or env vars)
β οΈ If any required
.env
variable is missing or invalid, the gateway will fail fast at startup with a validation error via Pydantic.
You can get started by copying the provided .env.example to .env
and making the necessary edits to fit your environment.
APP_NAME
Gateway / OpenAPI title MCP Gateway
string HOST
Bind address for the app 0.0.0.0
IPv4/IPv6 PORT
Port the server listens on 4444
1-65535 DATABASE_URL
SQLAlchemy connection URL sqlite:///./mcp.db
any SQLAlchemy dialect APP_ROOT_PATH
Subpath prefix for app (e.g. /gateway
) (empty) string TEMPLATES_DIR
Path to Jinja2 templates mcpgateway/templates
path STATIC_DIR
Path to static files mcpgateway/static
path
Setting Description Default Optionsπ‘ Use
APP_ROOT_PATH=/foo
if reverse-proxying under a subpath likehttps://host.com/foo/
.
BASIC_AUTH_USER
Username for Admin UI login and HTTP Basic authentication admin
string BASIC_AUTH_PASSWORD
Password for Admin UI login and HTTP Basic authentication changeme
string AUTH_REQUIRED
Require authentication for all API routes true
bool JWT_SECRET_KEY
Secret key used to sign JWT tokens for API access my-test-key
string JWT_ALGORITHM
Algorithm used to sign the JWTs (HS256
is default, HMAC-based) HS256
PyJWT algs TOKEN_EXPIRY
Expiry of generated JWTs in minutes 10080
int > 0 AUTH_ENCRYPTION_SECRET
Passphrase used to derive AES key for encrypting tool auth headers my-test-salt
string
Setting Description Default Optionsπ
BASIC_AUTH_USER
/PASSWORD
are used for:
- Logging into the web-based Admin UI
- Accessing APIs via Basic Auth (
curl -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN"
)π
JWT_SECRET_KEY
is used to:
Sign JSON Web Tokens (
Authorization: Bearer <token>
)Generate tokens via:
export MCPGATEWAY_BEARER_TOKEN=$(python3 -m mcpgateway.utils.create_jwt_token --username admin --exp 0 --secret my-test-key) echo $MCPGATEWAY_BEARER_TOKENTokens allow non-interactive API clients to authenticate securely.
π§ͺ Set
AUTH_REQUIRED=false
during development if you want to disable all authentication (e.g. for local testing or open APIs) or clients that don't support SSE authentication. In production, you should use the SSE to stdiomcpgateway-wrapper
for such tools that don't support authenticated SSE, while still ensuring the gateway uses authentication.π
AUTH_ENCRYPTION_SECRET
is used to encrypt and decrypt tool authentication credentials (auth_value
). You must set the same value across environments to decode previously stored encrypted auth values. Recommended: use a long, random string.
MCPGATEWAY_UI_ENABLED
Enable the interactive Admin dashboard true
bool MCPGATEWAY_ADMIN_API_ENABLED
Enable API endpoints for admin ops true
bool
Setting Description Default Optionsπ₯οΈ Set both to
false
to disable management UI and APIs in production.
SKIP_SSL_VERIFY
Skip upstream TLS verification false
bool ALLOWED_ORIGINS
CORS allow-list ["http://localhost","http://localhost:4444"]
JSON array CORS_ENABLED
Enable CORS true
bool
Setting Description Default OptionsNote: do not quote the ALLOWED_ORIGINS values, this needs to be valid JSON, such as:
ALLOWED_ORIGINS=["http://localhost", "http://localhost:4444"]
LOG_LEVEL
Minimum log level INFO
DEBUG
...CRITICAL
LOG_FORMAT
Log format json
json
, text
LOG_FILE
Log output file (none) path or empty Setting Description Default Options TRANSPORT_TYPE
Enabled transports all
http
,ws
,sse
,stdio
,all
WEBSOCKET_PING_INTERVAL
WebSocket ping (secs) 30
int > 0 SSE_RETRY_TIMEOUT
SSE retry timeout (ms) 5000
int > 0 USE_STATEFUL_SESSIONS
streamable http config false
bool JSON_RESPONSE_ENABLED
json/sse streams (streamable http) true
bool Setting Description Default Options FEDERATION_ENABLED
Enable federation true
bool FEDERATION_DISCOVERY
Auto-discover peers false
bool FEDERATION_PEERS
Comma-sep peer URLs []
JSON array FEDERATION_TIMEOUT
Gateway timeout (secs) 30
int > 0 FEDERATION_SYNC_INTERVAL
Sync interval (secs) 300
int > 0 Setting Description Default Options RESOURCE_CACHE_SIZE
LRU cache size 1000
int > 0 RESOURCE_CACHE_TTL
Cache TTL (seconds) 3600
int > 0 MAX_RESOURCE_SIZE
Max resource bytes 10485760
int > 0 ALLOWED_MIME_TYPES
Acceptable MIME types see code JSON array Setting Description Default Options TOOL_TIMEOUT
Tool invocation timeout (secs) 60
int > 0 MAX_TOOL_RETRIES
Max retry attempts 3
int β₯ 0 TOOL_RATE_LIMIT
Tool calls per minute 100
int > 0 TOOL_CONCURRENT_LIMIT
Concurrent tool invocations 10
int > 0 Setting Description Default Options PROMPT_CACHE_SIZE
Cached prompt templates 100
int > 0 MAX_PROMPT_SIZE
Max prompt template size (bytes) 102400
int > 0 PROMPT_RENDER_TIMEOUT
Jinja render timeout (secs) 10
int > 0 Setting Description Default Options HEALTH_CHECK_INTERVAL
Health poll interval (secs) 60
int > 0 HEALTH_CHECK_TIMEOUT
Health request timeout (secs) 10
int > 0 UNHEALTHY_THRESHOLD
Fail-count before peer deactivation, 3
int > 0 Set to -1 if deactivation is not needed. Setting Description Default Options DB_POOL_SIZE
. SQLAlchemy connection pool size 200
int > 0 DB_MAX_OVERFLOW
. Extra connections beyond pool 10
int β₯ 0 DB_POOL_TIMEOUT
. Wait for connection (secs) 30
int > 0 DB_POOL_RECYCLE
. Recycle connections (secs) 3600
int > 0 DB_MAX_RETRIES
. Max Retry Attempts 3
int > 0 DB_RETRY_INTERVAL_MS
Retry Interval (ms) 2000
int > 0 Setting Description Default Options CACHE_TYPE
Backend (memory
/redis
) memory
none
, memory
,redis
REDIS_URL
Redis connection URL (none) string or empty CACHE_PREFIX
Key prefix mcpgw:
string REDIS_MAX_RETRIES
Max Retry Attempts 3
int > 0 REDIS_RETRY_INTERVAL_MS
Retry Interval (ms) 2000
int > 0
Setting Description Default Optionsπ§
none
disables caching entirely. Usememory
for dev,database
for persistence, orredis
for distributed caching.
DEV_MODE
Enable dev mode false
bool RELOAD
Auto-reload on changes false
bool DEBUG
Debug logging false
bool
make serve # Run production Gunicorn server on make serve-ssl # Run Gunicorn behind HTTPS on :4444 (uses ./certs)
To run the development (uvicorn) server:
make dev # or ./run.sh --reload --log debug --workers 2
run.sh
is a wrapper arounduvicorn
that loads.env
, supports reload, and passes arguments to the server.
Key flags:
Flag Purpose Example-e, --env FILE
load env-file --env prod.env
-H, --host
bind address --host 127.0.0.1
-p, --port
listen port --port 8080
-w, --workers
gunicorn workers --workers 4
-r, --reload
auto-reload --reload
uvicorn mcpgateway.main:app --host 0.0.0.0 --port 4444 --workers 4
# Generate a JWT token using JWT_SECRET_KEY and export it as MCPGATEWAY_BEARER_TOKEN # Note that the module needs to be installed. If running locally use: export MCPGATEWAY_BEARER_TOKEN=$(JWT_SECRET_KEY=my-test-key python3 -m mcpgateway.utils.create_jwt_token) # Use the JWT token in an API call curl -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" http://localhost:4444/toolsβοΈ AWS / Azure / OpenShift
Deployment details can be found in the GitHub Pages.
βοΈ IBM Cloud Code Engine DeploymentThis project supports deployment to IBM Cloud Code Engine using the ibmcloud CLI and the IBM Container Registry.
βοΈ IBM Cloud Code Engine Deploymentmake ibmcloud-cli-install
to install)Create a .env
file (or export the variables in your shell). The first block is required; the second provides tunable defaults you can override:
# ββ Required βββββββββββββββββββββββββββββββββββββββββββββ IBMCLOUD_REGION=us-south IBMCLOUD_RESOURCE_GROUP=default IBMCLOUD_PROJECT=my-codeengine-project IBMCLOUD_CODE_ENGINE_APP=mcpgateway IBMCLOUD_IMAGE_NAME=us.icr.io/myspace/mcpgateway:latest IBMCLOUD_IMG_PROD=mcpgateway/mcpgateway IBMCLOUD_API_KEY=your_api_key_here # Optional - omit to use interactive `ibmcloud login --sso` # ββ Optional overrides (sensible defaults provided) ββββββ IBMCLOUD_CPU=1 # vCPUs for the app IBMCLOUD_MEMORY=4G # Memory allocation IBMCLOUD_REGISTRY_SECRET=my-regcred # Name of the Container Registry secret
Target Purposeβ Quick check:
make ibmcloud-check-env
make ibmcloud-cli-install
Install IBM Cloud CLI and required plugins make ibmcloud-login
Log in to IBM Cloud (API key or SSO) make ibmcloud-ce-login
Select the Code Engine project & region make ibmcloud-tag
Tag the local container image make ibmcloud-push
Push the image to IBM Container Registry make ibmcloud-deploy
Create or update the Code Engine application (uses CPU/memory/secret) make ibmcloud-ce-status
Show current deployment status make ibmcloud-ce-logs
Stream logs from the running app make ibmcloud-ce-rm
Delete the Code Engine application
make ibmcloud-check-env make ibmcloud-cli-install make ibmcloud-login make ibmcloud-ce-login make ibmcloud-tag make ibmcloud-push make ibmcloud-deploy make ibmcloud-ce-status make ibmcloud-ce-logs
You can test the API endpoints through curl, or Swagger UI, and check detailed documentation on ReDoc:
Generate an API Bearer token, and test the various API endpoints.
π Authentication & Health Checks# Generate a bearer token using the configured secret key (use the same as your .env) export MCPGATEWAY_BEARER_TOKEN=$(python3 -m mcpgateway.utils.create_jwt_token -u admin --secret my-test-key) echo ${MCPGATEWAY_BEARER_TOKEN} # Quickly confirm that authentication works and the gateway is healthy curl -s -k -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" https://localhost:4444/health # {"status":"healthy"} # Quickly confirm the gateway version & DB connectivity curl -s -k -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" https://localhost:4444/version | jqπ§± Protocol APIs (MCP) /protocol
# Initialize MCP session curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "protocol_version":"2025-03-26", "capabilities":{}, "client_info":{"name":"MyClient","version":"1.0.0"} }' \ http://localhost:4444/protocol/initialize # Ping (JSON-RPC style) curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":1,"method":"ping"}' \ http://localhost:4444/protocol/ping # Completion for prompt/resource arguments (not implemented) curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "ref":{"type":"ref/prompt","name":"example_prompt"}, "argument":{"name":"topic","value":"py"} }' \ http://localhost:4444/protocol/completion/complete # Sampling (streaming) (not implemented) curl -N -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "messages":[{"role":"user","content":{"type":"text","text":"Hello"}}], "maxTokens":16 }' \ http://localhost:4444/protocol/sampling/createMessageπ§ JSON-RPC Utility /rpc
# Generic JSON-RPC calls (tools, gateways, roots, etc.) curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":1,"method":"list_tools"}' \ http://localhost:4444/rpc
Handles any method name: list_tools
, list_gateways
, prompts/get
, or invokes a tool if method matches a registered tool name .
# Register a new tool curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name":"clock_tool", "url":"http://localhost:9000/rpc", "description":"Returns current time", "input_schema":{ "type":"object", "properties":{"timezone":{"type":"string"}}, "required":[] } }' \ http://localhost:4444/tools # List tools curl -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" http://localhost:4444/tools # Get tool by ID curl -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" http://localhost:4444/tools/1 # Update tool curl -X PUT -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "description":"Updated desc" }' \ http://localhost:4444/tools/1 # Toggle active status curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ http://localhost:4444/tools/1/toggle?activate=false curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ http://localhost:4444/tools/1/toggle?activate=true # Delete tool curl -X DELETE -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" http://localhost:4444/tools/1π Gateway Management /gateways
# Register an MCP server as a new gateway provider curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name":"peer_gateway","url":"http://peer:4444"}' \ http://localhost:4444/gateways # List gateways curl -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" http://localhost:4444/gateways # Get gateway by ID curl -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" http://localhost:4444/gateways/1 # Update gateway curl -X PUT -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ -H "Content-Type: application/json" \ -d '{"description":"New description"}' \ http://localhost:4444/gateways/1 # Toggle active status curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ http://localhost:4444/gateways/1/toggle?activate=false # Delete gateway curl -X DELETE -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" http://localhost:4444/gateways/1π Resource Management /resources
# Register resource curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "uri":"config://app/settings", "name":"App Settings", "content":"key=value" }' \ http://localhost:4444/resources # List resources curl -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" http://localhost:4444/resources # Read a resource curl -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" http://localhost:4444/resources/config://app/settings # Update resource curl -X PUT -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ -H "Content-Type: application/json" \ -d '{"content":"new=value"}' \ http://localhost:4444/resources/config://app/settings # Delete resource curl -X DELETE -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" http://localhost:4444/resources/config://app/settings # Subscribe to updates (SSE) curl -N -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" http://localhost:4444/resources/subscribe/config://app/settingsπ Prompt Management /prompts
# Create prompt template curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name":"greet", "template":"Hello, {{ user }}!", "argument_schema":{ "type":"object", "properties":{"user":{"type":"string"}}, "required":["user"] } }' \ http://localhost:4444/prompts # List prompts curl -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" http://localhost:4444/prompts # Get prompt (with args) curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ -H "Content-Type: application/json" \ -d '{"user":"Alice"}' \ http://localhost:4444/prompts/greet # Get prompt (no args) curl -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" http://localhost:4444/prompts/greet # Update prompt curl -X PUT -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ -H "Content-Type: application/json" \ -d '{"template":"Hi, {{ user }}!"}' \ http://localhost:4444/prompts/greet # Toggle active curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ http://localhost:4444/prompts/5/toggle?activate=false # Delete prompt curl -X DELETE -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" http://localhost:4444/prompts/greetπ² Root Management /roots
# List roots curl -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" http://localhost:4444/roots # Add root curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ -H "Content-Type: application/json" \ -d '{"uri":"/data","name":"Data Root"}' \ http://localhost:4444/roots # Remove root curl -X DELETE -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" http://localhost:4444/roots/%2Fdata # Subscribe to root changes (SSE) curl -N -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" http://localhost:4444/roots/changesπ₯οΈ Server Management /servers
# List servers curl -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" http://localhost:4444/servers # Get server curl -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" http://localhost:4444/servers/UUID_OF_SERVER_1 # Create server curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name":"db","description":"Database","associatedTools": ["1","2","3"]}' \ http://localhost:4444/servers # Update server curl -X PUT -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ -H "Content-Type: application/json" \ -d '{"description":"Updated"}' \ http://localhost:4444/servers/UUID_OF_SERVER_1 # Toggle active curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \ http://localhost:4444/servers/UUID_OF_SERVER_1/toggle?activate=falseπ Metrics /metrics
# Get aggregated metrics curl -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" http://localhost:4444/metrics # Reset metrics (all or per-entity) curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" http://localhost:4444/metrics/reset curl -X POST -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" http://localhost:4444/metrics/reset?entity=tool&id=1π‘ Events & Health
# SSE: all events curl -N -H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" http://localhost:4444/events # WebSocket wscat -c ws://localhost:4444/ws \ -H "Authorization: Basic $(echo -n admin:changeme|base64)" # Health check curl http://localhost:4444/health
Full Swagger UI at /docs
.
uvicorn sample_tool.clock_tool:app --host 0.0.0.0 --port 9000
curl -X POST -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":1,"method":"get_time","params":{"timezone":"UTC"}}' \ http://localhost:9000/rpc
make test # Run unit tests make lint # Run lint toolsπ Directory and file structure for mcpgateway
# ββββββββββ CI / Quality & Meta-files ββββββββββ βββ .bumpversion.cfg # Automated semantic-version bumps βββ .coveragerc # Coverage.py settings βββ .darglint # Doc-string linter rules βββ .dockerignore # Context exclusions for image builds βββ .editorconfig # Consistent IDE / editor behaviour βββ .env # Local runtime variables (git-ignored) βββ .env.ce # IBM Code Engine runtime env (ignored) βββ .env.ce.example # Sample env for IBM Code Engine βββ .env.example # Generic sample env file βββ .env.gcr # Google Cloud Run runtime env (ignored) βββ .eslintrc.json # ESLint rules for JS / TS assets βββ .flake8 # Flake-8 configuration βββ .gitattributes # Git attributes (e.g. EOL normalisation) βββ .github # GitHub settings, CI/CD workflows & templates β βββ CODEOWNERS # Default reviewers β βββ workflows/ # Bandit, Docker, CodeQL, Python Package, Container Deployment, etc. βββ .gitignore # Git exclusion rules βββ .hadolint.yaml # Hadolint rules for Dockerfiles βββ .htmlhintrc # HTMLHint rules βββ .markdownlint.json # Markdown-lint rules βββ .pre-commit-config.yaml # Pre-commit hooks (ruff, black, mypy, ...) βββ .pycodestyle # PEP-8 checker settings βββ .pylintrc # Pylint configuration βββ .pyspelling.yml # Spell-checker dictionary & filters βββ .ruff.toml # Ruff linter / formatter settings βββ .spellcheck-en.txt # Extra dictionary entries βββ .stylelintrc.json # Stylelint rules for CSS βββ .travis.yml # Legacy Travis CI config (reference) βββ .whitesource # WhiteSource security-scanning config βββ .yamllint # yamllint ruleset # ββββββββββ Documentation & Guidance ββββββββββ βββ CHANGELOG.md # Version-by-version change log βββ CODE_OF_CONDUCT.md # Community behaviour guidelines βββ CONTRIBUTING.md # How to file issues & send PRs βββ DEVELOPING.md # Contributor workflows & style guide βββ LICENSE # Apache License 2.0 βββ README.md # Project overview & quick-start βββ SECURITY.md # Security policy & CVE disclosure process βββ TESTING.md # Testing strategy, fixtures & guidelines # ββββββββββ Containerisation & Runtime ββββββββββ βββ Containerfile # OCI image build (Docker / Podman) βββ Containerfile.lite # FROM scratch UBI-Micro production build βββ docker-compose.yml # Local multi-service stack βββ podman-compose-sonarqube.yaml # One-liner SonarQube stack βββ run-gunicorn.sh # Opinionated Gunicorn startup script βββ run.sh # Uvicorn shortcut with arg parsing # ββββββββββ Build / Packaging / Tooling ββββββββββ βββ MANIFEST.in # sdist inclusion rules βββ Makefile # Dev & deployment targets βββ package-lock.json # Deterministic npm lock-file βββ package.json # Front-end / docs tooling deps βββ pyproject.toml # Poetry / PDM config & lint rules βββ sonar-code.properties # SonarQube analysis settings βββ uv.lock # UV resolver lock-file # ββββββββββ Kubernetes & Helm Assets ββββββββββ βββ charts # Helm chart(s) for K8s / OpenShift β βββ mcp-stack # Umbrella chart β β βββ Chart.yaml # Chart metadata β β βββ templates/... # Manifest templates β β βββ values.yaml # Default values β βββ README.md # Install / upgrade guide βββ k8s # Raw (non-Helm) K8s manifests β βββ *.yaml # Deployment, Service, PVC resources # ββββββββββ Documentation Source ββββββββββ βββ docs # MkDocs site source β βββ base.yml # MkDocs "base" configuration snippet (do not modify) β βββ mkdocs.yml # Site configuration (requires base.yml) β βββ requirements.txt # Python dependencies for the MkDocs site β βββ Makefile # Make targets for building/serving the docs β βββ theme # Custom MkDocs theme assets β βββ logo.png # Logo for the documentation theme β βββ docs # Markdown documentation β βββ architecture/ # ADRs for the project β βββ articles/ # Long-form writeups β βββ blog/ # Blog posts β βββ deployment/ # Deployment guides (AWS, Azure, etc.) β βββ development/ # Development workflows & CI docs β βββ images/ # Diagrams & screenshots β βββ index.md # Top-level docs landing page β βββ manage/ # Management topics (backup, logging, tuning, upgrade) β βββ overview/ # Feature overviews & UI documentation β βββ security/ # Security guidance & policies β βββ testing/ # Testing strategy & fixtures β βββ using/ # User-facing usage guides (agents, clients, etc.) β βββ media/ # Social media, press coverage, videos & testimonials β β βββ press/ # Press articles and blog posts β β βββ social/ # Tweets, LinkedIn posts, YouTube embeds β β βββ testimonials/ # Customer quotes & community feedback β β βββ kit/ # Media kit & logos for bloggers & press βββ dictionary.dic # Custom dictionary for spell-checker (make spellcheck) # ββββββββββ Application & Libraries ββββββββββ βββ agent_runtimes # Configurable agentic frameworks converted to MCP Servers βββ mcpgateway # β main application package β βββ __init__.py # Package metadata & version constant β βββ admin.py # FastAPI routers for Admin UI β βββ cache β β βββ __init__.py β β βββ resource_cache.py # LRU+TTL cache implementation β β βββ session_registry.py # Session β cache mapping β βββ config.py # Pydantic settings loader β βββ db.py # SQLAlchemy models & engine setup β βββ federation β β βββ __init__.py β β βββ discovery.py # Peer-gateway discovery β β βββ forward.py # RPC forwarding β βββ handlers β β βββ __init__.py β β βββ sampling.py # Streaming sampling handler β βββ main.py # FastAPI app factory & startup events β βββ mcp.db # SQLite fixture for tests β βββ py.typed # PEP 561 marker (ships type hints) β βββ schemas.py # Shared Pydantic DTOs β βββ services β β βββ __init__.py β β βββ completion_service.py # Prompt / argument completion β β βββ gateway_service.py # Peer-gateway registry β β βββ logging_service.py # Central logging helpers β β βββ prompt_service.py # Prompt CRUD & rendering β β βββ resource_service.py # Resource registration & retrieval β β βββ root_service.py # File-system root registry β β βββ server_service.py # Server registry & monitoring β β βββ tool_service.py # Tool registry & invocation β βββ static β β βββ admin.css # Styles for Admin UI β β βββ admin.js # Behaviour for Admin UI β βββ templates β β βββ admin.html # HTMX/Alpine Admin UI template β βββ transports β β βββ __init__.py β β βββ base.py # Abstract transport interface β β βββ sse_transport.py # Server-Sent Events transport β β βββ stdio_transport.py # stdio transport for embedding β β βββ websocket_transport.py # WS transport with ping/pong β βββ models.py # Core enums / type aliases β βββ utils β β βββ create_jwt_token.py # CLI & library for JWT generation β β βββ services_auth.py # Service-to-service auth dependency β β βββ verify_credentials.py # Basic / JWT auth helpers β βββ validation β β βββ __init__.py β β βββ jsonrpc.py # JSON-RPC 2.0 validation β βββ version.py # Library version helper βββ mcpgateway-wrapper # Stdio client wrapper (PyPI) β βββ pyproject.toml β βββ README.md β βββ src/mcpgateway_wrapper/ β βββ __init__.py β βββ server.py # Wrapper entry-point βββ mcp-servers # Sample downstream MCP servers βββ mcp.db # Default SQLite DB (auto-created) βββ mcpgrid # Experimental grid client / PoC βββ os_deps.sh # Installs system-level deps for CI # ββββββββββ Tests & QA Assets ββββββββββ βββ test_readme.py # Guard: README stays in sync βββ tests β βββ conftest.py # Shared fixtures β βββ e2e/... # End-to-end scenarios β βββ hey/... # Load-test logs & helper script β βββ integration/... # API-level integration tests β βββ unit/... # Pure unit tests for business logic
This project offer the following Makefile targets. Type make
in the project root to show all targets.
π MCP CONTEXT FORGE (An enterprise-ready Model Context Protocol Gateway) π§ SYSTEM-LEVEL DEPENDENCIES (DEV BUILD ONLY) os-deps - Install Graphviz, Pandoc, Trivy, SCC used for dev docs generation and security scan π± VIRTUAL ENVIRONMENT & INSTALLATION venv - Create a fresh virtual environment with uv & friends activate - Activate the virtual environment in the current shell install - Install project into the venv install-dev - Install project (incl. dev deps) into the venv install-db - Install project (incl. postgres and redis) into venv update - Update all installed deps inside the venv check-env - Verify all required env vars in .env are present βΆοΈ SERVE & TESTING serve - Run production Gunicorn server on :4444 certs - Generate self-signed TLS cert & key in ./certs (won't overwrite) serve-ssl - Run Gunicorn behind HTTPS on :4444 (uses ./certs) dev - Run fast-reload dev server (uvicorn) run - Execute helper script ./run.sh test - Run unit tests with pytest test-curl - Smoke-test API endpoints with curl script pytest-examples - Run README / examples through pytest-examples clean - Remove caches, build artefacts, virtualenv, docs, certs, coverage, SBOM, etc. π COVERAGE & METRICS coverage - Run tests with coverage, emit md/HTML/XML + badge pip-licenses - Produce dependency license inventory (markdown) scc - Quick LoC/complexity snapshot with scc scc-report - Generate HTML LoC & per-file metrics with scc π DOCUMENTATION & SBOM docs - Build docs (graphviz + handsdown + images + SBOM) images - Generate architecture & dependency diagrams π LINTING & STATIC ANALYSIS lint - Run the full linting suite (see targets below) black - Reformat code with black autoflake - Remove unused imports / variables with autoflake isort - Organise & sort imports with isort flake8 - PEP-8 style & logical errors pylint - Pylint static analysis markdownlint - Lint Markdown files with markdownlint (requires markdownlint-cli) mypy - Static type-checking with mypy bandit - Security scan with bandit pydocstyle - Docstring style checker pycodestyle - Simple PEP-8 checker pre-commit - Run all configured pre-commit hooks ruff - Ruff linter + formatter ty - Ty type checker from astral pyright - Static type-checking with Pyright radon - Code complexity & maintainability metrics pyroma - Validate packaging metadata importchecker - Detect orphaned imports spellcheck - Spell-check the codebase fawltydeps - Detect undeclared / unused deps wily - Maintainability report pyre - Static analysis with Facebook Pyre depend - List dependencies in βrequirements format snakeviz - Profile & visualise with snakeviz pstats - Generate PNG call-graph from cProfile stats spellcheck-sort - Sort local spellcheck dictionary tox - Run tox across multi-Python versions sbom - Produce a CycloneDX SBOM and vulnerability scan pytype - Flow-sensitive type checker check-manifest - Verify sdist/wheel completeness yamllint - Lint YAML files (uses .yamllint) jsonlint - Validate every *.json file with jq (--exit-status) tomllint - Validate *.toml files with tomlcheck πΈοΈ WEBPAGE LINTERS & STATIC ANALYSIS (HTML/CSS/JS lint + security scans + formatting) install-web-linters - Install HTMLHint, Stylelint, ESLint, Retire.js & Prettier via npm lint-web - Run HTMLHint, Stylelint, ESLint, Retire.js and npm audit format-web - Format HTML, CSS & JS files with Prettier osv-install - Install/upgrade osv-scanner (Go) osv-scan-source - Scan source & lockfiles for CVEs osv-scan-image - Scan the built container image for CVEs osv-scan - Run all osv-scanner checks (source, image, licence) π‘ SONARQUBE ANALYSIS sonar-deps-podman - Install podman-compose + supporting tools sonar-deps-docker - Install docker-compose + supporting tools sonar-up-podman - Launch SonarQube with podman-compose sonar-up-docker - Launch SonarQube with docker-compose sonar-submit-docker - Run containerised Sonar Scanner CLI with Docker sonar-submit-podman - Run containerised Sonar Scanner CLI with Podman pysonar-scanner - Run scan with Python wrapper (pysonar-scanner) sonar-info - How to create a token & which env vars to export π‘οΈ SECURITY & PACKAGE SCANNING trivy - Scan container image for CVEs (HIGH/CRIT). Needs podman socket enabled grype-scan - Scan container for security audit and vulnerability scanning dockle - Lint the built container image via tarball (no daemon/socket needed) hadolint - Lint Containerfile/Dockerfile(s) with hadolint pip-audit - Audit Python dependencies for published CVEs π¦ DEPENDENCY MANAGEMENT deps-update - Run update-deps.py to update all dependencies in pyproject.toml and docs/requirements.txt containerfile-update - Update base image in Containerfile to latest tag π¦ PACKAGING & PUBLISHING dist - Clean-build wheel *and* sdist into ./dist wheel - Build wheel only sdist - Build source distribution only verify - Build + twine + check-manifest + pyroma (no upload) publish - Verify, then upload to PyPI (needs TWINE_* creds) π¦ PODMAN CONTAINER BUILD & RUN podman-dev - Build development container image podman - Build container image podman-prod - Build production container image (using ubi-micro β scratch). Not supported on macOS. podman-run - Run the container on HTTP (port 4444) podman-run-shell - Run the container on HTTP (port 4444) and start a shell podman-run-ssl - Run the container on HTTPS (port 4444, self-signed) podman-run-ssl-host - Run the container on HTTPS with --network=host (port 4444, self-signed) podman-stop - Stop & remove the container podman-test - Quick curl smoke-test against the container podman-logs - Follow container logs (βC to quit) podman-stats - Show container resource stats (if supported) podman-top - Show live top-level process info in container podman-shell - Open an interactive shell inside the Podman container π DOCKER BUILD & RUN docker-dev - Build development Docker image docker - Build production Docker image docker-prod - Build production container image (using ubi-micro β scratch). Not supported on macOS. docker-run - Run the container on HTTP (port 4444) docker-run-ssl - Run the container on HTTPS (port 4444, self-signed) docker-stop - Stop & remove the container docker-test - Quick curl smoke-test against the container docker-logs - Follow container logs (βC to quit) docker-stats - Show container resource usage stats (non-streaming) docker-top - Show top-level process info in Docker container docker-shell - Open an interactive shell inside the Docker container π οΈ COMPOSE STACK - Build / start / stop the multi-service stack compose-up - Bring the whole stack up (detached) compose-restart - Recreate changed containers, pulling / building as needed compose-build - Build (or rebuild) images defined in the compose file compose-pull - Pull the latest images only compose-logs - Tail logs from all services (Ctrl-C to exit) compose-ps - Show container status table compose-shell - Open an interactive shell in the "gateway" container compose-stop - Gracefully stop the stack (keep containers) compose-down - Stop & remove containers (keep named volumes) compose-rm - Remove *stopped* containers compose-clean - β¨ Down **and** delete named volumes (data-loss β ) βοΈ IBM CLOUD CODE ENGINE ibmcloud-check-env - Verify all required IBM Cloud env vars are set ibmcloud-cli-install - Auto-install IBM Cloud CLI + required plugins (OS auto-detected) ibmcloud-login - Login to IBM Cloud CLI using IBMCLOUD_API_KEY (--sso) ibmcloud-ce-login - Set Code Engine target project and region ibmcloud-list-containers - List deployed Code Engine apps ibmcloud-tag - Tag container image for IBM Container Registry ibmcloud-push - Push image to IBM Container Registry ibmcloud-deploy - Deploy (or update) container image in Code Engine ibmcloud-ce-logs - Stream logs for the deployed application ibmcloud-ce-status - Get deployment status ibmcloud-ce-rm - Delete the Code Engine application π§ͺ MINIKUBE LOCAL CLUSTER minikube-install - Install Minikube (macOS, Linux, or Windows via choco) helm-install - Install Helm CLI (macOS, Linux, or Windows) minikube-start - Start local Minikube cluster with Ingress + DNS + metrics-server minikube-stop - Stop the Minikube cluster minikube-delete - Delete the Minikube cluster minikube-image-load - Build and load ghcr.io/ibm/mcp-context-forge:latest into Minikube minikube-k8s-apply - Apply Kubernetes manifests from k8s/ minikube-status - Show status of Minikube and ingress pods π οΈ HELM CHART TASKS helm-lint - Lint the Helm chart (static analysis) helm-package - Package the chart into dist/ as mcp-stack-<ver>.tgz helm-deploy - Upgrade/Install chart into Minikube (profile mcpgw) helm-delete - Uninstall the chart release from Minikube π LOCAL PYPI SERVER local-pypi-install - Install pypiserver for local testing local-pypi-start - Start local PyPI server on :8084 (no auth) local-pypi-start-auth - Start local PyPI server with basic auth (admin/admin) local-pypi-stop - Stop local PyPI server local-pypi-upload - Upload existing package to local PyPI (no auth) local-pypi-upload-auth - Upload existing package to local PyPI (with auth) local-pypi-test - Install package from local PyPI local-pypi-clean - Full cycle: build β upload β install locally π LOCAL DEVPI SERVER devpi-install - Install devpi server and client devpi-init - Initialize devpi server (first time only) devpi-start - Start devpi server devpi-stop - Stop devpi server devpi-setup-user - Create user and dev index devpi-upload - Upload existing package to devpi devpi-test - Install package from devpi devpi-clean - Full cycle: build β upload β install locally devpi-status - Show devpi server status devpi-web - Open devpi web interfacePort publishing on WSL2 (rootless Podman & Docker Desktop)
# Inside your WSL distro ss -tlnp | grep 4444 # Use ss netstat -anp | grep 4444 # or netstat
Seeing :::4444 LISTEN rootlessport
is normal - the IPv6 wildcard socket (::
) also accepts IPv4 traffic when net.ipv6.bindv6only = 0
(default on Linux).
WSL 2's NAT layer rewrites only the IPv6 side of the dual-stack listener. From Windows, http://127.0.0.1:4444
(or Docker Desktop's "localhost") therefore times-out.
# Inside the WSL distro echo "wsl" | sudo tee /etc/containers/podman-machine systemctl --user restart podman.socket
ss
should now show 0.0.0.0:4444
instead of :::4444
, and the service becomes reachable from Windows and the LAN.
Docker Desktop adds a "WSL integration" switch per-distro. Turn it on for your distro, restart Docker Desktop, then restart the container:
docker restart mcpgatewayGateway starts but immediately exits ("Failed to read DATABASE_URL")
Copy .env.example
to .env
first:
Then edit DATABASE_URL
, JWT_SECRET_KEY
, BASIC_AUTH_PASSWORD
, etc. Missing or empty required vars cause a fast-fail at startup.
make lint
and fix any issues.make test
green and 100% coverage.A complete changelog can be found here: CHANGELOG.md
Licensed under the Apache License 2.0 - see LICENSE
Core Authors and MaintainersSpecial thanks to our contributors for helping us improve ContextForge MCP Gateway:
Star History and Project ActivityRetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4