MCP Hub
Back to servers

mcp-bridgekit

Embeddable MCP stdio → HTTP bridge with background jobs & live dashboard. Survives Vercel/Cloudflare 30s timeouts. Now scales to 100+ users.

GitHub
Stars
16
Forks
5
Updated
Feb 23, 2026

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.

Version MIT Python

Deploy with Vercel


Table of Contents


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

ChallengeWithout BridgeKitWith BridgeKit
Web app needs MCP toolsCan't — browsers can't spawn subprocessesPOST /chat with tool name and args
Multiple users sharing toolsEach needs their own server setupPer-user session pooling (up to 100 concurrent)
Tool call takes 60 secondsHTTP gateway timeout (Vercel 30s, CloudFlare 30s)Auto-queues as background job, client polls GET /job/{id}
Which tools are available?Must read docs or hardcodeGET /tools/{user_id} — live discovery
Monitoring & debuggingBlind — no visibilityLive dashboard: sessions, jobs, logs, tools
Session cleanupZombie processes leak memoryAuto-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

ScenarioBetter Alternative
CLI tool calling MCP servers locallyUse MCP SDK directly — no HTTP needed
MCP server already speaks HTTP (Streamable HTTP transport)Connect directly — no bridge needed
Single-user desktop appMCP 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_id gets 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-Key header 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 /metrics in 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:

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)

Deploy with Vercel

Click the button above to deploy your own instance. You'll need:

  1. A Redis instance (e.g., Upstash, Railway, or Redis Cloud)
  2. Set the MCP_BRIDGEKIT_REDIS_URL environment 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_id instantly 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 CodeCause
SESSION_CREATE_FAILEDCould not spawn MCP server subprocess
TOOL_CALL_FAILEDTool raised an exception (after all retries)
TOOL_TIMED_OUTTool exceeded timeout — job queued
RATE_LIMITEDUser exceeded requests-per-minute limit
MISSING_API_KEYAuth enabled but header absent
INVALID_API_KEYWrong 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_):

VariableDefaultDescription
REDIS_URLredis://localhost:6379Redis connection
MAX_SESSIONS100Max concurrent MCP sessions
SESSION_TTL_SECONDS3600Session expiry (1 hour)
TIMEOUT_THRESHOLD_SECONDS25.0Seconds before queuing as background job
JOB_RESULT_TTL_SECONDS600How long job results stay in Redis
DEFAULT_MCP_COMMANDpythonDefault 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_MINUTE60v0.8 Max requests per user_id per minute. 0 = disabled.
MAX_TOOL_RETRIES2v0.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_SSEtruev0.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 ServercommandargsExample Tools
AWS MCPnpx["-y", "@aws/aws-mcp"]describe_instances, list_s3_buckets
AWS CDKnpx["-y", "@aws/aws-cdk-mcp-server"]GenerateCDK, ExplainCDK
AWS Docsnpx["-y", "@aws/aws-documentation-mcp-server"]search_documentation
GitHubnpx["-y", "@modelcontextprotocol/server-github"]search_repositories, create_issue
Filesystemnpx["-y", "@modelcontextprotocol/server-filesystem", "/data"]read_file, write_file
PostgreSQLnpx["-y", "@modelcontextprotocol/server-postgres"]query
Custom Pythonpython["/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:

ComponentHow to ScaleNotes
Web servergunicorn --workers NDockerfile defaults to 4 workers. Each worker has its own event loop.
RQ workersRun multiple mcp-bridgekit-worker processesdocker-compose defaults to 3 replicas. All point to the same Redis.
RedisUse managed Redis (Upstash, ElastiCache, Redis Cloud)Single Redis handles thousands of connections.
Multi-machineRun workers on different machines pointing to same RedisMCP_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

Reviews

No reviews yet

Sign in to write a review