About Bridgekit
Bridgekit is an MCP server in the Agents category: a scoped MCP server exposing company tools (Shopify, Triple Whale, Postgres) to an AI stack with per-client permission boundaries and an append-only audit log. Writes need an explicit scope. Zero runtime dependencies, hand-rolled MCP on Cloudflare Workers.
Install
git clone https://github.com/mkbhardwas12/mcp-bridgekitThis server has no ConduID identity, so agent calls to it are not receipted. Pin the version you install and review the source before granting it credentials.
Ask AI
Ask AI about Bridgekit
Powered by Claude · Grounded in docs
Security checks
- ✓README presentThe repository ships documentation.
5 checks have not been run yet.
README
MCP BridgeKit
Embeddable MCP stdio → HTTP bridge for web chatbots.
Turn any MCP stdio server into HTTP endpoints your web app can call. Per-user session pooling, real timeout handling with background job fallback, live dashboard.
Table of Contents
- What Is MCP BridgeKit?
- The Problem It Solves
- Use Cases & Scenarios
- Architecture
- Request Flow
- Key Features
- Quickstart
- Docker (Recommended)
- One-Click Deploy (Vercel)
- API Reference
- Concurrency Model
- Configuration
- Security (Auth & Rate Limiting)
- Push Notifications (Webhook & SSE)
- Connecting to Any MCP Server
- Embedding in Your App
- Project Structure
- TypeScript Version
- End-to-End Testing Guide
- Horizontal Scaling
- Full Architecture Docs
- License
What Is MCP BridgeKit?
MCP (Model Context Protocol) is an open standard that lets AI applications connect to external tools and data sources. MCP servers communicate over stdio (stdin/stdout) — which means they run as local subprocesses and speak JSON-RPC over pipes.
The problem: Web applications (React, Next.js, Vue, mobile apps) can't spawn local subprocesses. They only speak HTTP. There's a protocol mismatch.
MCP BridgeKit is the bridge. It sits between your web app and MCP stdio servers, translating HTTP requests into stdio subprocess calls and streaming results back:
Your Web App ──HTTP──▶ MCP BridgeKit ──stdio──▶ MCP Server (tool)
(this project)
Think of it as "nginx for MCP tools" — a reverse proxy that makes stdio tools available over HTTP.
The Problem It Solves
| Challenge | Without BridgeKit | With BridgeKit |
|---|---|---|
| Web app needs MCP tools | Can't — browsers can't spawn subprocesses | POST /chat with tool name and args |
| Multiple users sharing tools | Each needs their own server setup | Per-user session pooling (up to 100 concurrent) |
| Tool call takes 60 seconds | HTTP gateway timeout (Vercel 30s, CloudFlare 30s) | Auto-queues as background job, client polls GET /job/{id} |
| Which tools are available? | Must read docs or hardcode | GET /tools/{user_id} — live discovery |
| Monitoring & debugging | Blind — no visibility | Live dashboard: sessions, jobs, logs, tools |
| Session cleanup | Zombie processes leak memory | Auto-eviction (TTL + pool limit) + manual DELETE /session/{id} |
Use Cases & Scenarios
1. AI Chatbot with Tool Calling
Scenario: You're building a customer support chatbot in React. The AI can call tools like
search_docs,create_ticket,check_order_status— all implemented as MCP servers.
React App → POST /chat {tool: "search_docs", args: {query: "refund policy"}}
← SSE stream with search results
BridgeKit manages one MCP server process per conversation, so each user has isolated state.
2. Multi-Tenant SaaS Platform
Scenario: Your SaaS lets customers connect their own MCP tools (data analysis, code generation, API integrations). Each customer uses different tools.
Customer A → POST /chat {user_id: "cust-A", mcp_config: {command: "python", args: ["their_tool.py"]}}
Customer B → POST /chat {user_id: "cust-B", mcp_config: {command: "node", args: ["their_tool.js"]}}
Each customer gets a dedicated session with their own MCP server. Pool manages up to 100 concurrent sessions with automatic eviction.
3. Long-Running Data Processing
Scenario: An MCP tool runs complex SQL queries or ML inference that takes 45 seconds. Your frontend uses Vercel with a 30-second timeout.
Client → POST /chat {tool: "run_analysis", args: {dataset: "sales_2025"}}
← SSE: {status: "queued", job_id: "abc-123"}
# 45 seconds later...
Client → GET /job/abc-123
← {status: "completed", result: {revenue: 4200000, growth: "12%"}}
BridgeKit's asyncio.timeout(25s) catches the slow call, queues it via Redis/RQ, and a background worker completes it.
4. Internal Developer Tools
Scenario: Your team has MCP tools for database queries, log analysis, and deployment — you want a single HTTP API to access all of them.
# Query production database
curl -X POST localhost:8000/chat \
-d '{"user_id": "dev-1", "tool_name": "query_db", "tool_args": {"sql": "SELECT count(*) FROM users"}}'
# Check which tools are available
curl localhost:8000/tools/dev-1
Run docker-compose up and all your tools are accessible from any HTTP client.
5. Webhook / Integration Pipelines
Scenario: A Slack bot, Zapier workflow, or n8n pipeline needs to call MCP tools based on triggers.
Slack Event → Zapier → POST /chat {tool: "summarize", args: {text: "..."}}
← {result: "Here's the summary..."}
BridgeKit is a standard HTTP API — any integration platform can call it.
6. Mobile Applications
Scenario: An iOS/Android app needs to call MCP tools but can't run subprocesses on the device.
Mobile App → POST https://your-server.com/chat
← SSE stream or job_id for polling
Deploy BridgeKit on your server, and mobile clients communicate over HTTPS.
When NOT to Use BridgeKit
| Scenario | Better Alternative |
|---|---|
| CLI tool calling MCP servers locally | Use MCP SDK directly — no HTTP needed |
| MCP server already speaks HTTP (Streamable HTTP transport) | Connect directly — no bridge needed |
| Single-user desktop app | MCP SDK + stdio directly |
Architecture
graph LR
subgraph Clients
A[🌐 Web Chatbot]
B[⌨️ CLI / Script]
C[🔗 Third-party API]
end
subgraph BridgeKit
D[🚀 FastAPI Server\napp.py]
E[⚡ BridgeKit Core\ncore.py]
F[📊 Dashboard\ndashboard.py]
end
subgraph Backend
G[🧠 MCP Server\nstdio process]
H[(💾 Redis)]
I[👷 RQ Worker\nworker.py]
end
A -- POST /chat --> D
B -- POST /chat --> D
C -- POST /chat --> D
A -. GET /dashboard .-> F
D --> E
E -- stdio --> G
G -- stdout --> E
E -. timeout? .-> H
H --> I
I -- stdio --> G
I -- store result --> H
E -. poll result .-> H
style E fill:#10b981,stroke:#059669,color:#fff
style H fill:#f59e0b,stroke:#d97706,color:#fff
style D fill:#3b82f6,stroke:#2563eb,color:#fff
style G fill:#8b5cf6,stroke:#7c3aed,color:#fff
style I fill:#a855f7,stroke:#9333ea,color:#fff
Request Flow
sequenceDiagram
participant Client
participant FastAPI
participant BridgeKit as BridgeKit Core
participant MCP as MCP Server
participant Redis
participant Worker as RQ Worker
Client->>FastAPI: POST /chat {user_id, tool_name, tool_args}
FastAPI->>BridgeKit: bridge.call(req)
BridgeKit->>BridgeKit: Get/create session (per-user lock)
BridgeKit->>MCP: call_tool(name, args) with asyncio.timeout(25s)
alt ✅ Completes within timeout
MCP-->>BridgeKit: Tool result
BridgeKit-->>FastAPI: SSE: data: {result}
FastAPI-->>Client: Stream response
else ⏱ Timeout exceeded
BridgeKit->>Redis: Enqueue job + set status=queued
BridgeKit-->>FastAPI: SSE: {status: queued, job_id}
FastAPI-->>Client: Return job_id
Worker->>Redis: Pick up job
Worker->>MCP: Spawn session + call_tool
MCP-->>Worker: Result
Worker->>Redis: Store result + set status=completed
Client->>FastAPI: GET /job/{job_id}
FastAPI->>Redis: Get status + result
Redis-->>FastAPI: {status: completed, result: ...}
FastAPI-->>Client: Return result
end
Key Features
- Per-user sessions: Each
user_idgets its own MCP stdio process - Real timeout handling:
asyncio.timeout()wraps every tool call — if it exceeds the threshold, the call is automatically queued as a background job via Redis/RQ - Background job polling:
GET /job/{job_id}to check status/results - Tool discovery:
GET /tools/{user_id}lists available tools from the MCP server - Session management: Auto-eviction when pool is full, TTL-based expiry, manual
DELETE /session/{user_id} - Live dashboard: HTMX + Tailwind — sessions, jobs, logs, tools (no build step)
- Structured logging: via structlog
- API key auth (v0.8):
X-API-Keyheader protection — disabled by default, backward-compatible - Rate limiting (v0.8): Per-user fixed-window via Redis (default 60 req/min), returns HTTP 429
- Retry with backoff (v0.8): Transient failures retried up to 2× with exponential backoff (1s, 2s)
- Prometheus metrics (v0.8):
GET /metricsin Prometheus exposition format — scrape with any monitoring stack - Structured error codes (v0.8): All errors include
error_code(e.g.TOOL_CALL_FAILED,RATE_LIMITED) - Webhook push (v0.9): Worker POSTs job result to your URL when a background job completes
- SSE push (v0.9):
GET /mcp/events/{job_id}— browser/client listens in real-time, stream closes automatically on completion
Quickstart
# Clone & install
git clone https://github.com/mkbhardwas12/mcp-bridgekit.git
cd mcp-bridgekit
# Copy environment config
cp .env.example .env # edit if needed (all vars have sensible defaults)
# Install (pick one)
uv sync --dev # recommended — fastest
pip install -e ".[dev]" # alternative
# Start Redis (required for job queue)
docker run -d -p 6379:6379 redis:7-alpine
# Terminal 1 — Run the server
uvicorn mcp_bridgekit.app:app --reload
# Terminal 2 — Start the background worker
mcp-bridgekit-worker
Open:
- http://localhost:8000 — Landing page
- http://localhost:8000/dashboard — Live dashboard
- http://localhost:8000/docs — Interactive API docs
Docker (Recommended)
graph TB
subgraph Docker Compose
R[(Redis\nredis:7-alpine\nport 6379)]
S[BridgeKit Server\npython:3.12-slim\nport 8000]
W[RQ Worker\npython:3.12-slim]
end
Internet((Internet)) -->|:8000| S
S --> R
W --> R
style R fill:#ef4444,stroke:#dc2626,color:#fff
style S fill:#3b82f6,stroke:#2563eb,color:#fff
style W fill:#8b5cf6,stroke:#7c3aed,color:#fff
docker-compose up
This starts Redis, the BridgeKit server (port 8000), and 3 RQ worker replicas.
One-Click Deploy (Vercel)
Click the button above to deploy your own instance. You'll need:
- A Redis instance (e.g., Upstash, Railway, or Redis Cloud)
- Set the
MCP_BRIDGEKIT_REDIS_URLenvironment variable during setup
Note: Vercel's 30s function timeout is exactly why BridgeKit exists — any MCP tool call exceeding 25s is automatically queued as a background job. Your users get a
job_idinstantly and poll for results.
API Reference
POST /chat
Call an MCP tool. Returns SSE stream. Auto-queues on timeout.
{
"user_id": "user-123",
"messages": [{"role": "user", "content": "analyze sales data"}],
"tool_name": "analyze_data",
"tool_args": {"query": "Q4 revenue trends"},
"mcp_config": {"command": "python", "args": ["examples/mcp_server.py"]}
}
GET /job/{job_id}
Poll background job status. Returns queued, running, completed (with result), or failed.
GET /tools/{user_id}?command=python&args=examples/mcp_server.py
List available tools from the MCP server.
DELETE /session/{user_id}
Close a user's MCP session.
GET /health
Health check with stats: active sessions, total requests, errors, queued jobs, known tools.
GET /metrics
Prometheus-compatible metrics in text exposition format. Safe to scrape without auth.
bridgekit_active_sessions 3
bridgekit_requests_total 472
bridgekit_errors_total 2
bridgekit_queued_jobs 1
...
Configure Prometheus:
scrape_configs:
- job_name: mcp-bridgekit
static_configs:
- targets: [bridgekit:8000]
metrics_path: /metrics
GET /mcp/events/{job_id} (v0.9)
Server-Sent Events stream. Connect after submitting a long job; fires one event when the background job completes, then closes automatically. No auth required.
const es = new EventSource(`/mcp/events/${jobId}`);
es.onmessage = (e) => {
const data = JSON.parse(e.data);
if (data.status === 'completed') {
console.log('Job done!', data.result);
es.close();
}
};
Event payload:
{
"job_id": "abc-123",
"status": "completed",
"result": { ... },
"user_id": "user-456",
"completed_at": "2026-02-23T12:00:00+00:00"
}
Error Response Format (v0.8.0+)
All error payloads include a structured error_code field:
{ "status": "error", "error_code": "TOOL_CALL_FAILED", "message": "...", "attempts": 3 }
{ "status": "queued", "error_code": "TOOL_TIMED_OUT", "job_id": "..." }
| Error Code | Cause |
|---|---|
SESSION_CREATE_FAILED |
Could not spawn MCP server subprocess |
TOOL_CALL_FAILED |
Tool raised an exception (after all retries) |
TOOL_TIMED_OUT |
Tool exceeded timeout — job queued |
RATE_LIMITED |
User exceeded requests-per-minute limit |
MISSING_API_KEY |
Auth enabled but header absent |
INVALID_API_KEY |
Wrong API key provided |
Concurrency Model
graph TD
R1[Request user-abc] --> GL
R2[Request user-abc] --> GL
R3[Request user-xyz] --> GL
GL[🔒 Global Lock\nheld for nanoseconds]
GL -->|get/create| UL1[🔑 Lock: user-abc]
GL -->|get/create| UL2[🔑 Lock: user-xyz]
UL1 -->|serialize| S1[Session: user-abc\nMCP stdio process]
UL2 -->|serialize| S2[Session: user-xyz\nMCP stdio process]
S1 --> POOL[(Session Pool\nTTL: 3600s\nEviction: oldest)]
S2 --> POOL
style GL fill:#ef4444,stroke:#dc2626,color:#fff
style UL1 fill:#f59e0b,stroke:#d97706,color:#fff
style UL2 fill:#f59e0b,stroke:#d97706,color:#fff
style POOL fill:#10b981,stroke:#059669,color:#fff
Configuration
Set via environment variables or .env file (prefix: MCP_BRIDGEKIT_):
| Variable | Default | Description |
|---|---|---|
REDIS_URL |
redis://localhost:6379 |
Redis connection |
MAX_SESSIONS |
100 |
Max concurrent MCP sessions |
SESSION_TTL_SECONDS |
3600 |
Session expiry (1 hour) |
TIMEOUT_THRESHOLD_SECONDS |
25.0 |
Seconds before queuing as background job |
JOB_RESULT_TTL_SECONDS |
600 |
How long job results stay in Redis |
DEFAULT_MCP_COMMAND |
python |
Default MCP server command |
DEFAULT_MCP_ARGS |
["examples/mcp_server.py"] |
Default MCP server args |
API_KEY |
(empty) | v0.8 API key for X-API-Key header auth. Leave empty to disable. |
RATE_LIMIT_PER_MINUTE |
60 |
v0.8 Max requests per user_id per minute. 0 = disabled. |
MAX_TOOL_RETRIES |
2 |
v0.8 Retry transient tool failures N times with exponential backoff. |
WEBHOOK_URL |
(empty) | v0.9 URL to POST job completion payload to. Leave empty to disable. |
ENABLE_SSE |
true |
v0.9 Publish completion events to GET /mcp/events/{job_id}. |
Security (Auth & Rate Limiting)
Added in v0.8.0. All features are opt-in — defaults are backward-compatible.
API Key Authentication
Protects /chat, /tools/, /job/, and /session/ endpoints.
/health, /metrics, and /dashboard stay public for monitoring tools.
# Enable by setting a key
export MCP_BRIDGEKIT_API_KEY=your-secret-key
# All protected calls need the header
curl -X POST http://bridgekit:8000/chat \
-H "X-API-Key: your-secret-key" \
-H "Content-Type: application/json" \
-d '{...}'
Without the key (or with wrong key): HTTP 401 with error_code: MISSING_API_KEY or INVALID_API_KEY.
Rate Limiting
Per-user fixed-window rate limiting backed by Redis. Rejects excess requests with HTTP 429.
# Default: 60 requests per user_id per minute
export MCP_BRIDGEKIT_RATE_LIMIT_PER_MINUTE=60
# Disable entirely
export MCP_BRIDGEKIT_RATE_LIMIT_PER_MINUTE=0
Retry with Backoff
Transient tool-call failures (e.g. subprocess crash) are automatically retried.
# Default: 2 retries — waits 1s, then 2s between attempts
export MCP_BRIDGEKIT_MAX_TOOL_RETRIES=2
# Disable retries
export MCP_BRIDGEKIT_MAX_TOOL_RETRIES=0
Note: TimeoutError is never retried — it immediately queues as a background job.
Push Notifications (Webhook & SSE)
Added in v0.9.0. Instead of polling GET /job/{job_id}, get instant push when a background job finishes.
Option 1: Webhook (AWS API / backend)
BridgeKit POSTs the result to your configured URL when any background job completes.
export MCP_BRIDGEKIT_WEBHOOK_URL=https://your-api.example.com/webhook/mcp
Payload your endpoint receives:
{
"job_id": "abc-123",
"status": "completed",
"result": { ... },
"user_id": "user-456",
"completed_at": "2026-02-23T12:00:00+00:00"
}
Webhook failures are caught and logged — they never crash the worker.
Option 2: SSE (browser / real-time frontend)
Your frontend holds open a Server-Sent Events connection. The stream closes automatically after the completion event.
// After POST /chat returns {status: "queued", job_id: "abc-123"}
const es = new EventSource(`/mcp/events/abc-123`);
es.onmessage = (e) => {
const data = JSON.parse(e.data);
if (data.status === 'completed') {
renderResult(data.result);
es.close();
}
};
SSE uses Redis Pub/Sub internally, so it works correctly when the API server and RQ worker are in separate processes/containers.
Using Both Together (AWS example)
# Your AWS Lambda / FastAPI endpoint
@app.post("/api/v1/analyze")
async def analyze(req: BridgeRequest):
result = await bridge.call(req)
if result.status == "queued":
return {
"status": "queued",
"job_id": result.job_id,
"sse_url": f"https://bridgekit.yourdomain.com/mcp/events/{result.job_id}"
}
return result
Disable either channel independently:
MCP_BRIDGEKIT_ENABLE_SSE=false # turn off SSE, keep webhook
MCP_BRIDGEKIT_WEBHOOK_URL= # leave empty to disable webhook
Connecting to Any MCP Server
BridgeKit works with any MCP server — you just pass different mcp_config values.
The mcp_config tells BridgeKit which command to spawn:
Supported MCP Servers
| MCP Server | command |
args |
Example Tools |
|---|---|---|---|
| AWS MCP | npx |
["-y", "@aws/aws-mcp"] |
describe_instances, list_s3_buckets |
| AWS CDK | npx |
["-y", "@aws/aws-cdk-mcp-server"] |
GenerateCDK, ExplainCDK |
| AWS Docs | npx |
["-y", "@aws/aws-documentation-mcp-server"] |
search_documentation |
| GitHub | npx |
["-y", "@modelcontextprotocol/server-github"] |
search_repositories, create_issue |
| Filesystem | npx |
["-y", "@modelcontextprotocol/server-filesystem", "/data"] |
read_file, write_file |
| PostgreSQL | npx |
["-y", "@modelcontextprotocol/server-postgres"] |
query |
| Custom Python | python |
["/path/to/server.py"] |
Whatever you define |
Quick Example: AWS MCP from Your API
import httpx
BRIDGEKIT_URL = "http://your-bridgekit-host:8000"
# Step 1: Discover available tools
resp = await httpx.AsyncClient().get(
f"{BRIDGEKIT_URL}/tools/discovery",
params={"command": "npx", "args": "-y,@aws/aws-mcp"},
)
print(resp.json()) # Shows all tools with schemas
# Step 2: Call a tool
async with httpx.AsyncClient(timeout=60.0) as client:
resp = await client.post(f"{BRIDGEKIT_URL}/chat", json={
"user_id": "user-123",
"messages": [{"role": "user", "content": "list instances"}],
"mcp_config": {
"command": "npx",
"args": ["-y", "@aws/aws-mcp"],
},
"tool_name": "describe_instances",
"tool_args": {"region": "us-east-1"},
})
Prerequisites: Install the MCP server package on the BridgeKit host (npm install -g @aws/aws-mcp) and configure AWS credentials.
Full guide: See docs/INTEGRATION_GUIDE.md for deployment on AWS, credential setup, polling for long-running jobs, and complete working examples.
Code examples: See examples/aws_integration.py for 8 ready-to-use endpoint patterns.
Embedding in Your App
from fastapi import FastAPI
from mcp_bridgekit import BridgeKit, BridgeRequest
app = FastAPI()
bridge = BridgeKit()
@app.post("/chat")
async def chat(req: BridgeRequest):
return await bridge.call(req)
Project Structure
graph LR
subgraph src/mcp_bridgekit
APP[app.py\nFastAPI routes]
CORE[core.py\nBridgeKit engine ⚡]
CFG[config.py\npydantic-settings]
MDL[models.py\nPydantic models]
WRK[worker.py\nRQ worker]
DASH[dashboard.py\n/dashboard]
LAND[landing.py\n/ landing]
end
APP --> CORE
APP --> DASH
APP --> LAND
CORE --> CFG
CORE --> MDL
WRK --> CFG
style CORE fill:#10b981,stroke:#059669,color:#fff
style APP fill:#3b82f6,stroke:#2563eb,color:#fff
style WRK fill:#8b5cf6,stroke:#7c3aed,color:#fff
TypeScript Version
A TypeScript implementation is available in ts/. Same architecture — session pooling, timeout handling, Redis queueing.
cd ts && npm install && npm run build && npm start
End-to-End Testing Guide
Verify everything works with these steps:
1. Start Services
# Option A — Docker (easiest)
docker-compose up --build
# Option B — Manual (3 terminals)
# T1: docker run -d -p 6379:6379 redis:7-alpine
# T2: mcp-bridgekit-worker
# T3: uvicorn mcp_bridgekit.app:app --reload --port 8000
2. Check Dashboard
Open http://localhost:8000/dashboard — should show 0 sessions, 0 jobs.
3. Call a Tool (instant response)
curl -N -X POST http://localhost:8000/chat \
-H "Content-Type: application/json" \
-d '{
"user_id": "test-001",
"messages": [{"role": "user", "content": "hello"}],
"tool_name": "analyze_data",
"tool_args": {"query": "test query"}
}'
→ SSE stream with result appears immediately. Dashboard shows 1 active session.
4. Test Timeout Survival (background job)
Edit examples/mcp_server.py — change asyncio.sleep(2) to asyncio.sleep(35), then re-run the curl above.
→ Immediate response: {"status": "queued", "job_id": "..."}
→ Dashboard updates live (Queued Jobs increases)
→ Worker terminal shows processing
5. Poll Job Result
curl http://localhost:8000/job/{job_id_from_step_4}
→ Returns {"status": "completed", "result": {...}} once the worker finishes.
6. List Available Tools
curl "http://localhost:8000/tools/test-001"
7. Cleanup Session
curl -X DELETE http://localhost:8000/session/test-001
8. Health Check
curl http://localhost:8000/health
→ Returns active sessions, request counts, error counts, queue depth.
Expected result: Tools respond instantly → slow tools get queued → dashboard shows live updates → sessions scale cleanly.
Horizontal Scaling
BridgeKit is designed to scale horizontally:
| Component | How to Scale | Notes |
|---|---|---|
| Web server | gunicorn --workers N |
Dockerfile defaults to 4 workers. Each worker has its own event loop. |
| RQ workers | Run multiple mcp-bridgekit-worker processes |
docker-compose defaults to 3 replicas. All point to the same Redis. |
| Redis | Use managed Redis (Upstash, ElastiCache, Redis Cloud) | Single Redis handles thousands of connections. |
| Multi-machine | Run workers on different machines pointing to same Redis | MCP_BRIDGEKIT_REDIS_URL=redis://your-redis-host:6379 |
# Scale workers to 5 replicas
docker-compose up --scale worker=5
# Or run standalone workers on any machine
MCP_BRIDGEKIT_REDIS_URL=redis://shared-redis:6379 mcp-bridgekit-worker
📐 Full Architecture Docs
See ARCHITECTURE.md for detailed diagrams and component docs. When running the server, visit /architecture for an interactive HTML version.
License
MIT
README copy captured when this server was indexed; it may lag the repository. The original is authoritative.