@softeria/ms-365-mcp-server 0.63.0 → 0.63.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.
@@ -398,7 +398,7 @@
398
398
  {
399
399
  "pathPattern": "/drives/{drive-id}/root",
400
400
  "method": "get",
401
- "toolName": "get-root-folder",
401
+ "toolName": "get-drive-root-item",
402
402
  "scopes": ["Files.Read"]
403
403
  },
404
404
  {
@@ -571,14 +571,6 @@
571
571
  "contentType": "text/html",
572
572
  "llmTip": "Body must be a full HTML document (with <html><head><title>...</title></head><body>...</body></html>). Partial HTML fails silently."
573
573
  },
574
- {
575
- "pathPattern": "/me/onenote/pages/{onenotePage-id}/content",
576
- "method": "patch",
577
- "toolName": "update-onenote-page",
578
- "scopes": ["Notes.ReadWrite"],
579
- "contentType": "application/json",
580
- "llmTip": "Updates page content using PATCH commands. Body is an array of patch actions: [{ target: 'body', action: 'append', content: '<p>New content</p>' }]. Actions: 'append' (add to end), 'replace' (replace target), 'insert' (insert after target). Target can be 'body', 'title', or a specific element ID."
581
- },
582
574
  {
583
575
  "pathPattern": "/me/onenote/pages/{onenotePage-id}",
584
576
  "method": "delete",
package/dist/server.js CHANGED
@@ -17,6 +17,7 @@ import {
17
17
  import { getSecrets } from "./secrets.js";
18
18
  import { getCloudEndpoints } from "./cloud-config.js";
19
19
  import { requestContext } from "./request-context.js";
20
+ import crypto from "node:crypto";
20
21
  function parseHttpOption(httpOption) {
21
22
  if (typeof httpOption === "boolean") {
22
23
  return { host: void 0, port: 3e3 };
@@ -36,6 +37,8 @@ class MicrosoftGraphServer {
36
37
  this.version = "0.0.0";
37
38
  this.multiAccount = false;
38
39
  this.accountNames = [];
40
+ // Two-leg PKCE: stores client's code_challenge and server's code_verifier, keyed by OAuth state
41
+ this.pkceStore = /* @__PURE__ */ new Map();
39
42
  this.authManager = authManager;
40
43
  this.options = options;
41
44
  this.graphClient = null;
@@ -189,14 +192,15 @@ class MicrosoftGraphServer {
189
192
  const microsoftAuthUrl = new URL(
190
193
  `${cloudEndpoints.authority}/${tenantId}/oauth2/v2.0/authorize`
191
194
  );
195
+ const clientCodeChallenge = url.searchParams.get("code_challenge");
196
+ const clientCodeChallengeMethod = url.searchParams.get("code_challenge_method");
197
+ const state = url.searchParams.get("state");
192
198
  const allowedParams = [
193
199
  "response_type",
194
200
  "redirect_uri",
195
201
  "scope",
196
202
  "state",
197
203
  "response_mode",
198
- "code_challenge",
199
- "code_challenge_method",
200
204
  "prompt",
201
205
  "login_hint",
202
206
  "domain_hint"
@@ -207,6 +211,32 @@ class MicrosoftGraphServer {
207
211
  microsoftAuthUrl.searchParams.set(param, value);
208
212
  }
209
213
  });
214
+ if (clientCodeChallenge && state) {
215
+ const serverCodeVerifier = crypto.randomBytes(32).toString("base64url");
216
+ const serverCodeChallenge = crypto.createHash("sha256").update(serverCodeVerifier).digest("base64url");
217
+ this.pkceStore.set(state, {
218
+ clientCodeChallenge,
219
+ clientCodeChallengeMethod: clientCodeChallengeMethod || "S256",
220
+ serverCodeVerifier,
221
+ createdAt: Date.now()
222
+ });
223
+ const now = Date.now();
224
+ for (const [key, value] of this.pkceStore) {
225
+ if (now - value.createdAt > 10 * 60 * 1e3) {
226
+ this.pkceStore.delete(key);
227
+ }
228
+ }
229
+ microsoftAuthUrl.searchParams.set("code_challenge", serverCodeChallenge);
230
+ microsoftAuthUrl.searchParams.set("code_challenge_method", "S256");
231
+ logger.info("Two-leg PKCE: stored client challenge, generated server challenge", {
232
+ state: state.substring(0, 8) + "..."
233
+ });
234
+ } else if (clientCodeChallenge) {
235
+ microsoftAuthUrl.searchParams.set("code_challenge", clientCodeChallenge);
236
+ if (clientCodeChallengeMethod) {
237
+ microsoftAuthUrl.searchParams.set("code_challenge_method", clientCodeChallengeMethod);
238
+ }
239
+ }
210
240
  microsoftAuthUrl.searchParams.set("client_id", clientId);
211
241
  if (!microsoftAuthUrl.searchParams.get("scope")) {
212
242
  microsoftAuthUrl.searchParams.set("scope", "User.Read Files.Read Mail.Read");
@@ -250,13 +280,28 @@ class MicrosoftGraphServer {
250
280
  tenantId,
251
281
  hasClientSecret: !!clientSecret
252
282
  });
283
+ let serverCodeVerifier;
284
+ if (body.code_verifier) {
285
+ const clientVerifier = body.code_verifier;
286
+ const clientChallengeComputed = crypto.createHash("sha256").update(clientVerifier).digest("base64url");
287
+ for (const [state, pkceData] of this.pkceStore) {
288
+ if (pkceData.clientCodeChallenge === clientChallengeComputed) {
289
+ serverCodeVerifier = pkceData.serverCodeVerifier;
290
+ this.pkceStore.delete(state);
291
+ logger.info("Two-leg PKCE: matched client verifier, using server verifier", {
292
+ state: state.substring(0, 8) + "..."
293
+ });
294
+ break;
295
+ }
296
+ }
297
+ }
253
298
  const result = await exchangeCodeForToken(
254
299
  body.code,
255
300
  body.redirect_uri,
256
301
  clientId,
257
302
  clientSecret,
258
303
  tenantId,
259
- body.code_verifier,
304
+ serverCodeVerifier || body.code_verifier,
260
305
  this.secrets.cloudType
261
306
  );
262
307
  res.json(result);
@@ -1,10 +1,10 @@
1
- 2026-04-05 07:58:04 INFO: [GRAPH CLIENT] Final URL being sent to Microsoft: https://graph.microsoft.com/v1.0/me
2
- 2026-04-05 07:58:04 INFO: [GRAPH CLIENT] Final URL being sent to Microsoft: https://graph.microsoft.com/v1.0/me
3
- 2026-04-05 07:58:04 INFO: [GRAPH CLIENT] Final URL being sent to Microsoft: https://graph.microsoft.com/v1.0/me
4
- 2026-04-05 07:58:04 INFO: [GRAPH CLIENT] Final URL being sent to Microsoft: https://graph.microsoft.com/v1.0/me/messages
5
- 2026-04-05 07:58:04 INFO: [GRAPH CLIENT] Final URL being sent to Microsoft: https://graph.microsoft.com/v1.0/me/calendar
6
- 2026-04-05 07:58:05 INFO: Using environment variables for secrets
7
- 2026-04-05 07:58:05 INFO: Using environment variables for secrets
8
- 2026-04-05 07:58:05 INFO: Using environment variables for secrets
9
- 2026-04-05 07:58:05 INFO: Using environment variables for secrets
10
- 2026-04-05 07:58:05 INFO: Using environment variables for secrets
1
+ 2026-04-05 08:47:40 INFO: [GRAPH CLIENT] Final URL being sent to Microsoft: https://graph.microsoft.com/v1.0/me
2
+ 2026-04-05 08:47:40 INFO: [GRAPH CLIENT] Final URL being sent to Microsoft: https://graph.microsoft.com/v1.0/me
3
+ 2026-04-05 08:47:40 INFO: [GRAPH CLIENT] Final URL being sent to Microsoft: https://graph.microsoft.com/v1.0/me
4
+ 2026-04-05 08:47:40 INFO: [GRAPH CLIENT] Final URL being sent to Microsoft: https://graph.microsoft.com/v1.0/me/messages
5
+ 2026-04-05 08:47:40 INFO: [GRAPH CLIENT] Final URL being sent to Microsoft: https://graph.microsoft.com/v1.0/me/calendar
6
+ 2026-04-05 08:47:42 INFO: Using environment variables for secrets
7
+ 2026-04-05 08:47:42 INFO: Using environment variables for secrets
8
+ 2026-04-05 08:47:42 INFO: Using environment variables for secrets
9
+ 2026-04-05 08:47:42 INFO: Using environment variables for secrets
10
+ 2026-04-05 08:47:42 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.63.0",
3
+ "version": "0.63.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",
@@ -398,7 +398,7 @@
398
398
  {
399
399
  "pathPattern": "/drives/{drive-id}/root",
400
400
  "method": "get",
401
- "toolName": "get-root-folder",
401
+ "toolName": "get-drive-root-item",
402
402
  "scopes": ["Files.Read"]
403
403
  },
404
404
  {
@@ -571,14 +571,6 @@
571
571
  "contentType": "text/html",
572
572
  "llmTip": "Body must be a full HTML document (with <html><head><title>...</title></head><body>...</body></html>). Partial HTML fails silently."
573
573
  },
574
- {
575
- "pathPattern": "/me/onenote/pages/{onenotePage-id}/content",
576
- "method": "patch",
577
- "toolName": "update-onenote-page",
578
- "scopes": ["Notes.ReadWrite"],
579
- "contentType": "application/json",
580
- "llmTip": "Updates page content using PATCH commands. Body is an array of patch actions: [{ target: 'body', action: 'append', content: '<p>New content</p>' }]. Actions: 'append' (add to end), 'replace' (replace target), 'insert' (insert after target). Target can be 'body', 'title', or a specific element ID."
581
- },
582
574
  {
583
575
  "pathPattern": "/me/onenote/pages/{onenotePage-id}",
584
576
  "method": "delete",