@vurb/swarm 3.8.2

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.
@@ -0,0 +1,146 @@
1
+ import type { HandoffPayload, HandoffStateStore, ToolResponse } from '@vurb/core';
2
+ import type { Tool as McpTool } from '@modelcontextprotocol/sdk/types.js';
3
+ export interface SwarmGatewayConfig {
4
+ /**
5
+ * Domain → upstream URI registry.
6
+ * @example `{ finance: 'http://finance-agent:8081', devops: 'http://devops-agent:8082' }`
7
+ */
8
+ registry: Record<string, string>;
9
+ /** HMAC secret shared with all upstream micro-servers. */
10
+ delegationSecret: string;
11
+ /** Store for Claim-Check pattern (state > 2 KB). Defaults to InMemory. */
12
+ stateStore?: HandoffStateStore;
13
+ /** Upstream connection timeout in ms (default: 5 000). */
14
+ connectTimeoutMs?: number;
15
+ /** Tunnel idle timeout in ms (default: 300 000 = 5 min). */
16
+ idleTimeoutMs?: number;
17
+ /** Delegation token TTL in seconds (default: 60). */
18
+ tokenTtlSeconds?: number;
19
+ /**
20
+ * Upstream transport selection.
21
+ * - `'auto'` (default): SSE on Node.js, HTTP on edge runtimes
22
+ * - `'sse'`: Always use SSE (persistent connection)
23
+ * - `'http'`: Always use Streamable HTTP (stateless, edge-compatible)
24
+ */
25
+ upstreamTransport?: 'auto' | 'sse' | 'http';
26
+ /**
27
+ * Name used for the return-trip tool.
28
+ * Default: `'gateway'` → tool name `'gateway.return_to_triage'`
29
+ */
30
+ gatewayName?: string;
31
+ /**
32
+ * Maximum number of concurrent active handoff sessions.
33
+ * Excess activations are rejected with `REGISTRY_SESSION_LIMIT_EXCEEDED`.
34
+ * Default: 100.
35
+ */
36
+ maxSessions?: number;
37
+ }
38
+ /**
39
+ * SwarmGateway — B2BUA for multi-agent MCP orchestration.
40
+ *
41
+ * @example
42
+ * ```typescript
43
+ * import { SwarmGateway } from '@vurb/swarm';
44
+ *
45
+ * const gateway = new SwarmGateway({
46
+ * registry: {
47
+ * finance: 'http://finance-agent:8081',
48
+ * devops: 'http://devops-agent:8082',
49
+ * },
50
+ * delegationSecret: process.env.VURB_DELEGATION_SECRET!,
51
+ * });
52
+ *
53
+ * // Pass to attachToServer:
54
+ * registry.attachToServer(server, { swarmGateway: gateway });
55
+ * ```
56
+ */
57
+ export declare class SwarmGateway {
58
+ private readonly _config;
59
+ private readonly _sessions;
60
+ private readonly _rewriter;
61
+ private readonly _closingSessions;
62
+ constructor(config: SwarmGatewayConfig);
63
+ /**
64
+ * Activate a handoff tunnel to the upstream server described by `payload`.
65
+ *
66
+ * Called by `ServerAttachment` after detecting a `HandoffResponse` from a
67
+ * tool handler. Connects asynchronously — does not block the ACK to the LLM.
68
+ *
69
+ * **Concurrent safety**: If an activation is already in progress or active
70
+ * for `sessionId`, the previous session is disposed before the new one starts.
71
+ *
72
+ * @param payload - Handoff payload produced by `f.handoff()`
73
+ * @param sessionId - MCP session ID (used to key the active tunnel)
74
+ * @param signal - AbortSignal tied to the parent MCP connection
75
+ * @throws If the target is not found in the registry
76
+ * @throws If the session limit is exceeded
77
+ */
78
+ activateHandoff(payload: HandoffPayload, sessionId: string, signal: AbortSignal): Promise<void>;
79
+ /**
80
+ * Proxy the tools/list for an active session.
81
+ *
82
+ * Returns `null` for non-existent sessions.
83
+ * Returns only the return-trip escape tool if the upstream is unreachable.
84
+ * Returns an empty list while the session is still connecting.
85
+ */
86
+ proxyToolsList(sessionId: string): Promise<McpTool[] | null>;
87
+ /**
88
+ * Proxy a tools/call for an active session.
89
+ *
90
+ * Strips the namespace prefix before forwarding to the upstream.
91
+ *
92
+ * @returns The upstream's ToolResponse, or `null` if no active tunnel.
93
+ */
94
+ proxyToolsCall(sessionId: string, name: string, args: Record<string, unknown>, signal: AbortSignal): Promise<ToolResponse | null>;
95
+ /**
96
+ * Close the tunnel for `sessionId` and restore the gateway's tool list.
97
+ *
98
+ * Called when the LLM invokes the `gateway.return_to_triage` tool.
99
+ * `ServerAttachment` emits `notifications/tools/list_changed` after this.
100
+ */
101
+ returnToGateway(sessionId: string): Promise<void>;
102
+ /**
103
+ * `true` if there is a tracked tunnel (connecting or active) for the given session.
104
+ *
105
+ * Returning `true` during `'connecting'` ensures `ServerAttachment` routes
106
+ * tools/call through `proxyToolsCall`, which already returns the correct
107
+ * `HANDOFF_CONNECTING` error for in-progress tunnels — preventing gateway-local
108
+ * execution from silently bypassing the handoff.
109
+ */
110
+ hasActiveHandoff(sessionId: string): boolean;
111
+ /** `true` if an activation is in progress for the given session. */
112
+ isConnecting(sessionId: string): boolean;
113
+ /**
114
+ * Total number of tracked sessions (connecting + active).
115
+ *
116
+ * exposed for integration testing and observability.
117
+ * Allows tests to assert session lifecycle transitions deterministically
118
+ * without accessing private state via casting.
119
+ */
120
+ get sessionCount(): number;
121
+ /**
122
+ * Number of sessions currently in the `'connecting'` state.
123
+ *
124
+ * useful for load-shedding checks and integration tests.
125
+ */
126
+ get connectingCount(): number;
127
+ /**
128
+ * Dispose all active sessions and release all resources.
129
+ *
130
+ * Call this when shutting down the gateway to cleanly close
131
+ * all upstream connections, idle timers, and AbortController listeners.
132
+ */
133
+ dispose(): Promise<void>;
134
+ private _closeSession;
135
+ /**
136
+ * Extract the domain key from a target URI.
137
+ *
138
+ * `'mcp://finance-agent.internal:8080'` → `'finance'` (registry lookup)
139
+ * `'finance'` → `'finance'` (direct key)
140
+ *
141
+ * @throws If the target does not resolve to any entry in the registry.
142
+ * Configure all targets explicitly in `SwarmGatewayConfig.registry`.
143
+ */
144
+ private _resolveDomain;
145
+ }
146
+ //# sourceMappingURL=SwarmGateway.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SwarmGateway.d.ts","sourceRoot":"","sources":["../src/SwarmGateway.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,cAAc,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAIlF,OAAO,KAAK,EAAE,IAAI,IAAI,OAAO,EAAE,MAAM,oCAAoC,CAAC;AAM1E,MAAM,WAAW,kBAAkB;IAC/B;;;OAGG;IACH,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,0DAA0D;IAC1D,gBAAgB,EAAE,MAAM,CAAC;IACzB,0EAA0E;IAC1E,UAAU,CAAC,EAAE,iBAAiB,CAAC;IAC/B,0DAA0D;IAC1D,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,4DAA4D;IAC5D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,qDAAqD;IACrD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,CAAC;IAC5C;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AAkBD;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,YAAY;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA+B;IACvD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAmC;IAC7D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA2B;IAErD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAqB;gBAE1C,MAAM,EAAE,kBAAkB;IAiCtC;;;;;;;;;;;;;;OAcG;IACG,eAAe,CACjB,OAAO,EAAE,cAAc,EACvB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,WAAW,GACpB,OAAO,CAAC,IAAI,CAAC;IAiEhB;;;;;;OAMG;IACG,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC;IAsBlE;;;;;;OAMG;IACG,cAAc,CAChB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,MAAM,EAAE,WAAW,GACpB,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IA6C/B;;;;;OAKG;IACG,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIvD;;;;;;;OAOG;IACH,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAI5C,oEAAoE;IACpE,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAIxC;;;;;;OAMG;IACH,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED;;;;OAIG;IACH,IAAI,eAAe,IAAI,MAAM,CAM5B;IAED;;;;;OAKG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;YAOhB,aAAa;IAsB3B;;;;;;;;OAQG;IACH,OAAO,CAAC,cAAc;CAsCzB"}
@@ -0,0 +1,347 @@
1
+ /**
2
+ * Federated Handoff Protocol — SwarmGateway (B2BUA)
3
+ *
4
+ * The central orchestrator implementing the Back-to-Back User Agent pattern:
5
+ *
6
+ * - **External face (UAS)**: MCP server for the LLM client (Claude/Cursor)
7
+ * - **Internal face (UAC)**: MCP client for upstream micro-servers
8
+ *
9
+ * Lifecycle per session:
10
+ * 1. `activateHandoff()` — opens tunnel to upstream, mints delegation token
11
+ * 2. `proxyToolsList()` — returns prefixed upstream tools + return-trip tool
12
+ * 3. `proxyToolsCall()` — strips prefix, forwards to upstream
13
+ * 4. `returnToGateway()` — closes tunnel, session returns to gateway tools
14
+ *
15
+ * @module
16
+ */
17
+ import { randomUUID } from 'node:crypto';
18
+ import { mintDelegationToken, InMemoryHandoffStateStore, toolError, } from '@vurb/core';
19
+ import { UpstreamMcpClient } from './UpstreamMcpClient.js';
20
+ import { NamespaceRewriter, NamespaceError } from './NamespaceRewriter.js';
21
+ import { injectReturnTripTool } from './ReturnTripInjector.js';
22
+ // ============================================================================
23
+ // SwarmGateway
24
+ // ============================================================================
25
+ /**
26
+ * SwarmGateway — B2BUA for multi-agent MCP orchestration.
27
+ *
28
+ * @example
29
+ * ```typescript
30
+ * import { SwarmGateway } from '@vurb/swarm';
31
+ *
32
+ * const gateway = new SwarmGateway({
33
+ * registry: {
34
+ * finance: 'http://finance-agent:8081',
35
+ * devops: 'http://devops-agent:8082',
36
+ * },
37
+ * delegationSecret: process.env.VURB_DELEGATION_SECRET!,
38
+ * });
39
+ *
40
+ * // Pass to attachToServer:
41
+ * registry.attachToServer(server, { swarmGateway: gateway });
42
+ * ```
43
+ */
44
+ export class SwarmGateway {
45
+ _config;
46
+ _sessions = new Map();
47
+ _rewriter = new NamespaceRewriter();
48
+ // tracks sessions currently being closed to prevent concurrent double-dispose
49
+ _closingSessions = new Set();
50
+ constructor(config) {
51
+ // validate registry — reject entries with empty URIs before they
52
+ // cause a silent `TypeError: Invalid URL` deep in _resolveTransport at runtime.
53
+ // An empty-string URI is always a configuration error, not a runtime edge case.
54
+ const emptyUriKeys = Object.entries(config.registry)
55
+ .filter(([, uri]) => !uri)
56
+ .map(([key]) => JSON.stringify(key));
57
+ if (emptyUriKeys.length > 0) {
58
+ throw Object.assign(new Error(`[vurb/swarm] Registry entries with empty URIs: ${emptyUriKeys.join(', ')}. ` +
59
+ 'All registry values must be non-empty URI strings.'), { code: 'REGISTRY_INVALID_URI' });
60
+ }
61
+ this._config = {
62
+ stateStore: config.stateStore ?? new InMemoryHandoffStateStore(),
63
+ connectTimeoutMs: config.connectTimeoutMs ?? 5_000,
64
+ idleTimeoutMs: config.idleTimeoutMs ?? 300_000,
65
+ tokenTtlSeconds: config.tokenTtlSeconds ?? 60,
66
+ upstreamTransport: config.upstreamTransport ?? 'auto',
67
+ gatewayName: config.gatewayName ?? 'gateway',
68
+ // enforce a default session limit to prevent unbounded growth
69
+ maxSessions: config.maxSessions ?? 100,
70
+ registry: config.registry,
71
+ delegationSecret: config.delegationSecret,
72
+ };
73
+ }
74
+ // ── Public API ───────────────────────────────────────────
75
+ /**
76
+ * Activate a handoff tunnel to the upstream server described by `payload`.
77
+ *
78
+ * Called by `ServerAttachment` after detecting a `HandoffResponse` from a
79
+ * tool handler. Connects asynchronously — does not block the ACK to the LLM.
80
+ *
81
+ * **Concurrent safety**: If an activation is already in progress or active
82
+ * for `sessionId`, the previous session is disposed before the new one starts.
83
+ *
84
+ * @param payload - Handoff payload produced by `f.handoff()`
85
+ * @param sessionId - MCP session ID (used to key the active tunnel)
86
+ * @param signal - AbortSignal tied to the parent MCP connection
87
+ * @throws If the target is not found in the registry
88
+ * @throws If the session limit is exceeded
89
+ */
90
+ async activateHandoff(payload, sessionId, signal) {
91
+ // dispose any pre-existing session for this ID before starting
92
+ await this._closeSession(sessionId);
93
+ // count ALL sessions (connecting + active) to prevent bypass attacks
94
+ // where an attacker opens maxSessions 'connecting' tunnels and then opens more.
95
+ if (this._sessions.size >= this._config.maxSessions) {
96
+ throw Object.assign(new Error(`[vurb/swarm] Session limit of ${this._config.maxSessions} reached. ` +
97
+ 'Close existing sessions before activating more.'), { code: 'SESSION_LIMIT_EXCEEDED' });
98
+ }
99
+ // Mark the slot as 'connecting' immediately to prevent double-activation
100
+ this._sessions.set(sessionId, { status: 'connecting' });
101
+ // track the client so dispose() can be called if connect() fails
102
+ let client;
103
+ try {
104
+ // _resolveDomain throws on unknown targets
105
+ const domain = this._resolveDomain(payload.target);
106
+ const upstreamUri = this._config.registry[domain];
107
+ const traceparent = generateTraceparent();
108
+ const token = await mintDelegationToken(domain, this._config.tokenTtlSeconds, this._config.delegationSecret,
109
+ // use the configured gateway name as the token issuer (iss claim)
110
+ // so that tokens from different gateway instances are distinguishable in audit logs.
111
+ this._config.gatewayName, payload.carryOverState, this._config.stateStore, traceparent);
112
+ client = new UpstreamMcpClient(upstreamUri, {
113
+ connectTimeoutMs: this._config.connectTimeoutMs,
114
+ idleTimeoutMs: this._config.idleTimeoutMs,
115
+ delegationToken: token,
116
+ traceparent,
117
+ transport: this._config.upstreamTransport,
118
+ }, signal);
119
+ // update session with client reference BEFORE awaiting connect().
120
+ // This lets _closeSession dispose the client even during the in-flight connect,
121
+ // preventing dispose() called on the gateway from leaving zombie connections.
122
+ this._sessions.set(sessionId, { status: 'connecting', client });
123
+ await client.connect();
124
+ this._sessions.set(sessionId, { status: 'active', client, domain, traceparent });
125
+ }
126
+ catch (err) {
127
+ this._sessions.delete(sessionId);
128
+ // Dispose the client if it was created, even if _closeSession already did so
129
+ // concurrently (e.g. parentSignal abort racing with the in-flight connect).
130
+ // UpstreamMcpClient.dispose() is fully idempotent — safe to call multiple times.
131
+ await client?.dispose();
132
+ throw err;
133
+ }
134
+ }
135
+ /**
136
+ * Proxy the tools/list for an active session.
137
+ *
138
+ * Returns `null` for non-existent sessions.
139
+ * Returns only the return-trip escape tool if the upstream is unreachable.
140
+ * Returns an empty list while the session is still connecting.
141
+ */
142
+ async proxyToolsList(sessionId) {
143
+ const session = this._sessions.get(sessionId);
144
+ // distinguish 'no session' from 'still connecting'
145
+ if (!session)
146
+ return null;
147
+ // While connecting, report no tools yet (caller should retry)
148
+ if (session.status !== 'active')
149
+ return [];
150
+ // only wrap the network call in try/catch — not the pure transform
151
+ // functions. `rewriteList` and `injectReturnTripTool` are pure and should never
152
+ // throw. Catching them silently would hide programming errors as degraded UX.
153
+ let raw;
154
+ try {
155
+ raw = await session.client.listTools();
156
+ }
157
+ catch {
158
+ // upstream went away — return only the escape hatch
159
+ return injectReturnTripTool([], this._config.gatewayName);
160
+ }
161
+ const prefixed = this._rewriter.rewriteList(raw, session.domain);
162
+ return injectReturnTripTool(prefixed, this._config.gatewayName);
163
+ }
164
+ /**
165
+ * Proxy a tools/call for an active session.
166
+ *
167
+ * Strips the namespace prefix before forwarding to the upstream.
168
+ *
169
+ * @returns The upstream's ToolResponse, or `null` if no active tunnel.
170
+ */
171
+ async proxyToolsCall(sessionId, name, args, signal) {
172
+ const session = this._sessions.get(sessionId);
173
+ // No session at all — caller serves gateway-level tools
174
+ if (!session)
175
+ return null;
176
+ // when session is still connecting, return a descriptive error
177
+ // instead of null. Returning null causes ServerAttachment to look up the
178
+ // tool in the gateway's own list, which fails with a generic 'tool not found'.
179
+ if (session.status !== 'active') {
180
+ return toolError('HANDOFF_CONNECTING', {
181
+ message: 'The upstream specialist is still connecting. Please retry in a moment.',
182
+ suggestion: 'Wait 1-2 seconds and retry the same tool call.',
183
+ retryAfter: 2,
184
+ severity: 'warning',
185
+ });
186
+ }
187
+ try {
188
+ const strippedName = this._rewriter.stripPrefix(name, session.domain);
189
+ return await session.client.callTool(strippedName, args, signal);
190
+ }
191
+ catch (err) {
192
+ // instanceof instead of duck-typing .name
193
+ if (err instanceof NamespaceError) {
194
+ return toolError('HANDOFF_NAMESPACE_MISMATCH', {
195
+ message: `Tool "${name}" does not match the active upstream domain "${session.domain}".`,
196
+ suggestion: 'Re-fetch the tools/list and retry with a valid tool name.',
197
+ severity: 'error',
198
+ });
199
+ }
200
+ // log non-NamespaceError errors for observability before masking them.
201
+ // The LLM must not see internal error details (security + UX), but errors must
202
+ // surface somewhere so bugs are diagnosable in production.
203
+ console.warn(`[vurb/swarm] proxyToolsCall unexpected error for tool "${name}" in domain "${session.domain}":`, err);
204
+ return toolError('HANDOFF_UPSTREAM_UNAVAILABLE', {
205
+ message: `The upstream specialist "${session.domain}" is temporarily unavailable.`,
206
+ suggestion: 'Inform the user and retry in 30 seconds, or call gateway.return_to_triage.',
207
+ retryAfter: 30,
208
+ severity: 'error',
209
+ });
210
+ }
211
+ }
212
+ /**
213
+ * Close the tunnel for `sessionId` and restore the gateway's tool list.
214
+ *
215
+ * Called when the LLM invokes the `gateway.return_to_triage` tool.
216
+ * `ServerAttachment` emits `notifications/tools/list_changed` after this.
217
+ */
218
+ async returnToGateway(sessionId) {
219
+ await this._closeSession(sessionId);
220
+ }
221
+ /**
222
+ * `true` if there is a tracked tunnel (connecting or active) for the given session.
223
+ *
224
+ * Returning `true` during `'connecting'` ensures `ServerAttachment` routes
225
+ * tools/call through `proxyToolsCall`, which already returns the correct
226
+ * `HANDOFF_CONNECTING` error for in-progress tunnels — preventing gateway-local
227
+ * execution from silently bypassing the handoff.
228
+ */
229
+ hasActiveHandoff(sessionId) {
230
+ return this._sessions.has(sessionId);
231
+ }
232
+ /** `true` if an activation is in progress for the given session. */
233
+ isConnecting(sessionId) {
234
+ return this._sessions.get(sessionId)?.status === 'connecting';
235
+ }
236
+ /**
237
+ * Total number of tracked sessions (connecting + active).
238
+ *
239
+ * exposed for integration testing and observability.
240
+ * Allows tests to assert session lifecycle transitions deterministically
241
+ * without accessing private state via casting.
242
+ */
243
+ get sessionCount() {
244
+ return this._sessions.size;
245
+ }
246
+ /**
247
+ * Number of sessions currently in the `'connecting'` state.
248
+ *
249
+ * useful for load-shedding checks and integration tests.
250
+ */
251
+ get connectingCount() {
252
+ let count = 0;
253
+ for (const s of this._sessions.values()) {
254
+ if (s.status === 'connecting')
255
+ count++;
256
+ }
257
+ return count;
258
+ }
259
+ /**
260
+ * Dispose all active sessions and release all resources.
261
+ *
262
+ * Call this when shutting down the gateway to cleanly close
263
+ * all upstream connections, idle timers, and AbortController listeners.
264
+ */
265
+ async dispose() {
266
+ const sessionIds = [...this._sessions.keys()];
267
+ await Promise.allSettled(sessionIds.map(id => this._closeSession(id)));
268
+ }
269
+ // ── Private ─────────────────────────────────────────────
270
+ async _closeSession(sessionId) {
271
+ // prevent concurrent double-dispose (e.g. abort signal + returnToGateway racing).
272
+ // If already closing, bail out immediately — dispose() is idempotent in UpstreamMcpClient
273
+ // but calling it concurrently can cause duplicate timers and listener leaks.
274
+ if (this._closingSessions.has(sessionId))
275
+ return;
276
+ const session = this._sessions.get(sessionId);
277
+ if (!session)
278
+ return;
279
+ this._closingSessions.add(sessionId);
280
+ this._sessions.delete(sessionId);
281
+ try {
282
+ // also dispose 'connecting' sessions if client was already created.
283
+ // This aborts the in-flight connect() and cleans up the parentSignal listener.
284
+ // Sessions where client hasn't been set yet (first ~few ms of activateHandoff)
285
+ // will simply be deleted from the map — activateHandoff's catch handles cleanup.
286
+ if (session.status === 'active' || (session.status === 'connecting' && session.client)) {
287
+ await session.client.dispose();
288
+ }
289
+ }
290
+ finally {
291
+ this._closingSessions.delete(sessionId);
292
+ }
293
+ }
294
+ /**
295
+ * Extract the domain key from a target URI.
296
+ *
297
+ * `'mcp://finance-agent.internal:8080'` → `'finance'` (registry lookup)
298
+ * `'finance'` → `'finance'` (direct key)
299
+ *
300
+ * @throws If the target does not resolve to any entry in the registry.
301
+ * Configure all targets explicitly in `SwarmGatewayConfig.registry`.
302
+ */
303
+ _resolveDomain(target) {
304
+ // reject empty-string targets before the registry lookup.
305
+ // An empty target is always a caller error; if the registry happened to
306
+ // contain a '' key, we would silently accept it and produce confusing logs.
307
+ if (!target) {
308
+ throw Object.assign(new Error('[vurb/swarm] Handoff target must not be an empty string.'), { code: 'REGISTRY_LOOKUP_FAILED' });
309
+ }
310
+ // use Object.hasOwn instead of truthiness — an empty string value
311
+ // is a config error, not a valid reason to skip the lookup and throw a confusing error
312
+ if (Object.hasOwn(this._config.registry, target))
313
+ return target;
314
+ // Try to extract subdomain from mcp:// or mcps:// URI hostname
315
+ try {
316
+ const url = new URL(target
317
+ .replace(/^mcps:\/\//, 'https://') // handle secure mcp scheme
318
+ .replace(/^mcp:\/\//, 'http://'));
319
+ const subdomain = url.hostname.split('.')[0] ?? '';
320
+ if (subdomain && Object.hasOwn(this._config.registry, subdomain))
321
+ return subdomain;
322
+ }
323
+ catch {
324
+ // ignore URL parse error
325
+ }
326
+ // sanitize the target before including it in the error message.
327
+ // A verbatim target can contain control characters, ANSI escapes, or adversarial
328
+ // payloads that pollute logs or trip structured error parsers.
329
+ const safeTarget = String(target).replace(/[\x00-\x1f\x7f]/g, '').slice(0, 200);
330
+ throw Object.assign(new Error(`[vurb/swarm] Unknown handoff target "${safeTarget}". ` +
331
+ 'All targets must be registered in SwarmGatewayConfig.registry.'), { code: 'REGISTRY_LOOKUP_FAILED' });
332
+ }
333
+ }
334
+ // ============================================================================
335
+ // W3C traceparent
336
+ // ============================================================================
337
+ /**
338
+ * Generate a W3C Trace Context `traceparent` header value.
339
+ * Format: `00-{32 hex}-{16 hex}-01`
340
+ * Uses `crypto.randomUUID()` — no external dependencies.
341
+ */
342
+ function generateTraceparent() {
343
+ const traceId = randomUUID().replace(/-/g, '');
344
+ const spanId = randomUUID().replace(/-/g, '').slice(0, 16);
345
+ return `00-${traceId}-${spanId}-01`;
346
+ }
347
+ //# sourceMappingURL=SwarmGateway.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SwarmGateway.js","sourceRoot":"","sources":["../src/SwarmGateway.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,85 @@
1
+ import type { Tool as McpTool } from '@modelcontextprotocol/sdk/types.js';
2
+ import type { ToolResponse } from '@vurb/core';
3
+ /** Progress notification forwarded from the upstream to the gateway client. */
4
+ export interface ProgressNotification {
5
+ method: string;
6
+ params: Record<string, unknown>;
7
+ }
8
+ /** Callback used by SwarmGateway to relay progress upstream → LLM client. */
9
+ export type ProgressForwarder = (notification: ProgressNotification) => void;
10
+ export interface UpstreamMcpClientConfig {
11
+ /** Milliseconds to wait for the initial connection (default: 5 000). */
12
+ connectTimeoutMs: number;
13
+ /** Milliseconds of inactivity before the tunnel is closed (default: 300 000). */
14
+ idleTimeoutMs: number;
15
+ /** Delegation token to send via `x-vurb-delegation` header. */
16
+ delegationToken: string;
17
+ /** W3C traceparent to propagate (optional). */
18
+ traceparent?: string;
19
+ /**
20
+ * Transport override.
21
+ * - `'auto'` (default): SSE on Node.js, HTTP on edge runtimes
22
+ * - `'sse'`: always SSE (persistent)
23
+ * - `'http'`: always Streamable HTTP (stateless, edge-compatible)
24
+ */
25
+ transport?: 'auto' | 'sse' | 'http';
26
+ }
27
+ /**
28
+ * Outbound MCP client for the SwarmGateway B2BUA face.
29
+ *
30
+ * One instance per active handoff session. Closed via `dispose()` or
31
+ * automatically when the idle timeout or AbortSignal fires.
32
+ */
33
+ export declare class UpstreamMcpClient {
34
+ private readonly _targetUri;
35
+ private readonly _config;
36
+ private readonly _abortController;
37
+ private readonly _onParentAbort;
38
+ private readonly _parentSignal;
39
+ private _client;
40
+ private _idleTimer;
41
+ private _progressForwarder;
42
+ private _activeCalls;
43
+ private _disposed;
44
+ constructor(_targetUri: string, _config: UpstreamMcpClientConfig, parentSignal: AbortSignal);
45
+ /** Register a forwarder for upstream progress/logging notifications. */
46
+ setProgressForwarder(forwarder: ProgressForwarder): void;
47
+ /**
48
+ * Connect to the upstream server.
49
+ *
50
+ * @throws `Error` with `code: 'UPSTREAM_CONNECT_TIMEOUT'` if unreachable within `connectTimeoutMs`
51
+ */
52
+ connect(): Promise<void>;
53
+ /** List all tools exposed by the upstream server. */
54
+ listTools(): Promise<McpTool[]>;
55
+ /**
56
+ * Call a tool on the upstream server.
57
+ * @param name - Tool name (without namespace prefix)
58
+ * @param args - Tool arguments
59
+ * @param signal - AbortSignal from the parent tools/call request
60
+ */
61
+ callTool(name: string, args: Record<string, unknown>, signal: AbortSignal): Promise<ToolResponse>;
62
+ /** Close the connection and clear all timers. */
63
+ dispose(): Promise<void>;
64
+ private _assertConnected;
65
+ /**
66
+ * Build the MCP transport for the given headers and connect-phase abort signal.
67
+ *
68
+ * The `connectSignal` is only used for the initial TCP/SSE handshake.
69
+ * It is NOT the same as the session lifetime signal (`_abortController`).
70
+ *
71
+ * `mcps://` (secure MCP) is mapped to `https://`.
72
+ */
73
+ private _resolveTransport;
74
+ /**
75
+ * Register notification handlers with correctly typed wrappers.
76
+ *
77
+ * The MCP SDK's `setNotificationHandler` expects a Zod schema as the first argument.
78
+ * We create a minimal compliant object that satisfies the runtime duck-type without
79
+ * importing Zod directly (the SDK validates at transport level, not via our schema).
80
+ * The `as never` cast is retained on the schema only — all handler types are explicit.
81
+ */
82
+ private _wireNotifications;
83
+ private _resetIdleTimer;
84
+ }
85
+ //# sourceMappingURL=UpstreamMcpClient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UpstreamMcpClient.d.ts","sourceRoot":"","sources":["../src/UpstreamMcpClient.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,IAAI,IAAI,OAAO,EAAE,MAAM,oCAAoC,CAAC;AAE1E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,+EAA+E;AAC/E,MAAM,WAAW,oBAAoB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED,6EAA6E;AAC7E,MAAM,MAAM,iBAAiB,GAAG,CAAC,YAAY,EAAE,oBAAoB,KAAK,IAAI,CAAC;AAM7E,MAAM,WAAW,uBAAuB;IACpC,wEAAwE;IACxE,gBAAgB,EAAE,MAAM,CAAC;IACzB,iFAAiF;IACjF,aAAa,EAAE,MAAM,CAAC;IACtB,+DAA+D;IAC/D,eAAe,EAAE,MAAM,CAAC;IACxB,+CAA+C;IAC/C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,CAAC;CACvC;AAED;;;;;GAKG;AACH,qBAAa,iBAAiB;IAetB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,OAAO;IAf5B,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAkB;IAGnD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAa;IAC5C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAc;IAC5C,OAAO,CAAC,OAAO,CAAqB;IACpC,OAAO,CAAC,UAAU,CAA4C;IAC9D,OAAO,CAAC,kBAAkB,CAAgC;IAE1D,OAAO,CAAC,YAAY,CAAK;IAEzB,OAAO,CAAC,SAAS,CAAS;gBAGL,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,uBAAuB,EACjD,YAAY,EAAE,WAAW;IAY7B,wEAAwE;IACxE,oBAAoB,CAAC,SAAS,EAAE,iBAAiB,GAAG,IAAI;IAIxD;;;;OAIG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IA2D9B,qDAAqD;IAC/C,SAAS,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;IAuBrC;;;;;OAKG;IACG,QAAQ,CACV,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,MAAM,EAAE,WAAW,GACpB,OAAO,CAAC,YAAY,CAAC;IAwDxB,iDAAiD;IAC3C,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAoB9B,OAAO,CAAC,gBAAgB;IAMxB;;;;;;;OAOG;IACH,OAAO,CAAC,iBAAiB;IAmBzB;;;;;;;OAOG;IACH,OAAO,CAAC,kBAAkB;IAkC1B,OAAO,CAAC,eAAe;CAS1B"}