temporal-cortex-scheduling
List events, find free slots, and book meetings across Google Calendar, Outlook, and CalDAV. Multi-calendar availability merging, recurring event expansion, and atomic booking with Two-Phase Commit conflict prevention.
Packaged view
This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.
Install command
npx @skill-hub/cli install openclaw-skills-temporal-cortex-scheduling
Repository
Skill path: skills/billylui/temporal-cortex-scheduling
List events, find free slots, and book meetings across Google Calendar, Outlook, and CalDAV. Multi-calendar availability merging, recurring event expansion, and atomic booking with Two-Phase Commit conflict prevention.
Open repositoryBest for
Primary workflow: Ship Full Stack.
Technical facets: Full Stack.
Target audience: everyone.
License: MIT.
Original source
Catalog source: SkillHub Club.
Repository owner: openclaw.
This is still a mirrored public skill entry. Review the repository before installing into production workflows.
What it helps with
- Install temporal-cortex-scheduling into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
- Review https://github.com/openclaw/skills before adding temporal-cortex-scheduling to shared team environments
- Use temporal-cortex-scheduling for development workflows
Works across
Favorites: 0.
Sub-skills: 0.
Aggregator: No.
Original source / Raw SKILL.md
---
name: temporal-cortex-scheduling
description: |-
List events, find free slots, and book meetings across Google Calendar, Outlook, and CalDAV. Multi-calendar availability merging, recurring event expansion, and atomic booking with Two-Phase Commit conflict prevention.
license: MIT
compatibility: |-
Requires npx (Node.js 18+) or Docker for the MCP server. Stores OAuth credentials at ~/.config/temporal-cortex/. Works with Claude Code, Claude Desktop, Cursor, Windsurf, and any MCP-compatible client.
metadata:
author: temporal-cortex
version: "0.9.1"
mcp-server: "@temporal-cortex/cortex-mcp"
homepage: "https://temporal-cortex.com"
repository: "https://github.com/temporal-cortex/skills"
openclaw:
install:
- kind: node
package: "@temporal-cortex/[email protected]"
bins: [cortex-mcp]
requires:
bins:
- npx
config:
- ~/.config/temporal-cortex/credentials.json
- ~/.config/temporal-cortex/config.json
---
# Calendar Scheduling & Booking
14 tools (Layers 0, 2–4) for contact resolution, calendar discovery, event querying, free slot finding, availability checking, RRULE expansion, atomic booking, Open Scheduling, and proposal composition. 12 read-only tools + 2 write tools (`book_slot`, `request_booking`).
## Source & Provenance
- **Homepage:** [temporal-cortex.com](https://temporal-cortex.com)
- **Source code:** [github.com/temporal-cortex/mcp](https://github.com/temporal-cortex/mcp) (open-source Rust)
- **npm package:** [@temporal-cortex/cortex-mcp](https://www.npmjs.com/package/@temporal-cortex/cortex-mcp)
- **Skills repo:** [github.com/temporal-cortex/skills](https://github.com/temporal-cortex/skills)
## Runtime
These tools run inside the [Temporal Cortex MCP server](https://github.com/temporal-cortex/mcp) (`@temporal-cortex/cortex-mcp`), a compiled Rust binary distributed as an npm package.
**Install and startup lifecycle:**
1. `npx` resolves `@temporal-cortex/cortex-mcp` from the npm registry (one-time, cached locally after first download)
2. The postinstall script downloads the platform-specific binary from the [GitHub Release](https://github.com/temporal-cortex/mcp/releases/tag/mcp-v0.9.1) and verifies its SHA256 checksum against the embedded `checksums.json` — **installation halts on mismatch**
3. The MCP server starts as a local process communicating over stdio (no listening ports)
4. Calendar tools make authenticated API calls to your configured providers (Google Calendar API, Microsoft Graph API, CalDAV endpoints)
**Credential storage:** OAuth tokens are stored locally at `~/.config/temporal-cortex/credentials.json` and read exclusively by the local MCP server process. No credential data is transmitted to Temporal Cortex servers. The binary's filesystem access is limited to `~/.config/temporal-cortex/` — verifiable by inspecting the [open-source Rust code](https://github.com/temporal-cortex/mcp) or running under Docker where the mount is the only writable path.
**File access:** The binary reads and writes only `~/.config/temporal-cortex/` (credentials and config). No other filesystem writes.
**Network scope:** Calendar tools connect only to your configured providers (`googleapis.com`, `graph.microsoft.com`, or your CalDAV server). In Local Mode (default), no calls to Temporal Cortex servers and no telemetry is collected. In Platform Mode, three tools (`resolve_identity`, `query_public_availability`, `request_booking`) call `api.temporal-cortex.com` for cross-user scheduling — no credential data is included in these calls.
**Pre-run verification** (recommended before first use):
1. Inspect the npm package without executing: `npm pack @temporal-cortex/cortex-mcp --dry-run`
2. Verify checksums independently against the [GitHub Release](https://github.com/temporal-cortex/mcp/releases/download/mcp-v0.9.1/SHA256SUMS.txt) (see verification pipeline below)
3. For full containment, run in Docker instead of npx (see Docker containment below)
**Verification pipeline:** Checksums are published independently at each [GitHub Release](https://github.com/temporal-cortex/mcp/releases/tag/mcp-v0.9.1) as `SHA256SUMS.txt` — verify the binary before first use:
```bash
# 1. Fetch checksums from GitHub (independent of the npm package)
curl -sL https://github.com/temporal-cortex/mcp/releases/download/mcp-v0.9.1/SHA256SUMS.txt
# 2. Compare against the npm-installed binary
shasum -a 256 "$(npm root -g)/@temporal-cortex/cortex-mcp/bin/cortex-mcp"
```
As defense-in-depth, the npm package also embeds `checksums.json` and the postinstall script compares SHA256 hashes during install — **installation halts on mismatch** (the binary is deleted, not executed). This automated check supplements, but does not replace, independent verification above.
**Build provenance:** Binaries are cross-compiled from auditable Rust source in [GitHub Actions](https://github.com/temporal-cortex/mcp/actions) across 5 platforms (darwin-arm64, darwin-x64, linux-x64, linux-arm64, win32-x64). Source: [github.com/temporal-cortex/mcp](https://github.com/temporal-cortex/mcp) (MIT-licensed). The CI workflow, build artifacts, and release checksums are all publicly inspectable.
**Docker containment** (no Node.js on host, credential isolation via volume mount):
```json
{
"mcpServers": {
"temporal-cortex": {
"command": "docker",
"args": ["run", "--rm", "-i", "-v", "~/.config/temporal-cortex:/root/.config/temporal-cortex", "cortex-mcp"]
}
}
}
```
Build: `docker build -t cortex-mcp https://github.com/temporal-cortex/mcp.git`
## Tools
### Layer 0 — Discovery
| Tool | When to Use |
|------|------------|
| `resolve_identity` | DNS for Human Time: resolve an email, phone, or agent ID to a Temporal Cortex slug. Call before `query_public_availability`. Platform Mode only. |
| `search_contacts` | Search the user's address book by name (Google People API, Microsoft Graph). Returns matching contacts with emails, phones, organization, and job title. Opt-in — requires contacts permission. |
| `resolve_contact` | Given a confirmed contact's email, determine the best scheduling path: Open Scheduling (instant booking), email, or phone. Chains with `resolve_identity` when Platform API is available. |
### Layer 2 — Calendar Operations
| Tool | When to Use |
|------|------------|
| `list_calendars` | First call when calendars are unknown. Returns all connected calendars with provider-prefixed IDs, names, labels, primary status, and access roles. |
| `list_events` | List events in a time range. TOON format by default (~40% fewer tokens than JSON). Use provider-prefixed IDs for multi-calendar: `"google/primary"`, `"outlook/work"`. |
| `find_free_slots` | Find available gaps in a calendar. Set `min_duration_minutes` for minimum slot length. |
| `expand_rrule` | Expand recurrence rules (RFC 5545) into concrete instances. Handles DST, BYSETPOS, EXDATE, leap years. Use `dtstart` as local datetime (no timezone suffix). |
| `check_availability` | Check if a specific time slot is free. Checks both events and active booking locks. |
### Layer 3 — Availability
| Tool | When to Use |
|------|------------|
| `get_availability` | Merged free/busy view across multiple calendars. Pass `calendar_ids` array. Privacy: `"opaque"` (default, hides sources) or `"full"`. |
| `query_public_availability` | Check another user's public availability by Temporal Link slug. Pass the slug and date to find their open time slots. Platform Mode only. |
### Layer 4 — Booking
| Tool | When to Use |
|------|------------|
| `book_slot` | Book a time slot atomically. Lock → verify → write → release. **Always `check_availability` first.** |
| `request_booking` | Book on another user's public calendar by Temporal Link slug. Requires Platform Mode. |
| `compose_proposal` | Compose a scheduling proposal message for email, Slack, or SMS. Formats proposed times in the recipient's timezone with an optional Temporal Link self-serve booking URL. Does NOT send — returns formatted text for the agent to send via its channel MCP. |
## Critical Rules
1. **Discover calendars first** — call `list_calendars` when you don't know which calendars are connected. Use the returned provider-prefixed IDs for all subsequent calls.
2. **Use provider-prefixed IDs** for multi-calendar setups: `"google/primary"`, `"outlook/work"`, `"caldav/personal"`. Bare IDs (e.g., `"primary"`) route to the default provider.
3. **TOON is the default format** — output uses TOON (~40% fewer tokens than JSON). Pass `format: "json"` only if you need structured parsing.
4. **Check before booking** — always call `check_availability` before `book_slot`. Never skip the conflict check.
5. **Content safety** — event summaries and descriptions pass through a sanitization firewall before reaching the calendar API.
6. **Timezone awareness** — all tools accept RFC 3339 with timezone offsets. Never use bare dates.
7. **Confirm before booking** — when running autonomously, always present booking details (time, calendar, summary) to the user and wait for confirmation before calling `book_slot` or `request_booking`.
8. **Confirm contact selection** — when `search_contacts` returns multiple matches, present candidates to the user and confirm which contact is correct. Never auto-select.
9. **Confirm before sending proposals** — when using `compose_proposal`, present the composed message to the user before sending. Never auto-send outreach.
10. **Contact search is optional** — if contacts permission is not configured, ask the user for the email directly. The workflow works without contact search.
## Full Booking Workflow
```
0. Resolve Contact → search_contacts("Jane") → resolve_contact([email protected])
(skip if user provides email directly)
1. Discover → list_calendars
2. Orient → get_temporal_context (temporal-cortex-datetime)
3. Resolve Time → resolve_datetime("next Tuesday at 2pm") (temporal-cortex-datetime)
4. Route → If open_scheduling slug: fast path (query_public_availability → request_booking)
If email only: backward-compat path (find_free_slots → compose_proposal)
5. Check → check_availability(calendar_id, start, end)
6. Act → Fast: book_slot / request_booking
Backward-compat: compose_proposal → agent sends via channel MCP
```
If the slot is busy at step 5, use `find_free_slots` to suggest alternatives.
## Open Scheduling Workflow (Platform Mode)
```
1. Identify → resolve_identity("[email protected]") → slug: "jane-doe"
2. Orient → get_temporal_context (temporal-cortex-datetime)
3. Discover → query_public_availability(slug, date) → available slots
4. Present → Show top 3 options to user
5. Book → request_booking(slug, start, end, title, attendee_email)
```
## Two-Phase Commit Protocol
```
Agent calls book_slot(calendar_id, start, end, summary)
│
├─ 1. LOCK → Acquire exclusive lock on the time slot
│ (in-memory local; Redis Redlock in Platform Mode)
│
├─ 2. VERIFY → Check for overlapping events and active locks
│
├─ 3. WRITE → Create event in calendar provider (Google/Outlook/CalDAV)
│ Record event in shadow calendar
│
└─ 4. RELEASE → Release the exclusive lock
```
If any step fails, the lock is released and the booking is aborted. No partial writes.
## Common Patterns
### Schedule with a Contact (End-to-End)
```
1. search_contacts(query: "Jane") → present candidates to user
2. User confirms: "Jane Doe ([email protected])"
3. resolve_contact(email: "[email protected]") → scheduling_paths
4. If open_scheduling: query_public_availability(slug, date) → request_booking
5. If email only:
a. find_free_slots(calendar_id, start, end) → available times
b. compose_proposal(contact_name, email, slots, timezone, format: "email")
c. Present composed message to user → user confirms → send via channel MCP
```
### List Events This Week
```
1. list_calendars → discover connected calendars
2. get_temporal_context → current time (use temporal-cortex-datetime)
3. resolve_datetime("start of this week") → week start
4. resolve_datetime("end of this week") → week end
5. list_events(calendar_id: "google/primary", start, end)
```
### Find Free Time Across Calendars
```
1. list_calendars → discover all connected calendars
2. get_availability(
start, end,
calendar_ids: ["google/primary", "outlook/work"],
privacy: "full"
) → merged free/busy blocks with source_count
```
### Check and Book a Slot
```
1. check_availability(calendar_id: "google/primary", start, end) → true/false
2. If free: book_slot(calendar_id: "google/primary", start, end, summary: "Team standup")
3. If busy: find_free_slots(calendar_id, start, end, min_duration_minutes: 30)
```
### Expand Recurring Events
```
expand_rrule(
rrule: "FREQ=MONTHLY;BYDAY=FR;BYSETPOS=-1",
dtstart: "2026-01-01T10:00:00", ← local datetime, no timezone suffix
timezone: "America/New_York",
count: 12
) → last Friday of every month for 2026
```
## Provider-Prefixed Calendar IDs
All calendar IDs use provider-prefixed format:
| Format | Example | Routes to |
|--------|---------|-----------|
| `google/<id>` | `"google/primary"` | Google Calendar |
| `outlook/<id>` | `"outlook/work"` | Microsoft Outlook |
| `caldav/<id>` | `"caldav/personal"` | CalDAV (iCloud, Fastmail) |
| `<id>` (bare) | `"primary"` | Default provider |
## Privacy Modes
| Mode | `source_count` | Use case |
|------|---------------|----------|
| `"opaque"` (default) | Always `0` | Sharing availability externally |
| `"full"` | Actual count | Internal use — shows which calendars are busy |
## Tool Annotations (`book_slot`)
| Property | Value | Meaning |
|----------|-------|---------|
| `readOnlyHint` | `false` | Creates calendar events |
| `destructiveHint` | `false` | Never deletes or overwrites existing events |
| `idempotentHint` | `false` | Calling twice creates two events |
| `openWorldHint` | `true` | Makes external API calls |
## Tool Annotations (`request_booking`)
| Property | Value | Meaning |
|----------|-------|---------|
| `readOnlyHint` | `false` | Creates calendar events on another user's calendar |
| `destructiveHint` | `false` | Never deletes or overwrites existing events |
| `idempotentHint` | `false` | Calling twice creates two bookings |
| `openWorldHint` | `true` | Calls the Platform API |
## Tool Annotations (`compose_proposal`)
| Property | Value | Meaning |
|----------|-------|---------|
| `readOnlyHint` | `true` | Pure formatting — no state modification |
| `destructiveHint` | `false` | Never deletes data |
| `idempotentHint` | `true` | Same input always gives same output |
| `openWorldHint` | `false` | No external calls — pure computation |
## Tool Annotations (`search_contacts`)
| Property | Value | Meaning |
|----------|-------|---------|
| `readOnlyHint` | `true` | Reads contacts only — no modifications |
| `destructiveHint` | `false` | Never deletes contacts |
| `idempotentHint` | `true` | Same query always gives same results |
| `openWorldHint` | `true` | Calls external contact API (Google People / Microsoft Graph) |
## Error Handling
| Error | Action |
|-------|--------|
| "No credentials found" | Run: `npx @temporal-cortex/cortex-mcp auth google` (or `outlook` / `caldav`). |
| "Timezone not configured" | Prompt for IANA timezone. Or run the auth command which configures timezone. |
| Slot is busy / conflict detected | Use `find_free_slots` to suggest alternatives. Present options to user. |
| Lock acquisition failed | Another agent is booking the same slot. Wait briefly and retry, or suggest alternative times. |
| Content rejected by sanitization | Rephrase the event summary/description. The firewall blocks prompt injection attempts. |
## Open Scheduling & Temporal Links
When a user has Open Scheduling enabled, their Temporal Link (`book.temporal-cortex.com/{slug}`) allows anyone to:
1. **Query availability** — `GET /public/{slug}/availability?date=YYYY-MM-DD`
2. **Book a meeting** — `POST /public/{slug}/book` with `{start, end, title, attendee_email}`
3. **Discover via Agent Card** — `GET /public/{slug}/.well-known/agent-card.json`
### Workflow: Book via Temporal Link
1. User shares their Temporal Link
2. Agent calls availability endpoint to find free slots
3. Agent calls booking endpoint with selected slot
4. Meeting is created on the user's default calendar
See [Temporal Links Reference](references/TEMPORAL-LINKS.md) for detailed API documentation.
## Additional References
- [Calendar Tools Reference](references/CALENDAR-TOOLS.md) — Complete input/output schemas for all 14 tools
- [Multi-Calendar Guide](references/MULTI-CALENDAR.md) — Provider routing, labels, privacy modes, cross-provider operations
- [RRULE Guide](references/RRULE-GUIDE.md) — Recurrence rule patterns, DST edge cases, 5 LLM failure modes
- [Booking Safety](references/BOOKING-SAFETY.md) — 2PC details, concurrent booking, lock TTL, content sanitization
- [Temporal Links](references/TEMPORAL-LINKS.md) — Open Scheduling endpoints, Agent Card integration, calendar routing
- [Open Scheduling Guide](references/OPEN-SCHEDULING.md) — Identity resolution, public availability, external booking via MCP tools
---
## Referenced Files
> The following files are referenced in this skill and included for context.
### references/TEMPORAL-LINKS.md
```markdown
# Temporal Links
Temporal Links are public URLs that expose a Temporal Cortex user's scheduling availability and booking endpoint. When a user enables Open Scheduling and chooses a slug, their Temporal Link becomes:
```
https://book.temporal-cortex.com/{slug}
```
Agents use Temporal Links to query availability and book meetings without needing calendar credentials or OAuth tokens. All requests are unauthenticated — the calendar owner controls access through their Open Scheduling settings.
## Endpoints
Each Temporal Link exposes four endpoints:
| Endpoint | Method | Purpose |
|----------|--------|---------|
| `/public/{slug}/availability` | `GET` | Query available time slots |
| `/public/{slug}/book` | `POST` | Book a meeting |
| `/public/{slug}/a2a` | `POST` | Agent-to-agent JSON-RPC |
| `/public/{slug}/.well-known/agent-card.json` | `GET` | Discover scheduling capabilities |
## Querying Availability
Retrieve free time slots for a given date.
```bash
curl "https://book.temporal-cortex.com/public/jane-doe/availability?date=2026-03-10"
```
With optional parameters:
```bash
curl "https://book.temporal-cortex.com/public/jane-doe/availability?date=2026-03-10&duration=60&timezone=Europe/London"
```
**Parameters:**
- `date` (required) — `YYYY-MM-DD` format
- `duration` (optional) — Minimum slot length in minutes. Default: 30.
- `timezone` (optional) — IANA timezone for response times. Default: the calendar owner's configured timezone.
**Response:**
```json
{
"slug": "jane-doe",
"date": "2026-03-10",
"timezone": "America/New_York",
"slots": [
{ "start": "2026-03-10T09:00:00-05:00", "end": "2026-03-10T09:30:00-05:00" },
{ "start": "2026-03-10T10:00:00-05:00", "end": "2026-03-10T11:00:00-05:00" },
{ "start": "2026-03-10T14:00:00-05:00", "end": "2026-03-10T15:00:00-05:00" }
]
}
```
## Booking a Meeting
Book a slot on the user's default calendar. Requires `attendee_email`.
```bash
curl -X POST "https://book.temporal-cortex.com/public/jane-doe/book" \
-H "Content-Type: application/json" \
-d '{
"start": "2026-03-10T10:00:00-05:00",
"end": "2026-03-10T10:30:00-05:00",
"title": "Project kickoff",
"attendee_email": "[email protected]",
"attendee_name": "Alex Chen"
}'
```
**Response (201 Created):**
```json
{
"booking_id": "bk_a1b2c3d4e5f6",
"status": "confirmed",
"calendar_event": {
"start": "2026-03-10T10:00:00-05:00",
"end": "2026-03-10T10:30:00-05:00",
"title": "Project kickoff",
"attendee_email": "[email protected]"
}
}
```
If the slot has been taken between the availability query and the booking request, the response is `409 Conflict`:
```json
{
"error": "slot_unavailable",
"message": "The requested time slot is no longer available."
}
```
## Agent-to-Agent (A2A) JSON-RPC
The A2A endpoint accepts JSON-RPC 2.0 requests for programmatic agent-to-agent scheduling.
### Query availability
```bash
curl -X POST "https://book.temporal-cortex.com/public/jane-doe/a2a" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "query_availability",
"params": { "date": "2026-03-10", "duration": 30, "timezone": "America/New_York" },
"id": 1
}'
```
### Book a slot
```bash
curl -X POST "https://book.temporal-cortex.com/public/jane-doe/a2a" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "book_slot",
"params": {
"start": "2026-03-10T14:00:00-05:00",
"end": "2026-03-10T14:30:00-05:00",
"title": "Strategy sync",
"attendee_email": "[email protected]"
},
"id": 2
}'
```
## Agent Card Integration
Every Open Scheduling profile publishes an Agent Card at a well-known URL:
```bash
curl "https://book.temporal-cortex.com/public/jane-doe/.well-known/agent-card.json"
```
**Response:**
```json
{
"name": "Jane Doe",
"description": "Schedule a meeting with Jane Doe via Temporal Cortex",
"url": "https://book.temporal-cortex.com/jane-doe",
"capabilities": {
"a2a": "https://book.temporal-cortex.com/public/jane-doe/a2a",
"availability": "https://book.temporal-cortex.com/public/jane-doe/availability",
"booking": "https://book.temporal-cortex.com/public/jane-doe/book"
},
"provider": {
"name": "Temporal Cortex",
"url": "https://temporal-cortex.com"
},
"version": "1.0"
}
```
A2A-compatible agents can discover and interact with Open Scheduling profiles by:
1. Fetching the Agent Card to discover endpoint URLs
2. Calling `query_availability` via the A2A endpoint to find free slots
3. Calling `book_slot` via the A2A endpoint to confirm a meeting
The Agent Card follows the emerging A2A protocol convention, making Temporal Cortex profiles discoverable by any agent that supports `.well-known/agent-card.json` resolution.
## Calendar Routing
When a booking is created through a Temporal Link, the meeting is placed on the calendar owner's **default booking calendar**. This is configured in the dashboard under **Settings > Open Scheduling > Default Calendar**.
Calendar routing works as follows:
- **Availability** is computed by merging busy times across all calendars the user has marked as contributing to availability. This includes multiple providers (e.g., a Google Calendar for personal events and an Outlook calendar for work).
- **Bookings** are written to a single designated calendar. The user selects which calendar receives new bookings in the dashboard.
- **Provider-prefixed IDs** are used internally (e.g., `google/primary`, `outlook/work`), but the public API abstracts this away. Callers of the Temporal Link endpoints do not need to know which provider hosts the booking calendar.
If the user has not selected a default booking calendar, the system falls back to the primary calendar of their first connected provider.
## Rate Limits
| Endpoint | Limit |
|----------|-------|
| Availability queries | 60 requests per minute per IP |
| Bookings | 10 per hour per IP |
| Agent Card | 60 requests per minute per IP |
| A2A `query_availability` | 60 requests per minute per IP |
| A2A `book_slot` | 10 per hour per IP |
Exceeding the limit returns `429 Too Many Requests` with a `Retry-After` header.
```
### references/CALENDAR-TOOLS.md
```markdown
# Calendar Tools Reference
Complete input/output schemas for the 6 calendar tools documented below. All are read-only and idempotent. TOON is the default output format for tools that support it (~40% fewer tokens than JSON).
## Tool Annotations
| Tool | `readOnlyHint` | `destructiveHint` | `idempotentHint` | `openWorldHint` |
|------|:-:|:-:|:-:|:-:|
| `list_calendars` | true | false | true | true |
| `list_events` | true | false | true | true |
| `find_free_slots` | true | false | true | true |
| `expand_rrule` | true | false | true | false |
| `check_availability` | true | false | true | true |
| `get_availability` | true | false | true | true |
`expand_rrule` is closed-world (no external API calls). All others make external API calls to calendar providers.
---
## list_calendars
**Input:**
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `provider` | string | No | Filter by provider: `"google"`, `"outlook"`, `"caldav"` |
| `format` | string | No | `"toon"` (default) or `"json"` |
**Output:** Array of calendars, each with: `id` (provider-prefixed), `provider`, `name`, `label` (optional, user-assigned), `primary` (boolean), `access_role`
---
## list_events
**Input:**
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `calendar_id` | string | Yes | Calendar ID (supports provider prefix) |
| `start` | string | Yes | Range start (RFC 3339) |
| `end` | string | Yes | Range end (RFC 3339) |
| `format` | string | No | `"toon"` (default, ~40% fewer tokens) or `"json"` |
**Output:** `content`, `format`, `count`
---
## find_free_slots
**Input:**
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `calendar_id` | string | Yes | Calendar ID (supports provider prefix) |
| `start` | string | Yes | Window start (RFC 3339) |
| `end` | string | Yes | Window end (RFC 3339) |
| `min_duration_minutes` | integer | No | Minimum slot length (default: 30) |
| `format` | string | No | `"toon"` (default) or `"json"` |
**Output:** `slots` (array of `{start, end, duration_minutes}`), `count`
---
## expand_rrule
**Input:**
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `rrule` | string | Yes | RFC 5545 RRULE string |
| `dtstart` | string | Yes | Local datetime (no timezone suffix) |
| `timezone` | string | Yes | IANA timezone |
| `duration_minutes` | integer | No | Instance duration (default: 60) |
| `count` | integer | No | Max instances to return |
| `format` | string | No | `"toon"` (default) or `"json"` |
**Output:** `instances` (array of `{start, end}`), `count`
---
## check_availability
**Input:**
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `calendar_id` | string | Yes | Calendar ID (supports provider prefix) |
| `start` | string | Yes | Slot start (RFC 3339) |
| `end` | string | Yes | Slot end (RFC 3339) |
**Output:** `available` (boolean)
---
## get_availability
**Input:**
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `start` | string | Yes | Window start (RFC 3339) |
| `end` | string | Yes | Window end (RFC 3339) |
| `calendar_ids` | string[] | No | Calendar IDs (default: `["primary"]`) |
| `min_free_slot_minutes` | integer | No | Min free slot (default: 30) |
| `privacy` | string | No | `"opaque"` (default) or `"full"` |
| `format` | string | No | `"toon"` (default) or `"json"` |
**Output:** `busy` (array of `{start, end, source_count}`), `free` (array of `{start, end, duration_minutes}`), `calendars_merged`, `privacy`
```
### references/MULTI-CALENDAR.md
```markdown
# Multi-Calendar Operations
Temporal Cortex supports simultaneous connections to Google Calendar, Microsoft Outlook, and CalDAV (iCloud, Fastmail). All calendars are merged into a unified view.
## Discovering Connected Calendars
Use `list_calendars` as the first step to discover all connected calendars:
```
list_calendars()
→ [
{ id: "google/primary", provider: "google", name: "Personal", label: "Main", primary: true, access_role: "owner" }
{ id: "google/work", provider: "google", name: "Work Calendar", primary: false, access_role: "writer" }
{ id: "outlook/AAA123", provider: "outlook", name: "Outlook Calendar", primary: true, access_role: "owner" }
]
```
Filter by provider if needed: `list_calendars(provider: "google")`.
Use the returned `id` values as `calendar_id` in all subsequent tool calls. This eliminates guessing at calendar IDs.
## Calendar Labels
Labels are user-assigned nicknames for calendars. They help agents and users identify calendars by purpose rather than cryptic IDs:
| Calendar ID | Name (from provider) | Label (user-assigned) |
|------------|---------------------|----------------------|
| `google/primary` | "Personal" | "Main" |
| `google/abc123` | "[email protected]" | "Work" |
| `outlook/AAA123` | "Calendar" | "Outlook Work" |
Labels are set during `cortex-mcp setup` or via configuration. When presenting calendars to users, prefer the label (if set) over the raw name.
## Provider-Prefixed Calendar IDs
All calendar IDs returned by `list_calendars` use provider-prefixed format:
| Format | Example | Routes to |
|--------|---------|-----------|
| `google/<id>` | `"google/primary"` | Google Calendar |
| `outlook/<id>` | `"outlook/work"` | Microsoft Outlook |
| `caldav/<id>` | `"caldav/personal"` | CalDAV (iCloud, Fastmail) |
| `<id>` (bare) | `"primary"` | Default provider |
Bare IDs (without prefix) route to the default provider configured during setup. Prefer using the full provider-prefixed IDs from `list_calendars` for clarity.
CalDAV calendar IDs can contain slashes (e.g., path-style IDs). The router splits on the first `/` only — everything after the prefix slash is the calendar ID.
## Cross-Calendar Availability
Use `get_availability` with the calendar IDs from `list_calendars` to get a merged view:
```
1. list_calendars() → discover connected calendars
2. get_availability(
start: "2026-03-16T00:00:00-04:00",
end: "2026-03-17T00:00:00-04:00",
calendar_ids: ["google/primary", "outlook/AAA123", "caldav/personal"],
privacy: "full"
) → merged free/busy blocks across all calendars
```
The server fetches events from all providers in parallel and merges them into unified busy/free blocks.
## Privacy Modes
| Mode | `source_count` | Use case |
|------|---------------|----------|
| `"opaque"` (default) | Always `0` | Sharing availability externally — hides which calendars are busy |
| `"full"` | Actual count | Internal use — shows how many calendars contribute to each busy block |
## Provider Authentication
Each provider requires a one-time authentication:
```bash
# Google Calendar (OAuth2, browser-based consent)
npx @temporal-cortex/cortex-mcp auth google
# Microsoft Outlook (Azure AD OAuth2 with PKCE)
npx @temporal-cortex/cortex-mcp auth outlook
# CalDAV (app-specific password, presets for iCloud/Fastmail)
npx @temporal-cortex/cortex-mcp auth caldav
```
Credentials are stored locally at `~/.config/temporal-cortex/credentials.json`. Provider registrations are saved in `~/.config/temporal-cortex/config.json`.
## Environment Variables
| Variable | When Needed | Description |
|----------|-------------|-------------|
| `GOOGLE_CLIENT_ID` | Custom OAuth app only | Google OAuth Client ID (built-in default available) |
| `GOOGLE_CLIENT_SECRET` | Custom OAuth app only | Google OAuth Client Secret (built-in default available) |
| `MICROSOFT_CLIENT_ID` | Custom OAuth app only | Azure AD application (client) ID (built-in default available) |
| `MICROSOFT_CLIENT_SECRET` | Custom OAuth app only | Azure AD client secret (built-in default available) |
| `TIMEZONE` | Optional | IANA timezone override (auto-detected if not set) |
| `WEEK_START` | Optional | `"monday"` (default) or `"sunday"` |
CalDAV providers need no environment variables — authentication is interactive.
## Single-Provider Shortcuts
If only one provider is configured (visible via `list_calendars`), you can use bare calendar IDs:
```json
{ "calendar_id": "primary" }
```
This routes to whichever provider is configured. No prefix needed.
## Booking Across Providers
`book_slot` works with any provider. Use the provider prefix to specify where to create the event:
```json
{
"calendar_id": "google/primary",
"start": "2026-03-16T14:00:00-04:00",
"end": "2026-03-16T15:00:00-04:00",
"summary": "Team Sync"
}
```
The conflict check queries the specified calendar. For cross-calendar conflict detection, use `get_availability` first.
```
### references/RRULE-GUIDE.md
```markdown
# RRULE Guide
The `expand_rrule` tool expands RFC 5545 recurrence rules into concrete event instances using Truth Engine — deterministic computation, not LLM inference.
## Common Patterns
| Pattern | RRULE | Example |
|---------|-------|---------|
| Every weekday | `FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR` | Daily standup |
| Biweekly Monday | `FREQ=WEEKLY;INTERVAL=2;BYDAY=MO` | Sprint planning |
| Monthly on 15th | `FREQ=MONTHLY;BYMONTHDAY=15` | Payroll |
| Last Friday of month | `FREQ=MONTHLY;BYDAY=FR;BYSETPOS=-1` | Monthly review |
| Third Tuesday | `FREQ=MONTHLY;BYDAY=TU;BYSETPOS=3` | Board meeting |
| Yearly Feb 29 | `FREQ=YEARLY;BYMONTH=2;BYMONTHDAY=29` | Leap year event |
## Input Format
```json
{
"rrule": "FREQ=WEEKLY;BYDAY=MO,WE,FR",
"dtstart": "2026-03-01T09:00:00",
"timezone": "America/New_York",
"duration_minutes": 60,
"count": 10
}
```
**Important**: `dtstart` is a **local datetime** (no timezone suffix). The `timezone` parameter determines how it maps to UTC. `UNTIL` values in the RRULE must be in UTC when a timezone is specified.
## 5 Edge Cases Where LLMs Fail
### 1. DST Transitions
"Third Tuesday of March 2026" (spring-forward on March 8):
- Pre-DST: UTC offset -05:00 → Post-DST: -04:00
- LLMs often produce wrong UTC time or skip the month
Truth Engine: Correct UTC conversion across the DST boundary.
### 2. BYSETPOS=-1 (Last Occurrence)
`FREQ=MONTHLY;BYDAY=FR;BYSETPOS=-1` means "last Friday of the month."
- Months have 4 or 5 Fridays — the last one varies
- LLMs frequently return the first Friday instead
Truth Engine: Correctly identifies the last occurrence in every month.
### 3. EXDATE with Timezones
Excluding specific dates requires exact timezone matching:
- `EXDATE` values must match the generated instances exactly
- LLMs often ignore EXDATE entirely or apply it to wrong dates
Truth Engine: Exact matching with timezone-aware comparison.
### 4. INTERVAL with BYDAY
`FREQ=WEEKLY;INTERVAL=2;BYDAY=MO,WE,FR` means every-other-week on Mon/Wed/Fri.
- `INTERVAL=2` applies to weeks, not individual days
- LLMs frequently generate every-week occurrences
Truth Engine: Correct interval application to the frequency unit.
### 5. Leap Year Recurrence
`FREQ=YEARLY;BYMONTH=2;BYMONTHDAY=29` should only produce instances in leap years.
- 2028, 2032, 2036... are leap years
- LLMs often generate Feb 28 or Mar 1 in non-leap years
Truth Engine: Only produces instances when Feb 29 exists.
```
### references/BOOKING-SAFETY.md
```markdown
# Booking Safety
The `book_slot` tool uses Two-Phase Commit (2PC) to guarantee conflict-free calendar booking. This is the only tool that modifies external state.
## Two-Phase Commit Flow
```
Agent calls book_slot(calendar_id, start, end, summary)
│
├─ 1. LOCK → Acquire exclusive lock on the time slot
│ (in-memory local; Redis Redlock in platform mode)
│
├─ 2. VERIFY → Check shadow calendar for overlapping events
│ Check for active booking locks on the same slot
│
├─ 3. WRITE → Create event in calendar provider (Google/Outlook/CalDAV)
│ Record event in shadow calendar
│
└─ 4. RELEASE → Release the exclusive lock
```
If any step fails, the lock is released and the booking is aborted. No partial writes.
## Conflict Resolution
When a conflict is detected (step 2), `book_slot` returns an error with details:
```json
{
"error": "Conflict detected",
"conflicting_event": {
"summary": "Team Standup",
"start": "2026-03-16T14:00:00Z",
"end": "2026-03-16T14:30:00Z"
}
}
```
**Agent action**: Use `find_free_slots` to find alternatives and present them to the user.
## Concurrent Booking
Two agents booking the same 2pm slot simultaneously:
- Agent A acquires the lock first, verifies, writes. Succeeds.
- Agent B waits for lock, acquires it, verifies — finds Agent A's event. Fails with conflict error.
Exactly one booking succeeds. The other gets a clear error.
## Lock Configuration
| Setting | Default | Environment Variable |
|---------|---------|---------------------|
| Lock TTL | 30 seconds | `LOCK_TTL_SECS` |
| Lock manager | In-memory (local) | Automatic |
| Lock manager | Redis Redlock (platform) | Set `REDIS_URLS` |
> **Note:** `LOCK_TTL_SECS` and `REDIS_URLS` are **Platform Mode only** environment variables used by the managed Platform deployment. Local/skill users do not need to set these — the in-memory lock manager is used automatically.
If a lock is not released within the TTL (process crash, network timeout), it expires automatically.
## Content Sanitization
All user-provided text in `book_slot` passes through a prompt injection firewall before reaching the calendar API:
- **Event summary** — checked for injection patterns
- **Event description** — checked for injection patterns
- **Rejected content** — returns an error asking to rephrase
This prevents malicious content from being written to calendars via AI agents.
## Tool Annotations
| Property | Value | Meaning |
|----------|-------|---------|
| `readOnlyHint` | `false` | Creates calendar events |
| `destructiveHint` | `false` | Never deletes or overwrites existing events |
| `idempotentHint` | `false` | Calling twice creates two events |
| `openWorldHint` | `true` | Makes external API calls |
All other tools (11/12) are read-only and idempotent — safe to retry without side effects.
## Best Practices
1. **Always `check_availability` before `book_slot`** — catch conflicts before acquiring a lock
2. **Include attendee emails** when booking meetings — they receive calendar invitations
3. **Keep summaries clear and concise** — they pass through sanitization
4. **Handle lock failures gracefully** — suggest alternative times if the slot is contended
```
### references/OPEN-SCHEDULING.md
```markdown
# Open Scheduling Guide (MCP Tools)
Open Scheduling allows AI agents to find people, check their availability, and book meetings on their calendars — entirely through MCP tools. This guide covers the three Platform Mode tools that enable cross-user scheduling.
## Prerequisites
- **Platform Mode only** — these tools require `API_BASE_URL` to be set. In Local Mode, they return an error.
- The target user must have Open Scheduling enabled on their Temporal Cortex account.
## Tool Overview
| Tool | Layer | Purpose |
|------|-------|---------|
| `resolve_identity` | 0 — Discovery | Email/phone → Temporal Link slug |
| `query_public_availability` | 3 — Availability | Slug + date → available time slots |
| `request_booking` | 4 — Booking | Slug + time → confirmed booking |
## Workflow
```
1. resolve_identity("[email protected]")
→ { found: true, slug: "jane-doe", open_scheduling_enabled: true }
2. get_temporal_context()
→ current time, timezone, DST info
3. query_public_availability("jane-doe", "2026-03-10")
→ { slots: [{ start: "09:00", end: "10:00" }, ...] }
4. Present options to user and get confirmation
5. request_booking("jane-doe", start, end, "Project Review", "[email protected]")
→ { booking_id: "...", status: "confirmed", calendar_event: {...} }
```
## resolve_identity
DNS for Human Time — resolves an email, phone number, or agent ID to a Temporal Cortex slug.
**Input:**
- `identity` (required) — email, phone, or agent ID
**Output:**
```json
{
"found": true,
"slug": "jane-doe",
"display_name": "Jane Doe",
"open_scheduling_enabled": true
}
```
**Error cases:**
- Identity not found → `found: false`
- Empty identity → validation error
- Local Mode → error message about Platform Mode requirement
## query_public_availability
Check another user's public availability for a given date.
**Input:**
- `slug` (required) — Temporal Link slug (e.g., "jane-doe")
- `date` (required) — Date in YYYY-MM-DD format
- `duration_minutes` (optional) — Minimum slot duration, default 30
- `timezone` (optional) — IANA timezone for response formatting
**Output:**
```json
{
"slug": "jane-doe",
"date": "2026-03-10",
"timezone": "America/New_York",
"slots": [
{ "start": "09:00", "end": "10:00" },
{ "start": "14:00", "end": "15:30" }
]
}
```
**Error cases:**
- 404 → slug not found
- 429 → rate limit exceeded (60/min per IP)
- Invalid date format → validation error
## request_booking
Book a meeting on another user's public calendar.
**Input:**
- `slug` (required) — Temporal Link slug
- `start` (required) — RFC 3339 datetime
- `end` (required) — RFC 3339 datetime
- `title` (required) — Meeting title
- `attendee_email` (required) — Your email (for the calendar invitation)
- `attendee_name` (optional) — Your display name
- `description` (optional) — Meeting description
**Output:**
```json
{
"booking_id": "abc123",
"status": "confirmed",
"calendar_event": {
"start": "2026-03-10T09:00:00-04:00",
"end": "2026-03-10T10:00:00-04:00",
"title": "Project Review",
"attendee_email": "[email protected]"
}
}
```
**Error cases:**
- 409 Conflict → slot no longer available. Re-query availability and pick a different slot.
- 429 Rate Limited → 10 bookings/hr limit. Wait and retry later.
- 404 → slug not found
- Empty title or email → validation error
## Content Safety
All booking requests pass through the Platform's content sanitization firewall server-side. Prompt injection attempts in titles or descriptions are blocked before the calendar event is created.
## Rate Limits
| Endpoint | Limit |
|----------|-------|
| Availability queries | 60/min per IP |
| Booking requests | 10/hr per IP |
## Agent Card Discovery
Users with Open Scheduling enabled also expose an A2A Agent Card at:
```
GET /public/{slug}/.well-known/agent-card.json
```
This enables agent-to-agent discovery per the Google A2A protocol.
## Temporal Links
Each user's public scheduling page is accessible at:
```
book.temporal-cortex.com/{slug}
```
This is the human-facing equivalent of the MCP tools — both use the same backend API.
```
---
## Skill Companion Files
> Additional files collected from the skill directory layout.
### _meta.json
```json
{
"owner": "billylui",
"slug": "temporal-cortex-scheduling",
"displayName": "temporal-cortex-scheduling",
"latest": {
"version": "0.9.1",
"publishedAt": 1773139590444,
"commit": "https://github.com/openclaw/skills/commit/d7c110b5e959c8c53d37781d4eacc3fcefef1370"
},
"history": [
{
"version": "0.8.1",
"publishedAt": 1772859830199,
"commit": "https://github.com/openclaw/skills/commit/e8cf2bb41aa704191448a9f928824fb118e7ebfa"
},
{
"version": "0.7.8",
"publishedAt": 1772788611238,
"commit": "https://github.com/openclaw/skills/commit/72bab5d9214acdc793d6a4fdc2b73dbf44546894"
},
{
"version": "0.7.3",
"publishedAt": 1772635281712,
"commit": "https://github.com/openclaw/skills/commit/13f6467909de814746e436da3e1e6165b2f7b7b8"
},
{
"version": "0.6.2",
"publishedAt": 1772602564501,
"commit": "https://github.com/openclaw/skills/commit/2dc37598062c1925af5a3d03f3e98e89346dfa13"
},
{
"version": "0.5.8",
"publishedAt": 1772355111022,
"commit": "https://github.com/openclaw/skills/commit/9561bad805da48a9dc8ca160fb199887ded8b4ee"
}
]
}
```