@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.
- package/CHANGELOG.md +41 -0
- package/README.md +280 -19
- package/dashboard/index.html +79 -22
- package/dist/auth/middleware.d.ts +21 -2
- package/dist/auth/middleware.d.ts.map +1 -1
- package/dist/auth/middleware.js +63 -13
- package/dist/auth/middleware.js.map +1 -1
- package/dist/auth/ratelimit.d.ts +16 -1
- package/dist/auth/ratelimit.d.ts.map +1 -1
- package/dist/auth/ratelimit.js +15 -8
- package/dist/auth/ratelimit.js.map +1 -1
- package/dist/auth/scopes.d.ts +40 -0
- package/dist/auth/scopes.d.ts.map +1 -0
- package/dist/auth/scopes.js +76 -0
- package/dist/auth/scopes.js.map +1 -0
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +99 -10
- package/dist/config/loader.js.map +1 -1
- package/dist/gateway/api.d.ts +30 -0
- package/dist/gateway/api.d.ts.map +1 -1
- package/dist/gateway/api.js +308 -11
- package/dist/gateway/api.js.map +1 -1
- package/dist/gateway/index.d.ts +4 -0
- package/dist/gateway/index.d.ts.map +1 -1
- package/dist/gateway/index.js +55 -2
- package/dist/gateway/index.js.map +1 -1
- package/dist/gateway/supervisor.d.ts +1 -0
- package/dist/gateway/supervisor.d.ts.map +1 -1
- package/dist/gateway/supervisor.js +12 -1
- package/dist/gateway/supervisor.js.map +1 -1
- package/dist/index.d.ts +14 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp/catalog.d.ts +30 -0
- package/dist/mcp/catalog.d.ts.map +1 -0
- package/dist/mcp/catalog.js +78 -0
- package/dist/mcp/catalog.js.map +1 -0
- package/dist/mcp/endpoint.d.ts +132 -0
- package/dist/mcp/endpoint.d.ts.map +1 -0
- package/dist/mcp/endpoint.js +714 -0
- package/dist/mcp/endpoint.js.map +1 -0
- package/dist/mcp/llm-schemas.d.ts +36 -0
- package/dist/mcp/llm-schemas.d.ts.map +1 -0
- package/dist/mcp/llm-schemas.js +69 -0
- package/dist/mcp/llm-schemas.js.map +1 -0
- package/dist/mcp/naming.d.ts +53 -0
- package/dist/mcp/naming.d.ts.map +1 -0
- package/dist/mcp/naming.js +71 -0
- package/dist/mcp/naming.js.map +1 -0
- package/dist/middleware/cors.d.ts +2 -0
- package/dist/middleware/cors.d.ts.map +1 -1
- package/dist/middleware/cors.js +25 -19
- package/dist/middleware/cors.js.map +1 -1
- package/dist/monitor/audit.d.ts +60 -0
- package/dist/monitor/audit.d.ts.map +1 -0
- package/dist/monitor/audit.js +155 -0
- package/dist/monitor/audit.js.map +1 -0
- package/dist/monitor/index.d.ts +19 -0
- package/dist/monitor/index.d.ts.map +1 -1
- package/dist/monitor/index.js +88 -0
- package/dist/monitor/index.js.map +1 -1
- package/dist/proxy/index.d.ts +25 -2
- package/dist/proxy/index.d.ts.map +1 -1
- package/dist/proxy/index.js +171 -16
- package/dist/proxy/index.js.map +1 -1
- package/dist/registry/index.d.ts +16 -2
- package/dist/registry/index.d.ts.map +1 -1
- package/dist/registry/index.js +37 -1
- package/dist/registry/index.js.map +1 -1
- package/dist/utils/tool-filter.d.ts +24 -0
- package/dist/utils/tool-filter.d.ts.map +1 -0
- package/dist/utils/tool-filter.js +48 -0
- package/dist/utils/tool-filter.js.map +1 -0
- package/dist/utils/types.d.ts +116 -2
- package/dist/utils/types.d.ts.map +1 -1
- 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
|
-
|
|
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;
|
|
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"}
|
package/dist/auth/middleware.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
|
87
|
+
let index = -1;
|
|
55
88
|
// Compare against every key (no early exit) to keep timing independent of position.
|
|
56
|
-
|
|
57
|
-
if (timingSafeEqual(c, k))
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
return
|
|
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(
|
|
73
|
-
const keyDigests =
|
|
74
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAC;AAErD,OAAO,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AAEjC,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../src/auth/middleware.ts"],"names":[],"mappings":"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"}
|
package/dist/auth/ratelimit.d.ts
CHANGED
|
@@ -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,
|
|
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"}
|
package/dist/auth/ratelimit.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
57
|
-
|
|
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":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;
|
|
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;
|
|
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"}
|
package/dist/config/loader.js
CHANGED
|
@@ -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(
|
|
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
|
-
|
|
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:
|
|
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
|