@softeria/ms-365-mcp-server 0.161.2 → 0.161.4

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
@@ -633,7 +633,7 @@ npx @softeria/ms-365-mcp-server --list-presets # See all available presets
633
633
 
634
634
  Available presets: `mail`, `calendar`, `files`, `personal`, `work`, `excel`, `contacts`, `tasks`, `onenote`, `search`, `users`, `outlook`, `onedrive`, `teams`, `teams-write`, `all`
635
635
 
636
- 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.
636
+ 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, and joins every other preset (bar `teams-write`) when HTTP mode runs with `--enable-attachment-urls`. So a preset that can find a file can always read its bytes.
637
637
 
638
638
  The `outlook`, `onedrive` and `teams` presets are app-scoped: they expose exactly one Microsoft app. Use these for "expose exactly one app" deployments:
639
639
 
@@ -699,7 +699,8 @@ When running as an MCP server, the following options can be used:
699
699
  --trust-proxy-auth
700
700
  --enable-auth-tools Enable login/logout tools when using HTTP mode (disabled by default in HTTP mode)
701
701
  --enable-attachment-urls Let get-download-url mint a server-served URL for byte resources Graph
702
- exposes no pre-authenticated URL for (see "Server-Minted Attachment URLs")
702
+ exposes no pre-authenticated URL for (see "Server-Minted Attachment URLs").
703
+ Also adds get-download-url to any --preset that lacks it (except teams-write)
703
704
  --attachment-port <port> Serve /attachment on its own listener on this port instead of on the
704
705
  MCP app, so a fetcher that can read attachments cannot also reach /mcp
705
706
  (requires --enable-attachment-urls; see "Splitting the attachment listener")
@@ -761,7 +762,13 @@ agent's context. A 73 KB, 3-page PDF costs about 24,500 tokens that way, and the
761
762
  cannot parse them anyway.
762
763
 
763
764
  `--enable-attachment-urls` (HTTP mode, off by default) closes that gap. When Graph has no
764
- URL of its own, `get-download-url` mints one this server serves:
765
+ URL of its own, `get-download-url` mints one this server serves. With it on,
766
+ `get-download-url` is registered under every `--preset` (except the send-only `teams-write`),
767
+ so a `--preset mail` agent that lists an attachment can mint a URL for it. A minted URL only
768
+ works if the preset's scopes cover the resource: a contacts, tasks or excel preset has no Mail
769
+ scopes, so a URL minted for a mail attachment fails when fetched.
770
+
771
+ The minted URL looks like:
765
772
 
766
773
  ```
767
774
  GET /attachment?t=<ticket>&dgk=<key-id>&dgx=<expiry>&dgs=<signature>
package/dist/cli.js CHANGED
@@ -29,7 +29,7 @@ program.name("ms-365-mcp-server").description("Microsoft 365 MCP Server").versio
29
29
  "Enable login/logout tools when using HTTP mode (disabled by default in HTTP mode)"
30
30
  ).option(
31
31
  "--enable-attachment-urls",
32
- "HTTP mode only. Let get-download-url mint a short-TTL, single-use URL served by this server for Graph byte resources that expose no pre-authenticated URL of their own (mail and event attachments, meeting recordings, other $value endpoints). Requires MS365_MCP_ATTACHMENT_URL_BASE and MS365_MCP_ATTACHMENT_URL_KEY (or _KEY_FILE)"
32
+ "HTTP mode only. Let get-download-url mint a short-TTL, single-use URL served by this server for Graph byte resources that expose no pre-authenticated URL of their own (mail and event attachments, meeting recordings, other $value endpoints). Also adds get-download-url to any --preset that lacks it (except teams-write). Requires MS365_MCP_ATTACHMENT_URL_BASE and MS365_MCP_ATTACHMENT_URL_KEY (or _KEY_FILE)"
33
33
  ).option(
34
34
  "--attachment-port <port>",
35
35
  "HTTP mode only. Serve the attachment download route on its own listener on this port instead of on the MCP app, so a caller that can fetch attachments cannot also reach /mcp. Requires --enable-attachment-urls, and MS365_MCP_ATTACHMENT_URL_BASE must name this port. A separate port only isolates the two surfaces if they also bind separate interfaces \u2014 see --attachment-host. Equivalent env var: MS365_MCP_ATTACHMENT_PORT."
@@ -106,7 +106,9 @@ function parseArgs() {
106
106
  if (options.preset) {
107
107
  const presetNames = options.preset.split(",").map((p) => p.trim());
108
108
  try {
109
- options.enabledTools = getCombinedPresetPattern(presetNames);
109
+ options.enabledTools = getCombinedPresetPattern(presetNames, {
110
+ attachmentUrls: Boolean(options.http && options.enableAttachmentUrls)
111
+ });
110
112
  const requiresOrgMode = presetNames.some((preset) => presetRequiresOrgMode(preset));
111
113
  if (requiresOrgMode && !options.orgMode) {
112
114
  console.warn(
@@ -1036,6 +1036,19 @@ const UTILITY_TOOLS = [
1036
1036
  }
1037
1037
  const pathPart = target.replace(/\/+$/, "");
1038
1038
  if (/^(\/me|\/users\/[^/]+)\/messages\/[^/]+\/attachments\//.test(pathPart) || /^(\/me|\/users\/[^/]+)\/events\/[^/]+\/attachments\//.test(pathPart) || /^\/groups\/[^/]+\/messages\/[^/]+\/attachments\//.test(pathPart) || /^\/groups\/[^/]+\/events\/[^/]+\/attachments\//.test(pathPart)) {
1039
+ if (getAttachmentMinting() && !pathPart.endsWith("/$value")) {
1040
+ return {
1041
+ content: [
1042
+ {
1043
+ type: "text",
1044
+ text: JSON.stringify({
1045
+ error: "Only the /$value byte endpoint can be minted a download URL: end target with /$value, e.g. /me/messages/{message-id}/attachments/{attachment-id}/$value. The attachment resource itself returns JSON with base64 contentBytes."
1046
+ })
1047
+ }
1048
+ ],
1049
+ isError: true
1050
+ };
1051
+ }
1039
1052
  const minted = await mintDownloadUrl(pathPart, accountParam, authManager);
1040
1053
  if (minted) return minted;
1041
1054
  return {
@@ -1053,6 +1066,19 @@ const UTILITY_TOOLS = [
1053
1066
  if (/^(\/me|\/users\/[^/]+)\/onlineMeetings\/[^/]+\/recordings\/[^/]+(?:\/content)?$/.test(
1054
1067
  pathPart
1055
1068
  ) || /^\/communications\/calls\/[^/]+\/recordings\/[^/]+(?:\/content)?$/.test(pathPart)) {
1069
+ if (getAttachmentMinting() && !pathPart.endsWith("/content")) {
1070
+ return {
1071
+ content: [
1072
+ {
1073
+ type: "text",
1074
+ text: JSON.stringify({
1075
+ error: "Only the /content byte endpoint can be minted a download URL: end target with /content, e.g. /me/onlineMeetings/{meeting-id}/recordings/{recording-id}/content."
1076
+ })
1077
+ }
1078
+ ],
1079
+ isError: true
1080
+ };
1081
+ }
1056
1082
  const minted = await mintDownloadUrl(pathPart, accountParam, authManager);
1057
1083
  if (minted) return minted;
1058
1084
  return {
@@ -101,7 +101,7 @@ const TOOL_CATEGORIES = {
101
101
  description: "All available tools"
102
102
  }
103
103
  };
104
- function getCombinedPresetPattern(presets) {
104
+ function getCombinedPresetPattern(presets, { attachmentUrls = false } = {}) {
105
105
  const patterns = presets.map((preset) => {
106
106
  const category = TOOL_CATEGORIES[preset];
107
107
  if (!category) {
@@ -111,6 +111,9 @@ function getCombinedPresetPattern(presets) {
111
111
  }
112
112
  return category.pattern.source;
113
113
  });
114
+ if (attachmentUrls && presets.some((p) => !PRESET_META[p]?.omitUniversalUtilities) && !patterns.some((p) => new RegExp(p).test("get-download-url"))) {
115
+ patterns.push("^get-download-url$");
116
+ }
114
117
  return patterns.join("|");
115
118
  }
116
119
  function listPresets() {
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.161.2",
4
+ "version": "0.161.4",
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",