different-ai/openwork · error · SafeProbeFailure

invalid_catalog

invalid_catalog

Error message

invalid_catalog

What it means

validateCatalog throws SafeProbeFailure("invalid_catalog") at line 550 when the tools/list result's 'tools' member is not an array or its length exceeds MAX_TOOL_COUNT. The probe caps catalog size both as a sanity check and a resource guard, and requires the field to be a proper array.

Source

Thrown at apps/server/src/agent-context-cloud-probe.ts:550

  if (payload.id !== requestId) throw new SafeProbeFailure("request_id_mismatch");
  if (!isRecord(payload.result)) throw new SafeProbeFailure("invalid_jsonrpc_envelope");
  return payload.result;
}

function requireSupportedProtocolVersion(initializeResult: Record<string, unknown>): void {
  const version = initializeResult.protocolVersion;
  if (typeof version !== "string" || version.length === 0 || version.length > MAX_PROTOCOL_HEADER_LENGTH) {
    throw new SafeProbeFailure("invalid_jsonrpc_envelope");
  }
  if (version !== MCP_PROTOCOL_VERSION) throw new SafeProbeFailure("unsupported_protocol_version");
}

function validateCatalog(rpcResult: Record<string, unknown>): { toolIds: string[]; totalToolCount: number } {
  if (rpcResult.nextCursor !== undefined && rpcResult.nextCursor !== null) {
    throw new SafeProbeFailure("pagination_unsupported");
  }
  if (!Array.isArray(rpcResult.tools) || rpcResult.tools.length > MAX_TOOL_COUNT) {
    throw new SafeProbeFailure("invalid_catalog");
  }
  const seen = new Set<string>();
  for (const tool of rpcResult.tools) {
    if (!isRecord(tool) || typeof tool.name !== "string" || tool.name.length > MAX_TOOL_ID_LENGTH || !TOOL_ID.test(tool.name)) {
      throw new SafeProbeFailure("invalid_catalog");
    }
    if (seen.has(tool.name)) throw new SafeProbeFailure("invalid_catalog");
    seen.add(tool.name);
  }
  // Additional provider tools are forward-compatible and allowed, but never
  // reflect provider-controlled catalog names into the diagnostic report. In
  // particular, a compromised trusted endpoint must not be able to echo the
  // bearer token back as a syntactically valid tool identifier. Only the
  // expected allowlisted IDs and the aggregate count are exported.
  const missing = REQUIRED_TOOL_IDS.filter((toolId) => !seen.has(toolId));
  if (missing.length > 0) throw new CatalogRequiredToolsFailure(rpcResult.tools.length);
  return { toolIds: [...REQUIRED_TOOL_IDS], totalToolCount: rpcResult.tools.length };
}

View on GitHub (pinned to 2b7df46e8a)

Solutions

  1. Fix the server so tools/list returns { tools: [...] } as an array
  2. Reduce the number of registered/exposed tools below MAX_TOOL_COUNT (trim plugins or integrations)
  3. Raise the probe's MAX_TOOL_COUNT only via a deliberate client change
  4. Inspect the raw tools/list body to see the actual shape and size

Example fix

// before
{"result":{"tools":{"myTool":{...}}}}
// after
{"result":{"tools":[{"name":"myTool",...}]}}
Defensive patterns

Strategy: validation

Validate before calling

const result = JSON.parse(raw)?.result;
if (!result || !Array.isArray(result.tools)) throw new Error("tools/list result.tools must be an array");
if (result.tools.length > MAX_TOOL_COUNT) throw new Error(`catalog too large: ${result.tools.length} > ${MAX_TOOL_COUNT}`);

Type guard

function hasValidToolsArray(result: Record<string, unknown>, max: number): result is { tools: unknown[] } & Record<string, unknown> {
  return Array.isArray(result.tools) && result.tools.length <= max;
}

Try / catch

try {
  const catalog = validateCatalog(rpcResult);
} catch (e) {
  if (e instanceof SafeProbeFailure && e.code === "invalid_catalog") {
    console.error("tools/list shape or size rejected", { isArray: Array.isArray(rpcResult.tools), count: rpcResult.tools?.length });
  } else throw e;
}

Prevention

When it happens

Trigger: Server returns tools as null/object/string, omits tools entirely, or returns more tools than MAX_TOOL_COUNT.

Common situations: Nonconformant MCP server with a different tools/list response shape; a server whose catalog legitimately exceeds the probe's safety cap (very large tool registries); middleware injecting extra tool entries.

Related errors


AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01). Data as JSON: /api/errors/9e70362e30a72160. Report an issue: GitHub.