Add optional mailbox (UPN/SMTP) routing on email/folder/thread tools via
users/{upn}/... and X-AnchorMailbox. New list-mailboxes probes primary,
OUTLOOK_SHARED_MAILBOXES seeds, cache, and candidates. Send supports
mailbox-rooted sendMail and onBehalfOf. MSAL requests Mail.*.Shared;
check-auth-status reports token scp gaps. Docs, env example, tests.
145 lines
5.5 KiB
JavaScript
145 lines
5.5 KiB
JavaScript
/**
|
|
* Authentication-related tools for the Outlook MCP server
|
|
*/
|
|
const config = require('../config');
|
|
const tokenManager = require('./token-manager');
|
|
const { analyzeTokenScopes, expectedSharedScopes } = require('../utils/mailbox');
|
|
|
|
async function handleAbout() {
|
|
const sharedLine = config.ENABLE_SHARED_MAILBOXES
|
|
? 'Shared mailbox support: enabled (optional mailbox param + list-mailboxes). Requires Mail.*.Shared scopes and re-auth after upgrade.'
|
|
: 'Shared mailbox support: disabled (OUTLOOK_ENABLE_SHARED_MAILBOXES=false).';
|
|
return {
|
|
content: [{
|
|
type: "text",
|
|
text: `Outlook Assistant MCP Server v${config.SERVER_VERSION}\n\nProvides access to Microsoft Outlook email, calendar, folders, and rules through Microsoft Graph API.\n${sharedLine}`
|
|
}]
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Initiates device code flow via MSAL. Returns the code and URL to the user
|
|
* immediately; MSAL polls internally until the user completes sign-in.
|
|
*/
|
|
async function handleAuthenticate(args) {
|
|
if (config.USE_TEST_MODE) {
|
|
tokenManager.createTestTokens();
|
|
return {
|
|
content: [{ type: "text", text: 'Successfully authenticated with Microsoft Graph API (test mode)' }]
|
|
};
|
|
}
|
|
|
|
let flowResult;
|
|
try {
|
|
flowResult = await tokenManager.initiateDeviceCodeFlow();
|
|
} catch (err) {
|
|
console.error('[authenticate] Failed to initiate device code flow:', err.message);
|
|
return {
|
|
content: [{ type: "text", text: `Failed to start authentication: ${err.message}` }]
|
|
};
|
|
}
|
|
|
|
const { deviceCodeInfo, tokenPromise } = flowResult;
|
|
const { userCode, verificationUri, expiresIn } = deviceCodeInfo;
|
|
const minutesRemaining = Math.floor(expiresIn / 60);
|
|
|
|
// MSAL polls internally — just log completion when it resolves
|
|
tokenPromise
|
|
.then(result => {
|
|
if (result) console.error('[authenticate] Sign-in completed — MSAL cached tokens.');
|
|
})
|
|
.catch(err => {
|
|
console.error(`[authenticate] Device code flow ended: ${err.message}`);
|
|
});
|
|
|
|
const scopeHint = config.ENABLE_SHARED_MAILBOXES
|
|
? `\nThis login requests shared-mailbox scopes: ${expectedSharedScopes().join(', ')}.\nAfter signing in, call check-auth-status and confirm those scopes are present.`
|
|
: '';
|
|
|
|
return {
|
|
content: [{
|
|
type: "text",
|
|
text: [
|
|
`To sign in to Microsoft, please:`,
|
|
``,
|
|
` 1. Open: ${verificationUri}`,
|
|
` 2. Enter code: ${userCode}`,
|
|
``,
|
|
`You have ${minutesRemaining} minutes to complete sign-in.`,
|
|
`After signing in, call check-auth-status to confirm.`,
|
|
scopeHint
|
|
].filter(Boolean).join('\n')
|
|
}]
|
|
};
|
|
}
|
|
|
|
async function handleCheckAuthStatus() {
|
|
console.error(`[CHECK-AUTH-STATUS] Checking for valid token...`);
|
|
try {
|
|
const token = await tokenManager.getAccessToken();
|
|
if (!token) {
|
|
console.error('[CHECK-AUTH-STATUS] No valid token found');
|
|
return { content: [{ type: "text", text: "Not authenticated" }] };
|
|
}
|
|
console.error('[CHECK-AUTH-STATUS] Valid token acquired');
|
|
|
|
const lines = ['Authenticated and ready'];
|
|
|
|
if (config.ENABLE_SHARED_MAILBOXES) {
|
|
const analysis = analyzeTokenScopes(token);
|
|
if (analysis.missing.length === 0) {
|
|
lines.push('Shared mailbox scopes: OK (' + analysis.present.join(', ') + ')');
|
|
} else if (analysis.present.length === 0) {
|
|
lines.push('Shared mailbox scopes: MISSING (' + analysis.missing.join(', ') + ')');
|
|
lines.push('Re-run authenticate (device code) after adding these delegated permissions in Entra and granting admin consent.');
|
|
} else {
|
|
lines.push('Shared mailbox scopes: PARTIAL');
|
|
lines.push(' present: ' + analysis.present.join(', '));
|
|
lines.push(' missing: ' + analysis.missing.join(', '));
|
|
lines.push('Re-run authenticate so the token picks up the missing scopes.');
|
|
}
|
|
if (analysis.scp) {
|
|
lines.push('Token scp: ' + analysis.scp);
|
|
}
|
|
} else {
|
|
lines.push('Shared mailbox feature disabled (OUTLOOK_ENABLE_SHARED_MAILBOXES=false).');
|
|
}
|
|
|
|
return { content: [{ type: "text", text: lines.join('\n') }] };
|
|
} catch (e) {
|
|
console.error('[CHECK-AUTH-STATUS] Error:', e.message);
|
|
return { content: [{ type: "text", text: "Not authenticated" }] };
|
|
}
|
|
}
|
|
|
|
const authTools = [
|
|
{
|
|
name: "about",
|
|
description: "Returns information about this Outlook Assistant server",
|
|
inputSchema: { type: "object", properties: {}, required: [] },
|
|
handler: handleAbout
|
|
},
|
|
{
|
|
name: "authenticate",
|
|
description: "Authenticate with Microsoft Graph API using device code flow. Returns a short code and URL. IMPORTANT: Show the user the code and URL — they must visit the URL on any device and enter the code to complete sign-in. After they sign in, call check-auth-status to confirm. After upgrading to shared-mailbox support, re-authenticate so Mail.*.Shared scopes appear on the token.",
|
|
inputSchema: {
|
|
type: "object",
|
|
properties: {
|
|
force: {
|
|
anyOf: [{ type: "boolean" }, { type: "string" }],
|
|
description: "Force re-authentication even if already authenticated"
|
|
}
|
|
},
|
|
required: []
|
|
},
|
|
handler: handleAuthenticate
|
|
},
|
|
{
|
|
name: "check-auth-status",
|
|
description: "Check the current authentication status with Microsoft Graph API, including whether shared-mailbox scopes are present on the access token",
|
|
inputSchema: { type: "object", properties: {}, required: [] },
|
|
handler: handleCheckAuthStatus
|
|
}
|
|
];
|
|
|
|
module.exports = { authTools, handleAbout, handleAuthenticate, handleCheckAuthStatus };
|