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.

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)

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.