@softeria/ms-365-mcp-server 0.169.2 → 0.170.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/.env.example CHANGED
@@ -15,7 +15,7 @@ MS365_MCP_CLIENT_SECRET=your-azure-ad-app-client-secret-here
15
15
  # Tenant ID - use "common" for multi-tenant or your specific tenant ID
16
16
  MS365_MCP_TENANT_ID=common
17
17
 
18
- # Cloud environment: global (default) or china (21Vianet)
18
+ # Cloud environment: global (default), china (21Vianet) or gcc-high (US Government)
19
19
  # MS365_MCP_CLOUD_TYPE=global
20
20
 
21
21
  # Instructions for Global Cloud:
@@ -37,6 +37,11 @@ MS365_MCP_TENANT_ID=common
37
37
  # 3. Follow the same steps as above
38
38
  # 4. Set MS365_MCP_CLOUD_TYPE=china
39
39
 
40
+ # Instructions for GCC High:
41
+ # 1. Go to https://portal.azure.us
42
+ # 2. Register an app in your government tenant (no default client ID exists)
43
+ # 3. Set MS365_MCP_CLIENT_ID, MS365_MCP_TENANT_ID and MS365_MCP_CLOUD_TYPE=gcc-high
44
+
40
45
  # -------------------------------------------------------------------
41
46
  # Azure Key Vault Integration (Optional)
42
47
  # -------------------------------------------------------------------
package/README.md CHANGED
@@ -11,10 +11,13 @@ API.
11
11
 
12
12
  This server supports multiple Microsoft cloud environments:
13
13
 
14
- | Cloud | Description | Auth Endpoint | Graph API Endpoint |
15
- | -------------------- | ---------------------------------- | ------------------------- | ------------------------------- |
16
- | **Global** (default) | International Microsoft 365 | login.microsoftonline.com | graph.microsoft.com |
17
- | **China** (21Vianet) | Microsoft 365 operated by 21Vianet | login.chinacloudapi.cn | microsoftgraph.chinacloudapi.cn |
14
+ | Cloud | Description | Auth Endpoint | Graph API Endpoint |
15
+ | -------------------- | ------------------------------------ | ------------------------- | ------------------------------- |
16
+ | **Global** (default) | International Microsoft 365 | login.microsoftonline.com | graph.microsoft.com |
17
+ | **China** (21Vianet) | Microsoft 365 operated by 21Vianet | login.chinacloudapi.cn | microsoftgraph.chinacloudapi.cn |
18
+ | **GCC High** | Microsoft 365 US Government GCC High | login.microsoftonline.us | graph.microsoft.us |
19
+
20
+ GCC High has no pre-registered client: register an app in your government tenant and set `MS365_MCP_CLIENT_ID` (and `MS365_MCP_TENANT_ID`), then use `--cloud gcc-high`.
18
21
 
19
22
  To route Graph traffic through a proxy of your own (one that terminates the client's plain HTTP and originates TLS to Microsoft), set `MS365_MCP_GRAPH_BASE_URL` to its base URL; see the environment variable list under CLI Options, including what still bypasses it.
20
23
 
@@ -673,7 +676,7 @@ The following options can be used when running ms-365-mcp-server directly from t
673
676
  --org-mode Enable organization/work mode from start (includes Teams, SharePoint, etc.)
674
677
  --work-mode Alias for --org-mode
675
678
  --force-work-scopes Backwards compatibility alias for --org-mode (deprecated)
676
- --cloud <type> Microsoft cloud environment: global (default) or china (21Vianet)
679
+ --cloud <type> Microsoft cloud environment: global (default), china (21Vianet) or gcc-high
677
680
  --allowed-scopes <scopes> Limit exposed tools to Graph scopes covered by this allowlist
678
681
  --extra-scopes <scopes> Append additional Graph scopes to the token request (for use with your own app registration + graph-batch)
679
682
  --expected-username <username> Require local MSAL auth to use this account username
@@ -745,7 +748,7 @@ Environment variables:
745
748
  - `MS365_MCP_ATTACHMENT_HOST=<host>`: Interface the `MS365_MCP_ATTACHMENT_PORT` listener binds (alternative to --attachment-host; requires `--attachment-port`). Defaults to the host `--http` bound — which for a wildcard `--http` means both ports answer everywhere and the port split isolates nothing. See "Splitting the attachment listener"
746
749
  - `MS365_MCP_HTTP_LOCAL_FILE_TOOLS=true|1`: Register download-bytes-to-file, and upload-attachment-from-file when `MS365_MCP_UPLOAD_DIRS` is set, over HTTP (alternative to --http-local-file-tools; same restrictions)
747
750
  - `MS365_MCP_UPLOAD_DIRS=<dirs>`: Directories `upload-attachment-from-file` may read from, as absolute paths separated like `PATH`. Unset (the default) leaves the tool unregistered. See "Attaching a local file in stdio mode"
748
- - `MS365_MCP_CLOUD_TYPE=global|china`: Microsoft cloud environment (alternative to --cloud flag)
751
+ - `MS365_MCP_CLOUD_TYPE=global|china|gcc-high`: Microsoft cloud environment (alternative to --cloud flag)
749
752
  - `MS365_MCP_GRAPH_BASE_URL=<url>`: Send Graph API requests to this base URL instead of the cloud's `graph.microsoft.com` (e.g. `http://127.0.0.1:10255/tenant-a/graph` for an egress proxy that originates TLS itself). Absolute http(s) URL without query or fragment; a path prefix is kept, so requests go to `<url>/v1.0/...`. The login authority and the On-Behalf-Of token resource are unchanged. Two things bypass it: `get-download-url` returns Graph's pre-authenticated SharePoint URL, and the `302` from `/content` points straight at SharePoint, so whoever follows either connects to SharePoint directly; if you control egress, allow those SharePoint hosts or disable those tools. Not read from `.env`
750
753
  - `LOG_LEVEL`: Set logging level (default: 'info')
751
754
  - `SILENT=true|1`: Disable console output
package/dist/cli.js CHANGED
@@ -55,7 +55,10 @@ program.name("ms-365-mcp-server").description("Microsoft 365 MCP Server").versio
55
55
  ).option("--list-presets", "List all available presets and exit").option("--list-permissions", "List all required Graph API permissions and exit").option(
56
56
  "--org-mode",
57
57
  "Enable organization/work mode from start (includes Teams, SharePoint, etc.)"
58
- ).option("--work-mode", "Alias for --org-mode").option("--force-work-scopes", "Backwards compatibility alias for --org-mode (deprecated)").option("--toon", "(experimental) Enable TOON output format for 30-60% token reduction").option("--discovery", "Enable runtime tool discovery and loading (experimental feature)").option("--cloud <type>", "Microsoft cloud environment: global (default) or china (21Vianet)").option(
58
+ ).option("--work-mode", "Alias for --org-mode").option("--force-work-scopes", "Backwards compatibility alias for --org-mode (deprecated)").option("--toon", "(experimental) Enable TOON output format for 30-60% token reduction").option("--discovery", "Enable runtime tool discovery and loading (experimental feature)").option(
59
+ "--cloud <type>",
60
+ "Microsoft cloud environment: global (default), china (21Vianet) or gcc-high (US Government GCC High)"
61
+ ).option(
59
62
  "--enable-dynamic-registration",
60
63
  "Enable OAuth Dynamic Client Registration endpoint (kept for backwards compatibility, now enabled by default in HTTP mode)"
61
64
  ).option(
@@ -8,6 +8,11 @@ const CLOUD_ENDPOINTS = {
8
8
  authority: "https://login.chinacloudapi.cn",
9
9
  graphApi: "https://microsoftgraph.chinacloudapi.cn",
10
10
  portal: "https://portal.azure.cn"
11
+ },
12
+ "gcc-high": {
13
+ authority: "https://login.microsoftonline.us",
14
+ graphApi: "https://graph.microsoft.us",
15
+ portal: "https://portal.azure.us"
11
16
  }
12
17
  };
13
18
  const DEFAULT_CLIENT_IDS = {
@@ -15,7 +20,13 @@ const DEFAULT_CLIENT_IDS = {
15
20
  china: "f3e61a6e-bc26-4281-8588-2c7359a02141"
16
21
  };
17
22
  function getDefaultClientId(cloudType = "global") {
18
- return DEFAULT_CLIENT_IDS[cloudType];
23
+ const clientId = DEFAULT_CLIENT_IDS[cloudType];
24
+ if (!clientId) {
25
+ throw new Error(
26
+ `No default client ID exists for the ${cloudType} cloud. Register an app in the ${cloudType} tenant and set MS365_MCP_CLIENT_ID.`
27
+ );
28
+ }
29
+ return clientId;
19
30
  }
20
31
  const GRAPH_BASE_URL_ENV = "MS365_MCP_GRAPH_BASE_URL";
21
32
  function getGraphBaseUrlOverride() {
@@ -3,10 +3,29 @@ const USER_ENTITY_PATH = /^\/users(?:\/[^/]+)?\/?$/i;
3
3
  const DIRECTORY_NAVIGATION_PATH = /^(?:\/me|\/users\/[^/]+)\/(?:manager|directReports)\/?$|^\/groups\/[^/]+\/(?:members|owners)\/?$/i;
4
4
  const EXPAND_USER_PATH = /^\/me\/?$|^\/groups(?:\/[^/]+)?\/?$/i;
5
5
  function normalizePath(path) {
6
- const withoutQuery = path.split("?")[0];
6
+ let withoutQuery = path.split("?")[0];
7
+ for (let i = 0; i < 4; i++) {
8
+ const next = withoutQuery.replace(
9
+ /%([0-9a-f]{2})/gi,
10
+ (_, hex) => String.fromCharCode(parseInt(hex, 16))
11
+ );
12
+ if (next === withoutQuery) break;
13
+ withoutQuery = next;
14
+ }
7
15
  return withoutQuery.startsWith("/") ? withoutQuery : `/${withoutQuery}`;
8
16
  }
17
+ const ENCODED_SEPARATOR = /%(?:25)*(?:2f|5c)/i;
18
+ function deleteOdataOption(params, name) {
19
+ const values = [];
20
+ for (const key of [...new Set(params.keys())]) {
21
+ if (key.replace(/^\$/, "").toLowerCase() !== name) continue;
22
+ values.push(...params.getAll(key));
23
+ params.delete(key);
24
+ }
25
+ return values;
26
+ }
9
27
  function userFieldEnforcement(path) {
28
+ if (ENCODED_SEPARATOR.test(path.split("?")[0])) return "request-and-response";
10
29
  const normalized = normalizePath(path);
11
30
  if (USER_ENTITY_PATH.test(normalized)) return "request-and-response";
12
31
  if (DIRECTORY_NAVIGATION_PATH.test(normalized)) return "response-only";
@@ -23,8 +42,7 @@ function stripUserFieldExpandFromUrl(url) {
23
42
  const [path, query = ""] = url.split("?");
24
43
  if (!shouldStripUserFieldExpand(path) || query === "") return url;
25
44
  const params = new URLSearchParams(query);
26
- if (!params.has("$expand")) return url;
27
- params.delete("$expand");
45
+ if (deleteOdataOption(params, "expand").length === 0) return url;
28
46
  const rebuilt = params.toString();
29
47
  if (rebuilt === "") return path;
30
48
  return `${path}?${rebuilt.replace(/%24/gi, "$").replace(/%2C/gi, ",")}`;
@@ -46,14 +64,15 @@ function restrictUserFieldQuery(queryParams, allowlist, enforcement) {
46
64
  function restrictUserFieldUrl(url, allowlist, enforcement) {
47
65
  const [path, query = ""] = url.split("?");
48
66
  const params = new URLSearchParams(query);
67
+ const requested = enforcement === "request-and-response" ? deleteOdataOption(params, "select") : params.getAll("$select");
49
68
  const fields = effectiveUserFields(
50
- parseSelectFields(params.get("$select") ?? void 0),
69
+ parseSelectFields(requested.join(",") || void 0),
51
70
  allowlist
52
71
  );
53
72
  if (enforcement === "request-and-response") {
54
73
  params.set("$select", fields.join(","));
55
74
  }
56
- params.delete("$expand");
75
+ deleteOdataOption(params, "expand");
57
76
  const rebuilt = params.toString();
58
77
  if (rebuilt === "") return { url: path, fields };
59
78
  return { url: `${path}?${rebuilt.replace(/%24/gi, "$").replace(/%2C/gi, ",")}`, fields };
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.169.2",
4
+ "version": "0.170.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",