@winstonsayno/mcp-gateway 0.0.0-stage → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/CHANGELOG.md +124 -0
  2. package/LICENSE +21 -0
  3. package/README.md +377 -2
  4. package/dashboard/README.md +27 -0
  5. package/dashboard/index.html +415 -0
  6. package/dist/auth/middleware.d.ts +24 -0
  7. package/dist/auth/middleware.d.ts.map +1 -0
  8. package/dist/auth/middleware.js +108 -0
  9. package/dist/auth/middleware.js.map +1 -0
  10. package/dist/auth/ratelimit.d.ts +19 -0
  11. package/dist/auth/ratelimit.d.ts.map +1 -0
  12. package/dist/auth/ratelimit.js +81 -0
  13. package/dist/auth/ratelimit.js.map +1 -0
  14. package/dist/cli.d.ts +6 -0
  15. package/dist/cli.d.ts.map +1 -0
  16. package/dist/cli.js +129 -0
  17. package/dist/cli.js.map +1 -0
  18. package/dist/config/loader.d.ts +13 -0
  19. package/dist/config/loader.d.ts.map +1 -0
  20. package/dist/config/loader.js +268 -0
  21. package/dist/config/loader.js.map +1 -0
  22. package/dist/config/watcher.d.ts +29 -0
  23. package/dist/config/watcher.d.ts.map +1 -0
  24. package/dist/config/watcher.js +96 -0
  25. package/dist/config/watcher.js.map +1 -0
  26. package/dist/gateway/api.d.ts +29 -0
  27. package/dist/gateway/api.d.ts.map +1 -0
  28. package/dist/gateway/api.js +280 -0
  29. package/dist/gateway/api.js.map +1 -0
  30. package/dist/gateway/index.d.ts +49 -0
  31. package/dist/gateway/index.d.ts.map +1 -0
  32. package/dist/gateway/index.js +260 -0
  33. package/dist/gateway/index.js.map +1 -0
  34. package/dist/gateway/supervisor.d.ts +58 -0
  35. package/dist/gateway/supervisor.d.ts.map +1 -0
  36. package/dist/gateway/supervisor.js +205 -0
  37. package/dist/gateway/supervisor.js.map +1 -0
  38. package/dist/index.d.ts +15 -0
  39. package/dist/index.d.ts.map +1 -0
  40. package/dist/index.js +12 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/middleware/cors.d.ts +19 -0
  43. package/dist/middleware/cors.d.ts.map +1 -0
  44. package/dist/middleware/cors.js +65 -0
  45. package/dist/middleware/cors.js.map +1 -0
  46. package/dist/middleware/error-handler.d.ts +40 -0
  47. package/dist/middleware/error-handler.d.ts.map +1 -0
  48. package/dist/middleware/error-handler.js +98 -0
  49. package/dist/middleware/error-handler.js.map +1 -0
  50. package/dist/middleware/request-id.d.ts +23 -0
  51. package/dist/middleware/request-id.d.ts.map +1 -0
  52. package/dist/middleware/request-id.js +30 -0
  53. package/dist/middleware/request-id.js.map +1 -0
  54. package/dist/middleware/timeout.d.ts +12 -0
  55. package/dist/middleware/timeout.d.ts.map +1 -0
  56. package/dist/middleware/timeout.js +39 -0
  57. package/dist/middleware/timeout.js.map +1 -0
  58. package/dist/monitor/index.d.ts +49 -0
  59. package/dist/monitor/index.d.ts.map +1 -0
  60. package/dist/monitor/index.js +204 -0
  61. package/dist/monitor/index.js.map +1 -0
  62. package/dist/proxy/index.d.ts +86 -0
  63. package/dist/proxy/index.d.ts.map +1 -0
  64. package/dist/proxy/index.js +389 -0
  65. package/dist/proxy/index.js.map +1 -0
  66. package/dist/registry/index.d.ts +41 -0
  67. package/dist/registry/index.d.ts.map +1 -0
  68. package/dist/registry/index.js +157 -0
  69. package/dist/registry/index.js.map +1 -0
  70. package/dist/transport/channel.d.ts +61 -0
  71. package/dist/transport/channel.d.ts.map +1 -0
  72. package/dist/transport/channel.js +38 -0
  73. package/dist/transport/channel.js.map +1 -0
  74. package/dist/transport/sse-parser.d.ts +30 -0
  75. package/dist/transport/sse-parser.d.ts.map +1 -0
  76. package/dist/transport/sse-parser.js +107 -0
  77. package/dist/transport/sse-parser.js.map +1 -0
  78. package/dist/transport/sse.d.ts +39 -0
  79. package/dist/transport/sse.d.ts.map +1 -0
  80. package/dist/transport/sse.js +142 -0
  81. package/dist/transport/sse.js.map +1 -0
  82. package/dist/transport/stdio.d.ts +35 -0
  83. package/dist/transport/stdio.d.ts.map +1 -0
  84. package/dist/transport/stdio.js +149 -0
  85. package/dist/transport/stdio.js.map +1 -0
  86. package/dist/transport/streamable-http.d.ts +45 -0
  87. package/dist/transport/streamable-http.d.ts.map +1 -0
  88. package/dist/transport/streamable-http.js +183 -0
  89. package/dist/transport/streamable-http.js.map +1 -0
  90. package/dist/transport/websocket.d.ts +38 -0
  91. package/dist/transport/websocket.d.ts.map +1 -0
  92. package/dist/transport/websocket.js +172 -0
  93. package/dist/transport/websocket.js.map +1 -0
  94. package/dist/utils/logger.d.ts +16 -0
  95. package/dist/utils/logger.d.ts.map +1 -0
  96. package/dist/utils/logger.js +60 -0
  97. package/dist/utils/logger.js.map +1 -0
  98. package/dist/utils/mutex.d.ts +25 -0
  99. package/dist/utils/mutex.d.ts.map +1 -0
  100. package/dist/utils/mutex.js +62 -0
  101. package/dist/utils/mutex.js.map +1 -0
  102. package/dist/utils/semaphore.d.ts +16 -0
  103. package/dist/utils/semaphore.d.ts.map +1 -0
  104. package/dist/utils/semaphore.js +42 -0
  105. package/dist/utils/semaphore.js.map +1 -0
  106. package/dist/utils/types.d.ts +215 -0
  107. package/dist/utils/types.d.ts.map +1 -0
  108. package/dist/utils/types.js +5 -0
  109. package/dist/utils/types.js.map +1 -0
  110. package/dist/utils/version.d.ts +6 -0
  111. package/dist/utils/version.d.ts.map +1 -0
  112. package/dist/utils/version.js +18 -0
  113. package/dist/utils/version.js.map +1 -0
  114. package/package.json +78 -4
package/CHANGELOG.md ADDED
@@ -0,0 +1,124 @@
1
+ # Changelog
2
+
3
+ All notable changes to mcp-gateway will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ---
9
+
10
+ ## [Unreleased]
11
+
12
+ ## [0.3.0] - 2026-10-07
13
+
14
+ ### Added
15
+ - **Remote upstream transports are routable**: `streamable-http` (MCP 2025-03-26+: `Mcp-Session-Id`, `MCP-Protocol-Version`, JSON or SSE responses, `DELETE` on close), `sse` (MCP 2024-11-05 HTTP+SSE) and `websocket` (`mcp` subprotocol). Per-server `headers` (with `${VAR}` expansion) and `subprotocol` options.
16
+ - The proxy is now a transport-independent session layer over small channels (`src/transport/*`), so timeouts, upstream cancellation, `maxConcurrency` and server→client `ping` work identically on every transport. `notifications/tools/list_changed` refreshes the tool registry.
17
+ - **Automatic reconnect** of crashed / disconnected / never-connected servers with exponential backoff and jitter (`reconnect` block, per-server overrides). New `reconnecting` status, `health.reconnect` and `session` details in `/servers`, `POST /api/v1/servers/:id/reconnect`, `503` responses carry `status` and `Retry-After`.
18
+ - Health checks send a real MCP `ping` and record latency; a connected server that stops answering is `degraded`. Interval configurable via `healthCheckIntervalMs`.
19
+ - Prometheus: `mcp_gateway_server_up`, `mcp_gateway_server_status`, `mcp_gateway_server_reconnects_total`, `mcp_gateway_server_reconnect_attempt`, `mcp_gateway_server_ping_ms`. JSON `/metrics` includes a `servers` array.
20
+ - **Optional auth for `/health` and `/metrics`** (`auth.protect.health`, `auth.protect.metrics`, default off); always-public `GET /api/v1/health/live` liveness probe; `dashboard.enabled` switch.
21
+ - **Dashboard works with auth on**: API key / JWT field (sessionStorage, optional localStorage) sent as a Bearer token; shows reconnect state; fields aligned with the actual API.
22
+ - **Hot reload** now also applies `auth` (strategy, keys, secret, protect flags), `rateLimit`, `corsOrigins`, `monitor.requestLog` / `monitor.prometheus` and `reconnect`. An unusable auth config is rejected and the current one kept.
23
+ - Conformance tests against the official `@modelcontextprotocol/sdk` servers (Streamable HTTP, SSE, WebSocket adapter); supervisor, hot-reload and auth-protection tests.
24
+ - `examples/docker/prometheus.yml` (the compose file referenced it but it was missing) and `examples/remote-servers/`.
25
+
26
+ ### Changed
27
+ - `initialize` requests protocol `2025-06-18` and accepts `2025-03-26` / `2024-11-05` answers (the version the server picks is used).
28
+ - `@modelcontextprotocol/sdk` moved to `devDependencies` (used only by tests); no runtime dependency was added.
29
+ - `/health` reports `degraded` while any server is `reconnecting`; its `servers` summary has a `reconnecting` count.
30
+ - `/servers` redacts `headers` values and URL credentials / query values in addition to `env`.
31
+ - Docker `HEALTHCHECK` and the compose example use `/api/v1/health/live`.
32
+ - The SSE / WebSocket classes in `src/transport/` were rewritten as channels; they no longer reconnect on their own (the supervisor re-runs the full MCP handshake instead).
33
+
34
+ ### Security
35
+ - API-key comparison is now constant-time; client ids are key fingerprints instead of key prefixes.
36
+ - Unsupported auth strategies (`oauth2`, unknown values) and `api-key`/`jwt` without keys/secret now refuse to start instead of silently disabling auth.
37
+ - JWT verification is pinned to HS256/384/512.
38
+ - `/servers` responses redact `env` values.
39
+ - Client-supplied `X-Request-Id` values are validated; tool-call arguments are no longer logged.
40
+
41
+ ### Fixed
42
+ - `${VAR}` references in stdio `args` are now expanded (the multi-server and Docker examples relied on it; only `env` was expanded before).
43
+ - Docker compose example referenced a missing `prometheus.yml` and `mcp-gateway.yml`; both are now included.
44
+ - Dashboard read fields the API never returned (`healthy`, `uptimeSeconds`, `errorRate`, `p50LatencyMs`, …), could not authenticate, and inserted server-provided strings as raw HTML.
45
+ - Project did not compile (`tsc` errors in transports, watcher and JWT auth); `npm start` pointed at `dist/cli.ts`.
46
+ - Failed `initialize` left the child process running and the server reported as connected.
47
+ - Reconnecting a server id leaked the previous process; an old process' exit could remove the new session.
48
+ - SIGKILL escalation never ran (`proc.killed` check); EPIPE on a dead child's stdin crashed the gateway.
49
+ - Multi-byte UTF-8 split across stdout chunks was corrupted; server→client requests with colliding ids were taken as responses.
50
+ - `maxConcurrency` was ignored; timed-out calls are now cancelled upstream; `tools/list` pagination is followed.
51
+ - CORS with several origins produced an invalid `Access-Control-Allow-Origin` header.
52
+ - The SSE parser lost events split across chunks, ignored the MCP `endpoint` event and dropped the `sessionId` query; SSE/WS reconnected after an intentional disconnect.
53
+ - WebSocket transport relied on a global `WebSocket` missing on Node 20; now uses `ws`.
54
+ - Rate limiter was fixed-window (README said sliding) and its timer kept the process alive.
55
+ - Prometheus `*_total` series were last-minute counts (not counters); `*/*` requests got Prometheus text, breaking the dashboard.
56
+ - Listen errors (EADDRINUSE) hung startup; `stop()` hung on keep-alive sockets and was not idempotent.
57
+ - Malformed JSON bodies returned 500/HTML; upstream errors now map to 502 and timeouts to 504; ambiguous tool names return 409.
58
+ - Hard-coded `0.1.0` version strings; startup summary always reported 0 failed servers.
59
+ - Config hot reload, request ids, CORS, error handler and `/dashboard` were implemented but never wired in.
60
+ - Docker: `npm ci` needed a lockfile (now committed), dashboard copied, runs as non-root; compose healthcheck used `curl` (not in image).
61
+
62
+ ### Added
63
+ - Test suite (vitest) with a fake stdio MCP server; GitHub Actions CI on Node 20 and 22.
64
+ - Config validation: unique server ids, `command` for stdio, `url` for sse/websocket.
65
+
66
+ ---
67
+
68
+ ## [0.2.0] - 2026-03-27
69
+
70
+ ### New Features
71
+
72
+ **SSE Transport (`src/transport/sse.ts`)**
73
+ Full Server-Sent Events transport implementation for MCP servers that expose an SSE endpoint. Supports automatic reconnection with exponential back-off (up to `maxReconnectAttempts`), pending-request correlation by JSON-RPC id, and a companion POST `/message` endpoint for sending requests.
74
+
75
+ **WebSocket Transport (`src/transport/websocket.ts`)**
76
+ Full-duplex WebSocket transport for lower-latency MCP server communication. Includes automatic reconnection, keep-alive pings at a configurable interval, and the same pending-request correlation model as the SSE transport.
77
+
78
+ **Config Hot Reload (`src/config/watcher.ts`)**
79
+ The gateway now watches its config file for changes and applies new server registrations without requiring a restart. A 500 ms debounce prevents thrashing on rapid saves. Invalid configs are rejected with a clear error log while the previous config remains active.
80
+
81
+ **Request Tracing (`src/middleware/request-id.ts`)**
82
+ Every request now carries a unique `X-Request-Id` header. The middleware honours existing `X-Request-Id` or `X-Correlation-Id` headers sent by clients, falling back to a generated UUID v4. The id is reflected in the response and included in all log lines for that request.
83
+
84
+ **CORS Middleware (`src/middleware/cors.ts`)**
85
+ Configurable CORS support with wildcard, exact-origin, and regex-pattern matching. Exposes `X-Request-Id` and `X-RateLimit-*` headers to browsers by default.
86
+
87
+ **Web Dashboard (`dashboard/index.html`)**
88
+ A zero-dependency, single-file HTML dashboard served at `/dashboard`. Displays server health, tool inventory, recent requests, and aggregate metrics. Auto-refreshes every 10 seconds.
89
+
90
+ ### Bug Fixes
91
+
92
+ **[BUG-001] Concurrent restart race condition**
93
+ When multiple requests arrived simultaneously while a server process was restarting, the proxy could spawn duplicate processes. Fixed by introducing a per-server `Mutex` that serialises all `connect()` calls for the same server id.
94
+
95
+ **[BUG-002] JSON-RPC id collision under high concurrency**
96
+ `Date.now()` was used as the JSON-RPC request id, which could produce collisions when multiple requests were dispatched within the same millisecond. Replaced with a monotonic integer counter (`_idSeq`).
97
+
98
+ **[BUG-003] Leaked stdio handles on process crash**
99
+ When an MCP server process crashed, its `stdin` and `stdout` streams were not explicitly destroyed, leaving file-descriptor leaks. The `exit` and `error` handlers now call `.destroy()` on both streams before removing the session.
100
+
101
+ **[BUG-004] Silent spawn failures**
102
+ A `spawn error` event (e.g., command not found) was logged but did not reject pending requests, leaving callers hanging until their timeout fired. The `error` handler now immediately rejects all pending requests for that session.
103
+
104
+ **[BUG-005] Unhandled errors leaked raw stack traces**
105
+ Express errors were passed through without a centralised handler, causing raw `Error` objects (including stack traces) to be serialised into responses in production. A new `errorHandler` middleware normalises all errors into a consistent `{ error: { code, message, requestId } }` envelope and suppresses stack traces outside of development mode.
106
+
107
+ **[BUG-006] Requests hung indefinitely on slow servers**
108
+ Tool-call requests to unresponsive MCP servers could block the event loop indefinitely. A new `timeoutMiddleware` enforces a per-request deadline (default: 30 s) and returns a `504 Gateway Timeout` with a `Retry-After` header.
109
+
110
+ ### Internal Changes
111
+
112
+ - Added `src/utils/mutex.ts` — lightweight async mutex with no external dependencies
113
+ - Added `src/middleware/error-handler.ts` — centralised error normalisation and `GatewayError` class
114
+ - Added `src/middleware/timeout.ts` — per-request timeout enforcement
115
+ - Updated `src/proxy/index.ts` — incorporates all bug fixes above; private methods renamed with `_` prefix for clarity
116
+ - Updated client info version string from `0.1.0` to `0.2.0` in MCP `initialize` handshake
117
+
118
+ ---
119
+
120
+ ## [0.1.0] - 2026-03-24
121
+
122
+ ### Added
123
+
124
+ Initial public release. See the [v0.1.0 release notes](https://github.com/HarrisonCN/mcp-gateway/releases/tag/v0.1.0) for the full feature list.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 HarrisonCN
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,378 @@
1
- # Temporary Holding Version
1
+ <div align="center">
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ <img src="https://raw.githubusercontent.com/HarrisonCN/mcp-gateway/main/docs/assets/logo.svg" alt="mcp-gateway" width="120" />
4
+
5
+ # mcp-gateway
6
+
7
+ **A lightweight, open-source gateway for your MCP servers.**
8
+
9
+ Route · Authenticate · Rate-limit · Monitor — all your [Model Context Protocol](https://modelcontextprotocol.io) servers from a single endpoint.
10
+
11
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
12
+ [![Node.js](https://img.shields.io/badge/node-%3E%3D20.0.0-brightgreen.svg)](https://nodejs.org)
13
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.5-blue.svg)](https://www.typescriptlang.org)
14
+ [![npm version](https://img.shields.io/badge/npm-v0.2.0-blue.svg)](https://www.npmjs.com/package/mcp-gateway)
15
+ [![Docker](https://img.shields.io/badge/docker-ghcr.io-blue.svg)](https://ghcr.io/HarrisonCN/mcp-gateway)
16
+
17
+ [English](#) · [中文](docs/README.zh-CN.md) · [Docs](docs/) · [Examples](examples/)
18
+
19
+ </div>
20
+
21
+ ---
22
+
23
+ ## The Problem
24
+
25
+ As [MCP](https://modelcontextprotocol.io) becomes the standard protocol for AI agents to interact with tools, teams are running **dozens of MCP servers** — filesystem, GitHub, databases, Slack, search, and more. Managing them is chaos:
26
+
27
+ - Every AI client connects to every server independently
28
+ - No central authentication or access control
29
+ - No visibility into which tools are being called, by whom, and how often
30
+ - No rate limiting to prevent runaway agents from hammering your APIs
31
+
32
+ **mcp-gateway solves this.** It sits between your AI clients and your MCP servers, acting as a single, observable, secure entry point.
33
+
34
+ ```
35
+ ┌─────────────────────────────────────────────────────────┐
36
+ │ AI Clients │
37
+ │ Claude Code · Cursor · Copilot · Your App · Scripts │
38
+ └─────────────────────┬───────────────────────────────────┘
39
+ │ HTTP / REST
40
+ ▼
41
+ ┌─────────────────────────────────────────────────────────┐
42
+ │ mcp-gateway │
43
+ │ │
44
+ │ ┌──────────┐ ┌──────────┐ ┌──────────────────────┐ │
45
+ │ │ Auth │ │ Router │ │ Metrics / Monitor │ │
46
+ │ │ API Key │ │ Tool → │ │ Prometheus · Logs │ │
47
+ │ │ JWT │ │ Server │ │ Dashboard │ │
48
+ │ └──────────┘ └──────────┘ └──────────────────────┘ │
49
+ │ │
50
+ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
51
+ │ │Rate Limit│ │ Registry │ │ Health │ │
52
+ │ └──────────┘ └──────────┘ └──────────┘ │
53
+ └──────┬──────────────┬──────────────┬────────────────────┘
54
+ │ │ │ stdio / SSE / WS
55
+ ▼ ▼ ▼
56
+ ┌──────────┐ ┌──────────┐ ┌──────────┐
57
+ │Filesystem│ │ GitHub │ │PostgreSQL│ ... more
58
+ │ Server │ │ Server │ │ Server │
59
+ └──────────┘ └──────────┘ └──────────┘
60
+ ```
61
+
62
+ ## Features
63
+
64
+ - **Unified API endpoint** — one URL for all your MCP tools, auto-routed by tool name
65
+ - **Every MCP transport** — `stdio`, `streamable-http` (current spec), legacy `sse` (HTTP+SSE) and `websocket` upstream servers, with per-server headers for upstream auth
66
+ - **Automatic reconnect** — crashed or disconnected servers are reconnected with exponential backoff + jitter; state is visible in `/servers`, `/health`, the dashboard and Prometheus
67
+ - **Authentication** — API key (constant-time compare), JWT (HS256/384/512), or no-auth; misconfiguration fails closed
68
+ - **Rate limiting** — per-key sliding-window counter, with standard `X-RateLimit-*` headers
69
+ - **Concurrency limits** — per-server `maxConcurrency`, queued requests count against `timeout`
70
+ - **Health monitoring** — periodic MCP `ping` health checks with latency (every 30 s, configurable)
71
+ - **Metrics** — Prometheus-compatible `/metrics` endpoint (monotonic counters) + JSON aggregation
72
+ - **Config hot reload** — servers, API keys / auth, rate limits, CORS and reconnect policy apply without a restart (disable with `--no-watch`)
73
+ - **Optional auth for health & metrics** — keep `/health` and `/metrics` public (default) or put them behind auth; the dashboard asks for a key
74
+ - **Tool discovery** — `GET /api/v1/tools` lists all tools across all servers
75
+ - **YAML/JSON config** — simple, declarative configuration with env var overrides
76
+ - **Docker-ready** — official Docker image, Compose examples included
77
+ - **TypeScript SDK** — embed the gateway as a library in your own project
78
+
79
+ ## Quick Start
80
+
81
+ ### Install
82
+
83
+ ```bash
84
+ npm install -g @winstonsayno/mcp-gateway
85
+ # or
86
+ npx @winstonsayno/mcp-gateway init
87
+ ```
88
+
89
+ ### Configure
90
+
91
+ ```bash
92
+ # Generate a default config file
93
+ mcp-gateway init
94
+
95
+ # Edit mcp-gateway.yml to add your servers
96
+ ```
97
+
98
+ ```yaml
99
+ # mcp-gateway.yml
100
+ port: 4000
101
+
102
+ servers:
103
+ - id: filesystem
104
+ name: Filesystem
105
+ transport: stdio
106
+ command: npx
107
+ args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
108
+
109
+ - id: github
110
+ name: GitHub
111
+ transport: stdio
112
+ command: npx
113
+ args: ["-y", "@modelcontextprotocol/server-github"]
114
+ env:
115
+ GITHUB_PERSONAL_ACCESS_TOKEN: "${GITHUB_TOKEN}"
116
+ ```
117
+
118
+ ### Run
119
+
120
+ ```bash
121
+ mcp-gateway start
122
+ # → mcp-gateway listening on http://0.0.0.0:4000
123
+ # → ✓ Filesystem — 8 tools available
124
+ # → ✓ GitHub — 26 tools available
125
+ ```
126
+
127
+ ### Call a Tool
128
+
129
+ ```bash
130
+ # List all available tools
131
+ curl http://localhost:4000/api/v1/tools
132
+
133
+ # Call a tool (auto-routes to the right server)
134
+ curl -X POST http://localhost:4000/api/v1/tools/call \
135
+ -H "Content-Type: application/json" \
136
+ -d '{"tool": "read_file", "arguments": {"path": "/tmp/hello.txt"}}'
137
+
138
+ # With authentication
139
+ curl -X POST http://localhost:4000/api/v1/tools/call \
140
+ -H "Authorization: Bearer your-api-key" \
141
+ -H "Content-Type: application/json" \
142
+ -d '{"tool": "create_issue", "server": "github", "arguments": {"title": "Bug report", "body": "..."}}'
143
+ ```
144
+
145
+ ## API Reference
146
+
147
+ | Method | Path | Description |
148
+ |--------|------|-------------|
149
+ | `GET` | `/api/v1/health` | Gateway health and server summary |
150
+ | `GET` | `/api/v1/servers` | List all registered servers |
151
+ | `GET` | `/api/v1/servers/:id` | Get server details and tools |
152
+ | `GET` | `/api/v1/tools` | List all tools (filterable by `?server=` or `?tag=`) |
153
+ | `POST` | `/api/v1/tools/call` | Invoke a tool |
154
+ | `POST` | `/api/v1/servers/:id/reconnect` | Reconnect a server now (resets backoff) |
155
+ | `GET` | `/api/v1/health/live` | Liveness probe — always public, returns only `{"status":"ok"}` |
156
+ | `GET` | `/api/v1/metrics` | Aggregated metrics (JSON or Prometheus) |
157
+ | `GET` | `/api/v1/requests` | Recent request log (`?limit=`, max 500) |
158
+
159
+ `/health` and `/metrics` are unauthenticated by default; set `auth.protect.health` / `auth.protect.metrics`
160
+ to require auth for them too (`/health/live` always stays public for Docker / Kubernetes probes).
161
+ Every other route requires auth when it is enabled.
162
+ `/metrics` returns JSON by default (`?window=<ms>`); with `monitor.prometheus: true` it returns the
163
+ Prometheus text format when the client asks for `text/plain` (as Prometheus does) or passes `?format=prometheus`.
164
+
165
+ `POST /api/v1/tools/call` responses:
166
+
167
+ | Status | Meaning |
168
+ |--------|---------|
169
+ | `200` | Tool returned a result |
170
+ | `400` | Invalid body (`tool` must be a string, `arguments` an object) or malformed JSON |
171
+ | `404` | Unknown tool or server |
172
+ | `409` | Tool name is exposed by several servers — pass `"server"` to choose |
173
+ | `429` | Rate limited (see `Retry-After`) |
174
+ | `502` | The MCP server returned an error |
175
+ | `503` | Server is not connected (body has `status`, e.g. `reconnecting`; `Retry-After` when a retry is scheduled) |
176
+ | `504` | The MCP server did not answer within `timeout` |
177
+
178
+ ## Configuration Reference
179
+
180
+ ```yaml
181
+ port: 4000 # HTTP port (env: MCP_GATEWAY_PORT)
182
+ host: 0.0.0.0 # Bind address (env: MCP_GATEWAY_HOST)
183
+ logLevel: info # debug | info | warn | error
184
+
185
+ auth:
186
+ strategy: api-key # none | api-key | jwt (oauth2 is not implemented and is rejected)
187
+ apiKeys:
188
+ - "your-secret-key"
189
+ protect:
190
+ health: false # true → /api/v1/health requires auth
191
+ metrics: false # true → /api/v1/metrics requires auth (configure your scraper)
192
+
193
+ reconnect: # automatic reconnect of crashed / disconnected servers
194
+ enabled: true
195
+ initialDelayMs: 1000 # first retry delay
196
+ maxDelayMs: 60000 # backoff cap
197
+ multiplier: 2 # delay *= multiplier after each failure
198
+ jitter: 0.2 # ±20 % randomisation
199
+ maxAttempts: 0 # 0 = retry forever; else give up (status: offline)
200
+
201
+ healthCheckIntervalMs: 30000 # MCP ping interval
202
+ dashboard:
203
+ enabled: true # serve /dashboard
204
+
205
+ rateLimit:
206
+ limit: 100 # Max requests per window
207
+ windowSeconds: 60 # Window duration
208
+ perKey: true # Per-key or global
209
+
210
+ monitor:
211
+ requestLog: true # Log all requests
212
+ prometheus: true # Enable Prometheus /metrics
213
+ retentionHours: 24 # Metrics retention
214
+
215
+ corsOrigins:
216
+ - "https://your-app.com"
217
+
218
+ servers:
219
+ - id: my-server # Unique identifier
220
+ name: My Server # Display name
221
+ transport: stdio # stdio | streamable-http | sse | websocket
222
+ command: npx
223
+ args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
224
+ env:
225
+ MY_VAR: "${ENV_VAR}" # Environment variable substitution
226
+ tags: [files, local]
227
+ enabled: true
228
+ timeout: 30000 # ms, includes time queued behind maxConcurrency
229
+ maxConcurrency: 10 # max in-flight tool calls for this server
230
+ reconnect: # optional per-server override of the reconnect block
231
+ maxAttempts: 5
232
+
233
+ - id: remote
234
+ name: Remote server
235
+ transport: streamable-http # MCP 2025-03-26+ (POST + optional SSE responses, Mcp-Session-Id)
236
+ url: https://mcp.example.com/mcp
237
+ headers:
238
+ Authorization: "Bearer ${REMOTE_MCP_TOKEN}" # ${VAR} expanded from the gateway's env
239
+
240
+ - id: legacy
241
+ name: Legacy SSE server
242
+ transport: sse # MCP 2024-11-05 HTTP+SSE (GET stream + POST to the announced endpoint)
243
+ url: http://localhost:8080/sse
244
+
245
+ - id: socket
246
+ name: WebSocket server
247
+ transport: websocket # one JSON-RPC message per frame, "mcp" subprotocol
248
+ url: ws://localhost:8081
249
+ subprotocol: mcp # "" to request no subprotocol
250
+ ```
251
+
252
+ ### Hot reload
253
+
254
+ With `mcp-gateway start` the config file is watched (disable with `--no-watch`). On save:
255
+
256
+ | Applied immediately | Needs a restart |
257
+ |---------------------|-----------------|
258
+ | `servers` (added / changed / removed / disabled) | `port`, `host` |
259
+ | `auth` (strategy, keys, JWT secret, `protect`) | `monitor.retentionHours` |
260
+ | `rateLimit` (counters reset when it changes) | `healthCheckIntervalMs` |
261
+ | `corsOrigins`, `monitor.requestLog`, `monitor.prometheus` | `dashboard` |
262
+ | `reconnect`, `logLevel` | |
263
+
264
+ An invalid file is rejected and the running config is kept. `MCP_GATEWAY_*` env overrides keep precedence.
265
+
266
+ ### Reconnect & server status
267
+
268
+ Each server's `health.status` is one of `online`, `degraded` (connected, but the health ping failed),
269
+ `reconnecting` (lost — a retry is scheduled or running), `offline` (gave up, or reconnect disabled) or `unknown`.
270
+ `GET /api/v1/servers/:id` also returns `health.reconnect` (`state`, `attempt`, `nextAttemptAt`, `lastError`,
271
+ `reconnects`) and `session` (`transport`, negotiated `protocolVersion`, `serverInfo`, `connectedAt`).
272
+
273
+ Prometheus series added: `mcp_gateway_server_up`, `mcp_gateway_server_status{status=…}`,
274
+ `mcp_gateway_server_reconnects_total`, `mcp_gateway_server_reconnect_attempt`, `mcp_gateway_server_ping_ms`.
275
+
276
+ ## Docker
277
+
278
+ ```bash
279
+ # Pull and run
280
+ docker run -p 4000:4000 \
281
+ -v $(pwd)/mcp-gateway.yml:/app/mcp-gateway.yml \
282
+ -e GITHUB_TOKEN=ghp_... \
283
+ ghcr.io/harrisonCN/mcp-gateway:latest
284
+
285
+ # Or with Docker Compose (gateway + Prometheus; see examples/docker)
286
+ cd examples/docker
287
+ GATEWAY_API_KEY=change-me docker compose up
288
+ ```
289
+
290
+ The image's `HEALTHCHECK` uses the always-public `/api/v1/health/live`, so it keeps working when
291
+ `auth.protect.health` is on.
292
+
293
+ ## Dashboard
294
+
295
+ Open `http://localhost:4000/dashboard`. When auth is enabled, paste an API key (or JWT) in the header:
296
+ it is kept in the browser tab (`sessionStorage`, or `localStorage` with “remember”) and sent as
297
+ `Authorization: Bearer …` on every API call. The page itself contains no data; set `dashboard.enabled: false`
298
+ to stop serving it.
299
+
300
+ ## Embed as a Library
301
+
302
+ ```typescript
303
+ import { Gateway, loadConfig } from 'mcp-gateway';
304
+
305
+ const config = await loadConfig('./mcp-gateway.yml');
306
+ const gateway = new Gateway(config);
307
+
308
+ await gateway.start();
309
+ // Gateway is now running at http://localhost:4000
310
+
311
+ // Graceful shutdown
312
+ process.on('SIGTERM', () => gateway.stop());
313
+ ```
314
+
315
+ ## What's New (unreleased)
316
+
317
+ | Feature | Description |
318
+ |---------|-------------|
319
+ | **Remote transports** | `streamable-http`, `sse` and `websocket` servers are now routable (checked against the official MCP SDK servers in the test suite) |
320
+ | **Auto reconnect** | Exponential backoff with jitter, `reconnecting` status, manual `POST /servers/:id/reconnect`, Prometheus series |
321
+ | **Health pings** | Real MCP `ping` health checks with latency; `degraded` when a connected server stops answering |
322
+ | **Tool list updates** | `notifications/tools/list_changed` refreshes the tool registry |
323
+ | **Protected health/metrics** | `auth.protect.health` / `auth.protect.metrics`, public `/health/live`, dashboard API-key support |
324
+ | **More hot reload** | Auth, API keys, rate limits, CORS, reconnect policy |
325
+
326
+ ## What's New in v0.2.0
327
+
328
+ | Feature | Description |
329
+ |---------|-------------|
330
+ | **SSE Transport** | Transport class (`src/transport/sse.ts`); routable since the unreleased version |
331
+ | **WebSocket Transport** | Transport class (`src/transport/websocket.ts`); routable since the unreleased version |
332
+ | **Config Hot Reload** | Edit the server list in `mcp-gateway.yml` without restarting |
333
+ | **Request Tracing** | `X-Request-Id` on every request & response |
334
+ | **CORS Middleware** | Configurable cross-origin support |
335
+ | **Web Dashboard** | Live monitoring UI at `/dashboard` |
336
+ | **4 Bug Fixes** | Concurrency, id collision, handle leaks, timeouts |
337
+
338
+ ## Roadmap
339
+
340
+ | Feature | Status |
341
+ |---------|--------|
342
+ | stdio transport | ✅ Done |
343
+ | SSE transport | ✅ Done |
344
+ | WebSocket transport | ✅ Done |
345
+ | Streamable HTTP transport | ✅ Done (standalone GET notification stream not yet used) |
346
+ | Automatic reconnect with backoff | ✅ Done |
347
+ | Config hot reload | ✅ Done (v0.2.0) |
348
+ | Web dashboard UI | ✅ Done (v0.2.0) |
349
+ | Redis-backed rate limiting | 📋 Planned |
350
+ | OAuth2 / OIDC auth | 📋 Planned |
351
+ | Tool-level access control (RBAC) | 📋 Planned |
352
+ | Request replay & debugging | 📋 Planned |
353
+ | Multi-tenant mode | 📋 Planned |
354
+ | OpenTelemetry tracing | 📋 Planned |
355
+
356
+ ## Contributing
357
+
358
+ Contributions are welcome! See [CONTRIBUTING.md](docs/CONTRIBUTING.md).
359
+
360
+ ```bash
361
+ git clone https://github.com/HarrisonCN/mcp-gateway.git
362
+ cd mcp-gateway
363
+ npm install
364
+ npm run typecheck && npm test
365
+ npm run dev -- start -c examples/basic/mcp-gateway.yml
366
+ ```
367
+
368
+ ## License
369
+
370
+ MIT © 2026 [HarrisonCN](https://github.com/HarrisonCN)
371
+
372
+ ---
373
+
374
+ <div align="center">
375
+ <sub>
376
+ Built for the agentic era · If this helps you, please ⭐ star the repo
377
+ </sub>
378
+ </div>
@@ -0,0 +1,27 @@
1
+ # mcp-gateway Dashboard
2
+
3
+ A lightweight, zero-dependency web dashboard for monitoring your mcp-gateway instance in real time.
4
+
5
+ ## Features
6
+
7
+ - Live server status (online / degraded / reconnecting / offline) with next retry and reconnect count
8
+ - Tool inventory across all registered servers
9
+ - Request log with method, tool name, duration, and HTTP status
10
+ - Aggregate metrics: total requests, error rate, average / p95 latency, uptime
11
+ - Works with auth enabled: paste an API key or JWT in the header (kept in `sessionStorage`, or `localStorage` with “remember”), sent as `Authorization: Bearer …`
12
+ - Auto-refreshes every 10 seconds; manual refresh button available
13
+
14
+ ## Access
15
+
16
+ When the gateway is running, the dashboard is served at:
17
+
18
+ ```
19
+ http://localhost:4000/dashboard
20
+ ```
21
+
22
+ It reads data exclusively from the gateway's own REST API (`/api/v1/*`), so no additional backend is needed.
23
+ The page itself is a static shell without data; disable it with `dashboard: { enabled: false }`.
24
+
25
+ ## Screenshot
26
+
27
+ The dashboard uses a GitHub-dark colour scheme and is fully responsive down to mobile widths.