UNPKG

bc-webclient-mcp

Version:

Model Context Protocol (MCP) server for Microsoft Dynamics 365 Business Central via WebUI protocol. Enables AI assistants to interact with BC through the web client protocol, supporting Card, List, and Document pages with full line item support and server

123 lines 4.31 kB
import * as msal from '@azure/msal-node'; import { logger } from './core/logger.js'; /** * Azure AD Authentication using Device Code Flow * This is ideal for CLI/headless scenarios */ export class BCAuth { msalClient; config; constructor(config) { this.config = config; const msalConfig = { auth: { clientId: config.azureClientId, authority: config.azureAuthority, }, system: { loggerOptions: { loggerCallback(loglevel, message, containsPii) { if (!containsPii) { logger.info(message); } }, piiLoggingEnabled: false, logLevel: msal.LogLevel.Warning, } } }; this.msalClient = new msal.PublicClientApplication(msalConfig); } /** * Authenticate using Device Code Flow * User will be prompted to visit a URL and enter a code */ async authenticateDeviceCode() { const deviceCodeRequest = { deviceCodeCallback: (response) => { logger.info('\n========================================'); logger.info('AUTHENTICATION REQUIRED'); logger.info('========================================'); logger.info(response.message); logger.info('========================================\n'); }, scopes: this.getScopes() }; try { const response = await this.msalClient.acquireTokenByDeviceCode(deviceCodeRequest); if (!response?.accessToken) { throw new Error('No access token received'); } logger.info('Authentication successful'); logger.info(` User: ${response.account?.username || 'Unknown'}`); logger.info(` Token expires: ${response.expiresOn?.toLocaleString() || 'Unknown'}\n`); return response.accessToken; } catch (error) { if (error instanceof Error) { throw new Error(`Authentication failed: ${error.message}`); } throw error; } } /** * Authenticate using Username/Password (if allowed by tenant) * Note: This requires special Azure AD configuration and is less secure */ async authenticateUsernamePassword(username, password) { const usernamePasswordRequest = { scopes: this.getScopes(), username, password, }; try { const response = await this.msalClient.acquireTokenByUsernamePassword(usernamePasswordRequest); if (!response?.accessToken) { throw new Error('No access token received'); } logger.info('Authentication successful'); return response.accessToken; } catch (error) { if (error instanceof Error) { throw new Error(`Authentication failed: ${error.message}`); } throw error; } } /** * Get required OAuth scopes for Business Central */ getScopes() { // Standard BC API scopes // For BC Online, the resource is typically: // https://api.businesscentral.dynamics.com/.default // or specific scopes like: // https://api.businesscentral.dynamics.com/Financials.ReadWrite.All return [ 'https://api.businesscentral.dynamics.com/.default' ]; } /** * Try silent token acquisition (if cached) */ async acquireTokenSilent() { try { const cache = this.msalClient.getTokenCache(); const accounts = await cache.getAllAccounts(); if (accounts.length === 0) { return null; } const silentRequest = { account: accounts[0], scopes: this.getScopes(), }; const response = await this.msalClient.acquireTokenSilent(silentRequest); return response?.accessToken || null; } catch (error) { return null; } } } //# sourceMappingURL=auth.js.map