
Research
/Security News
Mini Shai-Hulud Campaign Hits Red Hat Cloud Services npm Packages
A mini Shai-Hulud campaign compromised Red Hat Cloud Services npm packages to steal developer and CI/CD secrets during installation.
@vurb/swarm
Advanced tools
Federated Handoff Protocol for Vurb — SwarmGateway B2BUA, multi-agent orchestration, zero-trust delegation, and upstream MCP client tunneling.
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
MCP Multi-Agent Orchestration for Vurb.ts — the Model Context Protocol framework for building production MCP server networks.
@vurb/swarmlets 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:
HandoffResponse from one of your tools.tools/list and tools/call through that tunnel, with namespace prefixing.gateway.return_to_triage escape tool so the LLM can come back when done.npm install @vurb/swarm @vurb/core
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,
});
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
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
| Phase | What happens |
|---|---|
mintDelegationToken | HMAC-SHA256 signed payload: iss, sub, iat, exp, tid, optional traceparent |
| State > 2 KB | Claim-Check: state stored in HandoffStateStore, only UUID key in token |
requireGatewayClearance | Verifies HMAC, checks expiry, hydrates carry-over state one-shot |
| Replay or expired | → EXPIRED_DELEGATION_TOKEN — explicit rejection, no silent failure |
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.
The LLM always sees gateway.return_to_triage in the upstream tools list. Calling it:
notifications/tools/list_changed.The summary provided by the LLM is anti-IPI sanitised before being returned:
<, >, &[SYSTEM] / [SISTEMA] patterns blocked<upstream_report source="finance" trusted="false"> XML envelopeconst 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| Value | Transport | Use when |
|---|---|---|
'auto' | SSE on Node.js, HTTP on edge | Default — works everywhere |
'sse' | SSE (persistent connection) | Long-running sessions, streaming |
'http' | Streamable HTTP (stateless) | Cloudflare Workers, Vercel Edge |
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. RedisGETDEL) to enforce the one-shot guarantee under high concurrency. Separateget+deleteoperations have a race window where two simultaneous verifications of the same token can both succeed.
| Property | How it's enforced |
|---|---|
| Zero-trust upstream | Every request carries a short-lived HMAC-SHA256 token |
| One-shot state | Claim-Check state is atomically deleted on first read |
| Replay protection | Expired or consumed state_id → EXPIRED_DELEGATION_TOKEN |
| Session isolation | Each session has its own UpstreamMcpClient instance |
| Session limit | maxSessions prevents resource exhaustion |
| Zombie prevention | Idle timeout + AbortSignal cascade close orphan tunnels |
| IPI mitigation | Return summaries sanitised + wrapped in trusted="false" XML |
| Namespace enforcement | Prefix mismatch → HANDOFF_NAMESPACE_MISMATCH, never silently routed |
| Distributed tracing | W3C traceparent generated per handoff, propagated to upstream |
Every handoff generates a W3C traceparent (00-{traceId}-{spanId}-01) that is:
traceparent HTTP header.ctx.traceparent (from requireGatewayClearance).This allows you to correlate gateway ↔ upstream spans in any OpenTelemetry-compatible backend.
// 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);
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
| Code | When |
|---|---|
HANDOFF_CONNECTING | Upstream is still establishing — retry |
HANDOFF_UPSTREAM_UNAVAILABLE | Upstream dropped mid-session |
HANDOFF_NAMESPACE_MISMATCH | Tool prefix doesn't match active domain |
SESSION_LIMIT_EXCEEDED | maxSessions cap reached |
REGISTRY_LOOKUP_FAILED | Unknown target in registry |
REGISTRY_INVALID_URI | Registry entry has empty URI |
UPSTREAM_CONNECT_TIMEOUT | Upstream didn't respond within connectTimeoutMs |
EXPIRED_DELEGATION_TOKEN | Token expired or Claim-Check state already consumed |
| File | Responsibility |
|---|---|
SwarmGateway.ts | B2BUA orchestrator — session lifecycle, proxy routing |
UpstreamMcpClient.ts | Outbound MCP client (SSE/HTTP), idle timer, signal cascade |
NamespaceRewriter.ts | Tool name prefix/unprefix, NamespaceError |
ReturnTripInjector.ts | gateway.return_to_triage injection + anti-IPI sanitiser |
Apache-2.0 © Vinkius
FAQs
Federated Handoff Protocol for Vurb — SwarmGateway B2BUA, multi-agent orchestration, zero-trust delegation, and upstream MCP client tunneling.
We found that @vurb/swarm demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

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.

Research
/Security News
A mini Shai-Hulud campaign compromised Red Hat Cloud Services npm packages to steal developer and CI/CD secrets during installation.

Research
/Security News
The North Korean malware loader hides in a Packagist-listed package and its GitHub branch to fetch and execute remote code in a likely Contagious Interview-style lure.

Security News
The Rust project is moving toward formal rules on LLM use in contributions after months of internal debate over maintainer burden, code quality, and contributor experience.