@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,266 @@
1
+ /**
2
+ * Federated Handoff Protocol — Upstream MCP Client
3
+ *
4
+ * Outbound MCP client that tunnels from the SwarmGateway to an upstream
5
+ * micro-server. Implements:
6
+ *
7
+ * - **AbortSignal cascade**: closing the parent connection aborts the tunnel
8
+ * - **Connect timeout**: the timeout AbortController cancels the in-flight HTTP request
9
+ * - **Idle timeout**: closes zombie tunnels after configurable inactivity
10
+ * - **Progress passthrough**: pipes `notifications/progress` and
11
+ * `notifications/message` from upstream back to the LLM client
12
+ *
13
+ * @module
14
+ */
15
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
16
+ import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
17
+ import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
18
+ /**
19
+ * Outbound MCP client for the SwarmGateway B2BUA face.
20
+ *
21
+ * One instance per active handoff session. Closed via `dispose()` or
22
+ * automatically when the idle timeout or AbortSignal fires.
23
+ */
24
+ export class UpstreamMcpClient {
25
+ _targetUri;
26
+ _config;
27
+ _abortController;
28
+ // keep ref to listener so we can remove it in dispose() to prevent
29
+ // the closure from holding a reference to this instance after the session ends.
30
+ _onParentAbort;
31
+ _parentSignal;
32
+ _client;
33
+ _idleTimer;
34
+ _progressForwarder;
35
+ // tracks concurrent tool calls to suspend the idle timer during execution
36
+ _activeCalls = 0;
37
+ // set to true by dispose() to prevent orphan timers from firing
38
+ _disposed = false;
39
+ constructor(_targetUri, _config, parentSignal) {
40
+ this._targetUri = _targetUri;
41
+ this._config = _config;
42
+ this._abortController = new AbortController();
43
+ // save ref to both signal and listener for cleanup in dispose().
44
+ // Using `{ once: true }` auto-removes on fire, but NOT on voluntary dispose().
45
+ // Without explicit removal, the closure keeps this entire instance alive
46
+ // in memory for as long as parentSignal lives (typically the MCP connection lifetime).
47
+ this._onParentAbort = () => this._abortController.abort();
48
+ this._parentSignal = parentSignal;
49
+ parentSignal.addEventListener('abort', this._onParentAbort, { once: true });
50
+ }
51
+ /** Register a forwarder for upstream progress/logging notifications. */
52
+ setProgressForwarder(forwarder) {
53
+ this._progressForwarder = forwarder;
54
+ }
55
+ /**
56
+ * Connect to the upstream server.
57
+ *
58
+ * @throws `Error` with `code: 'UPSTREAM_CONNECT_TIMEOUT'` if unreachable within `connectTimeoutMs`
59
+ */
60
+ async connect() {
61
+ const headers = {
62
+ 'x-vurb-delegation': this._config.delegationToken,
63
+ };
64
+ if (this._config.traceparent) {
65
+ headers['traceparent'] = this._config.traceparent;
66
+ }
67
+ // use a dedicated connect-phase controller merged with the session-lifetime
68
+ // signal. The SSEClientTransport holds the signal for the full SSE stream lifetime,
69
+ // so it must be the session signal — not the one-shot connect timeout signal.
70
+ // We create a merged signal that fires on either: (a) connect timeout, or (b) session abort.
71
+ const connectController = new AbortController();
72
+ // AbortSignal.any is available in Node ≥18.17.
73
+ // the old fallback used only connectController.signal, ignoring
74
+ // _abortController.signal entirely on older runtimes. This meant a parent-abort
75
+ // during connect() would not cancel the in-flight TCP/HTTP request immediately.
76
+ // The manual merge below ensures both signals are observed on all runtimes.
77
+ const connectSignal = typeof AbortSignal.any === 'function'
78
+ ? AbortSignal.any([connectController.signal, this._abortController.signal])
79
+ : (() => {
80
+ const mergedController = new AbortController();
81
+ const abort = () => mergedController.abort();
82
+ connectController.signal.addEventListener('abort', abort, { once: true });
83
+ this._abortController.signal.addEventListener('abort', abort, { once: true });
84
+ return mergedController.signal;
85
+ })();
86
+ const transport = this._resolveTransport(headers, connectSignal);
87
+ // do NOT set this._client before the promise settles.
88
+ // We use a local variable during connect; only assign to this._client on success.
89
+ const client = new Client({ name: 'vurb-swarm-gateway', version: '0.1.0' });
90
+ await new Promise((resolve, reject) => {
91
+ const timer = setTimeout(() => {
92
+ connectController.abort(); // cancel the in-flight request
93
+ reject(Object.assign(new Error(`[vurb/swarm] Upstream "${this._targetUri}" did not respond within ${this._config.connectTimeoutMs}ms.`), { code: 'UPSTREAM_CONNECT_TIMEOUT' }));
94
+ }, this._config.connectTimeoutMs);
95
+ void client.connect(transport).then(() => {
96
+ clearTimeout(timer);
97
+ resolve();
98
+ }, (err) => {
99
+ clearTimeout(timer);
100
+ reject(err);
101
+ });
102
+ });
103
+ // only set this._client AFTER a successful connect.
104
+ // If the promise above rejected (timeout, network error), this line is never reached
105
+ // and _client remains undefined — _assertConnected() correctly blocks further calls.
106
+ this._client = client;
107
+ this._wireNotifications();
108
+ this._resetIdleTimer();
109
+ }
110
+ /** List all tools exposed by the upstream server. */
111
+ async listTools() {
112
+ this._assertConnected();
113
+ // apply the same idle-timer suspension pattern as callTool.
114
+ // Without this, a slow upstream (large tool list) could cause the idle timer
115
+ // to fire mid-awaiting the listTools RPC and close the tunnel prematurely.
116
+ this._activeCalls++;
117
+ clearTimeout(this._idleTimer);
118
+ this._idleTimer = undefined;
119
+ try {
120
+ // declare inside the try block (same pattern as callTool)
121
+ // so TypeScript never infers `response` as `T | undefined` outside it.
122
+ const response = await this._client.listTools();
123
+ return response.tools;
124
+ }
125
+ finally {
126
+ this._activeCalls--;
127
+ if (this._activeCalls === 0) {
128
+ this._resetIdleTimer();
129
+ }
130
+ }
131
+ }
132
+ /**
133
+ * Call a tool on the upstream server.
134
+ * @param name - Tool name (without namespace prefix)
135
+ * @param args - Tool arguments
136
+ * @param signal - AbortSignal from the parent tools/call request
137
+ */
138
+ async callTool(name, args, signal) {
139
+ this._assertConnected();
140
+ // suspend the idle timer while a tool call is in progress.
141
+ // Previously, the timer was reset only at the START of the call. A long-running
142
+ // tool (e.g., 4+ minutes) would not reset the timer on completion, causing
143
+ // the tunnel to close while the tool is still executing. Now we pause the
144
+ // timer during the call and restart it only after the call completes.
145
+ this._activeCalls++;
146
+ clearTimeout(this._idleTimer);
147
+ this._idleTimer = undefined;
148
+ // declare result inside the try block (same pattern as listTools after ).
149
+ // With `let result` outside, TypeScript infers `result: T | undefined` — if callTool()
150
+ // throws, `result.content` below would never execute but TS doesn't know that.
151
+ // Moving everything inside the try makes the types strict and intent crystal-clear.
152
+ try {
153
+ const result = await this._client.callTool({ name, arguments: args }, undefined, { signal });
154
+ // ToolResponse only supports type:'text'. Produce human-readable representations
155
+ // for non-text blocks (image, resource, audio) rather than losing type information
156
+ // via a raw JSON.stringify. LLMs benefit from knowing what was returned.
157
+ const content = result.content.map(c => {
158
+ if (c.type === 'text') {
159
+ return { type: 'text', text: c.text ?? '' };
160
+ }
161
+ if (c.type === 'image') {
162
+ const size = c.data?.length ?? 0;
163
+ return { type: 'text', text: `[Image (${c.mimeType ?? 'unknown type'}); ${size} base64 chars — not renderable as text]` };
164
+ }
165
+ if (c.type === 'resource') {
166
+ return { type: 'text', text: `[Resource: ${JSON.stringify(c.resource)}]` };
167
+ }
168
+ return { type: 'text', text: `[${c.type}: ${JSON.stringify(c)}]` };
169
+ });
170
+ return { content, isError: result.isError === true };
171
+ }
172
+ finally {
173
+ this._activeCalls--;
174
+ // Restart the idle timer only when no other calls are pending
175
+ if (this._activeCalls === 0) {
176
+ this._resetIdleTimer();
177
+ }
178
+ }
179
+ }
180
+ /** Close the connection and clear all timers. */
181
+ async dispose() {
182
+ // mark as disposed FIRST so any in-flight finally blocks
183
+ // (e.g. callTool's _activeCalls--) won't restart the idle timer.
184
+ this._disposed = true;
185
+ // remove the parent signal listener to break the closure reference.
186
+ // Without this, parentSignal retains a reference to this instance for its entire
187
+ // lifetime (usually the MCP connection), preventing GC after the session ends.
188
+ this._parentSignal.removeEventListener('abort', this._onParentAbort);
189
+ clearTimeout(this._idleTimer);
190
+ this._abortController.abort();
191
+ try {
192
+ await this._client?.close();
193
+ }
194
+ catch {
195
+ // Best-effort close — swallow errors during cleanup
196
+ }
197
+ this._client = undefined;
198
+ }
199
+ // ── Private ─────────────────────────────────────────────
200
+ _assertConnected() {
201
+ if (!this._client) {
202
+ throw new Error('[vurb/swarm] UpstreamMcpClient is not connected. Call connect() first.');
203
+ }
204
+ }
205
+ /**
206
+ * Build the MCP transport for the given headers and connect-phase abort signal.
207
+ *
208
+ * The `connectSignal` is only used for the initial TCP/SSE handshake.
209
+ * It is NOT the same as the session lifetime signal (`_abortController`).
210
+ *
211
+ * `mcps://` (secure MCP) is mapped to `https://`.
212
+ */
213
+ _resolveTransport(headers, sessionSignal) {
214
+ const mode = this._config.transport ?? 'auto';
215
+ const useHttp = mode === 'http' ||
216
+ (mode === 'auto' && typeof globalThis['EdgeRuntime'] !== 'undefined');
217
+ const url = new URL(this._targetUri
218
+ .replace(/^mcps:\/\//, 'https://') // secure MCP scheme
219
+ .replace(/^mcp:\/\//, 'http://'));
220
+ if (useHttp) {
221
+ return new StreamableHTTPClientTransport(url, {
222
+ requestInit: { headers, signal: sessionSignal },
223
+ });
224
+ }
225
+ return new SSEClientTransport(url, {
226
+ requestInit: { headers, signal: sessionSignal },
227
+ });
228
+ }
229
+ /**
230
+ * Register notification handlers with correctly typed wrappers.
231
+ *
232
+ * The MCP SDK's `setNotificationHandler` expects a Zod schema as the first argument.
233
+ * We create a minimal compliant object that satisfies the runtime duck-type without
234
+ * importing Zod directly (the SDK validates at transport level, not via our schema).
235
+ * The `as never` cast is retained on the schema only — all handler types are explicit.
236
+ */
237
+ _wireNotifications() {
238
+ if (!this._client)
239
+ return;
240
+ // include safeParse() so future MCP SDK versions that call it
241
+ // don't silently break notification forwarding. The cast to `never` is retained
242
+ // only on the schema argument — handler types remain explicit.
243
+ const makeSchema = (method) => ({
244
+ parse: (v) => v,
245
+ safeParse: (v) => ({ success: true, data: v }),
246
+ shape: { method: { value: method } },
247
+ });
248
+ this._client.setNotificationHandler(makeSchema('notifications/progress'), (n) => {
249
+ this._progressForwarder?.({ method: 'notifications/progress', params: n.params ?? {} });
250
+ });
251
+ this._client.setNotificationHandler(makeSchema('notifications/message'), (n) => {
252
+ this._progressForwarder?.({ method: 'notifications/message', params: n.params ?? {} });
253
+ });
254
+ }
255
+ _resetIdleTimer() {
256
+ // never restart the timer after dispose() — that would schedule
257
+ // another dispose() call on a dead client, leaking the timer handle.
258
+ if (this._disposed)
259
+ return;
260
+ clearTimeout(this._idleTimer);
261
+ this._idleTimer = setTimeout(() => {
262
+ void this.dispose();
263
+ }, this._config.idleTimeoutMs);
264
+ }
265
+ }
266
+ //# sourceMappingURL=UpstreamMcpClient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UpstreamMcpClient.js","sourceRoot":"","sources":["../src/UpstreamMcpClient.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAC;AAC7E,OAAO,EAAE,6BAA6B,EAAE,MAAM,oDAAoD,CAAC;AAoCnG;;;;;GAKG;AACH,MAAM,OAAO,iBAAiB;IAeL;IACA;IAfJ,gBAAgB,CAAkB;IACnD,mEAAmE;IACnE,gFAAgF;IAC/D,cAAc,CAAa;IAC3B,aAAa,CAAc;IACpC,OAAO,CAAqB;IAC5B,UAAU,CAA4C;IACtD,kBAAkB,CAAgC;IAC1D,0EAA0E;IAClE,YAAY,GAAG,CAAC,CAAC;IACzB,gEAAgE;IACxD,SAAS,GAAG,KAAK,CAAC;IAE1B,YACqB,UAAkB,EAClB,OAAgC,EACjD,YAAyB;QAFR,eAAU,GAAV,UAAU,CAAQ;QAClB,YAAO,GAAP,OAAO,CAAyB;QAGjD,IAAI,CAAC,gBAAgB,GAAG,IAAI,eAAe,EAAE,CAAC;QAC9C,iEAAiE;QACjE,+EAA+E;QAC/E,yEAAyE;QACzE,uFAAuF;QACvF,IAAI,CAAC,cAAc,GAAG,GAAG,EAAE,CAAC,IAAI,CAAC,gBAAgB,CAAC,KAAK,EAAE,CAAC;QAC1D,IAAI,CAAC,aAAa,GAAG,YAAY,CAAC;QAClC,YAAY,CAAC,gBAAgB,CAAC,OAAO,EAAE,IAAI,CAAC,cAAc,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IAChF,CAAC;IAED,wEAAwE;IACxE,oBAAoB,CAAC,SAA4B;QAC7C,IAAI,CAAC,kBAAkB,GAAG,SAAS,CAAC;IACxC,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,OAAO;QACT,MAAM,OAAO,GAA2B;YACpC,mBAAmB,EAAE,IAAI,CAAC,OAAO,CAAC,eAAe;SACpD,CAAC;QACF,IAAI,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC;YAC3B,OAAO,CAAC,aAAa,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC;QACtD,CAAC;QAED,4EAA4E;QAC5E,oFAAoF;QACpF,8EAA8E;QAC9E,6FAA6F;QAC7F,MAAM,iBAAiB,GAAG,IAAI,eAAe,EAAE,CAAC;QAChD,+CAA+C;QAC/C,gEAAgE;QAChE,gFAAgF;QAChF,gFAAgF;QAChF,4EAA4E;QAC5E,MAAM,aAAa,GAAG,OAAO,WAAW,CAAC,GAAG,KAAK,UAAU;YACvD,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,iBAAiB,CAAC,MAAM,EAAE,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC;YAC3E,CAAC,CAAC,CAAC,GAAG,EAAE;gBACJ,MAAM,gBAAgB,GAAG,IAAI,eAAe,EAAE,CAAC;gBAC/C,MAAM,KAAK,GAAG,GAAG,EAAE,CAAC,gBAAgB,CAAC,KAAK,EAAE,CAAC;gBAC7C,iBAAiB,CAAC,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;gBAC1E,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;gBAC9E,OAAO,gBAAgB,CAAC,MAAM,CAAC;YACnC,CAAC,CAAC,EAAE,CAAC;QACT,MAAM,SAAS,GAAG,IAAI,CAAC,iBAAiB,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;QAEjE,sDAAsD;QACtD,kFAAkF;QAClF,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,EAAE,IAAI,EAAE,oBAAoB,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;QAE5E,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACxC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;gBAC1B,iBAAiB,CAAC,KAAK,EAAE,CAAC,CAAC,+BAA+B;gBAC1D,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,KAAK,CAC1B,0BAA0B,IAAI,CAAC,UAAU,4BAA4B,IAAI,CAAC,OAAO,CAAC,gBAAgB,KAAK,CAC1G,EAAE,EAAE,IAAI,EAAE,0BAA0B,EAAE,CAAC,CAAC,CAAC;YAC9C,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC;YAElC,KAAK,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE;gBACrC,YAAY,CAAC,KAAK,CAAC,CAAC;gBACpB,OAAO,EAAE,CAAC;YACd,CAAC,EAAE,CAAC,GAAY,EAAE,EAAE;gBAChB,YAAY,CAAC,KAAK,CAAC,CAAC;gBACpB,MAAM,CAAC,GAAG,CAAC,CAAC;YAChB,CAAC,CAAC,CAAC;QACP,CAAC,CAAC,CAAC;QAEH,oDAAoD;QACpD,qFAAqF;QACrF,qFAAqF;QACrF,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC;QAEtB,IAAI,CAAC,kBAAkB,EAAE,CAAC;QAC1B,IAAI,CAAC,eAAe,EAAE,CAAC;IAC3B,CAAC;IAED,qDAAqD;IACrD,KAAK,CAAC,SAAS;QACX,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAExB,4DAA4D;QAC5D,6EAA6E;QAC7E,2EAA2E;QAC3E,IAAI,CAAC,YAAY,EAAE,CAAC;QACpB,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC9B,IAAI,CAAC,UAAU,GAAG,SAAS,CAAC;QAE5B,IAAI,CAAC;YACD,0DAA0D;YAC1D,uEAAuE;YACvE,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAQ,CAAC,SAAS,EAAE,CAAC;YACjD,OAAO,QAAQ,CAAC,KAAK,CAAC;QAC1B,CAAC;gBAAS,CAAC;YACP,IAAI,CAAC,YAAY,EAAE,CAAC;YACpB,IAAI,IAAI,CAAC,YAAY,KAAK,CAAC,EAAE,CAAC;gBAC1B,IAAI,CAAC,eAAe,EAAE,CAAC;YAC3B,CAAC;QACL,CAAC;IACL,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,QAAQ,CACV,IAAY,EACZ,IAA6B,EAC7B,MAAmB;QAEnB,IAAI,CAAC,gBAAgB,EAAE,CAAC;QAExB,2DAA2D;QAC3D,gFAAgF;QAChF,2EAA2E;QAC3E,0EAA0E;QAC1E,sEAAsE;QACtE,IAAI,CAAC,YAAY,EAAE,CAAC;QACpB,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC9B,IAAI,CAAC,UAAU,GAAG,SAAS,CAAC;QAC5B,0EAA0E;QAC1E,uFAAuF;QACvF,+EAA+E;QAC/E,oFAAoF;QACpF,IAAI,CAAC;YACD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAQ,CAAC,QAAQ,CACvC,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,EACzB,SAAS,EACT,EAAE,MAAM,EAAE,CACb,CAAC;YAEF,iFAAiF;YACjF,mFAAmF;YACnF,yEAAyE;YACzE,MAAM,OAAO,GAAI,MAAM,CAAC,OAOrB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE;gBACR,IAAI,CAAC,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;oBACpB,OAAO,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC;gBACzD,CAAC;gBACD,IAAI,CAAC,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;oBACrB,MAAM,IAAI,GAAG,CAAC,CAAC,IAAI,EAAE,MAAM,IAAI,CAAC,CAAC;oBACjC,OAAO,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC,QAAQ,IAAI,cAAc,MAAM,IAAI,yCAAyC,EAAE,CAAC;gBACvI,CAAC;gBACD,IAAI,CAAC,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;oBACxB,OAAO,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,cAAc,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC;gBACxF,CAAC;gBACD,OAAO,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;YAChF,CAAC,CAAC,CAAC;YAEH,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;QACzD,CAAC;gBAAS,CAAC;YACP,IAAI,CAAC,YAAY,EAAE,CAAC;YACpB,8DAA8D;YAC9D,IAAI,IAAI,CAAC,YAAY,KAAK,CAAC,EAAE,CAAC;gBAC1B,IAAI,CAAC,eAAe,EAAE,CAAC;YAC3B,CAAC;QACL,CAAC;IACL,CAAC;IAED,iDAAiD;IACjD,KAAK,CAAC,OAAO;QACT,yDAAyD;QACzD,iEAAiE;QACjE,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;QACtB,oEAAoE;QACpE,iFAAiF;QACjF,+EAA+E;QAC/E,IAAI,CAAC,aAAa,CAAC,mBAAmB,CAAC,OAAO,EAAE,IAAI,CAAC,cAAc,CAAC,CAAC;QACrE,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC9B,IAAI,CAAC,gBAAgB,CAAC,KAAK,EAAE,CAAC;QAC9B,IAAI,CAAC;YACD,MAAM,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC;QAChC,CAAC;QAAC,MAAM,CAAC;YACL,oDAAoD;QACxD,CAAC;QACD,IAAI,CAAC,OAAO,GAAG,SAAS,CAAC;IAC7B,CAAC;IAED,2DAA2D;IAEnD,gBAAgB;QACpB,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YAChB,MAAM,IAAI,KAAK,CAAC,wEAAwE,CAAC,CAAC;QAC9F,CAAC;IACL,CAAC;IAED;;;;;;;OAOG;IACK,iBAAiB,CAAC,OAA+B,EAAE,aAA0B;QACjF,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,IAAI,MAAM,CAAC;QAC9C,MAAM,OAAO,GAAG,IAAI,KAAK,MAAM;YAC3B,CAAC,IAAI,KAAK,MAAM,IAAI,OAAQ,UAAsC,CAAC,aAAa,CAAC,KAAK,WAAW,CAAC,CAAC;QAEvG,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,UAAU;aAC9B,OAAO,CAAC,YAAY,EAAE,UAAU,CAAC,CAAE,oBAAoB;aACvD,OAAO,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC,CAAC;QAEtC,IAAI,OAAO,EAAE,CAAC;YACV,OAAO,IAAI,6BAA6B,CAAC,GAAG,EAAE;gBAC1C,WAAW,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE;aAClD,CAAyB,CAAC;QAC/B,CAAC;QACD,OAAO,IAAI,kBAAkB,CAAC,GAAG,EAAE;YAC/B,WAAW,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE;SACK,CAAyB,CAAC;IACtF,CAAC;IAED;;;;;;;OAOG;IACK,kBAAkB;QACtB,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO;QAO1B,8DAA8D;QAC9D,gFAAgF;QAChF,+DAA+D;QAC/D,MAAM,UAAU,GAAG,CAAC,MAAc,EAAE,EAAE,CAAC,CAAC;YACpC,KAAK,EAAE,CAAC,CAAU,EAA0B,EAAE,CAC1C,CAA2B;YAC/B,SAAS,EAAE,CAAC,CAAU,EAAmD,EAAE,CACvE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAA2B,EAAE,CAAC;YAC1D,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE;SACvC,CAAC,CAAC;QAEH,IAAI,CAAC,OAAO,CAAC,sBAAsB,CAC/B,UAAU,CAAC,wBAAwB,CAAU,EAC7C,CAAC,CAAyB,EAAE,EAAE;YAC1B,IAAI,CAAC,kBAAkB,EAAE,CAAC,EAAE,MAAM,EAAE,wBAAwB,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,IAAI,EAAE,EAAE,CAAC,CAAC;QAC5F,CAAC,CACJ,CAAC;QAEF,IAAI,CAAC,OAAO,CAAC,sBAAsB,CAC/B,UAAU,CAAC,uBAAuB,CAAU,EAC5C,CAAC,CAAyB,EAAE,EAAE;YAC1B,IAAI,CAAC,kBAAkB,EAAE,CAAC,EAAE,MAAM,EAAE,uBAAuB,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,IAAI,EAAE,EAAE,CAAC,CAAC;QAC3F,CAAC,CACJ,CAAC;IACN,CAAC;IAEO,eAAe;QACnB,gEAAgE;QAChE,qEAAqE;QACrE,IAAI,IAAI,CAAC,SAAS;YAAE,OAAO;QAC3B,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC9B,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC,GAAG,EAAE;YAC9B,KAAK,IAAI,CAAC,OAAO,EAAE,CAAC;QACxB,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;IACnC,CAAC;CACJ"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * @vurb/swarm — Federated Handoff Protocol
3
+ *
4
+ * Multi-agent orchestration for Vurb MCP servers.
5
+ * Implements the SwarmGateway B2BUA pattern for secure, efficient
6
+ * agent handoffs with zero-trust delegation and distributed tracing.
7
+ *
8
+ * @example Gateway setup
9
+ * ```typescript
10
+ * import { SwarmGateway } from '@vurb/swarm';
11
+ * import { f } from '@vurb/core';
12
+ *
13
+ * const gateway = new SwarmGateway({
14
+ * registry: { finance: 'http://finance-agent:8081' },
15
+ * delegationSecret: process.env.VURB_DELEGATION_SECRET!,
16
+ * });
17
+ *
18
+ * export const triage = f.tool('system.triage')
19
+ * .withEnum('domain', ['finance', 'devops'])
20
+ * .withString('context', 'What the user needs.')
21
+ * .handle(async (input) => f.handoff(`mcp://${input.domain}-agent`, {
22
+ * carryOverState: { intent: input.context },
23
+ * reason: `Triage → ${input.domain}`,
24
+ * }));
25
+ * ```
26
+ *
27
+ * @example Upstream micro-server
28
+ * ```typescript
29
+ * import { requireGatewayClearance } from '@vurb/core';
30
+ *
31
+ * export const refund = f.tool('finance.refund')
32
+ * .use(requireGatewayClearance(process.env.VURB_DELEGATION_SECRET!))
33
+ * .withString('invoiceId', 'Invoice ID')
34
+ * .handle(async (input, ctx) => success(await stripe.refund(input.invoiceId)));
35
+ * ```
36
+ *
37
+ * @module
38
+ */
39
+ export { SwarmGateway } from './SwarmGateway.js';
40
+ export type { SwarmGatewayConfig } from './SwarmGateway.js';
41
+ export { UpstreamMcpClient } from './UpstreamMcpClient.js';
42
+ export type { UpstreamMcpClientConfig, ProgressForwarder, ProgressNotification } from './UpstreamMcpClient.js';
43
+ export { NamespaceRewriter, NamespaceError } from './NamespaceRewriter.js';
44
+ export { injectReturnTripTool, formatSafeReturn } from './ReturnTripInjector.js';
45
+ export { handoff, isHandoffResponse, mintDelegationToken, verifyDelegationToken, HandoffAuthError, InMemoryHandoffStateStore, requireGatewayClearance, } from '@vurb/core';
46
+ export type { HandoffPayload, HandoffResponse, HandoffStateStore, DelegationClaims, GatewayClearanceContext, } from '@vurb/core';
47
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,YAAY,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAG5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,YAAY,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAG/G,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC3E,OAAO,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAMjF,OAAO,EACH,OAAO,EAAE,iBAAiB,EAC1B,mBAAmB,EAAE,qBAAqB,EAAE,gBAAgB,EAC5D,yBAAyB,EACzB,uBAAuB,GAC1B,MAAM,YAAY,CAAC;AACpB,YAAY,EACR,cAAc,EAAE,eAAe,EAAE,iBAAiB,EAClD,gBAAgB,EAChB,uBAAuB,GAC1B,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,51 @@
1
+ /**
2
+ * @vurb/swarm — Federated Handoff Protocol
3
+ *
4
+ * Multi-agent orchestration for Vurb MCP servers.
5
+ * Implements the SwarmGateway B2BUA pattern for secure, efficient
6
+ * agent handoffs with zero-trust delegation and distributed tracing.
7
+ *
8
+ * @example Gateway setup
9
+ * ```typescript
10
+ * import { SwarmGateway } from '@vurb/swarm';
11
+ * import { f } from '@vurb/core';
12
+ *
13
+ * const gateway = new SwarmGateway({
14
+ * registry: { finance: 'http://finance-agent:8081' },
15
+ * delegationSecret: process.env.VURB_DELEGATION_SECRET!,
16
+ * });
17
+ *
18
+ * export const triage = f.tool('system.triage')
19
+ * .withEnum('domain', ['finance', 'devops'])
20
+ * .withString('context', 'What the user needs.')
21
+ * .handle(async (input) => f.handoff(`mcp://${input.domain}-agent`, {
22
+ * carryOverState: { intent: input.context },
23
+ * reason: `Triage → ${input.domain}`,
24
+ * }));
25
+ * ```
26
+ *
27
+ * @example Upstream micro-server
28
+ * ```typescript
29
+ * import { requireGatewayClearance } from '@vurb/core';
30
+ *
31
+ * export const refund = f.tool('finance.refund')
32
+ * .use(requireGatewayClearance(process.env.VURB_DELEGATION_SECRET!))
33
+ * .withString('invoiceId', 'Invoice ID')
34
+ * .handle(async (input, ctx) => success(await stripe.refund(input.invoiceId)));
35
+ * ```
36
+ *
37
+ * @module
38
+ */
39
+ // ── Gateway ──────────────────────────────────────────────
40
+ export { SwarmGateway } from './SwarmGateway.js';
41
+ // ── Client (upstream connection) ─────────────────────────
42
+ export { UpstreamMcpClient } from './UpstreamMcpClient.js';
43
+ // ── Utilities ────────────────────────────────────────────
44
+ export { NamespaceRewriter, NamespaceError } from './NamespaceRewriter.js';
45
+ export { injectReturnTripTool, formatSafeReturn } from './ReturnTripInjector.js';
46
+ // ── FHP primitives (re-exported from @vurb/core) ────────
47
+ // Provided here for convenience — teams importing from @vurb/swarm
48
+ // get the full FHP API surface without a separate @vurb/core import.
49
+ // The canonical source is always @vurb/core.
50
+ export { handoff, isHandoffResponse, mintDelegationToken, verifyDelegationToken, HandoffAuthError, InMemoryHandoffStateStore, requireGatewayClearance, } from '@vurb/core';
51
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH,4DAA4D;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAGjD,4DAA4D;AAC5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAG3D,4DAA4D;AAC5D,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC3E,OAAO,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEjF,2DAA2D;AAC3D,mEAAmE;AACnE,qEAAqE;AACrE,6CAA6C;AAC7C,OAAO,EACH,OAAO,EAAE,iBAAiB,EAC1B,mBAAmB,EAAE,qBAAqB,EAAE,gBAAgB,EAC5D,yBAAyB,EACzB,uBAAuB,GAC1B,MAAM,YAAY,CAAC"}
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "@vurb/swarm",
3
+ "version": "3.8.2",
4
+ "description": "Federated Handoff Protocol for Vurb — SwarmGateway B2BUA, multi-agent orchestration, zero-trust delegation, and upstream MCP client tunneling.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ }
13
+ },
14
+ "scripts": {
15
+ "build": "rimraf dist && tsc",
16
+ "test": "vitest run",
17
+ "prepublishOnly": "npm run build"
18
+ },
19
+ "keywords": [
20
+ "mcp",
21
+ "vurb",
22
+ "swarm",
23
+ "multi-agent",
24
+ "handoff",
25
+ "gateway",
26
+ "b2bua",
27
+ "federation",
28
+ "ai",
29
+ "llm",
30
+ "delegation",
31
+ "zero-trust"
32
+ ],
33
+ "author": "Renato Marinho",
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "git+https://github.com/vinkius-labs/vurb.ts.git",
37
+ "directory": "packages/swarm"
38
+ },
39
+ "bugs": {
40
+ "url": "https://github.com/vinkius-labs/vurb.ts/issues"
41
+ },
42
+ "homepage": "https://vurb.vinkius.com/",
43
+ "files": [
44
+ "dist",
45
+ "README.md",
46
+ "LICENSE"
47
+ ],
48
+ "engines": {
49
+ "node": ">=18.0.0"
50
+ },
51
+ "publishConfig": {
52
+ "access": "public"
53
+ },
54
+ "peerDependencies": {
55
+ "@vurb/core": "^3.8.0",
56
+ "@modelcontextprotocol/sdk": "^1.12.1"
57
+ },
58
+ "license": "Apache-2.0",
59
+ "devDependencies": {
60
+ "@vurb/core": ">=3.8.0",
61
+ "rimraf": "^6.0.0",
62
+ "vitest": "^3.0.0"
63
+ }
64
+ }