Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions crews/calendar-scheduling/.env_example
Original file line number Diff line number Diff line change
@@ -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
5 changes: 5 additions & 0 deletions crews/calendar-scheduling/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.env
.env.local
__pycache__/
*.pyc
.DS_Store
57 changes: 57 additions & 0 deletions crews/calendar-scheduling/README.md
Original file line number Diff line number Diff line change
@@ -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.
64 changes: 64 additions & 0 deletions crews/calendar-scheduling/agents.py
Original file line number Diff line number Diff line change
@@ -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,
)
76 changes: 76 additions & 0 deletions crews/calendar-scheduling/main.py
Original file line number Diff line number Diff line change
@@ -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()
53 changes: 53 additions & 0 deletions crews/calendar-scheduling/tasks.py
Original file line number Diff line number Diff line change
@@ -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,
)