msteams-mcp 0.26.0 → 0.27.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.
package/README.md CHANGED
@@ -1,24 +1,31 @@
1
- # Teams MCP Server
1
+ # Microsoft Teams MCP Server & CLI
2
2
 
3
- [![CI](https://github.com/m0nkmaster/msteams-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/m0nkmaster/msteams-mcp/actions/workflows/ci.yml)
4
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
- [![Node.js](https://img.shields.io/badge/node-%3E%3D18-brightgreen.svg)](https://nodejs.org/)
3
+ [CI](https://github.com/m0nkmaster/msteams-mcp/actions/workflows/ci.yml)
4
+ [npm version](https://www.npmjs.com/package/msteams-mcp)
5
+ [npm downloads](https://www.npmjs.com/package/msteams-mcp)
6
+ [License: MIT](https://opensource.org/licenses/MIT)
7
+ [Node.js](https://nodejs.org/)
6
8
 
7
- An MCP (Model Context Protocol) server that enables AI assistants to interact with Microsoft Teams. Search messages, send replies, manage favourites, and more.
9
+ Give AI access to Microsoft Teams with your standard Teams login.
8
10
 
9
- ## How It Works
11
+ > [!IMPORTANT]
12
+ >
13
+ > ## No Azure setup, app registration, Graph permissions or IT support required.
10
14
 
11
- This server calls Microsoft's Teams APIs directly (Substrate, chatsvc, CSA) - the same APIs the Teams web app uses. No Azure AD app registration or admin consent required.
15
+ Search messages and email, catch up on unread chats, read threads, send replies, retrieve meeting transcripts and more.
12
16
 
13
- **Authentication flow:**
14
- 1. AI runs `teams_login` to open a browser for you to log in
15
- 2. OAuth tokens are extracted and cached
16
- 3. All operations use cached tokens directly (no browser needed)
17
- 4. Automatic token refresh (~1 hour)
17
+ ## Why msteams-mcp?
18
+
19
+ - **Do real work** - search, unread chats, threads, replies, people, channels, meetings, transcripts, files, reactions and more.
20
+ - **Sign in once** - tokens refresh automatically and the browser stays out of routine operations.
21
+ - **Secure** - the assistant has only your existing Teams permissions, with encrypted session data stored locally.
22
+ - **Use it anywhere** - every capability works through both MCP clients and the included `msteams` CLI.
23
+
24
+
25
+
26
+ ## Quick start
18
27
 
19
- **Security:** Uses the same authentication as the Teams web client - your access is limited to what your account can already do.
20
28
 
21
- ## Installation
22
29
 
23
30
  ### Prerequisites
24
31
 
@@ -26,7 +33,9 @@ This server calls Microsoft's Teams APIs directly (Substrate, chatsvc, CSA) - t
26
33
  - A Microsoft account with Teams access
27
34
  - Google Chrome, Microsoft Edge, or Chromium browser installed
28
35
 
29
- ### Configure Your MCP Client
36
+
37
+
38
+ ### Configure your MCP client
30
39
 
31
40
  Add to your MCP client configuration (e.g., Claude Desktop, Windsurf, Cursor):
32
41
 
@@ -41,7 +50,18 @@ Add to your MCP client configuration (e.g., Claude Desktop, Windsurf, Cursor):
41
50
  }
42
51
  ```
43
52
 
44
- That's it. `npx` will automatically download and run the latest version.
53
+ That is it. `npx` downloads and runs the latest version. Ask your assistant to call `teams_login` once, complete the normal Microsoft sign-in in the browser, and then start using Teams.
54
+
55
+ ## How it works
56
+
57
+ This server calls Microsoft's Teams services directly (Substrate, chatsvc, CSA), using the authenticated session created by the Teams web app.
58
+
59
+ 1. `teams_login` opens a browser for your normal Microsoft sign-in.
60
+ 2. The resulting OAuth tokens and session data are encrypted and cached locally.
61
+ 3. Routine operations use those tokens directly, without browser automation.
62
+ 4. Expiring tokens are refreshed automatically over HTTP, with headless browser SSO as a fallback.
63
+
64
+ Your access remains limited to what your Teams account can already see and do.
45
65
 
46
66
  ### From Source (alternative)
47
67
 
@@ -68,9 +88,9 @@ Then configure your MCP client:
68
88
 
69
89
  The server uses your system's Chrome (macOS/Linux) or Edge (Windows) for authentication.
70
90
 
71
- ### CLI (alternative to the MCP server)
91
+ ### CLI
72
92
 
73
- The same functionality is also available as a standalone command-line tool, `msteams`. This is useful when you want Teams from a shell or script instead of an MCP client (for example, if an MCP integration is unreliable in your environment).
93
+ The same functionality is also available as a standalone command-line tool, `msteams`. This is useful when you want Teams from a shell or script instead of an MCP client.
74
94
 
75
95
  Install it globally from npm:
76
96
 
@@ -78,7 +98,7 @@ Install it globally from npm:
78
98
  npm install -g msteams-mcp
79
99
  ```
80
100
 
81
- This installs two binaries: `msteams-mcp` (the MCP server) and `msteams` (the CLI). Or run it without installing:
101
+ This installs two binaries: `msteams-mcp` (the MCP server) and `msteams` (the CLI wrapper). Or run it without installing:
82
102
 
83
103
  ```bash
84
104
  npx -y msteams-mcp msteams status
@@ -88,86 +108,115 @@ See [CLI Usage](#cli-usage) for commands.
88
108
 
89
109
  ## Available Tools
90
110
 
111
+
112
+
91
113
  ### Search & Discovery
92
114
 
93
- | Tool | Description |
94
- |------|-------------|
95
- | `teams_search` | Search Teams messages with operators (`from:`, `sent:`, `in:`, `hasattachment:`, etc.) |
96
- | `teams_search_email` | Search emails in your mailbox (same auth as Teams — no extra login) |
97
- | `teams_list_chats` | List recent conversations (1:1, group, meeting, channel) with a last-message preview |
98
- | `teams_get_message` | Get a single message by ID with full content (any age); includes reactions |
99
- | `teams_get_thread` | Get messages from a conversation/thread; includes reactions; `threadRootId` scopes to one channel thread; `fromUrl` accepts a Teams message deep link |
100
- | `teams_find_channel` | Find channels by name (your teams + org-wide discovery) |
101
- | `teams_get_activity` | Get activity feed (mentions, reactions, replies, notifications) |
115
+
116
+ | Tool | Description |
117
+ | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
118
+ | `teams_search` | Search Teams messages with operators (`from:`, `sent:`, `in:`, `hasattachment:`, etc.) |
119
+ | `teams_search_email` | Search emails in your mailbox (same auth as Teams - no extra login) |
120
+ | `teams_list_chats` | List recent conversations (1:1, group, meeting, channel) with a last-message preview |
121
+ | `teams_get_message` | Get a single message by ID with full content (any age); includes reactions |
122
+ | `teams_get_thread` | Get messages from a conversation/thread; includes reactions; `threadRootId` scopes to one channel thread; `fromUrl` accepts a Teams message deep link |
123
+ | `teams_find_channel` | Find channels by name (your teams + org-wide discovery) |
124
+ | `teams_get_tags` | List a team's channel tags for tag @mentions |
125
+ | `teams_get_activity` | Get activity feed (mentions, reactions, replies, notifications) |
126
+
127
+
128
+
102
129
 
103
130
  ### Messaging
104
131
 
105
- | Tool | Description |
106
- |------|-------------|
107
- | `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 |
108
- | `teams_wait_for_reply` | Block until a new message arrives (server-side poll, capped ~110s); idempotent `after`/`nextAfter` cursor — pair with `teams_send_message` |
109
- | `teams_edit_message` | Edit one of your own messages (`contentType` supported) |
110
- | `teams_delete_message` | Delete one of your own messages (soft delete) |
132
+
133
+ | Tool | Description |
134
+ | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
135
+ | `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 |
136
+ | `teams_wait_for_reply` | Block until a new message arrives (server-side poll, capped ~110s); idempotent `after`/`nextAfter` cursor - pair with `teams_send_message` |
137
+ | `teams_edit_message` | Edit one of your own messages (`contentType` supported) |
138
+ | `teams_delete_message` | Delete one of your own messages (soft delete) |
139
+
140
+
141
+
111
142
 
112
143
  ### People & Contacts
113
144
 
114
- | Tool | Description |
115
- |------|-------------|
116
- | `teams_get_me` | Get current user profile (email, name, ID) |
117
- | `teams_search_people` | Search for people by name or email |
118
- | `teams_get_frequent_contacts` | Get frequently contacted people (useful for name resolution) |
119
- | `teams_get_person` | Resolve one or more MRIs to full profiles (name, email, job title, department) |
120
- | `teams_get_chat` | Get conversation ID for 1:1 chat with a person |
121
- | `teams_create_group_chat` | Create a new group chat with multiple people (2+ others) |
145
+
146
+ | Tool | Description |
147
+ | ----------------------------- | ------------------------------------------------------------------------------ |
148
+ | `teams_get_me` | Get current user profile (email, name, ID) |
149
+ | `teams_search_people` | Search for people by name or email |
150
+ | `teams_get_frequent_contacts` | Get frequently contacted people (useful for name resolution) |
151
+ | `teams_get_person` | Resolve one or more MRIs to full profiles (name, email, job title, department) |
152
+ | `teams_get_chat` | Get conversation ID for 1:1 chat with a person |
153
+ | `teams_create_group_chat` | Create a new group chat with multiple people (2+ others) |
154
+
155
+
156
+
122
157
 
123
158
  ### Organisation
124
159
 
125
- | Tool | Description |
126
- |------|-------------|
127
- | `teams_get_favorites` | Get pinned/favourite conversations |
128
- | `teams_add_favorite` | Pin a conversation |
129
- | `teams_remove_favorite` | Unpin a conversation |
130
- | `teams_save_message` | Bookmark a message |
131
- | `teams_unsave_message` | Remove bookmark from a message |
132
- | `teams_get_saved_messages` | Get list of saved/bookmarked messages with source references |
133
- | `teams_get_followed_threads` | Get list of followed threads with source references |
134
- | `teams_get_unread` | Get unread counts (aggregate or per-conversation) |
135
- | `teams_mark_read` | Mark a conversation as read up to a message |
160
+
161
+ | Tool | Description |
162
+ | ---------------------------- | ------------------------------------------------------------ |
163
+ | `teams_get_favorites` | Get pinned/favourite conversations |
164
+ | `teams_add_favorite` | Pin a conversation |
165
+ | `teams_remove_favorite` | Unpin a conversation |
166
+ | `teams_save_message` | Bookmark a message |
167
+ | `teams_unsave_message` | Remove bookmark from a message |
168
+ | `teams_get_saved_messages` | Get list of saved/bookmarked messages with source references |
169
+ | `teams_get_followed_threads` | Get list of followed threads with source references |
170
+ | `teams_get_unread` | Get unread counts (aggregate or per-conversation) |
171
+ | `teams_mark_read` | Mark a conversation as read up to a message |
172
+
173
+
174
+
136
175
 
137
176
  ### Reactions
138
177
 
139
- | Tool | Description |
140
- |------|-------------|
141
- | `teams_search_emoji` | Search for emojis by name (standard + custom org emojis) |
142
- | `teams_add_reaction` | Add an emoji reaction to a message |
143
- | `teams_remove_reaction` | Remove an emoji reaction from a message |
178
+
179
+ | Tool | Description |
180
+ | ----------------------- | -------------------------------------------------------- |
181
+ | `teams_search_emoji` | Search for emojis by name (standard + custom org emojis) |
182
+ | `teams_add_reaction` | Add an emoji reaction to a message |
183
+ | `teams_remove_reaction` | Remove an emoji reaction from a message |
184
+
144
185
 
145
186
  **Quick reactions:** `like`, `heart`, `laugh`, `surprised`, `sad`, `angry` can be used directly without searching.
146
187
 
147
188
  ### Calendar & Meetings
148
189
 
149
- | Tool | Description |
150
- |------|-------------|
151
- | `teams_get_meetings` | Get meetings from calendar (defaults to next 7 days) |
190
+
191
+ | Tool | Description |
192
+ | ---------------------- | ---------------------------------------------------------------------- |
193
+ | `teams_get_meetings` | Get meetings from calendar (defaults to next 7 days) |
152
194
  | `teams_get_transcript` | Get meeting transcript (requires `threadId` from `teams_get_meetings`) |
153
195
 
196
+
154
197
  `teams_get_meetings` returns: subject, times, organiser, join URL, `threadId` for meeting chat. Use `threadId` with `teams_get_thread` to read meeting chat, or with `teams_get_transcript` to get the full transcript with speakers and timestamps.
155
198
 
156
199
  ### Files
157
200
 
158
- | Tool | Description |
159
- |------|-------------|
201
+
202
+ | Tool | Description |
203
+ | ------------------------ | ------------------------------------------------------------------ |
160
204
  | `teams_get_shared_files` | Get files and links shared in a conversation (supports pagination) |
161
205
 
206
+
162
207
  Returns both files (name, extension, URL, size) and links (URL, title), along with who shared each item. Works for channels, group chats, 1:1 chats, and meeting chats.
163
208
 
164
209
  ### Session
165
210
 
166
- | Tool | Description |
167
- |------|-------------|
168
- | `teams_login` | Trigger manual login (opens browser) |
211
+
212
+ | Tool | Description |
213
+ | -------------- | -------------------------------------- |
214
+ | `teams_login` | Trigger manual login (opens browser) |
169
215
  | `teams_status` | Check authentication and session state |
170
216
 
217
+
218
+
219
+
171
220
  ### Search Operators
172
221
 
173
222
  Both `teams_search` (Teams messages) and `teams_search_email` (emails) support native operators:
@@ -192,11 +241,15 @@ Combine operators: `from:sarah@co.com sent:>=2026-01-18 hasattachment:true`
192
241
 
193
242
  The server also exposes passive resources for context discovery:
194
243
 
195
- | Resource URI | Description |
196
- |--------------|-------------|
197
- | `teams://me/profile` | Current user's profile |
198
- | `teams://me/favorites` | Pinned conversations |
199
- | `teams://status` | Authentication status |
244
+
245
+ | Resource URI | Description |
246
+ | ---------------------- | ---------------------- |
247
+ | `teams://me/profile` | Current user's profile |
248
+ | `teams://me/favorites` | Pinned conversations |
249
+ | `teams://status` | Authentication status |
250
+
251
+
252
+
200
253
 
201
254
  ## CLI Usage
202
255
 
@@ -253,13 +306,16 @@ npm run cli -- status
253
306
  npm run cli -- send "Hi" --to "conversation-id"
254
307
  ```
255
308
 
309
+
310
+
256
311
  ## Limitations
257
312
 
258
313
  - **Login required** - Run `teams_login` to authenticate (opens browser)
259
314
  - **Token expiry** - Tokens expire after ~1 hour; headless refresh is attempted or run `teams_login` again when needed
260
315
  - **Undocumented APIs** - Uses Microsoft's internal APIs which may change without notice
261
- - **Search limitations** - Full-text search only; thread replies not matching search terms won't appear (use `teams_get_thread` for full context)
262
- - **Own messages only** - Edit/delete only works on your own messages
316
+ - **Search limitations** - Full-text search only; thread replies not matching search terms won't appear (but LLM will likely use `teams_get_thread` for full context)
317
+
318
+
263
319
 
264
320
  ## Session Files
265
321
 
@@ -310,6 +366,3 @@ See [AGENTS.md](AGENTS.md) for detailed architecture and contribution guidelines
310
366
 
311
367
  ---
312
368
 
313
- ## Teams Chat Export Bookmarklet
314
-
315
- This repo also includes a standalone bookmarklet for exporting Teams chat messages to Markdown. See [teams-bookmarklet/README.md](teams-bookmarklet/README.md).
@@ -83,5 +83,10 @@ export declare function clearTokenCache(): void;
83
83
  /**
84
84
  * Gets the Teams origin from session state.
85
85
  * Checks multiple known Teams domains to support government clouds.
86
+ *
87
+ * Prefers an origin that has a SubstrateSearch token so Outlook/email and
88
+ * Substrate search keep working when both teams.cloud.microsoft and
89
+ * teams.microsoft.com are present in the saved session (common after the
90
+ * New Teams host migration).
86
91
  */
87
92
  export declare function getTeamsOrigin(state: SessionState): SessionState['origins'][number] | null;
@@ -208,18 +208,45 @@ export function clearTokenCache() {
208
208
  * Used to find the correct origin in session state.
209
209
  */
210
210
  const TEAMS_ORIGINS = [
211
- 'https://teams.microsoft.com', // Commercial
211
+ 'https://teams.cloud.microsoft', // New Teams URL (often holds SubstrateSearch tokens)
212
+ 'https://teams.microsoft.com', // Commercial legacy
212
213
  'https://teams.microsoft.us', // GCC-High
213
214
  'https://dod.teams.microsoft.us', // DoD
214
- 'https://teams.cloud.microsoft', // New Teams URL
215
215
  ];
216
+ /**
217
+ * Returns true if localStorage has a SubstrateSearch MSAL token entry.
218
+ */
219
+ function hasSubstrateSearchToken(localStorage) {
220
+ for (const item of localStorage ?? []) {
221
+ try {
222
+ const entry = JSON.parse(item.value);
223
+ if (entry.target?.includes('SubstrateSearch') &&
224
+ typeof entry.secret === 'string' &&
225
+ entry.secret.startsWith('ey')) {
226
+ return true;
227
+ }
228
+ }
229
+ catch {
230
+ // ignore non-JSON localStorage values
231
+ }
232
+ }
233
+ return false;
234
+ }
216
235
  /**
217
236
  * Gets the Teams origin from session state.
218
237
  * Checks multiple known Teams domains to support government clouds.
238
+ *
239
+ * Prefers an origin that has a SubstrateSearch token so Outlook/email and
240
+ * Substrate search keep working when both teams.cloud.microsoft and
241
+ * teams.microsoft.com are present in the saved session (common after the
242
+ * New Teams host migration).
219
243
  */
220
244
  export function getTeamsOrigin(state) {
221
245
  if (!state.origins)
222
246
  return null;
247
+ const withSubstrate = state.origins.find(o => hasSubstrateSearchToken(o.localStorage));
248
+ if (withSubstrate)
249
+ return withSubstrate;
223
250
  // Try known Teams origins in priority order
224
251
  for (const knownOrigin of TEAMS_ORIGINS) {
225
252
  const origin = state.origins.find(o => o.origin === knownOrigin);
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Tests for getTeamsOrigin — especially New Teams (teams.cloud.microsoft)
3
+ * SubstrateSearch token selection when multiple origins are present.
4
+ */
5
+ export {};
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Tests for getTeamsOrigin — especially New Teams (teams.cloud.microsoft)
3
+ * SubstrateSearch token selection when multiple origins are present.
4
+ */
5
+ import { describe, it, expect } from 'vitest';
6
+ import { getTeamsOrigin } from './session-store.js';
7
+ function makeOrigin(origin, localStorage = []) {
8
+ return { origin, localStorage };
9
+ }
10
+ function makeSubstrateEntry(target = 'https://substrate.office.com/SubstrateSearch-Internal.ReadWrite') {
11
+ return {
12
+ name: 'msal-substrate-token',
13
+ value: JSON.stringify({
14
+ credentialType: 'AccessToken',
15
+ target,
16
+ // JWT-shaped secret (header.payload.sig) — extractor only checks prefix
17
+ secret: 'eyJhbGciOiJub25lIn0.eyJzdWIiOiJ0ZXN0In0.sig',
18
+ }),
19
+ };
20
+ }
21
+ function makeChatSvcEntry() {
22
+ return {
23
+ name: 'msal-chatsvc-token',
24
+ value: JSON.stringify({
25
+ credentialType: 'AccessToken',
26
+ target: 'https://chatsvcagg.teams.microsoft.com/.default',
27
+ secret: 'eyJhbGciOiJub25lIn0.eyJzdWIiOiJjaGF0In0.sig',
28
+ }),
29
+ };
30
+ }
31
+ describe('getTeamsOrigin', () => {
32
+ it('prefers the origin that holds a SubstrateSearch token', () => {
33
+ const state = {
34
+ cookies: [],
35
+ origins: [
36
+ makeOrigin('https://teams.microsoft.com', [makeChatSvcEntry()]),
37
+ makeOrigin('https://teams.cloud.microsoft', [makeSubstrateEntry()]),
38
+ ],
39
+ };
40
+ const chosen = getTeamsOrigin(state);
41
+ expect(chosen?.origin).toBe('https://teams.cloud.microsoft');
42
+ });
43
+ it('falls back to teams.cloud.microsoft when no Substrate token exists', () => {
44
+ const state = {
45
+ cookies: [],
46
+ origins: [
47
+ makeOrigin('https://teams.microsoft.com', [makeChatSvcEntry()]),
48
+ makeOrigin('https://teams.cloud.microsoft', [makeChatSvcEntry()]),
49
+ ],
50
+ };
51
+ const chosen = getTeamsOrigin(state);
52
+ expect(chosen?.origin).toBe('https://teams.cloud.microsoft');
53
+ });
54
+ it('returns teams.microsoft.com when it is the only known origin', () => {
55
+ const state = {
56
+ cookies: [],
57
+ origins: [makeOrigin('https://teams.microsoft.com', [makeChatSvcEntry()])],
58
+ };
59
+ const chosen = getTeamsOrigin(state);
60
+ expect(chosen?.origin).toBe('https://teams.microsoft.com');
61
+ });
62
+ it('returns null when no Teams origins are present', () => {
63
+ const state = {
64
+ cookies: [],
65
+ origins: [makeOrigin('https://login.microsoftonline.com', [])],
66
+ };
67
+ expect(getTeamsOrigin(state)).toBeNull();
68
+ });
69
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "msteams-mcp",
3
- "version": "0.26.0",
3
+ "version": "0.27.1",
4
4
  "description": "MCP server for Microsoft Teams - search messages, send replies, manage favourites",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",