@softeria/ms-365-mcp-server 0.28.3 → 0.30.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
@@ -10,7 +10,10 @@ MS365_MCP_CLIENT_SECRET=your-azure-ad-app-client-secret-here
10
10
  # Tenant ID - use "common" for multi-tenant or your specific tenant ID
11
11
  MS365_MCP_TENANT_ID=common
12
12
 
13
- # Instructions:
13
+ # Cloud environment: global (default) or china (21Vianet)
14
+ # MS365_MCP_CLOUD_TYPE=global
15
+
16
+ # Instructions for Global Cloud:
14
17
  # 1. Go to https://portal.azure.com
15
18
  # 2. Navigate to Azure Active Directory → App registrations → New registration
16
19
  # 3. Set name: "MS365 MCP Server"
@@ -23,6 +26,12 @@ MS365_MCP_TENANT_ID=common
23
26
  # 7. Replace the values above with your actual credentials
24
27
  # 8. Rename this file to .env
25
28
 
29
+ # Instructions for China Cloud (21Vianet):
30
+ # 1. Go to https://portal.azure.cn
31
+ # 2. Navigate to Azure Active Directory → App registrations → New registration
32
+ # 3. Follow the same steps as above
33
+ # 4. Set MS365_MCP_CLOUD_TYPE=china
34
+
26
35
  # -------------------------------------------------------------------
27
36
  # Azure Key Vault Integration (Optional)
28
37
  # -------------------------------------------------------------------
@@ -35,6 +44,7 @@ MS365_MCP_TENANT_ID=common
35
44
  # - ms365-mcp-client-id (required)
36
45
  # - ms365-mcp-tenant-id (optional, defaults to "common")
37
46
  # - ms365-mcp-client-secret (optional)
47
+ # - ms365-mcp-cloud-type (optional, defaults to "global")
38
48
  #
39
49
  # Authentication uses DefaultAzureCredential, which supports:
40
50
  # - Managed Identity (recommended for Azure Container Apps)
package/README.md CHANGED
@@ -7,6 +7,15 @@ Microsoft 365 MCP Server
7
7
  A Model Context Protocol (MCP) server for interacting with Microsoft 365 and Microsoft Office services through the Graph
8
8
  API.
9
9
 
10
+ ## Supported Clouds
11
+
12
+ This server supports multiple Microsoft cloud environments:
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 |
18
+
10
19
  ## Prerequisites
11
20
 
12
21
  - Node.js >= 20 (recommended)
@@ -188,9 +197,9 @@ Test login in Claude Desktop:
188
197
 
189
198
  ### Claude Desktop
190
199
 
191
- To add this MCP server to Claude Desktop:
200
+ To add this MCP server to Claude Desktop, edit the config file under Settings > Developer.
192
201
 
193
- Edit the config file under Settings > Developer:
202
+ #### Personal Account (MSA)
194
203
 
195
204
  ```json
196
205
  {
@@ -203,12 +212,60 @@ Edit the config file under Settings > Developer:
203
212
  }
204
213
  ```
205
214
 
215
+ #### Work/School Account (Global)
216
+
217
+ ```json
218
+ {
219
+ "mcpServers": {
220
+ "ms365": {
221
+ "command": "npx",
222
+ "args": ["-y", "@softeria/ms-365-mcp-server", "--org-mode"]
223
+ }
224
+ }
225
+ }
226
+ ```
227
+
228
+ #### Work/School Account (China 21Vianet)
229
+
230
+ ```json
231
+ {
232
+ "mcpServers": {
233
+ "ms365-china": {
234
+ "command": "npx",
235
+ "args": ["-y", "@softeria/ms-365-mcp-server", "--org-mode", "--cloud", "china"]
236
+ }
237
+ }
238
+ }
239
+ ```
240
+
206
241
  ### Claude Code CLI
207
242
 
243
+ #### Personal Account (MSA)
244
+
208
245
  ```bash
209
246
  claude mcp add ms365 -- npx -y @softeria/ms-365-mcp-server
210
247
  ```
211
248
 
249
+ #### Work/School Account (Global)
250
+
251
+ ```bash
252
+ # macOS/Linux
253
+ claude mcp add ms365 -- npx -y @softeria/ms-365-mcp-server --org-mode
254
+
255
+ # Windows (use cmd /c wrapper)
256
+ claude mcp add ms365 -s user -- cmd /c "npx -y @softeria/ms-365-mcp-server --org-mode"
257
+ ```
258
+
259
+ #### Work/School Account (China 21Vianet)
260
+
261
+ ```bash
262
+ # macOS/Linux
263
+ claude mcp add ms365-china -- npx -y @softeria/ms-365-mcp-server --org-mode --cloud china
264
+
265
+ # Windows (use cmd /c wrapper)
266
+ claude mcp add ms365-china -s user -- cmd /c "npx -y @softeria/ms-365-mcp-server --org-mode --cloud china"
267
+ ```
268
+
212
269
  For other interfaces that support MCPs, please refer to their respective documentation for the correct
213
270
  integration method.
214
271
 
@@ -364,6 +421,7 @@ The following options can be used when running ms-365-mcp-server directly from t
364
421
  --org-mode Enable organization/work mode from start (includes Teams, SharePoint, etc.)
365
422
  --work-mode Alias for --org-mode
366
423
  --force-work-scopes Backwards compatibility alias for --org-mode (deprecated)
424
+ --cloud <type> Microsoft cloud environment: global (default) or china (21Vianet)
367
425
  ```
368
426
 
369
427
  ### Server Options
@@ -390,6 +448,7 @@ Environment variables:
390
448
  - `MS365_MCP_ORG_MODE=true|1`: Enable organization/work mode (alternative to --org-mode flag)
391
449
  - `MS365_MCP_FORCE_WORK_SCOPES=true|1`: Backwards compatibility for MS365_MCP_ORG_MODE
392
450
  - `MS365_MCP_OUTPUT_FORMAT=toon`: Enable TOON output format (alternative to --toon flag)
451
+ - `MS365_MCP_CLOUD_TYPE=global|china`: Microsoft cloud environment (alternative to --cloud flag)
393
452
  - `LOG_LEVEL`: Set logging level (default: 'info')
394
453
  - `SILENT=true|1`: Disable console output
395
454
  - `MS365_MCP_CLIENT_ID`: Custom Azure app client ID (defaults to built-in app)
package/dist/auth.js CHANGED
@@ -4,6 +4,7 @@ import fs, { existsSync, 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
8
  let keytar = null;
8
9
  async function getKeytar() {
9
10
  if (keytar === void 0) {
@@ -36,10 +37,11 @@ const FALLBACK_DIR = path.dirname(fileURLToPath(import.meta.url));
36
37
  const FALLBACK_PATH = path.join(FALLBACK_DIR, "..", ".token-cache.json");
37
38
  const SELECTED_ACCOUNT_PATH = path.join(FALLBACK_DIR, "..", ".selected-account.json");
38
39
  function createMsalConfig(secrets) {
40
+ const cloudEndpoints = getCloudEndpoints(secrets.cloudType);
39
41
  return {
40
42
  auth: {
41
- clientId: secrets.clientId || "084a3e9f-a9f4-43f7-89f9-d229cf97853e",
42
- authority: `https://login.microsoftonline.com/${secrets.tenantId || "common"}`
43
+ clientId: secrets.clientId || getDefaultClientId(secrets.cloudType),
44
+ authority: `${cloudEndpoints.authority}/${secrets.tenantId || "common"}`
43
45
  }
44
46
  };
45
47
  }
@@ -299,7 +301,9 @@ class AuthManager {
299
301
  }
300
302
  logger.info("Token retrieved successfully, testing Graph API access...");
301
303
  try {
302
- const response = await fetch("https://graph.microsoft.com/v1.0/me", {
304
+ const secrets = await getSecrets();
305
+ const cloudEndpoints = getCloudEndpoints(secrets.cloudType);
306
+ const response = await fetch(`${cloudEndpoints.graphApi}/v1.0/me`, {
303
307
  headers: {
304
308
  Authorization: `Bearer ${token}`
305
309
  }
package/dist/cli.js CHANGED
@@ -23,7 +23,7 @@ program.name("ms-365-mcp-server").description("Microsoft 365 MCP Server").versio
23
23
  ).option("--list-presets", "List all available presets and exit").option(
24
24
  "--org-mode",
25
25
  "Enable organization/work mode from start (includes Teams, SharePoint, etc.)"
26
- ).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)");
26
+ ).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)");
27
27
  function parseArgs() {
28
28
  program.parse();
29
29
  const options = program.opts();
@@ -65,6 +65,9 @@ function parseArgs() {
65
65
  if (process.env.MS365_MCP_OUTPUT_FORMAT === "toon") {
66
66
  options.toon = true;
67
67
  }
68
+ if (options.cloud) {
69
+ process.env.MS365_MCP_CLOUD_TYPE = options.cloud;
70
+ }
68
71
  return options;
69
72
  }
70
73
  export {
@@ -0,0 +1,49 @@
1
+ const CLOUD_ENDPOINTS = {
2
+ global: {
3
+ authority: "https://login.microsoftonline.com",
4
+ graphApi: "https://graph.microsoft.com",
5
+ portal: "https://portal.azure.com"
6
+ },
7
+ china: {
8
+ authority: "https://login.chinacloudapi.cn",
9
+ graphApi: "https://microsoftgraph.chinacloudapi.cn",
10
+ portal: "https://portal.azure.cn"
11
+ }
12
+ };
13
+ const DEFAULT_CLIENT_IDS = {
14
+ global: "084a3e9f-a9f4-43f7-89f9-d229cf97853e",
15
+ china: "f3e61a6e-bc26-4281-8588-2c7359a02141"
16
+ };
17
+ function getDefaultClientId(cloudType = "global") {
18
+ return DEFAULT_CLIENT_IDS[cloudType];
19
+ }
20
+ function getCloudEndpoints(cloudType = "global") {
21
+ const endpoints = CLOUD_ENDPOINTS[cloudType];
22
+ if (!endpoints) {
23
+ throw new Error(
24
+ `Unknown cloud type: ${cloudType}. Valid values: ${Object.keys(CLOUD_ENDPOINTS).join(", ")}`
25
+ );
26
+ }
27
+ return endpoints;
28
+ }
29
+ function isValidCloudType(value) {
30
+ return value in CLOUD_ENDPOINTS;
31
+ }
32
+ function parseCloudType(value) {
33
+ if (!value) return "global";
34
+ const normalized = value.toLowerCase().trim();
35
+ if (!isValidCloudType(normalized)) {
36
+ throw new Error(
37
+ `Invalid cloud type: ${value}. Valid values: ${Object.keys(CLOUD_ENDPOINTS).join(", ")}`
38
+ );
39
+ }
40
+ return normalized;
41
+ }
42
+ export {
43
+ CLOUD_ENDPOINTS,
44
+ DEFAULT_CLIENT_IDS,
45
+ getCloudEndpoints,
46
+ getDefaultClientId,
47
+ isValidCloudType,
48
+ parseCloudType
49
+ };
@@ -662,8 +662,8 @@ const microsoft_graph_driveItem = z.object({
662
662
  webUrl: z.string().describe(
663
663
  "URL that either displays the resource in the browser (for Office file formats), or is a direct link to the file (for other formats). Read-only."
664
664
  ).nullish(),
665
- createdByUser: microsoft_graph_user.describe("[Note: Simplified from 133 properties to 25 most common ones]").optional(),
666
- lastModifiedByUser: microsoft_graph_user.describe("[Note: Simplified from 133 properties to 25 most common ones]").optional(),
665
+ createdByUser: microsoft_graph_user.describe("[Note: Simplified from 135 properties to 25 most common ones]").optional(),
666
+ lastModifiedByUser: microsoft_graph_user.describe("[Note: Simplified from 135 properties to 25 most common ones]").optional(),
667
667
  audio: microsoft_graph_audio.optional(),
668
668
  bundle: microsoft_graph_bundle.optional(),
669
669
  cTag: z.string().describe(
@@ -1759,9 +1759,10 @@ const microsoft_graph_listItem = z.object({
1759
1759
  webUrl: z.string().describe(
1760
1760
  "URL that either displays the resource in the browser (for Office file formats), or is a direct link to the file (for other formats). Read-only."
1761
1761
  ).nullish(),
1762
- createdByUser: microsoft_graph_user.describe("[Note: Simplified from 133 properties to 25 most common ones]").optional(),
1763
- lastModifiedByUser: microsoft_graph_user.describe("[Note: Simplified from 133 properties to 25 most common ones]").optional(),
1762
+ createdByUser: microsoft_graph_user.describe("[Note: Simplified from 135 properties to 25 most common ones]").optional(),
1763
+ lastModifiedByUser: microsoft_graph_user.describe("[Note: Simplified from 135 properties to 25 most common ones]").optional(),
1764
1764
  contentType: microsoft_graph_contentTypeInfo.optional(),
1765
+ deleted: microsoft_graph_deleted.optional(),
1765
1766
  sharepointIds: microsoft_graph_sharepointIds.optional(),
1766
1767
  analytics: microsoft_graph_itemAnalytics.optional(),
1767
1768
  documentSetVersions: z.array(microsoft_graph_documentSetVersion).describe("Version information for a document set version created by a user.").optional(),
@@ -1879,8 +1880,8 @@ const microsoft_graph_list = z.lazy(
1879
1880
  webUrl: z.string().describe(
1880
1881
  "URL that either displays the resource in the browser (for Office file formats), or is a direct link to the file (for other formats). Read-only."
1881
1882
  ).nullish(),
1882
- createdByUser: microsoft_graph_user.describe("[Note: Simplified from 133 properties to 25 most common ones]").optional(),
1883
- lastModifiedByUser: microsoft_graph_user.describe("[Note: Simplified from 133 properties to 25 most common ones]").optional(),
1883
+ createdByUser: microsoft_graph_user.describe("[Note: Simplified from 135 properties to 25 most common ones]").optional(),
1884
+ lastModifiedByUser: microsoft_graph_user.describe("[Note: Simplified from 135 properties to 25 most common ones]").optional(),
1884
1885
  displayName: z.string().describe("The displayable title of the list.").nullish(),
1885
1886
  list: microsoft_graph_listInfo.optional(),
1886
1887
  sharepointIds: microsoft_graph_sharepointIds.optional(),
@@ -1911,8 +1912,8 @@ const microsoft_graph_drive = z.lazy(
1911
1912
  webUrl: z.string().describe(
1912
1913
  "URL that either displays the resource in the browser (for Office file formats), or is a direct link to the file (for other formats). Read-only."
1913
1914
  ).nullish(),
1914
- createdByUser: microsoft_graph_user.describe("[Note: Simplified from 133 properties to 25 most common ones]").optional(),
1915
- lastModifiedByUser: microsoft_graph_user.describe("[Note: Simplified from 133 properties to 25 most common ones]").optional(),
1915
+ createdByUser: microsoft_graph_user.describe("[Note: Simplified from 135 properties to 25 most common ones]").optional(),
1916
+ lastModifiedByUser: microsoft_graph_user.describe("[Note: Simplified from 135 properties to 25 most common ones]").optional(),
1916
1917
  driveType: z.string().describe(
1917
1918
  "Describes the type of drive represented by this resource. OneDrive personal drives return personal. OneDrive for Business returns business. SharePoint document libraries return documentLibrary. Read-only."
1918
1919
  ).nullish(),
@@ -3166,8 +3167,8 @@ const microsoft_graph_baseItem = z.object({
3166
3167
  webUrl: z.string().describe(
3167
3168
  "URL that either displays the resource in the browser (for Office file formats), or is a direct link to the file (for other formats). Read-only."
3168
3169
  ).nullish(),
3169
- createdByUser: microsoft_graph_user.describe("[Note: Simplified from 133 properties to 25 most common ones]").optional(),
3170
- lastModifiedByUser: microsoft_graph_user.describe("[Note: Simplified from 133 properties to 25 most common ones]").optional()
3170
+ createdByUser: microsoft_graph_user.describe("[Note: Simplified from 135 properties to 25 most common ones]").optional(),
3171
+ lastModifiedByUser: microsoft_graph_user.describe("[Note: Simplified from 135 properties to 25 most common ones]").optional()
3171
3172
  }).strict();
3172
3173
  const microsoft_graph_site = z.object({
3173
3174
  id: z.string().describe("The unique identifier for an entity. Read-only.").optional(),
@@ -3188,8 +3189,8 @@ const microsoft_graph_site = z.object({
3188
3189
  webUrl: z.string().describe(
3189
3190
  "URL that either displays the resource in the browser (for Office file formats), or is a direct link to the file (for other formats). Read-only."
3190
3191
  ).nullish(),
3191
- createdByUser: microsoft_graph_user.describe("[Note: Simplified from 133 properties to 25 most common ones]").optional(),
3192
- lastModifiedByUser: microsoft_graph_user.describe("[Note: Simplified from 133 properties to 25 most common ones]").optional(),
3192
+ createdByUser: microsoft_graph_user.describe("[Note: Simplified from 135 properties to 25 most common ones]").optional(),
3193
+ lastModifiedByUser: microsoft_graph_user.describe("[Note: Simplified from 135 properties to 25 most common ones]").optional(),
3193
3194
  isPersonalSite: z.boolean().describe("Identifies whether the site is personal or not. Read-only.").nullish(),
3194
3195
  root: microsoft_graph_root.optional(),
3195
3196
  sharepointIds: microsoft_graph_sharepointIds.optional(),
@@ -1,6 +1,7 @@
1
1
  import logger from "./logger.js";
2
2
  import { refreshAccessToken } from "./lib/microsoft-auth.js";
3
3
  import { encode as toonEncode } from "@toon-format/toon";
4
+ import { getCloudEndpoints } from "./cloud-config.js";
4
5
  class GraphClient {
5
6
  constructor(authManager, secrets, outputFormat = "json") {
6
7
  this.accessToken = null;
@@ -81,14 +82,21 @@ class GraphClient {
81
82
  } else {
82
83
  logger.info("GraphClient: Refreshing token with public client");
83
84
  }
84
- const response = await refreshAccessToken(refreshToken, clientId, clientSecret, tenantId);
85
+ const response = await refreshAccessToken(
86
+ refreshToken,
87
+ clientId,
88
+ clientSecret,
89
+ tenantId,
90
+ this.secrets.cloudType
91
+ );
85
92
  this.accessToken = response.access_token;
86
93
  if (response.refresh_token) {
87
94
  this.refreshToken = response.refresh_token;
88
95
  }
89
96
  }
90
97
  async performRequest(endpoint, accessToken, options) {
91
- const url = `https://graph.microsoft.com/v1.0${endpoint}`;
98
+ const cloudEndpoints = getCloudEndpoints(this.secrets.cloudType);
99
+ const url = `${cloudEndpoints.graphApi}/v1.0${endpoint}`;
92
100
  const headers = {
93
101
  Authorization: `Bearer ${accessToken}`,
94
102
  "Content-Type": "application/json",
@@ -251,7 +251,10 @@ function registerGraphTools(server, graphClient, readOnly = false, enabledToolsP
251
251
  paramSchema,
252
252
  {
253
253
  title: tool.alias,
254
- readOnlyHint: tool.method.toUpperCase() === "GET"
254
+ readOnlyHint: tool.method.toUpperCase() === "GET",
255
+ destructiveHint: ["POST", "PATCH", "DELETE"].includes(tool.method.toUpperCase()),
256
+ openWorldHint: true
257
+ // All tools call Microsoft Graph API
255
258
  },
256
259
  async (params) => executeGraphTool(tool, endpointConfig, graphClient, params)
257
260
  );
@@ -295,7 +298,9 @@ function registerDiscoveryTools(server, graphClient, readOnly = false, orgMode =
295
298
  },
296
299
  {
297
300
  title: "search-tools",
298
- readOnlyHint: true
301
+ readOnlyHint: true,
302
+ openWorldHint: true
303
+ // Searches Microsoft Graph API tools
299
304
  },
300
305
  async ({ query, category, limit = 20 }) => {
301
306
  const maxLimit = Math.min(limit, 50);
@@ -348,7 +353,11 @@ function registerDiscoveryTools(server, graphClient, readOnly = false, orgMode =
348
353
  },
349
354
  {
350
355
  title: "execute-tool",
351
- readOnlyHint: false
356
+ readOnlyHint: false,
357
+ destructiveHint: true,
358
+ // Can execute any tool, including write operations
359
+ openWorldHint: true
360
+ // Executes against Microsoft Graph API
352
361
  },
353
362
  async ({ tool_name, parameters = {} }) => {
354
363
  const toolData = toolsRegistry.get(tool_name);
@@ -1,4 +1,5 @@
1
1
  import logger from "../logger.js";
2
+ import { getCloudEndpoints } from "../cloud-config.js";
2
3
  const microsoftBearerTokenAuthMiddleware = (req, res, next) => {
3
4
  const authHeader = req.headers.authorization;
4
5
  if (!authHeader || !authHeader.startsWith("Bearer ")) {
@@ -13,7 +14,8 @@ const microsoftBearerTokenAuthMiddleware = (req, res, next) => {
13
14
  };
14
15
  next();
15
16
  };
16
- async function exchangeCodeForToken(code, redirectUri, clientId, clientSecret, tenantId = "common", codeVerifier) {
17
+ async function exchangeCodeForToken(code, redirectUri, clientId, clientSecret, tenantId = "common", codeVerifier, cloudType = "global") {
18
+ const cloudEndpoints = getCloudEndpoints(cloudType);
17
19
  const params = new URLSearchParams({
18
20
  grant_type: "authorization_code",
19
21
  code,
@@ -26,7 +28,7 @@ async function exchangeCodeForToken(code, redirectUri, clientId, clientSecret, t
26
28
  if (codeVerifier) {
27
29
  params.append("code_verifier", codeVerifier);
28
30
  }
29
- const response = await fetch(`https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`, {
31
+ const response = await fetch(`${cloudEndpoints.authority}/${tenantId}/oauth2/v2.0/token`, {
30
32
  method: "POST",
31
33
  headers: {
32
34
  "Content-Type": "application/x-www-form-urlencoded"
@@ -40,7 +42,8 @@ async function exchangeCodeForToken(code, redirectUri, clientId, clientSecret, t
40
42
  }
41
43
  return response.json();
42
44
  }
43
- async function refreshAccessToken(refreshToken, clientId, clientSecret, tenantId = "common") {
45
+ async function refreshAccessToken(refreshToken, clientId, clientSecret, tenantId = "common", cloudType = "global") {
46
+ const cloudEndpoints = getCloudEndpoints(cloudType);
44
47
  const params = new URLSearchParams({
45
48
  grant_type: "refresh_token",
46
49
  refresh_token: refreshToken,
@@ -49,7 +52,7 @@ async function refreshAccessToken(refreshToken, clientId, clientSecret, tenantId
49
52
  if (clientSecret) {
50
53
  params.append("client_secret", clientSecret);
51
54
  }
52
- const response = await fetch(`https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`, {
55
+ const response = await fetch(`${cloudEndpoints.authority}/${tenantId}/oauth2/v2.0/token`, {
53
56
  method: "POST",
54
57
  headers: {
55
58
  "Content-Type": "application/x-www-form-urlencoded"
@@ -1,18 +1,20 @@
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
4
  class MicrosoftOAuthProvider extends ProxyOAuthServerProvider {
4
5
  constructor(authManager, secrets) {
5
6
  const tenantId = secrets.tenantId || "common";
6
7
  const clientId = secrets.clientId;
8
+ const cloudEndpoints = getCloudEndpoints(secrets.cloudType);
7
9
  super({
8
10
  endpoints: {
9
- authorizationUrl: `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/authorize`,
10
- tokenUrl: `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`,
11
- revocationUrl: `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/logout`
11
+ authorizationUrl: `${cloudEndpoints.authority}/${tenantId}/oauth2/v2.0/authorize`,
12
+ tokenUrl: `${cloudEndpoints.authority}/${tenantId}/oauth2/v2.0/token`,
13
+ revocationUrl: `${cloudEndpoints.authority}/${tenantId}/oauth2/v2.0/logout`
12
14
  },
13
15
  verifyAccessToken: async (token) => {
14
16
  try {
15
- const response = await fetch("https://graph.microsoft.com/v1.0/me", {
17
+ const response = await fetch(`${cloudEndpoints.graphApi}/v1.0/me`, {
16
18
  headers: {
17
19
  Authorization: `Bearer ${token}`
18
20
  }
package/dist/secrets.js CHANGED
@@ -1,10 +1,12 @@
1
1
  import logger from "./logger.js";
2
+ import { parseCloudType } from "./cloud-config.js";
2
3
  class EnvironmentSecretsProvider {
3
4
  async getSecrets() {
4
5
  return {
5
6
  clientId: process.env.MS365_MCP_CLIENT_ID || "",
6
7
  tenantId: process.env.MS365_MCP_TENANT_ID || "common",
7
- clientSecret: process.env.MS365_MCP_CLIENT_SECRET
8
+ clientSecret: process.env.MS365_MCP_CLIENT_SECRET,
9
+ cloudType: parseCloudType(process.env.MS365_MCP_CLOUD_TYPE)
8
10
  };
9
11
  }
10
12
  }
@@ -18,11 +20,14 @@ class KeyVaultSecretsProvider {
18
20
  const credential = new DefaultAzureCredential();
19
21
  const client = new SecretClient(this.vaultUrl, credential);
20
22
  logger.info(`Fetching secrets from Key Vault: ${this.vaultUrl}`);
21
- const [clientIdSecret, tenantIdSecret, clientSecretResult] = await Promise.all([
22
- client.getSecret("ms365-mcp-client-id"),
23
- client.getSecret("ms365-mcp-tenant-id").catch(() => null),
24
- client.getSecret("ms365-mcp-client-secret").catch(() => null)
25
- ]);
23
+ const [clientIdSecret, tenantIdSecret, clientSecretResult, cloudTypeResult] = await Promise.all(
24
+ [
25
+ client.getSecret("ms365-mcp-client-id"),
26
+ client.getSecret("ms365-mcp-tenant-id").catch(() => null),
27
+ client.getSecret("ms365-mcp-client-secret").catch(() => null),
28
+ client.getSecret("ms365-mcp-cloud-type").catch(() => null)
29
+ ]
30
+ );
26
31
  if (!clientIdSecret.value) {
27
32
  throw new Error("Required secret ms365-mcp-client-id not found in Key Vault");
28
33
  }
@@ -30,7 +35,8 @@ class KeyVaultSecretsProvider {
30
35
  return {
31
36
  clientId: clientIdSecret.value,
32
37
  tenantId: tenantIdSecret?.value || "common",
33
- clientSecret: clientSecretResult?.value
38
+ clientSecret: clientSecretResult?.value,
39
+ cloudType: parseCloudType(cloudTypeResult?.value)
34
40
  };
35
41
  }
36
42
  }
package/dist/server.js CHANGED
@@ -15,6 +15,7 @@ import {
15
15
  refreshAccessToken
16
16
  } from "./lib/microsoft-auth.js";
17
17
  import { getSecrets } from "./secrets.js";
18
+ import { getCloudEndpoints } from "./cloud-config.js";
18
19
  function parseHttpOption(httpOption) {
19
20
  if (typeof httpOption === "boolean") {
20
21
  return { host: void 0, port: 3e3 };
@@ -134,8 +135,9 @@ class MicrosoftGraphServer {
134
135
  const url = new URL(req.url, `${req.protocol}://${req.get("host")}`);
135
136
  const tenantId = this.secrets?.tenantId || "common";
136
137
  const clientId = this.secrets.clientId;
138
+ const cloudEndpoints = getCloudEndpoints(this.secrets.cloudType);
137
139
  const microsoftAuthUrl = new URL(
138
- `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/authorize`
140
+ `${cloudEndpoints.authority}/${tenantId}/oauth2/v2.0/authorize`
139
141
  );
140
142
  const allowedParams = [
141
143
  "response_type",
@@ -201,7 +203,8 @@ class MicrosoftGraphServer {
201
203
  clientId,
202
204
  clientSecret,
203
205
  tenantId,
204
- body.code_verifier
206
+ body.code_verifier,
207
+ this.secrets.cloudType
205
208
  );
206
209
  res.json(result);
207
210
  } else if (body.grant_type === "refresh_token") {
@@ -217,7 +220,8 @@ class MicrosoftGraphServer {
217
220
  body.refresh_token,
218
221
  clientId,
219
222
  clientSecret,
220
- tenantId
223
+ tenantId,
224
+ this.secrets.cloudType
221
225
  );
222
226
  res.json(result);
223
227
  } else {
@@ -1,5 +1,5 @@
1
- 2025-12-23 08:30:22 INFO: Using environment variables for secrets
2
- 2025-12-23 08:30:22 INFO: Using environment variables for secrets
3
- 2025-12-23 08:30:22 INFO: Using environment variables for secrets
4
- 2025-12-23 08:30:22 INFO: Using environment variables for secrets
5
- 2025-12-23 08:30:22 INFO: Using environment variables for secrets
1
+ 2026-01-12 13:28:46 INFO: Using environment variables for secrets
2
+ 2026-01-12 13:28:46 INFO: Using environment variables for secrets
3
+ 2026-01-12 13:28:46 INFO: Using environment variables for secrets
4
+ 2026-01-12 13:28:46 INFO: Using environment variables for secrets
5
+ 2026-01-12 13:28:46 INFO: Using environment variables for secrets
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softeria/ms-365-mcp-server",
3
- "version": "0.28.3",
3
+ "version": "0.30.0",
4
4
  "description": " A Model Context Protocol (MCP) server for interacting with Microsoft 365 and Office services through the Graph API",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",