Forge MCP Gateway
AI-powered MCP tool routing hub — part of the Forge Space open developer workspace.
Self-hosted gateway using IBM Context Forge with AI-driven optimization, predictive scaling, ML monitoring, and enterprise security. Connect any MCP server, route intelligently, manage via admin UI.
One connection from Cursor (or other MCP clients) to the gateway; add upstream MCP servers via the Admin UI.
GitHub repo description (paste in Settings → General → Description, 350 char max):
Self-hosted Forge MCP gateway using IBM Context Forge. One Cursor (or MCP client) connection; add upstream servers via Admin UI. Docker, virtual servers, tool-router. MIT.
License: MIT
Prerequisites
- Docker
- Docker Compose V2 (
docker compose) or V1 (docker-compose)
Optional: Dev Container for one-click lint/test (shellcheck, ruff, pytest) without installing them on the host. Part of the Forge Space Ecosystem - Complete AI-powered development platform.
Quick start
cp .env.example .env
# Edit .env: set PLATFORM_ADMIN_EMAIL, PLATFORM_ADMIN_PASSWORD, JWT_SECRET_KEY, AUTH_ENCRYPTION_SECRET (min 32 chars each). Run: make generate-secrets
make start
Then run make register to register gateways and get the Cursor URL.
- Admin UI: http://localhost:4444/admin
- Stop:
make stop(or./start.sh stop)
Admin UI boot contract:
NEXT_PUBLIC_SUPABASE_URLmust be a validhttporhttpsURLNEXT_PUBLIC_SUPABASE_ANON_KEYmust be present- if either value is missing or invalid, the admin shell stays bootable and shows a configuration-required state instead of crashing at import time
- monitoring dashboard service rows are keyboard-operable and do not rely on clickable non-semantic containers
Wrapper Bridge (Recommended)
Use the wrapper bridge as the stable MCP client entrypoint:
{
"mcpServers": {
"forge-mcp-gateway": {
"command": "/absolute/path/to/forge-mcp-gateway/scripts/mcp-wrapper.sh",
"env": {
"MCP_CLIENT_SERVER_URL": "http://localhost:4444/servers/<UUID>/mcp"
},
"timeout": 120000
}
}
}
Get your configuration:
- Start gateway:
make start - Register servers:
make register(saves URL todata/.mcp-client-url) - Run
./scripts/setup-forge-space-mcp.shto configure your IDE mcp config safely- If
make registercannot producedata/.mcp-client-url(minimal mode), run:./scripts/setup-forge-space-mcp.sh --mcp-url http://host.docker.internal:4444/servers/<UUID>/mcp
- If
NPX Client Status
@forgespace/mcp-gateway-client is resolvable on npm. Wrapper-first setup remains the recommended
IDE path for homelab stability, while direct npx is available for smoke checks and manual use.
Maintainer release path:
- Confirm scope/token access:
npm whoami && npm access list packages @forgespace --jsonnpm access ...is advisory; definitive permission is validated by the publish step.
- Run publish workflow:
gh workflow run npm-release-core.yml -f publish=true -f npm_tag=latest - Verify package availability:
npm view @forgespace/mcp-gateway-client version - Verify CLI entrypoint through workflow logs:
- check
Verify published package resolvabilityinnpm-release-core.yml - the workflow runs a retried CLI smoke invocation after package resolvability succeeds.
- check
Default make start (or ./start.sh) starts the gateway and all local servers (e.g. sequential-thinking). Use make gateway-only (or ./start.sh gateway-only) for the gateway alone. Data is stored in ./data (SQLite). Add gateways in Admin UI or run make register after start; create a virtual server, attach tools, note its UUID.
Registering URL-based MCP servers
Servers that expose an HTTP/SSE URL can be added as gateways so one Cursor connection reaches them through Context Forge. Either add them in Admin UI (MCP Servers → Add New MCP Server or Gateway) or run once the gateway is up:
make register
(or ./scripts/register-gateways.sh). The command is idempotent: if a gateway name already exists (e.g. after restart with the same DB), it reports "OK name (already registered)" instead of failing. It waits up to 90s for the gateway to respond at /health (override with REGISTER_GATEWAY_MAX_WAIT). If the first URL fails (e.g. 127.0.0.1 on Docker Desktop), it retries with localhost or vice versa. If still unreachable, run docker compose ps gateway and docker compose logs gateway. If a gateway shows FAIL, the gateway could not initialize the remote URL (see Troubleshooting). Run REGISTER_VERBOSE=1 make register to see the full API response. For local SSE gateways (e.g. sqlite, desktop-commander, github), the script retries once after 15s on "Unable to connect" or "Unexpected error". If all local gateways fail, translate containers may still be starting (first run pulls npm packages): wait 30–60s and run again, or set REGISTER_WAIT_SECONDS=30 in .env or run make register-wait.
make register reads scripts/gateways.txt (one line per gateway: Name|URL or Name|URL|Transport) or the EXTRA_GATEWAYS env var (comma-separated). Default gateways.txt registers local servers up to snyk (sequential-thinking through snyk). sqlite, github, and Context7 are commented out (they often fail with "Unable to connect" or "Unexpected error"; uncomment after checking docker compose logs sqlite|github or add Context7/sqlite/github via Admin UI). .env.example sets REGISTER_WAIT_SECONDS=30 so translate containers are ready before registration. After make start, run make register (or make register-wait to force a 30s wait). After registering gateways, the script creates or updates virtual server(s) and prints Cursor URLs. Cursor (and some other MCP clients) can only handle about 60 tools per connection; if you register many gateways, a single virtual server with all tools can trigger a warning. To stay under the limit, use multiple virtual servers, each with a subset of gateways:
- With
config/virtual-servers.txt: One line per server:ServerName|gateway1,gateway2,.... The script creates or updates each named server with up to 60 tools from those gateways and prints one URL per server. Connect Cursor to one URL (e.g.cursor-defaultfor general dev,cursor-searchfor search/docs). See scripts/README.md and docs/AI_USAGE.md. - Single entry point (router): The cursor-router virtual server is the default for the wrapper. It exposes only the tool-router gateway (1–2 tools). Set
GATEWAY_JWTin.env(runmake jwtand paste the token; refresh periodically, e.g. weekly) so the router can call the gateway API. See docs/AI_USAGE.md. - Without
virtual-servers.txt: A single virtual server (namedefaultorREGISTER_VIRTUAL_SERVER_NAME) gets all tools; fine if you have few gateways. Example remote entries:
| Name | URL | Transport |
|---|---|---|
| Context7 | https://mcp.context7.com/mcp | Streamable HTTP |
| context-awesome | https://www.context-awesome.com/api/mcp | Streamable HTTP |
| prisma-remote | https://mcp.prisma.io/mcp | Streamable HTTP |
| cloudflare-observability | https://observability.mcp.cloudflare.com/mcp | Streamable HTTP |
| cloudflare-bindings | https://bindings.mcp.cloudflare.com/mcp | Streamable HTTP |
Auth (v0, apify-dribbble, etc.): Add the gateway in Admin UI, then edit it and set Passthrough Headers (e.g. Authorization) or Authentication type OAuth so the gateway sends the token. Do not put secrets in gateways.txt or in the repo. For the exact Context Forge structure (gateway, virtual server, prompts, resources) and which registrations need manual steps, see docs/ADMIN_UI_MANUAL_REGISTRATION.md.
Some remote URLs may show "Failed to initialize" (e.g. context-awesome returns 406 from this gateway). See Troubleshooting below. Stdio-only servers (e.g. sequential-thinking) need the translate setup in the next section or stay in Cursor.
Local servers (stdio → SSE)
The default ./start.sh starts the gateway and these local translate services (stdio → SSE):
| Gateway name | URL (internal) | Notes |
|---|---|---|
| sequential-thinking | http://sequential-thinking:8013/sse | — |
| chrome-devtools | http://chrome-devtools:8014/sse | — |
| playwright | http://playwright:8015/sse | — |
| magicuidesign-mcp | http://magicuidesign-mcp:8016/sse | @magicuidesign/mcp |
| desktop-commander | http://desktop-commander:8017/sse | — |
| puppeteer | http://puppeteer:8018/sse | — |
| browser-tools | http://browser-tools:8019/sse | — |
| tavily | http://tavily:8020/sse | Set TAVILY_API_KEY in .env |
| filesystem | http://filesystem:8021/sse | Set FILESYSTEM_VOLUME (host path) in .env; default ./workspace |
| reactbits | http://reactbits:8022/sse | reactbits-dev-mcp-server |
| snyk | http://snyk:8023/sse | Set SNYK_TOKEN in .env (Snyk CLI auth) |
| memory | http://memory:8027/sse | Persistent knowledge graph; data in ./data/memory (no API key) |
| git-mcp | http://git-mcp:8028/sse | Local git operations: commit, branch, diff, log (no API key) |
| fetch | http://fetch:8029/sse | Web content fetching to markdown for LLM use (no API key) |
| postgres | http://postgres:8031/sse | Set POSTGRES_CONNECTION_STRING in .env; see Multi-User Config |
| mongodb | http://mongodb:8032/sse | Set MONGODB_CONNECTION_STRING in .env; see Multi-User Config |
| tool-router | http://tool-router:8030/sse | Single entry point; set GATEWAY_JWT in .env (see cursor-router) |
| sqlite | http://sqlite:8024/sse | Set SQLITE_DB_PATH / SQLITE_VOLUME in .env; default ./data |
| github | http://github:8025/sse | Set GITHUB_PERSONAL_ACCESS_TOKEN in .env |
| ui | http://ui:8026/sse | AI-driven UI generation (7 tools); set FIGMA_ACCESS_TOKEN in .env for Figma sync |
After start, run make register to register them (or add in Admin UI with the URLs above, Transport SSE). This creates or updates a virtual server and prints its Cursor URL. Attach tools in Admin UI if you skip that step. Optional: set REGISTER_PROMPTS=true and add config/prompts.txt (format: name|description|template with {{arg}} and \n for newlines), then run make register. Use make gateway-only to run only the gateway (no translate services).
Stack-focused gateways (React, Node, TypeScript, Java, Spring, Prisma, etc.): config/gateways.txt includes local SSE servers and optional remote entries (Context7, context-awesome, prisma-remote). Add more via EXTRA_GATEWAYS in .env. Next.js (next-devtools-mcp) is project-local; run it inside your Next app. Spring AI MCP runs on the host; add its URL to EXTRA_GATEWAYS if you expose it.
Servers that stay in Cursor or as remote gateways: context-forge (gateway wrapper), browserstack, infisical-lukbot use custom scripts or tokens; run them on the host or add their HTTP URL in Admin UI if you expose them. Remote-only servers (Context7, context-awesome, prisma, cloudflare-*, v0, apify-dribbble) add as gateways with URL; v0 and apify need Passthrough Headers or OAuth in Admin UI.
Connect Cursor
The gateway requires a Bearer JWT on every request.
Automatic JWT (recommended)
Use the wrapper script so no token is stored in mcp.json and no weekly refresh is needed. From the
repo: ensure .env is set, run make register once (this writes data/.mcp-client-url), then run
./scripts/setup-forge-space-mcp.sh to set the context-forge entry in your IDE mcp config to the
wrapper. The wrapper config includes a 2-minute MCP timeout to avoid "Request timed out" (-32001);
set CURSOR_MCP_TIMEOUT_MS in .env (e.g. 180000) to change it. Before first use, run
make cursor-pull so the Context Forge Docker image is cached and the first IDE connection does not
time out while the image downloads. Restart your IDE. The wrapper uses the mcp-router
virtual server by default; set REGISTER_MCP_CLIENT_SERVER_NAME=cursor-default in .env and run
make register to use the full tool set instead. The wrapper generates a fresh JWT on each
connection and runs the gateway Docker image. On Linux the script adds
--add-host=host.docker.internal:host-gateway automatically. Optional: set
MCP_CLIENT_SERVER_URL in your IDE config if you prefer not to use data/.mcp-client-url, or pass
--mcp-url directly to ./scripts/setup-forge-space-mcp.sh.
To configure manually instead, set the entry to
{"command": "/absolute/path/to/forge-mcp-gateway/scripts/mcp-wrapper.sh", "timeout": 120000}
(use your clone path).
Manual JWT (URL-based or docker args)
-
Generate a JWT (e.g. 1 week): run
make jwt(ormake refresh-cursor-jwtto update the token in mcp.json in place; run weekly or before opening Cursor).Or run:
docker exec mcpgateway python3 -m mcpgateway.utils.create_jwt_token \ --username "$PLATFORM_ADMIN_EMAIL" --exp 10080 --secret "$JWT_SECRET_KEY" -
Add to
~/.cursor/mcp.json(docker wrapper with token in args):{ "mcpServers": { "context-forge": { "command": "docker", "args": [ "run", "--rm", "-i", "-e", "MCP_SERVER_URL=http://host.docker.internal:4444/servers/YOUR_SERVER_UUID/mcp", "-e", "MCP_AUTH=Bearer YOUR_JWT_TOKEN", "-e", "MCP_TOOL_CALL_TIMEOUT=120", "ghcr.io/ibm/mcp-context-forge:1.0.0-BETA-2", "python3", "-m", "mcpgateway.wrapper" ] } } }On Linux add after
"-i":"--add-host=host.docker.internal:host-gateway". Restart Cursor after changing the config.Alternative: URL-based (Streamable HTTP or SSE) Example with your server UUID and a token in headers:
"context-forge": { "type": "streamableHttp", "url": "http://localhost:4444/servers/YOUR_SERVER_UUID/mcp", "headers": { "Authorization": "Bearer YOUR_JWT_TOKEN" } }For SSE use
"type": "sse"and path.../servers/YOUR_SERVER_UUID/sse. Both requireAuthorization: Bearer YOUR_JWT_TOKEN. To avoid storing the token in mcp.json, use the Automatic JWT wrapper or the docker wrapper above. To refresh the token in mcp.json without copy-paste, runmake refresh-cursor-jwt(e.g. weekly via cron:0 9 * * 0or a launchd plist).
Environment
See .env.example. Required: PLATFORM_ADMIN_EMAIL, PLATFORM_ADMIN_PASSWORD, JWT_SECRET_KEY, AUTH_ENCRYPTION_SECRET (each at least 32 chars; run make generate-secrets). Never commit .env or secrets.
CI Tenant Contract
The test-autogen-warn workflow job requires these repository variables:
FORGE_TENANT_IDFORGE_TENANT_PROFILE_REF
CI attempts a best-effort checkout of Forge-Space/forge-tenant-profiles for warn-only parity.
For private profile access, configure FORGE_TENANT_PROFILES_READ_TOKEN.
If the profile repo, repository variables, or resolved profile path are unavailable, parity is
skipped and the warn-only flow remains non-blocking.
Automated Maintenance
This repository includes automated workflows for dependency updates, MCP server discovery, and Docker image updates.
Dependency Updates (Renovate)
Schedule: Every Monday at 2 AM UTC
Renovate automatically checks for updates to:
- Python dependencies (
requirements.txt) - Docker images (Context Forge gateway)
- GitHub Actions
Auto-merge policy:
- ✅ Patch/minor updates: Auto-merge after 3-day stabilization + passing CI
- ❌ Major updates: Require manual review (labeled
breaking-change) - 🔒 Security vulnerabilities: Immediate auto-merge
Setup: Add RENOVATE_TOKEN secret to repository settings (GitHub PAT with repo and workflow scopes).
Dashboard: Check the Dependency Dashboard issue for pending updates.
MCP Server Registry Check
Schedule: Every Monday at 3 AM UTC
Automatically scans the MCP Registry for:
- New servers not in
gateways.txt - Status of commented servers (auth requirements, etc.)
Creates/updates a GitHub issue with findings. No secrets required.
Docker Image Updates
Schedule: Every Monday at 4 AM UTC
Checks IBM/mcp-context-forge for new releases and automatically creates PRs with:
- Updated image tags in all files
- Changelog link
- Testing checklist
PRs require manual review before merge.
n8n Automation
Self-hosted n8n workflow automation for CI alerts, security advisories, cross-repo release coordination, and monitoring. Runs in Docker alongside the gateway but in its own compose file.
Setup
cp .env.n8n.example .env.n8n
make n8n-secrets # generate webhook HMAC secrets
# paste secrets into .env.n8n, configure GitHub token + Slack webhook
make n8n-start
# open http://localhost:5678 and import workflows from n8n-workflows/
Workflows
| # | Workflow | Trigger | Phase |
|---|---|---|---|
| 1 | CI Failure Alert | GitHub webhook (workflow_run) | 1 |
| 2 | Security Advisory Aggregator | Daily 9 AM cron | 1 |
| 3 | Upstream Release Notifier | GitHub webhook (release) | 2 |
| 4 | Stale PR Reminder | Daily 10 AM weekdays | 2 |
| 5 | Weekly Velocity Report | Monday 9 AM cron | 3 |
| 6 | Docker Health Monitor | Every 5 minutes | 3 |
Commands
make n8n-start # start n8n container
make n8n-stop # stop n8n
make n8n-logs # tail logs
make n8n-health # health check
make n8n-backup # export workflows
make n8n-secrets # generate webhook secrets
Security
- Localhost-only access (
127.0.0.1:5678) - HMAC-SHA256 signature verification on all GitHub webhooks
- One unique secret per workflow
- Resource-limited: 0.5 CPU, 512MB RAM, 50 PIDs
- Read-only GitHub API access (no merging, no deploying)
- n8n data excluded from git (
n8n-data/,n8n-logs/) - JSON-RPC and SSE error responses redact internal exception details; full traces stay server-side.
/rpc/streamquality events include an additivesecurity_spokereport block (v1, advisory fail-open, DAST hooks telemetry only).
API Documentation
The gateway exposes an auto-generated OpenAPI spec with all 21 endpoints documented:
- Swagger UI:
http://localhost:8030/docs - ReDoc:
http://localhost:8030/redoc - OpenAPI JSON:
http://localhost:8030/openapi.json
Endpoints are grouped by tag: rpc (JSON-RPC tool execution), audit (governance trail), health (probes), monitoring (cache/performance metrics).
Development
- Workflow and adding gateways/prompts: docs/DEVELOPMENT.md
- Script index: scripts/README.md
- Maintenance automation: See Automated Maintenance above
- Lint baseline:
ruff check tool_router/ dribbble_mcp/is enforced in CI and includes test files (import ordering and unused unpacked variables fail the lint job).
Trunk Based Development Workflow
This project uses Trunk Based Development with the following branch strategy:
Branch Structure
- main: Production-ready code, always deployable
- dev: Development environment branch, continuously deployed
- release/x.y.z: Release preparation branches
- feature/*: Feature development branches
Workflow
-
Feature Development
git checkout dev git pull origin dev git checkout -b feature/your-feature-name dev # Make your changes git commit -m "feat: add your feature" git push origin feature/your-feature-name -
Testing & Review
- Create PR from
feature/your-feature-nametorelease/x.y.z - All CI tests must pass
- Code review required
- Create PR from
-
Release Preparation
git checkout release/x.y.z git merge feature/your-feature-name git push origin release/x.y.z -
Production Deployment
git checkout main git merge release/x.y.z git tag v1.2.3 git push origin main --tags
Branch Protection
- main: Require PR approval, passing CI, no force pushes
- release/*: Require PR approval and passing CI
- dev: Require passing CI only
Environment Configuration
- Dev Environment: Uses
.env.development, auto-deployed fromdevbranch - Production Environment: Uses
.env.production, deployed frommainmerges
Using the gateway with AI
Which tools to use for planning, docs, search, browser, DB: docs/AI_USAGE.md.
🏗️ Shared Package Structure
This project uses a centralized shared package structure for UIForge-wide standardization:
.github/shared/
├── workflows/ # Reusable CI/CD templates
├── configs/ # Shared configurations
├── scripts/ # Utility scripts
├── templates/ # GitHub templates
└── README.md # Comprehensive documentation
Key Benefits
- 40% reduction in duplicate configurations
- Standardized CI/CD pipelines across UIForge projects
- Unified security scanning and dependency management
- Automated setup with symlink management
Usage
- Setup: Run
./scripts/setup-shared-symlinks.shto create configuration links - Documentation: See
.github/shared/README.mdfor detailed usage - Migration: Use
docs/UIFORGE_MIGRATION_GUIDE.mdfor other projects
CI/CD Pipeline
The main workflow (.github/workflows/ci.yml) uses shared templates:
jobs:
ci:
uses: ./.github/shared/workflows/base-ci.yml
with:
project-type: 'gateway'
node-version: '22'
python-version: '3.12'
Troubleshooting
403 "Insufficient permissions. Required: admin.dashboard" on /admin
The admin UI requires an authenticated user with the admin.dashboard permission. Open the gateway root or login page first (e.g. http://localhost:4444/ or http://localhost:4444/login), sign in with PLATFORM_ADMIN_EMAIL and PLATFORM_ADMIN_PASSWORD from your .env, then go to http://localhost:4444/admin. If the browser prompts for credentials, use the same email and password. Do not put secrets in the repo; keep them only in .env.
"Failed to initialize gateway" when adding a gateway
The gateway (Context Forge) checks the URL when you save. Common causes: (1) Remote server down or unreachable from the container. (2) Wrong URL or path (e.g. some servers need /sse). (3) Server rejects the request — e.g. Context Awesome (https://www.context-awesome.com/api/mcp) returns 406 unless the client sends Accept: application/json, text/event-stream; if Context Forge does not send that header, initialization fails and this is an upstream limitation. Workaround: use the server from a client that supports that URL (e.g. Cursor with the URL in mcp.json) or watch Context Forge for fixes.
If you see "Failed to initialize gateway" or "Unable to connect to gateway" for all local gateways when running make register, the translate services must listen on 0.0.0.0 so the gateway container can reach them (docker-compose already uses --host 0.0.0.0). If you changed the translate command, add --host 0.0.0.0. Otherwise, translate containers may still be starting (first run can take 30–60s while npx installs packages): wait and run make register again, or run make register-wait (or REGISTER_WAIT_SECONDS=30 make register). If only one local gateway (e.g. sqlite) fails, the same wait-and-retry usually fixes it. Ensure make start was used (not make gateway-only) and docker compose ps shows the translate services running. If translate containers are Restarting, they may be crashing (e.g. missing npx in the image): rebuild with docker compose build --no-cache sequential-thinking, then make stop and make start.
Script hangs on "Waiting for gateway"
The register script polls the gateway for up to 90s. On Docker Desktop for Mac, if you use GATEWAY_URL=http://127.0.0.1:4444, the script now tries localhost first. If it still hangs, set GATEWAY_URL=http://localhost:4444 in .env.
MCP Registry shows "Failed" for some servers
When you add a server from the registry, the gateway validates it (connects and lists tools). Failure usually means the remote server is down, slow, or unreachable from the gateway. Try Click to Retry. If it still fails, add the gateway manually: Gateways → New Gateway, enter the same name and URL (e.g. https://mcp.deepwiki.com/sse). Servers that need OAuth show "OAuth Config Required" in the registry; after adding them, edit the gateway and set Authentication type to OAuth with the provider’s client ID, secret, and URLs (see Context Forge OAuth docs).
context-forge shows "Error" / "Needs authentication" / "Loading tools" forever, or logs "Server disconnected without sending a response" / "No server info found"
Often caused by weak JWT_SECRET_KEY or AUTH_ENCRYPTION_SECRET (gateway logs: "Secret has low entropy", "Secret should be at least 32 characters"). Fix: run make generate-secrets, add the two lines to .env, then make stop, make start, make register, and fully quit Cursor (Cmd+Q / Alt+F4) and reopen. Reload Window is not enough. If that’s not the cause:
-
If you use the wrapper (recommended): Run
python3 scripts/ide-setup.py setup cursor --action verifyto check gateway,data/.mcp-client-url, server existence, Context Forge image, and gateway reachability from Docker. If any check fails, runmake startthenmake register, then rerun./scripts/setup-forge-space-mcp.shand fully restart your IDE. Runmake cursor-pullonce so the first IDE start does not timeout while the image downloads. To use the default mcp-router (tool-router): remove or comment outREGISTER_MCP_CLIENT_SERVER_NAMEin.env, runmake register, then restart the IDE. If you haveREGISTER_MCP_CLIENT_SERVER_NAME=cursor-defaultin.env, the wrapper uses cursor-default; the URL indata/.mcp-client-urlis only updated when you runmake register. If logs show "No server info found": (1) Ensure the gateway is running (make start) and reachable from Docker (the wrapper runs in a container and useshost.docker.internal:4444). (2) Runmake registerto refresh the URL, then restart the IDE. (3) For mcp-router, setGATEWAY_JWTin.env(runmake jwtand paste) so the router can call the gateway. If logs show "Request timed out" (MCP error -32001): (1) Runmake cursor-pullso the Context Forge image is cached. (2) Rerun./scripts/setup-forge-space-mcp.shafter settingCURSOR_MCP_TIMEOUT_MS=180000in.envif needed. (3) Fully restart the IDE. Ifmake registerreportsGET /servers returned 404, the gateway is in minimal mode and cannot writedata/.mcp-client-url; run./scripts/setup-forge-space-mcp.sh --mcp-url <server-url>and keep the URL inMCP_CLIENT_SERVER_URLin your IDE entry. -
If you use a manual JWT (URL in mcp.json): (1)
make start(ensure gateway is running). (2)make refresh-cursor-jwt(updates the Bearer token in~/.cursor/mcp.jsonfor the context-forge entry; the script also findsuser-context-forgeif that’s the key Cursor uses). (3) Restart Cursor. Your URL must end with/mcpor/sse. If Cursor is on the host and the gateway runs in Docker, usehttp://host.docker.internal:4444/servers/UUID/mcp. Alternatively switch to the wrapper with./scripts/setup-forge-space-mcp.sh.
"Method Not Allowed" or "Invalid OAuth error response" when connecting Cursor to context-forge
You are using a URL like http://localhost:4444/servers/UUID without a transport path. Use /sse for SSE or /mcp for streamable HTTP (e.g. .../servers/UUID/sse). You must also send the JWT: use the docker wrapper with MCP_SERVER_URL=.../servers/UUID/mcp and MCP_AUTH=Bearer YOUR_JWT_TOKEN, or add headers: { "Authorization": "Bearer YOUR_JWT_TOKEN" } to the SSE URL config.
500 on /admin (gateways/partial, prompts/partial) or "Loading gateways..." / "Loading prompts..." never finish
The admin UI loads data from the gateway backend; when the backend returns 500, those requests fail. A common cause is a corrupted SQLite database. Check gateway logs: docker compose logs gateway --tail 100. If you see sqlite3.DatabaseError: database disk image is malformed, follow the recovery steps in the next bullet (stop, remove ./data/mcp.db and WAL/SHM, restart). Then run make register again to re-register gateways. See data/README.md. You can run make reset-db to stop the stack and remove the DB files in one step, then make start and make register.
"database disk image is malformed" or "FileLock health check failed"
The SQLite database in ./data/mcp.db is corrupted (e.g. after a hard shutdown or disk issue). Stop the stack, remove the DB file (and mcp.db-shm, mcp.db-wal if present), and restart so the gateway creates a fresh database. Use make reset-db then make start (and make register to re-add gateways). See data/README.md.
Admin "Prompts" page stuck on "Loading prompts..."
The Context Forge admin UI at /admin/#prompts can hang due to an upstream frontend/API mismatch or slow response. Workarounds: (1) Use the API: run make jwt to get a token, then curl -H "Authorization: Bearer <token>" <gateway>/prompts. (2) Register prompts via Make: set REGISTER_PROMPTS=true in .env and add lines to config/prompts.txt (format: name|description|template), then run make register. (3) Inspect in browser: DevTools → Network, filter by "prompts" or XHR; check the request URL, status, and response body. If the API returns 200 with valid JSON and the UI still spins, report to IBM/mcp-context-forge. A CSP error in the console for fonts.googleapis.com is unrelated to prompts loading.
Missing servers and authentication
Some gateways are commented out in config/gateways.txt so make register succeeds by default. To enable them:
- Commented local (sqlite, github): Uncomment the corresponding lines in
config/gateways.txt, set the required env vars in.env(e.g.GITHUB_PERSONAL_ACCESS_TOKENfor github), runmake registerormake register-wait. If they fail, see the "Failed to initialize gateway" / "Unable to connect" bullets above anddocker compose logs sqliteordocker compose logs github. - Commented remote (Context7, context-awesome, prisma-remote, cloudflare-*, v0, apify-dribbble): Uncomment in
scripts/gateways.txtand runmake register, or add the gateway via Admin UI (MCP Servers → Add New MCP Server or Gateway). For Context7, v0, apify-dribbble, cloudflare-*: after adding the gateway, edit it in Admin UI and set Passthrough Headers (e.g.Authorization) or Authentication type OAuth so the gateway can call the upstream. See docs/ADMIN_UI_MANUAL_REGISTRATION.md.
Authentication checklist: Local gateways that need keys: Tavily → TAVILY_API_KEY in .env; Snyk → SNYK_TOKEN; GitHub → GITHUB_PERSONAL_ACCESS_TOKEN. Remote gateways Context7, v0, apify-dribbble, cloudflare-* → configure Passthrough Headers or OAuth in Admin UI (do not put secrets in config/gateways.txt or the repo).
Contributing / Forking
You can fork this repo to run your own MCP gateway stack. After forking: copy .env.example to .env, set secrets (make generate-secrets), then make start and make register. To contribute back: run make lint and make test, open a PR with a clear description; see CHANGELOG.md for the project’s change conventions.
References
- Context Forge – Docker, stdio wrapper, translate
- MCP Registry – Discover servers; add via Admin UI