msoutlook-mcp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/README.md +134 -0
  2. package/dist/api/calendar.d.ts +90 -0
  3. package/dist/api/calendar.d.ts.map +1 -0
  4. package/dist/api/calendar.js +102 -0
  5. package/dist/api/calendar.js.map +1 -0
  6. package/dist/api/client.d.ts +14 -0
  7. package/dist/api/client.d.ts.map +1 -0
  8. package/dist/api/client.js +93 -0
  9. package/dist/api/client.js.map +1 -0
  10. package/dist/api/contacts.d.ts +43 -0
  11. package/dist/api/contacts.d.ts.map +1 -0
  12. package/dist/api/contacts.js +45 -0
  13. package/dist/api/contacts.js.map +1 -0
  14. package/dist/api/mail.d.ts +90 -0
  15. package/dist/api/mail.d.ts.map +1 -0
  16. package/dist/api/mail.js +148 -0
  17. package/dist/api/mail.js.map +1 -0
  18. package/dist/api/people.d.ts +28 -0
  19. package/dist/api/people.d.ts.map +1 -0
  20. package/dist/api/people.js +16 -0
  21. package/dist/api/people.js.map +1 -0
  22. package/dist/auth/browser-login.d.ts +26 -0
  23. package/dist/auth/browser-login.d.ts.map +1 -0
  24. package/dist/auth/browser-login.js +398 -0
  25. package/dist/auth/browser-login.js.map +1 -0
  26. package/dist/auth/index.d.ts +34 -0
  27. package/dist/auth/index.d.ts.map +1 -0
  28. package/dist/auth/index.js +89 -0
  29. package/dist/auth/index.js.map +1 -0
  30. package/dist/auth/session-store.d.ts +38 -0
  31. package/dist/auth/session-store.d.ts.map +1 -0
  32. package/dist/auth/session-store.js +163 -0
  33. package/dist/auth/session-store.js.map +1 -0
  34. package/dist/auth/token-extractor.d.ts +46 -0
  35. package/dist/auth/token-extractor.d.ts.map +1 -0
  36. package/dist/auth/token-extractor.js +126 -0
  37. package/dist/auth/token-extractor.js.map +1 -0
  38. package/dist/auth/token-refresh.d.ts +23 -0
  39. package/dist/auth/token-refresh.d.ts.map +1 -0
  40. package/dist/auth/token-refresh.js +133 -0
  41. package/dist/auth/token-refresh.js.map +1 -0
  42. package/dist/browser/cookie-import.d.ts +30 -0
  43. package/dist/browser/cookie-import.d.ts.map +1 -0
  44. package/dist/browser/cookie-import.js +446 -0
  45. package/dist/browser/cookie-import.js.map +1 -0
  46. package/dist/constants.d.ts +27 -0
  47. package/dist/constants.d.ts.map +1 -0
  48. package/dist/constants.js +39 -0
  49. package/dist/constants.js.map +1 -0
  50. package/dist/index.d.ts +11 -0
  51. package/dist/index.d.ts.map +1 -0
  52. package/dist/index.js +23 -0
  53. package/dist/index.js.map +1 -0
  54. package/dist/server.d.ts +6 -0
  55. package/dist/server.d.ts.map +1 -0
  56. package/dist/server.js +20 -0
  57. package/dist/server.js.map +1 -0
  58. package/dist/tools/auth-tools.d.ts +6 -0
  59. package/dist/tools/auth-tools.d.ts.map +1 -0
  60. package/dist/tools/auth-tools.js +116 -0
  61. package/dist/tools/auth-tools.js.map +1 -0
  62. package/dist/tools/calendar-tools.d.ts +6 -0
  63. package/dist/tools/calendar-tools.d.ts.map +1 -0
  64. package/dist/tools/calendar-tools.js +168 -0
  65. package/dist/tools/calendar-tools.js.map +1 -0
  66. package/dist/tools/contact-tools.d.ts +6 -0
  67. package/dist/tools/contact-tools.d.ts.map +1 -0
  68. package/dist/tools/contact-tools.js +105 -0
  69. package/dist/tools/contact-tools.js.map +1 -0
  70. package/dist/tools/mail-tools.d.ts +6 -0
  71. package/dist/tools/mail-tools.d.ts.map +1 -0
  72. package/dist/tools/mail-tools.js +196 -0
  73. package/dist/tools/mail-tools.js.map +1 -0
  74. package/dist/utils/http.d.ts +15 -0
  75. package/dist/utils/http.d.ts.map +1 -0
  76. package/dist/utils/http.js +48 -0
  77. package/dist/utils/http.js.map +1 -0
  78. package/dist/utils/logger.d.ts +10 -0
  79. package/dist/utils/logger.d.ts.map +1 -0
  80. package/dist/utils/logger.js +21 -0
  81. package/dist/utils/logger.js.map +1 -0
  82. package/package.json +53 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mail.d.ts","sourceRoot":"","sources":["../../src/api/mail.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Mail API — email CRUD, search, and folder operations.
3
+ */
4
+ import { owaGet, owaPost, owaPatch, owaDelete } from './client.js';
5
+ export async function listMessages(opts = {}) {
6
+ const folder = opts.folder ?? 'Inbox';
7
+ const params = {
8
+ '$top': String(opts.top ?? 20),
9
+ '$orderby': opts.orderBy ?? 'ReceivedDateTime desc',
10
+ '$select': (opts.select ?? [
11
+ 'Id', 'Subject', 'BodyPreview', 'From', 'ToRecipients', 'ReceivedDateTime',
12
+ 'IsRead', 'HasAttachments', 'Importance', 'Flag', 'ConversationId', 'WebLink',
13
+ ]).join(','),
14
+ };
15
+ if (opts.skip)
16
+ params['$skip'] = String(opts.skip);
17
+ if (opts.filter)
18
+ params['$filter'] = opts.filter;
19
+ if (opts.search)
20
+ params['$search'] = `"${opts.search}"`;
21
+ const res = await owaGet(`/MailFolders/${folder}/messages`, params);
22
+ return res.value;
23
+ }
24
+ // ─────────────────────────────────────────────────────────────────────────────
25
+ // Get message
26
+ // ─────────────────────────────────────────────────────────────────────────────
27
+ export async function getMessage(id, includeAttachments = false) {
28
+ const params = includeAttachments ? { '$expand': 'Attachments' } : undefined;
29
+ return owaGet(`/messages/${id}`, params);
30
+ }
31
+ export async function sendEmail(opts) {
32
+ const toRecipients = opts.to.map(addr => ({
33
+ EmailAddress: { Address: addr },
34
+ }));
35
+ const ccRecipients = opts.cc?.map(addr => ({ EmailAddress: { Address: addr } }));
36
+ const bccRecipients = opts.bcc?.map(addr => ({ EmailAddress: { Address: addr } }));
37
+ await owaPost('/sendmail', {
38
+ Message: {
39
+ Subject: opts.subject,
40
+ Body: { ContentType: opts.bodyType ?? 'Text', Content: opts.body },
41
+ ToRecipients: toRecipients,
42
+ ...(ccRecipients?.length ? { CcRecipients: ccRecipients } : {}),
43
+ ...(bccRecipients?.length ? { BccRecipients: bccRecipients } : {}),
44
+ Importance: opts.importance ?? 'Normal',
45
+ },
46
+ SaveToSentItems: opts.saveToSentItems !== false,
47
+ });
48
+ }
49
+ // ─────────────────────────────────────────────────────────────────────────────
50
+ // Create draft
51
+ // ─────────────────────────────────────────────────────────────────────────────
52
+ export async function createDraft(opts) {
53
+ const toRecipients = opts.to.map(addr => ({ EmailAddress: { Address: addr } }));
54
+ const ccRecipients = opts.cc?.map(addr => ({ EmailAddress: { Address: addr } }));
55
+ const bccRecipients = opts.bcc?.map(addr => ({ EmailAddress: { Address: addr } }));
56
+ return owaPost('/messages', {
57
+ Subject: opts.subject,
58
+ Body: { ContentType: opts.bodyType ?? 'Text', Content: opts.body },
59
+ ToRecipients: toRecipients,
60
+ ...(ccRecipients?.length ? { CcRecipients: ccRecipients } : {}),
61
+ ...(bccRecipients?.length ? { BccRecipients: bccRecipients } : {}),
62
+ Importance: opts.importance ?? 'Normal',
63
+ });
64
+ }
65
+ // ─────────────────────────────────────────────────────────────────────────────
66
+ // Reply
67
+ // ─────────────────────────────────────────────────────────────────────────────
68
+ export async function replyToMessage(id, body, replyAll = false) {
69
+ const action = replyAll ? 'replyall' : 'reply';
70
+ await owaPost(`/messages/${id}/${action}`, {
71
+ Comment: body,
72
+ });
73
+ }
74
+ // ─────────────────────────────────────────────────────────────────────────────
75
+ // Forward
76
+ // ─────────────────────────────────────────────────────────────────────────────
77
+ export async function forwardMessage(id, to, comment) {
78
+ await owaPost(`/messages/${id}/forward`, {
79
+ ToRecipients: to.map(addr => ({ EmailAddress: { Address: addr } })),
80
+ Comment: comment ?? '',
81
+ });
82
+ }
83
+ // ─────────────────────────────────────────────────────────────────────────────
84
+ // Mark read/unread
85
+ // ─────────────────────────────────────────────────────────────────────────────
86
+ export async function markMessageRead(id, isRead = true) {
87
+ await owaPatch(`/messages/${id}`, { IsRead: isRead });
88
+ }
89
+ // ─────────────────────────────────────────────────────────────────────────────
90
+ // Flag
91
+ // ─────────────────────────────────────────────────────────────────────────────
92
+ export async function flagMessage(id, status = 'Flagged') {
93
+ await owaPatch(`/messages/${id}`, { Flag: { FlagStatus: status } });
94
+ }
95
+ // ─────────────────────────────────────────────────────────────────────────────
96
+ // Move
97
+ // ─────────────────────────────────────────────────────────────────────────────
98
+ export async function moveMessage(id, destinationFolderId) {
99
+ return owaPost(`/messages/${id}/move`, {
100
+ DestinationId: destinationFolderId,
101
+ });
102
+ }
103
+ // ─────────────────────────────────────────────────────────────────────────────
104
+ // Delete
105
+ // ─────────────────────────────────────────────────────────────────────────────
106
+ export async function deleteMessage(id) {
107
+ await owaDelete(`/messages/${id}`);
108
+ }
109
+ // ─────────────────────────────────────────────────────────────────────────────
110
+ // Search
111
+ // ─────────────────────────────────────────────────────────────────────────────
112
+ export async function searchMessages(query, top = 20) {
113
+ return listMessages({
114
+ search: query,
115
+ top,
116
+ select: ['Id', 'Subject', 'BodyPreview', 'From', 'ReceivedDateTime', 'IsRead', 'WebLink'],
117
+ });
118
+ }
119
+ // ─────────────────────────────────────────────────────────────────────────────
120
+ // Folders
121
+ // ─────────────────────────────────────────────────────────────────────────────
122
+ export async function listFolders() {
123
+ const res = await owaGet('/mailfolders', {
124
+ '$top': '50',
125
+ '$select': 'Id,DisplayName,UnreadItemCount,TotalItemCount,ChildFolderCount',
126
+ });
127
+ return res.value;
128
+ }
129
+ export async function getFolder(idOrName) {
130
+ return owaGet(`/mailfolders/${idOrName}`);
131
+ }
132
+ // ─────────────────────────────────────────────────────────────────────────────
133
+ // Unread count
134
+ // ─────────────────────────────────────────────────────────────────────────────
135
+ export async function getUnreadMessages(top = 10) {
136
+ return listMessages({
137
+ filter: 'IsRead eq false',
138
+ top,
139
+ orderBy: 'ReceivedDateTime desc',
140
+ });
141
+ }
142
+ // ─────────────────────────────────────────────────────────────────────────────
143
+ // Send draft
144
+ // ─────────────────────────────────────────────────────────────────────────────
145
+ export async function sendDraft(draftId) {
146
+ await owaPost(`/messages/${draftId}/send`, {});
147
+ }
148
+ //# sourceMappingURL=mail.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mail.js","sourceRoot":"","sources":["../../src/api/mail.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * People and directory lookup API.
3
+ */
4
+ export interface Person {
5
+ Id?: string;
6
+ DisplayName: string;
7
+ GivenName?: string;
8
+ Surname?: string;
9
+ EmailAddresses?: Array<{
10
+ Name?: string;
11
+ Address: string;
12
+ }>;
13
+ Phones?: Array<{
14
+ Number: string;
15
+ Type: string;
16
+ }>;
17
+ JobTitle?: string;
18
+ Department?: string;
19
+ OfficeLocation?: string;
20
+ CompanyName?: string;
21
+ UserPrincipalName?: string;
22
+ ScoredEmailAddresses?: Array<{
23
+ Address: string;
24
+ RelevanceScore: number;
25
+ }>;
26
+ }
27
+ export declare function searchPeople(query: string, top?: number): Promise<Person[]>;
28
+ //# sourceMappingURL=people.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"people.d.ts","sourceRoot":"","sources":["../../src/api/people.ts"],"names":[],"mappings":"AAAA;;GAEG;AASH,MAAM,WAAW,MAAM;IACrB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC3D,MAAM,CAAC,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,oBAAoB,CAAC,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC3E;AAMD,wBAAsB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,SAAK,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAO7E"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * People and directory lookup API.
3
+ */
4
+ import { owaGet } from './client.js';
5
+ // ─────────────────────────────────────────────────────────────────────────────
6
+ // Search people
7
+ // ─────────────────────────────────────────────────────────────────────────────
8
+ export async function searchPeople(query, top = 10) {
9
+ const res = await owaGet('/people', {
10
+ '$search': `"${query}"`,
11
+ '$top': String(top),
12
+ '$select': 'DisplayName,GivenName,Surname,ScoredEmailAddresses,JobTitle,Department,UserPrincipalName',
13
+ });
14
+ return res.value;
15
+ }
16
+ //# sourceMappingURL=people.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"people.js","sourceRoot":"","sources":["../../src/api/people.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAsBrC,gFAAgF;AAChF,gBAAgB;AAChB,gFAAgF;AAEhF,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,KAAa,EAAE,GAAG,GAAG,EAAE;IACxD,MAAM,GAAG,GAAG,MAAM,MAAM,CAAwB,SAAS,EAAE;QACzD,SAAS,EAAE,IAAI,KAAK,GAAG;QACvB,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC;QACnB,SAAS,EAAE,0FAA0F;KACtG,CAAC,CAAC;IACH,OAAO,GAAG,CAAC,KAAK,CAAC;AACnB,CAAC"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Playwright-based browser login flow for Outlook Web App.
3
+ *
4
+ * Ported closely from msteams-mcp's browser auth approach:
5
+ * - Uses the system default browser (Edge on Windows, Chrome on macOS)
6
+ * - Imports Microsoft SSO cookies from the user's real browser profile,
7
+ * enabling silent authentication without typing credentials
8
+ * - Headless first, visible browser only as last resort
9
+ * - Handles stale SingletonLock files from crashed sessions
10
+ */
11
+ export declare function headlessLogin(): Promise<LoginResult | null>;
12
+ export declare function headedLogin(clearCookiesFirst?: boolean): Promise<LoginResult | null>;
13
+ export interface LoginResult {
14
+ upn: string;
15
+ /** How authentication was completed. */
16
+ method: 'token-cache' | 'headless-sso' | 'headed-browser';
17
+ }
18
+ /**
19
+ * Login to Outlook Web.
20
+ *
21
+ * @param forceNew - When true, clear the saved session and force a full
22
+ * re-authentication (mirrors msteams-mcp's `forceNew` option).
23
+ */
24
+ export declare function browserLogin(forceNew?: boolean): Promise<LoginResult | null>;
25
+ export declare function headlessTokenRefresh(): Promise<boolean>;
26
+ //# sourceMappingURL=browser-login.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser-login.d.ts","sourceRoot":"","sources":["../../src/auth/browser-login.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AA6QH,wBAAsB,aAAa,IAAI,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAkCjE;AA4BD,wBAAsB,WAAW,CAAC,iBAAiB,UAAQ,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAiExF;AAMD,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,wCAAwC;IACxC,MAAM,EAAE,aAAa,GAAG,cAAc,GAAG,gBAAgB,CAAC;CAC3D;AAMD;;;;;GAKG;AACH,wBAAsB,YAAY,CAAC,QAAQ,UAAQ,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAiBhF;AAMD,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,OAAO,CAAC,CAE7D"}
@@ -0,0 +1,398 @@
1
+ /**
2
+ * Playwright-based browser login flow for Outlook Web App.
3
+ *
4
+ * Ported closely from msteams-mcp's browser auth approach:
5
+ * - Uses the system default browser (Edge on Windows, Chrome on macOS)
6
+ * - Imports Microsoft SSO cookies from the user's real browser profile,
7
+ * enabling silent authentication without typing credentials
8
+ * - Headless first, visible browser only as last resort
9
+ * - Handles stale SingletonLock files from crashed sessions
10
+ */
11
+ import { chromium } from 'playwright';
12
+ import * as fs from 'node:fs';
13
+ import * as path from 'node:path';
14
+ import { execSync } from 'node:child_process';
15
+ import { logger } from '../utils/logger.js';
16
+ import { OWA_URL, LOGIN_TIMEOUT_MS } from '../constants.js';
17
+ import { getBrowserProfileDir, writeSessionState, writeTokenCache, clearSession, } from './session-store.js';
18
+ import { extractTokensFromLocalStorage, getOwaLocalStorage } from './token-extractor.js';
19
+ import { importMicrosoftCookies } from '../browser/cookie-import.js';
20
+ // ─────────────────────────────────────────────────────────────────────────────
21
+ // Browser channel
22
+ // ─────────────────────────────────────────────────────────────────────────────
23
+ /** Microsoft login domains — redirect to these means we're not authenticated. */
24
+ const LOGIN_DOMAINS = [
25
+ 'login.microsoftonline.com',
26
+ 'login.live.com',
27
+ 'login.microsoft.com',
28
+ ];
29
+ /**
30
+ * Predicate evaluated INSIDE the browser. Returns true once an OWA-scoped MSAL
31
+ * access token is present in localStorage. This is the real signal that auth
32
+ * has completed — we deliberately do NOT check the `olk-isauthed` flag, whose
33
+ * value is "1" (not "true") and varies, while this token check is what the
34
+ * token extractor itself relies on.
35
+ */
36
+ function owaAccessTokenPresent() {
37
+ return Object.keys(localStorage).some(k => k.includes('accesstoken') && k.includes('outlook.office.com'));
38
+ }
39
+ /**
40
+ * Determine the Playwright channel to use.
41
+ *
42
+ * Priority:
43
+ * 1. MSOUTLOOK_BROWSER env var (explicit override)
44
+ * 2. macOS system default browser (read from LaunchServices)
45
+ * 3. Platform fallback: Chrome on macOS/Linux, Edge on Windows
46
+ */
47
+ function getBrowserChannel() {
48
+ const override = process.env.MSOUTLOOK_BROWSER?.trim().toLowerCase();
49
+ if (override && override !== 'chromium' && override !== 'bundled')
50
+ return override;
51
+ if (process.platform === 'darwin') {
52
+ const detected = getMacOSDefaultBrowser();
53
+ if (detected) {
54
+ logger.debug(`macOS default browser detected: ${detected}`);
55
+ return detected;
56
+ }
57
+ return 'chrome'; // macOS fallback (same as msteams-mcp)
58
+ }
59
+ return process.platform === 'win32' ? 'msedge' : 'chrome';
60
+ }
61
+ /**
62
+ * Detect the macOS default browser by reading the LaunchServices plist.
63
+ * Returns a Playwright channel name or undefined if unrecognised.
64
+ */
65
+ function getMacOSDefaultBrowser() {
66
+ try {
67
+ const plistPath = path.join(process.env.HOME ?? '', 'Library/Preferences/com.apple.LaunchServices/com.apple.launchservices.secure.plist');
68
+ const json = execSync(`plutil -convert json -o - "${plistPath}"`, {
69
+ encoding: 'utf8',
70
+ timeout: 3000,
71
+ });
72
+ const data = JSON.parse(json);
73
+ const handlers = data.LSHandlers ?? [];
74
+ const httpsHandler = handlers.find(h => h.LSHandlerURLScheme === 'https');
75
+ const bundleId = (httpsHandler?.LSHandlerRoleAll ?? '').toLowerCase();
76
+ if (bundleId.includes('microsoft.edgemac') || bundleId.includes('edge'))
77
+ return 'msedge';
78
+ if (bundleId.includes('google.chrome') || bundleId.includes('chrome'))
79
+ return 'chrome';
80
+ // Firefox and Safari don't have Playwright channels — fall through to platform default
81
+ return undefined;
82
+ }
83
+ catch {
84
+ return undefined;
85
+ }
86
+ }
87
+ // ─────────────────────────────────────────────────────────────────────────────
88
+ // SingletonLock cleanup (same as msteams-mcp)
89
+ // ─────────────────────────────────────────────────────────────────────────────
90
+ function cleanupStaleSingletonLock(profileDir) {
91
+ const lockPath = path.join(profileDir, 'SingletonLock');
92
+ if (!fs.existsSync(lockPath))
93
+ return;
94
+ try {
95
+ const linkTarget = fs.readlinkSync(lockPath);
96
+ const match = linkTarget.match(/-(\d+)$/);
97
+ if (match) {
98
+ const pid = parseInt(match[1], 10);
99
+ try {
100
+ process.kill(pid, 0); // signal 0 = check if process exists
101
+ return; // still running — don't remove
102
+ }
103
+ catch {
104
+ // process is gone — remove stale lock
105
+ }
106
+ }
107
+ fs.unlinkSync(lockPath);
108
+ logger.debug('Removed stale SingletonLock');
109
+ }
110
+ catch {
111
+ // ignore
112
+ }
113
+ }
114
+ // ─────────────────────────────────────────────────────────────────────────────
115
+ // Shared helpers
116
+ // ─────────────────────────────────────────────────────────────────────────────
117
+ function hasSavedBrowserProfile() {
118
+ const dir = getBrowserProfileDir();
119
+ return fs.existsSync(dir) && fs.readdirSync(dir).length > 0;
120
+ }
121
+ async function extractAndCacheTokens(context) {
122
+ const state = await context.storageState();
123
+ writeSessionState(state);
124
+ const ls = getOwaLocalStorage(state);
125
+ if (!ls) {
126
+ logger.debug('Could not find Outlook origin in session state');
127
+ return null;
128
+ }
129
+ const tokens = extractTokensFromLocalStorage(ls);
130
+ if (!tokens) {
131
+ logger.debug('Could not extract OWA tokens from localStorage');
132
+ return null;
133
+ }
134
+ const cache = {
135
+ owaToken: tokens.owaToken,
136
+ owaTokenExpiry: tokens.owaTokenExpiry.getTime(),
137
+ graphToken: tokens.graphToken,
138
+ graphTokenExpiry: tokens.graphTokenExpiry?.getTime(),
139
+ refreshToken: tokens.refreshToken,
140
+ tenantId: tokens.tenantId,
141
+ upn: tokens.upn,
142
+ extractedAt: Date.now(),
143
+ };
144
+ writeTokenCache(cache);
145
+ return tokens.upn ?? 'unknown';
146
+ }
147
+ /**
148
+ * Launch a persistent context, clean up stale locks first.
149
+ * Falls back to removing the lock and retrying once if a lock conflict occurs.
150
+ */
151
+ async function launchContext(profileDir, headless, channel) {
152
+ cleanupStaleSingletonLock(profileDir);
153
+ const launch = () => chromium.launchPersistentContext(profileDir, {
154
+ headless,
155
+ channel,
156
+ viewport: { width: 1280, height: 800 },
157
+ acceptDownloads: false,
158
+ });
159
+ try {
160
+ return await launch();
161
+ }
162
+ catch (err) {
163
+ const msg = err instanceof Error ? err.message : String(err);
164
+ if (msg.includes('ProcessSingleton') || msg.includes('SingletonLock')) {
165
+ logger.debug('Profile lock conflict — removing lock and retrying');
166
+ const lockPath = path.join(profileDir, 'SingletonLock');
167
+ try {
168
+ fs.unlinkSync(lockPath);
169
+ }
170
+ catch { /* ignore */ }
171
+ return launch();
172
+ }
173
+ throw err;
174
+ }
175
+ }
176
+ /**
177
+ * Wait for OWA to authenticate AND for MSAL tokens to be present in localStorage.
178
+ * Uses a redirect-detection approach (like msteams-mcp) for fast failure on
179
+ * unauthenticated sessions, so we don't sit at a blank login page for 30s+.
180
+ *
181
+ * @returns true if authenticated with tokens, false if redirected to login
182
+ */
183
+ async function waitForOwaAuth(context, timeoutMs) {
184
+ const page = context.pages()[0] ?? await context.newPage();
185
+ // Detect redirect to Microsoft login — fast signal that the session is invalid
186
+ let redirectedToLogin = false;
187
+ const onNavigation = (frame) => {
188
+ if (frame === page.mainFrame() && LOGIN_DOMAINS.some(d => frame.url().includes(d))) {
189
+ redirectedToLogin = true;
190
+ }
191
+ };
192
+ page.on('framenavigated', onNavigation);
193
+ try {
194
+ await page.goto(OWA_URL, { waitUntil: 'domcontentloaded' });
195
+ // Give MSAL 5 s to either redirect to login or silently authenticate.
196
+ // If redirected to login page, return false immediately.
197
+ const deadline = Date.now() + Math.min(timeoutMs, 5_000);
198
+ while (Date.now() < deadline) {
199
+ if (redirectedToLogin)
200
+ return false;
201
+ await page.waitForTimeout(100);
202
+ }
203
+ if (redirectedToLogin)
204
+ return false;
205
+ // No login redirect — session appears valid. Now wait for the MSAL token.
206
+ await page.waitForFunction(owaAccessTokenPresent, { timeout: timeoutMs });
207
+ return true;
208
+ }
209
+ finally {
210
+ page.off('framenavigated', onNavigation);
211
+ }
212
+ }
213
+ // ─────────────────────────────────────────────────────────────────────────────
214
+ // MSAL token polling (mirrors msteams-mcp waitForTokenRefresh)
215
+ // ─────────────────────────────────────────────────────────────────────────────
216
+ /**
217
+ * When the browser session is valid (no login redirect) but MSAL tokens are
218
+ * still being acquired, poll localStorage in-browser until tokens appear.
219
+ * This is the case where session cookies are alive but the hour-long access
220
+ * token has expired — OWA JS silently acquires new ones.
221
+ *
222
+ * Polls in-browser to avoid deserialising the full session state on every
223
+ * check. Returns the UPN once tokens are available.
224
+ */
225
+ async function waitForMsalTokens(context, timeoutMs = 20_000) {
226
+ const page = context.pages()[0];
227
+ if (!page)
228
+ return false;
229
+ logger.debug('Waiting for MSAL to acquire tokens...');
230
+ const interval = 1_000;
231
+ const deadline = Date.now() + timeoutMs;
232
+ while (Date.now() < deadline) {
233
+ const ready = await page.evaluate(owaAccessTokenPresent).catch(() => false);
234
+ if (ready) {
235
+ logger.debug(`MSAL tokens appeared after ${Math.round((Date.now() - (deadline - timeoutMs)) / 1000)}s`);
236
+ return true;
237
+ }
238
+ await page.waitForTimeout(interval);
239
+ }
240
+ logger.debug('MSAL token wait timed out');
241
+ return false;
242
+ }
243
+ // ─────────────────────────────────────────────────────────────────────────────
244
+ // Headless login (primary)
245
+ // ─────────────────────────────────────────────────────────────────────────────
246
+ export async function headlessLogin() {
247
+ logger.debug('Attempting headless (silent) login...');
248
+ const profileDir = getBrowserProfileDir();
249
+ const channel = getBrowserChannel();
250
+ let context = null;
251
+ try {
252
+ context = await launchContext(profileDir, true, channel);
253
+ const authenticated = await waitForOwaAuth(context, 5_000);
254
+ if (!authenticated) {
255
+ logger.debug('Headless: session invalid or expired (login redirect detected)');
256
+ return null;
257
+ }
258
+ // Session is valid. Check if MSAL tokens are already there, or wait
259
+ // up to 20s for OWA JS to silently acquire them (mirrors msteams-mcp).
260
+ const tokensReady = await waitForMsalTokens(context, 20_000);
261
+ if (!tokensReady) {
262
+ logger.debug('Headless: MSAL tokens did not appear — falling back to headed login');
263
+ return null;
264
+ }
265
+ const upn = await extractAndCacheTokens(context);
266
+ if (!upn)
267
+ return null;
268
+ logger.info(`Headless login succeeded (${upn})`);
269
+ return { upn, method: 'headless-sso' };
270
+ }
271
+ catch (err) {
272
+ logger.debug('Headless login failed', err instanceof Error ? err.message : String(err));
273
+ return null;
274
+ }
275
+ finally {
276
+ await context?.close().catch(() => { });
277
+ }
278
+ }
279
+ // ─────────────────────────────────────────────────────────────────────────────
280
+ // Headed login (fallback)
281
+ // ─────────────────────────────────────────────────────────────────────────────
282
+ /** Inject a visible progress overlay into the page (mirrors msteams-mcp). Failures are silently ignored — purely cosmetic. */
283
+ async function showOverlay(page, phase) {
284
+ const phases = {
285
+ pending: { icon: '⋯', title: "You're signed in!", detail: 'Setting up your Outlook connection...', bg: '#5b5fc7' },
286
+ saving: { icon: '⋯', title: 'Saving your session...', detail: "So you won't need to log in again.", bg: '#5b5fc7' },
287
+ done: { icon: '✓', title: 'All done!', detail: 'This window will close automatically.', bg: '#107c10' },
288
+ error: { icon: '✕', title: 'Something went wrong', detail: 'Please try again.', bg: '#c42b1c' },
289
+ };
290
+ const p = phases[phase];
291
+ try {
292
+ await page.evaluate(({ icon, title, detail, bg }) => {
293
+ const existing = document.getElementById('msoutlook-mcp-overlay');
294
+ if (existing)
295
+ existing.remove();
296
+ const overlay = document.createElement('div');
297
+ overlay.id = 'msoutlook-mcp-overlay';
298
+ Object.assign(overlay.style, { position: 'fixed', inset: '0', background: 'rgba(0,0,0,.65)', display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: '999999', fontFamily: "'Segoe UI',system-ui,sans-serif" });
299
+ overlay.innerHTML = `<div style="background:white;border-radius:12px;padding:40px 48px;max-width:420px;text-align:center;box-shadow:0 8px 32px rgba(0,0,0,.3)"><div style="width:64px;height:64px;border-radius:50%;background:${bg};color:white;font-size:32px;display:flex;align-items:center;justify-content:center;margin:0 auto 24px">${icon}</div><h2 style="margin:0 0 12px;font-size:20px;font-weight:600;color:#242424">${title}</h2><p style="margin:0;font-size:14px;color:#616161;line-height:1.5">${detail}</p></div>`;
300
+ document.body.appendChild(overlay);
301
+ }, p);
302
+ }
303
+ catch { /* cosmetic — ignore */ }
304
+ }
305
+ export async function headedLogin(clearCookiesFirst = false) {
306
+ logger.info('Opening browser for interactive login...');
307
+ const profileDir = getBrowserProfileDir();
308
+ const channel = getBrowserChannel();
309
+ let context = null;
310
+ let browserClosed = false;
311
+ try {
312
+ context = await launchContext(profileDir, false, channel);
313
+ context.on('close', () => { browserClosed = true; });
314
+ // For force_new: clear browser cookies before importing fresh ones
315
+ if (clearCookiesFirst) {
316
+ await context.clearCookies();
317
+ logger.debug('Browser cookies cleared for force_new login');
318
+ }
319
+ // Import Microsoft SSO cookies from the user's real browser — enables
320
+ // instant silent sign-in without typing credentials (same as msteams-mcp).
321
+ await importMicrosoftCookies(context, channel);
322
+ const authenticated = await waitForOwaAuth(context, LOGIN_TIMEOUT_MS);
323
+ const page = context.pages()[0];
324
+ if (!authenticated) {
325
+ // Login redirect — user needs to sign in manually; wait for them
326
+ logger.info('Waiting for you to complete sign-in in the browser...');
327
+ if (page) {
328
+ await page.waitForFunction(owaAccessTokenPresent, { timeout: LOGIN_TIMEOUT_MS });
329
+ }
330
+ }
331
+ // Session authenticated — show progress overlay while we save
332
+ if (page) {
333
+ await showOverlay(page, 'pending');
334
+ await page.waitForTimeout(800);
335
+ await showOverlay(page, 'saving');
336
+ }
337
+ const upn = await extractAndCacheTokens(context);
338
+ if (!upn) {
339
+ if (page)
340
+ await showOverlay(page, 'error');
341
+ return null;
342
+ }
343
+ if (page) {
344
+ await showOverlay(page, 'done');
345
+ await page.waitForTimeout(1200); // let user read "All done!"
346
+ }
347
+ logger.info(`Headed login succeeded (${upn}). Browser closing.`);
348
+ return { upn, method: 'headed-browser' };
349
+ }
350
+ catch (err) {
351
+ if (browserClosed) {
352
+ logger.error('Login aborted — browser was closed before authentication completed.');
353
+ }
354
+ else {
355
+ logger.error('Headed login failed', err instanceof Error ? err.message : String(err));
356
+ }
357
+ return null;
358
+ }
359
+ finally {
360
+ if (context && !browserClosed) {
361
+ await context.close().catch(() => { });
362
+ }
363
+ }
364
+ }
365
+ // ─────────────────────────────────────────────────────────────────────────────
366
+ // Main entry point
367
+ // ─────────────────────────────────────────────────────────────────────────────
368
+ /**
369
+ * Login to Outlook Web.
370
+ *
371
+ * @param forceNew - When true, clear the saved session and force a full
372
+ * re-authentication (mirrors msteams-mcp's `forceNew` option).
373
+ */
374
+ export async function browserLogin(forceNew = false) {
375
+ if (forceNew) {
376
+ clearSession();
377
+ logger.info('Forced re-login — cleared previous session and token cache.');
378
+ }
379
+ if (!forceNew && hasSavedBrowserProfile()) {
380
+ const result = await headlessLogin();
381
+ if (result)
382
+ return result;
383
+ logger.info('Headless login failed — falling back to visible browser...');
384
+ }
385
+ else if (!forceNew) {
386
+ logger.info('No saved browser profile — opening visible browser for first-time setup...');
387
+ }
388
+ // Pass clearCookiesFirst=true for force_new so the browser profile's cookies
389
+ // are wiped before importing fresh SSO cookies (mirrors msteams-mcp forceNewLogin).
390
+ return headedLogin(forceNew);
391
+ }
392
+ // ─────────────────────────────────────────────────────────────────────────────
393
+ // Token refresh alias
394
+ // ─────────────────────────────────────────────────────────────────────────────
395
+ export async function headlessTokenRefresh() {
396
+ return (await headlessLogin()) !== null;
397
+ }
398
+ //# sourceMappingURL=browser-login.js.map