{"record":{"id":"9e70362e30a72160","repo":"different-ai/openwork","slug":"invalid-catalog","errorCode":"invalid_catalog","errorMessage":"invalid_catalog","messagePattern":"invalid_catalog","errorType":"error_code","errorClass":"SafeProbeFailure","httpStatus":null,"severity":"error","filePath":"apps/server/src/agent-context-cloud-probe.ts","lineNumber":550,"sourceCode":"  if (payload.id !== requestId) throw new SafeProbeFailure(\"request_id_mismatch\");\n  if (!isRecord(payload.result)) throw new SafeProbeFailure(\"invalid_jsonrpc_envelope\");\n  return payload.result;\n}\n\nfunction requireSupportedProtocolVersion(initializeResult: Record<string, unknown>): void {\n  const version = initializeResult.protocolVersion;\n  if (typeof version !== \"string\" || version.length === 0 || version.length > MAX_PROTOCOL_HEADER_LENGTH) {\n    throw new SafeProbeFailure(\"invalid_jsonrpc_envelope\");\n  }\n  if (version !== MCP_PROTOCOL_VERSION) throw new SafeProbeFailure(\"unsupported_protocol_version\");\n}\n\nfunction validateCatalog(rpcResult: Record<string, unknown>): { toolIds: string[]; totalToolCount: number } {\n  if (rpcResult.nextCursor !== undefined && rpcResult.nextCursor !== null) {\n    throw new SafeProbeFailure(\"pagination_unsupported\");\n  }\n  if (!Array.isArray(rpcResult.tools) || rpcResult.tools.length > MAX_TOOL_COUNT) {\n    throw new SafeProbeFailure(\"invalid_catalog\");\n  }\n  const seen = new Set<string>();\n  for (const tool of rpcResult.tools) {\n    if (!isRecord(tool) || typeof tool.name !== \"string\" || tool.name.length > MAX_TOOL_ID_LENGTH || !TOOL_ID.test(tool.name)) {\n      throw new SafeProbeFailure(\"invalid_catalog\");\n    }\n    if (seen.has(tool.name)) throw new SafeProbeFailure(\"invalid_catalog\");\n    seen.add(tool.name);\n  }\n  // Additional provider tools are forward-compatible and allowed, but never\n  // reflect provider-controlled catalog names into the diagnostic report. In\n  // particular, a compromised trusted endpoint must not be able to echo the\n  // bearer token back as a syntactically valid tool identifier. Only the\n  // expected allowlisted IDs and the aggregate count are exported.\n  const missing = REQUIRED_TOOL_IDS.filter((toolId) => !seen.has(toolId));\n  if (missing.length > 0) throw new CatalogRequiredToolsFailure(rpcResult.tools.length);\n  return { toolIds: [...REQUIRED_TOOL_IDS], totalToolCount: rpcResult.tools.length };\n}","sourceCodeStart":532,"sourceCodeEnd":568,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/apps/server/src/agent-context-cloud-probe.ts#L532-L568","documentation":"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.","triggerScenarios":"Server returns tools as null/object/string, omits tools entirely, or returns more tools than MAX_TOOL_COUNT.","commonSituations":"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.","solutions":["Fix the server so tools/list returns { tools: [...] } as an array","Reduce the number of registered/exposed tools below MAX_TOOL_COUNT (trim plugins or integrations)","Raise the probe's MAX_TOOL_COUNT only via a deliberate client change","Inspect the raw tools/list body to see the actual shape and size"],"exampleFix":"// before\n{\"result\":{\"tools\":{\"myTool\":{...}}}}\n// after\n{\"result\":{\"tools\":[{\"name\":\"myTool\",...}]}}","handlingStrategy":"validation","validationCode":"const result = JSON.parse(raw)?.result;\nif (!result || !Array.isArray(result.tools)) throw new Error(\"tools/list result.tools must be an array\");\nif (result.tools.length > MAX_TOOL_COUNT) throw new Error(`catalog too large: ${result.tools.length} > ${MAX_TOOL_COUNT}`);","typeGuard":"function hasValidToolsArray(result: Record<string, unknown>, max: number): result is { tools: unknown[] } & Record<string, unknown> {\n  return Array.isArray(result.tools) && result.tools.length <= max;\n}","tryCatchPattern":"try {\n  const catalog = validateCatalog(rpcResult);\n} catch (e) {\n  if (e instanceof SafeProbeFailure && e.code === \"invalid_catalog\") {\n    console.error(\"tools/list shape or size rejected\", { isArray: Array.isArray(rpcResult.tools), count: rpcResult.tools?.length });\n  } else throw e;\n}","preventionTips":["Contract-test tools/list response shape on every server release","Keep exposed tool counts under the client's MAX_TOOL_COUNT cap (disable unused plugins)","Inspect the raw tools/list body before assuming a client bug"],"tags":["mcp","tools-list","schema"],"backgroundTag":"invalid-tool-catalog","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}