ruvnet/ruflo · error · HttpFetchValidationError
INVALID_HEADERS
INVALID_HEADERS
Error message
headers must be an object
What it means
Before per-header validation, the http_fetch handler requires input.headers to be a plain object: typeof 'object', non-null, and not an array. Anything else — a string like 'Content-Type: application/json', an array of [name, value] pairs, or a number — throws HttpFetchValidationError 'headers must be an object' (code INVALID_HEADERS), returned as success:false with status 0. headers is optional; omitting it or passing {} is fine because the handler defaults null/undefined to {}.
Solutions
- Pass headers as a flat string-to-string object, e.g. { 'content-type': 'application/json' }, or omit the parameter entirely
- Convert tuple arrays first: Object.fromEntries([['accept','json'], ['x-trace','abc']])
- Parse serialized configs before the call: JSON.parse(headerJson)
- Log the runtime type at the call site (console.dir(input.headers)) to confirm what you are actually sending
Example fix
// before
await mcp.callTool('http_fetch', {
url: 'https://api.example.com',
headers: 'Content-Type: application/json', // INVALID_HEADERS: string, not object
});
// after
await mcp.callTool('http_fetch', {
url: 'https://api.example.com',
headers: { 'Content-Type': 'application/json' },
}); Defensive patterns
Strategy: type-guard
Validate before calling
function normalizeHeaders(input: unknown): Record<string, string> | undefined {
if (input == null) return undefined;
if (typeof input !== 'object' || Array.isArray(input)) {
throw new TypeError('headers must be a flat object of string -> string');
}
return Object.fromEntries(Object.entries(input).map(([k, v]) => [k, String(v)]));
}
// const headers = normalizeHeaders(args.headers); Type guard
function isPlainHeadersObject(v: unknown): v is Record<string, unknown> {
return typeof v === 'object' && v !== null && !Array.isArray(v);
}
// if (args.headers !== undefined && !isPlainHeadersObject(args.headers)) fail fast with your own message Try / catch
try {
const res = await httpFetch({ url, headers: raw });
if (!res.success && res.errorCode === 'INVALID_HEADERS') {
// log the runtime type of raw (typeof + Array.isArray) and fix the caller's shape
}
} catch (e) {
if (e instanceof HttpFetchValidationError && e.code === 'INVALID_HEADERS') { /* shape error: never retry as-is */ }
} Prevention
- Always build headers as an object literal or Object.fromEntries, never a string or tuple array
- Parse serialized header configs (JSON.parse) before passing them
- Validate externally sourced arguments with a schema (Zod record of strings) before invoking MCP tools
- Remember headers is optional — omit it rather than passing null
When it happens
Trigger: Calling http_fetch with headers as a raw string (copy-pasted curl -H syntax), an array of tuples like [['accept','json']], or a JSON.stringify'd object that was never parsed. Only non-object values that are present trip the check, since `input.headers ?? {}` absorbs null/undefined.
Common situations: Translating curl commands into tool arguments; passing headers: JSON.stringify(obj) by mistake; config systems that deliver arrays of {name, value} records; LLM-generated arguments using the wrong shape.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- INVALID_HEADER_VALUE
- FORBIDDEN_HEADER
- Failed to fetch base servers
- FORBIDDEN_PROTOCOL
- HTTP transport already running
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/6892141e45171e1a.
Report an issue: GitHub.
Appendix: source
Thrown at v3/@claude-flow/cli/src/mcp-tools/http-fetch-tools.ts:199
status: 0,
statusText: '',
headers: {},
body: '',
bodyTruncated: false,
bytesRead: 0,
durationMs: Date.now() - startedAt,
url,
method,
error: err.message,
errorCode: err.code ?? 'VALIDATION_ERROR',
};
}
let headers: Record<string, string>;
try {
const raw = (input.headers ?? {}) as Record<string, string>;
if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {
throw new HttpFetchValidationError('headers must be an object', 'INVALID_HEADERS');
}
headers = validateHeaders(raw);
} catch (e) {
const err = e as HttpFetchValidationError;
return {
success: false,
status: 0,
statusText: '',
headers: {},
body: '',
bodyTruncated: false,
bytesRead: 0,
durationMs: Date.now() - startedAt,
url,
method,
error: err.message,
errorCode: err.code ?? 'VALIDATION_ERROR',
};View on GitHub (pinned to fa13ee4ad6)