@softeria/ms-365-mcp-server 0.128.0 → 0.128.2

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/dist/auth.js CHANGED
@@ -31,6 +31,36 @@ function createMsalConfig(secrets) {
31
31
  }
32
32
  };
33
33
  }
34
+ function buildDiskCoherencyCachePlugin(storage) {
35
+ return {
36
+ beforeCacheAccess: async (context) => {
37
+ try {
38
+ const cacheRaw = await storage.load("token-cache");
39
+ if (cacheRaw) {
40
+ context.tokenCache.deserialize(unwrapCache(cacheRaw).data);
41
+ }
42
+ } catch (error) {
43
+ logger.error(`Error reloading token cache: ${error.message}`);
44
+ if (storage.failClosed) {
45
+ throw error;
46
+ }
47
+ }
48
+ },
49
+ afterCacheAccess: async (context) => {
50
+ if (!context.cacheHasChanged) {
51
+ return;
52
+ }
53
+ try {
54
+ await storage.save("token-cache", wrapCache(context.tokenCache.serialize()));
55
+ } catch (error) {
56
+ logger.error(`Error saving token cache: ${error.message}`);
57
+ if (storage.failClosed) {
58
+ throw error;
59
+ }
60
+ }
61
+ }
62
+ };
63
+ }
34
64
  const SCOPE_HIERARCHY = {
35
65
  "Mail.ReadWrite": ["Mail.Read"],
36
66
  "Calendars.ReadWrite": ["Calendars.Read"],
@@ -312,8 +342,15 @@ function consumersAuthorityHint(error, account, authority) {
312
342
  class AuthManager {
313
343
  constructor(config, scopes = [], expectedAccount, storage) {
314
344
  logger.info(`And scopes are ${scopes.join(", ")}`, scopes);
315
- this.config = config;
316
345
  this.scopes = scopes;
346
+ this.storage = storage ?? new DefaultTokenCacheStorage();
347
+ this.config = {
348
+ ...config,
349
+ cache: {
350
+ ...config.cache,
351
+ cachePlugin: buildDiskCoherencyCachePlugin(this.storage)
352
+ }
353
+ };
317
354
  this.msalApp = new PublicClientApplication(this.config);
318
355
  this.accessToken = null;
319
356
  this.tokenExpiry = null;
@@ -323,7 +360,6 @@ class AuthManager {
323
360
  this.expectedHomeAccountId = this.normalizeExpectedHomeAccountId(
324
361
  expectedAccount?.expectedHomeAccountId
325
362
  );
326
- this.storage = storage ?? new DefaultTokenCacheStorage();
327
363
  const oauthTokenFromEnv = process.env.MS365_MCP_OAUTH_TOKEN;
328
364
  this.oauthToken = oauthTokenFromEnv ?? null;
329
365
  this.isOAuthMode = oauthTokenFromEnv != null;
@@ -367,17 +403,6 @@ class AuthManager {
367
403
  }
368
404
  }
369
405
  }
370
- async saveTokenCache() {
371
- try {
372
- const stamped = wrapCache(this.msalApp.getTokenCache().serialize());
373
- await this.storage.save("token-cache", stamped);
374
- } catch (error) {
375
- logger.error(`Error saving token cache: ${error.message}`);
376
- if (this.storage.failClosed) {
377
- throw error;
378
- }
379
- }
380
- }
381
406
  async saveSelectedAccount() {
382
407
  try {
383
408
  const stamped = wrapCache(JSON.stringify({ accountId: this.selectedAccountId }));
@@ -491,7 +516,10 @@ class AuthManager {
491
516
  try {
492
517
  await this.msalApp.getTokenCache().removeAccount(account);
493
518
  } catch (error) {
494
- logger.warn(`Failed to remove unexpected account from cache: ${error.message}`);
519
+ logger.error(`Failed to remove unexpected account from cache: ${error.message}`);
520
+ throw new Error(
521
+ `Authenticated Microsoft account '${this.describeAccount(account)}' does not match expected Microsoft account '${this.expectedAccountLabel()}', and it could not be removed from the token cache (${error.message}). Its tokens may remain persisted - run --logout to clear the cache, then re-login.`
522
+ );
495
523
  }
496
524
  throw new Error(
497
525
  `Authenticated Microsoft account '${this.describeAccount(account)}' does not match expected Microsoft account '${this.expectedAccountLabel()}'. Login was not persisted.`
@@ -522,7 +550,6 @@ class AuthManager {
522
550
  const response = await this.msalApp.acquireTokenSilent(silentRequest);
523
551
  this.accessToken = response.accessToken;
524
552
  this.tokenExpiry = response.expiresOn ? new Date(response.expiresOn).getTime() : null;
525
- await this.saveTokenCache();
526
553
  return this.accessToken;
527
554
  } catch (error) {
528
555
  const hint = consumersAuthorityHint(error, currentAccount, this.config.auth.authority);
@@ -584,7 +611,6 @@ class AuthManager {
584
611
  await this.saveSelectedAccount();
585
612
  logger.info(`Auto-selected new account: ${response.account.username}`);
586
613
  }
587
- await this.saveTokenCache();
588
614
  return this.accessToken;
589
615
  } catch (error) {
590
616
  logger.error(`Error in device code flow: ${error.message}`);
@@ -626,7 +652,6 @@ class AuthManager {
626
652
  await this.saveSelectedAccount();
627
653
  logger.info(`Auto-selected new account: ${response.account.username}`);
628
654
  }
629
- await this.saveTokenCache();
630
655
  return this.accessToken;
631
656
  } catch (error) {
632
657
  logger.error(`Error in interactive browser flow: ${error.message}`);
@@ -842,7 +867,6 @@ class AuthManager {
842
867
  };
843
868
  try {
844
869
  const response = await this.msalApp.acquireTokenSilent(silentRequest);
845
- await this.saveTokenCache();
846
870
  return response.accessToken;
847
871
  } catch (error) {
848
872
  const hint = consumersAuthorityHint(error, targetAccount, this.config.auth.authority);
@@ -858,6 +882,7 @@ class AuthManager {
858
882
  var auth_default = AuthManager;
859
883
  export {
860
884
  buildAllowedScopeDiagnostics,
885
+ buildDiskCoherencyCachePlugin,
861
886
  buildScopeDiagnostics,
862
887
  buildScopesFromEndpoints,
863
888
  collapseScopeHierarchy,
@@ -359,6 +359,7 @@
359
359
  "toolName": "create-calendar-event",
360
360
  "presets": ["calendar", "outlook", "personal"],
361
361
  "scopes": ["Calendars.ReadWrite"],
362
+ "descriptionOverride": "Create (schedule) a new calendar event — a meeting or appointment — on the user's calendar. Set subject, start/end times, time zone, location, body, and attendees; supports online meetings and recurrence.",
362
363
  "llmTip": "CRITICAL: Do not try to guess the email address of the recipients. Use the list-users tool to find the email address of the recipients."
363
364
  },
364
365
  {
@@ -75,6 +75,8 @@ class GraphClient {
75
75
  const text = await response.text();
76
76
  if (text === "") {
77
77
  result = { message: "OK!" };
78
+ } else if (options.rawResponse) {
79
+ result = { message: "OK!", rawResponse: text };
78
80
  } else {
79
81
  try {
80
82
  result = JSON.parse(text);
@@ -170,7 +170,10 @@ const UTILITY_TOOLS = [
170
170
  if (authManager && !authManager.isOAuthModeEnabled() && !getRequestTokens()) {
171
171
  accountAccessToken = await authManager.getTokenForAccount(accountParam);
172
172
  }
173
- return await graphClient.graphRequest(target, { accessToken: accountAccessToken });
173
+ return await graphClient.graphRequest(target, {
174
+ accessToken: accountAccessToken,
175
+ rawResponse: true
176
+ });
174
177
  } catch (error) {
175
178
  return {
176
179
  content: [{ type: "text", text: JSON.stringify({ error: error.message }) }],
@@ -823,7 +826,7 @@ function registerGraphTools(server, graphClient, readOnly = false, enabledToolsP
823
826
  ).optional();
824
827
  }
825
828
  let toolDescription = withApiVersionPrefix(
826
- tool.description || `Execute ${tool.method.toUpperCase()} request to ${tool.path}`,
829
+ (endpointConfig?.descriptionOverride ?? tool.description) || `Execute ${tool.method.toUpperCase()} request to ${tool.path}`,
827
830
  endpointConfig
828
831
  );
829
832
  if (endpointConfig?.llmTip) {
@@ -920,7 +923,10 @@ function buildDiscoverySearchIndex(toolsRegistry, utilityTools = []) {
920
923
  const nt = tokenize(name);
921
924
  nameTokens.set(name, new Set(nt));
922
925
  const pathTokens = tokenize(tool.path);
923
- const descTokens = tokenize(tool.description).slice(0, DESC_CAP_TOKENS);
926
+ const descTokens = tokenize(config?.descriptionOverride ?? tool.description).slice(
927
+ 0,
928
+ DESC_CAP_TOKENS
929
+ );
924
930
  const tipTokens = tokenize(config?.llmTip).slice(0, TIP_EXCERPT_TOKENS);
925
931
  const tokens = [
926
932
  ...nt,
@@ -1032,7 +1038,7 @@ function registerDiscoveryTools(server, graphClient, readOnly = false, orgMode =
1032
1038
  method: tool.method.toUpperCase(),
1033
1039
  path: tool.path,
1034
1040
  description: withApiVersionPrefix(
1035
- tool.description || `${tool.method.toUpperCase()} ${tool.path}`,
1041
+ (config?.descriptionOverride ?? tool.description) || `${tool.method.toUpperCase()} ${tool.path}`,
1036
1042
  config
1037
1043
  ),
1038
1044
  ...config?.llmTip ? { llmTip: config.llmTip } : {}
@@ -1111,7 +1117,11 @@ function registerDiscoveryTools(server, graphClient, readOnly = false, orgMode =
1111
1117
  async ({ tool_name }) => {
1112
1118
  const entry = toolsRegistry.get(tool_name);
1113
1119
  if (entry) {
1114
- const schema = describeToolSchema(entry.tool, entry.config?.llmTip);
1120
+ const schema = describeToolSchema(
1121
+ entry.tool,
1122
+ entry.config?.llmTip,
1123
+ entry.config?.descriptionOverride
1124
+ );
1115
1125
  return {
1116
1126
  content: [{ type: "text", text: JSON.stringify(schema, null, 2) }]
1117
1127
  };
@@ -7,7 +7,7 @@ function unwrapOptional(schema) {
7
7
  }
8
8
  return { inner: schema, optional: false };
9
9
  }
10
- function describeToolSchema(tool, llmTip) {
10
+ function describeToolSchema(tool, llmTip, descriptionOverride) {
11
11
  const params = (tool.parameters ?? []).map((p) => {
12
12
  const { inner, optional } = unwrapOptional(p.schema);
13
13
  const isPath = p.type === "Path";
@@ -25,7 +25,7 @@ function describeToolSchema(tool, llmTip) {
25
25
  name: tool.alias,
26
26
  method: tool.method.toUpperCase(),
27
27
  path: tool.path,
28
- description: tool.description ?? "",
28
+ description: descriptionOverride ?? tool.description ?? "",
29
29
  ...llmTip ? { llmTip } : {},
30
30
  parameters: params
31
31
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softeria/ms-365-mcp-server",
3
- "version": "0.128.0",
3
+ "version": "0.128.2",
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",
@@ -359,6 +359,7 @@
359
359
  "toolName": "create-calendar-event",
360
360
  "presets": ["calendar", "outlook", "personal"],
361
361
  "scopes": ["Calendars.ReadWrite"],
362
+ "descriptionOverride": "Create (schedule) a new calendar event — a meeting or appointment — on the user's calendar. Set subject, start/end times, time zone, location, body, and attendees; supports online meetings and recurrence.",
362
363
  "llmTip": "CRITICAL: Do not try to guess the email address of the recipients. Use the list-users tool to find the email address of the recipients."
363
364
  },
364
365
  {