vercel/ai · error

Invalid host tool relay response.

Error message

Invalid host tool relay response.

What it means

Thrown by validateInvocationResult when the relay's /invoke response is not a well-formed invocation result: the body is not an object, lacks a string 'correlationToken', or has a non-boolean 'isError'. The bridge uses this to guarantee every tool invocation result handed to the MCP server has a correlation token matching the request.

Source

Thrown at packages/harness-acp/src/v1/bridge/host-tool-mcp.ts:154

    typeof value.name === 'string' &&
    (value.description === undefined ||
      typeof value.description === 'string') &&
    (value.inputSchema === undefined ||
      (isRecord(value.inputSchema) && !Array.isArray(value.inputSchema)))
  );
}

function validateInvocationResult({
  value,
}: {
  value: unknown;
}): HostToolMCPInvocationResult {
  if (
    !isRecord(value) ||
    typeof value.correlationToken !== 'string' ||
    (value.isError !== undefined && typeof value.isError !== 'boolean')
  ) {
    throw new Error('Invalid host tool relay response.');
  }
  return {
    output: value.output,
    ...(value.isError ? { isError: true } : {}),
    correlationToken: value.correlationToken,
  };
}

function readErrorMessage({
  value,
  status,
}: {
  value: unknown;
  status: number;
}): string {
  return isRecord(value) && typeof value.error === 'string'
    ? value.error
    : `Host tool relay returned HTTP ${status}.`;

View on GitHub (pinned to 69428b1f8b)

Solutions

  1. Inspect the relay's /invoke handler and confirm it always echoes the request's correlationToken as a string in the response.
  2. Check relay and bridge package versions match; align to the same AI SDK release.
  3. Capture the raw response body to check for an enveloping proxy rewriting it.
  4. If 'isError' is set, ensure it is a JSON boolean (true/false), not the strings "true"/"false".

Example fix

// before: relay response
{ "output": "done" }
// after
{ "output": "done", "correlationToken": "<requestId from invoke>", "isError": false }
Defensive patterns

Strategy: type-guard

Validate before calling

function isValidInvocationResponse(value) {
  return (
    value != null && typeof value === 'object' && !Array.isArray(value) &&
    typeof value.correlationToken === 'string' &&
    (value.isError === undefined || typeof value.isError === 'boolean')
  );
}
// check before accepting the relay body as an invocation result

Type guard

function isInvocationResult(value: unknown): value is HostToolMCPInvocationResult {
  return (
    value != null && typeof value === 'object' && !Array.isArray(value) &&
    typeof (value as any).correlationToken === 'string' &&
    ((value as any).isError === undefined || typeof (value as any).isError === 'boolean')
  );
}

Try / catch

try {
  const result = await invokeViaRelay({ toolName, input });
} catch (error) {
  // 'Invalid host tool relay response.' => relay violated the /invoke contract;
  // return an MCP tool error to the client rather than crashing the server.
  return { isError: true, message: error instanceof Error ? error.message : String(error) };
}

Prevention

When it happens

Trigger: hostToolServer invoke -> postRelay('/invoke') returning HTTP 200 with a body that: is not a JSON object, omits 'correlationToken', has a non-string correlationToken, or has 'isError' set to a non-boolean value.

Common situations: Relay implementation bug dropping correlationToken on success paths; a middleware/proxy wrapping or replacing the response body; relay/bridge version mismatch after a wire-format change; relay echoing an error envelope with HTTP 200.

Related errors


AI-assisted analysis of vercel/ai@69428b1f8b (2026-08-30). Data as JSON: /api/errors/455f9c956e05db2f. Report an issue: GitHub.