From d0ce1061fe49993d9c2ca2024e39044006f4d74b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 3 Nov 2025 02:12:50 +0000 Subject: [PATCH 1/3] Initial plan From 73dcedc9b50b8e8be095c3aaf4e843a4a9b5531d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 3 Nov 2025 02:24:05 +0000 Subject: [PATCH 2/3] Improve calendar date context handling for LLMs - Enhanced calendar_current_context tool with comprehensive documentation explaining WHEN and WHY to use it - Improved context output formatting with clear sections and visual separators - Added more helpful date anchors (yesterday, day after tomorrow, this weekend, in 2 weeks) - Provided concrete examples showing how to use ISO dates from context in queries - Strengthened context guard messages to be more actionable and explanatory - Updated tool descriptions to emphasize prerequisite of calling calendar_current_context first - Added warnings and best practice notes to get_events, create_event, and create_task tools - Updated test expectations to match improved error messages Co-authored-by: jck411 <81551487+jck411@users.noreply.github.com> --- src/backend/mcp_servers/calendar_server.py | 81 +++++++++++++++++----- src/backend/services/time_context.py | 27 ++++++-- tests/test_calendar_server.py | 3 +- 3 files changed, 85 insertions(+), 26 deletions(-) diff --git a/src/backend/mcp_servers/calendar_server.py b/src/backend/mcp_servers/calendar_server.py index 50e3a4d..5280d2d 100644 --- a/src/backend/mcp_servers/calendar_server.py +++ b/src/backend/mcp_servers/calendar_server.py @@ -180,8 +180,12 @@ def _require_recent_context() -> Optional[str]: return None return ( - "Confirm the current date and time using calendar_current_context " - "before requesting date-based calendar operations." + "⚠️ REQUIRED: Call calendar_current_context first to get the current date and time.\n\n" + "Why? Without knowing what day it is RIGHT NOW, any calendar operation using " + "relative dates (today, tomorrow, next week) will use stale information and " + "produce incorrect results.\n\n" + "Action: Call calendar_current_context() now, then retry this operation with " + "the accurate date information it provides." ) @@ -630,15 +634,41 @@ async def generate_auth_url( @mcp.tool("calendar_current_context") async def calendar_current_context(timezone: Optional[str] = None) -> str: - """Report the up-to-date calendar context and record that it was checked.""" + """Get the current date and time context for calendar operations. + + IMPORTANT: Always call this tool FIRST before any calendar operations that involve + dates or times. This ensures you have accurate information about: + - What day it is today + - What day of the week it is + - What dates correspond to "tomorrow", "next week", etc. + + Without this context, you may use outdated date information, leading to incorrect + calendar queries or event creation. Call this tool: + - At the start of any conversation involving calendar or dates + - Before searching for events with relative dates (today, tomorrow, next week) + - Before creating or updating events + - When the user asks "what's on my schedule" or similar time-based queries + + The context remains valid for 5 minutes, after which you should refresh it. + """ snapshot = create_time_snapshot(timezone) _mark_context_checked(snapshot.now_utc) lines = list(build_context_lines(snapshot)) + lines.append("") + lines.append("✓ Context refreshed and valid for the next 5 minutes.") + lines.append("") + lines.append( + "IMPORTANT: Use the exact ISO dates shown above (YYYY-MM-DD format) when " + "constructing calendar queries. For example:" + ) + lines.append(f"- To find today's events: use time_min='{snapshot.date.isoformat()}'") + lines.append( + f"- To find tomorrow's events: use time_min='{(snapshot.date + datetime.timedelta(days=1)).isoformat()}'" + ) lines.append( - "Use these values when preparing time ranges, and re-run this tool if " - "your reasoning depends on the current date." + "- For date ranges, use the 'Upcoming anchors' shown above as reference points." ) return "\n".join(lines) @@ -655,25 +685,29 @@ async def get_events( detailed: bool = False, ) -> str: """ - Retrieve events across the user's Google calendars. + Retrieve events from Google Calendar. - Always call ``calendar_current_context`` first so the LLM has an accurate - notion of "today". With no ``calendar_id`` (or when using phrases such as - "my schedule") the search spans the preconfigured household calendars. - Provide a specific ID or friendly name (for example "Family Calendar" or - "Dad Work Schedule") to narrow the query to a single calendar. + ⚠️ PREREQUISITE: You MUST call calendar_current_context() first, before using + this tool. This ensures you have accurate date information for keywords like + "today", "tomorrow", etc. + + This tool searches across the user's calendars (or a specific calendar if specified). + With no calendar_id (or when using phrases like "my schedule") the search spans + all preconfigured household calendars. Provide a specific ID or friendly name + (e.g., "Family Calendar", "Dad Work Schedule") to narrow the query. Args: user_email: The user's email address (defaults to Jack's primary account). calendar_id: Optional calendar ID or friendly name. - time_min: Start time (ISO format or keywords like "today"). + time_min: Start time (ISO format YYYY-MM-DD or keywords like "today"). + Use exact dates from calendar_current_context output. time_max: End time (optional, ISO format or keywords). max_results: Maximum number of events to return after aggregation. - query: Optional search query. - detailed: Whether to include full details in results. + query: Optional search query for event titles/descriptions. + detailed: Whether to include full event details in results. Returns: - Formatted string with event details. + Formatted string with event details, or an error if context is stale. """ try: @@ -927,12 +961,17 @@ async def create_event( """ Create a new calendar event. + 💡 BEST PRACTICE: Call calendar_current_context() first to get accurate dates + when using relative terms like "today" or "tomorrow". + Args: user_email: The user's email address summary: Event title/summary - start_time: Start time (RFC3339 format or YYYY-MM-DD for all-day) - end_time: End time (RFC3339 format or YYYY-MM-DD for all-day) - calendar_id: Calendar ID (default: 'primary') + start_time: Start time - use ISO format (YYYY-MM-DD for all-day events, + YYYY-MM-DDTHH:MM:SS for timed events). Get exact dates from + calendar_current_context when using relative dates. + end_time: End time (same format as start_time) + calendar_id: Calendar ID or friendly name (default: 'primary') description: Optional event description location: Optional event location attendees: Optional list of attendee email addresses @@ -1797,12 +1836,16 @@ async def create_task( """ Create a new Google Task. + ⚠️ PREREQUISITE: If setting a due date, call calendar_current_context() first + to ensure accurate date interpretation (e.g., what "today" or "tomorrow" means). + Args: user_email: The user's email address. task_list_id: Task list identifier (default: '@default'). title: Title for the new task. notes: Optional detailed notes. - due: Optional due date/time (keywords supported). + due: Optional due date/time. Use ISO format (YYYY-MM-DD) or keywords + (today, tomorrow). Get exact dates from calendar_current_context. parent: Optional parent task ID for subtasks. previous: Optional sibling task ID for positioning. diff --git a/src/backend/services/time_context.py b/src/backend/services/time_context.py index f6f0474..58465e4 100644 --- a/src/backend/services/time_context.py +++ b/src/backend/services/time_context.py @@ -114,27 +114,42 @@ def build_context_lines( include_week: bool = True, upcoming_anchors: Sequence[tuple[str, _dt.timedelta]] = ( ("Tomorrow", _dt.timedelta(days=1)), + ("Day after tomorrow", _dt.timedelta(days=2)), ("In 3 days", _dt.timedelta(days=3)), - ("Next week", _dt.timedelta(weeks=1)), + ("This weekend", _dt.timedelta(days=5)), + ("Next week (7 days)", _dt.timedelta(weeks=1)), + ("In 2 weeks", _dt.timedelta(weeks=2)), ), ) -> Iterable[str]: """Yield human-readable context lines for ``snapshot``.""" today_local = snapshot.date - - yield f"Current date: {today_local.isoformat()} ({snapshot.now_local.strftime('%A')})" + yesterday = today_local - _dt.timedelta(days=1) + + yield "=" * 60 + yield "CURRENT DATE AND TIME CONTEXT" + yield "=" * 60 + yield "" + yield f"Today: {today_local.isoformat()} ({snapshot.now_local.strftime('%A')})" + yield f"Yesterday: {yesterday.isoformat()} ({yesterday.strftime('%A')})" yield f"Current time: {snapshot.format_time()}" yield f"Timezone: {snapshot.timezone_display()}" yield f"ISO timestamp (local): {snapshot.iso_local}" yield f"ISO timestamp (UTC): {snapshot.iso_utc}" + yield "" if include_week: start_of_week = today_local - _dt.timedelta(days=today_local.weekday()) end_of_week = start_of_week + _dt.timedelta(days=6) - yield f"Week range: {start_of_week.isoformat()} → {end_of_week.isoformat()}" + yield f"Current week: {start_of_week.isoformat()} → {end_of_week.isoformat()}" + yield f" (Monday to Sunday)" + yield "" if upcoming_anchors: - yield "Upcoming anchors:" + yield "Upcoming date anchors (for calendar queries):" for label, delta in upcoming_anchors: anchor = today_local + delta - yield f"- {label}: {anchor.isoformat()} ({anchor.strftime('%A')})" + yield f" • {label}: {anchor.isoformat()} ({anchor.strftime('%A')})" + yield "" + + yield "=" * 60 diff --git a/tests/test_calendar_server.py b/tests/test_calendar_server.py index 09d97fd..ece1cfe 100644 --- a/tests/test_calendar_server.py +++ b/tests/test_calendar_server.py @@ -248,7 +248,8 @@ async def test_get_events_requires_context_gate(): result = await get_events(user_email="test@example.com") - assert "Confirm the current date and time" in result + assert "Call calendar_current_context first" in result + assert "REQUIRED" in result @pytest.mark.asyncio From 5ebd34b642fd842b5f4f891388fcea660268480b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 3 Nov 2025 02:27:19 +0000 Subject: [PATCH 3/3] Address code review feedback - Fix datetime import usage to use 'dt' alias consistently - Calculate next weekend (Saturday) dynamically based on current day instead of fixed offset - Remove trailing whitespace Co-authored-by: jck411 <81551487+jck411@users.noreply.github.com> --- src/backend/mcp_servers/calendar_server.py | 2 +- src/backend/services/time_context.py | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/backend/mcp_servers/calendar_server.py b/src/backend/mcp_servers/calendar_server.py index 5280d2d..484a689 100644 --- a/src/backend/mcp_servers/calendar_server.py +++ b/src/backend/mcp_servers/calendar_server.py @@ -665,7 +665,7 @@ async def calendar_current_context(timezone: Optional[str] = None) -> str: ) lines.append(f"- To find today's events: use time_min='{snapshot.date.isoformat()}'") lines.append( - f"- To find tomorrow's events: use time_min='{(snapshot.date + datetime.timedelta(days=1)).isoformat()}'" + f"- To find tomorrow's events: use time_min='{(snapshot.date + dt.timedelta(days=1)).isoformat()}'" ) lines.append( "- For date ranges, use the 'Upcoming anchors' shown above as reference points." diff --git a/src/backend/services/time_context.py b/src/backend/services/time_context.py index 58465e4..ceb2ea9 100644 --- a/src/backend/services/time_context.py +++ b/src/backend/services/time_context.py @@ -116,7 +116,6 @@ def build_context_lines( ("Tomorrow", _dt.timedelta(days=1)), ("Day after tomorrow", _dt.timedelta(days=2)), ("In 3 days", _dt.timedelta(days=3)), - ("This weekend", _dt.timedelta(days=5)), ("Next week (7 days)", _dt.timedelta(weeks=1)), ("In 2 weeks", _dt.timedelta(weeks=2)), ), @@ -150,6 +149,17 @@ def build_context_lines( for label, delta in upcoming_anchors: anchor = today_local + delta yield f" • {label}: {anchor.isoformat()} ({anchor.strftime('%A')})" + + # Add next Saturday/Sunday dynamically + # weekday(): Monday=0, Tuesday=1, ..., Saturday=5, Sunday=6 + days_until_saturday = (5 - today_local.weekday()) % 7 + if days_until_saturday == 0: + # Today is Saturday, show next Saturday + days_until_saturday = 7 + next_saturday = today_local + _dt.timedelta(days=days_until_saturday) + next_sunday = next_saturday + _dt.timedelta(days=1) + + yield f" • Next weekend: {next_saturday.isoformat()} (Saturday)" yield "" - + yield "=" * 60