msteams-mcp 0.23.1 → 0.25.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.
Files changed (50) hide show
  1. package/README.md +7 -5
  2. package/dist/api/chatsvc-activity.js +1 -2
  3. package/dist/api/chatsvc-api.d.ts +2 -0
  4. package/dist/api/chatsvc-api.js +2 -0
  5. package/dist/api/chatsvc-conversations.d.ts +37 -0
  6. package/dist/api/chatsvc-conversations.js +85 -0
  7. package/dist/api/chatsvc-conversations.test.d.ts +4 -0
  8. package/dist/api/chatsvc-conversations.test.js +51 -0
  9. package/dist/api/chatsvc-messaging.d.ts +167 -3
  10. package/dist/api/chatsvc-messaging.js +401 -32
  11. package/dist/api/chatsvc-messaging.test.js +204 -1
  12. package/dist/api/chatsvc-readstatus.d.ts +4 -0
  13. package/dist/api/chatsvc-readstatus.js +46 -19
  14. package/dist/api/index.d.ts +1 -0
  15. package/dist/api/index.js +1 -0
  16. package/dist/api/profile-api.d.ts +47 -0
  17. package/dist/api/profile-api.js +96 -0
  18. package/dist/auth/token-extractor.js +7 -6
  19. package/dist/browser/auth.js +9 -6
  20. package/dist/browser/context.js +0 -9
  21. package/dist/constants.d.ts +10 -0
  22. package/dist/constants.js +13 -0
  23. package/dist/server.js +4 -4
  24. package/dist/tools/auth-tools.js +6 -8
  25. package/dist/tools/message-tools.d.ts +120 -10
  26. package/dist/tools/message-tools.js +180 -10
  27. package/dist/tools/people-tools.d.ts +14 -0
  28. package/dist/tools/people-tools.js +37 -1
  29. package/dist/tools/search-tools.d.ts +54 -10
  30. package/dist/tools/search-tools.js +41 -7
  31. package/dist/tools/search-tools.test.js +15 -1
  32. package/dist/types/errors.js +2 -2
  33. package/dist/types/errors.test.js +1 -1
  34. package/dist/types/result.d.ts +0 -16
  35. package/dist/types/result.js +0 -33
  36. package/dist/types/result.test.js +1 -61
  37. package/dist/utils/api-config.d.ts +12 -0
  38. package/dist/utils/api-config.js +17 -0
  39. package/dist/utils/auth-guards.d.ts +14 -0
  40. package/dist/utils/auth-guards.js +18 -0
  41. package/dist/utils/parsers-html.d.ts +10 -0
  42. package/dist/utils/parsers-html.js +37 -0
  43. package/dist/utils/parsers-identifiers.d.ts +21 -0
  44. package/dist/utils/parsers-identifiers.js +38 -0
  45. package/dist/utils/parsers-reactions.d.ts +38 -0
  46. package/dist/utils/parsers-reactions.js +123 -0
  47. package/dist/utils/parsers.d.ts +4 -2
  48. package/dist/utils/parsers.js +4 -2
  49. package/dist/utils/parsers.test.js +168 -1
  50. package/package.json +1 -4
package/README.md CHANGED
@@ -76,8 +76,9 @@ The server uses your system's Chrome (macOS/Linux) or Edge (Windows) for authent
76
76
  |------|-------------|
77
77
  | `teams_search` | Search Teams messages with operators (`from:`, `sent:`, `in:`, `hasattachment:`, etc.) |
78
78
  | `teams_search_email` | Search emails in your mailbox (same auth as Teams — no extra login) |
79
- | `teams_get_message` | Get a single message by ID with full content (any age) |
80
- | `teams_get_thread` | Get messages from a conversation/thread |
79
+ | `teams_list_chats` | List recent conversations (1:1, group, meeting, channel) with a last-message preview |
80
+ | `teams_get_message` | Get a single message by ID with full content (any age); includes reactions |
81
+ | `teams_get_thread` | Get messages from a conversation/thread; includes reactions; `threadRootId` scopes to one channel thread; `fromUrl` accepts a Teams message deep link |
81
82
  | `teams_find_channel` | Find channels by name (your teams + org-wide discovery) |
82
83
  | `teams_get_activity` | Get activity feed (mentions, reactions, replies, notifications) |
83
84
 
@@ -85,8 +86,9 @@ The server uses your system's Chrome (macOS/Linux) or Edge (Windows) for authent
85
86
 
86
87
  | Tool | Description |
87
88
  |------|-------------|
88
- | `teams_send_message` | Send a message (default: self-chat/notes). Use `replyToMessageId` for thread replies |
89
- | `teams_edit_message` | Edit one of your own messages |
89
+ | `teams_send_message` | Send a message (default: self-chat/notes). `replyToMessageId` for thread replies, `subject` for a new channel thread, `scheduleAt` to schedule, `contentType` (`auto`/`text`/`html`/`markdown`) to control formatting |
90
+ | `teams_wait_for_reply` | Block until a new message arrives (server-side poll, capped ~110s); idempotent `after`/`nextAfter` cursor — pair with `teams_send_message` |
91
+ | `teams_edit_message` | Edit one of your own messages (`contentType` supported) |
90
92
  | `teams_delete_message` | Delete one of your own messages (soft delete) |
91
93
 
92
94
  ### People & Contacts
@@ -96,6 +98,7 @@ The server uses your system's Chrome (macOS/Linux) or Edge (Windows) for authent
96
98
  | `teams_get_me` | Get current user profile (email, name, ID) |
97
99
  | `teams_search_people` | Search for people by name or email |
98
100
  | `teams_get_frequent_contacts` | Get frequently contacted people (useful for name resolution) |
101
+ | `teams_get_person` | Resolve one or more MRIs to full profiles (name, email, job title, department) |
99
102
  | `teams_get_chat` | Get conversation ID for 1:1 chat with a person |
100
103
  | `teams_create_group_chat` | Create a new group chat with multiple people (2+ others) |
101
104
 
@@ -257,7 +260,6 @@ Development commands:
257
260
  npm run dev # Run MCP server in dev mode
258
261
  npm run build # Compile TypeScript
259
262
  npm run lint # Run ESLint
260
- npm run research # Explore Teams APIs (logs network calls)
261
263
  npm test # Run unit tests
262
264
  npm run typecheck # TypeScript type checking
263
265
  ```
@@ -72,8 +72,7 @@ export async function getActivityFeed(options = {}) {
72
72
  let syncState;
73
73
  if (metadata?.syncState) {
74
74
  try {
75
- const metaUrl = new URL(metadata.syncState);
76
- syncState = metaUrl.searchParams.get('syncState') ?? undefined;
75
+ syncState = new URL(metadata.syncState).searchParams.get('syncState') ?? undefined;
77
76
  }
78
77
  catch {
79
78
  syncState = metadata.syncState;
@@ -10,6 +10,7 @@
10
10
  * - chatsvc-reactions: add/remove emoji reactions
11
11
  * - chatsvc-virtual: saved messages, followed threads, save/unsave
12
12
  * - chatsvc-readstatus: consumption horizons, mark as read, unread counts
13
+ * - chatsvc-conversations: recent conversation list (chats, groups, channels)
13
14
  * - chatsvc-common: shared utilities (date formatting)
14
15
  */
15
16
  export * from './chatsvc-messaging.js';
@@ -17,4 +18,5 @@ export * from './chatsvc-activity.js';
17
18
  export * from './chatsvc-reactions.js';
18
19
  export * from './chatsvc-virtual.js';
19
20
  export * from './chatsvc-readstatus.js';
21
+ export * from './chatsvc-conversations.js';
20
22
  export * from './chatsvc-common.js';
@@ -10,6 +10,7 @@
10
10
  * - chatsvc-reactions: add/remove emoji reactions
11
11
  * - chatsvc-virtual: saved messages, followed threads, save/unsave
12
12
  * - chatsvc-readstatus: consumption horizons, mark as read, unread counts
13
+ * - chatsvc-conversations: recent conversation list (chats, groups, channels)
13
14
  * - chatsvc-common: shared utilities (date formatting)
14
15
  */
15
16
  // Re-export everything from sub-modules to maintain backward compatibility
@@ -18,4 +19,5 @@ export * from './chatsvc-activity.js';
18
19
  export * from './chatsvc-reactions.js';
19
20
  export * from './chatsvc-virtual.js';
20
21
  export * from './chatsvc-readstatus.js';
22
+ export * from './chatsvc-conversations.js';
21
23
  export * from './chatsvc-common.js';
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Chat Service API - Conversation list.
3
+ *
4
+ * Lists the user's recent conversations (1:1 chats, group chats, meetings and
5
+ * channels) with a last-message preview, via the chatsvc conversations endpoint.
6
+ */
7
+ import { type Result } from '../types/result.js';
8
+ /** A summarised conversation as returned by the chat list. */
9
+ export interface ChatSummary {
10
+ conversationId: string;
11
+ /** Channel/group topic, or empty for 1:1 chats. */
12
+ topic: string;
13
+ chatType: 'channel' | 'meeting' | 'group' | 'oneOnOne' | 'chat';
14
+ /** ISO timestamp of the last message, or empty if unknown. */
15
+ lastMessageTime: string;
16
+ /** Display name of the last message's sender. */
17
+ lastMessageFrom: string;
18
+ /** Plain-text preview of the last message, truncated. */
19
+ lastMessagePreview: string;
20
+ }
21
+ /** Result of listing conversations. */
22
+ export interface GetConversationsResult {
23
+ conversations: ChatSummary[];
24
+ }
25
+ /**
26
+ * Parses a raw chatsvc conversation into a {@link ChatSummary}, or null if it
27
+ * has no usable ID. Pure — no IO — so the field mapping and truncation are
28
+ * unit-testable against fixture JSON.
29
+ */
30
+ export declare function parseConversation(raw: Record<string, unknown>): ChatSummary | null;
31
+ /**
32
+ * Lists the user's recent conversations (chats, group chats, meetings and
33
+ * channels), newest activity first, capped at `limit`.
34
+ */
35
+ export declare function getConversations(options?: {
36
+ limit?: number;
37
+ }): Promise<Result<GetConversationsResult>>;
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Chat Service API - Conversation list.
3
+ *
4
+ * Lists the user's recent conversations (1:1 chats, group chats, meetings and
5
+ * channels) with a last-message preview, via the chatsvc conversations endpoint.
6
+ */
7
+ import { httpRequest } from '../utils/http.js';
8
+ import { CHATSVC_API, getSkypeAuthHeaders } from '../utils/api-config.js';
9
+ import { ok } from '../types/result.js';
10
+ import { requireMessageAuthWithConfig } from '../utils/auth-guards.js';
11
+ import { stripHtml } from '../utils/parsers.js';
12
+ import { DEFAULT_THREAD_LIMIT } from '../constants.js';
13
+ /** Maximum length of a last-message preview before truncation. */
14
+ const PREVIEW_MAX_LENGTH = 120;
15
+ /** Classifies a conversation type from its ID. */
16
+ function classifyChatType(id) {
17
+ if (id.includes('meeting_'))
18
+ return 'meeting';
19
+ if (id.includes('@thread.tacv2'))
20
+ return 'channel';
21
+ if (id.includes('@unq.gbl.spaces'))
22
+ return 'oneOnOne';
23
+ if (id.includes('@thread.v2'))
24
+ return 'group';
25
+ return 'chat';
26
+ }
27
+ /** Truncates a preview to {@link PREVIEW_MAX_LENGTH} characters, adding an ellipsis. */
28
+ function truncatePreview(text) {
29
+ if (text.length <= PREVIEW_MAX_LENGTH)
30
+ return text;
31
+ return `${text.slice(0, PREVIEW_MAX_LENGTH)}…`;
32
+ }
33
+ /**
34
+ * Parses a raw chatsvc conversation into a {@link ChatSummary}, or null if it
35
+ * has no usable ID. Pure — no IO — so the field mapping and truncation are
36
+ * unit-testable against fixture JSON.
37
+ */
38
+ export function parseConversation(raw) {
39
+ const id = typeof raw.id === 'string' ? raw.id : '';
40
+ if (!id)
41
+ return null;
42
+ const threadProperties = (raw.threadProperties ?? {});
43
+ const topic = typeof threadProperties.topic === 'string' ? threadProperties.topic : '';
44
+ const lastMessage = (raw.lastMessage ?? {});
45
+ const lastMessageTime = (typeof lastMessage.originalarrivaltime === 'string' && lastMessage.originalarrivaltime) ||
46
+ (typeof lastMessage.composetime === 'string' && lastMessage.composetime) ||
47
+ '';
48
+ const lastMessageFrom = typeof lastMessage.imdisplayname === 'string' ? lastMessage.imdisplayname : '';
49
+ const rawContent = typeof lastMessage.content === 'string' ? lastMessage.content : '';
50
+ return {
51
+ conversationId: id,
52
+ topic,
53
+ chatType: classifyChatType(id),
54
+ lastMessageTime,
55
+ lastMessageFrom,
56
+ lastMessagePreview: truncatePreview(stripHtml(rawContent)),
57
+ };
58
+ }
59
+ /**
60
+ * Lists the user's recent conversations (chats, group chats, meetings and
61
+ * channels), newest activity first, capped at `limit`.
62
+ */
63
+ export async function getConversations(options = {}) {
64
+ const authResult = requireMessageAuthWithConfig();
65
+ if (!authResult.ok) {
66
+ return authResult;
67
+ }
68
+ const { auth, region, baseUrl } = authResult.value;
69
+ const limit = options.limit ?? DEFAULT_THREAD_LIMIT;
70
+ const response = await httpRequest(CHATSVC_API.conversations(region, baseUrl), {
71
+ method: 'GET',
72
+ headers: getSkypeAuthHeaders(auth.skypeToken, auth.authToken, baseUrl),
73
+ });
74
+ if (!response.ok) {
75
+ return response;
76
+ }
77
+ const raw = Array.isArray(response.value.data.conversations)
78
+ ? response.value.data.conversations
79
+ : [];
80
+ const conversations = raw
81
+ .map(c => parseConversation(c))
82
+ .filter((c) => c !== null)
83
+ .slice(0, limit);
84
+ return ok({ conversations });
85
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Unit tests for conversation-list parsing.
3
+ */
4
+ export {};
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Unit tests for conversation-list parsing.
3
+ */
4
+ import { describe, it, expect } from 'vitest';
5
+ import { parseConversation } from './chatsvc-conversations.js';
6
+ describe('parseConversation', () => {
7
+ it('parses a 1:1 chat with a last-message preview', () => {
8
+ const result = parseConversation({
9
+ id: '19:aaa_bbb@unq.gbl.spaces',
10
+ lastMessage: {
11
+ imdisplayname: 'Bob',
12
+ content: '<p>Hi there</p>',
13
+ originalarrivaltime: '2026-06-30T10:00:00Z',
14
+ },
15
+ });
16
+ expect(result).toEqual({
17
+ conversationId: '19:aaa_bbb@unq.gbl.spaces',
18
+ topic: '',
19
+ chatType: 'oneOnOne',
20
+ lastMessageTime: '2026-06-30T10:00:00Z',
21
+ lastMessageFrom: 'Bob',
22
+ lastMessagePreview: 'Hi there',
23
+ });
24
+ });
25
+ it('classifies channel, group and meeting conversation IDs', () => {
26
+ expect(parseConversation({ id: '19:x@thread.tacv2', threadProperties: { topic: 'General' } })?.chatType).toBe('channel');
27
+ expect(parseConversation({ id: '19:x@thread.v2' })?.chatType).toBe('group');
28
+ expect(parseConversation({ id: '19:meeting_x@thread.v2' })?.chatType).toBe('meeting');
29
+ });
30
+ it('uses the channel/group topic when present', () => {
31
+ const result = parseConversation({ id: '19:x@thread.tacv2', threadProperties: { topic: 'General' } });
32
+ expect(result?.topic).toBe('General');
33
+ });
34
+ it('truncates a long preview to 120 characters with an ellipsis', () => {
35
+ const long = 'a'.repeat(200);
36
+ const result = parseConversation({ id: '19:x@thread.v2', lastMessage: { content: long } });
37
+ expect(result?.lastMessagePreview.length).toBe(121); // 120 chars + ellipsis
38
+ expect(result?.lastMessagePreview.endsWith('…')).toBe(true);
39
+ });
40
+ it('returns null when the conversation has no id', () => {
41
+ expect(parseConversation({})).toBeNull();
42
+ expect(parseConversation({ id: '' })).toBeNull();
43
+ });
44
+ it('falls back to composetime when originalarrivaltime is absent', () => {
45
+ const result = parseConversation({
46
+ id: '19:x@thread.v2',
47
+ lastMessage: { composetime: '2026-06-30T09:00:00Z', content: 'hey' },
48
+ });
49
+ expect(result?.lastMessageTime).toBe('2026-06-30T09:00:00Z');
50
+ });
51
+ });
@@ -5,11 +5,13 @@
5
5
  * Conversation properties and participant extraction.
6
6
  */
7
7
  import { type Result } from '../types/result.js';
8
- import { type ExtractedLink } from '../utils/parsers.js';
8
+ import { type ExtractedLink, type Reaction, type ReactionSummary } from '../utils/parsers.js';
9
9
  /** Result of sending a message. */
10
10
  export interface SendMessageResult {
11
11
  messageId: string;
12
12
  timestamp?: number;
13
+ /** For scheduled messages: the ISO 8601 UTC time the message will be sent. */
14
+ scheduledFor?: string;
13
15
  }
14
16
  /** A message from a thread/conversation. */
15
17
  export interface ThreadMessage {
@@ -32,6 +34,10 @@ export interface ThreadMessage {
32
34
  threadRootId?: string;
33
35
  /** True if this message is a reply within a channel thread (not a top-level post) */
34
36
  isThreadReply?: boolean;
37
+ /** Emoji reactions on this message, with reactor identity (names resolved where possible). */
38
+ reactions?: Reaction[];
39
+ /** Reaction counts keyed by emoji (e.g. { like: 3, heart: 1 }). */
40
+ reactionSummary?: ReactionSummary;
35
41
  }
36
42
  /** Result of getting thread messages. */
37
43
  export interface GetThreadResult {
@@ -68,6 +74,24 @@ export interface SendMessageOptions {
68
74
  * are part of the same flat conversation.
69
75
  */
70
76
  replyToMessageId?: string;
77
+ /**
78
+ * How to interpret the content. Defaults to `markdown` (convert markdown +
79
+ * @mentions + links to Teams HTML — the historical behaviour). Use `text` to
80
+ * send verbatim with no reinterpretation, `html` for caller-supplied Teams
81
+ * HTML, or `auto` to convert only when markdown/mention syntax is present.
82
+ */
83
+ contentType?: ContentType;
84
+ /**
85
+ * For channel posts: a thread subject/title. Starts a new titled thread.
86
+ * Ignored for 1:1/group chats (they have no per-message subject).
87
+ */
88
+ subject?: string;
89
+ /**
90
+ * Schedule the message for future delivery. ISO 8601 (e.g.
91
+ * "2026-04-11T09:00:00Z"); a timezone-less value is treated as UTC. Cannot be
92
+ * combined with mentions or a thread reply.
93
+ */
94
+ scheduleAt?: string;
71
95
  }
72
96
  /** Result of getting a 1:1 conversation. */
73
97
  export interface GetOneOnOneChatResult {
@@ -86,6 +110,16 @@ export interface CreateGroupChatResult {
86
110
  /** Optional note about the result, e.g. if the ID could not be retrieved. */
87
111
  note?: string;
88
112
  }
113
+ /**
114
+ * Generates a Teams client message ID.
115
+ *
116
+ * Teams requires this to be "a number in string format" — a UUID is rejected
117
+ * with `StoreInvalidInput - ClientMessageId must be a number in string format`.
118
+ * This mirrors the Teams web client's large random integer: current time in
119
+ * milliseconds plus 6 random digits, keeping IDs numeric and unique even for
120
+ * rapid successive sends.
121
+ */
122
+ export declare function generateClientMessageId(): string;
89
123
  /**
90
124
  * Sends a message to a Teams conversation.
91
125
  *
@@ -116,23 +150,28 @@ export declare function getMessage(conversationId: string, messageId: string): P
116
150
  * @param options.limit - Maximum messages to return (default 50)
117
151
  * @param options.startTime - Fetch messages from this timestamp onwards
118
152
  * @param options.order - Sort order: 'desc' (newest-first, default) or 'asc' (oldest-first)
153
+ * @param options.replyToMessageId - For channels: scope to replies of a specific top-level post
119
154
  */
120
155
  export declare function getThreadMessages(conversationId: string, options?: {
121
156
  limit?: number;
122
157
  startTime?: number;
123
158
  order?: 'asc' | 'desc';
159
+ replyToMessageId?: string;
124
160
  }): Promise<Result<GetThreadResult>>;
125
161
  /**
126
162
  * Edits an existing message.
127
163
  *
164
+ * Uses the same content pipeline as {@link sendMessage} (markdown, @[mentions], links).
165
+ *
128
166
  * Note: You can only edit your own messages. The API will reject
129
167
  * attempts to edit messages from other users.
130
168
  *
131
169
  * @param conversationId - The conversation containing the message
132
170
  * @param messageId - The ID of the message to edit
133
- * @param newContent - The new content for the message
171
+ * @param newContent - New content (markdown by default; supports mentions like sendMessage)
172
+ * @param contentType - How to interpret the content (default `markdown`)
134
173
  */
135
- export declare function editMessage(conversationId: string, messageId: string, newContent: string): Promise<Result<EditMessageResult>>;
174
+ export declare function editMessage(conversationId: string, messageId: string, newContent: string, contentType?: ContentType): Promise<Result<EditMessageResult>>;
136
175
  /**
137
176
  * Deletes a message (soft delete).
138
177
  *
@@ -204,3 +243,128 @@ export declare function parseContentWithMentionsAndLinks(content: string): {
204
243
  html: string;
205
244
  mentions: Mention[];
206
245
  };
246
+ /** How message content should be interpreted before sending. */
247
+ export type ContentType = 'auto' | 'text' | 'html' | 'markdown';
248
+ /** Content resolved into the shape the chatsvc/Graph message body expects. */
249
+ export interface ResolvedContent {
250
+ /** The body content to send. */
251
+ content: string;
252
+ /** The wire message type: plain `Text` or `RichText/Html`. */
253
+ messagetype: 'Text' | 'RichText/Html';
254
+ /** Any @mentions extracted from the content (empty for text/html modes). */
255
+ mentions: Mention[];
256
+ }
257
+ /**
258
+ * Resolves outbound message content according to the requested content type.
259
+ *
260
+ * - `markdown` (default): convert markdown + @mentions + links to Teams HTML.
261
+ * This preserves the historical behaviour where all content was converted.
262
+ * - `text`: send verbatim as plain text — no markdown, mention or link
263
+ * processing. The escape hatch for content that should not be reinterpreted
264
+ * (e.g. `5*3*2`, `a_b_c`).
265
+ * - `html`: caller-supplied Teams HTML, sent as `RichText/Html` unchanged.
266
+ * - `auto`: convert only when the content actually contains markdown formatting
267
+ * or mention/link syntax; otherwise send plain text.
268
+ */
269
+ export declare function resolveMessageContent(content: string, contentType?: ContentType): ResolvedContent;
270
+ /**
271
+ * Parses and validates a schedule datetime for a scheduled message.
272
+ *
273
+ * Accepts ISO 8601 (with or without a timezone) and a space-separated
274
+ * `YYYY-MM-DD HH:mm[:ss]` form. A timezone-less value is treated as UTC (so
275
+ * results are deterministic regardless of the host clock). Rejects unparseable
276
+ * or past times. `now` is injectable for testing.
277
+ */
278
+ export declare function parseScheduleTime(input: string, now?: number): Result<{
279
+ iso: string;
280
+ epochMs: number;
281
+ }>;
282
+ /**
283
+ * Builds the chatsvc `/drafts` request body for a scheduled message — the same
284
+ * mechanism the Teams web client uses. `sendAt` must be epoch milliseconds.
285
+ */
286
+ export declare function buildScheduledDraftBody(opts: {
287
+ conversationId: string;
288
+ content: string;
289
+ messagetype: 'Text' | 'RichText/Html';
290
+ epochMs: number;
291
+ userMri: string;
292
+ isoNow: string;
293
+ conversationLink: string;
294
+ clientMessageId: string;
295
+ }): Record<string, unknown>;
296
+ /**
297
+ * Extracts the two member object IDs from a 1:1 conversation ID of the form
298
+ * `19:<a>_<b>@unq.gbl.spaces`. Returns null for any other conversation type or
299
+ * a malformed ID (so the caller only attempts thread creation for real 1:1s).
300
+ */
301
+ export declare function extractOneOnOneMemberIds(conversationId: string): string[] | null;
302
+ /**
303
+ * Builds the `/v1/threads` body that creates the unique-roster 1:1 thread — what
304
+ * the Teams client does the first time you message someone. Both members are
305
+ * added as Admins with the fixed unique-roster properties.
306
+ */
307
+ export declare function buildOneOnOneThreadBody(memberIds: string[]): Record<string, unknown>;
308
+ /** Result of waiting for a new message in a conversation. */
309
+ export interface WaitForReplyResult {
310
+ /** True if the wait timed out before any new message arrived. */
311
+ timedOut: boolean;
312
+ /** The baseline message ID the wait started from. */
313
+ after: string;
314
+ /** Resume cursor: pass as `after` to a follow-up wait to continue from here. */
315
+ nextAfter: string;
316
+ /** The new messages that arrived (oldest first), or empty on timeout. */
317
+ newMessages: ThreadMessage[];
318
+ /** How many poll iterations ran. */
319
+ polls: number;
320
+ /** How many seconds the wait blocked for. */
321
+ waitedSeconds: number;
322
+ }
323
+ /** Options for {@link waitForReply}. */
324
+ export interface WaitForReplyOptions {
325
+ /** Only return messages newer than this message ID. Defaults to the caller's most recent message. */
326
+ after?: string;
327
+ /** Maximum seconds to block (clamped to MAX_WAIT_SECONDS). */
328
+ maxWaitSeconds?: number;
329
+ /** Seconds between polls. */
330
+ intervalSeconds?: number;
331
+ /** Include the caller's own messages in the result (default false). */
332
+ includeSelf?: boolean;
333
+ /** Messages fetched per poll (default 50). */
334
+ limit?: number;
335
+ }
336
+ /** Clamps the wait timing knobs into their sane range (pure, for testability). */
337
+ export declare function clampWaitParams(maxWaitSeconds: number, intervalSeconds: number): {
338
+ maxWait: number;
339
+ interval: number;
340
+ };
341
+ /**
342
+ * Selects the messages strictly newer than `afterId`, oldest first. Excludes the
343
+ * caller's own messages unless `includeSelf` is set; ignores non-numeric IDs.
344
+ * Pure so the filter/sort semantics are unit-testable.
345
+ */
346
+ export declare function selectNewMessages<T extends {
347
+ id: string;
348
+ isFromMe?: boolean;
349
+ }>(messages: readonly T[], afterId: number, includeSelf: boolean): T[];
350
+ /**
351
+ * Resolves the baseline message ID for a wait with no explicit `after`: the
352
+ * caller's own most recent message, else the most recent message overall, else
353
+ * 0 for an empty thread. Anchoring to the caller's own last message makes a wait
354
+ * restarted after a send idempotent without remembering the sent ID. Pure.
355
+ */
356
+ export declare function resolveWaitBaseline(messages: readonly {
357
+ id: string;
358
+ isFromMe?: boolean;
359
+ }[]): number;
360
+ /**
361
+ * Blocks until a new message appears in a conversation, then returns only the
362
+ * new messages. Polls inside the call so a waiting agent makes a single tool
363
+ * call instead of a poll loop. Read-only and idempotent: it never posts, and the
364
+ * `after` baseline makes a restart resume from the same point without replaying.
365
+ *
366
+ * The block is capped at MAX_WAIT_SECONDS so an MCP client timeout never kills
367
+ * the call; on timeout it returns `timedOut: true` with `nextAfter` so the
368
+ * caller can simply call again to keep waiting.
369
+ */
370
+ export declare function waitForReply(conversationId: string, options?: WaitForReplyOptions): Promise<Result<WaitForReplyResult>>;