Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@vurb/swarm

Package Overview
Dependencies
Maintainers
1
Versions
21
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@vurb/swarm

Federated Handoff Protocol for Vurb — SwarmGateway B2BUA, multi-agent orchestration, zero-trust delegation, and upstream MCP client tunneling.

latest
Source
npmnpm
Version
3.19.6
Version published
Maintainers
1
Created
Source

@vurb/swarm

MCP Multi-Agent Orchestration for Vurb.ts — A framework for creating multi-agent MCP server networks
Federated Handoff Protocol · Zero-trust HMAC delegation · Namespace isolation · B2BUA gateway · Claude · Cursor · Copilot

npm License Node MCP Vurb.ts

MCP Multi-Agent Orchestration for Vurb.ts — the Model Context Protocol framework for building production MCP server networks. @vurb/swarm lets a single gateway MCP server dynamically hand off an LLM session to a specialist upstream MCP micro-server — and bring it back — without the LLM ever losing context or the conversation thread.

The gateway acts as a Back-to-Back User Agent (B2BUA):

LLM (Claude / Cursor / Copilot)
        │   MCP  (tools/list, tools/call)
        ▼
┌──────────────────┐
│  SwarmGateway    │  ← you run this (the "triage" server)
│  (B2BUA / UAS)   │
└────────┬─────────┘
         │  FHP tunnel  (x-vurb-delegation + traceparent)
         ▼
┌──────────────────┐
│  Upstream server │  ← specialist micro-server (finance, devops, hr…)
│  (UAC target)    │
└──────────────────┘

The LLM sees one coherent conversation. Internally, the gateway:

  • Detects a HandoffResponse from one of your tools.
  • Mints a short-lived HMAC-SHA256 delegation token carrying the carry-over context.
  • Opens an MCP tunnel to the upstream micro-server.
  • Proxies all tools/list and tools/call through that tunnel, with namespace prefixing.
  • Injects a gateway.return_to_triage escape tool so the LLM can come back when done.
  • On return, cleanly closes the tunnel and restores the gateway's original tools.

Installation

npm install @vurb/swarm @vurb/core

Quick start

1. Gateway server

import { ToolRegistry } from '@vurb/core';
import { SwarmGateway } from '@vurb/swarm';

const gateway = new SwarmGateway({
    registry: {
        finance: 'http://finance-agent:8081',
        devops:  'http://devops-agent:8082',
    },
    delegationSecret: process.env.VURB_DELEGATION_SECRET!,
});

const registry = new ToolRegistry<AppContext>();

// A triage tool that decides which specialist to call
registry.define('triage')
    .action('route', z.object({ intent: z.string() }), async ({ intent }, f) => {
        if (intent.includes('invoice'))
            return f.handoff('finance', {
                reason: 'Routing to finance specialist.',
                carryOverState: { originalIntent: intent },
            });
        return f.text('I can help with that directly.');
    });

registry.attachToServer(server, {
    contextFactory: createContext,
    swarmGateway: gateway,
});

2. Upstream specialist server

The upstream is a regular Vurb server that uses requireGatewayClearance middleware:

import { ToolRegistry } from '@vurb/core';
import { requireGatewayClearance } from '@vurb/core';

// Attach the zero-trust middleware — rejects any request without a valid token
app.use('/mcp', requireGatewayClearance({
    secret: process.env.VURB_DELEGATION_SECRET!,
}));

const registry = new ToolRegistry<FinanceContext>();

registry.define('invoices')
    .action('list',   z.object({ status: z.string().optional() }), listInvoices)
    .action('refund', z.object({ invoiceId: z.string() }),         refundInvoice);

// The LLM calls these as: finance.invoices_list, finance.invoices_refund

How the FHP works

Activation flow

LLM calls triage.route → HandoffResponse detected by ServerAttachment
    → SwarmGateway.activateHandoff()
        → mintDelegationToken(domain, ttl, secret, carryOverState)
        → UpstreamMcpClient.connect()          (async, non-blocking)
    → LLM receives: HANDOFF_CONNECTING (tools reloading…)
    → notifications/tools/list_changed emitted
    → LLM calls tools/list → SwarmGateway.proxyToolsList()
        → upstream tools prefixed as finance.*
        → gateway.return_to_triage injected

Token lifecycle

PhaseWhat happens
mintDelegationTokenHMAC-SHA256 signed payload: iss, sub, iat, exp, tid, optional traceparent
State > 2 KBClaim-Check: state stored in HandoffStateStore, only UUID key in token
requireGatewayClearanceVerifies HMAC, checks expiry, hydrates carry-over state one-shot
Replay or expiredEXPIRED_DELEGATION_TOKEN — explicit rejection, no silent failure

Namespace isolation

Every tool from the upstream is automatically prefixed with its domain:

upstream: listInvoices  →  gateway exposes: finance.listInvoices
upstream: refund        →  gateway exposes: finance.refund

The gateway strips the prefix before forwarding. If a call arrives with a mismatched prefix: HANDOFF_NAMESPACE_MISMATCH.

Return trip

The LLM always sees gateway.return_to_triage in the upstream tools list. Calling it:

  • Closes the upstream tunnel.
  • Notifies the gateway to emit notifications/tools/list_changed.
  • LLM re-fetches tools and sees the original gateway tools again.

The summary provided by the LLM is anti-IPI sanitised before being returned:

  • HTML-escaped <, >, &
  • [SYSTEM] / [SISTEMA] patterns blocked
  • Hard-truncated at 2000 characters
  • Wrapped in <upstream_report source="finance" trusted="false"> XML envelope

Configuration

const gateway = new SwarmGateway({
    // Required
    registry: {
        finance: 'http://finance-agent:8081',
        devops:  'http://devops-agent:8082',
    },
    delegationSecret: process.env.VURB_DELEGATION_SECRET!,

    // Optional
    stateStore:        myRedisStore,      // custom HandoffStateStore (default: in-memory)
    connectTimeoutMs:  5_000,             // upstream connection timeout (default: 5 s)
    idleTimeoutMs:     300_000,           // idle tunnel timeout (default: 5 min)
    tokenTtlSeconds:   60,                // delegation token TTL (default: 60 s)
    upstreamTransport: 'auto',            // 'auto' | 'sse' | 'http' (default: 'auto')
    gatewayName:       'gateway',         // prefix for return_to_triage (default: 'gateway')
    maxSessions:       100,               // concurrent session limit (default: 100)
});

upstreamTransport

ValueTransportUse when
'auto'SSE on Node.js, HTTP on edgeDefault — works everywhere
'sse'SSE (persistent connection)Long-running sessions, streaming
'http'Streamable HTTP (stateless)Cloudflare Workers, Vercel Edge

Custom state store

For Claim-Check tokens (carry-over state > 2 KB) the in-memory default is not suitable for distributed deployments. Implement HandoffStateStore:

import type { HandoffStateStore } from '@vurb/core';

const redisStore: HandoffStateStore = {
    async set(id, state, ttlSeconds) {
        await redis.set(`vurb:state:${id}`, JSON.stringify(state), { EX: ttlSeconds });
    },
    // Atomic: read + delete in one operation — prevents replay under concurrency
    async getAndDelete(id) {
        const raw = await redis.getdel(`vurb:state:${id}`);
        return raw ? JSON.parse(raw) : undefined;
    },
};

const gateway = new SwarmGateway({
    registry: { finance: '...' },
    delegationSecret: process.env.VURB_DELEGATION_SECRET!,
    stateStore: redisStore,
});

Important: External stores must use a native atomic getAndDelete (e.g. Redis GETDEL) to enforce the one-shot guarantee under high concurrency. Separate get + delete operations have a race window where two simultaneous verifications of the same token can both succeed.

Security properties

PropertyHow it's enforced
Zero-trust upstreamEvery request carries a short-lived HMAC-SHA256 token
One-shot stateClaim-Check state is atomically deleted on first read
Replay protectionExpired or consumed state_idEXPIRED_DELEGATION_TOKEN
Session isolationEach session has its own UpstreamMcpClient instance
Session limitmaxSessions prevents resource exhaustion
Zombie preventionIdle timeout + AbortSignal cascade close orphan tunnels
IPI mitigationReturn summaries sanitised + wrapped in trusted="false" XML
Namespace enforcementPrefix mismatch → HANDOFF_NAMESPACE_MISMATCH, never silently routed
Distributed tracingW3C traceparent generated per handoff, propagated to upstream

Distributed tracing

Every handoff generates a W3C traceparent (00-{traceId}-{spanId}-01) that is:

  • Embedded in the delegation token as a claim.
  • Sent to the upstream via the traceparent HTTP header.
  • Accessible on the upstream via ctx.traceparent (from requireGatewayClearance).

This allows you to correlate gateway ↔ upstream spans in any OpenTelemetry-compatible backend.

Lifecycle & cleanup

// Graceful shutdown — closes all active tunnels
await gateway.dispose();

// Inspection (useful in tests and monitoring)
gateway.sessionCount;    // total sessions (connecting + active)
gateway.connectingCount; // sessions still establishing connection
gateway.hasActiveHandoff(sessionId);
gateway.isConnecting(sessionId);

Target resolution

The target in f.handoff(target, ...) supports two formats:

// Direct registry key (recommended)
f.handoff('finance', { reason: '...' })

// MCP URI (hostname subdomain is matched against registry)
f.handoff('mcp://finance-agent.internal:8080', { reason: '...' })
f.handoff('mcps://finance-agent.internal', { reason: '...' })  // secure

Error codes

CodeWhen
HANDOFF_CONNECTINGUpstream is still establishing — retry
HANDOFF_UPSTREAM_UNAVAILABLEUpstream dropped mid-session
HANDOFF_NAMESPACE_MISMATCHTool prefix doesn't match active domain
SESSION_LIMIT_EXCEEDEDmaxSessions cap reached
REGISTRY_LOOKUP_FAILEDUnknown target in registry
REGISTRY_INVALID_URIRegistry entry has empty URI
UPSTREAM_CONNECT_TIMEOUTUpstream didn't respond within connectTimeoutMs
EXPIRED_DELEGATION_TOKENToken expired or Claim-Check state already consumed

Package layout

FileResponsibility
SwarmGateway.tsB2BUA orchestrator — session lifecycle, proxy routing
UpstreamMcpClient.tsOutbound MCP client (SSE/HTTP), idle timer, signal cascade
NamespaceRewriter.tsTool name prefix/unprefix, NamespaceError
ReturnTripInjector.tsgateway.return_to_triage injection + anti-IPI sanitiser

License

Apache-2.0 © Vinkius

Keywords

mcp

FAQs

Package last updated on 11 May 2026

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts