ruvnet/ruflo · error

mcp-caller-auth-verification-failed

mcp-caller-auth-verification-failed

Error message

mcp-caller-auth-verification-failed:${result.reason}

What it means

The final check in resolveMcpCallerIdentity cryptographically verifies the invocation token against the caller's public key. When verifyInvocationToken returns valid=false, the error includes the verifier's reason (e.g. expired, bad signature) as mcp-caller-auth-verification-failed:<reason>, so the suffix tells you exactly which verification step failed.

Solutions

  1. Read the reason suffix in the error message and address that specific failure
  2. Re-issue the invocation token signed by the key matching CLAUDE_FLOW_MCP_CALLER_PUBKEY
  3. Check for clock skew (NTP) if the reason indicates expiry
  4. Rotate both token and pubkey together after key rotation

Example fix

// before
# token signed by old key, pubkey = new key
// after
# re-issue token with new key, export matching CLAUDE_FLOW_MCP_INVOCATION_TOKEN and CLAUDE_FLOW_MCP_CALLER_PUBKEY
Defensive patterns

Strategy: try-catch

Try / catch

try { await callMcpTool(tool, args); } catch (e) { const m = /^mcp-caller-auth-verification-failed:(.+)$/.exec(e.message); if (m) { const reason = m[1]; if (reason.includes('expired')) reissueToken(); else if (reason.includes('signature')) rotateKeyAndToken(); } throw e; }

Prevention

When it happens

Trigger: A well-formed token and pubkey are both set, but verifyInvocationToken rejects them: signature mismatch, expired token, wrong audience/nonce, or the token was signed by a different key than the configured pubkey.

Common situations: Rotating the caller keypair without re-issuing the token; clock skew making a fresh token appear expired; reusing a token past its TTL; configuring the pubkey of a different agent than the token's signer.

Related errors


AI-assisted analysis of ruvnet/ruflo@2602b642d9 (2026-09-15). Data as JSON: /api/errors/af63ae7c0dfe41aa. Report an issue: GitHub.

Appendix: source

Thrown at v3/@claude-flow/cli/src/services/policy-runtime.ts:370

  if (!encodedToken || !publicKeyHex) {
    throw new Error('mcp-caller-auth-enabled-but-no-token');
  }

  const token = decodeTokenEnvelope(encodedToken);
  if (!token) {
    throw new Error('mcp-caller-auth-enabled-but-no-token');
  }

  let publicKey;
  try {
    publicKey = publicKeyFromHex(publicKeyHex);
  } catch {
    throw new Error('mcp-caller-auth-enabled-but-no-token');
  }

  const result = verifyInvocationToken(token, publicKey, {});
  if (!result.valid) {
    throw new Error(`mcp-caller-auth-verification-failed:${result.reason}`);
  }

  return { id: token.callerId, type: 'agent' };
}

export async function authorizeMcpTool(
  toolName: string,
  input: Record<string, unknown>,
  context: Record<string, unknown> = {},
  attributes: Readonly<{
    actionType?: string;
    network?: boolean;
    destructive?: boolean;
    namespaceAccess?: 'read' | 'write';
    envelope?: CapabilityEnvelope;
    costUsd?: number;
    tokens?: number;
    concurrency?: number;

View on GitHub (pinned to 2602b642d9)