Skip to content

Add Microsoft To Do MCP tools - #64

Open
NadodiLabs wants to merge 2 commits into
ryaker:mainfrom
NadodiLabs:carby/microsoft-todo-support
Open

Add Microsoft To Do MCP tools#64
NadodiLabs wants to merge 2 commits into
ryaker:mainfrom
NadodiLabs:carby/microsoft-todo-support

Conversation

@NadodiLabs

@NadodiLabs NadodiLabs commented Jun 28, 2026

Copy link
Copy Markdown

Summary

  • add Microsoft To Do MCP tools for listing task lists and tasks
  • request Tasks.ReadWrite in auth defaults and document the new permission
  • add Jest coverage for To Do handlers and auth status token checks

Testing

  • npm test -- --runTestsByPath test/todo/handlers.test.js test/auth/tools.test.js
  • node index.js MCP initialize + tools/list transport verification

Summary by CodeRabbit

  • New Features
    • Added Microsoft To Do support, including tools to list task lists and list tasks for a selected list.
    • Extended Microsoft Graph app access to include To Do permissions so To Do data can be retrieved.
  • Bug Fixes
    • Improved authentication readiness checks to more reliably confirm a usable access token before reporting the app as ready.
    • Updated authentication-related server messaging for clearer “unauthorized/missing auth” guidance.
  • Documentation
    • Updated the README with Microsoft To Do setup details, required permissions, and the new available tools.

@codeant-ai

codeant-ai Bot commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

User does not have a PR Review subscription.

Go to Team management and add this email to the PR Review subscription.

@coderabbitai

coderabbitai Bot commented Jun 28, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 802e113a-7a91-4bc6-aa9b-77307f9d584e

📥 Commits

Reviewing files that changed from the base of the PR and between 953eaf2 and ecb056e.

📒 Files selected for processing (3)
  • test/todo/handlers.test.js
  • todo/lists.js
  • todo/tasks.js
💤 Files with no reviewable changes (2)
  • todo/lists.js
  • todo/tasks.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/todo/handlers.test.js

📝 Walkthrough

Walkthrough

Adds Microsoft To Do support with new list/task Graph handlers, tool registration, expanded OAuth scopes, auth-status changes, mock Graph responses, tests, and README documentation.

Changes

Microsoft To Do Integration

Layer / File(s) Summary
Config and OAuth scopes
config.js, auth/oauth-server.js, auth/token-storage.js, outlook-auth-server.js
Tasks.ReadWrite is added to OAuth scope defaults, and config.js adds TODO_LIST_SELECT_FIELDS and TODO_TASK_SELECT_FIELDS.
To Do handlers
todo/lists.js, todo/tasks.js
handleListTodoLists and handleListTodoTasks fetch Microsoft Graph To Do data, format the returned text, and map auth and API errors to user-facing messages.
Tool registry wiring
todo/index.js, index.js
todoTools is defined for list-todo-lists and list-todo-tasks, then added to the server tool registry.
Auth status and token logging
auth/tools.js, auth/token-storage.js
handleCheckAuthStatus now uses TokenStorage, and token-storage status messages switch from console.log to console.error.
Tests and Graph mocks
test/auth/tools.test.js, test/todo/handlers.test.js, utils/mock-data.js
Tests cover auth-status states and To Do handler responses, while mock Graph data adds me/todo/lists and me/todo/lists/{id}/tasks.
README updates
README.md
Microsoft To Do is documented in supported services, features, tools, directory structure, and app permissions.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • ryaker/outlook-mcp#48: Both PRs touch auth/token-storage.js and related OAuth/token configuration behavior.

Suggested labels

size:L

🐇 Hop, hop, the To Do lights glow bright,
Lists and tasks are fetched just right.
Scopes expanded, tokens hum along,
Graph replies turn into a rabbit song.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding Microsoft To Do MCP tools.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 OpenGrep (1.23.0)
test/todo/handlers.test.js

┌──────────────┐
│ Opengrep CLI │
└──────────────┘

�[32m✔�[39m �[1mOpengrep OSS�[0m
�[32m✔�[39m Basic security coverage for first-party code vulnerabilities.

[00.48][ERROR]: unable to find a config; path .coderabbit-opengrep-fallback.yml does not exist


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request adds Microsoft To Do integration to the MCP server, introducing tools to list task lists and tasks, along with corresponding tests and configuration updates. The review feedback focuses on improving robustness by adding optional chaining and validation guards to input arguments and API responses, as well as stripping HTML tags from task bodies to ensure clean output.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread todo/lists.js
const { ensureAuthenticated } = require('../auth');

async function handleListTodoLists(args = {}) {
const count = Math.min(args.count || 10, config.MAX_RESULT_COUNT);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To prevent potential errors from invalid or negative input values, and to safely handle cases where args might be null, use args?.count and guard the value with Math.max(1, ...).

Suggested change
const count = Math.min(args.count || 10, config.MAX_RESULT_COUNT);
const count = Math.max(1, Math.min(Number(args?.count) || 10, config.MAX_RESULT_COUNT));

Comment thread todo/lists.js
}
);

if (!response.value || response.value.length === 0) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Add optional chaining when checking response to prevent a TypeError if the API call returns an empty or malformed response.

Suggested change
if (!response.value || response.value.length === 0) {
if (!response?.value || response.value.length === 0) {

Comment thread todo/tasks.js
}

async function handleListTodoTasks(args = {}) {
const listId = args.listId;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Use optional chaining on args to prevent a TypeError if the function is called with a nullish argument.

Suggested change
const listId = args.listId;
const listId = args?.listId;

Comment thread todo/tasks.js
};
}

const count = Math.min(args.count || 10, config.MAX_RESULT_COUNT);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To prevent potential errors from invalid or negative input values, and to safely handle cases where args might be null, use args?.count and guard the value with Math.max(1, ...).

Suggested change
const count = Math.min(args.count || 10, config.MAX_RESULT_COUNT);
const count = Math.max(1, Math.min(Number(args?.count) || 10, config.MAX_RESULT_COUNT));

Comment thread todo/tasks.js
}
);

if (!response.value || response.value.length === 0) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Add optional chaining when checking response to prevent a TypeError if the API call returns an empty or malformed response.

Suggested change
if (!response.value || response.value.length === 0) {
if (!response?.value || response.value.length === 0) {

Comment thread todo/tasks.js
const due = formatGraphDateTime(task.dueDateTime);
const completed = formatGraphDateTime(task.completedDateTime);
const importance = task.importance || 'normal';
const body = task.body?.content ? `\nNotes: ${task.body.content}` : '';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Microsoft To Do task bodies can have a contentType of 'html'. If the content is HTML, displaying raw HTML tags to the user or LLM can be messy. Strip HTML tags using a simple regex if the content type is 'html'.

      let bodyContent = task.body?.content || '';
      if (bodyContent && task.body?.contentType === 'html') {
        bodyContent = bodyContent.replace(/<[^>]*>/g, '');
      }
      const body = bodyContent ? `\nNotes: ${bodyContent}` : '';

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
auth/token-storage.js (1)

99-113: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Delete the cache file when invalidating tokens.

Both failure paths null out this.tokens and then call _saveTokensToFile(), but _saveTokensToFile() is a no-op when this.tokens is falsy. The stale token file stays on disk, so the next auth-status check can reload expired credentials and repeat the same failing refresh path.

Suggested fix
-          this.tokens = null; // Invalidate tokens on refresh failure
-          await this._saveTokensToFile(); // Persist invalidation
+          await this.clearTokens(); // Remove the stale cache file
           return null;
@@
-        this.tokens = null; // Invalidate tokens as they are expired and cannot be refreshed
-        await this._saveTokensToFile(); // Persist invalidation
+        await this.clearTokens(); // Remove the stale cache file
         return null;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@auth/token-storage.js` around lines 99 - 113, The token invalidation paths in
token-storage.js leave the cache file on disk because _saveTokensToFile() does
nothing after this.tokens is set to null. In the refreshAccessToken failure
branch and the no refresh_token branch inside the expiration check, delete the
persisted token cache explicitly instead of relying on _saveTokensToFile(),
using the existing token storage helpers in TokenStorage so stale credentials
are not reloaded on the next auth check.
🧹 Nitpick comments (3)
README.md (1)

106-111: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add blank lines around the Microsoft To Do table for markdownlint compliance.

The table is missing a blank line before the ### Microsoft To Do heading (preceded immediately by the Outlook table row at line 105). This triggers MD058. Insert a blank line before line 106 and ensure line 111 remains blank after the table.

 | create-rule | Create inbox rule |
+
 ### Microsoft To Do
+
 | Tool | Description |
 |------|-------------|
 | list-todo-lists | List Microsoft To Do task lists |
 | list-todo-tasks | List tasks in a Microsoft To Do task list |
+
 ### OneDrive
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 106 - 111, The Microsoft To Do section in README.md
needs markdownlint-compliant spacing. Update the markdown around the Microsoft
To Do table so there is a blank line before the `### Microsoft To Do` heading
and a blank line after the table, matching the surrounding sections and
satisfying MD058.

Source: Linters/SAST tools

test/auth/tools.test.js (1)

1-78: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for the “token loaded but unusable” path.

handleCheckAuthStatus() has a separate branch where getTokens() returns an access_token but getValidAccessToken() returns null, and that should still produce "Not authenticated". This suite currently skips that regression point.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/auth/tools.test.js` around lines 1 - 78, The handleCheckAuthStatus suite
is missing coverage for the branch where getTokens() returns a token object but
getValidAccessToken() returns null. Add a test in auth/tools.test.js that mocks
token-storage so handleCheckAuthStatus() sees an access_token, then fails to
obtain a usable token, and assert it still returns “Not authenticated” while
only calling getValidAccessToken once.
test/todo/handlers.test.js (1)

80-145: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover the tasks UNAUTHORIZED branch too.

handleListTodoTasks() returns a distinct re-auth message when Graph rejects with UNAUTHORIZED, but the tasks suite never asserts that behavior. Since this PR adds Tasks.ReadWrite-gated flows, that branch is worth locking down here as well.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/todo/handlers.test.js` around lines 80 - 145, The handleListTodoTasks
test suite is missing coverage for the Graph UNAUTHORIZED re-auth path, so add a
case that makes callGraphAPI reject with an UNAUTHORIZED error and asserts the
re-authentication message returned by handleListTodoTasks. Reuse the existing
handleListTodoTasks, ensureAuthenticated, and callGraphAPI setup in this
describe block, and verify the branch that handles expired/invalid Graph access
is locked down alongside the existing empty-state and auth-required tests.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@auth/oauth-server.js`:
- Line 62: The fallback scope list in oauth-server.js is missing the existing
mail-write and OneDrive permissions, so update the scopes default used by the
oauth server configuration to include Mail.ReadWrite, Files.Read, and
Files.ReadWrite alongside the current scopes. Keep the change in the scopes
initialization that reads process.env[`${envPrefix}SCOPES`] so the default
MS_SCOPES value still matches what config.js expects.

In `@auth/token-storage.js`:
- Line 20: `TokenStorage` is missing several default OAuth scopes from the rest
of the auth flow, so its fallback no longer matches `config.js` and can drop
permissions during `exchangeCodeForTokens()` and `refreshAccessToken()`. Update
the default `scopes` value in `TokenStorage` to include the same full set used
by the auth configuration, specifically restoring the mail-write and OneDrive
scopes so `this.config.scopes` stays aligned when `MS_SCOPES` is unset.

In `@auth/tools.js`:
- Around line 5-7: The test-mode token dependency was removed too early, but
handleAuthenticate() still relies on tokenManager.createTestTokens() when
USE_TEST_MODE is enabled. Restore or preserve the tokenManager dependency in
auth/tools.js until that test-mode branch is fully migrated, and ensure
handleAuthenticate() can still return the mocked auth result without throwing.

In `@todo/index.js`:
- Around line 14-17: The `count` schema in the task-list tool contract is too
permissive and should only allow positive integers. Update the relevant schema
definitions in `todo/index.js` for both occurrences of `count` (including the
other block referenced in the comment) to use `type: 'integer'` with a minimum
of 1 and the existing configured maximum, so the handlers that pass `count` into
Graph’s `$top` only receive valid values.

In `@todo/lists.js`:
- Around line 54-59: The To Do error handling only recognizes the UNAUTHORIZED
case, so Graph 403 permission failures still fall through to the generic
message. Update the response handling in lists.js and the matching path in
tasks.js to also treat 403/missing Tasks.ReadWrite as the re-auth case, or
normalize that status in graph-api.js so the existing UNAUTHORIZED branch covers
it. Use the existing error-mapping flow around the list/tasks handlers to keep
the authorization guidance consistent.

In `@todo/tasks.js`:
- Around line 13-23: The date formatting logic in the
`raw/zone/hasOffset/iso/parsed` block is incorrectly treating Graph wall times
as UTC by appending “Z” to zone-local `dateTime` values. Update the parsing in
this helper so only offset-aware strings are passed through as-is, and for
`dateTimeValue.dateTime` with `dateTimeValue.timeZone` use the supplied zone to
convert before calling `toLocaleString`, preserving the local wall time instead
of shifting it.

---

Outside diff comments:
In `@auth/token-storage.js`:
- Around line 99-113: The token invalidation paths in token-storage.js leave the
cache file on disk because _saveTokensToFile() does nothing after this.tokens is
set to null. In the refreshAccessToken failure branch and the no refresh_token
branch inside the expiration check, delete the persisted token cache explicitly
instead of relying on _saveTokensToFile(), using the existing token storage
helpers in TokenStorage so stale credentials are not reloaded on the next auth
check.

---

Nitpick comments:
In `@README.md`:
- Around line 106-111: The Microsoft To Do section in README.md needs
markdownlint-compliant spacing. Update the markdown around the Microsoft To Do
table so there is a blank line before the `### Microsoft To Do` heading and a
blank line after the table, matching the surrounding sections and satisfying
MD058.

In `@test/auth/tools.test.js`:
- Around line 1-78: The handleCheckAuthStatus suite is missing coverage for the
branch where getTokens() returns a token object but getValidAccessToken()
returns null. Add a test in auth/tools.test.js that mocks token-storage so
handleCheckAuthStatus() sees an access_token, then fails to obtain a usable
token, and assert it still returns “Not authenticated” while only calling
getValidAccessToken once.

In `@test/todo/handlers.test.js`:
- Around line 80-145: The handleListTodoTasks test suite is missing coverage for
the Graph UNAUTHORIZED re-auth path, so add a case that makes callGraphAPI
reject with an UNAUTHORIZED error and asserts the re-authentication message
returned by handleListTodoTasks. Reuse the existing handleListTodoTasks,
ensureAuthenticated, and callGraphAPI setup in this describe block, and verify
the branch that handles expired/invalid Graph access is locked down alongside
the existing empty-state and auth-required tests.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 10558a8d-c4da-411b-8efe-a18bb9f2a544

📥 Commits

Reviewing files that changed from the base of the PR and between 95d6ff2 and 953eaf2.

📒 Files selected for processing (13)
  • README.md
  • auth/oauth-server.js
  • auth/token-storage.js
  • auth/tools.js
  • config.js
  • index.js
  • outlook-auth-server.js
  • test/auth/tools.test.js
  • test/todo/handlers.test.js
  • todo/index.js
  • todo/lists.js
  • todo/tasks.js
  • utils/mock-data.js

Comment thread auth/oauth-server.js
clientSecret: process.env[`${envPrefix}CLIENT_SECRET`] || '',
redirectUri: process.env[`${envPrefix}REDIRECT_URI`] || 'http://localhost:3333/auth/callback',
scopes: (process.env[`${envPrefix}SCOPES`] || 'offline_access User.Read Mail.Read').split(' '),
scopes: (process.env[`${envPrefix}SCOPES`] || 'offline_access User.Read Mail.Read Mail.Send Calendars.Read Calendars.ReadWrite Contacts.Read Tasks.ReadWrite').split(' '),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Restore the existing mail-write and OneDrive scopes here.

This fallback now omits Mail.ReadWrite, Files.Read, and Files.ReadWrite, while config.js Line 23 still expects them. Any install that relies on the default MS_SCOPES value will authenticate successfully but then hit 403s on mail-write and OneDrive tools.

Suggested fix
-    scopes: (process.env[`${envPrefix}SCOPES`] || 'offline_access User.Read Mail.Read Mail.Send Calendars.Read Calendars.ReadWrite Contacts.Read Tasks.ReadWrite').split(' '),
+    scopes: (process.env[`${envPrefix}SCOPES`] || 'offline_access User.Read Mail.Read Mail.ReadWrite Mail.Send Calendars.Read Calendars.ReadWrite Files.Read Files.ReadWrite Contacts.Read Tasks.ReadWrite').trim().split(/\s+/),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
scopes: (process.env[`${envPrefix}SCOPES`] || 'offline_access User.Read Mail.Read Mail.Send Calendars.Read Calendars.ReadWrite Contacts.Read Tasks.ReadWrite').split(' '),
scopes: (process.env[`${envPrefix}SCOPES`] || 'offline_access User.Read Mail.Read Mail.ReadWrite Mail.Send Calendars.Read Calendars.ReadWrite Files.Read Files.ReadWrite Contacts.Read Tasks.ReadWrite').trim().split(/\s+/),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@auth/oauth-server.js` at line 62, The fallback scope list in oauth-server.js
is missing the existing mail-write and OneDrive permissions, so update the
scopes default used by the oauth server configuration to include Mail.ReadWrite,
Files.Read, and Files.ReadWrite alongside the current scopes. Keep the change in
the scopes initialization that reads process.env[`${envPrefix}SCOPES`] so the
default MS_SCOPES value still matches what config.js expects.

Comment thread auth/token-storage.js
clientSecret,
redirectUri: process.env.MS_REDIRECT_URI || 'http://localhost:3333/auth/callback',
scopes: (process.env.MS_SCOPES || 'offline_access User.Read Mail.Read').split(' '),
scopes: (process.env.MS_SCOPES || 'offline_access User.Read Mail.Read Mail.Send Calendars.Read Calendars.ReadWrite Contacts.Read Tasks.ReadWrite').split(' '),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Keep TokenStorage's default scopes aligned with the rest of the auth flow.

This fallback dropped Mail.ReadWrite, Files.Read, and Files.ReadWrite, but both exchangeCodeForTokens() and refreshAccessToken() reuse this.config.scopes. With MS_SCOPES unset, refreshed tokens can lose existing mail-write/OneDrive permissions relative to config.js Line 23.

Suggested fix
-      scopes: (process.env.MS_SCOPES || 'offline_access User.Read Mail.Read Mail.Send Calendars.Read Calendars.ReadWrite Contacts.Read Tasks.ReadWrite').split(' '),
+      scopes: (process.env.MS_SCOPES || 'offline_access User.Read Mail.Read Mail.ReadWrite Mail.Send Calendars.Read Calendars.ReadWrite Files.Read Files.ReadWrite Contacts.Read Tasks.ReadWrite').trim().split(/\s+/),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
scopes: (process.env.MS_SCOPES || 'offline_access User.Read Mail.Read Mail.Send Calendars.Read Calendars.ReadWrite Contacts.Read Tasks.ReadWrite').split(' '),
scopes: (process.env.MS_SCOPES || 'offline_access User.Read Mail.Read Mail.ReadWrite Mail.Send Calendars.Read Calendars.ReadWrite Files.Read Files.ReadWrite Contacts.Read Tasks.ReadWrite').trim().split(/\s+/),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@auth/token-storage.js` at line 20, `TokenStorage` is missing several default
OAuth scopes from the rest of the auth flow, so its fallback no longer matches
`config.js` and can drop permissions during `exchangeCodeForTokens()` and
`refreshAccessToken()`. Update the default `scopes` value in `TokenStorage` to
include the same full set used by the auth configuration, specifically restoring
the mail-write and OneDrive scopes so `this.config.scopes` stays aligned when
`MS_SCOPES` is unset.

Comment thread auth/tools.js
Comment on lines +5 to +7
const TokenStorage = require('./token-storage');

const tokenStorage = new TokenStorage();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Keep the test-mode dependency until that branch is migrated.

Lines 5-7 remove tokenManager, but handleAuthenticate() still calls tokenManager.createTestTokens() on Line 33. With USE_TEST_MODE=true, this throws immediately instead of returning the mocked auth result.

Suggested fix
+const tokenManager = require('./token-manager');
 const TokenStorage = require('./token-storage');
 
 const tokenStorage = new TokenStorage();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const TokenStorage = require('./token-storage');
const tokenStorage = new TokenStorage();
const tokenManager = require('./token-manager');
const TokenStorage = require('./token-storage');
const tokenStorage = new TokenStorage();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@auth/tools.js` around lines 5 - 7, The test-mode token dependency was removed
too early, but handleAuthenticate() still relies on
tokenManager.createTestTokens() when USE_TEST_MODE is enabled. Restore or
preserve the tokenManager dependency in auth/tools.js until that test-mode
branch is fully migrated, and ensure handleAuthenticate() can still return the
mocked auth result without throwing.

Comment thread todo/index.js
Comment on lines +14 to +17
count: {
type: 'number',
description: 'Number of task lists to retrieve (default: 10, max: 50)'
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Constrain count to positive integers.

Both schemas currently allow values like -1 and 1.5, but the handlers pass count straight into Graph’s $top. Tighten the tool contract to type: 'integer' with a positive minimum and the configured max so clients don’t generate invalid requests.

Also applies to: 33-36

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@todo/index.js` around lines 14 - 17, The `count` schema in the task-list tool
contract is too permissive and should only allow positive integers. Update the
relevant schema definitions in `todo/index.js` for both occurrences of `count`
(including the other block referenced in the comment) to use `type: 'integer'`
with a minimum of 1 and the existing configured maximum, so the handlers that
pass `count` into Graph’s `$top` only receive valid values.

Comment thread todo/lists.js
Comment on lines +54 to +59
if (error.message === 'UNAUTHORIZED') {
return {
content: [{
type: 'text',
text: 'Microsoft To Do access is not authorized for the current token. Re-authenticate after granting Tasks.ReadWrite.'
}]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

For Microsoft Graph To Do endpoints, what HTTP status is returned when the token is valid but missing the required Tasks.ReadWrite permission?

💡 Result:

When a request is made to a Microsoft Graph API endpoint with a valid access token that lacks the required permissions (such as Tasks.ReadWrite), the API returns an HTTP 403 Forbidden status code [1][2][3]. In this scenario, the response body typically includes a JSON object with an error code, such as Authorization_RequestDenied, and a message stating that there are insufficient privileges to complete the operation [4]. While a 403 Forbidden status is the standard response for insufficient permissions, developers should be aware that some scenarios—such as misconfigured tokens, incorrect API endpoints, or issues with token scopes—may occasionally result in a 401 Unauthorized status [5][6][7]. However, when the token is technically valid but simply lacks the specific permission scope required for the requested action, 403 Forbidden is the expected HTTP response [1][5].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== files ==\n'
git ls-files 'todo/*' 'utils/*' | sort

printf '\n== outline: todo/lists.js ==\n'
ast-grep outline todo/lists.js --view expanded || true

printf '\n== outline: todo/tasks.js ==\n'
ast-grep outline todo/tasks.js --view expanded || true

printf '\n== outline: utils/graph-api.js ==\n'
ast-grep outline utils/graph-api.js --view expanded || true

printf '\n== relevant snippets ==\n'
sed -n '1,220p' todo/lists.js | cat -n
printf '\n--- todo/tasks.js ---\n'
sed -n '1,220p' todo/tasks.js | cat -n
printf '\n--- utils/graph-api.js ---\n'
sed -n '1,240p' utils/graph-api.js | cat -n

Repository: ryaker/outlook-mcp

Length of output: 17207


Handle Graph 403 permission failures here too. Graph returns 403 for missing Tasks.ReadWrite, but utils/graph-api.js only maps 401 to UNAUTHORIZED, so this falls through to the generic error message instead of the re-auth guidance. Handle 403 here (and in todo/tasks.js) or normalize it in utils/graph-api.js.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@todo/lists.js` around lines 54 - 59, The To Do error handling only recognizes
the UNAUTHORIZED case, so Graph 403 permission failures still fall through to
the generic message. Update the response handling in lists.js and the matching
path in tasks.js to also treat 403/missing Tasks.ReadWrite as the re-auth case,
or normalize that status in graph-api.js so the existing UNAUTHORIZED branch
covers it. Use the existing error-mapping flow around the list/tasks handlers to
keep the authorization guidance consistent.

Comment thread todo/tasks.js
Comment on lines +13 to +23
const raw = dateTimeValue.dateTime;
const zone = dateTimeValue.timeZone;
const hasOffset = /[zZ]$|[+\-]\d{2}:\d{2}$/.test(raw);
const iso = hasOffset ? raw : `${raw}Z`;
const parsed = new Date(iso);

if (!Number.isNaN(parsed.getTime())) {
return parsed.toLocaleString();
}

return zone ? `${raw} (${zone})` : raw;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

In Microsoft Graph dateTimeTimeZoneobjects, does thedateTimefield represent local wall time in the accompanyingtimeZone when no offset is present?

💡 Result:

Yes, in Microsoft Graph dateTimeTimeZone objects, the dateTime field represents the local wall time in the specified timeZone when no offset is provided [1][2][3]. The dateTime property stores a date and time string in a combined format (e.g., YYYY-MM-DDTHH:MM:SS.sssssss) [1]. Because it does not contain offset information (such as 'Z' or a UTC offset like '+05:00'), it is considered a "floating" or "unspecified" time [3][4]. The accompanying timeZone property acts as the context, telling the application how to interpret that wall time [1][5]. When working with these objects, it is important to treat the dateTime value as a local time representation rather than a UTC timestamp [5]. If you interpret it as UTC and attempt to adjust it, you will likely introduce errors [4]. To correctly use this data, you should parse the dateTime string and apply the rules (including Daylight Saving Time adjustments) of the specified timeZone [5]. For developers using the Microsoft Graph SDK for.NET, the DateTimeTimeZone class includes methods and extensions to help handle these conversions, such as ToDateTimeOffset(), which appropriately accounts for the timeZone property to resolve the time to a specific point in time [4].

Citations:


Don’t treat Graph wall times as UTC. dateTime is the local time in timeZone; appending Z shifts non-UTC values before formatting, so due/completed times can render incorrectly. Parse only offset-aware values, or convert using the supplied zone.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@todo/tasks.js` around lines 13 - 23, The date formatting logic in the
`raw/zone/hasOffset/iso/parsed` block is incorrectly treating Graph wall times
as UTC by appending “Z” to zone-local `dateTime` values. Update the parsing in
this helper so only offset-aware strings are passed through as-is, and for
`dateTimeValue.dateTime` with `dateTimeValue.timeZone` use the supplied zone to
convert before calling `toLocaleString`, preserving the local wall time instead
of shifting it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant