ruvnet/ruflo · error · Error
gateway tool error
Error message
gateway tool error
What it means
Thrown by `gatewayTool` after a successful `gatewayRpc` round-trip when the remote tool reported failure: either the MCP response has `isError: true` or the parsed content JSON contains an `error` field. Unlike error 26, the JSON-RPC layer succeeded — the tool executed but returned an error payload. Falls back to the literal 'gateway tool error' when the parsed body has no `error` message.
Solutions
- Inspect the thrown message — if it is the generic fallback 'gateway tool error', enable verbose logging or call the gateway directly to see the tool's full content payload.
- Validate the tool's input against its declared inputSchema before invoking (e.g. required `msgType`/`payload` for federation_publish).
- For admin-gated tools, ensure RUFLO_X_ADMIN_TOKEN is set and valid (error 28/29 catch the missing-token case earlier; an invalid token may surface here).
- Retry on transient gateway-side failures; check gateway status/logs if errors persist.
- Confirm gateway/tool version compatibility if the response shape changed after a gateway upgrade.
Example fix
// before
const out = await gatewayTool('federation_publish', { msgType: 'Status' });
// after: guard required fields first
if (!msgType || !payload) throw new Error('federation_publish requires msgType and payload');
const out = await gatewayTool('federation_publish', { msgType, payload }); Defensive patterns
Strategy: type-guard
Validate before calling
const required = ['msgType', 'payload']; // per tool inputSchema
for (const k of required) {
if (!(k in args)) throw new Error(`x_federation tool missing required field: ${k}`);
} Type guard
type ToolResponse = { content?: Array<{ text?: string }>; isError?: boolean };
function toolReportedError(r: ToolResponse, parsed: Record<string, unknown>): boolean {
return r.isError === true || 'error' in parsed;
} Try / catch
try {
const parsed = await gatewayTool(name, args);
} catch (e) {
if (e instanceof Error && e.message === 'gateway tool error') {
console.error('tool failed without a message — inspect full gateway content payload / gateway logs');
} else if (e instanceof Error) {
console.error('tool error:', e.message);
} else throw e;
} Prevention
- Validate inputs against each x_federation tool's declared inputSchema before invoking.
- Handle both error channels: the JSON-RPC error (26) and the MCP isError/content error (this one).
- Capture the full tool content on failure for diagnostics instead of only the thrown message.
- Keep gateway and CLI versions aligned so response shapes stay stable.
- Retry transient tool failures with backoff; escalate persistent ones to gateway logs.
When it happens
Trigger: Calling any `x_federation_*` gateway tool where the remote handler failed but responded via the MCP `isError` flag or an `{ error: ... }` content body: e.g. `federation_publish` rejecting a msgType/payload, `federation_invite_mint` failing server-side, or a tool returning an error result for invalid input while still returning HTTP 200 and a valid JSON-RPC result.
Common situations: Publishing a message with a payload the gateway's schema rejects; minting invites when the relay-side write fails; a gateway version mismatch where the remote tool's response shape changed (isError set but no parseable message); permissions/auth failures inside the tool that surface as tool-level errors rather than RPC errors.
Related errors
- Federation not initialized
- RUFLO_X_ADMIN_TOKEN is not set (admission is admin-gated)
- WG mesh layer not initialized (set config.wgMesh = true and…
- x.ruv.io
- at least one candidate is required
AI-assisted analysis of ruvnet/ruflo@9c61c86f06 (2026-09-22).
Data as JSON: /api/errors/d3211683f05d4967.
Report an issue: GitHub.
Appendix: source
Thrown at v3/@claude-flow/cli/src/mcp-tools/x-federation-tools.ts:118
*/
export function relayPayload(text: string): Record<string, unknown> {
const parsed = parseGatewayText(text);
return (parsed.untrusted === true && parsed.data !== undefined
? (parsed.data as Record<string, unknown>)
: parsed);
}
async function gatewayTool(name: string, args: Record<string, unknown>): Promise<unknown> {
const { gatewayUrl, ...rest } = args;
const r = (await gatewayRpc('tools/call', { name, arguments: rest }, gatewayUrl)) as { content?: Array<{ text?: string }>; isError?: boolean };
const raw = r.content?.[0]?.text ?? '{}';
// An isError result is the SDK's createToolError, whose message is raw text and
// not JSON. Parsing first turns "private channels cannot be published…" into
// "Unexpected token 'p'" — the same class of bug this parser exists to fix.
if (r.isError) {
let msg = raw;
try { msg = String((parseGatewayText(raw) as { error?: unknown }).error ?? raw); } catch { /* raw text: use as-is */ }
throw new Error(msg || 'gateway tool error');
}
const parsed = parseGatewayText(raw);
if (parsed.error) throw new Error(String(parsed.error));
return parsed;
}
async function gatewayResource(uri: string, gatewayUrl?: unknown): Promise<unknown> {
const r = (await gatewayRpc('resources/read', { uri }, gatewayUrl)) as { contents?: Array<{ text?: string }> };
return parseGatewayText(r.contents?.[0]?.text ?? '{}');
}
// Credential: intentionally env-only (a secret must never be a CLI flag — it would land in
// shell history / process lists). Registered in scripts/audit-env-var-precedence.mjs.
const adminToken = (): string | undefined => process.env.RUFLO_X_ADMIN_TOKEN;
export const xFederationTools: MCPTool[] = [
{
name: 'x_federation_sync',
description:
'Fetch recent signature-verified coordination messages from the open x.ruv.io swarm federation (Nostr, #t=ruflo-swarm). Use when you need to see what other ruflo nodes across the internet have posted (PeerHello/Status/Task/Result/Claim*). Reading the relay directly is wrong because you would have to do NIP-42 auth yourself; the gateway does it and only returns events whose signatures verify.',View on GitHub (pinned to 9c61c86f06)