@winstonsayno/mcp-gateway 1.0.1 → 1.2.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.
- package/CHANGELOG.md +96 -0
- package/README.md +46 -10
- package/dashboard/README.md +64 -15
- package/dashboard/index.html +1568 -419
- package/dist/auth/middleware.d.ts +29 -0
- package/dist/auth/middleware.d.ts.map +1 -1
- package/dist/auth/middleware.js +142 -18
- package/dist/auth/middleware.js.map +1 -1
- package/dist/cli.js +54 -1
- package/dist/cli.js.map +1 -1
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +110 -2
- package/dist/config/loader.js.map +1 -1
- package/dist/gateway/api.d.ts +5 -0
- package/dist/gateway/api.d.ts.map +1 -1
- package/dist/gateway/api.js +86 -4
- package/dist/gateway/api.js.map +1 -1
- package/dist/gateway/index.d.ts +10 -1
- package/dist/gateway/index.d.ts.map +1 -1
- package/dist/gateway/index.js +82 -5
- package/dist/gateway/index.js.map +1 -1
- package/dist/gateway/live.d.ts +85 -0
- package/dist/gateway/live.d.ts.map +1 -0
- package/dist/gateway/live.js +206 -0
- package/dist/gateway/live.js.map +1 -0
- package/dist/index.d.ts +11 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp/catalog.d.ts +1 -1
- package/dist/mcp/endpoint.d.ts +46 -1
- package/dist/mcp/endpoint.d.ts.map +1 -1
- package/dist/mcp/endpoint.js +312 -10
- package/dist/mcp/endpoint.js.map +1 -1
- package/dist/middleware/error-handler.d.ts +10 -1
- package/dist/middleware/error-handler.d.ts.map +1 -1
- package/dist/middleware/error-handler.js +17 -3
- package/dist/middleware/error-handler.js.map +1 -1
- package/dist/monitor/index.d.ts.map +1 -1
- package/dist/monitor/index.js +5 -0
- package/dist/monitor/index.js.map +1 -1
- package/dist/proxy/index.d.ts +15 -0
- package/dist/proxy/index.d.ts.map +1 -1
- package/dist/proxy/index.js +44 -4
- package/dist/proxy/index.js.map +1 -1
- package/dist/security/headers.d.ts +23 -0
- package/dist/security/headers.d.ts.map +1 -0
- package/dist/security/headers.js +71 -0
- package/dist/security/headers.js.map +1 -0
- package/dist/security/lockout.d.ts +41 -0
- package/dist/security/lockout.d.ts.map +1 -0
- package/dist/security/lockout.js +117 -0
- package/dist/security/lockout.js.map +1 -0
- package/dist/security/network.d.ts +31 -0
- package/dist/security/network.d.ts.map +1 -0
- package/dist/security/network.js +147 -0
- package/dist/security/network.js.map +1 -0
- package/dist/security/posture.d.ts +18 -0
- package/dist/security/posture.d.ts.map +1 -0
- package/dist/security/posture.js +73 -0
- package/dist/security/posture.js.map +1 -0
- package/dist/security/redact.d.ts +34 -0
- package/dist/security/redact.d.ts.map +1 -0
- package/dist/security/redact.js +115 -0
- package/dist/security/redact.js.map +1 -0
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +4 -2
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/types.d.ts +81 -2
- package/dist/utils/types.d.ts.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,102 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
9
9
|
|
|
10
10
|
## [Unreleased]
|
|
11
11
|
|
|
12
|
+
## [1.2.0] - 2026-10-07
|
|
13
|
+
|
|
14
|
+
Security hardening and more of the MCP spec on `/mcp`. Every new protection that could reject traffic that 1.1
|
|
15
|
+
accepted is **opt-in**; see *Upgrade notes*.
|
|
16
|
+
|
|
17
|
+
### Security
|
|
18
|
+
- **Hashed API keys**: `auth.apiKeys` entries (plain strings or `key`) may be `sha256:<64 hex>` digests, so the config
|
|
19
|
+
file never holds a usable key. Digest and plain form of a key share the same client id. New CLI commands
|
|
20
|
+
`mcp-gateway gen-key` (random `mgw_…` key + digest, `--bytes`, `--prefix`, `--json`) and
|
|
21
|
+
`mcp-gateway hash-key [key]` (reads stdin when no argument is given). Malformed `sha256:` values are a config error.
|
|
22
|
+
- **Key expiry / disabling**: `expiresAt` (ISO 8601) and `disabled` on object keys → `401`; open `/mcp` sessions of
|
|
23
|
+
such keys end on the next reload. Keys expiring within 7 days produce a warning.
|
|
24
|
+
- **JWT hardening** (`auth.jwt`): `issuer`, `audience` (string or list), `algorithms` allowlist, `clockToleranceSeconds`,
|
|
25
|
+
`requireExp`, `maxTokenAgeSeconds`; verification keys from `jwtSecret` (HS*), a PEM `publicKey` (RS/PS/ES/EdDSA) or
|
|
26
|
+
a `jwksUrl` (HTTPS, cached `jwksCacheSeconds`, refetch on unknown `kid`). HMAC and asymmetric algorithms are never
|
|
27
|
+
mixed (algorithm-confusion protection); exactly one key source must be configured.
|
|
28
|
+
- **Security headers** (`security.headers`, default on): `X-Content-Type-Options: nosniff`, `X-Frame-Options: DENY`,
|
|
29
|
+
`Referrer-Policy: no-referrer`, `Cross-Origin-Opener-Policy`, `Cross-Origin-Resource-Policy`, a deny-all CSP on API
|
|
30
|
+
responses and a dashboard CSP that allows exactly its inline script by SHA-256 hash. Optional `security.hsts`.
|
|
31
|
+
- **Network guards**: `security.ipAllowlist` (IPv4 / IPv6 / CIDR, via `net.BlockList`), `security.allowedHosts` (Host
|
|
32
|
+
header allowlist with `*.domain` wildcards), `security.trustProxy` (Express "trust proxy": decides `req.ip` for rate
|
|
33
|
+
limits, lockout, allowlist and logs). Liveness / readiness probes stay reachable.
|
|
34
|
+
- **DNS-rebinding protection** (`security.dnsRebindingProtection`): Host must be a loopback name / the bind address
|
|
35
|
+
(or `allowedHosts`), and `/mcp` accepts browser `Origin`s only when same-origin, loopback or explicitly listed.
|
|
36
|
+
- **Size limits**: `security.maxBodyBytes` (default 10 MiB as before, now configurable for REST and `/mcp`) and
|
|
37
|
+
`security.maxToolArgumentsBytes` for `tools/call`, `prompts/get` and `completion/complete` arguments (`413` on REST,
|
|
38
|
+
`-32602` on `/mcp`).
|
|
39
|
+
- **Brute-force lockout** (`security.authLockout`): after `maxFailures` (10) failed authentications from one IP within
|
|
40
|
+
`windowSeconds` (300), the IP gets `429` + `Retry-After` for `lockoutSeconds` (900) on every authenticated route,
|
|
41
|
+
including `/mcp` and `/api/v1/events`.
|
|
42
|
+
- **Secret redaction**: log lines and metadata, recorded `errorMessage`s (request log, audit log, `/requests`,
|
|
43
|
+
`/stats`, `/events`, dashboard) mask Bearer / Basic tokens, JWTs, common provider keys (OpenAI, Anthropic, GitHub,
|
|
44
|
+
GitLab, Slack, AWS, Google), `password=` / `token=` / `api_key=` pairs, URL credentials and values under
|
|
45
|
+
secret-looking keys. `GET /servers` now also masks secret-looking stdio `args` (`--token x`, `--api-key=x`). Extra
|
|
46
|
+
patterns via `security.redactPatterns`.
|
|
47
|
+
- **Secure-defaults check**: startup warnings / hints (auth off on a public bind, DNS rebinding, `/mcp` open to any
|
|
48
|
+
origin, plain-text / short / expiring keys, JWT without iss / aud / exp, no lockout, `corsOrigins: ["*"]` with auth,
|
|
49
|
+
headers disabled, error details exposed). `mcp-gateway validate` prints them; `--strict` exits with code 2.
|
|
50
|
+
- `GET /api/v1/security`: auth strategy, warnings, key hygiene counts and upcoming expiries, JWT settings, effective
|
|
51
|
+
security settings and lockout state — never key material; scoped clients get `403`. The dashboard's *Connect*
|
|
52
|
+
page shows it as a *Security posture* card (demo mock updated).
|
|
53
|
+
- `SECURITY.md`: hardening table and scope; `docs/deployment.md` security checklist extended.
|
|
54
|
+
|
|
55
|
+
### Fixed (security)
|
|
56
|
+
- Unexpected errors (500) no longer return their message and stack trace whenever `NODE_ENV` was not `production`
|
|
57
|
+
(the default for `npm i -g` installs). They are shown only with `NODE_ENV=development` or the new
|
|
58
|
+
`security.exposeErrorDetails: true`. Deliberate `GatewayError` details are unchanged.
|
|
59
|
+
|
|
60
|
+
### Added (MCP)
|
|
61
|
+
- **Progress notifications**: a single `tools/call` with `params._meta.progressToken` from a client that accepts
|
|
62
|
+
`text/event-stream` is forwarded with a gateway-generated token; upstream `notifications/progress` are mapped back
|
|
63
|
+
and the reply becomes an SSE stream (progress events, then the result). Plain JSON otherwise.
|
|
64
|
+
- **Logging**: `logging` capability, `logging/setLevel` per session; upstream `notifications/message` are forwarded to
|
|
65
|
+
sessions in scope at or above their level (`logger: "<serverId>/<logger>"`), and upstream servers announcing
|
|
66
|
+
`logging` are set to the most verbose level any session requested (re-applied after reconnects).
|
|
67
|
+
- **Completion**: `completions` capability and `completion/complete` routed by prompt (exposed name translated back)
|
|
68
|
+
or resource template / URI; servers without the capability answer an empty completion.
|
|
69
|
+
- **Resource subscriptions**: `resources.subscribe` capability, `resources/subscribe` / `resources/unsubscribe`
|
|
70
|
+
(routed like `resources/read`; `-32601` when the owning server does not support subscriptions) and
|
|
71
|
+
`notifications/resources/updated` forwarding. One upstream subscription per (server, URI) is shared and
|
|
72
|
+
reference-counted across sessions, released when the last session unsubscribes or ends, and restored after an
|
|
73
|
+
upstream reconnect.
|
|
74
|
+
- Proxy: `RequestOptions.onProgress`, `connected` and `notification` events.
|
|
75
|
+
- Library exports: `hashApiKey`, `isHashedKey`, `buildJwtVerifier`, `HMAC_ALGORITHMS`, `ASYMMETRIC_ALGORITHMS`,
|
|
76
|
+
`redactString`, `redactValue`, `redactArgs`, `configureRedaction`, `securityWarnings`, `AuthLockout`,
|
|
77
|
+
`createIpMatcher`, `hostAllowed`, `dashboardCsp`, `inlineScriptHashes`, `LOG_LEVELS`; types `SecurityConfig`,
|
|
78
|
+
`JwtConfig`, `AuthLockoutConfig`, `SecurityWarning`, `McpLogLevel`, `RequestOptions`, `ProgressUpdate`.
|
|
79
|
+
|
|
80
|
+
### Changed
|
|
81
|
+
- `auth.jwtSecret` is no longer required for `strategy: jwt` when `auth.jwt.publicKey` or `auth.jwt.jwksUrl` is set.
|
|
82
|
+
- `initialize` on `/mcp` now also announces `logging`, `completions` and `resources.subscribe` (clients ignore
|
|
83
|
+
capabilities they do not use).
|
|
84
|
+
- JWT verification failures are logged with the client IP.
|
|
85
|
+
|
|
86
|
+
### Upgrade notes
|
|
87
|
+
- No configuration changes are required. New behaviour that is on by default: security headers (disable with
|
|
88
|
+
`security.headers: false`), secret redaction in logs / recorded error messages, hidden 500 error details, and the
|
|
89
|
+
extra capabilities on `/mcp`.
|
|
90
|
+
- If you embedded the dashboard in an `<iframe>` on another origin, set `security.headers: false` (the new
|
|
91
|
+
`frame-ancestors 'none'` / `X-Frame-Options: DENY` block that).
|
|
92
|
+
- Tests or tooling that relied on 500 responses carrying `error.message` / `details` outside production need
|
|
93
|
+
`NODE_ENV=development` or `security.exposeErrorDetails: true`.
|
|
94
|
+
- Everything else (`ipAllowlist`, `allowedHosts`, `dnsRebindingProtection`, `authLockout`, `maxToolArgumentsBytes`,
|
|
95
|
+
`hsts`, `trustProxy`, JWT checks, key expiry) is opt-in.
|
|
96
|
+
|
|
97
|
+
## [1.1.0] - 2026-10-07
|
|
98
|
+
|
|
99
|
+
### Added
|
|
100
|
+
- **Dashboard v2** (`/dashboard`, still one self-contained HTML file: no build step, no CDN, no runtime dependencies):
|
|
101
|
+
- First-run **guided onboarding** (dismissible, reopen with **?**): connect with an API key (tested live), see the upstream servers, try a tool (`tools` list → form generated from the tool's JSON schema, or raw JSON → call → result), and copy-paste snippets for Claude Desktop (via `mcp-remote`), Cursor, Claude Code, the JS and Kotlin clients and curl, all pointing at this gateway's `/mcp` URL.
|
|
102
|
+
- **Live overview**: requests/min, p50 / p95 / p99 latency, error rate and servers-online cards with sparklines; hand-drawn SVG charts for request rate, latency and error rate (5 m / 15 m / 1 h / 6 h windows, hover / touch tooltips); top tools; usage per API key; live request stream; server health.
|
|
103
|
+
- Servers page with tool chips and one-click reconnect; Playground; request history with filters and cursor paging (cards on phones); Connect page.
|
|
104
|
+
- English / 中文 toggle, dark / light theme, responsive down to phone widths with a bottom tab bar, keyboard navigation (arrow-key tabs, focus-trapped dialog, Esc), `prefers-reduced-motion`, View Transitions, skeleton loaders; animations use transform / opacity only.
|
|
105
|
+
- `GET /api/v1/stats`: windowed time series (count, errors, p50 / p95 per bucket), summary, top tools, per-server and per-client usage (`?window=`, `?bucket=`).
|
|
106
|
+
- `GET /api/v1/events`: Server-Sent Events stream with a `request` event per recorded call and a `snapshot` (server health + summary) every 2 s; heartbeats, max 50 concurrent streams, closed on shutdown. Both new endpoints require auth and show restricted clients only their own calls. The dashboard falls back to polling every 2 s when the stream is unavailable.
|
|
107
|
+
|
|
12
108
|
## [1.0.1] - 2026-10-07
|
|
13
109
|
|
|
14
110
|
Bug-fix release; no API or configuration changes.
|
package/README.md
CHANGED
|
@@ -21,6 +21,8 @@ Route · Authenticate · Rate-limit · Monitor — all your [Model Context Proto
|
|
|
21
21
|
|
|
22
22
|
---
|
|
23
23
|
|
|
24
|
+
> **Live demo:** try the dashboard with simulated traffic — <https://harrisoncn.github.io/mcp-gateway/> (runs entirely in your browser).
|
|
25
|
+
|
|
24
26
|
## The Problem
|
|
25
27
|
|
|
26
28
|
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:
|
|
@@ -63,10 +65,11 @@ As [MCP](https://modelcontextprotocol.io) becomes the standard protocol for AI a
|
|
|
63
65
|
## Features
|
|
64
66
|
|
|
65
67
|
- **Unified API endpoint** — one URL for all your MCP tools, auto-routed by tool name
|
|
66
|
-
- **MCP endpoint for clients** — `/mcp` speaks MCP Streamable HTTP (2025-06-18 / 2025-03-26), so Claude Code, Cursor or any MCP client sees every upstream tool through one server, with the same auth, limits and metrics
|
|
68
|
+
- **MCP endpoint for clients** — `/mcp` speaks MCP Streamable HTTP (2025-06-18 / 2025-03-26), so Claude Code, Cursor or any MCP client sees every upstream tool through one server, with the same auth, limits and metrics — including progress notifications, cancellation, logging, completions and resource subscriptions
|
|
67
69
|
- **Every MCP transport** — `stdio`, `streamable-http` (current spec), legacy `sse` (HTTP+SSE) and `websocket` upstream servers, with per-server headers for upstream auth
|
|
68
70
|
- **Automatic reconnect** — crashed or disconnected servers are reconnected with exponential backoff + jitter; state is visible in `/servers`, `/health`, the dashboard and Prometheus
|
|
69
|
-
- **Authentication** — API
|
|
71
|
+
- **Authentication** — API keys (constant-time compare, storable as `sha256:` digests, with expiry), JWT (HMAC secret, PEM public key or JWKS URL; issuer / audience / exp checks), or no-auth; misconfiguration fails closed
|
|
72
|
+
- **Hardening** — security headers + hash-based CSP, IP allowlist, Host / Origin checks against DNS rebinding, body and argument size limits, brute-force lockout, secret redaction in logs and history, startup security warnings (`mcp-gateway validate --strict`)
|
|
70
73
|
- **Rate limiting** — per-key sliding-window counter, with standard `X-RateLimit-*` headers
|
|
71
74
|
- **Per-key scopes** — restrict an API key (or a JWT via claims) to some servers / tools and give it its own rate limit; enforced on REST and `/mcp`
|
|
72
75
|
- **Concurrency limits** — per-server `maxConcurrency`, queued requests count against `timeout`
|
|
@@ -199,9 +202,9 @@ What the endpoint does:
|
|
|
199
202
|
|
|
200
203
|
| | |
|
|
201
204
|
|---|---|
|
|
202
|
-
| `POST /mcp` | JSON-RPC: `initialize`, `ping`, `tools/list` (paginated), `tools/call`, `resources/list`, `resources/templates/list`, `resources/read`, `prompts/list`, `prompts/get`, notifications (incl. `notifications/cancelled`). Batches are accepted. Responses are `application/json
|
|
203
|
-
| `GET /mcp` | SSE stream for server→client notifications: `notifications/tools/list_changed`, `notifications/resources/list_changed` and `notifications/prompts/list_changed` are sent when the aggregated list a session sees changes (a server announces changes, connects, is removed by hot reload, …). |
|
|
204
|
-
| Resources & prompts | Resource URIs are passed through unchanged; when two servers list the same URI the lowest server id wins. `resources/read` is routed by exact URI, then by resource template, then to the only server with resources. Prompt names follow `toolNaming` like tools. `resources/subscribe` is
|
|
205
|
+
| `POST /mcp` | JSON-RPC: `initialize`, `ping`, `tools/list` (paginated), `tools/call`, `resources/list`, `resources/templates/list`, `resources/read`, `resources/subscribe` / `unsubscribe`, `prompts/list`, `prompts/get`, `logging/setLevel`, `completion/complete`, notifications (incl. `notifications/cancelled`). Batches are accepted. Responses are `application/json`; a single `tools/call` with `_meta.progressToken` switches to an SSE reply when the upstream reports progress (`notifications/progress`, then the result). |
|
|
206
|
+
| `GET /mcp` | SSE stream for server→client notifications: `notifications/tools/list_changed`, `notifications/resources/list_changed` and `notifications/prompts/list_changed` are sent when the aggregated list a session sees changes (a server announces changes, connects, is removed by hot reload, …); `notifications/resources/updated` for subscribed URIs; upstream `notifications/message` at or above the session's `logging/setLevel` level (`logger` = `<serverId>/<logger>`). |
|
|
207
|
+
| Resources & prompts | Resource URIs are passed through unchanged; when two servers list the same URI the lowest server id wins. `resources/read` is routed by exact URI, then by resource template, then to the only server with resources. Prompt names follow `toolNaming` like tools. `resources/subscribe` is routed the same way; sessions share one upstream subscription per URI, restored after reconnects. `completion/complete` is routed by prompt name or resource template. |
|
|
205
208
|
| `DELETE /mcp` | Ends the session. |
|
|
206
209
|
| Sessions | `initialize` returns `Mcp-Session-Id`; later requests must send it (`400` if missing, `404` if unknown or expired). A session is bound to the API key / JWT subject that created it. Idle sessions expire after `mcp.sessionIdleTimeoutSeconds`. |
|
|
207
210
|
| Tool names | `toolNaming: auto` (default) keeps a tool's name unless two servers expose the same name; then every copy becomes `<serverId>__<tool>`. `prefix` always uses `<serverId>__<tool>`. Ordering is deterministic (server id, then tool name). In `auto` mode the prefixed form is also accepted by `tools/call`. |
|
|
@@ -228,6 +231,8 @@ What the endpoint does:
|
|
|
228
231
|
| `GET` | `/api/v1/prompts` | Prompts of all servers (`?server=`) |
|
|
229
232
|
| `POST` | `/api/v1/prompts/get` | Get a prompt: `{"name": "...", "server"?: "...", "arguments"?: {...}}` |
|
|
230
233
|
| `GET` | `/api/v1/requests` | Request history, newest first (`?limit=` max 500, `server`, `tool`, `client`, `success`, `via`, `kind`, `since`, `until`, `cursor`) |
|
|
234
|
+
| `GET` | `/api/v1/stats` | Live dashboard data: time series (count, errors, p50/p95 per bucket), summary, top tools, per-server and per-key usage (`?window=`, `?bucket=` ms) |
|
|
235
|
+
| `GET` | `/api/v1/events` | Server-Sent Events: a `request` event per call, a `snapshot` (health + summary) every 2 s |
|
|
231
236
|
| `POST` `GET` `DELETE` | `/mcp` | MCP Streamable HTTP endpoint (see [above](#use-the-gateway-as-an-mcp-server-mcp)) |
|
|
232
237
|
|
|
233
238
|
`/health` and `/metrics` are unauthenticated by default; set `auth.protect.health` / `auth.protect.metrics`
|
|
@@ -290,11 +295,13 @@ auth:
|
|
|
290
295
|
strategy: api-key # none | api-key | jwt (oauth2 is not implemented and is rejected)
|
|
291
296
|
apiKeys:
|
|
292
297
|
- "your-secret-key" # full access
|
|
298
|
+
- "sha256:…" # a key stored as its digest (mcp-gateway gen-key / hash-key)
|
|
293
299
|
- key: "${APP_KEY}" # scoped key (see "Per-key scopes")
|
|
294
300
|
name: app
|
|
295
301
|
servers: ["github"]
|
|
296
302
|
tools: ["read_*"]
|
|
297
303
|
rateLimit: { limit: 30, windowSeconds: 60 }
|
|
304
|
+
expiresAt: "2027-01-01" # optional expiry; disabled: true switches a key off
|
|
298
305
|
protect:
|
|
299
306
|
health: false # true → /api/v1/health requires auth
|
|
300
307
|
metrics: false # true → /api/v1/metrics requires auth (configure your scraper)
|
|
@@ -321,6 +328,12 @@ monitor:
|
|
|
321
328
|
prometheus: true # Enable Prometheus /metrics
|
|
322
329
|
retentionHours: 24 # Metrics retention
|
|
323
330
|
|
|
331
|
+
security: # hardening (see docs/configuration.md#security)
|
|
332
|
+
authLockout: true # 429 for IPs with repeated auth failures
|
|
333
|
+
dnsRebindingProtection: false # true for a local gateway without auth
|
|
334
|
+
ipAllowlist: ["10.0.0.0/8"]
|
|
335
|
+
maxToolArgumentsBytes: 262144
|
|
336
|
+
|
|
324
337
|
corsOrigins:
|
|
325
338
|
- "https://your-app.com"
|
|
326
339
|
|
|
@@ -538,10 +551,18 @@ readinessProbe:
|
|
|
538
551
|
|
|
539
552
|
## Dashboard
|
|
540
553
|
|
|
541
|
-
Open `http://localhost:4000/dashboard`.
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
554
|
+
Open `http://localhost:4000/dashboard`. The first visit opens a short guided setup: connect with an API key,
|
|
555
|
+
see the upstream servers, call a tool from a form generated from its JSON schema, and copy a ready-made
|
|
556
|
+
config for Claude Desktop, Cursor, Claude Code, the JS / Kotlin clients or curl. Reopen it any time with the
|
|
557
|
+
**?** button. After that the dashboard shows live request rate, p50 / p95 latency, error rate, top tools,
|
|
558
|
+
usage per key, a live request stream, server health (with reconnect) and the filterable request history.
|
|
559
|
+
It is one static file with no build step and no CDN; English / 中文, dark / light, and it works on phones.
|
|
560
|
+
|
|
561
|
+

|
|
562
|
+
|
|
563
|
+
When auth is enabled the key is kept in the browser tab (`sessionStorage`, or `localStorage` with
|
|
564
|
+
“remember”) and sent as `Authorization: Bearer …` on every API call. The page itself contains no data; set
|
|
565
|
+
`dashboard.enabled: false` to stop serving it. See [dashboard/README.md](dashboard/README.md).
|
|
545
566
|
|
|
546
567
|
## Embed as a Library
|
|
547
568
|
|
|
@@ -558,6 +579,18 @@ await gateway.start();
|
|
|
558
579
|
process.on('SIGTERM', () => gateway.stop());
|
|
559
580
|
```
|
|
560
581
|
|
|
582
|
+
## What's New in v1.2
|
|
583
|
+
|
|
584
|
+
| Feature | Description |
|
|
585
|
+
|---------|-------------|
|
|
586
|
+
| **Hashed & expiring keys** | `auth.apiKeys` entries can be `sha256:<hex>` digests (`mcp-gateway gen-key`, `hash-key`) and carry `expiresAt` / `disabled` |
|
|
587
|
+
| **JWT hardening** | `auth.jwt`: `issuer`, `audience`, `algorithms` (HMAC / asymmetric never mixed), `clockToleranceSeconds`, `requireExp`, `maxTokenAgeSeconds`, PEM `publicKey` or cached `jwksUrl` (works with OAuth 2.0 / OIDC providers) |
|
|
588
|
+
| **`security` block** | headers + dashboard CSP, `hsts`, `trustProxy`, `ipAllowlist`, `allowedHosts`, `dnsRebindingProtection`, `maxBodyBytes`, `maxToolArgumentsBytes`, `authLockout`, `redactPatterns` — all hot reloadable |
|
|
589
|
+
| **Secure-defaults check** | startup warnings, `validate --strict`, `GET /api/v1/security` and a *Security posture* card in the dashboard |
|
|
590
|
+
| **More of MCP on `/mcp`** | `notifications/progress` (SSE replies), `logging/setLevel` + forwarded `notifications/message`, `completion/complete`, `resources/subscribe` / `unsubscribe` + `notifications/resources/updated` |
|
|
591
|
+
|
|
592
|
+
Details and upgrade notes: [CHANGELOG](CHANGELOG.md#120---2026-10-07).
|
|
593
|
+
|
|
561
594
|
## What's New in v1.0
|
|
562
595
|
|
|
563
596
|
| Feature | Description |
|
|
@@ -607,8 +640,11 @@ unknown fields). Deep imports, log format, the dashboard and the audit database
|
|
|
607
640
|
| JS / Kotlin clients, OpenAI / Anthropic tool schemas | ✅ Done (v1.0) |
|
|
608
641
|
| Resources & prompts passthrough, persistent audit log | ✅ Done (v1.0) |
|
|
609
642
|
| Stable API, docs, container image | ✅ Done (v1.0) |
|
|
643
|
+
| Security hardening (hashed keys, JWKS, lockout, DNS-rebinding guard, CSP) | ✅ Done (v1.2) |
|
|
644
|
+
| Progress, logging, completions, resource subscriptions on `/mcp` | ✅ Done (v1.2) |
|
|
610
645
|
| Redis-backed rate limiting | 📋 Planned |
|
|
611
|
-
| OAuth2 / OIDC auth |
|
|
646
|
+
| OAuth2 / OIDC auth | 🟡 JWT access tokens via `auth.jwt.jwksUrl` (v1.2); discovery / token introspection planned |
|
|
647
|
+
| Forwarding sampling / elicitation / roots requests to downstream clients | 📋 Planned |
|
|
612
648
|
| Tool-level access control | ✅ Done via per-key scopes (v0.6) |
|
|
613
649
|
| Request replay & debugging | 📋 Planned (history is available via the audit log) |
|
|
614
650
|
| Multi-tenant mode | 📋 Planned |
|
package/dashboard/README.md
CHANGED
|
@@ -1,27 +1,76 @@
|
|
|
1
1
|
# mcp-gateway Dashboard
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A guided, real-time web dashboard for mcp-gateway. It is **one self-contained file** (`index.html`):
|
|
4
|
+
no build step, no CDN, no runtime dependencies. Charts are hand-drawn SVG.
|
|
4
5
|
|
|
5
|
-
|
|
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
|
|
6
|
+

|
|
13
7
|
|
|
14
8
|
## Access
|
|
15
9
|
|
|
16
|
-
When the gateway is running, the dashboard is served at:
|
|
17
|
-
|
|
18
10
|
```
|
|
19
11
|
http://localhost:4000/dashboard
|
|
20
12
|
```
|
|
21
13
|
|
|
22
|
-
It reads
|
|
23
|
-
|
|
14
|
+
The page itself contains no data. It reads everything from the gateway's own API (`/api/v1/*`), so no extra
|
|
15
|
+
backend is needed. Disable it with `dashboard: { enabled: false }`. Add `?guide` to the URL to force the
|
|
16
|
+
onboarding guide open.
|
|
17
|
+
|
|
18
|
+
## Guided onboarding
|
|
19
|
+
|
|
20
|
+
Shown on the first visit (and whenever a key is required but missing). Dismiss it with **Skip**, **Esc** or ✕;
|
|
21
|
+
reopen it any time from the **?** button.
|
|
22
|
+
|
|
23
|
+
1. **Connect**: paste an API key or JWT and test it (leave empty when auth is off).
|
|
24
|
+
2. **Upstream servers**: what is behind the gateway, with status, transport and tool count.
|
|
25
|
+
3. **Try a tool**: pick any tool your key may use; a form is generated from its JSON schema (strings, numbers,
|
|
26
|
+
integers, booleans, enums, arrays / objects as JSON, required fields, defaults, descriptions), or switch to
|
|
27
|
+
raw JSON. Call it and see the result.
|
|
28
|
+
4. **Connect your client**: copy-paste config for Claude Desktop (through `mcp-remote`), Cursor, Claude Code,
|
|
29
|
+
the TypeScript client (`clients/js`), the Kotlin client (`clients/kotlin`) and curl, all pointing at this
|
|
30
|
+
gateway's `/mcp` URL. Snippets use `<YOUR_API_KEY>` unless you choose to insert your key.
|
|
31
|
+
|
|
32
|
+
## Pages
|
|
33
|
+
|
|
34
|
+
| Page | What it shows |
|
|
35
|
+
|---|---|
|
|
36
|
+
| **Overview** | Requests/min, p50 / p95 / p99 latency, error rate and servers online (with sparklines); request-rate, latency and error-rate charts (5 m – 6 h windows, hover / touch tooltips); top tools; usage per API key; live request stream (pausable); server health |
|
|
37
|
+
| **Servers** | One card per upstream server: status, transport, last ping, tools (click to try one), last error and next retry, **Reconnect** |
|
|
38
|
+
| **Playground** | The same schema-driven tool runner as the guide, with search and *copy as curl* |
|
|
39
|
+
| **History** | `GET /api/v1/requests` with server / tool / client / result / via / kind filters and cursor paging (audit log when enabled) |
|
|
40
|
+
| **Connect** | The client snippets from the guide |
|
|
41
|
+
|
|
42
|
+
## Live data
|
|
43
|
+
|
|
44
|
+
- `GET /api/v1/events` (Server-Sent Events) pushes every request as it happens plus a health / summary
|
|
45
|
+
snapshot every 2 s. The dashboard reads it with `fetch()` so the API key travels in the `Authorization`
|
|
46
|
+
header (`EventSource` cannot send headers). If the stream is unavailable it polls every 2 s and keeps
|
|
47
|
+
retrying the stream with backoff. The header pill shows **Live**, **Polling** or **Offline**.
|
|
48
|
+
- `GET /api/v1/stats?window=…` provides the time series and breakdowns.
|
|
49
|
+
|
|
50
|
+
See [docs/api-reference.md](../docs/api-reference.md#live-data-dashboard).
|
|
51
|
+
|
|
52
|
+
## Auth
|
|
53
|
+
|
|
54
|
+
When auth is enabled, the key is kept in the browser tab (`sessionStorage`) or, with “Remember on this device”,
|
|
55
|
+
in `localStorage`, and sent as `Authorization: Bearer …`. Scoped keys only see their own calls and in-scope
|
|
56
|
+
servers and tools.
|
|
57
|
+
|
|
58
|
+
## Accessibility & UX
|
|
59
|
+
|
|
60
|
+
- English / 中文 toggle (defaults to the browser language), dark / light theme (defaults to the OS setting).
|
|
61
|
+
- Responsive down to phone widths, with a bottom tab bar on small screens; safe-area aware.
|
|
62
|
+
- Keyboard: arrow keys move between tabs, the guide is a focus-trapped modal, **Esc** closes dialogs,
|
|
63
|
+
visible focus rings, a skip link.
|
|
64
|
+
- Respects `prefers-reduced-motion`. Animations only use `transform` / `opacity`; page and theme switches use
|
|
65
|
+
View Transitions where the browser supports them; skeleton loaders while data loads.
|
|
66
|
+
|
|
67
|
+
## Screenshots
|
|
68
|
+
|
|
69
|
+
| | |
|
|
70
|
+
|---|---|
|
|
71
|
+
|  |  |
|
|
72
|
+
|  |  |
|
|
24
73
|
|
|
25
|
-
##
|
|
74
|
+
## Demo build
|
|
26
75
|
|
|
27
|
-
|
|
76
|
+
`demo/mock.js` replaces `fetch` for the gateway API with an in-browser simulation (servers, tools, request stream, SSE events). The GitHub Pages workflow (`.github/workflows/pages.yml`) injects it before the dashboard script and publishes the result to <https://harrisoncn.github.io/mcp-gateway/>. It is not shipped in the npm package or Docker image.
|