@softeria/ms-365-mcp-server 0.158.0 → 0.158.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.
@@ -748,6 +748,7 @@ const microsoft_graph_customEmojiFromIdentitySet = z.object({
748
748
  user: microsoft_graph_identity.optional()
749
749
  }).passthrough();
750
750
  const microsoft_graph_teamworkCustomEmoji = z.object({
751
+ id: z.string().describe("The unique identifier for an entity. Read-only.").optional(),
751
752
  contentBytes: z.string().describe(
752
753
  "The base64-encoded image content of the emoji. Supported formats include PNG and GIF."
753
754
  ).nullish(),
@@ -2688,7 +2688,16 @@ const microsoft_graph_subscription = z.object({
2688
2688
  ).nullish(),
2689
2689
  resource: z.string().describe(
2690
2690
  "Required. Specifies the resource that is monitored for changes. Don't include the base URL (https://graph.microsoft.com/v1.0/). See the possible resource path values for each supported resource."
2691
- ).optional()
2691
+ ).optional(),
2692
+ vapidPublicKey: z.string().describe(
2693
+ "Optional. The application server's VAPID public key, base64url-encoded (P-256 uncompressed point, 65 bytes pre-encoding). Obtained by calling the getVapidPublicKey function on the subscription collection. The browser passes this value to PushManager.subscribe({ applicationServerKey: vapidPublicKey }) to bind the push subscription to this server identity. Required when notificationUrl targets a known Web Push service origin (for example, *.push.apple.com, fcm.googleapis.com, updates.push.services.mozilla.com); rejected with 400 Bad Request if supplied on a standard webhook subscription. For more information, see RFC 8292."
2694
+ ).nullish(),
2695
+ webPushEncryptionP256dhPublicKey: z.string().describe(
2696
+ "Optional. The subscriber's ECDH public key, base64url-encoded (P-256 uncompressed point, 65 bytes pre-encoding). Obtained from the browser via PushSubscription.getKey('p256dh'). Used as the peer public key during ECDH key agreement to derive the per-message content encryption key for RFC 8291 payload encryption. Required when notificationUrl targets a known Web Push service origin; rejected with 400 Bad Request if supplied on a standard webhook subscription. For more information, see RFC 8291 Section 3."
2697
+ ).nullish(),
2698
+ webPushEncryptionSecret: z.string().describe(
2699
+ "Optional. The subscriber's auth secret, base64url-encoded (16 bytes pre-encoding). Obtained from the browser via PushSubscription.getKey('auth'). Used as the HMAC-SHA-256 salt for the HKDF combine step that derives key material for RFC 8291 payload encryption. Write-only: this value is never returned in GET responses (returned as null). Treat as a secret. Required when notificationUrl targets a known Web Push service origin; rejected with 400 Bad Request if supplied on a standard webhook subscription. For more information, see RFC 8291 Section 3."
2700
+ ).nullish()
2692
2701
  }).passthrough();
2693
2702
  const microsoft_graph_list = z.lazy(
2694
2703
  () => z.object({
@@ -18,6 +18,7 @@ import { getAttachmentMinting } from "./lib/attachment-minting.js";
18
18
  import {
19
19
  buildAttachmentUrl,
20
20
  isPlainGraphPath,
21
+ isUnalteredGraphPath,
21
22
  TicketStoreFullError
22
23
  } from "./lib/attachment-tickets.js";
23
24
  import { buildBM25Index, scoreQuery, tokenize } from "./lib/bm25.js";
@@ -685,6 +686,8 @@ const UTILITY_TOOLS = [
685
686
  isError: true
686
687
  };
687
688
  }
689
+ const alteredTarget = unalteredTargetError(target);
690
+ if (alteredTarget) return alteredTarget;
688
691
  const restrictedTarget = userProfilePassthroughError(target, userFields);
689
692
  if (restrictedTarget) return restrictedTarget;
690
693
  try {
@@ -782,6 +785,8 @@ const UTILITY_TOOLS = [
782
785
  isError: true
783
786
  };
784
787
  }
788
+ const alteredTarget = unalteredTargetError(target);
789
+ if (alteredTarget) return alteredTarget;
785
790
  const restrictedTarget = userProfilePassthroughError(target, userFields);
786
791
  if (restrictedTarget) return restrictedTarget;
787
792
  if (!path.isAbsolute(outputPath)) {
@@ -907,6 +912,8 @@ const UTILITY_TOOLS = [
907
912
  isError: true
908
913
  };
909
914
  }
915
+ const alteredTarget = unalteredTargetError(target);
916
+ if (alteredTarget) return alteredTarget;
910
917
  const restrictedTarget = userProfilePassthroughError(target, userFields);
911
918
  if (restrictedTarget) return restrictedTarget;
912
919
  const queryIdx = target.indexOf("?");
@@ -1193,6 +1200,36 @@ function describeParamsForLog(params) {
1193
1200
  return `[unserializable: ${error instanceof Error ? error.name : "unknown error"}]`;
1194
1201
  }
1195
1202
  }
1203
+ function escapeIfStringLiteral(template, names, value) {
1204
+ const quoted = names.some((n) => template.includes(`'{${n}}'`) || template.includes(`':${n}'`));
1205
+ return quoted ? String(value).replace(/'/g, "''") : String(value);
1206
+ }
1207
+ function rawValueAddsSegments(template, names, value) {
1208
+ const isTail = names.some((n) => template.endsWith(`/{${n}}`) || template.endsWith(`/:${n}`));
1209
+ return isTail ? value.includes(":") : value.includes("/");
1210
+ }
1211
+ function invalidPathParameter(message) {
1212
+ return {
1213
+ content: [
1214
+ { type: "text", text: JSON.stringify({ error: "invalid_path_parameter", message }) }
1215
+ ],
1216
+ isError: true
1217
+ };
1218
+ }
1219
+ function unalteredTargetError(target) {
1220
+ if (isUnalteredGraphPath(target.split("?")[0])) return void 0;
1221
+ return {
1222
+ content: [
1223
+ {
1224
+ type: "text",
1225
+ text: JSON.stringify({
1226
+ error: 'target must not contain "." or ".." segments, a fragment or a backslash.'
1227
+ })
1228
+ }
1229
+ ],
1230
+ isError: true
1231
+ };
1232
+ }
1196
1233
  async function executeGraphTool(tool, config, graphClient, params, authManager, userFields) {
1197
1234
  logger.info(`Tool ${tool.alias} called with params: ${describeParamsForLog(params)}`);
1198
1235
  if (isConfirmGateEnabled() && isDestructiveOperation(tool.method, config) && params.confirm !== true) {
@@ -1314,8 +1351,17 @@ async function executeGraphTool(tool, config, graphClient, params, authManager,
1314
1351
  switch (paramDef.type) {
1315
1352
  case "Path": {
1316
1353
  const shouldSkipEncoding = config?.skipEncoding?.includes(paramName) ?? false;
1317
- const encodedValue = shouldSkipEncoding ? paramValue : encodeURIComponent(paramValue).replace(/%3D/g, "=");
1318
- path2 = path2.replace(`{${paramName}}`, encodedValue).replace(`:${paramName}`, encodedValue).replace(`{${camelCaseParamName}}`, encodedValue).replace(`:${camelCaseParamName}`, encodedValue);
1354
+ const names = [paramName, camelCaseParamName];
1355
+ if (shouldSkipEncoding && rawValueAddsSegments(tool.path, names, `${paramValue}`)) {
1356
+ return invalidPathParameter(
1357
+ `'${paramName}' cannot add path segments to this endpoint: no "/" in a value, and no ":" in a site path.`
1358
+ );
1359
+ }
1360
+ const encodedValue = shouldSkipEncoding ? paramValue : encodeURIComponent(escapeIfStringLiteral(tool.path, names, paramValue)).replace(
1361
+ /%3D/g,
1362
+ "="
1363
+ );
1364
+ path2 = path2.replace(`{${paramName}}`, () => encodedValue).replace(`:${paramName}`, () => encodedValue).replace(`{${camelCaseParamName}}`, () => encodedValue).replace(`:${camelCaseParamName}`, () => encodedValue);
1319
1365
  break;
1320
1366
  }
1321
1367
  case "Query":
@@ -1352,8 +1398,10 @@ async function executeGraphTool(tool, config, graphClient, params, authManager,
1352
1398
  body = paramValue;
1353
1399
  logger.info(`Set body param: ${JSON.stringify(body)}`);
1354
1400
  } else if (path2.includes(`:${paramName}`) || path2.includes(`{${paramName}}`) || path2.includes(`:${camelCaseParamName}`) || path2.includes(`{${camelCaseParamName}}`)) {
1355
- const encodedValue = encodeURIComponent(paramValue).replace(/%3D/g, "=");
1356
- path2 = path2.replace(`{${paramName}}`, encodedValue).replace(`:${paramName}`, encodedValue).replace(`{${camelCaseParamName}}`, encodedValue).replace(`:${camelCaseParamName}`, encodedValue);
1401
+ const encodedValue = encodeURIComponent(
1402
+ escapeIfStringLiteral(tool.path, [paramName, camelCaseParamName], paramValue)
1403
+ ).replace(/%3D/g, "=");
1404
+ path2 = path2.replace(`{${paramName}}`, () => encodedValue).replace(`:${paramName}`, () => encodedValue).replace(`{${camelCaseParamName}}`, () => encodedValue).replace(`:${camelCaseParamName}`, () => encodedValue);
1357
1405
  logger.info(`Path param fallback: replaced :${camelCaseParamName} with encoded value`);
1358
1406
  } else if (paramName.toLowerCase() === "accept" && config?.acceptType) {
1359
1407
  headers["Accept"] = `${paramValue}`;
@@ -1370,6 +1418,11 @@ async function executeGraphTool(tool, config, graphClient, params, authManager,
1370
1418
  logger.warn(`Dropping unrecognized parameter '${paramName}' for tool ${tool.alias}`);
1371
1419
  }
1372
1420
  }
1421
+ if (!isUnalteredGraphPath(path2)) {
1422
+ return invalidPathParameter(
1423
+ 'A path parameter would send this request to a different endpoint. Path parameters cannot be "." or "..", or contain "/../", "?", "#" or a backslash. Percent-encode "?" and "#" that are part of a name.'
1424
+ );
1425
+ }
1373
1426
  if (isPlainObject(body) && bodyShape != null && hasOwn(bodyShape, "body") && !Object.keys(body).some((k) => k.toLowerCase() === "body")) {
1374
1427
  const nestedShape = bodySchemaShape(bodyShape.body);
1375
1428
  if (nestedShape != null) {
@@ -24,6 +24,15 @@ function isPlainGraphPath(target) {
24
24
  }
25
25
  return resolved.origin === PROBE_ORIGIN && resolved.search === "" && resolved.hash === "" && resolved.pathname === PROBE_PREFIX + target;
26
26
  }
27
+ function isUnalteredGraphPath(path) {
28
+ const decode = (s) => decodeURIComponent(s.replace(/%(?![0-9a-f]{2})/gi, "%25"));
29
+ try {
30
+ const resolved = new URL(PROBE_ORIGIN + PROBE_PREFIX + path);
31
+ return resolved.origin === PROBE_ORIGIN && resolved.search === "" && resolved.hash === "" && decode(resolved.pathname) === decode(PROBE_PREFIX + path);
32
+ } catch {
33
+ return false;
34
+ }
35
+ }
27
36
  const MAX_LIVE_TICKETS = 256;
28
37
  const TICKET_BYTES = 32;
29
38
  class TicketStoreFullError extends Error {
@@ -94,5 +103,6 @@ export {
94
103
  TICKET_PARAM,
95
104
  TicketStoreFullError,
96
105
  buildAttachmentUrl,
97
- isPlainGraphPath
106
+ isPlainGraphPath,
107
+ isUnalteredGraphPath
98
108
  };
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.158.0",
4
+ "version": "0.158.2",
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",