@floriscornel/teams-mcp 0.7.0 → 0.9.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 (46) hide show
  1. package/README.md +231 -55
  2. package/dist/index.js +77 -32
  3. package/dist/index.js.map +1 -1
  4. package/dist/services/graph.d.ts +10 -0
  5. package/dist/services/graph.d.ts.map +1 -1
  6. package/dist/services/graph.js +24 -5
  7. package/dist/services/graph.js.map +1 -1
  8. package/dist/tools/auth.d.ts +1 -1
  9. package/dist/tools/auth.d.ts.map +1 -1
  10. package/dist/tools/auth.js +1 -1
  11. package/dist/tools/auth.js.map +1 -1
  12. package/dist/tools/chats.d.ts +2 -1
  13. package/dist/tools/chats.d.ts.map +1 -1
  14. package/dist/tools/chats.js +278 -6
  15. package/dist/tools/chats.js.map +1 -1
  16. package/dist/tools/search.d.ts +1 -1
  17. package/dist/tools/search.d.ts.map +1 -1
  18. package/dist/tools/search.js +1 -1
  19. package/dist/tools/search.js.map +1 -1
  20. package/dist/tools/teams.d.ts +1 -1
  21. package/dist/tools/teams.d.ts.map +1 -1
  22. package/dist/tools/teams.js +668 -465
  23. package/dist/tools/teams.js.map +1 -1
  24. package/dist/tools/users.d.ts +1 -1
  25. package/dist/tools/users.d.ts.map +1 -1
  26. package/dist/tools/users.js +1 -1
  27. package/dist/tools/users.js.map +1 -1
  28. package/dist/types/graph.d.ts +16 -2
  29. package/dist/types/graph.d.ts.map +1 -1
  30. package/dist/utils/attachments.d.ts +7 -0
  31. package/dist/utils/attachments.d.ts.map +1 -1
  32. package/dist/utils/attachments.js +22 -0
  33. package/dist/utils/attachments.js.map +1 -1
  34. package/dist/utils/content-type.d.ts +6 -0
  35. package/dist/utils/content-type.d.ts.map +1 -0
  36. package/dist/utils/content-type.js +43 -0
  37. package/dist/utils/content-type.js.map +1 -0
  38. package/dist/utils/file-upload.d.ts +51 -0
  39. package/dist/utils/file-upload.d.ts.map +1 -0
  40. package/dist/utils/file-upload.js +258 -0
  41. package/dist/utils/file-upload.js.map +1 -0
  42. package/dist/utils/html-to-markdown.d.ts +5 -2
  43. package/dist/utils/html-to-markdown.d.ts.map +1 -1
  44. package/dist/utils/html-to-markdown.js +92 -7
  45. package/dist/utils/html-to-markdown.js.map +1 -1
  46. package/package.json +14 -14
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
7
  [![GitHub stars](https://img.shields.io/github/stars/floriscornel/teams-mcp.svg)](https://github.com/floriscornel/teams-mcp/stargazers)
8
8
 
9
- A Model Context Protocol (MCP) server that provides seamless integration with Microsoft Graph APIs, enabling AI assistants to interact with Microsoft Teams, users, and organizational data.
9
+ A Model Context Protocol (MCP) server that provides seamless integration with Microsoft Graph APIs, enabling AI assistants to interact with Microsoft Teams, users, chats, files, and organizational data.
10
10
 
11
11
  <a href="https://glama.ai/mcp/servers/@floriscornel/teams-mcp">
12
12
  <img width="380" height="200" src="https://glama.ai/mcp/servers/@floriscornel/teams-mcp/badge" alt="Teams MCP server" />
@@ -30,9 +30,11 @@ To use this MCP server in Cursor/Claude/VS Code, add the following configuration
30
30
  ## šŸš€ Features
31
31
 
32
32
  ### šŸ” Authentication
33
- - OAuth 2.0 authentication flow with Microsoft Graph
34
- - Secure token management and refresh
35
- - Authentication status checking
33
+ - OAuth 2.0 device code authentication flow with Microsoft Graph
34
+ - Secure token management, cache persistence, and refresh token renewal
35
+ - Authentication status checking and logout support
36
+ - Read-only mode with reduced scopes
37
+ - Direct `AUTH_TOKEN` support for pre-issued Microsoft Graph access tokens
36
38
 
37
39
  ### šŸ‘„ User Management
38
40
  - Get current user information
@@ -44,22 +46,27 @@ To use this MCP server in Cursor/Claude/VS Code, add the following configuration
44
46
  - **Teams Management**
45
47
  - List user's joined teams
46
48
  - Access team details and metadata
47
-
49
+
48
50
  - **Channel Operations**
49
51
  - List channels within teams
50
- - Retrieve channel messages
52
+ - Retrieve channel messages and replies
51
53
  - Send messages to team channels
52
- - Support for message importance levels (normal, high, urgent)
53
-
54
+ - Reply to existing channel threads
55
+ - Edit and soft delete channel messages and replies
56
+ - Support for message importance levels (`normal`, `high`, `urgent`)
57
+ - Support for inline image attachments via URL or base64 data
58
+
54
59
  - **Team Members**
55
60
  - List team members and their roles
56
61
  - Access member information
62
+ - Search users for `@mentions`
57
63
 
58
64
  ### šŸ’¬ Chat & Messaging
59
65
  - **1:1 and Group Chats**
60
66
  - List user's chats
61
67
  - Create new 1:1 or group conversations
62
- - Retrieve chat message history with filtering and pagination
68
+ - Retrieve chat message history with filtering, ordering, and pagination
69
+ - Fetch all available messages via `@odata.nextLink` pagination
63
70
  - Send messages to existing chats
64
71
  - Edit previously sent chat messages
65
72
  - Soft delete chat messages
@@ -75,29 +82,38 @@ To use this MCP server in Cursor/Claude/VS Code, add the following configuration
75
82
  - **Hosted Content**
76
83
  - Download hosted content (images, files) from chat and channel messages
77
84
  - Access inline images and attachments shared in conversations
85
+ - Optionally save hosted content directly to disk
86
+
87
+ - **File Upload**
88
+ - Upload and send any file type (PDF, DOCX, XLSX, ZIP, images, etc.) to channels and chats
89
+ - Large file support (>4 MB) via resumable upload sessions
90
+ - Channel uploads go to SharePoint and chat uploads go to OneDrive
91
+ - Optional message text, custom filename, formatting, and importance levels
78
92
 
79
93
  ### šŸ” Advanced Search & Discovery
80
94
  - **Message Search**
81
95
  - Search across all Teams channels and chats using Microsoft Search API
82
96
  - Support for KQL (Keyword Query Language) syntax
83
- - Filter by sender, mentions, attachments, importance, and date ranges
97
+ - Filter by sender, mentions, attachments, read state, and date ranges
84
98
  - Get recent messages with advanced filtering options
85
- - Find messages mentioning specific users
99
+ - Find messages mentioning the current user
86
100
 
87
101
  ## Rich Message Formatting Support
88
102
 
89
- The following tools now support rich message formatting in Teams channels and chats:
103
+ The following tools support rich message formatting in Teams channels and chats:
90
104
  - `send_channel_message`
91
105
  - `send_chat_message`
92
106
  - `reply_to_channel_message`
93
107
  - `update_channel_message`
94
108
  - `update_chat_message`
109
+ - `send_file_to_channel`
110
+ - `send_file_to_chat`
95
111
 
96
112
  ### Format Options
97
113
 
98
114
  You can specify the `format` parameter to control the message formatting:
99
115
  - `text` (default): Plain text
100
- - `markdown`: Markdown formatting (bold, italic, lists, links, code, etc.) - converted to sanitized HTML
116
+ - `markdown`: Markdown formatting (bold, italic, lists, links, code, etc.) converted to sanitized HTML
101
117
 
102
118
  When `format` is set to `markdown`, the message content is converted to HTML using a secure markdown parser and sanitized to remove potentially dangerous content before being sent to Teams.
103
119
 
@@ -110,7 +126,8 @@ If `format` is not specified, the message will be sent as plain text.
110
126
  "teamId": "...",
111
127
  "channelId": "...",
112
128
  "message": "**Bold text** and _italic text_\n\n- List item 1\n- List item 2\n\n[Link](https://example.com)",
113
- "format": "markdown"
129
+ "format": "markdown",
130
+ "importance": "high"
114
131
  }
115
132
  ```
116
133
 
@@ -126,17 +143,16 @@ If `format` is not specified, the message will be sent as plain text.
126
143
 
127
144
  - **HTML Sanitization**: All markdown content is converted to HTML and sanitized to remove potentially dangerous elements (scripts, event handlers, etc.)
128
145
  - **Allowed Tags**: Only safe HTML tags are permitted (p, strong, em, a, ul, ol, li, h1-h6, code, pre, etc.)
129
- - **Safe Attributes**: Only safe attributes are allowed (href, target, src, alt, title, width, height)
146
+ - **Safe Attributes**: Only safe attributes are allowed
130
147
  - **XSS Prevention**: Content is automatically sanitized to prevent cross-site scripting attacks
131
148
 
132
149
  ### Supported Markdown Features
133
150
 
134
151
  - **Text formatting**: Bold (`**text**`), italic (`_text_`), strikethrough (`~~text~~`)
135
- - **Links**: `[text](url)`
152
+ - **Links**: `[text](url)`
136
153
  - **Lists**: Bulleted (`- item`) and numbered (`1. item`)
137
- - **Code**: Inline `` `code` `` and blocks ``` ```code``` ```
154
+ - **Code**: Inline `` `code` `` and fenced code blocks
138
155
  - **Headings**: `# H1` through `###### H6`
139
- - **Line breaks**: Automatic conversion of newlines to `<br>` tags
140
156
  - **Blockquotes**: `> quoted text`
141
157
  - **Tables**: GitHub-flavored markdown tables
142
158
 
@@ -158,19 +174,23 @@ Use the `contentFormat` parameter to control how message content is returned:
158
174
 
159
175
  ### What Gets Converted
160
176
 
161
- | HTML Element | Markdown Output |
162
- |---|---|
163
- | `<at id="0">Name</at>` (Teams mention) | `@Name` |
164
- | `<strong>text</strong>` | `**text**` |
165
- | `<em>text</em>` | `*text*` |
166
- | `<code>text</code>` | `` `text` `` |
167
- | `<a href="url">text</a>` | `[text](url)` |
168
- | `<ul><li>item</li></ul>` | `- item` |
169
- | `<table>...</table>` | GFM Markdown table |
170
- | `<attachment id="...">` | *(removed)* |
171
- | `<systemEventMessage/>` | *(removed)* |
172
- | `<hr>` | `---` |
173
- | `&nbsp;`, `&amp;`, etc. | Decoded to plain characters |
177
+ | HTML Element | Markdown Output |
178
+ | -------------------------------------- | --------------------------------------------------------- |
179
+ | `<at id="0">Name</at>` (Teams mention) | `@Name` (multi-word names merged using mentions metadata) |
180
+ | `<strong>text</strong>` | `**text**` |
181
+ | `<em>text</em>` | `*text*` |
182
+ | `<code>text</code>` | `` `text` `` |
183
+ | `<a href="url">text</a>` | `[text](url)` |
184
+ | `<ul><li>item</li></ul>` | `- item` |
185
+ | `<table>...</table>` | GFM Markdown table |
186
+ | `<attachment id="...">` | `{attachment:id}` |
187
+ | `<systemEventMessage/>` | *(removed)* |
188
+ | `<hr>` | `---` |
189
+ | `&nbsp;`, `&amp;`, etc. | Decoded to plain characters |
190
+
191
+ ### Attachment Metadata
192
+
193
+ Messages that contain file attachments or inline images include an `attachments` array in the response with metadata for each attachment (id, name, contentType, contentUrl, thumbnailUrl). The inline `{attachment:id}` markers in the markdown content correlate with entries in this array, allowing consumers to identify and download attachments via `download_message_hosted_content` or `download_chat_hosted_content`.
174
194
 
175
195
  ### Example Usage
176
196
 
@@ -182,7 +202,7 @@ Use the `contentFormat` parameter to control how message content is returned:
182
202
  }
183
203
  ```
184
204
 
185
- To get the original HTML (previous default behavior):
205
+ To get the original HTML:
186
206
 
187
207
  ```json
188
208
  {
@@ -210,22 +230,66 @@ npm run auth
210
230
  ### Prerequisites
211
231
  - Node.js 18+
212
232
  - Microsoft 365 account with appropriate permissions
213
- - Azure App Registration with Microsoft Graph permissions
233
+ - Microsoft Graph delegated permissions for the scopes below
214
234
 
215
235
  ### Required Microsoft Graph Permissions
236
+
237
+ **Full mode (default):**
216
238
  - `User.Read` - Read user profile
217
239
  - `User.ReadBasic.All` - Read basic user info
218
240
  - `Team.ReadBasic.All` - Read team information
219
241
  - `Channel.ReadBasic.All` - Read channel information
220
242
  - `ChannelMessage.Read.All` - Read channel messages
221
- - `ChannelMessage.Send` - Send channel messages
243
+ - `ChannelMessage.Send` - Send channel messages and replies
222
244
  - `ChannelMessage.ReadWrite` - Edit and delete channel messages
223
- - `Chat.Read` - Read chat messages
224
- - `Chat.ReadWrite` - Create and manage chats (including edit/delete messages)
225
- - `Mail.Read` - Required for Microsoft Search API
226
- - `Calendars.Read` - Required for Microsoft Search API
227
- - `Files.Read.All` - Required for Microsoft Search API
228
- - `Sites.Read.All` - Required for Microsoft Search API
245
+ - `Chat.Read` - Read chat messages (included via read-only scopes)
246
+ - `Chat.ReadWrite` - Create and manage chats, send/edit/delete chat messages (supersedes `Chat.Read`)
247
+ - `TeamMember.Read.All` - Read team members
248
+ - `Files.ReadWrite.All` - Required for file uploads to channels and chats
249
+
250
+ **Read-only mode** (`TEAMS_MCP_READ_ONLY=true`) — only these scopes are requested:
251
+ - `User.Read`
252
+ - `User.ReadBasic.All`
253
+ - `Team.ReadBasic.All`
254
+ - `Channel.ReadBasic.All`
255
+ - `ChannelMessage.Read.All`
256
+ - `TeamMember.Read.All`
257
+ - `Chat.Read`
258
+
259
+ ### Authentication Modes
260
+
261
+ **Full access:**
262
+
263
+ ```bash
264
+ npx @floriscornel/teams-mcp@latest authenticate
265
+ ```
266
+
267
+ **Read-only access:**
268
+
269
+ ```bash
270
+ npx @floriscornel/teams-mcp@latest authenticate --read-only
271
+ ```
272
+
273
+ **Direct token injection with an existing Microsoft Graph JWT:**
274
+
275
+ ```json
276
+ {
277
+ "mcpServers": {
278
+ "teams-mcp": {
279
+ "command": "npx",
280
+ "args": ["-y", "@floriscornel/teams-mcp@latest"],
281
+ "env": {
282
+ "AUTH_TOKEN": "<jwt-for-https://graph.microsoft.com>"
283
+ }
284
+ }
285
+ }
286
+ }
287
+ ```
288
+
289
+ ### Token Storage
290
+
291
+ - Auth metadata is stored locally at `~/.msgraph-mcp-auth.json`
292
+ - Token cache is stored locally at `~/.teams-mcp-token-cache.json`
229
293
 
230
294
  ## šŸ› ļø Usage
231
295
 
@@ -236,43 +300,105 @@ npm run dev
236
300
 
237
301
  # Production mode
238
302
  npm run build && node dist/index.js
303
+
304
+ # Start in read-only mode (disables all write tools)
305
+ TEAMS_MCP_READ_ONLY=true node dist/index.js
239
306
  ```
240
307
 
308
+ ### CLI Commands
309
+
310
+ ```bash
311
+ npx @floriscornel/teams-mcp@latest authenticate # Authenticate with full scopes
312
+ npx @floriscornel/teams-mcp@latest authenticate --read-only # Authenticate with read-only scopes
313
+ npx @floriscornel/teams-mcp@latest check # Check authentication status
314
+ npx @floriscornel/teams-mcp@latest logout # Clear authentication
315
+ npx @floriscornel/teams-mcp@latest auth # Alias for authenticate
316
+ npx @floriscornel/teams-mcp@latest # Start MCP server (default)
317
+ ```
318
+
319
+ ### Environment Variables
320
+
321
+ - `TEAMS_MCP_READ_ONLY=true` - Start the MCP server in read-only mode
322
+ - `AUTH_TOKEN=<jwt>` - Use a pre-existing Microsoft Graph access token instead of MSAL login
323
+
324
+ ### Read-Only Mode
325
+
326
+ The server supports a read-only mode that disables all write operations (sending messages, creating chats, uploading files, editing/deleting messages) and requests only read-permission scopes from Microsoft Graph.
327
+
328
+ **Enable read-only mode** using either:
329
+ - Environment variable: `TEAMS_MCP_READ_ONLY=true`
330
+ - CLI flag: `--read-only`
331
+
332
+ **Authenticate with reduced scopes:**
333
+ ```bash
334
+ npx @floriscornel/teams-mcp@latest authenticate --read-only
335
+ ```
336
+
337
+ **MCP server configuration (read-only):**
338
+ ```json
339
+ {
340
+ "mcpServers": {
341
+ "teams-mcp": {
342
+ "command": "npx",
343
+ "args": ["-y", "@floriscornel/teams-mcp@latest"],
344
+ "env": {
345
+ "TEAMS_MCP_READ_ONLY": "true"
346
+ }
347
+ }
348
+ }
349
+ }
350
+ ```
351
+
352
+ **Switching modes:** When switching from read-only to full mode, the server detects the scope mismatch and warns you to re-authenticate:
353
+ ```bash
354
+ npx @floriscornel/teams-mcp@latest authenticate
355
+ ```
356
+
357
+ **Read-only tools (16):**
358
+ `auth_status`, `get_current_user`, `search_users`, `get_user`, `list_teams`, `list_channels`, `get_channel_messages`, `get_channel_message_replies`, `list_team_members`, `search_users_for_mentions`, `download_message_hosted_content`, `list_chats`, `get_chat_messages`, `download_chat_hosted_content`, `search_messages`, `get_my_mentions`
359
+
360
+ **Write tools disabled in read-only mode (10):**
361
+ `send_channel_message`, `reply_to_channel_message`, `update_channel_message`, `delete_channel_message`, `send_file_to_channel`, `send_chat_message`, `create_chat`, `update_chat_message`, `delete_chat_message`, `send_file_to_chat`
362
+
241
363
  ### Available MCP Tools
242
364
 
243
365
  #### Authentication
244
- - `authenticate` - Initiate OAuth authentication flow
245
- - `logout` - Clear authentication tokens
246
- - `get_current_user` - Get authenticated user information
366
+ - `auth_status` - Check current authentication status
247
367
 
248
368
  #### User Operations
369
+ - `get_current_user` - Get authenticated user information
249
370
  - `search_users` - Search for users by name or email
250
371
  - `get_user` - Get detailed user information by ID or email
251
372
 
252
373
  #### Teams Operations
253
374
  - `list_teams` - List user's joined teams
254
375
  - `list_channels` - List channels in a specific team
255
- - `get_channel_messages` - Retrieve messages from a team channel with pagination and filtering
256
- - `send_channel_message` - Send a message to a team channel
257
- - `update_channel_message` - Edit a previously sent channel message
258
- - `delete_channel_message` - Soft delete a channel message (supports replies)
376
+ - `get_channel_messages` - Retrieve messages from a team channel with attachment summaries and content format selection
377
+ - `get_channel_message_replies` - Get replies to a specific channel message
378
+ - `send_channel_message` - Send a message to a team channel with optional mentions, importance, and image attachments
379
+ - `reply_to_channel_message` - Reply to an existing channel message
380
+ - `update_channel_message` - Edit a previously sent channel message or reply
381
+ - `delete_channel_message` - Soft delete a channel message or reply
259
382
  - `list_team_members` - List members of a specific team
383
+ - `search_users_for_mentions` - Search for team members to @mention in messages
384
+ - `send_file_to_channel` - Upload a local file and send it as a message to a channel
260
385
 
261
386
  #### Chat Operations
262
387
  - `list_chats` - List user's chats (1:1 and group)
263
- - `get_chat_messages` - Retrieve messages from a specific chat with pagination and filtering
388
+ - `get_chat_messages` - Retrieve messages from a specific chat with pagination, filters, ordering, and `fetchAll`
264
389
  - `send_chat_message` - Send a message to a chat
265
390
  - `create_chat` - Create a new 1:1 or group chat
266
391
  - `update_chat_message` - Edit a previously sent chat message
267
392
  - `delete_chat_message` - Soft delete a chat message
393
+ - `send_file_to_chat` - Upload a local file and send it as a message to a chat
268
394
 
269
395
  #### Media Operations
270
- - `download_message_hosted_content` - Download hosted content (images, files) from messages
396
+ - `download_message_hosted_content` - Download hosted content (images, files) from channel messages
397
+ - `download_chat_hosted_content` - Download hosted content (images, files) from chat messages
271
398
 
272
399
  #### Search Operations
273
400
  - `search_messages` - Search across all Teams messages using KQL syntax
274
- - `get_recent_messages` - Get recent messages with advanced filtering options
275
- - `get_my_mentions` - Find messages mentioning the current user
401
+ - `get_my_mentions` - Find recent messages mentioning the current user
276
402
 
277
403
  ## šŸ“‹ Examples
278
404
 
@@ -281,7 +407,11 @@ npm run build && node dist/index.js
281
407
  First, authenticate with Microsoft Graph:
282
408
 
283
409
  ```bash
410
+ # Full access (default)
284
411
  npx @floriscornel/teams-mcp@latest authenticate
412
+
413
+ # Read-only (reduced permission scopes)
414
+ npx @floriscornel/teams-mcp@latest authenticate --read-only
285
415
  ```
286
416
 
287
417
  Check your authentication status:
@@ -296,9 +426,52 @@ Logout if needed:
296
426
  npx @floriscornel/teams-mcp@latest logout
297
427
  ```
298
428
 
429
+ ### Chat Pagination Example
430
+
431
+ ```json
432
+ {
433
+ "chatId": "19:meeting_...",
434
+ "limit": 100,
435
+ "fetchAll": true,
436
+ "orderBy": "createdDateTime",
437
+ "descending": true,
438
+ "contentFormat": "markdown"
439
+ }
440
+ ```
441
+
442
+ ### Channel Message with Mentions and Image
443
+
444
+ ```json
445
+ {
446
+ "teamId": "team-id",
447
+ "channelId": "channel-id",
448
+ "message": "Please review **today's update**",
449
+ "format": "markdown",
450
+ "importance": "high",
451
+ "mentions": [
452
+ {
453
+ "mention": "alex.chen",
454
+ "userId": "00000000-0000-0000-0000-000000000000"
455
+ }
456
+ ],
457
+ "imageUrl": "https://example.com/status.png"
458
+ }
459
+ ```
460
+
461
+ ### File Upload Example
462
+
463
+ ```json
464
+ {
465
+ "chatId": "19:meeting_...",
466
+ "filePath": "/absolute/path/to/report.pdf",
467
+ "message": "Please review the attached report",
468
+ "format": "markdown"
469
+ }
470
+ ```
471
+
299
472
  ### Integrating with Cursor/Claude
300
473
 
301
- This MCP server is designed to work with AI assistants like Claude/Cursor/VS Code through the Model Context Protocol.
474
+ This MCP server is designed to work with AI assistants like Claude/Cursor/VS Code through the Model Context Protocol.
302
475
 
303
476
  ```json
304
477
  {
@@ -313,9 +486,12 @@ This MCP server is designed to work with AI assistants like Claude/Cursor/VS Cod
313
486
 
314
487
  ## šŸ”’ Security
315
488
 
316
- - All authentication is handled through Microsoft's OAuth 2.0 flow
489
+ - All authentication is handled through Microsoft's OAuth 2.0 flow or a caller-provided Microsoft Graph token
317
490
  - **Refresh token support**: Access tokens are automatically renewed using cached refresh tokens, so you don't need to re-authenticate every hour
318
491
  - Token cache is stored locally at `~/.teams-mcp-token-cache.json`
492
+ - Auth metadata is stored locally at `~/.msgraph-mcp-auth.json`
493
+ - Markdown content is sanitized before sending HTML to Teams
494
+ - `AUTH_TOKEN` is validated to ensure it targets `https://graph.microsoft.com`
319
495
  - No sensitive data is logged or exposed
320
496
  - Follows Microsoft Graph API security best practices
321
497
 
@@ -328,7 +504,7 @@ MIT License - see LICENSE file for details
328
504
  1. Fork the repository
329
505
  2. Create a feature branch
330
506
  3. Make your changes
331
- 4. Run linting and formatting
507
+ 4. Run build, linting, and tests
332
508
  5. Submit a pull request
333
509
 
334
510
  ## šŸ“ž Support
@@ -336,4 +512,4 @@ MIT License - see LICENSE file for details
336
512
  For issues and questions:
337
513
  - Check the existing GitHub issues
338
514
  - Review Microsoft Graph API documentation
339
- - Ensure proper authentication and permissions are configured
515
+ - Ensure proper authentication and permissions are configured
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import { PublicClientApplication, } from "@azure/msal-node";
6
6
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
7
7
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
8
8
  import { cachePlugin } from "./msal-cache.js";
9
- import { GraphService } from "./services/graph.js";
9
+ import { FULL_SCOPES, GraphService, READ_ONLY_SCOPES } from "./services/graph.js";
10
10
  import { registerAuthTools } from "./tools/auth.js";
11
11
  import { registerChatTools } from "./tools/chats.js";
12
12
  import { registerSearchTools } from "./tools/search.js";
@@ -16,23 +16,27 @@ import { registerUsersTools } from "./tools/users.js";
16
16
  const CLIENT_ID = "14d82eec-204b-4c2f-b7e8-296a70dab67e";
17
17
  const AUTHORITY = "https://login.microsoftonline.com/common";
18
18
  const AUTH_INFO_PATH = join(homedir(), ".msgraph-mcp-auth.json");
19
- // Scopes for delegated (user) authentication
20
- const DELEGATED_SCOPES = [
21
- "User.Read",
22
- "User.ReadBasic.All",
23
- "Team.ReadBasic.All",
24
- "Channel.ReadBasic.All",
25
- "ChannelMessage.Read.All",
26
- "ChannelMessage.Send",
27
- "TeamMember.Read.All",
28
- "Chat.ReadBasic",
29
- "Chat.ReadWrite",
30
- ];
19
+ /** Check whether CLI args contain --read-only. */
20
+ function hasReadOnlyFlag(args) {
21
+ return args.includes("--read-only");
22
+ }
23
+ /** Read the persisted auth info file (best-effort). */
24
+ async function readAuthInfo() {
25
+ try {
26
+ const data = await fs.readFile(AUTH_INFO_PATH, "utf8");
27
+ return JSON.parse(data);
28
+ }
29
+ catch {
30
+ return undefined;
31
+ }
32
+ }
31
33
  // Authentication functions
32
- async function authenticate() {
34
+ async function authenticate(readOnly) {
35
+ const scopes = readOnly ? READ_ONLY_SCOPES : FULL_SCOPES;
36
+ const modeLabel = readOnly ? "read-only" : "full access";
33
37
  console.log("šŸ” Microsoft Graph Authentication for MCP Server");
34
38
  console.log("=".repeat(50));
35
- console.log("Using Microsoft Graph CLI app");
39
+ console.log(`Using Microsoft Graph CLI app (${modeLabel})`);
36
40
  try {
37
41
  console.log("\nšŸ“± Using device code flow...");
38
42
  const msalConfig = {
@@ -46,7 +50,7 @@ async function authenticate() {
46
50
  };
47
51
  const client = new PublicClientApplication(msalConfig);
48
52
  const result = await client.acquireTokenByDeviceCode({
49
- scopes: DELEGATED_SCOPES,
53
+ scopes,
50
54
  deviceCodeCallback: (response) => {
51
55
  console.log("\nšŸ“± Please complete authentication:");
52
56
  console.log(`🌐 Visit: ${response.verificationUri}`);
@@ -62,10 +66,12 @@ async function authenticate() {
62
66
  timestamp: new Date().toISOString(),
63
67
  expiresAt: result.expiresOn?.toISOString(),
64
68
  account: result.account?.username,
69
+ grantedScopes: result.scopes,
65
70
  };
66
71
  await fs.writeFile(AUTH_INFO_PATH, JSON.stringify(authInfo, null, 2));
67
72
  console.log("\nāœ… Authentication successful!");
68
73
  console.log(`šŸ‘¤ Signed in as: ${result.account?.username || "Unknown"}`);
74
+ console.log(`šŸ”’ Mode: ${modeLabel}`);
69
75
  console.log(`šŸ’¾ Credentials saved to: ${AUTH_INFO_PATH}`);
70
76
  console.log("šŸ”„ Refresh token cached for automatic renewal");
71
77
  console.log("\nšŸš€ You can now use the MCP server in Cursor!");
@@ -96,6 +102,18 @@ async function checkAuth() {
96
102
  console.log("āœ… Authentication found");
97
103
  console.log(`šŸ‘¤ Account: ${authInfo.account || "Unknown"}`);
98
104
  console.log(`šŸ“… Authenticated on: ${authInfo.timestamp}`);
105
+ // Show granted scope mode
106
+ const grantedScopes = authInfo.grantedScopes;
107
+ if (grantedScopes) {
108
+ const hasWriteScopes = grantedScopes.some((s) => s === "ChannelMessage.Send" ||
109
+ s === "ChannelMessage.ReadWrite" ||
110
+ s === "Chat.ReadWrite" ||
111
+ s === "Files.ReadWrite.All");
112
+ console.log(`šŸ”’ Scope mode: ${hasWriteScopes ? "full access" : "read-only"}`);
113
+ }
114
+ else {
115
+ console.log("āš ļø Scope mode: unknown (authenticated before read-only support)");
116
+ }
99
117
  // Check if we have expiration info
100
118
  if (authInfo.expiresAt) {
101
119
  const expiresAt = new Date(authInfo.expiresAt);
@@ -140,34 +158,56 @@ async function logout() {
140
158
  console.log("šŸ”„ Run 'npx @floriscornel/teams-mcp@latest authenticate' to re-authenticate");
141
159
  }
142
160
  // MCP Server setup
143
- async function startMcpServer() {
161
+ async function startMcpServer(readOnly) {
144
162
  // Create MCP server
145
163
  const server = new McpServer({
146
164
  name: "teams-mcp",
147
- version: "0.7.0",
165
+ version: "0.9.0",
148
166
  });
149
167
  // Initialize Graph service (singleton)
150
168
  const graphService = GraphService.getInstance();
151
- // Register all tools
152
- registerAuthTools(server, graphService);
153
- registerUsersTools(server, graphService);
154
- registerTeamsTools(server, graphService);
155
- registerChatTools(server, graphService);
156
- registerSearchTools(server, graphService);
169
+ graphService.readOnlyMode = readOnly;
170
+ // Detect scope mismatch: warn when switching from read-only → full mode
171
+ if (!readOnly && !process.env.AUTH_TOKEN) {
172
+ const authInfo = await readAuthInfo();
173
+ if (authInfo) {
174
+ const grantedScopes = authInfo.grantedScopes;
175
+ const hasWriteScopes = grantedScopes?.some((s) => s === "ChannelMessage.Send" ||
176
+ s === "ChannelMessage.ReadWrite" ||
177
+ s === "Chat.ReadWrite" ||
178
+ s === "Files.ReadWrite.All");
179
+ if (grantedScopes && !hasWriteScopes) {
180
+ console.error("āš ļø Warning: You authenticated with read-only scopes but the server is running in full mode.");
181
+ console.error(" Write operations may fail. Re-authenticate without --read-only:");
182
+ console.error(" npx @floriscornel/teams-mcp@latest authenticate");
183
+ }
184
+ else if (!grantedScopes) {
185
+ console.error("āš ļø Warning: Could not determine granted scopes. If you experience permission errors,");
186
+ console.error(" re-authenticate: npx @floriscornel/teams-mcp@latest authenticate");
187
+ }
188
+ }
189
+ }
190
+ // Register all tools (write tools are skipped when readOnly is true)
191
+ registerAuthTools(server, graphService, readOnly);
192
+ registerUsersTools(server, graphService, readOnly);
193
+ registerTeamsTools(server, graphService, readOnly);
194
+ registerChatTools(server, graphService, readOnly);
195
+ registerSearchTools(server, graphService, readOnly);
157
196
  // Start server
158
197
  const transport = new StdioServerTransport();
159
198
  await server.connect(transport);
160
- console.error("Microsoft Graph MCP Server started");
199
+ console.error(`Microsoft Graph MCP Server started${readOnly ? " (read-only mode)" : ""}`);
161
200
  }
162
201
  // Main function to handle both CLI and MCP server modes
163
202
  async function main() {
164
203
  const args = process.argv.slice(2);
165
- const command = args[0];
204
+ const command = args.find((arg) => arg !== "--read-only");
205
+ const readOnly = hasReadOnlyFlag(args) || process.env.TEAMS_MCP_READ_ONLY === "true";
166
206
  // CLI commands
167
207
  switch (command) {
168
208
  case "authenticate":
169
209
  case "auth":
170
- await authenticate();
210
+ await authenticate(readOnly);
171
211
  return;
172
212
  case "check":
173
213
  await checkAuth();
@@ -181,14 +221,19 @@ async function main() {
181
221
  console.log("Microsoft Graph MCP Server");
182
222
  console.log("");
183
223
  console.log("Usage:");
184
- console.log(" npx @floriscornel/teams-mcp@latest authenticate # Authenticate with Microsoft");
185
- console.log(" npx @floriscornel/teams-mcp@latest check # Check authentication status");
186
- console.log(" npx @floriscornel/teams-mcp@latest logout # Clear authentication");
187
- console.log(" npx @floriscornel/teams-mcp@latest # Start MCP server (default)");
224
+ console.log(" npx @floriscornel/teams-mcp@latest authenticate # Authenticate with full scopes");
225
+ console.log(" npx @floriscornel/teams-mcp@latest authenticate --read-only # Authenticate with read-only scopes");
226
+ console.log(" npx @floriscornel/teams-mcp@latest check # Check authentication status");
227
+ console.log(" npx @floriscornel/teams-mcp@latest logout # Clear authentication");
228
+ console.log(" npx @floriscornel/teams-mcp@latest # Start MCP server (default)");
229
+ console.log("");
230
+ console.log("Environment variables:");
231
+ console.log(" TEAMS_MCP_READ_ONLY=true # Start MCP server in read-only mode");
232
+ console.log(" AUTH_TOKEN=<jwt> # Use a pre-existing access token");
188
233
  return;
189
234
  case undefined:
190
235
  // No command = start MCP server
191
- await startMcpServer();
236
+ await startMcpServer(readOnly);
192
237
  return;
193
238
  default:
194
239
  console.error(`Unknown command: ${command}`);