Install
openclaw skills install temporal-cortex-schedulingList 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.
openclaw skills install temporal-cortex-scheduling14 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).
These tools run inside the Temporal Cortex MCP server (@temporal-cortex/cortex-mcp), a compiled Rust binary distributed as an npm package.
Install and startup lifecycle:
npx resolves @temporal-cortex/cortex-mcp from the npm registry (one-time, cached locally after first download)checksums.json — installation halts on mismatchCredential 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 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):
npm pack @temporal-cortex/cortex-mcp --dry-runVerification pipeline: Checksums are published independently at each GitHub Release as SHA256SUMS.txt — verify the binary before first use:
# 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 across 5 platforms (darwin-arm64, darwin-x64, linux-x64, linux-arm64, win32-x64). Source: 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):
{
"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
| 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. |
| 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. |
| 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. |
| 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. |
list_calendars when you don't know which calendars are connected. Use the returned provider-prefixed IDs for all subsequent calls."google/primary", "outlook/work", "caldav/personal". Bare IDs (e.g., "primary") route to the default provider.format: "json" only if you need structured parsing.check_availability before book_slot. Never skip the conflict check.book_slot or request_booking.search_contacts returns multiple matches, present candidates to the user and confirm which contact is correct. Never auto-select.compose_proposal, present the composed message to the user before sending. Never auto-send outreach.0. Resolve Contact → search_contacts("Jane") → resolve_contact(jane@example.com)
(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.
1. Identify → resolve_identity("jane@example.com") → 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)
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.
1. search_contacts(query: "Jane") → present candidates to user
2. User confirms: "Jane Doe (jane@example.com)"
3. resolve_contact(email: "jane@example.com") → 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
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)
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
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_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
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 |
| Mode | source_count | Use case |
|---|---|---|
"opaque" (default) | Always 0 | Sharing availability externally |
"full" | Actual count | Internal use — shows which calendars are busy |
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 |
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 |
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 |
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 | 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. |
When a user has Open Scheduling enabled, their Temporal Link (book.temporal-cortex.com/{slug}) allows anyone to:
GET /public/{slug}/availability?date=YYYY-MM-DDPOST /public/{slug}/book with {start, end, title, attendee_email}GET /public/{slug}/.well-known/agent-card.jsonSee Temporal Links Reference for detailed API documentation.