n8n-io/n8n · critical · Error

Failed to obtain token for MCP server '${server.mcpServerNam

Error message

Failed to obtain token for MCP server '${server.mcpServerName}'

What it means

Thrown by attachMcpServerAuthorization in the Microsoft 365 Agent node when neither the cached shared-scope token (mcpAuthToken) nor a fresh per-server token from AgenticAuthenticationService.GetAgenticUserToken yields a non-empty value. The Microsoft 365 Agent requires a valid Azure AD bearer token scoped for the target MCP server before it can proxy tool calls, so a missing token is fatal for that server's authorization header. The check is a simple falsy guard on the resolved token string.

Source

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

) {
	const sharedScope =
		defaultToolingConfigurationProvider.getConfiguration().mcpPlatformAuthenticationScope;
	const scope = resolveTokenScopeForServer(server, sharedScope);

	if (scope === sharedScope && hasAuthorizationHeader(server.headers ?? {})) return server;

	const token =
		scope === sharedScope
			? mcpAuthToken
			: await AgenticAuthenticationService.GetAgenticUserToken(
					authorization,
					'agentic',
					turnContext,
					[scope],
				);

	if (!token) {
		throw new Error(`Failed to obtain token for MCP server '${server.mcpServerName}'`);
	}

	return {
		...server,
		headers: {
			...server.headers,
			Authorization: `Bearer ${token}`,
		},
	};
}

function getMcpServerHeaders(
	server: MCPServerConfig,
	turnContext: TurnContext,
	mcpAuthToken: string,
	tenantId: string | undefined,
) {
	const headers: Record<string, string> = {

View on GitHub (pinned to 5ac6606e81)

Solutions

  1. Verify the Azure AD app registration has the required API permissions (delegated or application) for the MCP server's scope, and that an admin granted consent.
  2. Check defaultToolingConfigurationProvider.getConfiguration().mcpPlatformAuthenticationScope matches a scope your app is authorized to request.
  3. Confirm the authorization object handed to attachMcpServerAuthorization is fresh and not expired/revoked before the turn.
  4. Reproduce GetAgenticUserToken in isolation with the same authorization, turnContext, and scope to see whether it returns null, and inspect the underlying AAD response.
  5. Check server.mcpServerName in MCPServerConfig resolves to a scope via resolveTokenScopeForServer that AAD actually recognizes.

Example fix

// before
const token = scope === sharedScope
  ? mcpAuthToken
  : await AgenticAuthenticationService.GetAgenticUserToken(
      authorization, 'agentic', turnContext, [scope],
    );
if (!token) {
  throw new Error(`Failed to obtain token for MCP server '${server.mcpServerName}'`);
}

// after — surface the upstream reason instead of a bare guard
let token: string | undefined;
try {
  token = scope === sharedScope
    ? mcpAuthToken
    : await AgenticAuthenticationService.GetAgenticUserToken(
        authorization, 'agentic', turnContext, [scope],
      );
} catch (e) {
  throw new Error(
    `Token request for MCP server '${server.mcpServerName}' (scope: ${scope}) failed: ${(e as Error).message}`,
  );
}
if (!token) {
  throw new Error(
    `No token returned for MCP server '${server.mcpServerName}' (scope: ${scope}) — verify AAD permissions and consent.`,
  );
}
Defensive patterns

Strategy: validation

Validate before calling

// Validate inputs before calling attachMcpServerAuthorization
function validateMcpAuthInputs(params: {
  authorization: Authorization;
  mcpAuthToken: string;
  scope: string;
  serverName: string;
}): string | null {
  const { authorization, mcpAuthToken, scope, serverName } = params;
  if (!scope || typeof scope !== 'string') {
    return `Cannot resolve a valid token scope for MCP server '${serverName}'`;
  }
  // If using the shared scope path, the cached token must already be present.
  const sharedScope =
    defaultToolingConfigurationProvider.getConfiguration().mcpPlatformAuthenticationScope;
  if (scope === sharedScope && !mcpAuthToken) {
    return `Shared-scope token is empty for MCP server '${serverName}'; check upstream auth.`;
  }
  // If using per-server scope, the authorization object must look usable.
  if (scope !== sharedScope && (!authorization || !('tenantId' in authorization))) {
    return `Authorization object is incomplete for per-server scope '${scope}' (server '${serverName}').`;
  }
  return null;
}

const problem = validateMcpAuthInputs({ authorization, mcpAuthToken, scope, serverName: server.mcpServerName });
if (problem) {
  // surface to the user / abort the turn before the upstream call
  throw new Error(problem);
}

Type guard

function isUsableAuthorization(a: unknown): a is Authorization {
  return !!a && typeof a === 'object' &&
    typeof (a as Authorization).tenantId === 'string' && (a as Authorization).tenantId.length > 0;
}

function hasNonEmptyToken(token: unknown): token is string {
  return typeof token === 'string' && token.trim().length > 0;
}

Try / catch

// Wrap the per-server token call so a null/throw is attributable.
let token: string | undefined;
try {
  token = scope === sharedScope
    ? mcpAuthToken
    : await AgenticAuthenticationService.GetAgenticUserToken(
        authorization, 'agentic', turnContext, [scope],
      );
} catch (e) {
  throw new Error(
    `GetAgenticUserToken threw for MCP server '${server.mcpServerName}' (scope ${scope}): ${(e as Error).message}`,
  );
}
if (!hasNonEmptyToken(token)) {
  throw new Error(
    `No token returned for MCP server '${server.mcpServerName}' (scope ${scope}); verify AAD permissions and admin consent.`,
  );
}

Prevention

When it happens

Trigger: Scope equals sharedScope but mcpAuthToken was passed empty/undefined; OR scope differs from sharedScope and GetAgenticUserToken(authorization, 'agentic', turnContext, [scope]) returned null/undefined. The latter happens when Azure AD refuses to mint a token for the requested scope: missing admin consent, service principal not provisioned for that resource, tenant mismatch between authorization and scope, or the underlying token service returning an empty string.

Common situations: Azure AD app registration missing the delegated/application permissions for the MCP server's resource scope; mcpPlatformAuthenticationScope in defaultToolingConfigurationProvider misconfigured or pointing at a scope the app cannot request; expired or revoked authorization passed into the turn; cross-tenant call where the service principal lives in a different tenant than the one in turnContext.

Related errors


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