@winstonsayno/mcp-gateway 4.7.0 → 4.9.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 +37 -0
- package/README.md +17 -0
- package/dist/cli.js +2 -2
- package/dist/cli.js.map +1 -1
- package/dist/config/loader.js +3 -3
- package/dist/config/loader.js.map +1 -1
- package/dist/config/migrate.d.ts +9 -3
- package/dist/config/migrate.d.ts.map +1 -1
- package/dist/config/migrate.js +37 -14
- package/dist/config/migrate.js.map +1 -1
- package/dist/edge/adapters.d.ts +12 -3
- package/dist/edge/adapters.d.ts.map +1 -1
- package/dist/edge/adapters.js +41 -11
- package/dist/edge/adapters.js.map +1 -1
- package/dist/edge/entry.d.ts +3 -1
- package/dist/edge/entry.d.ts.map +1 -1
- package/dist/edge/entry.js +1 -0
- package/dist/edge/entry.js.map +1 -1
- package/dist/edge/index.d.ts +35 -1
- package/dist/edge/index.d.ts.map +1 -1
- package/dist/edge/index.js +213 -15
- package/dist/edge/index.js.map +1 -1
- package/dist/edge/sync.d.ts +133 -0
- package/dist/edge/sync.d.ts.map +1 -0
- package/dist/edge/sync.js +190 -0
- package/dist/edge/sync.js.map +1 -0
- package/dist/gateway/admin.d.ts.map +1 -1
- package/dist/gateway/admin.js +9 -0
- package/dist/gateway/admin.js.map +1 -1
- package/dist/gateway/edge-control.d.ts +41 -0
- package/dist/gateway/edge-control.d.ts.map +1 -0
- package/dist/gateway/edge-control.js +139 -0
- package/dist/gateway/edge-control.js.map +1 -0
- package/dist/gateway/index.d.ts.map +1 -1
- package/dist/gateway/index.js +8 -0
- package/dist/gateway/index.js.map +1 -1
- package/dist/plugins/index.d.ts +21 -2
- package/dist/plugins/index.d.ts.map +1 -1
- package/dist/plugins/index.js +40 -2
- package/dist/plugins/index.js.map +1 -1
- package/dist/utils/deprecations.d.ts +33 -8
- package/dist/utils/deprecations.d.ts.map +1 -1
- package/dist/utils/deprecations.js +55 -10
- package/dist/utils/deprecations.js.map +1 -1
- package/dist/utils/types.d.ts +2 -2
- package/dist/utils/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/edge/adapters.d.ts
CHANGED
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
* Config can also come from environment variables with `configFromEnv(env)`:
|
|
10
10
|
* `MCP_GATEWAY_SERVERS` (JSON array of `{ id, url, headers?, tools? }`),
|
|
11
11
|
* `MCP_GATEWAY_API_KEYS` (comma separated), `MCP_GATEWAY_TOOL_NAMING`,
|
|
12
|
-
* `MCP_GATEWAY_CORS_ORIGINS
|
|
12
|
+
* `MCP_GATEWAY_CORS_ORIGINS`; offline / edge sync (4.8): `MCP_GATEWAY_CONTROL_PLANE`,
|
|
13
|
+
* `MCP_GATEWAY_CONTROL_KEY`, `MCP_GATEWAY_EDGE_ID`, `MCP_GATEWAY_QUEUE_TOOLS` (comma separated globs),
|
|
14
|
+
* `MCP_GATEWAY_SYNC_INTERVAL_MS` and a KV namespace bound as `MCP_GATEWAY_KV`.
|
|
13
15
|
*
|
|
14
16
|
* @module edge/adapters
|
|
15
17
|
*/
|
|
@@ -17,9 +19,16 @@ import { type EdgeConfig } from './index.js';
|
|
|
17
19
|
type Env = Record<string, unknown>;
|
|
18
20
|
/** Edge config from environment variables / Worker bindings. */
|
|
19
21
|
export declare function configFromEnv(env: Env): EdgeConfig;
|
|
20
|
-
|
|
22
|
+
interface WorkersCtx {
|
|
23
|
+
waitUntil(p: Promise<unknown>): void;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Cloudflare Workers module handler. The gateway (and its upstream sessions) is reused per isolate.
|
|
27
|
+
* With sync configured, add a Cron Trigger: `scheduled` pulls config, replays queued calls and pushes usage.
|
|
28
|
+
*/
|
|
21
29
|
export declare function workersHandler(build?: (env: Env) => EdgeConfig): {
|
|
22
|
-
fetch(request: Request, env?: Env): Promise<Response>;
|
|
30
|
+
fetch(request: Request, env?: Env, ctx?: WorkersCtx): Promise<Response>;
|
|
31
|
+
scheduled(_event: unknown, env?: Env, ctx?: WorkersCtx): Promise<unknown>;
|
|
23
32
|
};
|
|
24
33
|
interface ServeOptions {
|
|
25
34
|
port?: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapters.d.ts","sourceRoot":"","sources":["../../src/edge/adapters.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"adapters.d.ts","sourceRoot":"","sources":["../../src/edge/adapters.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAqB,KAAK,UAAU,EAAoB,MAAM,YAAY,CAAC;AAGlF,KAAK,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEnC,gEAAgE;AAChE,wBAAgB,aAAa,CAAC,GAAG,EAAE,GAAG,GAAG,UAAU,CAiClD;AAED,UAAU,UAAU;IAClB,SAAS,CAAC,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC;CACtC;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,GAAE,CAAC,GAAG,EAAE,GAAG,KAAK,UAA0B;mBAW3D,OAAO,QAAO,GAAG,QAAa,UAAU,GAAG,OAAO,CAAC,QAAQ,CAAC;sBAGzD,OAAO,QAAO,GAAG,QAAa,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC;EAMhF;AAED,UAAU,YAAY;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,0BAA0B;AAC1B,wBAAgB,SAAS,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,GAAE,YAAiB,GAAG,OAAO,CAK9E;AAED,wBAAwB;AACxB,wBAAgB,QAAQ,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,GAAE,YAAiB,GAAG,OAAO,CAK7E;AAED,qGAAqG;AACrG,wBAAsB,SAAS,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,GAAE,YAAiB,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAAC,CAyBlI"}
|
package/dist/edge/adapters.js
CHANGED
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
* Config can also come from environment variables with `configFromEnv(env)`:
|
|
10
10
|
* `MCP_GATEWAY_SERVERS` (JSON array of `{ id, url, headers?, tools? }`),
|
|
11
11
|
* `MCP_GATEWAY_API_KEYS` (comma separated), `MCP_GATEWAY_TOOL_NAMING`,
|
|
12
|
-
* `MCP_GATEWAY_CORS_ORIGINS
|
|
12
|
+
* `MCP_GATEWAY_CORS_ORIGINS`; offline / edge sync (4.8): `MCP_GATEWAY_CONTROL_PLANE`,
|
|
13
|
+
* `MCP_GATEWAY_CONTROL_KEY`, `MCP_GATEWAY_EDGE_ID`, `MCP_GATEWAY_QUEUE_TOOLS` (comma separated globs),
|
|
14
|
+
* `MCP_GATEWAY_SYNC_INTERVAL_MS` and a KV namespace bound as `MCP_GATEWAY_KV`.
|
|
13
15
|
*
|
|
14
16
|
* @module edge/adapters
|
|
15
17
|
*/
|
|
@@ -26,24 +28,52 @@ export function configFromEnv(env) {
|
|
|
26
28
|
servers = parsed;
|
|
27
29
|
}
|
|
28
30
|
const naming = str('MCP_GATEWAY_TOOL_NAMING');
|
|
31
|
+
const list = (k) => str(k)?.split(',').map((x) => x.trim()).filter(Boolean);
|
|
32
|
+
const cp = str('MCP_GATEWAY_CONTROL_PLANE');
|
|
33
|
+
const kv = env.MCP_GATEWAY_KV;
|
|
34
|
+
const interval = Number(str('MCP_GATEWAY_SYNC_INTERVAL_MS'));
|
|
29
35
|
return {
|
|
30
36
|
servers,
|
|
31
|
-
apiKeys:
|
|
32
|
-
|
|
33
|
-
|
|
37
|
+
apiKeys: list('MCP_GATEWAY_API_KEYS'),
|
|
38
|
+
// With a control plane, an unset naming comes from the snapshot.
|
|
39
|
+
toolNaming: naming === 'prefix' ? 'prefix' : naming === 'auto' || !cp ? 'auto' : undefined,
|
|
40
|
+
corsOrigins: list('MCP_GATEWAY_CORS_ORIGINS'),
|
|
41
|
+
...(cp
|
|
42
|
+
? {
|
|
43
|
+
sync: {
|
|
44
|
+
controlPlane: cp,
|
|
45
|
+
apiKey: str('MCP_GATEWAY_CONTROL_KEY'),
|
|
46
|
+
edgeId: str('MCP_GATEWAY_EDGE_ID'),
|
|
47
|
+
store: kv && typeof kv.get === 'function' ? kv : undefined,
|
|
48
|
+
},
|
|
49
|
+
offline: { queueTools: list('MCP_GATEWAY_QUEUE_TOOLS') },
|
|
50
|
+
...(Number.isFinite(interval) && str('MCP_GATEWAY_SYNC_INTERVAL_MS') ? { syncIntervalMs: interval } : {}),
|
|
51
|
+
}
|
|
52
|
+
: {}),
|
|
34
53
|
};
|
|
35
54
|
}
|
|
36
|
-
/**
|
|
55
|
+
/**
|
|
56
|
+
* Cloudflare Workers module handler. The gateway (and its upstream sessions) is reused per isolate.
|
|
57
|
+
* With sync configured, add a Cron Trigger: `scheduled` pulls config, replays queued calls and pushes usage.
|
|
58
|
+
*/
|
|
37
59
|
export function workersHandler(build = configFromEnv) {
|
|
38
60
|
let gw;
|
|
39
61
|
let built;
|
|
62
|
+
const get = (env) => {
|
|
63
|
+
if (!gw || built !== env) {
|
|
64
|
+
gw = createEdgeGateway(build(env));
|
|
65
|
+
built = env;
|
|
66
|
+
}
|
|
67
|
+
return gw;
|
|
68
|
+
};
|
|
40
69
|
return {
|
|
41
|
-
fetch(request, env = {}) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
70
|
+
fetch(request, env = {}, ctx) {
|
|
71
|
+
return get(env).fetch(request, ctx ? (p) => ctx.waitUntil(p) : undefined);
|
|
72
|
+
},
|
|
73
|
+
scheduled(_event, env = {}, ctx) {
|
|
74
|
+
const p = get(env).sync();
|
|
75
|
+
ctx?.waitUntil(p);
|
|
76
|
+
return p;
|
|
47
77
|
},
|
|
48
78
|
};
|
|
49
79
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapters.js","sourceRoot":"","sources":["../../src/edge/adapters.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"adapters.js","sourceRoot":"","sources":["../../src/edge/adapters.ts"],"names":[],"mappings":"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"}
|
package/dist/edge/entry.d.ts
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* Imports nothing from Node (except `serveNode`, which loads `node:http` lazily).
|
|
4
4
|
*/
|
|
5
5
|
export { createEdgeGateway, EdgeUpstream, readRpcReply, EDGE_PROTOCOL_VERSION } from './index.js';
|
|
6
|
-
export type { EdgeConfig, EdgeServerConfig, EdgeGateway } from './index.js';
|
|
6
|
+
export type { EdgeConfig, EdgeServerConfig, EdgeGateway, EdgeOfflineConfig, EdgeSyncReport } from './index.js';
|
|
7
|
+
export { EdgeSync, memoryStore } from './sync.js';
|
|
8
|
+
export type { EdgeSyncOptions, EdgeSyncStore, EdgeSnapshot, EdgeEvent, QueuedCall, PullResult, EdgeCatalogTool } from './sync.js';
|
|
7
9
|
export { workersHandler, serveDeno, serveBun, serveNode, configFromEnv } from './adapters.js';
|
|
8
10
|
//# sourceMappingURL=entry.d.ts.map
|
package/dist/edge/entry.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entry.d.ts","sourceRoot":"","sources":["../../src/edge/entry.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,YAAY,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAClG,YAAY,EAAE,UAAU,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"entry.d.ts","sourceRoot":"","sources":["../../src/edge/entry.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,YAAY,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAClG,YAAY,EAAE,UAAU,EAAE,gBAAgB,EAAE,WAAW,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC/G,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAClD,YAAY,EAAE,eAAe,EAAE,aAAa,EAAE,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAClI,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC"}
|
package/dist/edge/entry.js
CHANGED
|
@@ -3,5 +3,6 @@
|
|
|
3
3
|
* Imports nothing from Node (except `serveNode`, which loads `node:http` lazily).
|
|
4
4
|
*/
|
|
5
5
|
export { createEdgeGateway, EdgeUpstream, readRpcReply, EDGE_PROTOCOL_VERSION } from './index.js';
|
|
6
|
+
export { EdgeSync, memoryStore } from './sync.js';
|
|
6
7
|
export { workersHandler, serveDeno, serveBun, serveNode, configFromEnv } from './adapters.js';
|
|
7
8
|
//# sourceMappingURL=entry.js.map
|
package/dist/edge/entry.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entry.js","sourceRoot":"","sources":["../../src/edge/entry.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,YAAY,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAElG,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC"}
|
|
1
|
+
{"version":3,"file":"entry.js","sourceRoot":"","sources":["../../src/edge/entry.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,YAAY,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAElG,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAElD,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC"}
|
package/dist/edge/index.d.ts
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* @module edge
|
|
15
15
|
*/
|
|
16
16
|
import type { ToolFilterConfig } from '../utils/types.js';
|
|
17
|
+
import { EdgeSync, type EdgeCatalogTool, type EdgeSyncOptions, type PullResult } from './sync.js';
|
|
17
18
|
export declare const EDGE_PROTOCOL_VERSION = "2025-06-18";
|
|
18
19
|
export interface EdgeServerConfig {
|
|
19
20
|
id: string;
|
|
@@ -23,6 +24,15 @@ export interface EdgeServerConfig {
|
|
|
23
24
|
headers?: Record<string, string>;
|
|
24
25
|
tools?: ToolFilterConfig;
|
|
25
26
|
timeoutMs?: number;
|
|
27
|
+
/** Known tools (from a control-plane snapshot): served by `tools/list` while the upstream is unreachable. */
|
|
28
|
+
catalog?: EdgeCatalogTool[];
|
|
29
|
+
}
|
|
30
|
+
export interface EdgeOfflineConfig {
|
|
31
|
+
/**
|
|
32
|
+
* Tools (globs on the exposed name, or `<server>__<tool>`) whose calls are queued while their upstream is
|
|
33
|
+
* unreachable and replayed on the next sync. Needs `sync`. Only for idempotent / fire-and-forget tools.
|
|
34
|
+
*/
|
|
35
|
+
queueTools?: string[];
|
|
26
36
|
}
|
|
27
37
|
export interface EdgeConfig {
|
|
28
38
|
servers: EdgeServerConfig[];
|
|
@@ -38,6 +48,11 @@ export interface EdgeConfig {
|
|
|
38
48
|
fetch?: typeof fetch;
|
|
39
49
|
name?: string;
|
|
40
50
|
version?: string;
|
|
51
|
+
/** Control-plane sync (4.8): config snapshots, usage outbox, offline queue. */
|
|
52
|
+
sync?: EdgeSync | EdgeSyncOptions;
|
|
53
|
+
/** Minimum time between background syncs triggered by requests (default 60 s; 0 = only explicit `sync()`). */
|
|
54
|
+
syncIntervalMs?: number;
|
|
55
|
+
offline?: EdgeOfflineConfig;
|
|
41
56
|
}
|
|
42
57
|
interface UpstreamTool {
|
|
43
58
|
name: string;
|
|
@@ -61,11 +76,30 @@ export declare class EdgeUpstream {
|
|
|
61
76
|
private post;
|
|
62
77
|
private init;
|
|
63
78
|
request(method: string, params?: Json, retried?: boolean): Promise<unknown>;
|
|
79
|
+
/** True while the last tools/list failed and tools come from the stale cache or the snapshot catalog. */
|
|
80
|
+
offline: boolean;
|
|
64
81
|
tools(maxAgeMs?: number): Promise<UpstreamTool[]>;
|
|
82
|
+
/** Carry the session and tool cache over to a replacement with the same endpoint. */
|
|
83
|
+
adopt(prev: EdgeUpstream): void;
|
|
84
|
+
}
|
|
85
|
+
export interface EdgeSyncReport {
|
|
86
|
+
config: PullResult['status'] | 'disabled';
|
|
87
|
+
etag?: string;
|
|
88
|
+
replayed: number;
|
|
89
|
+
failed: number;
|
|
90
|
+
pending: number;
|
|
91
|
+
pushed: number;
|
|
92
|
+
offline: boolean;
|
|
93
|
+
errors: string[];
|
|
65
94
|
}
|
|
66
95
|
export interface EdgeGateway {
|
|
67
|
-
|
|
96
|
+
/** Fetch handler; `waitUntil` (Workers `ctx.waitUntil`) keeps background syncs alive after the response. */
|
|
97
|
+
fetch(request: Request, waitUntil?: (p: Promise<unknown>) => void): Promise<Response>;
|
|
68
98
|
upstreams: ReadonlyMap<string, EdgeUpstream>;
|
|
99
|
+
/** Pull config, replay queued calls, push usage events (no-op report when `sync` is not configured). */
|
|
100
|
+
sync(): Promise<EdgeSyncReport>;
|
|
101
|
+
/** The sync client, when configured. */
|
|
102
|
+
readonly syncClient?: EdgeSync;
|
|
69
103
|
}
|
|
70
104
|
/** Build the edge gateway's Fetch handler. */
|
|
71
105
|
export declare function createEdgeGateway(config: EdgeConfig): EdgeGateway;
|
package/dist/edge/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/edge/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/edge/index.ts"],"names":[],"mappings":"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"}
|
package/dist/edge/index.js
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* @module edge
|
|
15
15
|
*/
|
|
16
16
|
import { globToRegExp } from '../utils/tool-filter.js';
|
|
17
|
+
import { EdgeSync } from './sync.js';
|
|
17
18
|
export const EDGE_PROTOCOL_VERSION = '2025-06-18';
|
|
18
19
|
const SUPPORTED_VERSIONS = ['2025-06-18', '2025-03-26', '2024-11-05'];
|
|
19
20
|
const isObj = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
@@ -72,10 +73,14 @@ export async function readRpcReply(res, id) {
|
|
|
72
73
|
class RpcError extends Error {
|
|
73
74
|
code;
|
|
74
75
|
data;
|
|
75
|
-
|
|
76
|
+
transient;
|
|
77
|
+
constructor(code, message, data,
|
|
78
|
+
/** The upstream could not be reached or answered 5xx (worth queueing / retrying). */
|
|
79
|
+
transient = false) {
|
|
76
80
|
super(message);
|
|
77
81
|
this.code = code;
|
|
78
82
|
this.data = data;
|
|
83
|
+
this.transient = transient;
|
|
79
84
|
}
|
|
80
85
|
}
|
|
81
86
|
/** Minimal Streamable HTTP MCP client for one upstream. */
|
|
@@ -145,35 +150,180 @@ export class EdgeUpstream {
|
|
|
145
150
|
return this.request(method, params, true);
|
|
146
151
|
}
|
|
147
152
|
if (!res.ok)
|
|
148
|
-
throw new RpcError(-32000, `Upstream "${this.cfg.id}" HTTP ${res.status}
|
|
153
|
+
throw new RpcError(-32000, `Upstream "${this.cfg.id}" HTTP ${res.status}`, undefined, res.status >= 500);
|
|
149
154
|
const reply = await readRpcReply(res, id);
|
|
150
155
|
if (isObj(reply.error))
|
|
151
156
|
throw new RpcError(Number(reply.error.code ?? -32603), String(reply.error.message ?? 'error'), reply.error.data);
|
|
152
157
|
return reply.result;
|
|
153
158
|
}
|
|
159
|
+
/** True while the last tools/list failed and tools come from the stale cache or the snapshot catalog. */
|
|
160
|
+
offline = false;
|
|
154
161
|
async tools(maxAgeMs = 30_000) {
|
|
155
162
|
if (this.toolsCache && Date.now() - this.toolsCache.at < maxAgeMs)
|
|
156
163
|
return this.toolsCache.tools;
|
|
157
164
|
const all = [];
|
|
158
165
|
let cursor;
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
166
|
+
try {
|
|
167
|
+
for (let page = 0; page < 50; page++) {
|
|
168
|
+
const r = (await this.request('tools/list', cursor ? { cursor } : undefined));
|
|
169
|
+
all.push(...(r?.tools ?? []));
|
|
170
|
+
cursor = r?.nextCursor;
|
|
171
|
+
if (!cursor)
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
165
174
|
}
|
|
175
|
+
catch (err) {
|
|
176
|
+
// Offline: keep serving the last known tool list (or the control-plane catalog).
|
|
177
|
+
const known = this.toolsCache?.tools ?? this.cfg.catalog?.filter((t) => allowed(t.name, this.cfg.tools));
|
|
178
|
+
if (known?.length && isTransient(err)) {
|
|
179
|
+
this.offline = true;
|
|
180
|
+
return known;
|
|
181
|
+
}
|
|
182
|
+
throw err;
|
|
183
|
+
}
|
|
184
|
+
this.offline = false;
|
|
166
185
|
const tools = all.filter((t) => allowed(t.name, this.cfg.tools));
|
|
167
186
|
this.toolsCache = { at: Date.now(), tools };
|
|
168
187
|
return tools;
|
|
169
188
|
}
|
|
189
|
+
/** Carry the session and tool cache over to a replacement with the same endpoint. */
|
|
190
|
+
adopt(prev) {
|
|
191
|
+
this.session = prev.session;
|
|
192
|
+
this.seq = prev.seq;
|
|
193
|
+
this.toolsCache = prev.toolsCache;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/** Network failure, timeout, or upstream 5xx: the call may succeed later. */
|
|
197
|
+
function isTransient(err) {
|
|
198
|
+
return err instanceof RpcError ? err.transient : true;
|
|
170
199
|
}
|
|
171
200
|
/** Build the edge gateway's Fetch handler. */
|
|
172
201
|
export function createEdgeGateway(config) {
|
|
173
202
|
const fetchImpl = config.fetch ?? ((...a) => fetch(...a));
|
|
174
203
|
const upstreams = new Map(config.servers.map((s) => [s.id, new EdgeUpstream(s, fetchImpl)]));
|
|
175
204
|
const mcpPath = config.mcpPath ?? '/mcp';
|
|
176
|
-
|
|
205
|
+
let keys = (config.apiKeys ?? []).filter(Boolean);
|
|
206
|
+
let toolNaming = config.toolNaming;
|
|
207
|
+
let corsOrigins = config.corsOrigins;
|
|
208
|
+
const sync = config.sync instanceof EdgeSync ? config.sync : config.sync ? new EdgeSync({ fetch: config.fetch, ...config.sync }) : undefined;
|
|
209
|
+
const queueRes = (config.offline?.queueTools ?? []).map((g) => globToRegExp(g));
|
|
210
|
+
const syncInterval = config.syncIntervalMs ?? 60_000;
|
|
211
|
+
let appliedEtag;
|
|
212
|
+
let lastSync = 0;
|
|
213
|
+
let lastReport;
|
|
214
|
+
let syncing;
|
|
215
|
+
let booted;
|
|
216
|
+
/** Merge a control-plane snapshot with the local config (local servers / headers / settings win). */
|
|
217
|
+
function applySnapshot(snap) {
|
|
218
|
+
if (snap.etag === appliedEtag)
|
|
219
|
+
return;
|
|
220
|
+
appliedEtag = snap.etag;
|
|
221
|
+
const local = new Map(config.servers.map((s) => [s.id, s]));
|
|
222
|
+
const merged = snap.config.servers.map((s) => {
|
|
223
|
+
const l = local.get(s.id);
|
|
224
|
+
return l ? { ...s, ...l, headers: { ...(s.headers ?? {}), ...(l.headers ?? {}) }, catalog: l.catalog ?? s.catalog } : s;
|
|
225
|
+
});
|
|
226
|
+
for (const l of config.servers)
|
|
227
|
+
if (!merged.some((m) => m.id === l.id))
|
|
228
|
+
merged.push(l);
|
|
229
|
+
const next = new Map();
|
|
230
|
+
for (const s of merged) {
|
|
231
|
+
const u = new EdgeUpstream(s, fetchImpl);
|
|
232
|
+
const prev = upstreams.get(s.id);
|
|
233
|
+
if (prev && prev.cfg.url === s.url && JSON.stringify(prev.cfg.headers ?? {}) === JSON.stringify(s.headers ?? {}))
|
|
234
|
+
u.adopt(prev);
|
|
235
|
+
next.set(s.id, u);
|
|
236
|
+
}
|
|
237
|
+
upstreams.clear();
|
|
238
|
+
for (const [k, v] of next)
|
|
239
|
+
upstreams.set(k, v);
|
|
240
|
+
keys = [...new Set([...(config.apiKeys ?? []), ...(snap.config.apiKeys ?? [])].filter(Boolean))];
|
|
241
|
+
toolNaming = config.toolNaming ?? snap.config.toolNaming;
|
|
242
|
+
corsOrigins = config.corsOrigins ?? snap.config.corsOrigins;
|
|
243
|
+
}
|
|
244
|
+
/** First request: apply the cached snapshot, or pull one when nothing is cached. */
|
|
245
|
+
function boot() {
|
|
246
|
+
if (!sync)
|
|
247
|
+
return Promise.resolve();
|
|
248
|
+
booted ??= (async () => {
|
|
249
|
+
const cached = await sync.cached();
|
|
250
|
+
if (cached)
|
|
251
|
+
applySnapshot(cached);
|
|
252
|
+
else
|
|
253
|
+
await runSync();
|
|
254
|
+
})().catch(() => undefined);
|
|
255
|
+
return booted;
|
|
256
|
+
}
|
|
257
|
+
async function runSync() {
|
|
258
|
+
if (!sync)
|
|
259
|
+
return { config: 'disabled', replayed: 0, failed: 0, pending: 0, pushed: 0, offline: false, errors: [] };
|
|
260
|
+
syncing ??= (async () => {
|
|
261
|
+
const errors = [];
|
|
262
|
+
const pulled = await sync.pull();
|
|
263
|
+
if (pulled.snapshot)
|
|
264
|
+
applySnapshot(pulled.snapshot);
|
|
265
|
+
if (pulled.error)
|
|
266
|
+
errors.push(`pull: ${pulled.error}`);
|
|
267
|
+
// Replay queued calls (oldest first); stop at the first upstream that is still unreachable.
|
|
268
|
+
let replayed = 0;
|
|
269
|
+
let failed = 0;
|
|
270
|
+
const done = [];
|
|
271
|
+
const retry = [];
|
|
272
|
+
const down = new Set();
|
|
273
|
+
for (const q of await sync.queued()) {
|
|
274
|
+
if (down.has(q.server)) {
|
|
275
|
+
retry.push(q.id);
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
const started = Date.now();
|
|
279
|
+
try {
|
|
280
|
+
const u = upstreams.get(q.server);
|
|
281
|
+
if (!u)
|
|
282
|
+
throw new RpcError(-32602, `Unknown server: ${q.server}`);
|
|
283
|
+
await u.request('tools/call', { name: q.tool, arguments: q.arguments });
|
|
284
|
+
replayed++;
|
|
285
|
+
done.push(q.id);
|
|
286
|
+
await sync.record({ ts: new Date().toISOString(), server: q.server, tool: q.tool, durationMs: Date.now() - started, ok: true, mode: 'replayed' });
|
|
287
|
+
}
|
|
288
|
+
catch (err) {
|
|
289
|
+
if (isTransient(err)) {
|
|
290
|
+
down.add(q.server);
|
|
291
|
+
retry.push(q.id);
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
failed++;
|
|
295
|
+
done.push(q.id);
|
|
296
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
297
|
+
await sync.record({ ts: new Date().toISOString(), server: q.server, tool: q.tool, durationMs: Date.now() - started, ok: false, mode: 'replayed', error: msg });
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
if (done.length || retry.length)
|
|
302
|
+
await sync.settle(done, retry);
|
|
303
|
+
const pushed = await sync.push();
|
|
304
|
+
if (pushed.error)
|
|
305
|
+
errors.push(`push: ${pushed.error}`);
|
|
306
|
+
lastSync = Date.now();
|
|
307
|
+
lastReport = {
|
|
308
|
+
config: pulled.status,
|
|
309
|
+
etag: pulled.snapshot?.etag,
|
|
310
|
+
replayed,
|
|
311
|
+
failed,
|
|
312
|
+
pending: retry.length,
|
|
313
|
+
pushed: pushed.sent,
|
|
314
|
+
offline: pulled.status === 'offline' || pulled.status === 'none' || pushed.offline,
|
|
315
|
+
errors,
|
|
316
|
+
};
|
|
317
|
+
return lastReport;
|
|
318
|
+
})().finally(() => {
|
|
319
|
+
syncing = undefined;
|
|
320
|
+
});
|
|
321
|
+
return syncing;
|
|
322
|
+
}
|
|
323
|
+
function record(ev) {
|
|
324
|
+
if (sync)
|
|
325
|
+
void sync.record(ev).catch(() => undefined);
|
|
326
|
+
}
|
|
177
327
|
const json = (status, body, extra = {}) => new Response(JSON.stringify(body), { status, headers: { 'content-type': 'application/json', ...extra } });
|
|
178
328
|
async function authorized(req) {
|
|
179
329
|
if (keys.length === 0)
|
|
@@ -202,7 +352,7 @@ export function createEdgeGateway(config) {
|
|
|
202
352
|
count.set(t.tool.name, (count.get(t.tool.name) ?? 0) + 1);
|
|
203
353
|
const list = flat.map((t) => ({
|
|
204
354
|
...t,
|
|
205
|
-
exposed:
|
|
355
|
+
exposed: toolNaming === 'prefix' || (count.get(t.tool.name) ?? 0) > 1 ? `${t.serverId}__${t.tool.name}` : t.tool.name,
|
|
206
356
|
}));
|
|
207
357
|
return { list, errors };
|
|
208
358
|
}
|
|
@@ -211,12 +361,36 @@ export function createEdgeGateway(config) {
|
|
|
211
361
|
const hit = server ? list.find((t) => t.serverId === server && t.tool.name === name) : list.find((t) => t.exposed === name) ?? list.find((t) => t.tool.name === name);
|
|
212
362
|
if (!hit)
|
|
213
363
|
throw new RpcError(-32602, `Unknown tool: ${name}`);
|
|
214
|
-
const
|
|
215
|
-
|
|
364
|
+
const started = Date.now();
|
|
365
|
+
const ev = { server: hit.serverId, tool: hit.tool.name };
|
|
366
|
+
try {
|
|
367
|
+
const result = await upstreams.get(hit.serverId).request('tools/call', { name: hit.tool.name, arguments: args });
|
|
368
|
+
record({ ts: new Date().toISOString(), ...ev, durationMs: Date.now() - started, ok: true, mode: 'live' });
|
|
369
|
+
return { serverId: hit.serverId, result };
|
|
370
|
+
}
|
|
371
|
+
catch (err) {
|
|
372
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
373
|
+
const queueable = sync && isTransient(err) && queueRes.some((re) => re.test(hit.exposed) || re.test(`${hit.serverId}__${hit.tool.name}`));
|
|
374
|
+
if (queueable) {
|
|
375
|
+
const q = await sync.enqueue({ server: hit.serverId, tool: hit.tool.name, arguments: args });
|
|
376
|
+
record({ ts: new Date().toISOString(), ...ev, durationMs: Date.now() - started, ok: true, mode: 'queued', error: msg });
|
|
377
|
+
return {
|
|
378
|
+
serverId: hit.serverId,
|
|
379
|
+
queued: q.id,
|
|
380
|
+
result: {
|
|
381
|
+
content: [{ type: 'text', text: `Upstream "${hit.serverId}" is unreachable; the call was queued for delivery (${q.id}).` }],
|
|
382
|
+
structuredContent: { queued: true, id: q.id },
|
|
383
|
+
_meta: { 'mcp-gateway/queued': q.id },
|
|
384
|
+
},
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
record({ ts: new Date().toISOString(), ...ev, durationMs: Date.now() - started, ok: false, mode: 'live', error: msg });
|
|
388
|
+
throw err;
|
|
389
|
+
}
|
|
216
390
|
}
|
|
217
391
|
function cors(req, res) {
|
|
218
392
|
const origin = req.headers.get('origin');
|
|
219
|
-
const allowedOrigins =
|
|
393
|
+
const allowedOrigins = corsOrigins ?? [];
|
|
220
394
|
if (origin && (allowedOrigins.includes('*') || allowedOrigins.includes(origin))) {
|
|
221
395
|
res.headers.set('access-control-allow-origin', allowedOrigins.includes('*') ? '*' : origin);
|
|
222
396
|
res.headers.set('access-control-allow-headers', 'authorization, content-type, mcp-session-id, mcp-protocol-version, x-api-key');
|
|
@@ -322,18 +496,42 @@ export function createEdgeGateway(config) {
|
|
|
322
496
|
const started = Date.now();
|
|
323
497
|
try {
|
|
324
498
|
const r = await callTool(b.tool, isObj(b.arguments) ? b.arguments : {}, typeof b.server === 'string' ? b.server : undefined);
|
|
325
|
-
return json(200, { result: r.result, server: r.serverId, tool: b.tool, durationMs: Date.now() - started });
|
|
499
|
+
return json(r.queued ? 202 : 200, { result: r.result, server: r.serverId, tool: b.tool, durationMs: Date.now() - started, ...(r.queued ? { queued: r.queued } : {}) });
|
|
326
500
|
}
|
|
327
501
|
catch (err) {
|
|
328
502
|
const e = err instanceof RpcError ? err : new RpcError(-32603, err instanceof Error ? err.message : String(err));
|
|
329
503
|
return json(e.code === -32602 ? 404 : 502, { error: e.code === -32602 ? 'Not Found' : 'Bad Gateway', message: e.message, code: e.code });
|
|
330
504
|
}
|
|
331
505
|
}
|
|
506
|
+
if (path === '/api/v1/edge/status' && req.method === 'GET') {
|
|
507
|
+
return json(200, {
|
|
508
|
+
sync: !!sync,
|
|
509
|
+
edgeId: sync ? await sync.id() : undefined,
|
|
510
|
+
etag: appliedEtag ?? null,
|
|
511
|
+
lastSync: lastSync ? new Date(lastSync).toISOString() : null,
|
|
512
|
+
lastReport: lastReport ?? null,
|
|
513
|
+
outbox: sync ? (await sync.outbox()).length : 0,
|
|
514
|
+
queued: sync ? (await sync.queued()).length : 0,
|
|
515
|
+
offlineServers: [...upstreams.values()].filter((u) => u.offline).map((u) => u.cfg.id),
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
if (path === '/api/v1/edge/sync' && req.method === 'POST') {
|
|
519
|
+
return json(sync ? 200 : 404, sync ? await runSync() : { error: 'Not Found', message: 'sync is not configured' });
|
|
520
|
+
}
|
|
332
521
|
return json(404, { error: 'Not Found' });
|
|
333
522
|
}
|
|
334
523
|
return {
|
|
335
524
|
upstreams,
|
|
336
|
-
|
|
525
|
+
syncClient: sync,
|
|
526
|
+
sync: runSync,
|
|
527
|
+
async fetch(request, waitUntil) {
|
|
528
|
+
await boot();
|
|
529
|
+
if (sync && syncInterval > 0 && !syncing && Date.now() - lastSync >= syncInterval) {
|
|
530
|
+
// Mark now so concurrent requests do not each start a sync.
|
|
531
|
+
lastSync = Date.now();
|
|
532
|
+
const p = runSync().catch(() => undefined);
|
|
533
|
+
waitUntil?.(p);
|
|
534
|
+
}
|
|
337
535
|
return cors(request, await route(request));
|
|
338
536
|
},
|
|
339
537
|
};
|