Summary
get_calendar_events throws accountId is required when accountId (and calendarId) are omitted, even though accountId is declared optional in the tool schema and the analogous read tool search_emails handles the same case by querying all accounts. This is an inconsistency that reads as a bug: a caller following the schema (and the server's own prompt guidance) gets a hard error.
Impact
Observed downstream in a consuming agent: accountId is required on get_calendar_events was the single largest tool-call failure over a recent 14-day window (~30 occurrences, ~55% of that agent's structural failures). The model isn't misbehaving — it's treating accountId as optional, which the schema and a sibling tool both say it is.
Root cause
accountId is declared optional on the tool method, so the SDK-generated input schema does not mark it required:
// GetCalendarEventsTool.cs:26
[Description("Account ID to query. Obtain from list_accounts. Required unless calendarId uniquely identifies a single account.")] string? accountId = null,
The "required" is enforced only at runtime, by a throw:
// GetCalendarEventsTool.cs:34-37
if (string.IsNullOrEmpty(accountId))
{
if (string.IsNullOrEmpty(calendarId))
throw new McpException("accountId is required");
...
}
The sibling read tool does the opposite — omitting accountId queries all accounts:
// SearchEmailsTool.cs:36-41
if (string.IsNullOrEmpty(accountId))
{
validAccounts = (await accountRegistry.GetAllAccountsAsync()).ToList();
if (validAccounts.Count == 0)
throw new McpException("No accounts found");
}
get_calendar_events already has the same multi-account machinery (a validAccounts list queried via Task.WhenAll with per-account warning aggregation) — it's just artificially restricted to a single account and throws when none is supplied.
The server's own prompt also assumes the all-accounts behavior, contradicting the tool:
// CalendarPrompts.cs:22 (daily_briefing)
2. Call get_calendar_events with timeZone="...", startDate=today, endDate=today for each account (or omit accountId to query all at once).
Proposed fix
Make calendar read tools mirror search_emails: when accountId (and calendarId) are omitted, query all enabled calendar-capable accounts instead of throwing. For get_calendar_events this is a small change — set validAccounts to all enabled accounts in the empty-accountId branch rather than throwing.
Keep write / targeted tools (respond_to_event, create_event, move_email) requiring an explicit accountId — guessing an account for a write is unsafe, and the respond_to_invite prompt already enforces this ("defaulting to the first account often picks the wrong calendar").
Scope / audit
Related (separate, lower priority)
get_email_details and get_calendar_event_details read a specific item by an account-scoped id, so they legitimately require accountId. Upstream search_emails/get_calendar_events results already return accountId per item; the gap there is callers carrying that paired accountId forward (or an id-fallback search). Tracking here only for context — not part of this fix.
Summary
get_calendar_eventsthrowsaccountId is requiredwhenaccountId(andcalendarId) are omitted, even thoughaccountIdis declared optional in the tool schema and the analogous read toolsearch_emailshandles the same case by querying all accounts. This is an inconsistency that reads as a bug: a caller following the schema (and the server's own prompt guidance) gets a hard error.Impact
Observed downstream in a consuming agent:
accountId is requiredonget_calendar_eventswas the single largest tool-call failure over a recent 14-day window (~30 occurrences, ~55% of that agent's structural failures). The model isn't misbehaving — it's treatingaccountIdas optional, which the schema and a sibling tool both say it is.Root cause
accountIdis declared optional on the tool method, so the SDK-generated input schema does not mark it required:The "required" is enforced only at runtime, by a throw:
The sibling read tool does the opposite — omitting
accountIdqueries all accounts:get_calendar_eventsalready has the same multi-account machinery (avalidAccountslist queried viaTask.WhenAllwith per-account warning aggregation) — it's just artificially restricted to a single account and throws when none is supplied.The server's own prompt also assumes the all-accounts behavior, contradicting the tool:
Proposed fix
Make calendar read tools mirror
search_emails: whenaccountId(andcalendarId) are omitted, query all enabled calendar-capable accounts instead of throwing. Forget_calendar_eventsthis is a small change — setvalidAccountsto all enabled accounts in the empty-accountIdbranch rather than throwing.Keep write / targeted tools (
respond_to_event,create_event,move_email) requiring an explicitaccountId— guessing an account for a write is unsafe, and therespond_to_inviteprompt already enforces this ("defaulting to the first account often picks the wrong calendar").Scope / audit
get_calendar_events— default to all enabled accounts whenaccountId/calendarIdomitted (primary fix).get_emails,list_calendars,get_contextual_email_summary,get_contacts.accountId.Related (separate, lower priority)
get_email_detailsandget_calendar_event_detailsread a specific item by an account-scoped id, so they legitimately requireaccountId. Upstreamsearch_emails/get_calendar_eventsresults already returnaccountIdper item; the gap there is callers carrying that pairedaccountIdforward (or an id-fallback search). Tracking here only for context — not part of this fix.