@softeria/ms-365-mcp-server 0.127.0 → 0.128.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.
@@ -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
  };
@@ -1,9 +1,17 @@
1
1
  import logger from "../logger.js";
2
2
  import { getCloudEndpoints } from "../cloud-config.js";
3
- function buildWwwAuthenticate(req, error, description) {
3
+ function buildResourceMetadataUrl(req, publicUrl) {
4
+ if (publicUrl) {
5
+ const parsed = new URL(publicUrl);
6
+ const path = parsed.pathname.replace(/\/$/, "");
7
+ return `${parsed.origin}/.well-known/oauth-protected-resource${path}`;
8
+ }
4
9
  const protocol = req.secure ? "https" : "http";
5
10
  const origin = `${protocol}://${req.get("host")}`;
6
- const resourceMetadata = `${origin}/.well-known/oauth-protected-resource`;
11
+ return `${origin}/.well-known/oauth-protected-resource`;
12
+ }
13
+ function buildWwwAuthenticate(req, error, description, publicUrl) {
14
+ const resourceMetadata = buildResourceMetadataUrl(req, publicUrl);
7
15
  return `Bearer resource_metadata="${resourceMetadata}", error="${error}", error_description="${description}"`;
8
16
  }
9
17
  function isJwtExpired(token) {
@@ -46,7 +54,12 @@ const microsoftBearerTokenAuthMiddleware = (opts = {}) => (req, res, next) => {
46
54
  }
47
55
  res.status(401).set(
48
56
  "WWW-Authenticate",
49
- buildWwwAuthenticate(req, "invalid_token", "Missing or malformed Authorization header")
57
+ buildWwwAuthenticate(
58
+ req,
59
+ "invalid_token",
60
+ "Missing or malformed Authorization header",
61
+ opts.publicUrl
62
+ )
50
63
  ).json({
51
64
  error: "invalid_token",
52
65
  error_description: "Missing or malformed Authorization header"
@@ -57,7 +70,7 @@ const microsoftBearerTokenAuthMiddleware = (opts = {}) => (req, res, next) => {
57
70
  if (isJwtExpired(accessToken)) {
58
71
  res.status(401).set(
59
72
  "WWW-Authenticate",
60
- buildWwwAuthenticate(req, "invalid_token", "The access token has expired")
73
+ buildWwwAuthenticate(req, "invalid_token", "The access token has expired", opts.publicUrl)
61
74
  ).json({ error: "invalid_token", error_description: "The access token has expired" });
62
75
  return;
63
76
  }
@@ -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/dist/server.js CHANGED
@@ -229,7 +229,7 @@ class MicrosoftGraphServer {
229
229
  const metadata = {
230
230
  issuer: browserBase,
231
231
  authorization_endpoint: `${browserBase}/authorize`,
232
- token_endpoint: `${requestOrigin}/token`,
232
+ token_endpoint: `${browserBase}/token`,
233
233
  response_types_supported: ["code"],
234
234
  response_modes_supported: ["query"],
235
235
  grant_types_supported: ["authorization_code", "refresh_token"],
@@ -238,23 +238,25 @@ class MicrosoftGraphServer {
238
238
  scopes_supported: scopes
239
239
  };
240
240
  if (this.options.enableDynamicRegistration) {
241
- metadata.registration_endpoint = `${requestOrigin}/register`;
241
+ metadata.registration_endpoint = `${browserBase}/register`;
242
242
  }
243
243
  res.json(metadata);
244
244
  });
245
- app.get("/.well-known/oauth-protected-resource", async (req, res) => {
245
+ const protectedResourcesHandler = async (req, res) => {
246
246
  const protocol = req.secure ? "https" : "http";
247
247
  const requestOrigin = `${protocol}://${req.get("host")}`;
248
248
  const browserBase = publicBase ?? requestOrigin;
249
249
  const scopes = this.options.obo ? [`${this.secrets.clientId}/access_as_user`] : resolveAuthScopes(this.options);
250
250
  res.json({
251
- resource: `${requestOrigin}/mcp`,
251
+ resource: `${browserBase}/mcp`,
252
252
  authorization_servers: [browserBase],
253
253
  scopes_supported: scopes,
254
254
  bearer_methods_supported: ["header"],
255
255
  resource_documentation: browserBase
256
256
  });
257
- });
257
+ };
258
+ app.get("/.well-known/oauth-protected-resource", protectedResourcesHandler);
259
+ app.get("/.well-known/oauth-protected-resource/*path", protectedResourcesHandler);
258
260
  if (this.options.enableDynamicRegistration) {
259
261
  app.post("/register", async (req, res) => {
260
262
  const body = req.body;
@@ -470,7 +472,8 @@ class MicrosoftGraphServer {
470
472
  );
471
473
  const mcpAuth = microsoftBearerTokenAuthMiddleware({
472
474
  trustProxyAuth: this.options.trustProxyAuth,
473
- allowUnauthenticatedDiscovery: this.options.allowUnauthenticatedDiscovery
475
+ allowUnauthenticatedDiscovery: this.options.allowUnauthenticatedDiscovery,
476
+ publicUrl: publicBase
474
477
  });
475
478
  app.get(
476
479
  "/mcp",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softeria/ms-365-mcp-server",
3
- "version": "0.127.0",
3
+ "version": "0.128.1",
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
  {