@winstonsayno/mcp-gateway 0.3.0 → 1.0.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 (77) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +280 -19
  3. package/dashboard/index.html +79 -22
  4. package/dist/auth/middleware.d.ts +21 -2
  5. package/dist/auth/middleware.d.ts.map +1 -1
  6. package/dist/auth/middleware.js +63 -13
  7. package/dist/auth/middleware.js.map +1 -1
  8. package/dist/auth/ratelimit.d.ts +16 -1
  9. package/dist/auth/ratelimit.d.ts.map +1 -1
  10. package/dist/auth/ratelimit.js +15 -8
  11. package/dist/auth/ratelimit.js.map +1 -1
  12. package/dist/auth/scopes.d.ts +40 -0
  13. package/dist/auth/scopes.d.ts.map +1 -0
  14. package/dist/auth/scopes.js +76 -0
  15. package/dist/auth/scopes.js.map +1 -0
  16. package/dist/config/loader.d.ts.map +1 -1
  17. package/dist/config/loader.js +99 -10
  18. package/dist/config/loader.js.map +1 -1
  19. package/dist/gateway/api.d.ts +30 -0
  20. package/dist/gateway/api.d.ts.map +1 -1
  21. package/dist/gateway/api.js +308 -11
  22. package/dist/gateway/api.js.map +1 -1
  23. package/dist/gateway/index.d.ts +4 -0
  24. package/dist/gateway/index.d.ts.map +1 -1
  25. package/dist/gateway/index.js +55 -2
  26. package/dist/gateway/index.js.map +1 -1
  27. package/dist/gateway/supervisor.d.ts +1 -0
  28. package/dist/gateway/supervisor.d.ts.map +1 -1
  29. package/dist/gateway/supervisor.js +12 -1
  30. package/dist/gateway/supervisor.js.map +1 -1
  31. package/dist/index.d.ts +14 -1
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +8 -0
  34. package/dist/index.js.map +1 -1
  35. package/dist/mcp/catalog.d.ts +30 -0
  36. package/dist/mcp/catalog.d.ts.map +1 -0
  37. package/dist/mcp/catalog.js +78 -0
  38. package/dist/mcp/catalog.js.map +1 -0
  39. package/dist/mcp/endpoint.d.ts +132 -0
  40. package/dist/mcp/endpoint.d.ts.map +1 -0
  41. package/dist/mcp/endpoint.js +714 -0
  42. package/dist/mcp/endpoint.js.map +1 -0
  43. package/dist/mcp/llm-schemas.d.ts +36 -0
  44. package/dist/mcp/llm-schemas.d.ts.map +1 -0
  45. package/dist/mcp/llm-schemas.js +69 -0
  46. package/dist/mcp/llm-schemas.js.map +1 -0
  47. package/dist/mcp/naming.d.ts +53 -0
  48. package/dist/mcp/naming.d.ts.map +1 -0
  49. package/dist/mcp/naming.js +71 -0
  50. package/dist/mcp/naming.js.map +1 -0
  51. package/dist/middleware/cors.d.ts +2 -0
  52. package/dist/middleware/cors.d.ts.map +1 -1
  53. package/dist/middleware/cors.js +25 -19
  54. package/dist/middleware/cors.js.map +1 -1
  55. package/dist/monitor/audit.d.ts +60 -0
  56. package/dist/monitor/audit.d.ts.map +1 -0
  57. package/dist/monitor/audit.js +155 -0
  58. package/dist/monitor/audit.js.map +1 -0
  59. package/dist/monitor/index.d.ts +19 -0
  60. package/dist/monitor/index.d.ts.map +1 -1
  61. package/dist/monitor/index.js +88 -0
  62. package/dist/monitor/index.js.map +1 -1
  63. package/dist/proxy/index.d.ts +25 -2
  64. package/dist/proxy/index.d.ts.map +1 -1
  65. package/dist/proxy/index.js +171 -16
  66. package/dist/proxy/index.js.map +1 -1
  67. package/dist/registry/index.d.ts +16 -2
  68. package/dist/registry/index.d.ts.map +1 -1
  69. package/dist/registry/index.js +37 -1
  70. package/dist/registry/index.js.map +1 -1
  71. package/dist/utils/tool-filter.d.ts +24 -0
  72. package/dist/utils/tool-filter.d.ts.map +1 -0
  73. package/dist/utils/tool-filter.js +48 -0
  74. package/dist/utils/tool-filter.js.map +1 -0
  75. package/dist/utils/types.d.ts +116 -2
  76. package/dist/utils/types.d.ts.map +1 -1
  77. package/package.json +2 -1
@@ -12,13 +12,32 @@
12
12
  * /requests endpoint, and keys sharing a prefix shared a rate-limit bucket).
13
13
  */
14
14
  import type { Request, RequestHandler } from 'express';
15
- import type { AuthConfig } from '../utils/types.js';
15
+ import type { ApiKeyConfig, AuthConfig } from '../utils/types.js';
16
+ import { type AccessScope } from './scopes.js';
16
17
  export type AuthedRequest = Request & {
17
18
  clientId?: string;
18
19
  jwtPayload?: unknown;
20
+ /** Servers / tools / rate limit this client is restricted to (absent = unrestricted). */
21
+ scope?: AccessScope;
19
22
  };
20
- export declare function createAuthMiddleware(config?: AuthConfig): RequestHandler;
23
+ /** An auth middleware that can also look up the current scope of a known client id (api keys). */
24
+ export type AuthMiddleware = RequestHandler & {
25
+ /**
26
+ * Current scope for `clientId`: `{ known: false }` when no configured key
27
+ * produces that id any more. Undefined for strategies whose scopes travel
28
+ * with the credential (JWT).
29
+ */
30
+ resolveClient?: (clientId: string | undefined) => {
31
+ known: boolean;
32
+ scope?: AccessScope;
33
+ };
34
+ };
35
+ /** Normalise `auth.apiKeys` entries (plain strings or objects). */
36
+ export declare function normalizeApiKeys(keys: AuthConfig['apiKeys']): ApiKeyConfig[];
37
+ export declare function createAuthMiddleware(config?: AuthConfig): AuthMiddleware;
21
38
  export declare function fingerprint(key: string): string;
22
39
  /** Constant-time membership check against a pre-hashed key list. */
23
40
  export declare function matchesAnyKey(candidate: string, keyDigests: Buffer[]): boolean;
41
+ /** Constant-time lookup: index of the matching key digest, or -1. */
42
+ export declare function matchKeyIndex(candidate: string, keyDigests: Buffer[]): number;
24
43
  //# sourceMappingURL=middleware.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../src/auth/middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,KAAK,EAAE,OAAO,EAA0B,cAAc,EAAE,MAAM,SAAS,CAAC;AAE/E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAGpD,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAIlF,wBAAgB,oBAAoB,CAAC,MAAM,CAAC,EAAE,UAAU,GAAG,cAAc,CA2BxE;AAMD,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED,oEAAoE;AACpE,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,OAAO,CAQ9E"}
1
+ {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../src/auth/middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,KAAK,EAAE,OAAO,EAA0B,cAAc,EAAE,MAAM,SAAS,CAAC;AAE/E,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAElE,OAAO,EAAgB,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AAE7D,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,yFAAyF;IACzF,KAAK,CAAC,EAAE,WAAW,CAAC;CACrB,CAAC;AAEF,kGAAkG;AAClG,MAAM,MAAM,cAAc,GAAG,cAAc,GAAG;IAC5C;;;;OAIG;IACH,aAAa,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,KAAK;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,WAAW,CAAA;KAAE,CAAC;CAC3F,CAAC;AAEF,mEAAmE;AACnE,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,UAAU,CAAC,SAAS,CAAC,GAAG,YAAY,EAAE,CAI5E;AAaD,wBAAgB,oBAAoB,CAAC,MAAM,CAAC,EAAE,UAAU,GAAG,cAAc,CAmCxE;AAMD,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED,oEAAoE;AACpE,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,OAAO,CAE9E;AAED,qEAAqE;AACrE,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,MAAM,CAQ7E"}
@@ -14,17 +14,46 @@
14
14
  import { createHash, timingSafeEqual } from 'crypto';
15
15
  import { jwtVerify } from 'jose';
16
16
  import { logger } from '../utils/logger.js';
17
+ import { scopeFromJwt } from './scopes.js';
18
+ /** Normalise `auth.apiKeys` entries (plain strings or objects). */
19
+ export function normalizeApiKeys(keys) {
20
+ return (keys ?? [])
21
+ .map((k) => (typeof k === 'string' ? { key: k } : k))
22
+ .filter((k) => !!k && typeof k.key === 'string' && k.key.length > 0);
23
+ }
24
+ function scopeOf(entry) {
25
+ const scope = {};
26
+ if (entry.name)
27
+ scope.name = entry.name;
28
+ if (entry.servers)
29
+ scope.servers = [...entry.servers];
30
+ if (entry.tools)
31
+ scope.tools = [...entry.tools];
32
+ if (entry.rateLimit)
33
+ scope.rateLimit = { ...entry.rateLimit, perKey: true };
34
+ return Object.keys(scope).length > 0 ? scope : undefined;
35
+ }
17
36
  const JWT_ALGORITHMS = ['HS256', 'HS384', 'HS512'];
18
37
  export function createAuthMiddleware(config) {
19
38
  if (!config || config.strategy === 'none') {
20
- return (_req, _res, next) => next();
39
+ const none = (_req, _res, next) => next();
40
+ none.resolveClient = () => ({ known: true });
41
+ return none;
21
42
  }
22
43
  switch (config.strategy) {
23
44
  case 'api-key': {
24
- const keys = (config.apiKeys ?? []).filter((k) => k.length > 0);
45
+ const keys = normalizeApiKeys(config.apiKeys);
25
46
  if (keys.length === 0) {
26
47
  throw new Error('auth.strategy is "api-key" but no auth.apiKeys are configured');
27
48
  }
49
+ const names = new Set();
50
+ for (const k of keys) {
51
+ if (!k.name)
52
+ continue;
53
+ if (names.has(k.name))
54
+ throw new Error(`auth.apiKeys: duplicate key name "${k.name}"`);
55
+ names.add(k.name);
56
+ }
28
57
  return apiKeyMiddleware(keys);
29
58
  }
30
59
  case 'jwt': {
@@ -50,14 +79,18 @@ export function fingerprint(key) {
50
79
  }
51
80
  /** Constant-time membership check against a pre-hashed key list. */
52
81
  export function matchesAnyKey(candidate, keyDigests) {
82
+ return matchKeyIndex(candidate, keyDigests) >= 0;
83
+ }
84
+ /** Constant-time lookup: index of the matching key digest, or -1. */
85
+ export function matchKeyIndex(candidate, keyDigests) {
53
86
  const c = digest(candidate);
54
- let match = false;
87
+ let index = -1;
55
88
  // Compare against every key (no early exit) to keep timing independent of position.
56
- for (const k of keyDigests) {
57
- if (timingSafeEqual(c, k))
58
- match = true;
59
- }
60
- return match;
89
+ keyDigests.forEach((k, i) => {
90
+ if (timingSafeEqual(c, k) && index < 0)
91
+ index = i;
92
+ });
93
+ return index;
61
94
  }
62
95
  function extractKey(req) {
63
96
  const authHeader = req.headers.authorization;
@@ -69,18 +102,32 @@ function extractKey(req) {
69
102
  return apiKeyHeader;
70
103
  return undefined;
71
104
  }
72
- function apiKeyMiddleware(validKeys) {
73
- const keyDigests = validKeys.map(digest);
74
- return (req, res, next) => {
105
+ function apiKeyMiddleware(entries) {
106
+ const keyDigests = entries.map((e) => digest(e.key));
107
+ const clients = entries.map((e) => ({
108
+ clientId: e.name ? `key:${e.name}` : `key:${fingerprint(e.key)}`,
109
+ scope: scopeOf(e),
110
+ }));
111
+ const byClientId = new Map(clients.map((c) => [c.clientId, c]));
112
+ const mw = (req, res, next) => {
75
113
  const key = extractKey(req);
76
- if (!key || !matchesAnyKey(key, keyDigests)) {
114
+ const index = key ? matchKeyIndex(key, keyDigests) : -1;
115
+ if (index < 0) {
77
116
  logger.warn(`Unauthorized request from ${req.ip}: missing or invalid API key`);
78
117
  res.status(401).json({ error: 'Unauthorized', message: 'Valid API key required' });
79
118
  return;
80
119
  }
81
- req.clientId = `key:${fingerprint(key)}`;
120
+ const client = clients[index];
121
+ req.clientId = client.clientId;
122
+ if (client.scope)
123
+ req.scope = client.scope;
82
124
  next();
83
125
  };
126
+ mw.resolveClient = (clientId) => {
127
+ const c = clientId ? byClientId.get(clientId) : undefined;
128
+ return c ? { known: true, scope: c.scope } : { known: false };
129
+ };
130
+ return mw;
84
131
  }
85
132
  function jwtMiddleware(secret) {
86
133
  const secretKey = new TextEncoder().encode(secret);
@@ -96,6 +143,9 @@ function jwtMiddleware(secret) {
96
143
  jwtVerify(token, secretKey, { algorithms: JWT_ALGORITHMS }).then(({ payload }) => {
97
144
  req.jwtPayload = payload;
98
145
  req.clientId = `jwt:${String(payload.sub ?? 'unknown')}`;
146
+ const scope = scopeFromJwt(payload);
147
+ if (scope)
148
+ req.scope = scope;
99
149
  next();
100
150
  }, (err) => {
101
151
  logger.warn(`JWT verification failed: ${err instanceof Error ? err.message : String(err)}`);
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../src/auth/middleware.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../src/auth/middleware.ts"],"names":[],"mappings":"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"}
@@ -9,11 +9,26 @@
9
9
  *
10
10
  * For multi-instance deployments, replace with a shared (e.g. Redis) store.
11
11
  */
12
- import type { RequestHandler } from 'express';
12
+ import type { Request, Response, RequestHandler } from 'express';
13
13
  import type { RateLimitConfig } from '../utils/types.js';
14
+ export interface RateLimitDecision {
15
+ allowed: boolean;
16
+ limit: number;
17
+ remaining: number;
18
+ /** End of the current window (ms epoch). */
19
+ resetAt: number;
20
+ /** Seconds until a retry can succeed (only when not allowed). */
21
+ retryAfter?: number;
22
+ }
14
23
  export interface RateLimiter extends RequestHandler {
15
24
  /** Stop the background cleanup timer and drop all state. */
16
25
  close(): void;
26
+ /**
27
+ * Count one request for `req` without responding (used by `/mcp`, where a
28
+ * limit hit becomes a JSON-RPC error). Returns undefined when unlimited.
29
+ */
30
+ take(req: Request): RateLimitDecision | undefined;
17
31
  }
18
32
  export declare function createRateLimiter(config?: RateLimitConfig, now?: () => number): RateLimiter;
33
+ export declare function setRateLimitHeaders(res: Response, limit: number, remaining: number, resetAt: number): void;
19
34
  //# sourceMappingURL=ratelimit.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ratelimit.d.ts","sourceRoot":"","sources":["../../src/auth/ratelimit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAmC,cAAc,EAAE,MAAM,SAAS,CAAC;AAC/E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAUzD,MAAM,WAAW,WAAY,SAAQ,cAAc;IACjD,4DAA4D;IAC5D,KAAK,IAAI,IAAI,CAAC;CACf;AAED,wBAAgB,iBAAiB,CAC/B,MAAM,CAAC,EAAE,eAAe,EACxB,GAAG,GAAE,MAAM,MAAiB,GAC3B,WAAW,CAoEb"}
1
+ {"version":3,"file":"ratelimit.d.ts","sourceRoot":"","sources":["../../src/auth/ratelimit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAgB,cAAc,EAAE,MAAM,SAAS,CAAC;AAC/E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAUzD,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,4CAA4C;IAC5C,OAAO,EAAE,MAAM,CAAC;IAChB,iEAAiE;IACjE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,WAAY,SAAQ,cAAc;IACjD,4DAA4D;IAC5D,KAAK,IAAI,IAAI,CAAC;IACd;;;OAGG;IACH,IAAI,CAAC,GAAG,EAAE,OAAO,GAAG,iBAAiB,GAAG,SAAS,CAAC;CACnD;AAED,wBAAgB,iBAAiB,CAC/B,MAAM,CAAC,EAAE,eAAe,EACxB,GAAG,GAAE,MAAM,MAAiB,GAC3B,WAAW,CA6Eb;AAED,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,QAAQ,EACb,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,GACd,IAAI,CAMN"}
@@ -14,6 +14,7 @@ export function createRateLimiter(config, now = Date.now) {
14
14
  if (!config) {
15
15
  const passthrough = ((_req, _res, next) => next());
16
16
  passthrough.close = () => { };
17
+ passthrough.take = () => undefined;
17
18
  return passthrough;
18
19
  }
19
20
  const { limit, windowSeconds, perKey = true } = config;
@@ -29,7 +30,7 @@ export function createRateLimiter(config, now = Date.now) {
29
30
  }
30
31
  }, Math.max(windowMs, 1000));
31
32
  cleanup.unref();
32
- const limiter = ((req, res, next) => {
33
+ const take = (req) => {
33
34
  const clientId = perKey
34
35
  ? (req.clientId ?? req.ip ?? 'anonymous')
35
36
  : 'global';
@@ -51,27 +52,33 @@ export function createRateLimiter(config, now = Date.now) {
51
52
  const estimated = entry.current + entry.previous * overlap;
52
53
  const resetAt = windowStart + windowMs;
53
54
  if (estimated >= limit) {
54
- const retryAfter = Math.max(1, Math.ceil((resetAt - t) / 1000));
55
55
  logger.warn(`Rate limit exceeded for ${clientId}`);
56
- setRateLimitHeaders(res, limit, 0, resetAt);
57
- res.status(429).set('Retry-After', String(retryAfter)).json({
56
+ return { allowed: false, limit, remaining: 0, resetAt, retryAfter: Math.max(1, Math.ceil((resetAt - t) / 1000)) };
57
+ }
58
+ entry.current++;
59
+ return { allowed: true, limit, remaining: Math.max(0, Math.floor(limit - estimated - 1)), resetAt };
60
+ };
61
+ const limiter = ((req, res, next) => {
62
+ const d = take(req);
63
+ setRateLimitHeaders(res, d.limit, d.remaining, d.resetAt);
64
+ if (!d.allowed) {
65
+ res.status(429).set('Retry-After', String(d.retryAfter)).json({
58
66
  error: 'Too Many Requests',
59
67
  message: `Rate limit of ${limit} requests per ${windowSeconds}s exceeded`,
60
- retryAfter,
68
+ retryAfter: d.retryAfter,
61
69
  });
62
70
  return;
63
71
  }
64
- entry.current++;
65
- setRateLimitHeaders(res, limit, Math.floor(limit - estimated - 1), resetAt);
66
72
  next();
67
73
  });
74
+ limiter.take = take;
68
75
  limiter.close = () => {
69
76
  clearInterval(cleanup);
70
77
  store.clear();
71
78
  };
72
79
  return limiter;
73
80
  }
74
- function setRateLimitHeaders(res, limit, remaining, resetAt) {
81
+ export function setRateLimitHeaders(res, limit, remaining, resetAt) {
75
82
  res.set({
76
83
  'X-RateLimit-Limit': String(limit),
77
84
  'X-RateLimit-Remaining': String(Math.max(0, remaining)),
@@ -1 +1 @@
1
- {"version":3,"file":"ratelimit.js","sourceRoot":"","sources":["../../src/auth/ratelimit.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ratelimit.js","sourceRoot":"","sources":["../../src/auth/ratelimit.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Per-client access scopes.
3
+ *
4
+ * A scope limits which upstream servers and tools a client (an API key or a
5
+ * JWT) may see and call:
6
+ * - `servers`: glob patterns matched against server ids.
7
+ * - `tools`: glob patterns matched against tool names. A pattern containing
8
+ * `/` is matched against `<serverId>/<tool>` instead, so a tool can be
9
+ * allowed on one server only (`github/create_issue`, `fs/read_*`).
10
+ *
11
+ * An absent list means "no restriction"; an empty list allows nothing. Both
12
+ * lists must allow a tool for it to be visible / callable. Scopes are applied
13
+ * on top of each server's own `tools` filter, never instead of it.
14
+ *
15
+ * JWTs carry scopes in the `mcp_servers` / `mcp_tools` claims (an array of
16
+ * patterns, or one string of space- or comma-separated patterns).
17
+ *
18
+ * @module auth/scopes
19
+ */
20
+ import type { RateLimitConfig, ToolInfo } from '../utils/types.js';
21
+ export interface AccessScope {
22
+ /** Optional label (API key `name`), used in client ids and logs. */
23
+ name?: string;
24
+ servers?: string[];
25
+ tools?: string[];
26
+ /** Own rate limit for this client (replaces the global `rateLimit`). */
27
+ rateLimit?: RateLimitConfig;
28
+ }
29
+ export declare const JWT_SERVERS_CLAIM = "mcp_servers";
30
+ export declare const JWT_TOOLS_CLAIM = "mcp_tools";
31
+ /** Whether the scope lets the client use `serverId` at all. */
32
+ export declare function isServerInScope(scope: AccessScope | undefined, serverId: string): boolean;
33
+ /** Whether the scope lets the client see / call `toolName` on `serverId`. */
34
+ export declare function isToolInScope(scope: AccessScope | undefined, serverId: string, toolName: string): boolean;
35
+ export declare function filterToolsByScope<T extends Pick<ToolInfo, 'serverId' | 'name'>>(scope: AccessScope | undefined, tools: readonly T[]): T[];
36
+ /** Whether a scope restricts anything (unrestricted clients skip filtering). */
37
+ export declare function isRestricted(scope: AccessScope | undefined): boolean;
38
+ /** Scope from a verified JWT payload (undefined when it carries no scope claims). */
39
+ export declare function scopeFromJwt(payload: unknown): AccessScope | undefined;
40
+ //# sourceMappingURL=scopes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scopes.d.ts","sourceRoot":"","sources":["../../src/auth/scopes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAGnE,MAAM,WAAW,WAAW;IAC1B,oEAAoE;IACpE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,wEAAwE;IACxE,SAAS,CAAC,EAAE,eAAe,CAAC;CAC7B;AAED,eAAO,MAAM,iBAAiB,gBAAgB,CAAC;AAC/C,eAAO,MAAM,eAAe,cAAc,CAAC;AAI3C,+DAA+D;AAC/D,wBAAgB,eAAe,CAAC,KAAK,EAAE,WAAW,GAAG,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAGzF;AAED,6EAA6E;AAC7E,wBAAgB,aAAa,CAAC,KAAK,EAAE,WAAW,GAAG,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAMzG;AAED,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,IAAI,CAAC,QAAQ,EAAE,UAAU,GAAG,MAAM,CAAC,EAC9E,KAAK,EAAE,WAAW,GAAG,SAAS,EAC9B,KAAK,EAAE,SAAS,CAAC,EAAE,GAClB,CAAC,EAAE,CAGL;AAED,gFAAgF;AAChF,wBAAgB,YAAY,CAAC,KAAK,EAAE,WAAW,GAAG,SAAS,GAAG,OAAO,CAEpE;AAUD,qFAAqF;AACrF,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,WAAW,GAAG,SAAS,CAUtE"}
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Per-client access scopes.
3
+ *
4
+ * A scope limits which upstream servers and tools a client (an API key or a
5
+ * JWT) may see and call:
6
+ * - `servers`: glob patterns matched against server ids.
7
+ * - `tools`: glob patterns matched against tool names. A pattern containing
8
+ * `/` is matched against `<serverId>/<tool>` instead, so a tool can be
9
+ * allowed on one server only (`github/create_issue`, `fs/read_*`).
10
+ *
11
+ * An absent list means "no restriction"; an empty list allows nothing. Both
12
+ * lists must allow a tool for it to be visible / callable. Scopes are applied
13
+ * on top of each server's own `tools` filter, never instead of it.
14
+ *
15
+ * JWTs carry scopes in the `mcp_servers` / `mcp_tools` claims (an array of
16
+ * patterns, or one string of space- or comma-separated patterns).
17
+ *
18
+ * @module auth/scopes
19
+ */
20
+ import { globToRegExp } from '../utils/tool-filter.js';
21
+ export const JWT_SERVERS_CLAIM = 'mcp_servers';
22
+ export const JWT_TOOLS_CLAIM = 'mcp_tools';
23
+ const matches = (value, patterns) => patterns.some((p) => globToRegExp(p).test(value));
24
+ /** Whether the scope lets the client use `serverId` at all. */
25
+ export function isServerInScope(scope, serverId) {
26
+ if (!scope?.servers)
27
+ return true;
28
+ return matches(serverId, scope.servers);
29
+ }
30
+ /** Whether the scope lets the client see / call `toolName` on `serverId`. */
31
+ export function isToolInScope(scope, serverId, toolName) {
32
+ if (!scope)
33
+ return true;
34
+ if (!isServerInScope(scope, serverId))
35
+ return false;
36
+ if (!scope.tools)
37
+ return true;
38
+ const qualified = `${serverId}/${toolName}`;
39
+ return scope.tools.some((p) => globToRegExp(p).test(p.includes('/') ? qualified : toolName));
40
+ }
41
+ export function filterToolsByScope(scope, tools) {
42
+ if (!scope || (!scope.servers && !scope.tools))
43
+ return [...tools];
44
+ return tools.filter((t) => isToolInScope(scope, t.serverId, t.name));
45
+ }
46
+ /** Whether a scope restricts anything (unrestricted clients skip filtering). */
47
+ export function isRestricted(scope) {
48
+ return !!scope && (scope.servers !== undefined || scope.tools !== undefined);
49
+ }
50
+ function claimList(value) {
51
+ if (value === undefined || value === null)
52
+ return undefined;
53
+ if (Array.isArray(value))
54
+ return value.filter((v) => typeof v === 'string' && v.length > 0);
55
+ if (typeof value === 'string')
56
+ return value.split(/[\s,]+/).filter(Boolean);
57
+ // A malformed claim must not widen access: treat it as "nothing allowed".
58
+ return [];
59
+ }
60
+ /** Scope from a verified JWT payload (undefined when it carries no scope claims). */
61
+ export function scopeFromJwt(payload) {
62
+ if (!payload || typeof payload !== 'object')
63
+ return undefined;
64
+ const p = payload;
65
+ const servers = claimList(p[JWT_SERVERS_CLAIM]);
66
+ const tools = claimList(p[JWT_TOOLS_CLAIM]);
67
+ if (servers === undefined && tools === undefined)
68
+ return undefined;
69
+ const scope = {};
70
+ if (servers !== undefined)
71
+ scope.servers = servers;
72
+ if (tools !== undefined)
73
+ scope.tools = tools;
74
+ return scope;
75
+ }
76
+ //# sourceMappingURL=scopes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scopes.js","sourceRoot":"","sources":["../../src/auth/scopes.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/config/loader.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAgIvD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAMzE;AAED,wBAAsB,UAAU,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAsB5E;AAkCD,wBAAgB,qBAAqB,IAAI,MAAM,CA8E9C"}
1
+ {"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../src/config/loader.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AA2MvD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAMzE;AAED,wBAAsB,UAAU,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAsB5E;AAkCD,wBAAgB,qBAAqB,IAAI,MAAM,CAgG9C"}
@@ -7,6 +7,7 @@ import { existsSync } from 'fs';
7
7
  import { resolve } from 'path';
8
8
  import { parse as parseYaml } from 'yaml';
9
9
  import { z } from 'zod';
10
+ import { expandEnv } from '../transport/channel.js';
10
11
  // ─── Zod Schema ───────────────────────────────────────────────────────────────
11
12
  const ReconnectSchema = z.object({
12
13
  enabled: z.boolean().optional(),
@@ -29,6 +30,13 @@ function safeProtocol(url) {
29
30
  return '';
30
31
  }
31
32
  }
33
+ const ToolPatternList = z.array(z.string().min(1, 'patterns must be non-empty'));
34
+ const ToolFilterSchema = z
35
+ .object({
36
+ allow: ToolPatternList.optional(),
37
+ deny: ToolPatternList.optional(),
38
+ })
39
+ .strict();
32
40
  const McpServerSchema = z.object({
33
41
  id: z.string().min(1),
34
42
  name: z.string().min(1),
@@ -45,6 +53,7 @@ const McpServerSchema = z.object({
45
53
  enabled: z.boolean().default(true),
46
54
  timeout: z.number().positive().default(30000),
47
55
  maxConcurrency: z.number().int().positive().default(10),
56
+ tools: ToolFilterSchema.optional(),
48
57
  }).superRefine((s, ctx) => {
49
58
  if (s.transport === 'stdio' && !s.command) {
50
59
  ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['command'], message: 'required for stdio transport' });
@@ -63,13 +72,41 @@ const McpServerSchema = z.object({
63
72
  }
64
73
  }
65
74
  });
75
+ const PatternList = z.array(z.string().min(1, 'patterns must be non-empty'));
76
+ const RateLimitSchema = z.object({
77
+ limit: z.number().int().positive().default(100),
78
+ windowSeconds: z.number().positive().default(60),
79
+ perKey: z.boolean().default(true),
80
+ });
81
+ const ApiKeySchema = z.union([
82
+ z.string(),
83
+ z
84
+ .object({
85
+ // ${VAR} references are expanded from the gateway's environment.
86
+ key: z
87
+ .string()
88
+ .min(1)
89
+ .transform((k) => expandEnv(k)),
90
+ name: z
91
+ .string()
92
+ .regex(/^[A-Za-z0-9._-]{1,64}$/, 'letters, digits, ".", "_" or "-" (max 64)')
93
+ .optional(),
94
+ servers: PatternList.optional(),
95
+ tools: PatternList.optional(),
96
+ rateLimit: z
97
+ .object({ limit: z.number().int().positive(), windowSeconds: z.number().positive() })
98
+ .strict()
99
+ .optional(),
100
+ })
101
+ .strict(),
102
+ ]);
66
103
  const GatewayConfigSchema = z.object({
67
104
  port: z.number().int().min(1).max(65535).default(4000),
68
105
  host: z.string().default('0.0.0.0'),
69
106
  auth: z
70
107
  .object({
71
108
  strategy: z.enum(['none', 'api-key', 'jwt', 'oauth2']).default('none'),
72
- apiKeys: z.array(z.string()).optional(),
109
+ apiKeys: z.array(ApiKeySchema).optional(),
73
110
  jwtSecret: z.string().optional(),
74
111
  protect: z
75
112
  .object({
@@ -79,7 +116,22 @@ const GatewayConfigSchema = z.object({
79
116
  .optional(),
80
117
  })
81
118
  .superRefine((a, ctx) => {
82
- if (a.strategy === 'api-key' && !(a.apiKeys ?? []).some((k) => k.length > 0)) {
119
+ const keyOf = (k) => (typeof k === 'string' ? k : k.key);
120
+ const names = new Set();
121
+ (a.apiKeys ?? []).forEach((k, i) => {
122
+ if (typeof k === 'string' || !k.name)
123
+ return;
124
+ if (names.has(k.name)) {
125
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['apiKeys', i, 'name'], message: `duplicate key name "${k.name}"` });
126
+ }
127
+ names.add(k.name);
128
+ });
129
+ (a.apiKeys ?? []).forEach((k, i) => {
130
+ if (typeof k !== 'string' && k.key.length === 0) {
131
+ ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['apiKeys', i, 'key'], message: 'is empty (unset environment variable?)' });
132
+ }
133
+ });
134
+ if (a.strategy === 'api-key' && !(a.apiKeys ?? []).some((k) => keyOf(k).length > 0)) {
83
135
  ctx.addIssue({ code: z.ZodIssueCode.custom, path: ['apiKeys'], message: 'at least one key is required for api-key strategy' });
84
136
  }
85
137
  if (a.strategy === 'jwt' && !a.jwtSecret) {
@@ -90,13 +142,7 @@ const GatewayConfigSchema = z.object({
90
142
  }
91
143
  })
92
144
  .optional(),
93
- rateLimit: z
94
- .object({
95
- limit: z.number().int().positive().default(100),
96
- windowSeconds: z.number().positive().default(60),
97
- perKey: z.boolean().default(true),
98
- })
99
- .optional(),
145
+ rateLimit: RateLimitSchema.optional(),
100
146
  monitor: z
101
147
  .object({
102
148
  prometheus: z.boolean().default(false),
@@ -110,6 +156,31 @@ const GatewayConfigSchema = z.object({
110
156
  reconnect: ReconnectSchema.optional(),
111
157
  healthCheckIntervalMs: z.number().int().min(1000).default(30_000),
112
158
  dashboard: z.object({ enabled: z.boolean().default(true) }).optional(),
159
+ audit: z
160
+ .object({
161
+ enabled: z.boolean().default(false),
162
+ path: z.string().min(1).default('mcp-gateway-audit.db'),
163
+ retentionDays: z.number().int().min(0).default(30),
164
+ })
165
+ .strict()
166
+ .optional(),
167
+ mcp: z
168
+ .object({
169
+ enabled: z.boolean().default(true),
170
+ path: z
171
+ .string()
172
+ .regex(/^\/[A-Za-z0-9._~\-/]*$/, 'must be an absolute URL path such as /mcp')
173
+ .refine((p) => p !== '/' && !/^\/(api|dashboard)(\/|$)/.test(p), 'must not be "/" or under /api or /dashboard')
174
+ .default('/mcp'),
175
+ toolNaming: z.enum(['auto', 'prefix']).default('auto'),
176
+ pageSize: z.number().int().min(1).max(10_000).default(500),
177
+ sessionIdleTimeoutSeconds: z.number().int().positive().default(1800),
178
+ maxSessions: z.number().int().positive().default(1000),
179
+ allowedOrigins: z.array(z.string()).optional(),
180
+ instructions: z.string().optional(),
181
+ })
182
+ .strict()
183
+ .optional(),
113
184
  }).superRefine((c, ctx) => {
114
185
  const seen = new Set();
115
186
  c.servers.forEach((s, i) => {
@@ -198,7 +269,12 @@ logLevel: info
198
269
  # auth:
199
270
  # strategy: api-key
200
271
  # apiKeys:
201
- # - your-secret-key-here
272
+ # - your-secret-key-here # full access
273
+ # - key: \${AURA_GATEWAY_KEY} # scoped key; \${VAR} is expanded (patterns are globs)
274
+ # name: aura
275
+ # servers: ["github", "fs-*"]
276
+ # tools: ["read_*", "github/create_issue"]
277
+ # rateLimit: { limit: 30, windowSeconds: 60 }
202
278
  # protect:
203
279
  # health: false # true = /api/v1/health requires a key (/health/live stays public)
204
280
  # metrics: false # true = /api/v1/metrics requires a key (configure your scraper)
@@ -218,6 +294,19 @@ logLevel: info
218
294
  # windowSeconds: 60
219
295
  # perKey: true
220
296
 
297
+ # Downstream MCP endpoint: point Claude Code / Cursor / any MCP client at
298
+ # http://<host>:<port>/mcp (Streamable HTTP; uses the auth settings above)
299
+ # mcp:
300
+ # enabled: true
301
+ # path: /mcp
302
+ # toolNaming: auto # auto = prefix "<server>__" only on name collisions; prefix = always
303
+
304
+ # Persistent audit log of requests (SQLite via node:sqlite, Node 22.5+; default off)
305
+ # audit:
306
+ # enabled: true
307
+ # path: ./data/mcp-gateway-audit.db
308
+ # retentionDays: 30 # 0 = keep forever
309
+
221
310
  # Monitoring
222
311
  monitor:
223
312
  requestLog: true