n8n-io/n8n · error · Error

Failed to read MCP servers from endpoint: response is not a…

Error message

Failed to read MCP servers from endpoint: response is not a server list

What it means

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.

Solutions

  1. Check the console.error log line for payloadType (object vs array) to understand what came back.
  2. 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.
  3. Verify the agentic app id resolves to an MCP-capable agent, not a different resource that returns a different envelope.
  4. If a captive portal / proxy is intercepting, fix the network egress path so the request reaches the real Tooling Gateway.
Defensive patterns

Strategy: try-catch

Validate before calling

function looksLikeServerList(payload: unknown): boolean {
  return Array.isArray(payload)
    || (!!payload && typeof payload === 'object'
        && Array.isArray((payload as { servers?: unknown }).servers));
}

Type guard

interface McpServerList { servers?: unknown[] }
function isServerListPayload(p: unknown): p is McpServerList {
  return !!p && typeof p === 'object' && Array.isArray((p as McpServerList).servers);
}

Try / catch

try {
  return await getMcpServerConfigsWithoutAudienceTokens(turnContext, token);
} catch (e) {
  if ((e as Error).message.endsWith('response is not a server list')) {
    // schema drift: capture the raw payload out-of-band (never log inline) and
    // update getRawMcpServers to the current Microsoft MCP discovery schema
    await captureForDiagnosis(endpoint);
  }
  throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12). Data as JSON: /api/errors/4751214516354693. Report an issue: GitHub.

Appendix: source

Thrown at packages/@n8n/nodes-langchain/nodes/vendors/Microsoft/microsoft-utils.ts:251

			mcpAuthToken,
			turnContext,
			MICROSOFT_TOOL_OPTIONS,
		),
	});

	if (!response.ok) {
		throw new Error(`Failed to read MCP servers from endpoint: ${response.status}`);
	}

	const payload: unknown = await response.json();
	const rawServers = getRawMcpServers(payload);
	if (!rawServers) {
		// Log only the payload type, never the raw body: it's an untrusted external
		// response that may carry sensitive values.
		console.error('Microsoft MCP server discovery returned an unsupported payload shape', {
			payloadType: Array.isArray(payload) ? 'array' : typeof payload,
		});
		throw new Error('Failed to read MCP servers from endpoint: response is not a server list');
	}

	const servers = rawServers
		.map((rawServer) => normalizeMcpServerConfig(rawServer))
		.filter((server): server is MCPServerConfig => server !== undefined);

	console.warn(`Microsoft MCP server discovery completed: ${servers.length} servers found`);

	return servers;
}

async function attachMcpServerAuthorization(
	server: MCPServerConfig,
	turnContext: TurnContext,
	authorization: Authorization,
	mcpAuthToken: string,
) {
	const sharedScope =

View on GitHub (pinned to 5ac6606e81)