Skip to content

feat(outlook): add Microsoft Graph calendar operations - #6041

Open
mzxchandra wants to merge 14 commits into
stagingfrom
feat/outlook-calendar
Open

feat(outlook): add Microsoft Graph calendar operations#6041
mzxchandra wants to merge 14 commits into
stagingfrom
feat/outlook-calendar

Conversation

@mzxchandra

Copy link
Copy Markdown
Contributor

What

Extends the existing Outlook integration with Microsoft Graph Calendar support, so a user can build an email + calendar triage workflow entirely on the Microsoft/Graph stack. Reuses the existing outlook OAuth provider - no new OAuth app, no separate provider. Mail operations are untouched and backward compatible.

Changes

  • OAuth scopes (lib/oauth/oauth.ts): add delegated Calendars.ReadWrite + MailboxSettings.Read to the shared outlook service. auth.ts inherits them via getCanonicalScopesForProvider. Scope descriptions + test assertions added. A code comment notes existing connected users must reconnect (Microsoft only grants added scopes on re-consent).
  • 6 calendar tools (tools/outlook/calendar_*.ts): list_events (calendarView with @odata.nextLink paging), get_event, create_event, update_event (partial PATCH), delete_event, respond (accept/tentativelyAccept/decline). Shared calendar-utils.ts handles Graph's offset-less dateTime+timeZone shape, attendee normalization, and event flattening (id, subject, start, end, organizer, attendees, isAllDay, onlineMeeting, webLink, bodyPreview). Every tool carries the Microsoft Graph error extractor and 429/backoff retry (honors Retry-After) for the per-mailbox concurrency limit.
  • Block manifest (blocks/blocks/outlook.ts): 6 calendar operations in the dropdown with conditional subBlocks, tools.access, switch, inputs, and event-shaped outputs.
  • Registered in tools/registry.ts and the outlook barrel.

Testing

  • Unit: calendar-utils.test.ts (datetime offset/naive/all-day, attendee normalize, event flatten) + calendar-tools.test.ts (URL/body builders, respond sendResponse/comment rule, retry config). Plus oauth scope assertions. 102 tests pass.
  • Typecheck (my files: 0 errors), biome, and check:api-validation all clean.
  • E2E against a live personal Outlook mailbox: create → list → get → update → delete → respond all verified, plus datetime correctness, list paging, attendees flatten, mail backward-compat, and 404 error path.

Known limitations (Microsoft platform, not implementation)

  • Online-meeting joinUrl is work/school (Teams) only. Post-Skype-retirement there's no consumer online-meeting provider, so joinUrl is null on personal accounts. The tool sets isOnlineMeeting: true and lets Graph pick the mailbox default (Teams on work accounts) rather than hardcoding a provider.
  • Find Meeting Times was intentionally not shipped. findMeetingTimes is unsupported for personal accounts, and on work accounts requires Calendars.ReadWrite.Shared - a work-only scope that would break personal-account consent on the shared provider. It can't function on a provider that must serve both account types, so it's excluded.

🤖 Generated with Claude Code

Extend the shared outlook OAuth service with delegated Graph calendar scopes so
one Microsoft connection covers mail and calendar. Existing connected users must
reconnect to be granted the new scopes (noted in a code comment). Adds human-readable
scope descriptions and test assertions.
Six calendar operations against graph.microsoft.com/v1.0: list events (calendarView
with nextLink paging), get, create, update (partial PATCH), delete, and respond to
invites. Shared calendar-utils handles Graph's offset-less dateTime+timeZone shape,
attendee normalization, and event flattening. All tools carry the Microsoft Graph
error extractor and 429/backoff retry (honors Retry-After) for the mailbox concurrency
limit. Registered in the tool registry and barrel.
Add six calendar operations to the Outlook block operation dropdown with their
conditional subBlocks, tool wiring, inputs, and event-shaped outputs. Mail operations
are unchanged and backward compatible.
@vercel

vercel Bot commented Jul 29, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
docs Ready Ready Preview, Comment Jul 29, 2026 5:09pm

Request Review

@cursor

cursor Bot commented Jul 29, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Expands OAuth consent and enables create/update/delete/respond on user calendars via Microsoft Graph; mail paths are unchanged but users must reconnect for the new scope.

Overview
Adds Outlook calendar support on the existing outlook OAuth provider so workflows can manage events alongside mail, without a separate integration.

OAuth: Calendars.ReadWrite is added to the shared Outlook scopes (.Shared scopes are intentionally omitted for personal-account consent safety). Existing connections must re-consent for calendar tools to work.

Six new Graph tools — list (calendarView + nextLink paging with origin validation), get, create, update, delete, and respond to invites — share calendar-utils for Graph dateTime/timeZone shaping, all-day bounds, attendee normalization, flattened event outputs, 429/5xx retry, and create transactionId de-duplication on retries.

Workflow block & UI: The Outlook block gains six calendar operations, conditional subBlocks (including an outlook.calendars picker backed by a new GET /api/tools/outlook/calendars route), param mapping, and calendar-shaped outputs. Docs, integrations.json, registry wiring, block/tool unit tests, and API route-count baseline are updated accordingly.

Reviewed by Cursor Bugbot for commit bc55db2. Bugbot is set up for automated code reviews on this repo. Configure here.

Comment thread apps/sim/tools/outlook/calendar_list_events.ts Outdated
Comment thread apps/sim/tools/outlook/calendar-utils.ts Outdated
Comment thread apps/sim/blocks/blocks/outlook.ts Outdated
Comment thread apps/sim/lib/oauth/oauth.ts
@greptile-apps

greptile-apps Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Adds Microsoft Graph calendar operations to the existing Outlook integration (list/get/create/update/delete/respond), shared OAuth scopes, block UI, docs, and calendar selector API.

  • Reuses the outlook OAuth provider with Calendars.ReadWrite (no new provider).
  • Six calendar tools with Graph datetime/attendee helpers, paging guards, and 429 retry.
  • Outlook block operations, params mapping, calendars selector route, docs, and tests.

Confidence Score: 5/5

Safe to merge; prior review findings are addressed and no remaining blocking failures were identified.

Prior pageToken exfil, all-day zero-length range, unused MailboxSettings.Read scope, and unshipped meeting-times copy are fixed in the current HEAD; no blocking failure remains.

Important Files Changed

Filename Overview
apps/sim/tools/outlook/calendar_list_events.ts List events uses assertGraphNextPageUrl on pageToken before attaching the bearer token.
apps/sim/tools/outlook/calendar-utils.ts buildAllDayRange normalizes exclusive end-day for all-day create/update paths.
apps/sim/blocks/blocks/outlook.ts Calendar ops wired in the block; longDescription no longer claims unshipped meeting-time suggestions.
apps/sim/lib/oauth/oauth.ts Outlook scopes include Calendars.ReadWrite only; MailboxSettings.Read removed.
apps/sim/app/api/tools/outlook/calendars/route.ts Calendars selector drains Graph pages with nextLink origin validation and a page cap.

Reviews (9): Last reviewed commit: "fix(outlook): tighten date-only detectio..." | Re-trigger Greptile

Comment thread apps/sim/tools/outlook/calendar_create_event.ts Outdated
Comment thread apps/sim/tools/outlook/calendar_list_events.ts
- Validate list-events pageToken origin with assertGraphNextPageUrl (matches the
  onedrive/microsoft_ad Graph paging guard) so a workflow-supplied URL can't receive
  the Outlook bearer token.
- All-day create/update now normalize both bounds to midnight and force an exclusive
  end day (buildAllDayRange), instead of sending a zero-length same-midnight window
  that Graph rejects.
- Drop the stale 'suggest meeting times' claim from the block longDescription.
- Remove the unused MailboxSettings.Read scope (least privilege; no tool reads it).
@mzxchandra

Copy link
Copy Markdown
Contributor Author

@greptile

@mzxchandra

Copy link
Copy Markdown
Contributor Author

@cursor review

Comment thread apps/sim/tools/outlook/calendar_list_events.ts
Comment thread apps/sim/blocks/blocks/outlook.ts
Comment thread apps/sim/tools/outlook/calendar_respond.ts
Validation pass over the new Microsoft Graph calendar operations against the
v1.0 API docs, plus the calendar selection the tools were missing.

- Add an `outlook.calendars` picker (GET /me/calendars) with basic selector +
  advanced manual ID, wired through the `calendarId` canonical param. List and
  create now target `/me/calendars/{id}/...`; get/update/delete/respond keep
  using `/me/events/{id}` since event IDs are mailbox-unique.
- Fix all-day update rejecting when only one bound is supplied — both bounds are
  now normalized to midnight with an exclusive end day.
- Fix "Send Response to Organizer" reading as OFF while Graph's default is to
  notify; it is now a dropdown defaulting to Yes, and the param is always sent.
- Add timestamp wandConfig to the four calendar datetime fields and a list
  wandConfig to attendees.
- Centralize Graph URL construction in calendar-utils; guard maxResults against
  non-numeric input and trim the calendarView time-window bounds.
- Add three calendar templates and four calendar skills to OutlookBlockMeta.
- Regenerate integration docs.
onlineMeetingProvider is optional and defaults to unknown; the docs state that
setting isOnlineMeeting alone initializes onlineMeeting. They do not document
Graph substituting the calendar's defaultOnlineMeetingProvider, so the comments
now claim only that, plus the real reason not to pin teamsForBusiness (mailboxes
that disallow it via allowedOnlineMeetingProviders).
Cursor Bugbot round: startDateTime/endDateTime were required tool params, so a
paging call carrying only pageToken failed validateToolParameters before the
request was built — even though the url builder short-circuits on pageToken and
ignores both bounds. Relax them to optional and enforce the real invariant
(pageToken OR both bounds) in the url builder, matching
tools/sharepoint/list_sites.ts. Block subblocks stay required, so the normal
editor flow is unchanged.

Also correct the calendar_respond comment: Graph documents exactly two 400
conditions for accept/decline, both on proposedNewTime, which we never send.
A non-empty comment alongside sendResponse=false is valid.
@waleedlatif1

Copy link
Copy Markdown
Collaborator

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator

@cursor review

The calendar picker lists /me/calendars, which can include calendars other
users have shared with or delegated to the account. Calendars.ReadWrite covers
only the user's own calendars, so selecting a shared team calendar would 403 on
both read and write.

Calendars.ReadWrite is kept alongside it, not replaced: Graph documents it as
the sole accepted permission for creating and updating events and for
accept/tentativelyAccept/decline (all list "Higher: Not available"), so the
.Shared scope does not subsume it.

Added now rather than later because this PR already forces existing Outlook
users to re-consent for Calendars.ReadWrite; deferring would cost them a second
reconnect.
@waleedlatif1

Copy link
Copy Markdown
Collaborator

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator

@cursor review

Comment thread apps/sim/tools/outlook/calendar_update_event.ts
Comment thread apps/sim/blocks/blocks/outlook.ts
Comment thread apps/sim/tools/outlook/calendar_create_event.ts
…-day updates

Cursor round 2:

- Date-only bounds no longer produce a zero-length window. The param docs invited
  a date like 2025-06-03 for an all-day event, but buildAllDayRange only ran when
  isAllDay was explicitly true, so date-only input built a 00:00->00:00 timed
  window that Graph rejects. A date-only bound carries no time, so the only
  coherent reading is all-day; create/update now promote on that shape and the
  descriptions state it.
- Converting an event to all-day with no bounds now fails with an actionable
  message instead of a Graph 400. Graph requires all-day events to have midnight
  start and end in the same zone, and those cannot be derived from a partial
  PATCH against an event whose existing bounds are timed.
@waleedlatif1

Copy link
Copy Markdown
Collaborator

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator

@cursor review

Comment thread apps/sim/tools/outlook/calendar_update_event.ts
Regression from the previous round's date-only promotion. A single date-only
startDateTime or endDateTime satisfied "all provided bounds are date-only", so
the tool promoted to all-day and derived the missing side from the supplied one
— turning a partial reschedule of a timed or multi-day event into a one-day
all-day event and dropping the original other bound.

Implicit promotion now requires BOTH bounds to be date-only, matching
calendar_create_event. A lone date-only bound is ambiguous (convert to all-day,
or just move that edge?) and a PATCH cannot read the event's existing bounds to
disambiguate, so it stays on the timed path and leaves the other side untouched.
Deriving a missing bound remains allowed when isAllDay is set explicitly, since
that is stated intent rather than a guess.

Also pins the explicit-isAllDay-false + date-only override with a regression
test: the block always sends isAllDay for create, so an untouched switch arrives
as false, and the data shape has to win or the fix would be unreachable from the
UI.
@waleedlatif1

Copy link
Copy Markdown
Collaborator

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator

@cursor review

Comment thread apps/sim/lib/oauth/oauth.ts Outdated
…vider

Reverts the scope I added two rounds ago. It was the wrong call.

This provider is shared by work/school AND personal Outlook accounts, and the
.Shared calendar scopes are not confirmed supported for personal Microsoft
accounts. Requesting one risks failing consent for personal users — which would
take mail access down with it, breaking functionality that works today. The PR
already documents this exact reasoning as why findMeetingTimes was excluded, and
that decision was made against a live personal mailbox.

The evidence I added it on was a summarized read of the permissions reference
claiming MSA support; a targeted follow-up could not confirm it for
Calendars.ReadWrite.Shared specifically. Given the asymmetry — broken consent
for all personal users vs. a shared-calendar feature gap — least privilege wins.

Calendar operations therefore target calendars the account owns. The calendarId
param descriptions now say a calendar shared by another user may return 403, and
the scope list carries a comment explaining why .Shared must not be re-added.
@waleedlatif1

Copy link
Copy Markdown
Collaborator

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator

@cursor review

Comment thread apps/sim/tools/outlook/calendar-utils.ts
…onId

The retry config opts POSTs in via retryIdempotentOnly:false, but the executor's
isRetryableFailure covers 429 AND 500-599 — not just the throttle the comment
justified. A 5xx returned after Graph had already committed a create would be
retried and produce a duplicate calendar event.

create_event now sends a transactionId, which Graph documents for exactly this:
it discards a repeat POST carrying an id it has already seen. The request body is
built once per execution (formatRequestParams runs before the attempt loop), so
the id is stable across retries of a call and unique between calls.

The retry comment now describes what actually retries and why each non-idempotent
method is safe: PATCH replays the same partial body as a no-op, and respond is
state-idempotent though a post-commit retry can send the organizer a second
notification — accepted deliberately, since Graph exposes no transactionId for
accept/decline and failing outright under throttling is worse.
@waleedlatif1

Copy link
Copy Markdown
Collaborator

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator

@cursor review

@cursor cursor 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.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 2564def. Configure here.

Final validation pass over the calendar integration.

- isDateOnly matched "contains no T", so a space-separated datetime
  (2025-06-03 10:00) counted as date-only: the time was discarded and the value
  built as '2025-06-03 10:00T00:00:00', which Graph rejects. It now matches
  YYYY-MM-DD strictly, and buildGraphEventDateTime normalizes the space form to
  ISO rather than mangling it, so a natural input works instead of 400ing.
- The isOnlineMeeting param descriptions still claimed Graph 'uses the mailbox
  default provider' — the same unverified mechanism already removed from the code
  comments. They now state only what the docs and the author's live testing
  support: the join URL depends on the providers the mailbox allows, and stays
  null on personal accounts.
- Adds blocks/blocks/outlook.test.ts following the repo's per-block test
  convention: every calendar operation resolves to a registered tool in
  tools.access, supplies all required tool params, emits no params the tool
  cannot accept, maps one-to-one onto the calendar tools, and the calendarId
  canonical group and sendResponse default are pinned.
@waleedlatif1

Copy link
Copy Markdown
Collaborator

@greptile

@waleedlatif1

Copy link
Copy Markdown
Collaborator

@cursor review

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.

2 participants