mcp-streamable-http-bridge

mcp-streamable-http-bridge

Bridges stdio Model Context Protocol (MCP) servers to MCP Streamable HTTP behind a single gateway, enabling multi-tenant, multi-user deployment with per-tenant environment variables via HTTP headers.

Category
访问服务器

README

mcp-streamable-http-bridge

Bridge stdio Model Context Protocol (MCP) servers to MCP Streamable HTTP behind a single gateway. Each client supplies tenant-specific values via HTTP headers; the wrapper spawns isolated subprocesses, routes MCP traffic, evicts idle instances, and exposes logs and Prometheus metrics.

Objective

Stdio MCP servers assume one long-lived process per client with environment variables fixed at startup. That works for a desktop client (for example Cursor) but not for a shared, multi-user gateway.

This project:

  • Loads MCP server definitions from a config folder using the same shape as Cursor mcp.json (mcpServers).
  • Accepts HTTP requests and forwards them to the correct stdio child process.
  • Maps x-<ENV_NAME> request headers to process environment variables declared under each server’s env block.
  • Derives a stable hash from the server name plus resolved env values to reuse one subprocess per tenant.
  • Evicts subprocesses after a configurable idle period (global setting).
  • Ships with Node.js, npx, uv, and python3 in the container image for common MCP launch patterns.
  • Logs with Winston (console + size-rotated file) and exposes Prometheus metrics.

Implementation is Node.js (JavaScript), organized in a class-first structure.

Quick start

Docker Compose

Pull the published image from ghcr.io/elad-bar/mcp-streamable-http-bridge (built on push to main via GitHub Actions), or build locally:

docker compose pull
docker compose up -d
# or build locally (tags the same GHCR name):
docker compose up --build -d
  • Config: edit JSON under config/ (mounted read-only at /config in the container).
  • Logs: written to logs/ on the host via ./logs:/app/logs (active file /app/logs/mcp-wrapper.log inside the container).
  • Env: copy .env.example to .env to override defaults; Compose also reads optional .env for PORT on the host mapping.

Health: http://localhost:8080/ (HTML catalog) · Raw Markdown: /catalog.md · Metrics: http://localhost:8080/metrics

Docker (manual)

docker build -t ghcr.io/elad-bar/mcp-streamable-http-bridge:latest .
docker run --rm -p 8080:8080 \
  -v /path/to/mcp-config:/config:ro \
  -v /path/to/logs:/app/logs \
  -e MCP_WRAPPER_IDLE_MINUTES=15 \
  ghcr.io/elad-bar/mcp-streamable-http-bridge:latest

Local development

npm install
npm start    # reads ./config, writes ./logs/mcp-wrapper.log
npm test

Outside Docker, paths resolve to the repo config/ and logs/ directories. In the container they stay /config and /app/logs/mcp-wrapper.log (via /.dockerenv). Integration tests override paths via constructor options.

Configuration

Gateway config (config/)

The wrapper reads only config/mcp.json for gateway mcpServers. Other files in config/ (e.g. SSH MCP ssh-config.json, keys under config/keys/) are for child processes only and are not parsed by the gateway.

File Loaded by gateway? Role
config/mcp.json Yes MCP server spawn lines (command / args / env)
Other JSON in config/ No e.g. @fangjunjie/ssh-mcp-server --config-file
config/example.mock.mcp.json.sample No Copy/rename into mcp.json if you want the sample mock server

Each loaded file uses the gateway schema (stdio spawn line):

{
  "mcpServers": {
    "grafana": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-grafana"],
      "env": {
        "GRAFANA_URL": "https://grafana.example.com",
        "GRAFANA_API_KEY": ""
      }
    }
  }
}
  • command / args: spawn line for the MCP server (stdio).
  • env: keys injected into the child process. Values may be defaults in JSON and/or overridden by headers (see below).

Every key listed under env participates in tenant identity (hash) and in the subprocess environment.

Client config (Streamable HTTP)

MCP clients (e.g. Cursor) connect to the gateway, not to stdio directly. Use url pointing at /mcp/{serverName} and optional headers for tenant x-<ENV> values (same names as in the gateway env block):

{
  "mcpServers": {
    "grafana": {
      "url": "http://localhost:8080/mcp/grafana",
      "headers": {
        "x-GRAFANA_URL": "https://grafana.example.com",
        "x-GRAFANA_API_KEY": "<your-api-key>"
      }
    }
  }
}

The client performs MCP Streamable HTTP (initialize, session id, tool calls). You do not configure initialize by hand—only the gateway URL and tenant headers.

Open GET / in a browser for a styled catalog with per-server client snippets, or GET /catalog.md / Accept: text/markdown for raw Markdown. Set PUBLIC_BASE_URL when the gateway is behind a reverse proxy so URLs in the catalog use the public host.

HTTP usage (MCP Streamable HTTP)

The wrapper implements MCP Streamable HTTP on /mcp/{serverName}:

  1. First request — POST with method: "initialize", tenant x-* headers, and JSON-RPC body. Response includes mcp-session-id header.
  2. Later requests — POST with the same mcp-session-id and one JSON-RPC message per request (no tenant headers required if session is valid).
  3. Server → client streaming — GET with Accept: text/event-stream and mcp-session-id to receive MCP notifications over SSE.
Path Method Purpose
/ GET HTML catalog (GitHub-style); send Accept: text/markdown for raw Markdown
/catalog.md GET Raw Markdown catalog
/mcp/{serverName} POST Client → server MCP messages
/mcp/{serverName} GET SSE stream (requires session header)
/health GET Liveness
/metrics GET Prometheus scrape

Tenant headers

For each key in the server’s env object, send a header:

x-<ENV_NAME>: <value>

Header names are case-insensitive. Examples:

Config env key Header
GRAFANA_URL x-GRAFANA_URL
GRAFANA_API_KEY x-GRAFANA_API_KEY

Resolution order: header value if present, otherwise the value from JSON. If a key has no value from either source, the request is rejected before spawning.

Instance key: sha256(serverName + canonical resolved env) — same server + same env → same subprocess; any difference → a new subprocess.

Do not log header values or full instance keys; use structured logs with a short hash prefix for support.

Stdio children use Content-Length framed JSON-RPC on stdin/stdout (same as the official MCP SDK).

Environment variables

Gateway

Variable Default Description
PORT 8080 HTTP listen port
MCP_WRAPPER_IDLE_MINUTES 15 Idle time before stopping a tenant subprocess
MCP_PROTOCOL_VERSION 2024-11-05 MCP protocol version sent to stdio servers on spawn
PUBLIC_BASE_URL (empty) Public base URL for links in the GET / Markdown catalog (e.g. https://mcp.example.com)

Logging (Winston)

Variable Default Description
LOG_LEVEL info Log level for all transports
LOG_MAX_SIZE 10m Rotate when file exceeds this size (not daily)
LOG_MAX_FILES 5 Number of rotated files to retain

Fixed paths (not env-configurable): In Docker, MCP JSON under /config, logs /app/logs/mcp-wrapper.log, Prometheus at GET /metrics. Locally, config/ and logs/mcp-wrapper.log under the repo root. Bind-mount host config/ and logs/ in Compose.

Metrics (Prometheus)

Aggregated metrics avoid high-cardinality labels (no full tenant hash on time series). Typical series:

  • mcp_servers_configured — count after config load
  • mcp_instances_active{server} — running subprocesses per configured server
  • mcp_rpc_requests_total{server,mcp_method} — MCP methods (tools/list, tools/call, …)
  • mcp_tool_calls_total{server,tool} — tools/call by tool name
  • mcp_rpc_duration_seconds{server,mcp_method} — latency histograms

Per-tenant detail belongs in structured logs, not metric labels. See docs/architecture.md.

Documentation

Document Contents
docs/product.md Problem, users, features, non-goals
docs/architecture.md Components, flows, config, observability

License

Add your license file as appropriate for your distribution.

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选