@softeria/ms-365-mcp-server
Version:
A Model Context Protocol (MCP) server for interacting with Microsoft 365 and Office services through the Graph API
159 lines (158 loc) • 5.51 kB
JavaScript
import "dotenv/config";
import { parseArgs } from "./cli.js";
import logger from "./logger.js";
import AuthManager, { buildAllowedScopeDiagnostics, resolveAuthScopes } from "./auth.js";
import MicrosoftGraphServer from "./server.js";
import {
getExpectedAccountInertWarning,
shouldAssertExpectedAccountAtStartup,
shouldUseLocalAuthStorage
} from "./startup-pinning.js";
import { createTokenCacheStorage } from "./token-cache-storage.js";
import { dumpError, getActiveResources } from "./crash-logging.js";
import { version } from "./version.js";
process.on("unhandledRejection", (reason) => {
const dump = {
kind: "unhandledRejection",
reason: dumpError(reason),
activeResources: getActiveResources()
};
console.error("[ms365-mcp] unhandledRejection", JSON.stringify(dump));
logger.error("unhandledRejection", dump);
});
process.on("uncaughtException", (err, origin) => {
const dump = {
kind: "uncaughtException",
origin,
error: dumpError(err),
activeResources: getActiveResources()
};
console.error("[ms365-mcp] uncaughtException", JSON.stringify(dump));
logger.error("uncaughtException", dump);
});
async function main() {
try {
const args = parseArgs();
const includeWorkScopes = args.orgMode || false;
if (includeWorkScopes) {
logger.info("Organization mode enabled - including work account scopes");
}
const readOnly = args.readOnly || false;
const effectiveScopes = resolveAuthScopes(args);
if (args.listPermissions) {
const diagnostics = buildAllowedScopeDiagnostics(args);
const mode = includeWorkScopes ? "org" : "personal";
const filter = args.enabledTools ? args.enabledTools : void 0;
if (diagnostics.disabledTools.length > 0) {
console.error(
`Warning: allowed scopes disabled ${diagnostics.disabledTools.length} tools. Missing scopes: ${diagnostics.missingAllowedScopesForTools.join(", ")}`
);
}
console.log(
JSON.stringify(
{
mode,
readOnly,
filter,
...diagnostics
},
null,
2
)
);
process.exit(0);
}
const useLocalAuthStorage = shouldUseLocalAuthStorage(args);
const storage = await createTokenCacheStorage({
allowCommandStorage: useLocalAuthStorage,
logProvider: useLocalAuthStorage
});
const authManager = await AuthManager.create(
effectiveScopes,
{
expectedUsername: args.expectedUsername,
expectedHomeAccountId: args.expectedHomeAccountId
},
{ storage }
);
if (useLocalAuthStorage) {
await authManager.loadTokenCache();
}
if (args.authBrowser) {
authManager.setUseInteractiveAuth(true);
logger.info("Browser-based interactive auth enabled");
}
const expectedAccountWarning = getExpectedAccountInertWarning(args, authManager);
if (expectedAccountWarning) {
logger.warn(expectedAccountWarning);
console.error(expectedAccountWarning);
}
if (args.login) {
if (args.authBrowser) {
await authManager.acquireTokenInteractive();
} else {
await authManager.acquireTokenByDeviceCode();
}
logger.info("Login completed, testing connection with Graph API...");
const result = await authManager.testLogin();
console.log(JSON.stringify(result));
process.exit(0);
}
if (args.verifyLogin) {
logger.info("Verifying login...");
const result = await authManager.testLogin();
console.log(JSON.stringify(result));
process.exit(0);
}
if (args.logout) {
await authManager.logout();
console.log(JSON.stringify({ message: "Logged out successfully" }));
process.exit(0);
}
if (args.listAccounts) {
const accounts = await authManager.listAccounts();
const selectedAccountId = authManager.getSelectedAccountId();
const result = accounts.map((account) => ({
id: account.homeAccountId,
username: account.username,
name: account.name,
selected: account.homeAccountId === selectedAccountId
}));
console.log(JSON.stringify({ accounts: result }));
process.exit(0);
}
if (args.selectAccount) {
const success = await authManager.selectAccount(args.selectAccount);
if (success) {
console.log(JSON.stringify({ message: `Selected account: ${args.selectAccount}` }));
} else {
console.log(JSON.stringify({ error: `Account not found: ${args.selectAccount}` }));
process.exit(1);
}
process.exit(0);
}
if (args.removeAccount) {
const success = await authManager.removeAccount(args.removeAccount);
if (success) {
console.log(JSON.stringify({ message: `Removed account: ${args.removeAccount}` }));
} else {
console.log(JSON.stringify({ error: `Account not found: ${args.removeAccount}` }));
process.exit(1);
}
process.exit(0);
}
if (shouldAssertExpectedAccountAtStartup(args, authManager)) {
await authManager.assertExpectedAccountAvailable();
}
const server = new MicrosoftGraphServer(authManager, args);
await server.initialize(version);
await server.start();
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
logger.error(`Startup error: ${message}`);
console.error(message);
process.exit(1);
}
}
main();