msteams-mcp 0.2.1
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.
Potentially problematic release.
This version of msteams-mcp might be problematic. Click here for more details.
- package/LICENSE +21 -0
- package/README.md +261 -0
- package/dist/__fixtures__/api-responses.d.ts +254 -0
- package/dist/__fixtures__/api-responses.js +245 -0
- package/dist/api/calendar-api.d.ts +66 -0
- package/dist/api/calendar-api.js +179 -0
- package/dist/api/chatsvc-api.d.ts +352 -0
- package/dist/api/chatsvc-api.js +1100 -0
- package/dist/api/csa-api.d.ts +64 -0
- package/dist/api/csa-api.js +200 -0
- package/dist/api/index.d.ts +7 -0
- package/dist/api/index.js +7 -0
- package/dist/api/substrate-api.d.ts +50 -0
- package/dist/api/substrate-api.js +305 -0
- package/dist/auth/crypto.d.ts +32 -0
- package/dist/auth/crypto.js +66 -0
- package/dist/auth/index.d.ts +7 -0
- package/dist/auth/index.js +7 -0
- package/dist/auth/session-store.d.ts +87 -0
- package/dist/auth/session-store.js +230 -0
- package/dist/auth/token-extractor.d.ts +185 -0
- package/dist/auth/token-extractor.js +674 -0
- package/dist/auth/token-refresh.d.ts +25 -0
- package/dist/auth/token-refresh.js +85 -0
- package/dist/browser/auth.d.ts +53 -0
- package/dist/browser/auth.js +603 -0
- package/dist/browser/context.d.ts +40 -0
- package/dist/browser/context.js +122 -0
- package/dist/constants.d.ts +104 -0
- package/dist/constants.js +195 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +12 -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 +270 -0
- package/dist/research/search-research.d.ts +17 -0
- package/dist/research/search-research.js +317 -0
- package/dist/server.d.ts +66 -0
- package/dist/server.js +295 -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 +474 -0
- package/dist/tools/auth-tools.d.ts +26 -0
- package/dist/tools/auth-tools.js +191 -0
- package/dist/tools/index.d.ts +56 -0
- package/dist/tools/index.js +34 -0
- package/dist/tools/meeting-tools.d.ts +33 -0
- package/dist/tools/meeting-tools.js +64 -0
- package/dist/tools/message-tools.d.ts +269 -0
- package/dist/tools/message-tools.js +856 -0
- package/dist/tools/people-tools.d.ts +46 -0
- package/dist/tools/people-tools.js +112 -0
- package/dist/tools/registry.d.ts +23 -0
- package/dist/tools/registry.js +63 -0
- package/dist/tools/search-tools.d.ts +91 -0
- package/dist/tools/search-tools.js +222 -0
- package/dist/types/errors.d.ts +58 -0
- package/dist/types/errors.js +132 -0
- package/dist/types/result.d.ts +43 -0
- package/dist/types/result.js +51 -0
- package/dist/types/server.d.ts +27 -0
- package/dist/types/server.js +7 -0
- package/dist/types/teams.d.ts +85 -0
- package/dist/types/teams.js +4 -0
- package/dist/utils/api-config.d.ts +103 -0
- package/dist/utils/api-config.js +158 -0
- package/dist/utils/auth-guards.d.ts +67 -0
- package/dist/utils/auth-guards.js +147 -0
- package/dist/utils/http.d.ts +29 -0
- package/dist/utils/http.js +112 -0
- package/dist/utils/parsers.d.ts +247 -0
- package/dist/utils/parsers.js +731 -0
- package/dist/utils/parsers.test.d.ts +7 -0
- package/dist/utils/parsers.test.js +511 -0
- package/package.json +62 -0
package/dist/server.js
ADDED
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Server implementation for Teams search.
|
|
3
|
+
* Exposes tools and resources for interacting with Microsoft Teams.
|
|
4
|
+
*/
|
|
5
|
+
import { createRequire } from 'module';
|
|
6
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
7
|
+
const require = createRequire(import.meta.url);
|
|
8
|
+
const pkg = require('../package.json');
|
|
9
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
10
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
11
|
+
import { createBrowserContext, closeBrowser } from './browser/context.js';
|
|
12
|
+
import { ensureAuthenticated } from './browser/auth.js';
|
|
13
|
+
// Auth modules
|
|
14
|
+
import { hasSessionState, isSessionLikelyExpired, } from './auth/session-store.js';
|
|
15
|
+
import { getSubstrateTokenStatus, extractMessageAuth, extractCsaToken, getUserProfile, } from './auth/token-extractor.js';
|
|
16
|
+
// API modules
|
|
17
|
+
import { getFavorites } from './api/csa-api.js';
|
|
18
|
+
// Tool registry
|
|
19
|
+
import { getToolDefinitions, invokeTool } from './tools/registry.js';
|
|
20
|
+
// Types
|
|
21
|
+
import { ErrorCode, createError } from './types/errors.js';
|
|
22
|
+
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
23
|
+
// MCP Server Class
|
|
24
|
+
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
25
|
+
/**
|
|
26
|
+
* MCP Server for Teams integration.
|
|
27
|
+
*
|
|
28
|
+
* Encapsulates all server state to allow multiple instances.
|
|
29
|
+
* Implements ITeamsServer interface for use by tool handlers.
|
|
30
|
+
*/
|
|
31
|
+
export class TeamsServer {
|
|
32
|
+
browserManager = null;
|
|
33
|
+
isInitialised = false;
|
|
34
|
+
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
35
|
+
// Response Formatting
|
|
36
|
+
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
37
|
+
/**
|
|
38
|
+
* Returns a standard MCP error response.
|
|
39
|
+
*/
|
|
40
|
+
formatError(error) {
|
|
41
|
+
return {
|
|
42
|
+
content: [
|
|
43
|
+
{
|
|
44
|
+
type: 'text',
|
|
45
|
+
text: JSON.stringify({
|
|
46
|
+
success: false,
|
|
47
|
+
error: error.message,
|
|
48
|
+
errorCode: error.code,
|
|
49
|
+
retryable: error.retryable,
|
|
50
|
+
retryAfterMs: error.retryAfterMs,
|
|
51
|
+
suggestions: error.suggestions,
|
|
52
|
+
}, null, 2),
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
isError: true,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Returns a standard MCP success response.
|
|
60
|
+
*/
|
|
61
|
+
formatSuccess(data) {
|
|
62
|
+
return {
|
|
63
|
+
content: [
|
|
64
|
+
{
|
|
65
|
+
type: 'text',
|
|
66
|
+
text: JSON.stringify({ success: true, ...data }, null, 2),
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
72
|
+
// Browser State Management (exposed for tool handlers)
|
|
73
|
+
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
74
|
+
/**
|
|
75
|
+
* Gets the current browser manager.
|
|
76
|
+
*/
|
|
77
|
+
getBrowserManager() {
|
|
78
|
+
return this.browserManager;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Sets the browser manager.
|
|
82
|
+
*/
|
|
83
|
+
setBrowserManager(manager) {
|
|
84
|
+
this.browserManager = manager;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Resets browser state.
|
|
88
|
+
*/
|
|
89
|
+
resetBrowserState() {
|
|
90
|
+
this.browserManager = null;
|
|
91
|
+
this.isInitialised = false;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Marks the server as initialised.
|
|
95
|
+
*/
|
|
96
|
+
markInitialised() {
|
|
97
|
+
this.isInitialised = true;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Checks if the server is initialised.
|
|
101
|
+
*/
|
|
102
|
+
isInitialisedState() {
|
|
103
|
+
return this.isInitialised;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Ensures the browser is running and authenticated.
|
|
107
|
+
*/
|
|
108
|
+
async ensureBrowser(headless = true) {
|
|
109
|
+
if (this.browserManager && this.isInitialised) {
|
|
110
|
+
return this.browserManager;
|
|
111
|
+
}
|
|
112
|
+
// Close existing browser if any
|
|
113
|
+
if (this.browserManager) {
|
|
114
|
+
try {
|
|
115
|
+
await closeBrowser(this.browserManager, true);
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
// Ignore cleanup errors
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
this.browserManager = await createBrowserContext({ headless });
|
|
122
|
+
await ensureAuthenticated(this.browserManager.page, this.browserManager.context, (msg) => console.error(`[auth] ${msg}`));
|
|
123
|
+
this.isInitialised = true;
|
|
124
|
+
return this.browserManager;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Cleans up browser resources.
|
|
128
|
+
*/
|
|
129
|
+
async cleanup() {
|
|
130
|
+
if (this.browserManager) {
|
|
131
|
+
await closeBrowser(this.browserManager, true);
|
|
132
|
+
this.browserManager = null;
|
|
133
|
+
this.isInitialised = false;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
137
|
+
// Server Creation
|
|
138
|
+
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
139
|
+
/**
|
|
140
|
+
* Creates and configures the MCP server.
|
|
141
|
+
*/
|
|
142
|
+
async createServer() {
|
|
143
|
+
const server = new Server({
|
|
144
|
+
name: 'teams-mcp',
|
|
145
|
+
version: pkg.version,
|
|
146
|
+
}, {
|
|
147
|
+
capabilities: {
|
|
148
|
+
tools: {},
|
|
149
|
+
resources: {},
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
// Handle resource listing
|
|
153
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
154
|
+
return {
|
|
155
|
+
resources: [
|
|
156
|
+
{
|
|
157
|
+
uri: 'teams://me/profile',
|
|
158
|
+
name: 'Current User Profile',
|
|
159
|
+
description: 'The authenticated user\'s Teams profile including email and display name',
|
|
160
|
+
mimeType: 'application/json',
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
uri: 'teams://me/favorites',
|
|
164
|
+
name: 'Pinned Conversations',
|
|
165
|
+
description: 'The user\'s favourite/pinned Teams conversations',
|
|
166
|
+
mimeType: 'application/json',
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
uri: 'teams://status',
|
|
170
|
+
name: 'Authentication Status',
|
|
171
|
+
description: 'Current authentication status for all Teams APIs',
|
|
172
|
+
mimeType: 'application/json',
|
|
173
|
+
},
|
|
174
|
+
],
|
|
175
|
+
};
|
|
176
|
+
});
|
|
177
|
+
// Handle resource reading
|
|
178
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
179
|
+
const { uri } = request.params;
|
|
180
|
+
switch (uri) {
|
|
181
|
+
case 'teams://me/profile': {
|
|
182
|
+
const profile = getUserProfile();
|
|
183
|
+
return {
|
|
184
|
+
contents: [
|
|
185
|
+
{
|
|
186
|
+
uri,
|
|
187
|
+
mimeType: 'application/json',
|
|
188
|
+
text: JSON.stringify(profile ?? { error: 'No valid session' }, null, 2),
|
|
189
|
+
},
|
|
190
|
+
],
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
case 'teams://me/favorites': {
|
|
194
|
+
const result = await getFavorites();
|
|
195
|
+
return {
|
|
196
|
+
contents: [
|
|
197
|
+
{
|
|
198
|
+
uri,
|
|
199
|
+
mimeType: 'application/json',
|
|
200
|
+
text: JSON.stringify(result.ok ? result.value.favorites : { error: result.error.message }, null, 2),
|
|
201
|
+
},
|
|
202
|
+
],
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
case 'teams://status': {
|
|
206
|
+
const tokenStatus = getSubstrateTokenStatus();
|
|
207
|
+
const messageAuth = extractMessageAuth();
|
|
208
|
+
const csaToken = extractCsaToken();
|
|
209
|
+
const status = {
|
|
210
|
+
directApi: {
|
|
211
|
+
available: tokenStatus.hasToken,
|
|
212
|
+
expiresAt: tokenStatus.expiresAt,
|
|
213
|
+
minutesRemaining: tokenStatus.minutesRemaining,
|
|
214
|
+
},
|
|
215
|
+
messaging: {
|
|
216
|
+
available: messageAuth !== null,
|
|
217
|
+
},
|
|
218
|
+
favorites: {
|
|
219
|
+
available: messageAuth !== null && csaToken !== null,
|
|
220
|
+
},
|
|
221
|
+
session: {
|
|
222
|
+
exists: hasSessionState(),
|
|
223
|
+
likelyExpired: isSessionLikelyExpired(),
|
|
224
|
+
},
|
|
225
|
+
};
|
|
226
|
+
return {
|
|
227
|
+
contents: [
|
|
228
|
+
{
|
|
229
|
+
uri,
|
|
230
|
+
mimeType: 'application/json',
|
|
231
|
+
text: JSON.stringify(status, null, 2),
|
|
232
|
+
},
|
|
233
|
+
],
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
default:
|
|
237
|
+
throw new Error(`Unknown resource: ${uri}`);
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
// Handle tool listing
|
|
241
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
242
|
+
return { tools: getToolDefinitions() };
|
|
243
|
+
});
|
|
244
|
+
// Handle tool calls
|
|
245
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
246
|
+
const { name, arguments: args } = request.params;
|
|
247
|
+
try {
|
|
248
|
+
const ctx = { server: this };
|
|
249
|
+
const result = await invokeTool(name, args, ctx);
|
|
250
|
+
if (result.success) {
|
|
251
|
+
return this.formatSuccess(result.data);
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
return this.formatError(result.error);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
catch (error) {
|
|
258
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
259
|
+
return this.formatError(createError(ErrorCode.UNKNOWN, message, { retryable: false }));
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
// Cleanup on server close
|
|
263
|
+
server.onclose = async () => {
|
|
264
|
+
await this.cleanup();
|
|
265
|
+
};
|
|
266
|
+
return server;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
270
|
+
// Exports
|
|
271
|
+
// āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
272
|
+
/**
|
|
273
|
+
* Creates and runs the MCP server.
|
|
274
|
+
* Exported for backward compatibility.
|
|
275
|
+
*/
|
|
276
|
+
export async function createServer() {
|
|
277
|
+
const teamsServer = new TeamsServer();
|
|
278
|
+
return teamsServer.createServer();
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Runs the server with stdio transport.
|
|
282
|
+
*/
|
|
283
|
+
export async function runServer() {
|
|
284
|
+
const teamsServer = new TeamsServer();
|
|
285
|
+
const server = await teamsServer.createServer();
|
|
286
|
+
const transport = new StdioServerTransport();
|
|
287
|
+
await server.connect(transport);
|
|
288
|
+
// Handle shutdown signals
|
|
289
|
+
const shutdown = async () => {
|
|
290
|
+
await teamsServer.cleanup();
|
|
291
|
+
process.exit(0);
|
|
292
|
+
};
|
|
293
|
+
process.on('SIGINT', shutdown);
|
|
294
|
+
process.on('SIGTERM', shutdown);
|
|
295
|
+
}
|
|
@@ -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 {};
|