UNPKG

teams-mcp-server

Version:

Microsoft Teams MCP server with direct messaging support

137 lines (136 loc) 6.16 kB
import { debugLog } from '../utils/logger.js'; import { PublicClientApplication, ConfidentialClientApplication } from '@azure/msal-node'; import { authFlows } from './isAuthenticated.js'; export function createAuthorizeTool() { return { name: 'authorize', description: 'Exchange authorization code for tokens.', inputSchema: { type: 'object', properties: { code: { type: 'string', description: 'Authorization code from OAuth callback' }, callback_url: { type: 'string', description: 'Callback URL used in the authorization request' }, callback_state: { type: 'object', description: 'State data from OAuth callback (optional)' } }, required: ['code', 'callback_url'] }, handler: async (args) => { try { const CLIENT_ID = process.env.AZURE_CLIENT_ID; const TENANT_ID = process.env.AZURE_TENANT_ID; const CLIENT_SECRET = process.env.AZURE_CLIENT_SECRET; if (!CLIENT_ID || !TENANT_ID) { return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: 'Missing required environment variables: AZURE_CLIENT_ID, AZURE_TENANT_ID' }, null, 2) }] }; } const msalConfig = { auth: { clientId: CLIENT_ID, authority: `https://login.microsoftonline.com/${TENANT_ID}`, clientSecret: CLIENT_SECRET } }; const msalClient = CLIENT_SECRET ? new ConfidentialClientApplication(msalConfig) : new PublicClientApplication(msalConfig); const scopes = ['Chat.ReadWrite', 'ChatMessage.Send', 'User.Read', 'offline_access']; // Extract state from callback to get code verifier let codeVerifier; // The state parameter should be passed from the OAuth callback // We need to extract it from the callback URL or state const urlParams = new URLSearchParams(args.callback_url.split('?')[1] || ''); const stateParam = urlParams.get('state') || args.callback_state?.state; // Parse the state to extract the ID let stateId; if (stateParam) { try { const stateData = JSON.parse(stateParam); stateId = stateData.id; } catch (e) { // Fallback for old format (direct state string) stateId = stateParam; } } if (stateId && authFlows.has(stateId)) { codeVerifier = authFlows.get(stateId); authFlows.delete(stateId); // Clean up after use } const tokenRequest = { code: args.code, scopes, redirectUri: args.callback_url.split('?')[0], // Remove query params codeVerifier: CLIENT_SECRET ? undefined : codeVerifier }; debugLog('Exchanging authorization code for tokens'); try { const result = await msalClient.acquireTokenByCode(tokenRequest); return { content: [{ type: 'text', text: JSON.stringify({ success: true, tokens: { access_token: result.accessToken, refresh_token: null // Azure AD handles refresh tokens internally } }, null, 2) }] }; } catch (error) { debugLog(`Failed to exchange authorization code: ${error}`); // Check for specific Azure AD errors if (error.message?.includes('AADSTS7000218') || error.message?.includes('client_secret')) { return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: 'Azure app configuration error: Public client flows must be enabled in Azure AD app registration' }, null, 2) }] }; } return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: error.message || 'Failed to exchange authorization code' }, null, 2) }] }; } } catch (error) { debugLog(`Authorization failed: ${error}`); return { content: [{ type: 'text', text: JSON.stringify({ success: false, error: error.message }, null, 2) }] }; } } }; }