@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 +3 -0
- package/dist/auth.js +2 -3
- package/dist/cloud-config.js +21 -0
- package/dist/graph-client.js +2 -3
- package/dist/graph-tools.js +2 -2
- package/dist/oauth-provider.js +2 -2
- package/package.json +1 -1
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
|
|
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
|
}
|
package/dist/cloud-config.js
CHANGED
|
@@ -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
|
};
|
package/dist/graph-client.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import logger from "./logger.js";
|
|
2
2
|
import { encode as toonEncode } from "@toon-format/toon";
|
|
3
|
-
import {
|
|
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 = `${
|
|
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);
|
package/dist/graph-tools.js
CHANGED
|
@@ -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}`;
|
package/dist/oauth-provider.js
CHANGED
|
@@ -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(`${
|
|
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.
|
|
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",
|