msteams-mcp 0.2.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 (80) hide show
  1. package/README.md +229 -0
  2. package/dist/__fixtures__/api-responses.d.ts +228 -0
  3. package/dist/__fixtures__/api-responses.js +217 -0
  4. package/dist/api/chatsvc-api.d.ts +171 -0
  5. package/dist/api/chatsvc-api.js +459 -0
  6. package/dist/api/csa-api.d.ts +44 -0
  7. package/dist/api/csa-api.js +148 -0
  8. package/dist/api/index.d.ts +6 -0
  9. package/dist/api/index.js +6 -0
  10. package/dist/api/substrate-api.d.ts +50 -0
  11. package/dist/api/substrate-api.js +305 -0
  12. package/dist/auth/crypto.d.ts +32 -0
  13. package/dist/auth/crypto.js +66 -0
  14. package/dist/auth/index.d.ts +6 -0
  15. package/dist/auth/index.js +6 -0
  16. package/dist/auth/session-store.d.ts +82 -0
  17. package/dist/auth/session-store.js +136 -0
  18. package/dist/auth/token-extractor.d.ts +69 -0
  19. package/dist/auth/token-extractor.js +330 -0
  20. package/dist/browser/auth.d.ts +43 -0
  21. package/dist/browser/auth.js +232 -0
  22. package/dist/browser/context.d.ts +40 -0
  23. package/dist/browser/context.js +121 -0
  24. package/dist/browser/session.d.ts +34 -0
  25. package/dist/browser/session.js +92 -0
  26. package/dist/constants.d.ts +54 -0
  27. package/dist/constants.js +72 -0
  28. package/dist/index.d.ts +8 -0
  29. package/dist/index.js +12 -0
  30. package/dist/research/explore.d.ts +11 -0
  31. package/dist/research/explore.js +267 -0
  32. package/dist/research/search-research.d.ts +17 -0
  33. package/dist/research/search-research.js +317 -0
  34. package/dist/server.d.ts +64 -0
  35. package/dist/server.js +291 -0
  36. package/dist/teams/api-interceptor.d.ts +54 -0
  37. package/dist/teams/api-interceptor.js +391 -0
  38. package/dist/teams/direct-api.d.ts +321 -0
  39. package/dist/teams/direct-api.js +1305 -0
  40. package/dist/teams/messages.d.ts +14 -0
  41. package/dist/teams/messages.js +142 -0
  42. package/dist/teams/search.d.ts +40 -0
  43. package/dist/teams/search.js +458 -0
  44. package/dist/test/cli.d.ts +12 -0
  45. package/dist/test/cli.js +328 -0
  46. package/dist/test/debug-search.d.ts +10 -0
  47. package/dist/test/debug-search.js +147 -0
  48. package/dist/test/manual-test.d.ts +11 -0
  49. package/dist/test/manual-test.js +160 -0
  50. package/dist/test/mcp-harness.d.ts +17 -0
  51. package/dist/test/mcp-harness.js +427 -0
  52. package/dist/tools/auth-tools.d.ts +26 -0
  53. package/dist/tools/auth-tools.js +127 -0
  54. package/dist/tools/index.d.ts +45 -0
  55. package/dist/tools/index.js +12 -0
  56. package/dist/tools/message-tools.d.ts +139 -0
  57. package/dist/tools/message-tools.js +433 -0
  58. package/dist/tools/people-tools.d.ts +46 -0
  59. package/dist/tools/people-tools.js +123 -0
  60. package/dist/tools/registry.d.ts +23 -0
  61. package/dist/tools/registry.js +61 -0
  62. package/dist/tools/search-tools.d.ts +79 -0
  63. package/dist/tools/search-tools.js +168 -0
  64. package/dist/types/errors.d.ts +58 -0
  65. package/dist/types/errors.js +132 -0
  66. package/dist/types/result.d.ts +43 -0
  67. package/dist/types/result.js +51 -0
  68. package/dist/types/teams.d.ts +79 -0
  69. package/dist/types/teams.js +5 -0
  70. package/dist/utils/api-config.d.ts +66 -0
  71. package/dist/utils/api-config.js +113 -0
  72. package/dist/utils/auth-guards.d.ts +29 -0
  73. package/dist/utils/auth-guards.js +54 -0
  74. package/dist/utils/http.d.ts +29 -0
  75. package/dist/utils/http.js +111 -0
  76. package/dist/utils/parsers.d.ts +187 -0
  77. package/dist/utils/parsers.js +574 -0
  78. package/dist/utils/parsers.test.d.ts +7 -0
  79. package/dist/utils/parsers.test.js +360 -0
  80. package/package.json +58 -0
@@ -0,0 +1,232 @@
1
+ /**
2
+ * Authentication handling for Microsoft Teams.
3
+ * Manages login detection and manual authentication flows.
4
+ */
5
+ import { saveSessionState } from './context.js';
6
+ const TEAMS_URL = 'https://teams.microsoft.com';
7
+ // URLs that indicate we're in a login flow
8
+ const LOGIN_URL_PATTERNS = [
9
+ 'login.microsoftonline.com',
10
+ 'login.live.com',
11
+ 'login.microsoft.com',
12
+ ];
13
+ // Selectors that indicate successful authentication
14
+ const AUTH_SUCCESS_SELECTORS = [
15
+ '[data-tid="app-bar"]',
16
+ '[data-tid="search-box"]',
17
+ 'input[placeholder*="Search"]',
18
+ '[data-tid="chat-list"]',
19
+ '[data-tid="team-list"]',
20
+ ];
21
+ /**
22
+ * Checks if the current page URL indicates a login flow.
23
+ */
24
+ function isLoginUrl(url) {
25
+ return LOGIN_URL_PATTERNS.some(pattern => url.includes(pattern));
26
+ }
27
+ /**
28
+ * Checks if the page shows authenticated Teams content.
29
+ */
30
+ async function hasAuthenticatedContent(page) {
31
+ for (const selector of AUTH_SUCCESS_SELECTORS) {
32
+ try {
33
+ const count = await page.locator(selector).count();
34
+ if (count > 0) {
35
+ return true;
36
+ }
37
+ }
38
+ catch {
39
+ // Selector not found, continue checking others
40
+ }
41
+ }
42
+ return false;
43
+ }
44
+ // Search input selectors - must match those in teams/search.ts
45
+ const SEARCH_INPUT_SELECTORS = [
46
+ '[data-tid="searchInputField"]',
47
+ '[data-tid="app-search-input"]',
48
+ 'input[data-tid*="search"]',
49
+ 'input[placeholder*="Search"]',
50
+ ];
51
+ const SEARCH_BUTTON_SELECTORS = [
52
+ '[data-tid="search-box"]',
53
+ '[data-tid="search-button"]',
54
+ '[data-tid="app-bar-search"]',
55
+ '[role="search"] input',
56
+ ];
57
+ /**
58
+ * Triggers a search to cause MSAL to acquire the Substrate token.
59
+ * This is necessary because MSAL only acquires tokens for specific scopes
60
+ * when the app actually makes API calls requiring those scopes.
61
+ */
62
+ async function triggerTokenAcquisition(page, log) {
63
+ log('Triggering token acquisition...');
64
+ try {
65
+ // Wait for the app to be ready
66
+ await page.waitForTimeout(3000);
67
+ // Try to find a search input directly
68
+ let searchInput = null;
69
+ for (const selector of SEARCH_INPUT_SELECTORS) {
70
+ const loc = page.locator(selector).first();
71
+ if (await loc.isVisible().catch(() => false)) {
72
+ searchInput = loc;
73
+ break;
74
+ }
75
+ }
76
+ // If no input visible, try clicking a search button first
77
+ if (!searchInput) {
78
+ for (const selector of SEARCH_BUTTON_SELECTORS) {
79
+ const btn = page.locator(selector).first();
80
+ if (await btn.isVisible().catch(() => false)) {
81
+ await btn.click();
82
+ await page.waitForTimeout(1000);
83
+ // Now look for the input again
84
+ for (const inputSelector of SEARCH_INPUT_SELECTORS) {
85
+ const loc = page.locator(inputSelector).first();
86
+ if (await loc.isVisible().catch(() => false)) {
87
+ searchInput = loc;
88
+ break;
89
+ }
90
+ }
91
+ break;
92
+ }
93
+ }
94
+ }
95
+ if (searchInput) {
96
+ // Type a simple search query to trigger the API call
97
+ await searchInput.fill('test');
98
+ await page.keyboard.press('Enter');
99
+ // Wait for the search API call to complete
100
+ log('Waiting for search API response...');
101
+ await page.waitForTimeout(5000);
102
+ // Press Escape to close search and return to normal view
103
+ await page.keyboard.press('Escape');
104
+ await page.waitForTimeout(1000);
105
+ log('Token acquisition complete.');
106
+ }
107
+ else {
108
+ // Fallback: just wait and hope MSAL refreshes tokens
109
+ log('Search UI not found, waiting for background token refresh...');
110
+ await page.waitForTimeout(5000);
111
+ }
112
+ }
113
+ catch (error) {
114
+ // Non-fatal: tokens might still work from previous session
115
+ log(`Token acquisition warning: ${error instanceof Error ? error.message : String(error)}`);
116
+ await page.waitForTimeout(3000);
117
+ }
118
+ }
119
+ /**
120
+ * Gets the current authentication status.
121
+ */
122
+ export async function getAuthStatus(page) {
123
+ const currentUrl = page.url();
124
+ const onLoginPage = isLoginUrl(currentUrl);
125
+ // If on login page, definitely not authenticated
126
+ if (onLoginPage) {
127
+ return {
128
+ isAuthenticated: false,
129
+ isOnLoginPage: true,
130
+ currentUrl,
131
+ };
132
+ }
133
+ // If on Teams domain, check for authenticated content
134
+ if (currentUrl.includes('teams.microsoft.com')) {
135
+ const hasContent = await hasAuthenticatedContent(page);
136
+ return {
137
+ isAuthenticated: hasContent,
138
+ isOnLoginPage: false,
139
+ currentUrl,
140
+ };
141
+ }
142
+ // Unknown state
143
+ return {
144
+ isAuthenticated: false,
145
+ isOnLoginPage: false,
146
+ currentUrl,
147
+ };
148
+ }
149
+ /**
150
+ * Navigates to Teams and checks authentication status.
151
+ */
152
+ export async function navigateToTeams(page) {
153
+ await page.goto(TEAMS_URL, { waitUntil: 'domcontentloaded' });
154
+ // Wait a moment for redirects to complete
155
+ await page.waitForTimeout(2000);
156
+ return getAuthStatus(page);
157
+ }
158
+ /**
159
+ * Waits for the user to complete manual authentication.
160
+ * Returns when authenticated or throws after timeout.
161
+ *
162
+ * @param page - The page to monitor
163
+ * @param context - Browser context for saving session
164
+ * @param timeoutMs - Maximum time to wait (default: 5 minutes)
165
+ * @param onProgress - Callback for progress updates
166
+ */
167
+ export async function waitForManualLogin(page, context, timeoutMs = 5 * 60 * 1000, onProgress) {
168
+ const startTime = Date.now();
169
+ const log = onProgress ?? console.log;
170
+ log('Waiting for manual login...');
171
+ while (Date.now() - startTime < timeoutMs) {
172
+ const status = await getAuthStatus(page);
173
+ if (status.isAuthenticated) {
174
+ log('Authentication successful!');
175
+ // Trigger a search to cause MSAL to acquire the Substrate token
176
+ await triggerTokenAcquisition(page, log);
177
+ // Save the session state with fresh tokens
178
+ await saveSessionState(context);
179
+ log('Session state saved.');
180
+ return;
181
+ }
182
+ // Check every 2 seconds
183
+ await page.waitForTimeout(2000);
184
+ }
185
+ throw new Error('Authentication timeout: user did not complete login within the allowed time');
186
+ }
187
+ /**
188
+ * Performs a full authentication flow:
189
+ * 1. Navigate to Teams
190
+ * 2. Check if already authenticated
191
+ * 3. If not, wait for manual login
192
+ *
193
+ * @param page - The page to use
194
+ * @param context - Browser context for session management
195
+ * @param onProgress - Callback for progress updates
196
+ */
197
+ export async function ensureAuthenticated(page, context, onProgress) {
198
+ const log = onProgress ?? console.log;
199
+ log('Navigating to Teams...');
200
+ const status = await navigateToTeams(page);
201
+ if (status.isAuthenticated) {
202
+ log('Already authenticated.');
203
+ // Trigger a search to cause MSAL to acquire/refresh the Substrate token
204
+ await triggerTokenAcquisition(page, log);
205
+ // Save the session state with fresh tokens
206
+ await saveSessionState(context);
207
+ return;
208
+ }
209
+ if (status.isOnLoginPage) {
210
+ log('Login required. Please complete authentication in the browser window.');
211
+ await waitForManualLogin(page, context, undefined, onProgress);
212
+ // Navigate back to Teams after login (in case we're on a callback URL)
213
+ await navigateToTeams(page);
214
+ }
215
+ else {
216
+ // Unexpected state - might need manual intervention
217
+ log('Unexpected page state. Waiting for authentication...');
218
+ await waitForManualLogin(page, context, undefined, onProgress);
219
+ }
220
+ }
221
+ /**
222
+ * Forces a new login by clearing session and navigating to Teams.
223
+ */
224
+ export async function forceNewLogin(page, context, onProgress) {
225
+ const log = onProgress ?? console.log;
226
+ log('Starting fresh login...');
227
+ // Clear cookies to force re-authentication
228
+ await context.clearCookies();
229
+ // Navigate and wait for login
230
+ await navigateToTeams(page);
231
+ await waitForManualLogin(page, context, undefined, onProgress);
232
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Playwright browser context management.
3
+ * Creates and manages browser contexts with session persistence.
4
+ *
5
+ * Uses the system's installed Chrome or Edge browser rather than downloading
6
+ * Playwright's bundled Chromium. This significantly reduces install size.
7
+ */
8
+ import { type Browser, type BrowserContext, type Page } from 'playwright';
9
+ export interface BrowserManager {
10
+ browser: Browser;
11
+ context: BrowserContext;
12
+ page: Page;
13
+ isNewSession: boolean;
14
+ }
15
+ export interface CreateBrowserOptions {
16
+ headless?: boolean;
17
+ viewport?: {
18
+ width: number;
19
+ height: number;
20
+ };
21
+ }
22
+ /**
23
+ * Creates a browser context with optional session state restoration.
24
+ *
25
+ * Uses the system's installed Chrome or Edge browser to avoid downloading
26
+ * Playwright's bundled Chromium (~180MB savings).
27
+ *
28
+ * @param options - Browser configuration options
29
+ * @returns Browser manager with browser, context, and page
30
+ * @throws Error if system browser is not found (with helpful suggestions)
31
+ */
32
+ export declare function createBrowserContext(options?: CreateBrowserOptions): Promise<BrowserManager>;
33
+ /**
34
+ * Saves the current browser context's session state.
35
+ */
36
+ export declare function saveSessionState(context: BrowserContext): Promise<void>;
37
+ /**
38
+ * Closes the browser and optionally saves session state.
39
+ */
40
+ export declare function closeBrowser(manager: BrowserManager, saveSession?: boolean): Promise<void>;
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Playwright browser context management.
3
+ * Creates and manages browser contexts with session persistence.
4
+ *
5
+ * Uses the system's installed Chrome or Edge browser rather than downloading
6
+ * Playwright's bundled Chromium. This significantly reduces install size.
7
+ */
8
+ import { chromium } from 'playwright';
9
+ import { ensureUserDataDir, hasSessionState, SESSION_STATE_PATH, isSessionLikelyExpired, writeSessionState, readSessionState, } from '../auth/session-store.js';
10
+ import { areTokensExpired } from '../auth/token-extractor.js';
11
+ const DEFAULT_OPTIONS = {
12
+ headless: true,
13
+ viewport: { width: 1280, height: 800 },
14
+ };
15
+ /**
16
+ * Determines the browser channel to use based on the platform.
17
+ * - Windows: Use Microsoft Edge (always installed on Windows 10+)
18
+ * - macOS/Linux: Use Chrome
19
+ *
20
+ * @returns The browser channel name for Playwright
21
+ */
22
+ function getBrowserChannel() {
23
+ return process.platform === 'win32' ? 'msedge' : 'chrome';
24
+ }
25
+ /**
26
+ * Creates a browser context with optional session state restoration.
27
+ *
28
+ * Uses the system's installed Chrome or Edge browser to avoid downloading
29
+ * Playwright's bundled Chromium (~180MB savings).
30
+ *
31
+ * @param options - Browser configuration options
32
+ * @returns Browser manager with browser, context, and page
33
+ * @throws Error if system browser is not found (with helpful suggestions)
34
+ */
35
+ export async function createBrowserContext(options = {}) {
36
+ const opts = { ...DEFAULT_OPTIONS, ...options };
37
+ ensureUserDataDir();
38
+ const channel = getBrowserChannel();
39
+ let browser;
40
+ try {
41
+ browser = await chromium.launch({
42
+ headless: opts.headless,
43
+ channel,
44
+ });
45
+ }
46
+ catch (error) {
47
+ const browserName = channel === 'msedge' ? 'Microsoft Edge' : 'Google Chrome';
48
+ const installHint = channel === 'msedge'
49
+ ? 'Edge should be pre-installed on Windows. Try updating Windows or reinstalling Edge.'
50
+ : 'Install Chrome from https://www.google.com/chrome/ or run: npx playwright install chromium';
51
+ throw new Error(`Could not launch ${browserName}. ${installHint}\n\n` +
52
+ `Original error: ${error instanceof Error ? error.message : String(error)}`);
53
+ }
54
+ const hasSession = hasSessionState();
55
+ const sessionExpired = isSessionLikelyExpired();
56
+ const tokensExpired = areTokensExpired();
57
+ // Restore session if we have one and it's not ancient
58
+ const shouldRestoreSession = hasSession && !sessionExpired;
59
+ let context;
60
+ if (shouldRestoreSession) {
61
+ try {
62
+ // Read the decrypted session state
63
+ const state = readSessionState();
64
+ if (state) {
65
+ // Create a temporary file for Playwright (it needs a file path)
66
+ // We write the decrypted state to a temp location
67
+ const tempPath = SESSION_STATE_PATH + '.tmp';
68
+ const fs = await import('fs');
69
+ fs.writeFileSync(tempPath, JSON.stringify(state), { mode: 0o600 });
70
+ try {
71
+ context = await browser.newContext({
72
+ storageState: tempPath,
73
+ viewport: opts.viewport,
74
+ });
75
+ }
76
+ finally {
77
+ // Clean up temp file
78
+ fs.unlinkSync(tempPath);
79
+ }
80
+ }
81
+ else {
82
+ throw new Error('Failed to read session state');
83
+ }
84
+ }
85
+ catch (error) {
86
+ console.warn('Failed to restore session state, starting fresh:', error);
87
+ context = await browser.newContext({
88
+ viewport: opts.viewport,
89
+ });
90
+ }
91
+ }
92
+ else {
93
+ context = await browser.newContext({
94
+ viewport: opts.viewport,
95
+ });
96
+ }
97
+ const page = await context.newPage();
98
+ return {
99
+ browser,
100
+ context,
101
+ page,
102
+ isNewSession: !shouldRestoreSession,
103
+ };
104
+ }
105
+ /**
106
+ * Saves the current browser context's session state.
107
+ */
108
+ export async function saveSessionState(context) {
109
+ const state = await context.storageState();
110
+ writeSessionState(state);
111
+ }
112
+ /**
113
+ * Closes the browser and optionally saves session state.
114
+ */
115
+ export async function closeBrowser(manager, saveSession = true) {
116
+ if (saveSession) {
117
+ await saveSessionState(manager.context);
118
+ }
119
+ await manager.context.close();
120
+ await manager.browser.close();
121
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Session persistence utilities.
3
+ * Handles saving and restoring browser session state.
4
+ */
5
+ export declare const PROJECT_ROOT: string;
6
+ export declare const USER_DATA_DIR: string;
7
+ export declare const SESSION_STATE_PATH: string;
8
+ /**
9
+ * Ensures the user data directory exists.
10
+ */
11
+ export declare function ensureUserDataDir(): void;
12
+ /**
13
+ * Checks if a saved session state exists.
14
+ */
15
+ export declare function hasSessionState(): boolean;
16
+ /**
17
+ * Deletes the saved session state.
18
+ */
19
+ export declare function clearSessionState(): void;
20
+ /**
21
+ * Gets the age of the session state file in hours.
22
+ * Returns null if no session state exists.
23
+ */
24
+ export declare function getSessionAge(): number | null;
25
+ /**
26
+ * Checks if the session is likely expired based on file age.
27
+ * Sessions older than 12 hours are considered potentially expired.
28
+ */
29
+ export declare function isSessionLikelyExpired(): boolean;
30
+ /**
31
+ * Checks if the tokens in the session state are expired.
32
+ * Returns true if no tokens found or if the Substrate search token is expired.
33
+ */
34
+ export declare function areTokensExpired(): boolean;
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Session persistence utilities.
3
+ * Handles saving and restoring browser session state.
4
+ */
5
+ import * as fs from 'fs';
6
+ import * as path from 'path';
7
+ import { fileURLToPath } from 'url';
8
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
+ export const PROJECT_ROOT = path.resolve(__dirname, '../..');
10
+ export const USER_DATA_DIR = path.join(PROJECT_ROOT, '.user-data');
11
+ export const SESSION_STATE_PATH = path.join(PROJECT_ROOT, 'session-state.json');
12
+ /**
13
+ * Ensures the user data directory exists.
14
+ */
15
+ export function ensureUserDataDir() {
16
+ if (!fs.existsSync(USER_DATA_DIR)) {
17
+ fs.mkdirSync(USER_DATA_DIR, { recursive: true });
18
+ }
19
+ }
20
+ /**
21
+ * Checks if a saved session state exists.
22
+ */
23
+ export function hasSessionState() {
24
+ return fs.existsSync(SESSION_STATE_PATH);
25
+ }
26
+ /**
27
+ * Deletes the saved session state.
28
+ */
29
+ export function clearSessionState() {
30
+ if (fs.existsSync(SESSION_STATE_PATH)) {
31
+ fs.unlinkSync(SESSION_STATE_PATH);
32
+ }
33
+ }
34
+ /**
35
+ * Gets the age of the session state file in hours.
36
+ * Returns null if no session state exists.
37
+ */
38
+ export function getSessionAge() {
39
+ if (!hasSessionState()) {
40
+ return null;
41
+ }
42
+ const stats = fs.statSync(SESSION_STATE_PATH);
43
+ const ageMs = Date.now() - stats.mtimeMs;
44
+ return ageMs / (1000 * 60 * 60); // Convert to hours
45
+ }
46
+ /**
47
+ * Checks if the session is likely expired based on file age.
48
+ * Sessions older than 12 hours are considered potentially expired.
49
+ */
50
+ export function isSessionLikelyExpired() {
51
+ const age = getSessionAge();
52
+ if (age === null) {
53
+ return true;
54
+ }
55
+ return age > 12; // 12 hours
56
+ }
57
+ /**
58
+ * Checks if the tokens in the session state are expired.
59
+ * Returns true if no tokens found or if the Substrate search token is expired.
60
+ */
61
+ export function areTokensExpired() {
62
+ if (!hasSessionState()) {
63
+ return true;
64
+ }
65
+ try {
66
+ const state = JSON.parse(fs.readFileSync(SESSION_STATE_PATH, 'utf8'));
67
+ const teamsOrigin = state.origins?.find((o) => o.origin === 'https://teams.microsoft.com');
68
+ if (!teamsOrigin)
69
+ return true;
70
+ // Look for Substrate search token
71
+ for (const item of teamsOrigin.localStorage) {
72
+ try {
73
+ const val = JSON.parse(item.value);
74
+ if (val.target?.includes('substrate.office.com/search/SubstrateSearch')) {
75
+ const parts = val.secret.split('.');
76
+ if (parts.length === 3) {
77
+ const payload = JSON.parse(Buffer.from(parts[1], 'base64').toString());
78
+ const expiry = new Date(payload.exp * 1000);
79
+ return expiry.getTime() <= Date.now();
80
+ }
81
+ }
82
+ }
83
+ catch {
84
+ continue;
85
+ }
86
+ }
87
+ }
88
+ catch {
89
+ return true;
90
+ }
91
+ return true; // No token found = expired
92
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Shared constants used across the codebase.
3
+ *
4
+ * Centralising these values makes the code more maintainable and
5
+ * allows for easier configuration changes.
6
+ */
7
+ /** Minimum content length to be considered valid (characters). */
8
+ export declare const MIN_CONTENT_LENGTH = 5;
9
+ /** Default page size for search results. */
10
+ export declare const DEFAULT_PAGE_SIZE = 25;
11
+ /** Maximum page size for search results. */
12
+ export declare const MAX_PAGE_SIZE = 100;
13
+ /** Default limit for thread messages. */
14
+ export declare const DEFAULT_THREAD_LIMIT = 50;
15
+ /** Maximum limit for thread messages. */
16
+ export declare const MAX_THREAD_LIMIT = 200;
17
+ /** Default limit for people search. */
18
+ export declare const DEFAULT_PEOPLE_LIMIT = 10;
19
+ /** Maximum limit for people search. */
20
+ export declare const MAX_PEOPLE_LIMIT = 50;
21
+ /** Default limit for frequent contacts. */
22
+ export declare const DEFAULT_CONTACTS_LIMIT = 50;
23
+ /** Maximum limit for frequent contacts. */
24
+ export declare const MAX_CONTACTS_LIMIT = 500;
25
+ /** Default limit for channel search. */
26
+ export declare const DEFAULT_CHANNEL_LIMIT = 10;
27
+ /** Maximum limit for channel search. */
28
+ export declare const MAX_CHANNEL_LIMIT = 50;
29
+ /** Default timeout for waiting for search results. */
30
+ export declare const SEARCH_RESULT_TIMEOUT_MS = 10000;
31
+ /** Default HTTP request timeout. */
32
+ export declare const HTTP_REQUEST_TIMEOUT_MS = 30000;
33
+ /** Short delay for UI interactions. */
34
+ export declare const UI_SHORT_DELAY_MS = 300;
35
+ /** Medium delay for UI state changes. */
36
+ export declare const UI_MEDIUM_DELAY_MS = 1000;
37
+ /** Long delay for API responses to settle. */
38
+ export declare const UI_LONG_DELAY_MS = 2000;
39
+ /** Delay for MSAL token storage. */
40
+ export declare const MSAL_TOKEN_DELAY_MS = 3000;
41
+ /** Authentication check interval. */
42
+ export declare const AUTH_CHECK_INTERVAL_MS = 2000;
43
+ /** Default login timeout (5 minutes). */
44
+ export declare const LOGIN_TIMEOUT_MS: number;
45
+ /** Session expiry threshold in hours. */
46
+ export declare const SESSION_EXPIRY_HOURS = 12;
47
+ /** Default maximum retry attempts for HTTP requests. */
48
+ export declare const DEFAULT_MAX_RETRIES = 3;
49
+ /** Base delay for exponential backoff (milliseconds). */
50
+ export declare const RETRY_BASE_DELAY_MS = 1000;
51
+ /** Maximum delay between retries (milliseconds). */
52
+ export declare const RETRY_MAX_DELAY_MS = 10000;
53
+ /** Self-chat (notes) conversation ID. */
54
+ export declare const SELF_CHAT_ID = "48:notes";
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Shared constants used across the codebase.
3
+ *
4
+ * Centralising these values makes the code more maintainable and
5
+ * allows for easier configuration changes.
6
+ */
7
+ // ─────────────────────────────────────────────────────────────────────────────
8
+ // Content Thresholds
9
+ // ─────────────────────────────────────────────────────────────────────────────
10
+ /** Minimum content length to be considered valid (characters). */
11
+ export const MIN_CONTENT_LENGTH = 5;
12
+ // ─────────────────────────────────────────────────────────────────────────────
13
+ // Pagination Defaults
14
+ // ─────────────────────────────────────────────────────────────────────────────
15
+ /** Default page size for search results. */
16
+ export const DEFAULT_PAGE_SIZE = 25;
17
+ /** Maximum page size for search results. */
18
+ export const MAX_PAGE_SIZE = 100;
19
+ /** Default limit for thread messages. */
20
+ export const DEFAULT_THREAD_LIMIT = 50;
21
+ /** Maximum limit for thread messages. */
22
+ export const MAX_THREAD_LIMIT = 200;
23
+ /** Default limit for people search. */
24
+ export const DEFAULT_PEOPLE_LIMIT = 10;
25
+ /** Maximum limit for people search. */
26
+ export const MAX_PEOPLE_LIMIT = 50;
27
+ /** Default limit for frequent contacts. */
28
+ export const DEFAULT_CONTACTS_LIMIT = 50;
29
+ /** Maximum limit for frequent contacts. */
30
+ export const MAX_CONTACTS_LIMIT = 500;
31
+ /** Default limit for channel search. */
32
+ export const DEFAULT_CHANNEL_LIMIT = 10;
33
+ /** Maximum limit for channel search. */
34
+ export const MAX_CHANNEL_LIMIT = 50;
35
+ // ─────────────────────────────────────────────────────────────────────────────
36
+ // Timeouts (milliseconds)
37
+ // ─────────────────────────────────────────────────────────────────────────────
38
+ /** Default timeout for waiting for search results. */
39
+ export const SEARCH_RESULT_TIMEOUT_MS = 10000;
40
+ /** Default HTTP request timeout. */
41
+ export const HTTP_REQUEST_TIMEOUT_MS = 30000;
42
+ /** Short delay for UI interactions. */
43
+ export const UI_SHORT_DELAY_MS = 300;
44
+ /** Medium delay for UI state changes. */
45
+ export const UI_MEDIUM_DELAY_MS = 1000;
46
+ /** Long delay for API responses to settle. */
47
+ export const UI_LONG_DELAY_MS = 2000;
48
+ /** Delay for MSAL token storage. */
49
+ export const MSAL_TOKEN_DELAY_MS = 3000;
50
+ /** Authentication check interval. */
51
+ export const AUTH_CHECK_INTERVAL_MS = 2000;
52
+ /** Default login timeout (5 minutes). */
53
+ export const LOGIN_TIMEOUT_MS = 5 * 60 * 1000;
54
+ // ─────────────────────────────────────────────────────────────────────────────
55
+ // Session Management
56
+ // ─────────────────────────────────────────────────────────────────────────────
57
+ /** Session expiry threshold in hours. */
58
+ export const SESSION_EXPIRY_HOURS = 12;
59
+ // ─────────────────────────────────────────────────────────────────────────────
60
+ // Retry Configuration
61
+ // ─────────────────────────────────────────────────────────────────────────────
62
+ /** Default maximum retry attempts for HTTP requests. */
63
+ export const DEFAULT_MAX_RETRIES = 3;
64
+ /** Base delay for exponential backoff (milliseconds). */
65
+ export const RETRY_BASE_DELAY_MS = 1000;
66
+ /** Maximum delay between retries (milliseconds). */
67
+ export const RETRY_MAX_DELAY_MS = 10000;
68
+ // ─────────────────────────────────────────────────────────────────────────────
69
+ // Conversation IDs
70
+ // ─────────────────────────────────────────────────────────────────────────────
71
+ /** Self-chat (notes) conversation ID. */
72
+ export const SELF_CHAT_ID = '48:notes';
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Teams MCP Server entry point.
4
+ *
5
+ * This MCP server enables AI assistants to search Microsoft Teams
6
+ * messages using browser automation.
7
+ */
8
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Teams MCP Server entry point.
4
+ *
5
+ * This MCP server enables AI assistants to search Microsoft Teams
6
+ * messages using browser automation.
7
+ */
8
+ import { runServer } from './server.js';
9
+ runServer().catch((error) => {
10
+ console.error('Fatal error:', error);
11
+ process.exit(1);
12
+ });
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Research script to explore Teams web app behaviour.
3
+ *
4
+ * This script:
5
+ * 1. Launches a browser with persistent context
6
+ * 2. Navigates to Teams and handles authentication
7
+ * 3. Monitors network requests to discover API endpoints
8
+ * 4. Allows manual interaction to trigger searches
9
+ * 5. Logs discovered endpoints and data structures
10
+ */
11
+ export {};