teams-mcp-server
Version:
Microsoft Teams MCP server with direct messaging support
137 lines (136 loc) • 6.16 kB
JavaScript
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)
}]
};
}
}
};
}