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,267 @@
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
+ import { chromium } from 'playwright';
12
+ import * as fs from 'fs';
13
+ import * as path from 'path';
14
+ import { fileURLToPath } from 'url';
15
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
16
+ const PROJECT_ROOT = path.resolve(__dirname, '../..');
17
+ const USER_DATA_DIR = path.join(PROJECT_ROOT, '.user-data');
18
+ const SESSION_STATE_PATH = path.join(PROJECT_ROOT, 'session-state.json');
19
+ const FINDINGS_PATH = path.join(PROJECT_ROOT, 'research-findings.json');
20
+ // Track interesting API calls
21
+ const capturedNetworkData = {
22
+ requests: [],
23
+ responses: [],
24
+ };
25
+ // Patterns that indicate search-related API calls
26
+ const SEARCH_PATTERNS = [
27
+ /search/i,
28
+ /query/i,
29
+ /find/i,
30
+ /substrate/i,
31
+ /graph\.microsoft/i,
32
+ /teams.*api/i,
33
+ /chatservice/i,
34
+ /emea\.ng\.msg/i,
35
+ ];
36
+ function isInterestingUrl(url) {
37
+ // Skip static assets, telemetry, and auth-related noise
38
+ const skipPatterns = [
39
+ /\.(js|css|png|jpg|jpeg|gif|svg|woff|woff2|ttf|ico)(\?|$)/i,
40
+ /telemetry/i,
41
+ /analytics/i,
42
+ /logging/i,
43
+ /beacon/i,
44
+ /\.clarity\./i,
45
+ /fonts\./i,
46
+ /static/i,
47
+ ];
48
+ if (skipPatterns.some(pattern => pattern.test(url))) {
49
+ return false;
50
+ }
51
+ // Include if it matches search patterns or is a potential API call
52
+ return SEARCH_PATTERNS.some(pattern => pattern.test(url)) ||
53
+ url.includes('/api/') ||
54
+ url.includes('/v1/') ||
55
+ url.includes('/v2/');
56
+ }
57
+ async function captureRequest(request) {
58
+ const url = request.url();
59
+ if (!isInterestingUrl(url)) {
60
+ return;
61
+ }
62
+ const captured = {
63
+ url,
64
+ method: request.method(),
65
+ headers: request.headers(),
66
+ postData: request.postData() ?? undefined,
67
+ timestamp: new Date().toISOString(),
68
+ resourceType: request.resourceType(),
69
+ };
70
+ capturedNetworkData.requests.push(captured);
71
+ console.log(`\nšŸ“¤ REQUEST: ${request.method()} ${url}`);
72
+ if (captured.postData) {
73
+ try {
74
+ const parsed = JSON.parse(captured.postData);
75
+ console.log(' Body:', JSON.stringify(parsed, null, 2).substring(0, 500));
76
+ }
77
+ catch {
78
+ console.log(' Body:', captured.postData.substring(0, 200));
79
+ }
80
+ }
81
+ }
82
+ async function captureResponse(response) {
83
+ const url = response.url();
84
+ if (!isInterestingUrl(url)) {
85
+ return;
86
+ }
87
+ let body;
88
+ try {
89
+ const contentType = response.headers()['content-type'] || '';
90
+ if (contentType.includes('application/json')) {
91
+ body = await response.text();
92
+ }
93
+ }
94
+ catch {
95
+ // Response body may not be available
96
+ }
97
+ const captured = {
98
+ url,
99
+ status: response.status(),
100
+ statusText: response.statusText(),
101
+ headers: response.headers(),
102
+ body,
103
+ timestamp: new Date().toISOString(),
104
+ };
105
+ capturedNetworkData.responses.push(captured);
106
+ console.log(`\nšŸ“„ RESPONSE: ${response.status()} ${url}`);
107
+ if (body) {
108
+ try {
109
+ const parsed = JSON.parse(body);
110
+ console.log(' Body preview:', JSON.stringify(parsed, null, 2).substring(0, 500));
111
+ }
112
+ catch {
113
+ console.log(' Body preview:', body.substring(0, 200));
114
+ }
115
+ }
116
+ }
117
+ async function saveFindings() {
118
+ // Deduplicate and summarise findings
119
+ const uniqueEndpoints = new Map();
120
+ for (const req of capturedNetworkData.requests) {
121
+ const key = `${req.method}:${new URL(req.url).pathname}`;
122
+ if (!uniqueEndpoints.has(key)) {
123
+ uniqueEndpoints.set(key, req);
124
+ }
125
+ }
126
+ const findings = {
127
+ discoveredAt: new Date().toISOString(),
128
+ uniqueEndpoints: Array.from(uniqueEndpoints.values()).map(req => ({
129
+ method: req.method,
130
+ url: req.url,
131
+ hasPostData: !!req.postData,
132
+ postDataSample: req.postData?.substring(0, 500),
133
+ })),
134
+ allRequests: capturedNetworkData.requests,
135
+ allResponses: capturedNetworkData.responses,
136
+ };
137
+ fs.writeFileSync(FINDINGS_PATH, JSON.stringify(findings, null, 2));
138
+ console.log(`\nšŸ’¾ Findings saved to ${FINDINGS_PATH}`);
139
+ }
140
+ async function checkAuthentication(page) {
141
+ const url = page.url();
142
+ // Check if we're on a login page
143
+ if (url.includes('login.microsoftonline.com') ||
144
+ url.includes('login.live.com') ||
145
+ url.includes('login.microsoft.com')) {
146
+ return false;
147
+ }
148
+ // Check if we're on the Teams app
149
+ if (url.includes('teams.microsoft.com')) {
150
+ // Look for indicators that we're logged in
151
+ try {
152
+ // Wait briefly for the app to load
153
+ await page.waitForTimeout(2000);
154
+ // Check for common authenticated elements
155
+ const hasAppBar = await page.locator('[data-tid="app-bar"]').count() > 0;
156
+ const hasSearchBox = await page.locator('[data-tid="search-box"]').count() > 0 ||
157
+ await page.locator('input[placeholder*="Search"]').count() > 0;
158
+ return hasAppBar || hasSearchBox;
159
+ }
160
+ catch {
161
+ return false;
162
+ }
163
+ }
164
+ return false;
165
+ }
166
+ async function waitForAuthentication(page) {
167
+ console.log('\nšŸ” Please log in to Microsoft Teams in the browser window...');
168
+ console.log(' The script will continue once you are authenticated.\n');
169
+ // Poll for authentication
170
+ while (true) {
171
+ if (await checkAuthentication(page)) {
172
+ console.log('āœ… Authentication detected!');
173
+ break;
174
+ }
175
+ await page.waitForTimeout(2000);
176
+ }
177
+ }
178
+ async function main() {
179
+ console.log('šŸ” Teams Research Script');
180
+ console.log('========================\n');
181
+ console.log('This script will help discover how Teams web app works.\n');
182
+ // Ensure user data directory exists
183
+ if (!fs.existsSync(USER_DATA_DIR)) {
184
+ fs.mkdirSync(USER_DATA_DIR, { recursive: true });
185
+ }
186
+ let browser;
187
+ let context;
188
+ try {
189
+ // Launch browser with persistent context
190
+ browser = await chromium.launch({
191
+ headless: false, // Must be visible for manual login
192
+ });
193
+ // Check if we have saved session state
194
+ const hasSessionState = fs.existsSync(SESSION_STATE_PATH);
195
+ if (hasSessionState) {
196
+ console.log('šŸ“‚ Found existing session state, attempting to restore...');
197
+ context = await browser.newContext({
198
+ storageState: SESSION_STATE_PATH,
199
+ viewport: { width: 1280, height: 800 },
200
+ });
201
+ }
202
+ else {
203
+ console.log('šŸ“‚ No session state found, starting fresh...');
204
+ context = await browser.newContext({
205
+ viewport: { width: 1280, height: 800 },
206
+ });
207
+ }
208
+ const page = await context.newPage();
209
+ // Set up network interception
210
+ page.on('request', captureRequest);
211
+ page.on('response', captureResponse);
212
+ // Navigate to Teams
213
+ console.log('🌐 Navigating to Teams...');
214
+ await page.goto('https://teams.microsoft.com', { waitUntil: 'domcontentloaded' });
215
+ // Check if authentication is needed
216
+ const isAuthenticated = await checkAuthentication(page);
217
+ if (!isAuthenticated) {
218
+ await waitForAuthentication(page);
219
+ // Save session state after successful authentication
220
+ console.log('šŸ’¾ Saving session state...');
221
+ await context.storageState({ path: SESSION_STATE_PATH });
222
+ console.log('āœ… Session state saved!');
223
+ }
224
+ else {
225
+ console.log('āœ… Already authenticated!');
226
+ }
227
+ // Instructions for the user
228
+ console.log('\n' + '='.repeat(60));
229
+ console.log('šŸ”¬ RESEARCH MODE ACTIVE');
230
+ console.log('='.repeat(60));
231
+ console.log('\nThe browser is now monitoring network requests.');
232
+ console.log('Try the following to discover API endpoints:\n');
233
+ console.log('1. Use the search bar (Cmd/Ctrl+E or click the search icon)');
234
+ console.log('2. Search for some text');
235
+ console.log('3. Click on search results');
236
+ console.log('4. Navigate between chats and channels');
237
+ console.log('\nPress Ctrl+C in this terminal when done to save findings.');
238
+ console.log('='.repeat(60) + '\n');
239
+ // Keep the browser open until interrupted
240
+ await new Promise((resolve) => {
241
+ process.on('SIGINT', () => {
242
+ console.log('\n\nā¹ļø Stopping research...');
243
+ resolve();
244
+ });
245
+ });
246
+ // Save session state before closing
247
+ console.log('šŸ’¾ Saving final session state...');
248
+ await context.storageState({ path: SESSION_STATE_PATH });
249
+ // Save research findings
250
+ await saveFindings();
251
+ }
252
+ catch (error) {
253
+ console.error('āŒ Error:', error);
254
+ throw error;
255
+ }
256
+ finally {
257
+ if (context) {
258
+ await context.close();
259
+ }
260
+ if (browser) {
261
+ await browser.close();
262
+ }
263
+ }
264
+ console.log('\nāœ… Research session complete!');
265
+ console.log(` Review findings in: ${FINDINGS_PATH}`);
266
+ }
267
+ main().catch(console.error);
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Focused research script for Teams search API.
3
+ *
4
+ * Captures full request/response bodies for Substrate search endpoints
5
+ * to understand pagination, query format, and response structure.
6
+ *
7
+ * Usage:
8
+ * npm run research:search
9
+ *
10
+ * Instructions:
11
+ * 1. Run this script
12
+ * 2. Perform a search in Teams
13
+ * 3. Click "Messages" tab or "See more results"
14
+ * 4. Scroll through results to trigger pagination
15
+ * 5. Press Ctrl+C to stop and save findings
16
+ */
17
+ export {};
@@ -0,0 +1,317 @@
1
+ /**
2
+ * Focused research script for Teams search API.
3
+ *
4
+ * Captures full request/response bodies for Substrate search endpoints
5
+ * to understand pagination, query format, and response structure.
6
+ *
7
+ * Usage:
8
+ * npm run research:search
9
+ *
10
+ * Instructions:
11
+ * 1. Run this script
12
+ * 2. Perform a search in Teams
13
+ * 3. Click "Messages" tab or "See more results"
14
+ * 4. Scroll through results to trigger pagination
15
+ * 5. Press Ctrl+C to stop and save findings
16
+ */
17
+ import { chromium } from 'playwright';
18
+ import * as fs from 'fs';
19
+ import * as path from 'path';
20
+ import { fileURLToPath } from 'url';
21
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
22
+ const PROJECT_ROOT = path.resolve(__dirname, '../..');
23
+ const SESSION_STATE_PATH = path.join(PROJECT_ROOT, 'session-state.json');
24
+ const SEARCH_FINDINGS_PATH = path.join(PROJECT_ROOT, 'search-api-findings.json');
25
+ const captures = [];
26
+ // Focus specifically on search-related endpoints
27
+ function isSearchEndpoint(url) {
28
+ const searchPatterns = [
29
+ /substrate\.office\.com\/search/i,
30
+ /\/api\/v\d+\/query/i,
31
+ /\/api\/v\d+\/suggestions/i,
32
+ /\/search\//i,
33
+ ];
34
+ return searchPatterns.some(p => p.test(url));
35
+ }
36
+ async function captureSearchRequest(request) {
37
+ const url = request.url();
38
+ if (!isSearchEndpoint(url))
39
+ return;
40
+ const postData = request.postData();
41
+ let body;
42
+ if (postData) {
43
+ try {
44
+ body = JSON.parse(postData);
45
+ }
46
+ catch {
47
+ body = postData;
48
+ }
49
+ }
50
+ const capture = {
51
+ timestamp: new Date().toISOString(),
52
+ type: 'request',
53
+ url,
54
+ method: request.method(),
55
+ body,
56
+ headers: request.headers(),
57
+ };
58
+ captures.push(capture);
59
+ console.log('\n' + '━'.repeat(80));
60
+ console.log(`šŸ“¤ SEARCH REQUEST: ${request.method()} ${url}`);
61
+ console.log('━'.repeat(80));
62
+ if (body) {
63
+ console.log('\nšŸ“‹ Request Body:');
64
+ console.log(JSON.stringify(body, null, 2));
65
+ }
66
+ // Highlight pagination-related fields
67
+ if (body && typeof body === 'object') {
68
+ const bodyObj = body;
69
+ const paginationFields = ['Size', 'From', 'Skip', 'Top', 'Offset', 'PageSize', 'Page', 'MaxResults'];
70
+ const foundPagination = {};
71
+ function findPaginationFields(obj, prefix = '') {
72
+ if (!obj || typeof obj !== 'object')
73
+ return;
74
+ for (const [key, value] of Object.entries(obj)) {
75
+ const fullKey = prefix ? `${prefix}.${key}` : key;
76
+ if (paginationFields.some(p => key.toLowerCase().includes(p.toLowerCase()))) {
77
+ foundPagination[fullKey] = value;
78
+ }
79
+ if (typeof value === 'object') {
80
+ findPaginationFields(value, fullKey);
81
+ }
82
+ }
83
+ }
84
+ findPaginationFields(bodyObj);
85
+ if (Object.keys(foundPagination).length > 0) {
86
+ console.log('\nšŸ“Š Pagination Fields Found:');
87
+ console.log(JSON.stringify(foundPagination, null, 2));
88
+ }
89
+ }
90
+ }
91
+ async function captureSearchResponse(response) {
92
+ const url = response.url();
93
+ if (!isSearchEndpoint(url))
94
+ return;
95
+ let body;
96
+ try {
97
+ const contentType = response.headers()['content-type'] || '';
98
+ if (contentType.includes('application/json')) {
99
+ const text = await response.text();
100
+ body = JSON.parse(text);
101
+ }
102
+ }
103
+ catch {
104
+ // Response body may not be available
105
+ }
106
+ const capture = {
107
+ timestamp: new Date().toISOString(),
108
+ type: 'response',
109
+ url,
110
+ status: response.status(),
111
+ body,
112
+ };
113
+ captures.push(capture);
114
+ console.log('\n' + '━'.repeat(80));
115
+ console.log(`šŸ“„ SEARCH RESPONSE: ${response.status()} ${url}`);
116
+ console.log('━'.repeat(80));
117
+ if (body && typeof body === 'object') {
118
+ const bodyObj = body;
119
+ // Show structure summary
120
+ console.log('\nšŸ“‹ Response Structure:');
121
+ const summarise = (obj, depth = 0) => {
122
+ if (depth > 2)
123
+ return '...';
124
+ if (Array.isArray(obj)) {
125
+ return `Array[${obj.length}]${obj.length > 0 ? ` of ${summarise(obj[0], depth + 1)}` : ''}`;
126
+ }
127
+ if (obj && typeof obj === 'object') {
128
+ const keys = Object.keys(obj);
129
+ if (keys.length > 8) {
130
+ return `{${keys.slice(0, 5).join(', ')}, ... +${keys.length - 5} more}`;
131
+ }
132
+ return `{${keys.join(', ')}}`;
133
+ }
134
+ return typeof obj;
135
+ };
136
+ console.log(summarise(bodyObj));
137
+ // Show pagination-related response fields
138
+ const paginationResponseFields = ['Total', 'TotalCount', 'Count', 'HasMore', 'HasNext', 'NextLink', 'SkipToken', 'ContinuationToken'];
139
+ const foundPagination = {};
140
+ function findPaginationFields(obj, prefix = '') {
141
+ if (!obj || typeof obj !== 'object')
142
+ return;
143
+ for (const [key, value] of Object.entries(obj)) {
144
+ const fullKey = prefix ? `${prefix}.${key}` : key;
145
+ if (paginationResponseFields.some(p => key.toLowerCase().includes(p.toLowerCase()))) {
146
+ foundPagination[fullKey] = value;
147
+ }
148
+ if (typeof value === 'object' && !Array.isArray(value)) {
149
+ findPaginationFields(value, fullKey);
150
+ }
151
+ }
152
+ }
153
+ findPaginationFields(bodyObj);
154
+ if (Object.keys(foundPagination).length > 0) {
155
+ console.log('\nšŸ“Š Pagination Response Fields:');
156
+ console.log(JSON.stringify(foundPagination, null, 2));
157
+ }
158
+ // Show results count
159
+ const countArrays = (obj) => {
160
+ const results = [];
161
+ function traverse(o, path) {
162
+ if (Array.isArray(o) && o.length > 0) {
163
+ results.push({ path: path || 'root', count: o.length });
164
+ }
165
+ if (o && typeof o === 'object' && !Array.isArray(o)) {
166
+ for (const [key, value] of Object.entries(o)) {
167
+ traverse(value, path ? `${path}.${key}` : key);
168
+ }
169
+ }
170
+ }
171
+ traverse(obj, '');
172
+ return results;
173
+ };
174
+ const arrays = countArrays(bodyObj);
175
+ if (arrays.length > 0) {
176
+ console.log('\nšŸ“ˆ Result Counts:');
177
+ arrays.forEach(a => console.log(` ${a.path}: ${a.count} items`));
178
+ }
179
+ // Show first result sample if available
180
+ const findFirstResult = (obj) => {
181
+ if (Array.isArray(obj) && obj.length > 0) {
182
+ const first = obj[0];
183
+ if (first && typeof first === 'object' && Object.keys(first).length > 2) {
184
+ return first;
185
+ }
186
+ }
187
+ if (obj && typeof obj === 'object') {
188
+ for (const value of Object.values(obj)) {
189
+ const found = findFirstResult(value);
190
+ if (found)
191
+ return found;
192
+ }
193
+ }
194
+ return null;
195
+ };
196
+ const firstResult = findFirstResult(bodyObj);
197
+ if (firstResult) {
198
+ console.log('\nšŸ“„ First Result Sample:');
199
+ console.log(JSON.stringify(firstResult, null, 2).substring(0, 1000));
200
+ }
201
+ }
202
+ }
203
+ async function checkAuthentication(page) {
204
+ const url = page.url();
205
+ if (url.includes('login.microsoftonline.com') ||
206
+ url.includes('login.live.com') ||
207
+ url.includes('login.microsoft.com')) {
208
+ return false;
209
+ }
210
+ if (url.includes('teams.microsoft.com')) {
211
+ try {
212
+ await page.waitForTimeout(2000);
213
+ const hasAppBar = await page.locator('[data-tid="app-bar"]').count() > 0;
214
+ const hasSearchBox = await page.locator('[data-tid="search-box"]').count() > 0 ||
215
+ await page.locator('input[placeholder*="Search"]').count() > 0;
216
+ return hasAppBar || hasSearchBox;
217
+ }
218
+ catch {
219
+ return false;
220
+ }
221
+ }
222
+ return false;
223
+ }
224
+ async function waitForAuthentication(page) {
225
+ console.log('\nšŸ” Please log in to Microsoft Teams...');
226
+ while (true) {
227
+ if (await checkAuthentication(page)) {
228
+ console.log('āœ… Authentication detected!');
229
+ break;
230
+ }
231
+ await page.waitForTimeout(2000);
232
+ }
233
+ }
234
+ async function saveFindings() {
235
+ const findings = {
236
+ capturedAt: new Date().toISOString(),
237
+ totalCaptures: captures.length,
238
+ requests: captures.filter(c => c.type === 'request'),
239
+ responses: captures.filter(c => c.type === 'response'),
240
+ };
241
+ fs.writeFileSync(SEARCH_FINDINGS_PATH, JSON.stringify(findings, null, 2));
242
+ console.log(`\nšŸ’¾ Search findings saved to ${SEARCH_FINDINGS_PATH}`);
243
+ }
244
+ async function main() {
245
+ console.log('šŸ” Teams Search API Research');
246
+ console.log('============================\n');
247
+ let browser;
248
+ let context;
249
+ try {
250
+ browser = await chromium.launch({ headless: false });
251
+ const hasSessionState = fs.existsSync(SESSION_STATE_PATH);
252
+ if (hasSessionState) {
253
+ console.log('šŸ“‚ Restoring session state...');
254
+ context = await browser.newContext({
255
+ storageState: SESSION_STATE_PATH,
256
+ viewport: { width: 1400, height: 900 },
257
+ });
258
+ }
259
+ else {
260
+ console.log('šŸ“‚ No session state found, starting fresh...');
261
+ context = await browser.newContext({
262
+ viewport: { width: 1400, height: 900 },
263
+ });
264
+ }
265
+ const page = await context.newPage();
266
+ // Set up network interception for search endpoints only
267
+ page.on('request', captureSearchRequest);
268
+ page.on('response', captureSearchResponse);
269
+ console.log('🌐 Navigating to Teams...');
270
+ await page.goto('https://teams.microsoft.com', { waitUntil: 'domcontentloaded' });
271
+ const isAuthenticated = await checkAuthentication(page);
272
+ if (!isAuthenticated) {
273
+ await waitForAuthentication(page);
274
+ await context.storageState({ path: SESSION_STATE_PATH });
275
+ console.log('āœ… Session state saved!');
276
+ }
277
+ else {
278
+ console.log('āœ… Already authenticated!');
279
+ }
280
+ console.log('\n' + '═'.repeat(60));
281
+ console.log('šŸ”¬ SEARCH RESEARCH MODE');
282
+ console.log('═'.repeat(60));
283
+ console.log('\nTo capture search API data:');
284
+ console.log('');
285
+ console.log(' 1. Press Cmd/Ctrl+E to open search');
286
+ console.log(' 2. Type a search query and press Enter');
287
+ console.log(' 3. Click "Messages" tab to see message results');
288
+ console.log(' 4. Click "See all results" if available');
289
+ console.log(' 5. Scroll through results to trigger pagination');
290
+ console.log('');
291
+ console.log('Watch for šŸ“Š Pagination Fields in the output!');
292
+ console.log('');
293
+ console.log('Press Ctrl+C when done to save findings.');
294
+ console.log('═'.repeat(60) + '\n');
295
+ await new Promise((resolve) => {
296
+ process.on('SIGINT', () => {
297
+ console.log('\n\nā¹ļø Stopping research...');
298
+ resolve();
299
+ });
300
+ });
301
+ await context.storageState({ path: SESSION_STATE_PATH });
302
+ await saveFindings();
303
+ }
304
+ catch (error) {
305
+ console.error('āŒ Error:', error);
306
+ throw error;
307
+ }
308
+ finally {
309
+ if (context)
310
+ await context.close();
311
+ if (browser)
312
+ await browser.close();
313
+ }
314
+ console.log('\nāœ… Search research complete!');
315
+ console.log(` Review findings in: ${SEARCH_FINDINGS_PATH}`);
316
+ }
317
+ main().catch(console.error);
@@ -0,0 +1,64 @@
1
+ /**
2
+ * MCP Server implementation for Teams search.
3
+ * Exposes tools and resources for interacting with Microsoft Teams.
4
+ */
5
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
6
+ import { type BrowserManager } from './browser/context.js';
7
+ /**
8
+ * MCP Server for Teams integration.
9
+ *
10
+ * Encapsulates all server state to allow multiple instances.
11
+ */
12
+ export declare class TeamsServer {
13
+ private browserManager;
14
+ private isInitialised;
15
+ /**
16
+ * Returns a standard MCP error response.
17
+ */
18
+ private formatError;
19
+ /**
20
+ * Returns a standard MCP success response.
21
+ */
22
+ private formatSuccess;
23
+ /**
24
+ * Gets the current browser manager.
25
+ */
26
+ getBrowserManager(): BrowserManager | null;
27
+ /**
28
+ * Sets the browser manager.
29
+ */
30
+ setBrowserManager(manager: BrowserManager): void;
31
+ /**
32
+ * Resets browser state.
33
+ */
34
+ resetBrowserState(): void;
35
+ /**
36
+ * Marks the server as initialised.
37
+ */
38
+ markInitialised(): void;
39
+ /**
40
+ * Checks if the server is initialised.
41
+ */
42
+ isInitialisedState(): boolean;
43
+ /**
44
+ * Ensures the browser is running and authenticated.
45
+ */
46
+ ensureBrowser(headless?: boolean): Promise<BrowserManager>;
47
+ /**
48
+ * Cleans up browser resources.
49
+ */
50
+ cleanup(): Promise<void>;
51
+ /**
52
+ * Creates and configures the MCP server.
53
+ */
54
+ createServer(): Promise<Server>;
55
+ }
56
+ /**
57
+ * Creates and runs the MCP server.
58
+ * Exported for backward compatibility.
59
+ */
60
+ export declare function createServer(): Promise<Server>;
61
+ /**
62
+ * Runs the server with stdio transport.
63
+ */
64
+ export declare function runServer(): Promise<void>;