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
- 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.
- Check defaultToolingConfigurationProvider.getConfiguration().mcpPlatformAuthenticationScope matches a scope your app is authorized to request.
- Confirm the authorization object handed to attachMcpServerAuthorization is fresh and not expired/revoked before the turn.
- Reproduce GetAgenticUserToken in isolation with the same authorization, turnContext, and scope to see whether it returns null, and inspect the underlying AAD response.
- 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
- Keep a preflight check that confirms the Azure AD app registration has admin-consented permissions for every MCP server scope before deploying the workflow.
- Assert the authorization object is non-expired at turn start and refresh proactively rather than relying on the empty-token guard.
- Log the resolved scope per server during development to catch scope-resolution bugs early.
- Pin mcpPlatformAuthenticationScope to a value your app is definitely authorized to request.
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
- Missing gateway token in deeplink. Connect from n8n using th
- Failed to retrieve access token
- Failed to retrieve OAuth2 access token
- Failed to read MCP servers from endpoint: ${response.status}
- No suspended run found for runId: ${this.runId}
AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12).
Data as JSON: /api/errors/416a36c2fcff8f90.
Report an issue: GitHub.