msteams-mcp 0.30.0 ā 0.30.2
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.
- package/dist/api/index.d.ts +10 -0
- package/dist/api/index.js +10 -0
- package/dist/auth/index.d.ts +7 -0
- package/dist/auth/index.js +7 -0
- package/dist/auth/session-store.d.ts +5 -0
- package/dist/auth/session-store.js +7 -0
- package/dist/browser/auth.js +6 -4
- package/dist/browser/auth.test.js +20 -1
- package/dist/cli.js +0 -0
- package/dist/index.js +0 -0
- package/dist/research/auth-research.d.ts +10 -0
- package/dist/research/auth-research.js +175 -0
- package/dist/research/explore.d.ts +11 -0
- package/dist/research/explore.js +287 -0
- package/dist/research/search-research.d.ts +17 -0
- package/dist/research/search-research.js +317 -0
- package/dist/test/debug-search.d.ts +10 -0
- package/dist/test/debug-search.js +147 -0
- package/dist/test/mcp-harness.d.ts +17 -0
- package/dist/test/mcp-harness.js +476 -0
- package/dist/tools/auth-tools.js +26 -27
- package/dist/tools/auth-tools.test.js +18 -4
- package/package.json +1 -1
- package/dist/browser/context.test.d.ts +0 -8
- package/dist/browser/context.test.js +0 -55
|
@@ -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,147 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
/**
|
|
3
|
+
* Debug script to inspect the Teams search page.
|
|
4
|
+
* Takes screenshots and dumps page structure for debugging selectors.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* npm run debug:search
|
|
8
|
+
* npm run debug:search -- "search query"
|
|
9
|
+
*/
|
|
10
|
+
import { createBrowserContext, closeBrowser } from '../browser/context.js';
|
|
11
|
+
import { ensureAuthenticated } from '../browser/auth.js';
|
|
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 DEBUG_DIR = path.join(PROJECT_ROOT, 'debug-output');
|
|
18
|
+
async function main() {
|
|
19
|
+
const query = process.argv[2] ?? 'test';
|
|
20
|
+
console.log('š Debug Search Script\n');
|
|
21
|
+
console.log(`Query: "${query}"`);
|
|
22
|
+
// Ensure debug output directory exists
|
|
23
|
+
if (!fs.existsSync(DEBUG_DIR)) {
|
|
24
|
+
fs.mkdirSync(DEBUG_DIR, { recursive: true });
|
|
25
|
+
}
|
|
26
|
+
const manager = await createBrowserContext({ headless: false });
|
|
27
|
+
try {
|
|
28
|
+
// Authenticate
|
|
29
|
+
console.log('\n1. Authenticating...');
|
|
30
|
+
await ensureAuthenticated(manager.page, manager.context);
|
|
31
|
+
// Take initial screenshot
|
|
32
|
+
console.log('\n2. Taking initial screenshot...');
|
|
33
|
+
await manager.page.screenshot({
|
|
34
|
+
path: path.join(DEBUG_DIR, '01-initial.png'),
|
|
35
|
+
fullPage: true
|
|
36
|
+
});
|
|
37
|
+
// Wait for page to stabilise
|
|
38
|
+
await manager.page.waitForTimeout(3000);
|
|
39
|
+
// Open search with keyboard shortcut
|
|
40
|
+
console.log('\n3. Opening search (Cmd+E)...');
|
|
41
|
+
const isMac = process.platform === 'darwin';
|
|
42
|
+
await manager.page.keyboard.press(isMac ? 'Meta+e' : 'Control+e');
|
|
43
|
+
await manager.page.waitForTimeout(2000);
|
|
44
|
+
await manager.page.screenshot({
|
|
45
|
+
path: path.join(DEBUG_DIR, '02-search-opened.png'),
|
|
46
|
+
fullPage: true
|
|
47
|
+
});
|
|
48
|
+
// Find and list all inputs
|
|
49
|
+
console.log('\n4. Scanning for input elements...');
|
|
50
|
+
const inputs = await manager.page.locator('input').all();
|
|
51
|
+
console.log(` Found ${inputs.length} input elements:`);
|
|
52
|
+
for (let i = 0; i < inputs.length; i++) {
|
|
53
|
+
const input = inputs[i];
|
|
54
|
+
try {
|
|
55
|
+
const isVisible = await input.isVisible();
|
|
56
|
+
if (!isVisible)
|
|
57
|
+
continue;
|
|
58
|
+
const placeholder = await input.getAttribute('placeholder') ?? '';
|
|
59
|
+
const ariaLabel = await input.getAttribute('aria-label') ?? '';
|
|
60
|
+
const dataTid = await input.getAttribute('data-tid') ?? '';
|
|
61
|
+
const type = await input.getAttribute('type') ?? '';
|
|
62
|
+
const id = await input.getAttribute('id') ?? '';
|
|
63
|
+
console.log(` [${i}] visible=true, placeholder="${placeholder}", aria-label="${ariaLabel}", data-tid="${dataTid}", type="${type}", id="${id}"`);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
// Skip
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// Try to find and use search input
|
|
70
|
+
console.log('\n5. Looking for search input...');
|
|
71
|
+
const searchSelectors = [
|
|
72
|
+
'input[placeholder*="Search" i]',
|
|
73
|
+
'input[aria-label*="Search" i]',
|
|
74
|
+
'[data-tid*="search"] input',
|
|
75
|
+
'input[type="search"]',
|
|
76
|
+
];
|
|
77
|
+
let searchInput = null;
|
|
78
|
+
for (const sel of searchSelectors) {
|
|
79
|
+
const loc = manager.page.locator(sel).first();
|
|
80
|
+
if (await loc.count() > 0 && await loc.isVisible()) {
|
|
81
|
+
console.log(` Found with selector: ${sel}`);
|
|
82
|
+
searchInput = loc;
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (searchInput) {
|
|
87
|
+
// Type the query
|
|
88
|
+
console.log('\n6. Typing query...');
|
|
89
|
+
await searchInput.fill(query);
|
|
90
|
+
await manager.page.waitForTimeout(500);
|
|
91
|
+
await manager.page.screenshot({
|
|
92
|
+
path: path.join(DEBUG_DIR, '03-query-typed.png'),
|
|
93
|
+
fullPage: true
|
|
94
|
+
});
|
|
95
|
+
// Submit
|
|
96
|
+
console.log('\n7. Submitting search (Enter)...');
|
|
97
|
+
await manager.page.keyboard.press('Enter');
|
|
98
|
+
await manager.page.waitForTimeout(5000);
|
|
99
|
+
await manager.page.screenshot({
|
|
100
|
+
path: path.join(DEBUG_DIR, '04-results.png'),
|
|
101
|
+
fullPage: true
|
|
102
|
+
});
|
|
103
|
+
// Scan for result elements
|
|
104
|
+
console.log('\n8. Scanning for result elements...');
|
|
105
|
+
const resultSelectors = [
|
|
106
|
+
'[data-tid*="search"]',
|
|
107
|
+
'[data-tid*="result"]',
|
|
108
|
+
'[role="listitem"]',
|
|
109
|
+
'[role="option"]',
|
|
110
|
+
'.search-result',
|
|
111
|
+
'[data-tid*="message"]',
|
|
112
|
+
];
|
|
113
|
+
for (const sel of resultSelectors) {
|
|
114
|
+
const count = await manager.page.locator(sel).count();
|
|
115
|
+
if (count > 0) {
|
|
116
|
+
console.log(` ${sel}: ${count} elements`);
|
|
117
|
+
// Get first element's text content preview
|
|
118
|
+
const first = manager.page.locator(sel).first();
|
|
119
|
+
const text = await first.textContent().catch(() => null);
|
|
120
|
+
if (text) {
|
|
121
|
+
console.log(` Preview: "${text.substring(0, 80).replace(/\n/g, ' ')}..."`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// Dump page HTML structure (simplified)
|
|
126
|
+
console.log('\n9. Dumping main content structure...');
|
|
127
|
+
const mainContent = await manager.page.locator('main, [role="main"], #app, .app-container').first();
|
|
128
|
+
if (await mainContent.count() > 0) {
|
|
129
|
+
const html = await mainContent.innerHTML();
|
|
130
|
+
fs.writeFileSync(path.join(DEBUG_DIR, 'page-structure.html'), html.substring(0, 50000) // First 50KB
|
|
131
|
+
);
|
|
132
|
+
console.log(' Saved to debug-output/page-structure.html');
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
console.log(' ā Could not find search input');
|
|
137
|
+
}
|
|
138
|
+
console.log('\n10. Keeping browser open for 30 seconds for manual inspection...');
|
|
139
|
+
console.log(` Screenshots saved to: ${DEBUG_DIR}`);
|
|
140
|
+
await manager.page.waitForTimeout(30000);
|
|
141
|
+
}
|
|
142
|
+
finally {
|
|
143
|
+
await closeBrowser(manager, true);
|
|
144
|
+
}
|
|
145
|
+
console.log('\nā
Debug session complete');
|
|
146
|
+
}
|
|
147
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env npx tsx
|
|
2
|
+
/**
|
|
3
|
+
* MCP Protocol Test Harness
|
|
4
|
+
*
|
|
5
|
+
* Tests the MCP server by connecting a client through the actual MCP protocol,
|
|
6
|
+
* rather than calling underlying functions directly. This ensures the full
|
|
7
|
+
* protocol layer works correctly.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* npm run test:mcp # List tools and check status
|
|
11
|
+
* npm run test:mcp -- search "query" # Search for messages (shortcut)
|
|
12
|
+
* npm run test:mcp -- teams_search --query "q" # Generic tool call
|
|
13
|
+
* npm run test:mcp -- --json # Output as JSON
|
|
14
|
+
*
|
|
15
|
+
* Any unrecognised command is treated as a tool name. Use --key value for parameters.
|
|
16
|
+
*/
|
|
17
|
+
export {};
|