@mindstone/mcp-server-microsoft-teams 0.1.2 → 0.3.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.
package/dist/server.js CHANGED
@@ -1,12 +1,16 @@
1
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import { SERVER_NAME, SERVER_VERSION } from './types.js';
3
- import { registerTeamsTools } from './tools.js';
3
+ import { COMPOSE_MESSAGE_RESOURCE_URI, registerTeamsTools } from './tools.js';
4
+ import { COMPOSE_MESSAGE_HTML } from './resources/compose-message-template.js';
4
5
  export function createServer() {
5
6
  const server = new McpServer({
6
7
  name: SERVER_NAME,
7
8
  version: SERVER_VERSION,
8
9
  });
9
10
  registerTeamsTools(server);
11
+ server.registerResource('compose-message', COMPOSE_MESSAGE_RESOURCE_URI, { mimeType: 'text/html' }, async (uri) => ({
12
+ contents: [{ uri: uri.href, mimeType: 'text/html', text: COMPOSE_MESSAGE_HTML }],
13
+ }));
10
14
  return server;
11
15
  }
12
16
  //# sourceMappingURL=server.js.map
package/dist/teams.d.ts CHANGED
@@ -11,5 +11,15 @@ export declare function sendChatMessage(client: Client, args: ArgBag, signal: Ab
11
11
  export declare function listTeams(client: Client, _args: ArgBag, signal: AbortSignal): Promise<unknown>;
12
12
  export declare function listChannels(client: Client, args: ArgBag, signal: AbortSignal): Promise<unknown>;
13
13
  export declare function getPresence(client: Client, _args: ArgBag, signal: AbortSignal): Promise<unknown>;
14
+ export declare function getUserPresence(client: Client, args: ArgBag, signal: AbortSignal): Promise<unknown>;
15
+ export declare const PRESENCE_AVAILABILITY_VALUES: readonly ["Available", "Busy", "DoNotDisturb", "BeRightBack", "Away", "Offline"];
16
+ export declare function setPresence(client: Client, args: ArgBag, signal: AbortSignal): Promise<unknown>;
17
+ export declare function listChannelMessages(client: Client, args: ArgBag, signal: AbortSignal): Promise<unknown>;
18
+ export declare function sendChannelMessage(client: Client, args: ArgBag, signal: AbortSignal): Promise<unknown>;
19
+ export declare function replyToChannelMessage(client: Client, args: ArgBag, signal: AbortSignal): Promise<unknown>;
20
+ export declare function replyToChatMessage(client: Client, args: ArgBag, signal: AbortSignal): Promise<unknown>;
21
+ export declare function searchMessages(client: Client, args: ArgBag, signal: AbortSignal): Promise<unknown>;
22
+ export declare function findUser(client: Client, args: ArgBag, signal: AbortSignal): Promise<unknown>;
23
+ export declare function createChat(client: Client, args: ArgBag, signal: AbortSignal): Promise<unknown>;
14
24
  export {};
15
25
  //# sourceMappingURL=teams.d.ts.map
package/dist/teams.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { z } from 'zod';
1
2
  import { wrapUntrusted } from './untrusted-content.js';
2
3
  export class TeamsBusinessError extends Error {
3
4
  nextStep;
@@ -28,6 +29,23 @@ function clampTop(value, defaultValue, maxValue) {
28
29
  return defaultValue;
29
30
  return Math.max(1, Math.min(value, maxValue));
30
31
  }
32
+ // Structural Graph values (IDs, enum-like tokens, timestamps) are vendor-assigned
33
+ // rather than user-authored free text, so instead of an untrusted-content
34
+ // envelope — which would corrupt an ID the model must pass back verbatim in
35
+ // follow-up calls — they are validated against shapes that cannot carry
36
+ // envelope-breakout or markup characters. An unexpected value fails closed: the
37
+ // Zod error surfaces through the standard (enveloped) error path.
38
+ const graphStructuralTokenSchema = z
39
+ .string()
40
+ .max(512)
41
+ .regex(/^[^\s<>"'`\\]*$/, 'unexpected characters in Microsoft Graph identifier');
42
+ const graphIsoDateTimeSchema = z
43
+ .string()
44
+ .regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})?$/, 'unexpected Microsoft Graph datetime format');
45
+ const graphPresenceStateSchema = z
46
+ .string()
47
+ .max(64)
48
+ .regex(/^[A-Za-z][A-Za-z0-9]*$/, 'unexpected Microsoft Graph presence value');
31
49
  const HTML_ENTITIES = {
32
50
  '&nbsp;': ' ',
33
51
  '&amp;': '&',
@@ -58,15 +76,15 @@ function requireStringArg(args, name, label, nextStep) {
58
76
  return value;
59
77
  throw new TeamsBusinessError(`Missing required parameter: "${name}" (${label}).`, nextStep);
60
78
  }
61
- function formatMessage(msg) {
79
+ function formatMessage(msg, tool) {
62
80
  return {
63
- id: msg.id,
81
+ id: graphStructuralTokenSchema.nullish().parse(msg.id),
64
82
  from: msg.from?.user?.displayName
65
- ? wrapUntrusted(msg.from.user.displayName, 'microsoft-teams:list_chat_messages:from')
83
+ ? wrapUntrusted(msg.from.user.displayName, `microsoft-teams:${tool}:from`)
66
84
  : 'Unknown',
67
- content: wrapUntrusted(stripHtml(msg.body?.content ?? ''), 'microsoft-teams:list_chat_messages:content'),
68
- contentType: msg.body?.contentType,
69
- createdAt: msg.createdDateTime,
85
+ content: wrapUntrusted(stripHtml(msg.body?.content ?? ''), `microsoft-teams:${tool}:content`),
86
+ contentType: graphStructuralTokenSchema.nullish().parse(msg.body?.contentType),
87
+ createdAt: graphIsoDateTimeSchema.nullish().parse(msg.createdDateTime),
70
88
  };
71
89
  }
72
90
  export async function listChats(client, args, signal) {
@@ -85,13 +103,13 @@ export async function listChats(client, args, signal) {
85
103
  return {
86
104
  count: chats.length,
87
105
  chats: chats.map((chat) => ({
88
- id: chat.id,
106
+ id: graphStructuralTokenSchema.parse(chat.id),
89
107
  topic: chat.topic
90
108
  ? wrapUntrusted(chat.topic, 'microsoft-teams:list_chats:topic')
91
109
  : '(No topic)',
92
- type: chat.chatType,
93
- createdAt: chat.createdDateTime,
94
- lastUpdated: chat.lastUpdatedDateTime,
110
+ type: graphStructuralTokenSchema.nullish().parse(chat.chatType),
111
+ createdAt: graphIsoDateTimeSchema.nullish().parse(chat.createdDateTime),
112
+ lastUpdated: graphIsoDateTimeSchema.nullish().parse(chat.lastUpdatedDateTime),
95
113
  })),
96
114
  };
97
115
  }
@@ -103,17 +121,17 @@ export async function getChat(client, args, signal) {
103
121
  .expand('members')
104
122
  .get());
105
123
  return {
106
- id: chat.id,
124
+ id: graphStructuralTokenSchema.parse(chat.id),
107
125
  topic: chat.topic
108
126
  ? wrapUntrusted(chat.topic, 'microsoft-teams:get_chat:topic')
109
127
  : '(No topic)',
110
- type: chat.chatType,
111
- createdAt: chat.createdDateTime,
112
- lastUpdated: chat.lastUpdatedDateTime,
128
+ type: graphStructuralTokenSchema.nullish().parse(chat.chatType),
129
+ createdAt: graphIsoDateTimeSchema.nullish().parse(chat.createdDateTime),
130
+ lastUpdated: graphIsoDateTimeSchema.nullish().parse(chat.lastUpdatedDateTime),
113
131
  members: chat.members?.map((member) => ({
114
132
  displayName: wrapUntrusted(member.displayName, 'microsoft-teams:get_chat:members.displayName'),
115
133
  email: wrapUntrusted(member.email, 'microsoft-teams:get_chat:members.email'),
116
- roles: member.roles,
134
+ roles: z.array(graphStructuralTokenSchema).nullish().parse(member.roles),
117
135
  })),
118
136
  };
119
137
  }
@@ -129,13 +147,13 @@ export async function listChatMessages(client, args, signal) {
129
147
  return {
130
148
  chatId,
131
149
  count: messages.length,
132
- messages: messages.map(formatMessage),
150
+ messages: messages.map((msg) => formatMessage(msg, 'list_chat_messages')),
133
151
  };
134
152
  }
135
153
  export async function sendChatMessage(client, args, signal) {
136
154
  const chatId = requireStringArg(args, 'chatId', 'chat ID', 'send_chat_message');
137
155
  const content = requireStringArg(args, 'content', 'message body', 'send_chat_message');
138
- const response = (await client.api(`/me/chats/${chatId}/messages`).options({ signal }).post({
156
+ const response = sendMessageResponseSchema.parse(await client.api(`/me/chats/${chatId}/messages`).options({ signal }).post({
139
157
  body: {
140
158
  contentType: content.includes('<') ? 'html' : 'text',
141
159
  content,
@@ -149,10 +167,10 @@ export async function sendChatMessage(client, args, signal) {
149
167
  }
150
168
  function formatChannel(channel) {
151
169
  return {
152
- id: channel.id,
170
+ id: graphStructuralTokenSchema.parse(channel.id),
153
171
  name: wrapUntrusted(channel.displayName, 'microsoft-teams:list_channels:name'),
154
172
  description: wrapUntrusted(channel.description, 'microsoft-teams:list_channels:description'),
155
- membershipType: channel.membershipType,
173
+ membershipType: graphStructuralTokenSchema.nullish().parse(channel.membershipType),
156
174
  };
157
175
  }
158
176
  export async function listTeams(client, _args, signal) {
@@ -165,7 +183,7 @@ export async function listTeams(client, _args, signal) {
165
183
  return {
166
184
  count: teams.length,
167
185
  teams: teams.map((team) => ({
168
- id: team.id,
186
+ id: graphStructuralTokenSchema.parse(team.id),
169
187
  name: wrapUntrusted(team.displayName, 'microsoft-teams:list_teams:name'),
170
188
  description: wrapUntrusted(team.description, 'microsoft-teams:list_teams:description'),
171
189
  })),
@@ -188,9 +206,344 @@ export async function listChannels(client, args, signal) {
188
206
  export async function getPresence(client, _args, signal) {
189
207
  const presence = (await client.api('/me/presence').options({ signal }).get());
190
208
  return {
209
+ availability: graphPresenceStateSchema.nullish().parse(presence.availability),
210
+ activity: graphPresenceStateSchema.nullish().parse(presence.activity),
211
+ statusMessage: wrapUntrusted(presence.statusMessage?.message?.content ?? undefined, 'microsoft-teams:get_presence:statusMessage'),
212
+ };
213
+ }
214
+ const presenceSchema = z
215
+ .object({
216
+ availability: graphPresenceStateSchema.nullish(),
217
+ activity: graphPresenceStateSchema.nullish(),
218
+ statusMessage: z
219
+ .object({
220
+ message: z
221
+ .object({
222
+ content: z.string().nullish(),
223
+ })
224
+ .nullish(),
225
+ })
226
+ .nullish(),
227
+ })
228
+ .passthrough();
229
+ export async function getUserPresence(client, args, signal) {
230
+ const userId = requireStringArg(args, 'userId', 'user ID or email', 'get_user_presence');
231
+ const presence = presenceSchema.parse(await client.api(`/users/${encodeURIComponent(userId)}/presence`).options({ signal }).get());
232
+ return {
233
+ userId,
191
234
  availability: presence.availability,
192
235
  activity: presence.activity,
193
- statusMessage: wrapUntrusted(presence.statusMessage?.message?.content ?? undefined, 'microsoft-teams:get_presence:statusMessage'),
236
+ statusMessage: wrapUntrusted(presence.statusMessage?.message?.content ?? undefined, 'microsoft-teams:get_user_presence:statusMessage'),
237
+ };
238
+ }
239
+ export const PRESENCE_AVAILABILITY_VALUES = [
240
+ 'Available',
241
+ 'Busy',
242
+ 'DoNotDisturb',
243
+ 'BeRightBack',
244
+ 'Away',
245
+ 'Offline',
246
+ ];
247
+ export async function setPresence(client, args, signal) {
248
+ const availability = requireStringArg(args, 'availability', `presence availability (${PRESENCE_AVAILABILITY_VALUES.join(', ')})`, 'set_presence');
249
+ const duration = numberArg(args, 'durationMinutes');
250
+ // The tool schema bounds this to an integer in 5-480; keep the business layer
251
+ // fail-closed too rather than silently coercing a value into a different
252
+ // meaning before a network write.
253
+ if (duration != null && (!Number.isInteger(duration) || duration < 5 || duration > 480)) {
254
+ throw new TeamsBusinessError('Invalid "durationMinutes": must be a whole number of minutes between 5 and 480.', 'set_presence');
255
+ }
256
+ const durationMinutes = duration;
257
+ const body = { availability, activity: availability };
258
+ if (durationMinutes != null)
259
+ body.expirationDuration = `PT${durationMinutes}M`;
260
+ await client.api('/me/presence/setUserPreferredPresence').options({ signal }).post(body);
261
+ return {
262
+ success: true,
263
+ availability,
264
+ ...(durationMinutes != null ? { durationMinutes } : {}),
265
+ message: `Presence set to ${availability}`,
266
+ };
267
+ }
268
+ // ---------------------------------------------------------------------------
269
+ // Channel messages
270
+ // ---------------------------------------------------------------------------
271
+ // Every function validates the Graph strings it returns — free text via the
272
+ // untrusted-content envelope, structural values (IDs, enum-like tokens,
273
+ // datetimes) via the shape schemas above — so vendor-controlled markup never
274
+ // reaches model-visible output raw. The older functions still cast at fetch
275
+ // time for typing, but their output mappers apply the same schemas.
276
+ const graphMessageSchema = z
277
+ .object({
278
+ id: graphStructuralTokenSchema.optional(),
279
+ replyToId: graphStructuralTokenSchema.nullish(),
280
+ from: z
281
+ .object({
282
+ user: z
283
+ .object({
284
+ id: graphStructuralTokenSchema.nullish(),
285
+ displayName: z.string().nullish(),
286
+ })
287
+ .nullish(),
288
+ })
289
+ .nullish(),
290
+ body: z
291
+ .object({
292
+ content: z.string().nullish(),
293
+ contentType: graphStructuralTokenSchema.nullish(),
294
+ })
295
+ .nullish(),
296
+ createdDateTime: graphIsoDateTimeSchema.nullish(),
297
+ })
298
+ .passthrough();
299
+ const graphMessageCollectionSchema = z
300
+ .object({
301
+ value: z.array(graphMessageSchema).nullish(),
302
+ '@odata.nextLink': z.string().nullish(),
303
+ })
304
+ .passthrough();
305
+ const sendMessageResponseSchema = z
306
+ .object({
307
+ id: graphStructuralTokenSchema.optional(),
308
+ })
309
+ .passthrough();
310
+ function messagePostBody(content) {
311
+ return {
312
+ body: {
313
+ contentType: content.includes('<') ? 'html' : 'text',
314
+ content,
315
+ },
316
+ };
317
+ }
318
+ export async function listChannelMessages(client, args, signal) {
319
+ const teamId = requireStringArg(args, 'teamId', 'team ID', 'list_channel_messages');
320
+ const channelId = requireStringArg(args, 'channelId', 'channel ID', 'list_channel_messages');
321
+ const top = clampTop(numberArg(args, 'top'), 25, 50);
322
+ const response = graphMessageCollectionSchema.parse(await client
323
+ .api(`/teams/${teamId}/channels/${channelId}/messages`)
324
+ .options({ signal })
325
+ .top(top)
326
+ .get());
327
+ const messages = response.value ?? [];
328
+ return {
329
+ teamId,
330
+ channelId,
331
+ count: messages.length,
332
+ hasMore: Boolean(response['@odata.nextLink']),
333
+ messages: messages.map((msg) => ({
334
+ ...formatMessage(msg, 'list_channel_messages'),
335
+ replyToId: msg.replyToId ?? undefined,
336
+ })),
337
+ };
338
+ }
339
+ export async function sendChannelMessage(client, args, signal) {
340
+ const teamId = requireStringArg(args, 'teamId', 'team ID', 'send_channel_message');
341
+ const channelId = requireStringArg(args, 'channelId', 'channel ID', 'send_channel_message');
342
+ const content = requireStringArg(args, 'content', 'message body', 'send_channel_message');
343
+ const response = sendMessageResponseSchema.parse(await client
344
+ .api(`/teams/${teamId}/channels/${channelId}/messages`)
345
+ .options({ signal })
346
+ .post(messagePostBody(content)));
347
+ return {
348
+ success: true,
349
+ messageId: response.id,
350
+ message: 'Message sent successfully',
351
+ };
352
+ }
353
+ export async function replyToChannelMessage(client, args, signal) {
354
+ const teamId = requireStringArg(args, 'teamId', 'team ID', 'reply_to_channel_message');
355
+ const channelId = requireStringArg(args, 'channelId', 'channel ID', 'reply_to_channel_message');
356
+ const messageId = requireStringArg(args, 'messageId', 'channel message ID', 'reply_to_channel_message');
357
+ const content = requireStringArg(args, 'content', 'reply body', 'reply_to_channel_message');
358
+ const response = sendMessageResponseSchema.parse(await client
359
+ .api(`/teams/${teamId}/channels/${channelId}/messages/${messageId}/replies`)
360
+ .options({ signal })
361
+ .post(messagePostBody(content)));
362
+ return {
363
+ success: true,
364
+ messageId: response.id,
365
+ message: 'Reply sent successfully',
366
+ };
367
+ }
368
+ export async function replyToChatMessage(client, args, signal) {
369
+ const chatId = requireStringArg(args, 'chatId', 'chat ID', 'reply_to_message');
370
+ const messageId = requireStringArg(args, 'messageId', 'message ID', 'reply_to_message');
371
+ const content = requireStringArg(args, 'content', 'reply body', 'reply_to_message');
372
+ const response = sendMessageResponseSchema.parse(await client
373
+ .api(`/me/chats/${chatId}/messages/${messageId}/replies`)
374
+ .options({ signal })
375
+ .post(messagePostBody(content)));
376
+ return {
377
+ success: true,
378
+ messageId: response.id,
379
+ message: 'Reply sent successfully',
380
+ };
381
+ }
382
+ // ---------------------------------------------------------------------------
383
+ // Message search
384
+ // ---------------------------------------------------------------------------
385
+ const searchHitSchema = z
386
+ .object({
387
+ hitId: z.string().nullish(),
388
+ summary: z.string().nullish(),
389
+ resource: graphMessageSchema.extend({ chatId: graphStructuralTokenSchema.nullish() }).nullish(),
390
+ })
391
+ .passthrough();
392
+ const searchResponseSchema = z
393
+ .object({
394
+ value: z
395
+ .array(z
396
+ .object({
397
+ hitsContainers: z
398
+ .array(z
399
+ .object({
400
+ hits: z.array(searchHitSchema).nullish(),
401
+ total: z.number().nullish(),
402
+ })
403
+ .passthrough())
404
+ .nullish(),
405
+ })
406
+ .passthrough())
407
+ .nullish(),
408
+ })
409
+ .passthrough();
410
+ export async function searchMessages(client, args, signal) {
411
+ const query = requireStringArg(args, 'query', 'search text', 'search_messages');
412
+ const top = clampTop(numberArg(args, 'top'), 10, 25);
413
+ const response = searchResponseSchema.parse(await client
414
+ .api('/search/query')
415
+ .options({ signal })
416
+ .post({
417
+ requests: [
418
+ {
419
+ entityTypes: ['chatMessage'],
420
+ query: { queryString: query },
421
+ from: 0,
422
+ size: top,
423
+ },
424
+ ],
425
+ }));
426
+ const container = response.value?.[0]?.hitsContainers?.[0];
427
+ const hits = container?.hits ?? [];
428
+ return {
429
+ query,
430
+ count: hits.length,
431
+ total: container?.total ?? hits.length,
432
+ results: hits.map((hit) => ({
433
+ ...formatMessage(hit.resource ?? {}, 'search_messages'),
434
+ chatId: hit.resource?.chatId ?? undefined,
435
+ summary: wrapUntrusted(hit.summary ? stripHtml(hit.summary) : undefined, 'microsoft-teams:search_messages:summary'),
436
+ })),
437
+ };
438
+ }
439
+ // ---------------------------------------------------------------------------
440
+ // User lookup and chat creation
441
+ // ---------------------------------------------------------------------------
442
+ const graphUserSchema = z
443
+ .object({
444
+ id: graphStructuralTokenSchema,
445
+ displayName: z.string().nullish(),
446
+ mail: z.string().nullish(),
447
+ userPrincipalName: z.string().nullish(),
448
+ })
449
+ .passthrough();
450
+ const graphUserCollectionSchema = z
451
+ .object({
452
+ value: z.array(graphUserSchema).nullish(),
453
+ '@odata.nextLink': z.string().nullish(),
454
+ })
455
+ .passthrough();
456
+ function formatUser(user, tool) {
457
+ return {
458
+ id: user.id,
459
+ displayName: wrapUntrusted(user.displayName ?? undefined, `microsoft-teams:${tool}:displayName`),
460
+ email: wrapUntrusted(user.mail ?? user.userPrincipalName ?? undefined, `microsoft-teams:${tool}:email`),
461
+ };
462
+ }
463
+ export async function findUser(client, args, signal) {
464
+ const query = requireStringArg(args, 'query', 'name or email address', 'find_user');
465
+ if (query.includes('@')) {
466
+ try {
467
+ const user = graphUserSchema.parse(await client
468
+ .api(`/users/${encodeURIComponent(query)}`)
469
+ .options({ signal })
470
+ .select('id,displayName,mail,userPrincipalName')
471
+ .get());
472
+ return { count: 1, hasMore: false, users: [formatUser(user, 'find_user')] };
473
+ }
474
+ catch (err) {
475
+ // A 404 here means "no such user", which is a result, not a failure.
476
+ if (err?.statusCode === 404) {
477
+ return { count: 0, hasMore: false, users: [] };
478
+ }
479
+ throw err;
480
+ }
481
+ }
482
+ // $search on /users requires the ConsistencyLevel: eventual header; strip
483
+ // characters that would break out of the quoted search expression.
484
+ const safeQuery = query.replace(/["\\]/g, ' ').trim();
485
+ const response = graphUserCollectionSchema.parse(await client
486
+ .api('/users')
487
+ .options({ signal })
488
+ .header('ConsistencyLevel', 'eventual')
489
+ .search(`"displayName:${safeQuery}"`)
490
+ .select('id,displayName,mail,userPrincipalName')
491
+ .top(10)
492
+ .get());
493
+ const users = response.value ?? [];
494
+ // Name searches are capped at 10 results; surface Graph's continuation link
495
+ // so a truncated result is never mistaken for a complete one.
496
+ return {
497
+ count: users.length,
498
+ hasMore: Boolean(response['@odata.nextLink']),
499
+ users: users.map((user) => formatUser(user, 'find_user')),
500
+ };
501
+ }
502
+ function requireStringArrayArg(args, name, label, nextStep) {
503
+ const value = args[name];
504
+ if (Array.isArray(value) && value.length > 0) {
505
+ const members = value.map((entry) => (typeof entry === 'string' ? entry.trim() : ''));
506
+ if (members.every((entry) => entry.length > 0))
507
+ return members;
508
+ }
509
+ throw new TeamsBusinessError(`Missing required parameter: "${name}" (${label}). Provide a non-empty array of email addresses or user IDs.`, nextStep);
510
+ }
511
+ const createdChatSchema = z
512
+ .object({
513
+ id: graphStructuralTokenSchema,
514
+ chatType: z.enum(['oneOnOne', 'group', 'meeting']).nullish(),
515
+ })
516
+ .passthrough();
517
+ export async function createChat(client, args, signal) {
518
+ const members = [...new Set(requireStringArrayArg(args, 'members', 'chat participants', 'create_chat'))];
519
+ const topic = stringArg(args, 'topic');
520
+ const chatType = members.length === 1 ? 'oneOnOne' : 'group';
521
+ const memberBindings = [
522
+ {
523
+ '@odata.type': '#microsoft.graph.aadUserConversationMember',
524
+ roles: ['owner'],
525
+ 'user@odata.bind': 'https://graph.microsoft.com/v1.0/me',
526
+ },
527
+ ...members.map((member) => ({
528
+ '@odata.type': '#microsoft.graph.aadUserConversationMember',
529
+ roles: ['owner'],
530
+ 'user@odata.bind': `https://graph.microsoft.com/v1.0/users/${encodeURIComponent(member)}`,
531
+ })),
532
+ ];
533
+ const chat = createdChatSchema.parse(await client
534
+ .api('/chats')
535
+ .options({ signal })
536
+ .post({
537
+ chatType,
538
+ // Topic is only valid on group chats.
539
+ ...(chatType === 'group' && topic ? { topic } : {}),
540
+ 'members@odata.bind': memberBindings,
541
+ }));
542
+ return {
543
+ success: true,
544
+ chatId: chat.id,
545
+ chatType: chat.chatType ?? chatType,
546
+ message: 'Chat created successfully',
194
547
  };
195
548
  }
196
549
  //# sourceMappingURL=teams.js.map
package/dist/tools.d.ts CHANGED
@@ -1,3 +1,10 @@
1
1
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ /**
3
+ * `ui://` resource URI the compose_chat_message iframe is served under. The
4
+ * shared compose-app template posts `send_chat_message` when the user clicks
5
+ * Send; the HTML twin lives in src/resources/compose-message-template.ts
6
+ * (generated from the shared compose-app package, drift-gated).
7
+ */
8
+ export declare const COMPOSE_MESSAGE_RESOURCE_URI = "ui://microsoft-teams/compose-message";
2
9
  export declare function registerTeamsTools(server: McpServer): void;
3
10
  //# sourceMappingURL=tools.d.ts.map