From 4e2397cc583a25cb9342e9458e624f92fc220ec9 Mon Sep 17 00:00:00 2001 From: Billy Lui Date: Fri, 6 Mar 2026 16:35:18 +0800 Subject: [PATCH] Add calendar scheduling crew with Temporal Cortex MCP Co-Authored-By: Claude Opus 4.6 --- crews/calendar-scheduling/.env_example | 8 +++ crews/calendar-scheduling/.gitignore | 5 ++ crews/calendar-scheduling/README.md | 57 +++++++++++++++++++ crews/calendar-scheduling/agents.py | 64 ++++++++++++++++++++++ crews/calendar-scheduling/main.py | 76 ++++++++++++++++++++++++++ crews/calendar-scheduling/tasks.py | 53 ++++++++++++++++++ 6 files changed, 263 insertions(+) create mode 100644 crews/calendar-scheduling/.env_example create mode 100644 crews/calendar-scheduling/.gitignore create mode 100644 crews/calendar-scheduling/README.md create mode 100644 crews/calendar-scheduling/agents.py create mode 100644 crews/calendar-scheduling/main.py create mode 100644 crews/calendar-scheduling/tasks.py diff --git a/crews/calendar-scheduling/.env_example b/crews/calendar-scheduling/.env_example new file mode 100644 index 00000000..1982f5bd --- /dev/null +++ b/crews/calendar-scheduling/.env_example @@ -0,0 +1,8 @@ +OPENAI_API_KEY=your-openai-api-key + +# Required for Google Calendar (https://console.cloud.google.com/apis/credentials) +GOOGLE_CLIENT_ID=your-client-id.apps.googleusercontent.com +GOOGLE_CLIENT_SECRET=your-client-secret + +# Optional timezone override (auto-detected if not set) +# TIMEZONE=America/New_York diff --git a/crews/calendar-scheduling/.gitignore b/crews/calendar-scheduling/.gitignore new file mode 100644 index 00000000..4e551cd5 --- /dev/null +++ b/crews/calendar-scheduling/.gitignore @@ -0,0 +1,5 @@ +.env +.env.local +__pycache__/ +*.pyc +.DS_Store diff --git a/crews/calendar-scheduling/README.md b/crews/calendar-scheduling/README.md new file mode 100644 index 00000000..aa234822 --- /dev/null +++ b/crews/calendar-scheduling/README.md @@ -0,0 +1,57 @@ +# Calendar Scheduling Crew + +A multi-agent crew that schedules conflict-free calendar meetings using [Temporal Cortex](https://github.com/temporal-cortex/mcp), an MCP server that gives CrewAI agents deterministic calendar tools. + +## What It Does + +Three agents collaborate to schedule a meeting: + +1. **Temporal Analyst** — Gets the current time and resolves natural language like "next Tuesday at 2pm" into precise timestamps +2. **Calendar Manager** — Discovers connected calendars and finds available time slots +3. **Scheduling Coordinator** — Books the meeting using Two-Phase Commit (no double-bookings) + +The crew uses CrewAI's native `MCPServerAdapter` to auto-discover all 12 Temporal Cortex tools — no wrapper code needed. + +## Why Not Just Use a Calendar API Directly? + +LLMs score below 50% on temporal reasoning ([OOLONG benchmark](https://arxiv.org/abs/2511.02817)). They pick the wrong Tuesday, check the wrong timezone, and double-book your calendar. Temporal Cortex replaces LLM inference with deterministic computation for all calendar math — datetime resolution, cross-provider availability merging (Google + Outlook + CalDAV), and atomic booking. + +## Prerequisites + +- Python 3.10+ +- Node.js 18+ +- A Google Calendar, Microsoft Outlook, or CalDAV account + +## Setup + +1. Install dependencies: + ```bash + pip install crewai crewai-tools[mcp] python-dotenv + ``` + +2. Authenticate with your calendar provider: + ```bash + npx @temporal-cortex/cortex-mcp auth google + ``` + +3. Configure environment: + ```bash + cp .env_example .env + # Edit .env with your Google OAuth credentials + ``` + +4. Run: + ```bash + python main.py + ``` + +## Customization + +- **Change the meeting details**: Edit `main.py` to modify the meeting title, duration, or target time +- **Add more agents**: The [tool reference](https://github.com/temporal-cortex/mcp/blob/main/docs/tools.md) documents all 15 tools across 5 layers +- **Use Platform Mode**: Replace stdio with SSE transport to connect to the managed Temporal Cortex Platform (no Node.js required) — see `main.py` comments +- **Use DSL syntax**: For the simplest integration, use the `mcps` field directly on Agent instead of MCPServerAdapter + +## Cost + +This example uses your default LLM (typically GPT-4 or equivalent). A single crew run with 3 agents and 3 tasks costs approximately $0.05–0.15 in LLM API fees, depending on your model. The Temporal Cortex MCP server is free and runs locally. diff --git a/crews/calendar-scheduling/agents.py b/crews/calendar-scheduling/agents.py new file mode 100644 index 00000000..3098272a --- /dev/null +++ b/crews/calendar-scheduling/agents.py @@ -0,0 +1,64 @@ +from crewai import Agent + + +class CalendarAgents: + def temporal_analyst(self, tools): + return Agent( + role="Temporal Analyst", + backstory=( + "You are a time-awareness specialist. Before any calendar " + "operation, you call get_temporal_context to learn the current " + "time, timezone, UTC offset, and DST status. You convert " + "natural language like 'next Tuesday at 2pm' into exact " + "timestamps using resolve_datetime. You never guess dates " + "or times — you always use the tools." + ), + goal=( + "Establish temporal context by determining the current time, " + "timezone, and DST status, then resolve human datetime " + "expressions into precise RFC 3339 timestamps" + ), + tools=tools, + allow_delegation=False, + verbose=True, + ) + + def calendar_manager(self, tools): + return Agent( + role="Calendar Manager", + backstory=( + "You are a calendar operations specialist. You list calendars " + "to discover connected providers (Google, Outlook, CalDAV), " + "query events in time ranges, and find available slots using " + "find_free_slots. You always use provider-prefixed calendar " + "IDs (e.g., google/primary, outlook/work). You never assume " + "calendar IDs — you discover them first with list_calendars." + ), + goal=( + "Query connected calendars to find free time slots and " + "check availability across all providers" + ), + tools=tools, + allow_delegation=False, + verbose=True, + ) + + def scheduling_coordinator(self, tools): + return Agent( + role="Scheduling Coordinator", + backstory=( + "You are the lead scheduler. You take resolved timestamps " + "and available slots to select the best meeting time. You " + "use book_slot to create the event — this tool uses " + "Two-Phase Commit to acquire a lock, verify no conflicts " + "exist, write the event, then release the lock. You never " + "double-book." + ), + goal=( + "Book a conflict-free meeting using the available slots " + "and resolved timestamps from the other agents" + ), + tools=tools, + allow_delegation=False, + verbose=True, + ) diff --git a/crews/calendar-scheduling/main.py b/crews/calendar-scheduling/main.py new file mode 100644 index 00000000..f04943bc --- /dev/null +++ b/crews/calendar-scheduling/main.py @@ -0,0 +1,76 @@ +import os + +from crewai import Crew +from crewai_tools import MCPServerAdapter +from dotenv import load_dotenv + +from agents import CalendarAgents +from tasks import CalendarTasks + +load_dotenv() + +# --- Configuration --- +MEETING_TIME = "next Tuesday at 2pm" +MEETING_TITLE = "Team Sync" + +# Stdio transport — runs the MCP server locally via npx. +# Authenticate first: npx @temporal-cortex/cortex-mcp auth google +SERVER_PARAMS = { + "command": "npx", + "args": ["-y", "@temporal-cortex/cortex-mcp"], + "env": { + "GOOGLE_CLIENT_ID": os.getenv("GOOGLE_CLIENT_ID", ""), + "GOOGLE_CLIENT_SECRET": os.getenv("GOOGLE_CLIENT_SECRET", ""), + "TIMEZONE": os.getenv("TIMEZONE", ""), + }, +} + +# For Platform Mode (managed, no Node.js required), replace SERVER_PARAMS: +# SERVER_PARAMS = { +# "url": "https://mcp.temporal-cortex.com/mcp", +# "transport": "sse", +# "headers": { +# "Authorization": f"Bearer {os.getenv('TEMPORAL_CORTEX_API_KEY', '')}", +# }, +# } + + +def main(): + print("## Welcome to the Calendar Scheduling Crew") + print("-------------------------------------------") + + with MCPServerAdapter(SERVER_PARAMS) as tools: + print(f"Connected to Temporal Cortex — {len(tools)} tools discovered\n") + + agents = CalendarAgents() + calendar_tasks = CalendarTasks() + + # Create agents + temporal_analyst = agents.temporal_analyst(tools) + calendar_manager = agents.calendar_manager(tools) + coordinator = agents.scheduling_coordinator(tools) + + # Create tasks with dependencies + orient = calendar_tasks.orient_in_time(temporal_analyst, MEETING_TIME) + availability = calendar_tasks.find_availability(calendar_manager) + booking = calendar_tasks.book_meeting(coordinator, MEETING_TITLE) + + availability.context = [orient] + booking.context = [orient, availability] + + crew = Crew( + agents=[temporal_analyst, calendar_manager, coordinator], + tasks=[orient, availability, booking], + verbose=True, + ) + + result = crew.kickoff() + + print("\n\n################################################") + print("## Here is the result") + print("################################################\n") + print(result) + + +if __name__ == "__main__": + main() diff --git a/crews/calendar-scheduling/tasks.py b/crews/calendar-scheduling/tasks.py new file mode 100644 index 00000000..479230be --- /dev/null +++ b/crews/calendar-scheduling/tasks.py @@ -0,0 +1,53 @@ +from crewai import Task + + +class CalendarTasks: + def orient_in_time(self, agent, meeting_time): + return Task( + description=( + f"Get the current temporal context: call get_temporal_context " + f"to learn the current time, timezone, UTC offset, and DST " + f"status. Then resolve the meeting time expression " + f"'{meeting_time}' into a precise RFC 3339 timestamp " + f"using resolve_datetime." + ), + expected_output=( + "The current local time, timezone, DST status, and the " + "resolved RFC 3339 timestamp for the requested meeting time." + ), + agent=agent, + ) + + def find_availability(self, agent): + return Task( + description=( + "Using the resolved timestamp from the previous task, " + "find available time slots on the target date. First call " + "list_calendars to discover connected providers and their " + "calendar IDs. Then call find_free_slots for the primary " + "calendar on the target date to find 30-minute available " + "windows. Report all available slots." + ), + expected_output=( + "A list of connected calendars and available 30-minute time " + "slots on the target date, with start/end times in RFC 3339 " + "format and the calendar ID to use for booking." + ), + agent=agent, + ) + + def book_meeting(self, agent, meeting_title): + return Task( + description=( + f"From the available slots found in the previous task, " + f"select the best slot and book a 30-minute meeting " + f"titled '{meeting_title}'. Use the book_slot tool with " + f"the calendar ID from the Calendar Manager. The book_slot " + f"tool uses Two-Phase Commit to prevent double-bookings." + ), + expected_output=( + "Confirmation that the meeting was booked, including the " + "calendar ID, event title, start time, and end time." + ), + agent=agent, + )