feat(outlook): add Microsoft Graph calendar operations - #6041
feat(outlook): add Microsoft Graph calendar operations#6041mzxchandra wants to merge 14 commits into
Conversation
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.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
PR SummaryMedium Risk Overview OAuth: Six new Graph tools — list (calendarView + Workflow block & UI: The Outlook block gains six calendar operations, conditional subBlocks (including an Reviewed by Cursor Bugbot for commit bc55db2. Bugbot is set up for automated code reviews on this repo. Configure here. |
Greptile SummaryAdds 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.
Confidence Score: 5/5Safe 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.
|
| 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
- 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).
|
@cursor review |
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.
|
@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.
|
@cursor review |
…-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.
|
@cursor review |
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.
|
@cursor review |
…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.
|
@cursor review |
…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.
|
@cursor review |
There was a problem hiding this comment.
✅ 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.
|
@cursor review |
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
outlookOAuth provider - no new OAuth app, no separate provider. Mail operations are untouched and backward compatible.Changes
lib/oauth/oauth.ts): add delegatedCalendars.ReadWrite+MailboxSettings.Readto the sharedoutlookservice.auth.tsinherits them viagetCanonicalScopesForProvider. Scope descriptions + test assertions added. A code comment notes existing connected users must reconnect (Microsoft only grants added scopes on re-consent).tools/outlook/calendar_*.ts):list_events(calendarView with@odata.nextLinkpaging),get_event,create_event,update_event(partial PATCH),delete_event,respond(accept/tentativelyAccept/decline). Sharedcalendar-utils.tshandles Graph's offset-lessdateTime+timeZoneshape, 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 (honorsRetry-After) for the per-mailbox concurrency limit.blocks/blocks/outlook.ts): 6 calendar operations in the dropdown with conditional subBlocks,tools.access, switch,inputs, and event-shapedoutputs.tools/registry.tsand the outlook barrel.Testing
calendar-utils.test.ts(datetime offset/naive/all-day, attendee normalize, event flatten) +calendar-tools.test.ts(URL/body builders, respondsendResponse/comment rule, retry config). Plus oauth scope assertions. 102 tests pass.check:api-validationall clean.Known limitations (Microsoft platform, not implementation)
joinUrlis work/school (Teams) only. Post-Skype-retirement there's no consumer online-meeting provider, sojoinUrlis null on personal accounts. The tool setsisOnlineMeeting: trueand lets Graph pick the mailbox default (Teams on work accounts) rather than hardcoding a provider.findMeetingTimesis unsupported for personal accounts, and on work accounts requiresCalendars.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