@softeria/ms-365-mcp-server 0.118.1 → 0.119.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
@@ -584,7 +584,7 @@ Environment variables:
584
584
  - `SILENT=true|1`: Disable console output
585
585
  - `MS365_MCP_REDACT_PII=true|1`: Scrub JWTs, Bearer headers, OAuth token fields, and email addresses from log messages before they are written (default: disabled). Useful when logs are shipped to a central store or shared host.
586
586
  - `MS365_MCP_CLIENT_ID`: Custom Azure app client ID (defaults to built-in app)
587
- - `MS365_MCP_TENANT_ID`: Custom tenant ID (defaults to 'common' for multi-tenant)
587
+ - `MS365_MCP_TENANT_ID`: Custom tenant ID (defaults to 'common' for multi-tenant). **Personal Microsoft accounts should set this to `consumers`** - as of June 2026, refresh tokens issued via the default 'common' authority are rejected at the first refresh, so sessions die roughly an hour after login
588
588
  - `MS365_MCP_OAUTH_TOKEN`: Pre-existing OAuth token for Microsoft Graph API (BYOT method)
589
589
  - `MS365_MCP_KEYVAULT_URL`: Azure Key Vault URL for secrets management (see Azure Key Vault section)
590
590
  - `MS365_MCP_TOKEN_CACHE_PATH`: Custom file path for MSAL token cache (see Token Storage below)
package/dist/auth.js CHANGED
@@ -243,6 +243,13 @@ function describeAuthError(error) {
243
243
  }
244
244
  return error.message;
245
245
  }
246
+ const MSA_HOME_TENANT_ID = "9188040d-6c67-4c5b-b112-36a304b66dad";
247
+ function consumersAuthorityHint(error, account, authority) {
248
+ if (error instanceof AuthError && error.errorCode === "invalid_grant" && account?.tenantId === MSA_HOME_TENANT_ID && (!authority || /\/common\/?$/i.test(authority))) {
249
+ return `This looks like a known issue (June 2026) where Microsoft rejects refresh tokens issued to personal accounts via the default 'common' authority. If this server is used only with personal accounts, set MS365_MCP_TENANT_ID=consumers and re-login with: --login`;
250
+ }
251
+ return null;
252
+ }
246
253
  class AuthManager {
247
254
  constructor(config, scopes = [], expectedAccount, storage) {
248
255
  logger.info(`And scopes are ${scopes.join(", ")}`, scopes);
@@ -459,8 +466,13 @@ class AuthManager {
459
466
  await this.saveTokenCache();
460
467
  return this.accessToken;
461
468
  } catch (error) {
462
- logger.error(`Silent token acquisition failed: ${describeAuthError(error)}`);
463
- throw new Error("Silent token acquisition failed");
469
+ const hint = consumersAuthorityHint(error, currentAccount, this.config.auth.authority);
470
+ logger.error(
471
+ `Silent token acquisition failed: ${describeAuthError(error)}${hint ? ` ${hint}` : ""}`
472
+ );
473
+ throw new Error(
474
+ hint ? `Silent token acquisition failed. ${hint}` : "Silent token acquisition failed"
475
+ );
464
476
  }
465
477
  }
466
478
  throw new Error("No valid token found");
@@ -774,9 +786,12 @@ class AuthManager {
774
786
  await this.saveTokenCache();
775
787
  return response.accessToken;
776
788
  } catch (error) {
777
- logger.error(`Silent token acquisition failed: ${describeAuthError(error)}`);
789
+ const hint = consumersAuthorityHint(error, targetAccount, this.config.auth.authority);
790
+ logger.error(
791
+ `Silent token acquisition failed: ${describeAuthError(error)}${hint ? ` ${hint}` : ""}`
792
+ );
778
793
  throw new Error(
779
- `Failed to acquire token for account '${targetAccount.username || targetAccount.name || "unknown"}'. The token may have expired. Please re-login with: --login`
794
+ `Failed to acquire token for account '${targetAccount.username || targetAccount.name || "unknown"}'. ` + (hint ?? "The token may have expired. Please re-login with: --login")
780
795
  );
781
796
  }
782
797
  }
@@ -787,6 +802,7 @@ export {
787
802
  buildScopeDiagnostics,
788
803
  buildScopesFromEndpoints,
789
804
  collapseScopeHierarchy,
805
+ consumersAuthorityHint,
790
806
  auth_default as default,
791
807
  describeAuthError,
792
808
  getEndpointRequiredScopes,
package/dist/cli.js CHANGED
@@ -50,6 +50,9 @@ program.name("ms-365-mcp-server").description("Microsoft 365 MCP Server").versio
50
50
  ).option(
51
51
  "--trust-proxy-auth",
52
52
  "In HTTP mode, skip the built-in Bearer-token check on /mcp and ignore any forwarded Authorization header. All callers share the locally cached MSAL identity (same path stdio mode uses). Use only when an upstream reverse proxy has already authenticated the caller."
53
+ ).option(
54
+ "--allow-unauthenticated-discovery",
55
+ "In HTTP mode, allow MCP discovery requests (initialize, tools/list, prompts/list, resources/list, ping) without a bearer token, so a gateway can enumerate the tool catalog before any user has authenticated. Non-discovery requests (e.g. tools/call) still require a token. Off by default."
53
56
  ).addOption(
54
57
  // DEPRECATED: kept only so existing deployments that set --base-url or
55
58
  // MS365_MCP_BASE_URL do not crash at startup. Use --public-url /
@@ -155,6 +158,9 @@ function parseArgs() {
155
158
  if (process.env.MS365_MCP_TRUST_PROXY_AUTH === "true" || process.env.MS365_MCP_TRUST_PROXY_AUTH === "1") {
156
159
  options.trustProxyAuth = true;
157
160
  }
161
+ if (process.env.MS365_MCP_ALLOW_UNAUTHENTICATED_DISCOVERY === "true" || process.env.MS365_MCP_ALLOW_UNAUTHENTICATED_DISCOVERY === "1") {
162
+ options.allowUnauthenticatedDiscovery = true;
163
+ }
158
164
  if (options.cloud) {
159
165
  process.env.MS365_MCP_CLOUD_TYPE = options.cloud;
160
166
  }
@@ -17,6 +17,22 @@ function isJwtExpired(token) {
17
17
  return false;
18
18
  }
19
19
  }
20
+ const DISCOVERY_METHODS = /* @__PURE__ */ new Set([
21
+ "initialize",
22
+ "notifications/initialized",
23
+ "tools/list",
24
+ "prompts/list",
25
+ "resources/list",
26
+ "ping"
27
+ ]);
28
+ function isDiscoveryRequest(req) {
29
+ if (req.method !== "POST" || !req.body) return false;
30
+ const body = req.body;
31
+ if (Array.isArray(body)) {
32
+ return body.every((item) => DISCOVERY_METHODS.has(item?.method));
33
+ }
34
+ return DISCOVERY_METHODS.has(body?.method);
35
+ }
20
36
  const microsoftBearerTokenAuthMiddleware = (opts = {}) => (req, res, next) => {
21
37
  if (opts.trustProxyAuth) {
22
38
  next();
@@ -24,6 +40,10 @@ const microsoftBearerTokenAuthMiddleware = (opts = {}) => (req, res, next) => {
24
40
  }
25
41
  const authHeader = req.headers.authorization;
26
42
  if (!authHeader || !authHeader.startsWith("Bearer ")) {
43
+ if (opts.allowUnauthenticatedDiscovery && isDiscoveryRequest(req)) {
44
+ next();
45
+ return;
46
+ }
27
47
  res.status(401).set(
28
48
  "WWW-Authenticate",
29
49
  buildWwwAuthenticate(req, "invalid_token", "Missing or malformed Authorization header")
package/dist/server.js CHANGED
@@ -469,7 +469,8 @@ class MicrosoftGraphServer {
469
469
  })
470
470
  );
471
471
  const mcpAuth = microsoftBearerTokenAuthMiddleware({
472
- trustProxyAuth: this.options.trustProxyAuth
472
+ trustProxyAuth: this.options.trustProxyAuth,
473
+ allowUnauthenticatedDiscovery: this.options.allowUnauthenticatedDiscovery
473
474
  });
474
475
  app.get(
475
476
  "/mcp",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softeria/ms-365-mcp-server",
3
- "version": "0.118.1",
3
+ "version": "0.119.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",