{"record":{"id":"416a36c2fcff8f90","repo":"n8n-io/n8n","slug":"failed-to-obtain-token-for-mcp-server-server-mc","errorCode":null,"errorMessage":"Failed to obtain token for MCP server '${server.mcpServerName}'","messagePattern":"Failed to obtain token for MCP server '(.+?)'","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"critical","filePath":"packages/@n8n/nodes-langchain/nodes/vendors/Microsoft/microsoft-utils.ts","lineNumber":286,"sourceCode":") {\n\tconst sharedScope =\n\t\tdefaultToolingConfigurationProvider.getConfiguration().mcpPlatformAuthenticationScope;\n\tconst scope = resolveTokenScopeForServer(server, sharedScope);\n\n\tif (scope === sharedScope && hasAuthorizationHeader(server.headers ?? {})) return server;\n\n\tconst token =\n\t\tscope === sharedScope\n\t\t\t? mcpAuthToken\n\t\t\t: await AgenticAuthenticationService.GetAgenticUserToken(\n\t\t\t\t\tauthorization,\n\t\t\t\t\t'agentic',\n\t\t\t\t\tturnContext,\n\t\t\t\t\t[scope],\n\t\t\t\t);\n\n\tif (!token) {\n\t\tthrow new Error(`Failed to obtain token for MCP server '${server.mcpServerName}'`);\n\t}\n\n\treturn {\n\t\t...server,\n\t\theaders: {\n\t\t\t...server.headers,\n\t\t\tAuthorization: `Bearer ${token}`,\n\t\t},\n\t};\n}\n\nfunction getMcpServerHeaders(\n\tserver: MCPServerConfig,\n\tturnContext: TurnContext,\n\tmcpAuthToken: string,\n\ttenantId: string | undefined,\n) {\n\tconst headers: Record<string, string> = {","sourceCodeStart":268,"sourceCodeEnd":304,"githubUrl":"https://github.com/n8n-io/n8n/blob/5ac6606e81f67bb9534255570cd4e86fd8101eee/packages/@n8n/nodes-langchain/nodes/vendors/Microsoft/microsoft-utils.ts#L268-L304","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nconst token = scope === sharedScope\n  ? mcpAuthToken\n  : await AgenticAuthenticationService.GetAgenticUserToken(\n      authorization, 'agentic', turnContext, [scope],\n    );\nif (!token) {\n  throw new Error(`Failed to obtain token for MCP server '${server.mcpServerName}'`);\n}\n\n// after — surface the upstream reason instead of a bare guard\nlet token: string | undefined;\ntry {\n  token = scope === sharedScope\n    ? mcpAuthToken\n    : await AgenticAuthenticationService.GetAgenticUserToken(\n        authorization, 'agentic', turnContext, [scope],\n      );\n} catch (e) {\n  throw new Error(\n    `Token request for MCP server '${server.mcpServerName}' (scope: ${scope}) failed: ${(e as Error).message}`,\n  );\n}\nif (!token) {\n  throw new Error(\n    `No token returned for MCP server '${server.mcpServerName}' (scope: ${scope}) — verify AAD permissions and consent.`,\n  );\n}","handlingStrategy":"validation","validationCode":"// Validate inputs before calling attachMcpServerAuthorization\nfunction validateMcpAuthInputs(params: {\n  authorization: Authorization;\n  mcpAuthToken: string;\n  scope: string;\n  serverName: string;\n}): string | null {\n  const { authorization, mcpAuthToken, scope, serverName } = params;\n  if (!scope || typeof scope !== 'string') {\n    return `Cannot resolve a valid token scope for MCP server '${serverName}'`;\n  }\n  // If using the shared scope path, the cached token must already be present.\n  const sharedScope =\n    defaultToolingConfigurationProvider.getConfiguration().mcpPlatformAuthenticationScope;\n  if (scope === sharedScope && !mcpAuthToken) {\n    return `Shared-scope token is empty for MCP server '${serverName}'; check upstream auth.`;\n  }\n  // If using per-server scope, the authorization object must look usable.\n  if (scope !== sharedScope && (!authorization || !('tenantId' in authorization))) {\n    return `Authorization object is incomplete for per-server scope '${scope}' (server '${serverName}').`;\n  }\n  return null;\n}\n\nconst problem = validateMcpAuthInputs({ authorization, mcpAuthToken, scope, serverName: server.mcpServerName });\nif (problem) {\n  // surface to the user / abort the turn before the upstream call\n  throw new Error(problem);\n}","typeGuard":"function isUsableAuthorization(a: unknown): a is Authorization {\n  return !!a && typeof a === 'object' &&\n    typeof (a as Authorization).tenantId === 'string' && (a as Authorization).tenantId.length > 0;\n}\n\nfunction hasNonEmptyToken(token: unknown): token is string {\n  return typeof token === 'string' && token.trim().length > 0;\n}","tryCatchPattern":"// Wrap the per-server token call so a null/throw is attributable.\nlet token: string | undefined;\ntry {\n  token = scope === sharedScope\n    ? mcpAuthToken\n    : await AgenticAuthenticationService.GetAgenticUserToken(\n        authorization, 'agentic', turnContext, [scope],\n      );\n} catch (e) {\n  throw new Error(\n    `GetAgenticUserToken threw for MCP server '${server.mcpServerName}' (scope ${scope}): ${(e as Error).message}`,\n  );\n}\nif (!hasNonEmptyToken(token)) {\n  throw new Error(\n    `No token returned for MCP server '${server.mcpServerName}' (scope ${scope}); verify AAD permissions and admin consent.`,\n  );\n}","preventionTips":["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."],"tags":["mcp","authentication","azure-ad","microsoft-365","token"],"backgroundTag":null,"analyzedSha":"5ac6606e81f67bb9534255570cd4e86fd8101eee","analyzedAt":"2026-08-12T05:26:35.080Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}