@softeria/ms-365-mcp-server 0.158.2 → 0.159.1
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 +4 -1
- package/dist/auth.js +2 -3
- package/dist/cloud-config.js +21 -0
- package/dist/graph-client.js +2 -3
- package/dist/oauth-provider.js +2 -2
- package/dist/server.js +2 -1
- 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)
|
|
@@ -180,7 +182,7 @@ npx @softeria/ms-365-mcp-server \
|
|
|
180
182
|
--allowed-scopes 'User.Read Files.Read Notes.Read Tasks.Read Sites.Selected'
|
|
181
183
|
```
|
|
182
184
|
|
|
183
|
-
In HTTP mode, OAuth discovery advertises the effective filtered permissions so clients request the same consent surface. On-Behalf-Of mode (`--obo`) still advertises
|
|
185
|
+
In HTTP mode, OAuth discovery advertises the effective filtered permissions so clients request the same consent surface. On-Behalf-Of mode (`--obo`) still advertises `<clientId>/access_as_user` for protected-resource metadata and requests it on `/authorize`; `--allowed-scopes` does not override OBO, it only narrows the tool surface.
|
|
184
186
|
|
|
185
187
|
### Restricting user profile fields
|
|
186
188
|
|
|
@@ -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/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/dist/server.js
CHANGED
|
@@ -513,7 +513,8 @@ class MicrosoftGraphServer {
|
|
|
513
513
|
microsoftAuthUrl.searchParams.set("client_id", clientId);
|
|
514
514
|
const explicitAllowedScopes = parseAllowedScopes(this.options.allowedScopes);
|
|
515
515
|
const clientScope = microsoftAuthUrl.searchParams.get("scope");
|
|
516
|
-
const
|
|
516
|
+
const clientScopes = (clientScope ?? "").split(/\s+/).filter(Boolean);
|
|
517
|
+
const baseScopes = this.options.obo ? clientScopes.some((scope) => scope.endsWith("/access_as_user")) ? clientScopes : [`${clientId}/access_as_user`, ...clientScopes] : explicitAllowedScopes !== void 0 ? resolveAuthScopes(this.options) : clientScope ? clientScopes : buildScopesFromEndpoints(
|
|
517
518
|
this.options.orgMode,
|
|
518
519
|
this.options.enabledTools,
|
|
519
520
|
this.options.readOnly
|
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.1",
|
|
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",
|