@softeria/ms-365-mcp-server 0.158.1 → 0.159.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
@@ -16,6 +16,8 @@ This server supports multiple Microsoft cloud environments:
16
16
  | **Global** (default) | International Microsoft 365 | login.microsoftonline.com | graph.microsoft.com |
17
17
  | **China** (21Vianet) | Microsoft 365 operated by 21Vianet | login.chinacloudapi.cn | microsoftgraph.chinacloudapi.cn |
18
18
 
19
+ 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
+
19
21
  ## Prerequisites
20
22
 
21
23
  - Node.js >= 20 (recommended)
@@ -734,6 +736,7 @@ Environment variables:
734
736
  - `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"
735
737
  - `MS365_MCP_HTTP_LOCAL_FILE_TOOLS=true|1`: Register download-bytes-to-file over HTTP (alternative to --http-local-file-tools; same restrictions)
736
738
  - `MS365_MCP_CLOUD_TYPE=global|china`: Microsoft cloud environment (alternative to --cloud flag)
739
+ - `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`
737
740
  - `LOG_LEVEL`: Set logging level (default: 'info')
738
741
  - `SILENT=true|1`: Disable console output
739
742
  - `MS365_MCP_REDACT_PII=false|0`: Disable scrubbing of JWTs, Bearer headers, OAuth token fields, and email addresses from log messages (default: enabled). The server handles live Graph bearer tokens, so redaction is on unless you opt out for fully verbose local debugging.
package/dist/auth.js CHANGED
@@ -4,7 +4,7 @@ import { readFileSync } from "fs";
4
4
  import { fileURLToPath } from "url";
5
5
  import path from "path";
6
6
  import { getSecrets } from "./secrets.js";
7
- import { getCloudEndpoints, getDefaultClientId } from "./cloud-config.js";
7
+ import { getCloudEndpoints, getDefaultClientId, getGraphBaseUrl } from "./cloud-config.js";
8
8
  import {
9
9
  dedupeRefreshTokens
10
10
  } from "./lib/cache-dedupe.js";
@@ -910,8 +910,7 @@ class AuthManager {
910
910
  logger.info("Token retrieved successfully, testing Graph API access...");
911
911
  try {
912
912
  const secrets = await getSecrets();
913
- const cloudEndpoints = getCloudEndpoints(secrets.cloudType);
914
- const response = await fetch(`${cloudEndpoints.graphApi}/v1.0/me`, {
913
+ const response = await fetch(`${getGraphBaseUrl(secrets.cloudType)}/v1.0/me`, {
915
914
  headers: {
916
915
  Authorization: `Bearer ${token}`
917
916
  }
@@ -17,6 +17,23 @@ const DEFAULT_CLIENT_IDS = {
17
17
  function getDefaultClientId(cloudType = "global") {
18
18
  return DEFAULT_CLIENT_IDS[cloudType];
19
19
  }
20
+ const GRAPH_BASE_URL_ENV = "MS365_MCP_GRAPH_BASE_URL";
21
+ function getGraphBaseUrlOverride() {
22
+ const raw = process.env[GRAPH_BASE_URL_ENV]?.trim();
23
+ if (!raw) return void 0;
24
+ let parsed;
25
+ try {
26
+ parsed = new URL(raw);
27
+ } catch {
28
+ parsed = void 0;
29
+ }
30
+ if (!parsed || !/^https?:$/.test(parsed.protocol) || parsed.search || parsed.hash) {
31
+ throw new Error(
32
+ `${GRAPH_BASE_URL_ENV} must be an absolute http(s) URL without query or fragment, got: ${raw}`
33
+ );
34
+ }
35
+ return raw.replace(/\/+$/, "");
36
+ }
20
37
  function getCloudEndpoints(cloudType = "global") {
21
38
  const endpoints = CLOUD_ENDPOINTS[cloudType];
22
39
  if (!endpoints) {
@@ -26,6 +43,9 @@ function getCloudEndpoints(cloudType = "global") {
26
43
  }
27
44
  return endpoints;
28
45
  }
46
+ function getGraphBaseUrl(cloudType = "global") {
47
+ return getGraphBaseUrlOverride() ?? getCloudEndpoints(cloudType).graphApi;
48
+ }
29
49
  function isValidCloudType(value) {
30
50
  return value in CLOUD_ENDPOINTS;
31
51
  }
@@ -44,6 +64,7 @@ export {
44
64
  DEFAULT_CLIENT_IDS,
45
65
  getCloudEndpoints,
46
66
  getDefaultClientId,
67
+ getGraphBaseUrl,
47
68
  isValidCloudType,
48
69
  parseCloudType
49
70
  };
@@ -1,6 +1,6 @@
1
1
  import logger from "./logger.js";
2
2
  import { encode as toonEncode } from "@toon-format/toon";
3
- import { getCloudEndpoints } from "./cloud-config.js";
3
+ import { getGraphBaseUrl } from "./cloud-config.js";
4
4
  import { getRequestTokens } from "./request-context.js";
5
5
  import {
6
6
  fetchWithResilience,
@@ -293,9 +293,8 @@ class GraphClient {
293
293
  );
294
294
  }
295
295
  async performRequest(endpoint, accessToken, options) {
296
- const cloudEndpoints = getCloudEndpoints(this.secrets.cloudType);
297
296
  const apiVersion = options.apiVersion || "v1.0";
298
- const url = `${cloudEndpoints.graphApi}/${apiVersion}${endpoint}`;
297
+ const url = `${getGraphBaseUrl(this.secrets.cloudType)}/${apiVersion}${endpoint}`;
299
298
  logger.info(`[GRAPH CLIENT] Final URL being sent to Microsoft: ${url}`);
300
299
  const method = options.method || "GET";
301
300
  const signedBody = applyMessageSignoffToRequest(method, endpoint, options.body);
@@ -1361,7 +1361,7 @@ async function executeGraphTool(tool, config, graphClient, params, authManager,
1361
1361
  /%3D/g,
1362
1362
  "="
1363
1363
  );
1364
- path2 = path2.replace(`{${paramName}}`, encodedValue).replace(`:${paramName}`, encodedValue).replace(`{${camelCaseParamName}}`, encodedValue).replace(`:${camelCaseParamName}`, encodedValue);
1364
+ path2 = path2.replace(`{${paramName}}`, () => encodedValue).replace(`:${paramName}`, () => encodedValue).replace(`{${camelCaseParamName}}`, () => encodedValue).replace(`:${camelCaseParamName}`, () => encodedValue);
1365
1365
  break;
1366
1366
  }
1367
1367
  case "Query":
@@ -1401,7 +1401,7 @@ async function executeGraphTool(tool, config, graphClient, params, authManager,
1401
1401
  const encodedValue = encodeURIComponent(
1402
1402
  escapeIfStringLiteral(tool.path, [paramName, camelCaseParamName], paramValue)
1403
1403
  ).replace(/%3D/g, "=");
1404
- path2 = path2.replace(`{${paramName}}`, encodedValue).replace(`:${paramName}`, encodedValue).replace(`{${camelCaseParamName}}`, encodedValue).replace(`:${camelCaseParamName}`, encodedValue);
1404
+ path2 = path2.replace(`{${paramName}}`, () => encodedValue).replace(`:${paramName}`, () => encodedValue).replace(`{${camelCaseParamName}}`, () => encodedValue).replace(`:${camelCaseParamName}`, () => encodedValue);
1405
1405
  logger.info(`Path param fallback: replaced :${camelCaseParamName} with encoded value`);
1406
1406
  } else if (paramName.toLowerCase() === "accept" && config?.acceptType) {
1407
1407
  headers["Accept"] = `${paramValue}`;
@@ -1,6 +1,6 @@
1
1
  import { ProxyOAuthServerProvider } from "@modelcontextprotocol/sdk/server/auth/providers/proxyProvider.js";
2
2
  import logger from "./logger.js";
3
- import { getCloudEndpoints } from "./cloud-config.js";
3
+ import { getCloudEndpoints, getGraphBaseUrl } from "./cloud-config.js";
4
4
  class MicrosoftOAuthProvider extends ProxyOAuthServerProvider {
5
5
  constructor(authManager, secrets) {
6
6
  const tenantId = secrets.tenantId || "common";
@@ -14,7 +14,7 @@ class MicrosoftOAuthProvider extends ProxyOAuthServerProvider {
14
14
  },
15
15
  verifyAccessToken: async (token) => {
16
16
  try {
17
- const response = await fetch(`${cloudEndpoints.graphApi}/v1.0/me`, {
17
+ const response = await fetch(`${getGraphBaseUrl(secrets.cloudType)}/v1.0/me`, {
18
18
  headers: {
19
19
  Authorization: `Bearer ${token}`
20
20
  }
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.158.1",
4
+ "version": "0.159.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",