{"record":{"id":"4751214516354693","repo":"n8n-io/n8n","slug":"failed-to-read-mcp-servers-from-endpoint-response","errorCode":null,"errorMessage":"Failed to read MCP servers from endpoint: response is not a server list","messagePattern":"Failed to read MCP servers from endpoint: response is not a server list","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/@n8n/nodes-langchain/nodes/vendors/Microsoft/microsoft-utils.ts","lineNumber":251,"sourceCode":"\t\t\tmcpAuthToken,\n\t\t\tturnContext,\n\t\t\tMICROSOFT_TOOL_OPTIONS,\n\t\t),\n\t});\n\n\tif (!response.ok) {\n\t\tthrow new Error(`Failed to read MCP servers from endpoint: ${response.status}`);\n\t}\n\n\tconst payload: unknown = await response.json();\n\tconst rawServers = getRawMcpServers(payload);\n\tif (!rawServers) {\n\t\t// Log only the payload type, never the raw body: it's an untrusted external\n\t\t// response that may carry sensitive values.\n\t\tconsole.error('Microsoft MCP server discovery returned an unsupported payload shape', {\n\t\t\tpayloadType: Array.isArray(payload) ? 'array' : typeof payload,\n\t\t});\n\t\tthrow new Error('Failed to read MCP servers from endpoint: response is not a server list');\n\t}\n\n\tconst servers = rawServers\n\t\t.map((rawServer) => normalizeMcpServerConfig(rawServer))\n\t\t.filter((server): server is MCPServerConfig => server !== undefined);\n\n\tconsole.warn(`Microsoft MCP server discovery completed: ${servers.length} servers found`);\n\n\treturn servers;\n}\n\nasync function attachMcpServerAuthorization(\n\tserver: MCPServerConfig,\n\tturnContext: TurnContext,\n\tauthorization: Authorization,\n\tmcpAuthToken: string,\n) {\n\tconst sharedScope =","sourceCodeStart":233,"sourceCodeEnd":269,"githubUrl":"https://github.com/n8n-io/n8n/blob/5ac6606e81f67bb9534255570cd4e86fd8101eee/packages/@n8n/nodes-langchain/nodes/vendors/Microsoft/microsoft-utils.ts#L233-L269","documentation":"Shape-validation branch of MCP server discovery: response was 2xx but the JSON payload didn't match an expected server-list shape (getRawMcpServers returned falsy). The code intentionally does NOT log the raw body (untrusted, may carry sensitive values) — it logs only the payload type to console.error — then throws this message. So the payload arrived but in an unexpected schema.","triggerScenarios":"Microsoft's Tooling Gateway returned 200 with a payload that isn't a server list: an error object masquerading as success, a newer/undocumented schema version, an HTML login page captured as JSON, or an empty object when a servers array was expected.","commonSituations":"Gateway version drift (Microsoft changed the discovery schema); a transparent proxy/portal returning a captive-redirect JSON; tenant returned an empty/error envelope with HTTP 200; the agentic app id resolved to a non-MCP resource.","solutions":["Check the console.error log line for payloadType (object vs array) to understand what came back.","Capture the raw response (out-of-band, never logged by this code) to compare against the current Microsoft MCP discovery schema; update getRawMcpServers if the schema evolved.","Verify the agentic app id resolves to an MCP-capable agent, not a different resource that returns a different envelope.","If a captive portal / proxy is intercepting, fix the network egress path so the request reaches the real Tooling Gateway."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"function looksLikeServerList(payload: unknown): boolean {\n  return Array.isArray(payload)\n    || (!!payload && typeof payload === 'object'\n        && Array.isArray((payload as { servers?: unknown }).servers));\n}","typeGuard":"interface McpServerList { servers?: unknown[] }\nfunction isServerListPayload(p: unknown): p is McpServerList {\n  return !!p && typeof p === 'object' && Array.isArray((p as McpServerList).servers);\n}","tryCatchPattern":"try {\n  return await getMcpServerConfigsWithoutAudienceTokens(turnContext, token);\n} catch (e) {\n  if ((e as Error).message.endsWith('response is not a server list')) {\n    // schema drift: capture the raw payload out-of-band (never log inline) and\n    // update getRawMcpServers to the current Microsoft MCP discovery schema\n    await captureForDiagnosis(endpoint);\n  }\n  throw e;\n}","preventionTips":["Keep getRawMcpServers/normalizeMcpServerConfig in sync with the current Microsoft MCP discovery schema.","Ensure network egress reaches the real Tooling Gateway — not a captive portal returning a different envelope with HTTP 200.","Confirm the agentic app id resolves to an MCP-capable agent before relying on the payload shape."],"tags":["microsoft","mcp","tooling-gateway","schema","validation","untrusted-response"],"backgroundTag":null,"analyzedSha":"5ac6606e81f67bb9534255570cd4e86fd8101eee","analyzedAt":"2026-08-12T05:26:35.080Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}