diff --git a/Directory.Build.props b/Directory.Build.props
index a76fdc6..e9bbf38 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -1,5 +1,5 @@
- 1.3.3
+ 1.3.4
diff --git a/k8s/deployment.yaml b/k8s/deployment.yaml
index 54ab8f1..d147fd8 100644
--- a/k8s/deployment.yaml
+++ b/k8s/deployment.yaml
@@ -28,7 +28,7 @@ spec:
fsGroup: 1000
containers:
- name: calendar-mcp
- image: docker.io/rockylhotka/calendar-mcp-http:latest
+ image: docker.io/rockylhotka/calendar-mcp-http:1.3.4
ports:
- containerPort: 8080
name: http
diff --git a/src/CalendarMcp.Core/Skills/calendar.md b/src/CalendarMcp.Core/Skills/calendar.md
index c16c0f2..8a09eb0 100644
--- a/src/CalendarMcp.Core/Skills/calendar.md
+++ b/src/CalendarMcp.Core/Skills/calendar.md
@@ -33,8 +33,9 @@ used.
output and how `startDate`/`endDate` are interpreted.
- `startDate` defaults to today (in `timeZone`); `endDate` defaults to
7 days after `startDate`.
-- `accountId` is required unless `calendarId` uniquely identifies one
- account (the server resolves it).
+- `accountId` fans out across all enabled accounts when omitted (like
+ `list_calendars`). Provide it to scope to one account, or provide
+ `calendarId` alone to resolve the account when it uniquely identifies one.
- `count` is per-account.
Returns events sorted by start time, each with `id, accountId,
diff --git a/src/CalendarMcp.Core/Tools/GetCalendarEventsTool.cs b/src/CalendarMcp.Core/Tools/GetCalendarEventsTool.cs
index 150c89d..53b8122 100644
--- a/src/CalendarMcp.Core/Tools/GetCalendarEventsTool.cs
+++ b/src/CalendarMcp.Core/Tools/GetCalendarEventsTool.cs
@@ -18,12 +18,12 @@ public sealed class GetCalendarEventsTool(
IProviderServiceFactory providerFactory,
ILogger logger)
{
- [McpServerTool, Description("Get calendar events for a date range from a specific account. The timeZone parameter is required. The accountId parameter is required unless calendarId uniquely identifies a calendar across all accounts, in which case the account is resolved automatically. Returns events sorted by start time, each with: id, accountId, calendarId, subject, start/end in both UTC and local time, timezone, location, attendees, isAllDay, organizer. Use the returned accountId and id when calling delete_event, respond_to_event, or get_calendar_event_details.")]
+ [McpServerTool, Description("Get calendar events for a date range from one or all accounts. The timeZone parameter is required. Omit accountId (and calendarId) to query all enabled accounts at once; provide accountId to scope to one account, or provide calendarId alone to resolve the account automatically when it uniquely identifies a single account. Returns events sorted by start time, each with: id, accountId, calendarId, subject, start/end in both UTC and local time, timezone, location, attendees, isAllDay, organizer. Use the returned accountId and id when calling delete_event, respond_to_event, or get_calendar_event_details.")]
public async Task GetCalendarEvents(
[Description("IANA timezone name for displaying event times (e.g. `America/Chicago`, `America/New_York`, `Europe/London`, `Asia/Tokyo`). All event times are returned in both UTC and this local timezone. Required.")] string timeZone,
[Description("Start of the date range (ISO 8601 format, e.g. `2026-02-20`). Defaults to today.")] DateTime? startDate = null,
[Description("End of the date range, inclusive (ISO 8601 format, e.g. `2026-02-27`). Defaults to 7 days after startDate.")] DateTime? endDate = null,
- [Description("Account ID to query. Obtain from list_accounts. Required unless calendarId uniquely identifies a single account.")] string? accountId = null,
+ [Description("Account ID to query, or omit to query all enabled accounts. Obtain from list_accounts.")] string? accountId = null,
[Description("Calendar ID to query, or omit for all calendars. Obtain from list_calendars. If accountId is omitted, calendarId is used to identify the account automatically when it exists in exactly one account.")] string? calendarId = null,
[Description("Maximum number of events to return per account (default 50)")] int count = 50)
{
@@ -31,11 +31,17 @@ public async Task GetCalendarEvents(
if (tz == null)
throw new McpException($"Invalid IANA timezone: '{timeZone}'. Use a valid IANA timezone name such as 'America/Chicago', 'Europe/London', or 'Asia/Tokyo'.");
- if (string.IsNullOrEmpty(accountId))
+ // Determine which accounts to query (mirrors search_emails / list_calendars):
+ // - accountId provided -> that single account
+ // - calendarId provided alone -> resolve the owning account automatically
+ // - neither provided -> all enabled accounts
+ List validAccounts;
+ if (!string.IsNullOrEmpty(accountId))
+ {
+ validAccounts = new List { await ToolGuard.RequireAccountAsync(accountRegistry, accountId) };
+ }
+ else if (!string.IsNullOrEmpty(calendarId))
{
- if (string.IsNullOrEmpty(calendarId))
- throw new McpException("accountId is required");
-
// calendarId provided but no accountId — try to resolve the account automatically
try
{
@@ -72,19 +78,25 @@ public async Task GetCalendarEvents(
logger.LogError(ex, "Error resolving accountId from calendarId {CalendarId}", calendarId);
throw new McpException("Failed to resolve account from calendarId.", ex);
}
+
+ validAccounts = new List { await ToolGuard.RequireAccountAsync(accountRegistry, accountId) };
+ }
+ else
+ {
+ // Neither accountId nor calendarId supplied — query all enabled accounts.
+ validAccounts = accountRegistry.GetEnabledAccounts().ToList();
+ if (validAccounts.Count == 0)
+ throw new McpException("No accounts found");
}
var resolvedStart = startDate ?? TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, tz).Date;
var resolvedEnd = endDate.HasValue ? endDate.Value.Date.AddDays(1) : resolvedStart.AddDays(7);
- logger.LogInformation("Getting calendar events: startDate={StartDate}, endDate={EndDate}, accountId={AccountId}, count={Count}, timeZone={TimeZone}",
- resolvedStart, resolvedEnd, accountId, count, timeZone);
+ logger.LogInformation("Getting calendar events: startDate={StartDate}, endDate={EndDate}, accountCount={AccountCount}, count={Count}, timeZone={TimeZone}",
+ resolvedStart, resolvedEnd, validAccounts.Count, count, timeZone);
try
{
- var account = await ToolGuard.RequireAccountAsync(accountRegistry, accountId);
- var validAccounts = new List { account };
-
// Query all accounts in parallel
var warnings = new List