@softeria/ms-365-mcp-server 0.140.0 → 0.142.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/README.md CHANGED
@@ -539,9 +539,9 @@ npx @softeria/ms-365-mcp-server --preset mail
539
539
  npx @softeria/ms-365-mcp-server --list-presets # See all available presets
540
540
  ```
541
541
 
542
- Available presets: `mail`, `calendar`, `files`, `personal`, `work`, `excel`, `contacts`, `tasks`, `onenote`, `search`, `users`, `outlook`, `onedrive`, `teams`, `all`
542
+ Available presets: `mail`, `calendar`, `files`, `personal`, `work`, `excel`, `contacts`, `tasks`, `onenote`, `search`, `users`, `outlook`, `onedrive`, `teams`, `teams-write`, `all`
543
543
 
544
- Each endpoint in `endpoints.json` declares which presets it belongs to via a `presets` array, so every preset is an exact tool-name allow-list that never over-matches across apps (e.g. `mail` does not include shared-mailbox tools; those are in `work`). The universal binary reader `download-bytes` is included in every preset, so whatever an app returns (a file, an attachment, a photo, a recording) can always be fetched; `get-download-url` (a pre-authenticated URL for drive/SharePoint files) rides with the drive-backed presets. So a preset that can find a file can always read its bytes.
544
+ Each endpoint in `endpoints.json` declares which presets it belongs to via a `presets` array, so every preset is an exact tool-name allow-list that never over-matches across apps (e.g. `mail` does not include shared-mailbox tools; those are in `work`). The universal binary reader `download-bytes` is included in every preset except `teams-write`, so whatever an app returns (a file, an attachment, a photo, a recording) can always be fetched; `get-download-url` (a pre-authenticated URL for drive/SharePoint files) rides with the drive-backed presets. So a preset that can find a file can always read its bytes.
545
545
 
546
546
  The `outlook`, `onedrive` and `teams` presets are app-scoped: they expose exactly one Microsoft app. Use these for "expose exactly one app" deployments:
547
547
 
@@ -553,6 +553,12 @@ npx @softeria/ms-365-mcp-server --preset outlook
553
553
  npx @softeria/ms-365-mcp-server --org-mode --preset teams
554
554
  ```
555
555
 
556
+ The `teams-write` preset is the send-only counterpart to `--read-only`: send in chats, send/reply in channels, list chats/teams/channels by name, and activity notifications - no message reading and no byte downloaders. The requested token is minimal by construction (`Chat.ReadBasic`, the `*.Send` scopes, and basic team/channel listing - nothing that can read message content):
557
+
558
+ ```bash
559
+ npx @softeria/ms-365-mcp-server --org-mode --preset teams-write
560
+ ```
561
+
556
562
  ## Dynamic Tool Discovery
557
563
 
558
564
  Instead of loading every tool upfront, use dynamic discovery so the LLM finds and loads tools only when it needs them:
package/dist/auth.js CHANGED
@@ -62,6 +62,8 @@ function buildDiskCoherencyCachePlugin(storage) {
62
62
  };
63
63
  }
64
64
  const SCOPE_HIERARCHY = {
65
+ "Chat.ReadWrite": ["Chat.Read", "Chat.ReadBasic"],
66
+ "Chat.Read": ["Chat.ReadBasic"],
65
67
  "Mail.ReadWrite": ["Mail.Read"],
66
68
  "Calendars.ReadWrite": ["Calendars.Read"],
67
69
  "Files.ReadWrite": ["Files.Read"],
@@ -137,7 +139,7 @@ function getEndpointEffectiveLoginScopes(scopeGroups, allowedScopes) {
137
139
  function collapseRedundantScopes(scopes) {
138
140
  const scopesSet = new Set(scopes);
139
141
  Object.entries(SCOPE_HIERARCHY).forEach(([higherScope, lowerScopes]) => {
140
- if (scopesSet.has(higherScope) && lowerScopes.every((scope) => scopesSet.has(scope))) {
142
+ if (scopesSet.has(higherScope)) {
141
143
  lowerScopes.forEach((scope) => scopesSet.delete(scope));
142
144
  }
143
145
  });
package/dist/cli.js CHANGED
@@ -31,7 +31,7 @@ program.name("ms-365-mcp-server").description("Microsoft 365 MCP Server").versio
31
31
  "Append additional Graph scopes (whitespace-separated) to the token request, beyond those derived from enabled tools. Use with your own app registration (MS365_MCP_CLIENT_ID/SECRET) to request scopes the default app does not declare, then call the endpoints via graph-batch."
32
32
  ).option(
33
33
  "--preset <names>",
34
- "Use preset tool categories (comma-separated). Available: mail, calendar, files, personal, work, excel, contacts, tasks, onenote, search, users, all"
34
+ "Use preset tool categories (comma-separated). Available: mail, calendar, files, personal, work, excel, contacts, tasks, onenote, search, users, outlook, onedrive, teams, teams-write, all"
35
35
  ).option("--list-presets", "List all available presets and exit").option("--list-permissions", "List all required Graph API permissions and exit").option(
36
36
  "--org-mode",
37
37
  "Enable organization/work mode from start (includes Teams, SharePoint, etc.)"
@@ -1559,15 +1559,15 @@
1559
1559
  "pathPattern": "/me/chats",
1560
1560
  "method": "get",
1561
1561
  "toolName": "list-chats",
1562
- "presets": ["teams", "work"],
1563
- "workScopes": ["Chat.Read"]
1562
+ "presets": ["teams", "teams-write", "work"],
1563
+ "workScopes": ["Chat.ReadBasic"]
1564
1564
  },
1565
1565
  {
1566
1566
  "pathPattern": "/chats/{chat-id}",
1567
1567
  "method": "get",
1568
1568
  "toolName": "get-chat",
1569
- "presets": ["teams", "work"],
1570
- "workScopes": ["Chat.Read"]
1569
+ "presets": ["teams", "teams-write", "work"],
1570
+ "workScopes": ["Chat.ReadBasic"]
1571
1571
  },
1572
1572
  {
1573
1573
  "pathPattern": "/chats/{chat-id}/members",
@@ -1610,7 +1610,7 @@
1610
1610
  "pathPattern": "/chats/{chat-id}/messages",
1611
1611
  "method": "post",
1612
1612
  "toolName": "send-chat-message",
1613
- "presets": ["teams", "work"],
1613
+ "presets": ["teams", "teams-write", "work"],
1614
1614
  "workScopes": ["ChatMessage.Send"],
1615
1615
  "llmTip": "Use contentType 'html' in the body — plain text contentType gets mangled by Graph API. To @mention someone, put an <at id=\"0\">Name</at> tag in the html content and add a mentions array to the request body, as a sibling of the body property (not a separate top-level tool parameter): { body: { contentType: 'html', content: 'Hi <at id=\"0\">Name</at>' }, mentions: [{ id: 0, mentionText: 'Name', mentioned: { user: { id: '<aad-user-id>', displayName: 'Name', userIdentityType: 'aadUser' } } }] }. Each mention id must match its <at id> value; include userIdentityType: 'aadUser' or Graph returns 400 'value without a type name'."
1616
1616
  },
@@ -1618,28 +1618,28 @@
1618
1618
  "pathPattern": "/me/joinedTeams",
1619
1619
  "method": "get",
1620
1620
  "toolName": "list-joined-teams",
1621
- "presets": ["teams", "work"],
1621
+ "presets": ["teams", "teams-write", "work"],
1622
1622
  "workScopes": ["Team.ReadBasic.All"]
1623
1623
  },
1624
1624
  {
1625
1625
  "pathPattern": "/teams/{team-id}",
1626
1626
  "method": "get",
1627
1627
  "toolName": "get-team",
1628
- "presets": ["teams", "work"],
1628
+ "presets": ["teams", "teams-write", "work"],
1629
1629
  "workScopes": ["Team.ReadBasic.All"]
1630
1630
  },
1631
1631
  {
1632
1632
  "pathPattern": "/teams/{team-id}/channels",
1633
1633
  "method": "get",
1634
1634
  "toolName": "list-team-channels",
1635
- "presets": ["teams", "work"],
1635
+ "presets": ["teams", "teams-write", "work"],
1636
1636
  "workScopes": ["Channel.ReadBasic.All"]
1637
1637
  },
1638
1638
  {
1639
1639
  "pathPattern": "/teams/{team-id}/channels/{channel-id}",
1640
1640
  "method": "get",
1641
1641
  "toolName": "get-team-channel",
1642
- "presets": ["teams", "work"],
1642
+ "presets": ["teams", "teams-write", "work"],
1643
1643
  "workScopes": ["Channel.ReadBasic.All"]
1644
1644
  },
1645
1645
  {
@@ -1696,7 +1696,7 @@
1696
1696
  "pathPattern": "/teams/{team-id}/channels/{channel-id}/messages",
1697
1697
  "method": "post",
1698
1698
  "toolName": "send-channel-message",
1699
- "presets": ["teams", "work"],
1699
+ "presets": ["teams", "teams-write", "work"],
1700
1700
  "workScopes": ["ChannelMessage.Send"],
1701
1701
  "llmTip": "Use contentType 'html' in the body — plain text contentType gets mangled by Graph API. To @mention someone, put an <at id=\"0\">Name</at> tag in the html content and add a mentions array to the request body, as a sibling of the body property (not a separate top-level tool parameter): { body: { contentType: 'html', content: 'Hi <at id=\"0\">Name</at>' }, mentions: [{ id: 0, mentionText: 'Name', mentioned: { user: { id: '<aad-user-id>', displayName: 'Name', userIdentityType: 'aadUser' } } }] }. Each mention id must match its <at id> value; include userIdentityType: 'aadUser' or Graph returns 400 'value without a type name'."
1702
1702
  },
@@ -1704,7 +1704,7 @@
1704
1704
  "pathPattern": "/teams/{team-id}/channels/{channel-id}/messages/{chatMessage-id}/replies",
1705
1705
  "method": "post",
1706
1706
  "toolName": "reply-to-channel-message",
1707
- "presets": ["teams", "work"],
1707
+ "presets": ["teams", "teams-write", "work"],
1708
1708
  "workScopes": ["ChannelMessage.Send"],
1709
1709
  "llmTip": "Use contentType 'html' in the body — plain text contentType gets mangled by Graph API. To @mention someone, put an <at id=\"0\">Name</at> tag in the html content and add a mentions array to the request body, as a sibling of the body property (not a separate top-level tool parameter): { body: { contentType: 'html', content: 'Hi <at id=\"0\">Name</at>' }, mentions: [{ id: 0, mentionText: 'Name', mentioned: { user: { id: '<aad-user-id>', displayName: 'Name', userIdentityType: 'aadUser' } } }] }. Each mention id must match its <at id> value; include userIdentityType: 'aadUser' or Graph returns 400 'value without a type name'."
1710
1710
  },
@@ -2726,7 +2726,7 @@
2726
2726
  "pathPattern": "/me/teamwork/sendActivityNotification",
2727
2727
  "method": "post",
2728
2728
  "toolName": "send-my-activity-notification",
2729
- "presets": ["teams", "work"],
2729
+ "presets": ["teams", "teams-write", "work"],
2730
2730
  "workScopes": ["TeamsActivity.Send"],
2731
2731
  "contentType": "application/json",
2732
2732
  "llmTip": "Sends a Teams activity feed notification to the current user (the badge + entry in their Activity tab). Body: { topic: { source: 'entityUrl' | 'text', value: <Graph URL or plain text>, webUrl: <required when source='text', click-through URL> }, activityType: <must be declared in the calling Teams app's manifest, OR the reserved 'systemDefault' which provides free-form Actor+Reason text>, previewText: { content: 'short preview' }, templateParameters?: [{ name, value }] (substituted into the manifest's localized notification template), teamsAppId?: <optional disambiguator when multiple installed apps share a Microsoft Entra app ID — fetch via list-my-installed-teams-apps>, chainId?, iconId? }. Use to ping the user with 'action required' notifications from agentic workflows. See https://learn.microsoft.com/graph/teams-send-activityfeednotifications."
@@ -51,12 +51,18 @@ const PRESET_META = {
51
51
  teams: {
52
52
  description: "Teams app only: chats, channels, meetings and presence",
53
53
  requiresOrgMode: true
54
+ },
55
+ "teams-write": {
56
+ description: "Teams send-only: send/reply in chats and channels, list chats/teams/channels by name, activity notifications - no message reading",
57
+ requiresOrgMode: true,
58
+ // A write-only preset must not include the generic byte readers.
59
+ omitUniversalUtilities: true
54
60
  }
55
61
  };
56
62
  const UNIVERSAL_UTILITY_TOOLS = ["download-bytes", "download-bytes-to-file"];
57
63
  const SCOPED_UTILITY_TOOLS = {
58
64
  "get-download-url": ["files", "onedrive", "personal", "work", "search"],
59
- "parse-teams-url": ["teams", "work"]
65
+ "parse-teams-url": ["teams", "teams-write", "work"]
60
66
  };
61
67
  for (const [tool, presets] of Object.entries(SCOPED_UTILITY_TOOLS)) {
62
68
  for (const preset of presets) {
@@ -76,7 +82,7 @@ function presetPattern(preset) {
76
82
  }
77
83
  const names = [
78
84
  ...endpointNames,
79
- ...UNIVERSAL_UTILITY_TOOLS,
85
+ ...PRESET_META[preset]?.omitUniversalUtilities ? [] : UNIVERSAL_UTILITY_TOOLS,
80
86
  ...Object.entries(SCOPED_UTILITY_TOOLS).filter(([, presets]) => presets.includes(preset)).map(([name]) => name)
81
87
  ];
82
88
  return new RegExp(`^(?:${names.join("|")})$`);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@softeria/ms-365-mcp-server",
3
3
  "mcpName": "io.github.Softeria/ms-365-mcp-server",
4
- "version": "0.140.0",
4
+ "version": "0.142.0",
5
5
  "description": " A Model Context Protocol (MCP) server for interacting with Microsoft 365 and Office services through the Graph API",
6
6
  "type": "module",
7
7
  "main": "dist/index.js",
@@ -1559,15 +1559,15 @@
1559
1559
  "pathPattern": "/me/chats",
1560
1560
  "method": "get",
1561
1561
  "toolName": "list-chats",
1562
- "presets": ["teams", "work"],
1563
- "workScopes": ["Chat.Read"]
1562
+ "presets": ["teams", "teams-write", "work"],
1563
+ "workScopes": ["Chat.ReadBasic"]
1564
1564
  },
1565
1565
  {
1566
1566
  "pathPattern": "/chats/{chat-id}",
1567
1567
  "method": "get",
1568
1568
  "toolName": "get-chat",
1569
- "presets": ["teams", "work"],
1570
- "workScopes": ["Chat.Read"]
1569
+ "presets": ["teams", "teams-write", "work"],
1570
+ "workScopes": ["Chat.ReadBasic"]
1571
1571
  },
1572
1572
  {
1573
1573
  "pathPattern": "/chats/{chat-id}/members",
@@ -1610,7 +1610,7 @@
1610
1610
  "pathPattern": "/chats/{chat-id}/messages",
1611
1611
  "method": "post",
1612
1612
  "toolName": "send-chat-message",
1613
- "presets": ["teams", "work"],
1613
+ "presets": ["teams", "teams-write", "work"],
1614
1614
  "workScopes": ["ChatMessage.Send"],
1615
1615
  "llmTip": "Use contentType 'html' in the body — plain text contentType gets mangled by Graph API. To @mention someone, put an <at id=\"0\">Name</at> tag in the html content and add a mentions array to the request body, as a sibling of the body property (not a separate top-level tool parameter): { body: { contentType: 'html', content: 'Hi <at id=\"0\">Name</at>' }, mentions: [{ id: 0, mentionText: 'Name', mentioned: { user: { id: '<aad-user-id>', displayName: 'Name', userIdentityType: 'aadUser' } } }] }. Each mention id must match its <at id> value; include userIdentityType: 'aadUser' or Graph returns 400 'value without a type name'."
1616
1616
  },
@@ -1618,28 +1618,28 @@
1618
1618
  "pathPattern": "/me/joinedTeams",
1619
1619
  "method": "get",
1620
1620
  "toolName": "list-joined-teams",
1621
- "presets": ["teams", "work"],
1621
+ "presets": ["teams", "teams-write", "work"],
1622
1622
  "workScopes": ["Team.ReadBasic.All"]
1623
1623
  },
1624
1624
  {
1625
1625
  "pathPattern": "/teams/{team-id}",
1626
1626
  "method": "get",
1627
1627
  "toolName": "get-team",
1628
- "presets": ["teams", "work"],
1628
+ "presets": ["teams", "teams-write", "work"],
1629
1629
  "workScopes": ["Team.ReadBasic.All"]
1630
1630
  },
1631
1631
  {
1632
1632
  "pathPattern": "/teams/{team-id}/channels",
1633
1633
  "method": "get",
1634
1634
  "toolName": "list-team-channels",
1635
- "presets": ["teams", "work"],
1635
+ "presets": ["teams", "teams-write", "work"],
1636
1636
  "workScopes": ["Channel.ReadBasic.All"]
1637
1637
  },
1638
1638
  {
1639
1639
  "pathPattern": "/teams/{team-id}/channels/{channel-id}",
1640
1640
  "method": "get",
1641
1641
  "toolName": "get-team-channel",
1642
- "presets": ["teams", "work"],
1642
+ "presets": ["teams", "teams-write", "work"],
1643
1643
  "workScopes": ["Channel.ReadBasic.All"]
1644
1644
  },
1645
1645
  {
@@ -1696,7 +1696,7 @@
1696
1696
  "pathPattern": "/teams/{team-id}/channels/{channel-id}/messages",
1697
1697
  "method": "post",
1698
1698
  "toolName": "send-channel-message",
1699
- "presets": ["teams", "work"],
1699
+ "presets": ["teams", "teams-write", "work"],
1700
1700
  "workScopes": ["ChannelMessage.Send"],
1701
1701
  "llmTip": "Use contentType 'html' in the body — plain text contentType gets mangled by Graph API. To @mention someone, put an <at id=\"0\">Name</at> tag in the html content and add a mentions array to the request body, as a sibling of the body property (not a separate top-level tool parameter): { body: { contentType: 'html', content: 'Hi <at id=\"0\">Name</at>' }, mentions: [{ id: 0, mentionText: 'Name', mentioned: { user: { id: '<aad-user-id>', displayName: 'Name', userIdentityType: 'aadUser' } } }] }. Each mention id must match its <at id> value; include userIdentityType: 'aadUser' or Graph returns 400 'value without a type name'."
1702
1702
  },
@@ -1704,7 +1704,7 @@
1704
1704
  "pathPattern": "/teams/{team-id}/channels/{channel-id}/messages/{chatMessage-id}/replies",
1705
1705
  "method": "post",
1706
1706
  "toolName": "reply-to-channel-message",
1707
- "presets": ["teams", "work"],
1707
+ "presets": ["teams", "teams-write", "work"],
1708
1708
  "workScopes": ["ChannelMessage.Send"],
1709
1709
  "llmTip": "Use contentType 'html' in the body — plain text contentType gets mangled by Graph API. To @mention someone, put an <at id=\"0\">Name</at> tag in the html content and add a mentions array to the request body, as a sibling of the body property (not a separate top-level tool parameter): { body: { contentType: 'html', content: 'Hi <at id=\"0\">Name</at>' }, mentions: [{ id: 0, mentionText: 'Name', mentioned: { user: { id: '<aad-user-id>', displayName: 'Name', userIdentityType: 'aadUser' } } }] }. Each mention id must match its <at id> value; include userIdentityType: 'aadUser' or Graph returns 400 'value without a type name'."
1710
1710
  },
@@ -2726,7 +2726,7 @@
2726
2726
  "pathPattern": "/me/teamwork/sendActivityNotification",
2727
2727
  "method": "post",
2728
2728
  "toolName": "send-my-activity-notification",
2729
- "presets": ["teams", "work"],
2729
+ "presets": ["teams", "teams-write", "work"],
2730
2730
  "workScopes": ["TeamsActivity.Send"],
2731
2731
  "contentType": "application/json",
2732
2732
  "llmTip": "Sends a Teams activity feed notification to the current user (the badge + entry in their Activity tab). Body: { topic: { source: 'entityUrl' | 'text', value: <Graph URL or plain text>, webUrl: <required when source='text', click-through URL> }, activityType: <must be declared in the calling Teams app's manifest, OR the reserved 'systemDefault' which provides free-form Actor+Reason text>, previewText: { content: 'short preview' }, templateParameters?: [{ name, value }] (substituted into the manifest's localized notification template), teamsAppId?: <optional disambiguator when multiple installed apps share a Microsoft Entra app ID — fetch via list-my-installed-teams-apps>, chainId?, iconId? }. Use to ping the user with 'action required' notifications from agentic workflows. See https://learn.microsoft.com/graph/teams-send-activityfeednotifications."