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