Pace MCP server

Lets Claude (claude.ai connector, Claude Code plugin), ChatGPT, and Codex read your upcoming wakes and queue changes. The server holds intent; your iPhone is the only thing that rings. Every change an assistant makes is a proposal the Pace app pulls, applies to AlarmKit (or declines), and acknowledges.

┌──────────────┐  OAuth 2.1 + MCP   ┌──────────────────────┐  device token   ┌──────────────┐
│ Claude/ChatGPT│ ─────────────────▶ │  pace server (Go)    │ ◀────────────── │  iPhone      │
│ (LLM agent)  │  tools/resources   │  /mcp  intent queue  │  snapshot/pull/ │  AlarmKit    │
└──────────────┘                    │  + OAuth server      │  ack            │  = fire      │
                                    └──────────────────────┘                 └──────────────┘
                                          intent only                          only ringer

Endpoint: https://mcp-production-e0af.up.railway.app/mcp (Streamable HTTP). Claude Code plugin: github.com/guzus/pace-plugin. Programs that cannot speak MCP use the REST API and SDK; both surfaces share one service layer, so the same request over either is one intent.

Setup & authentication

  1. Add the server. claude.ai → Settings → Connectors → Add custom connector → https://mcp-production-e0af.up.railway.app/mcp. Claude Code: claude mcp add --transport http pace https://mcp-production-e0af.up.railway.app/mcp, then /mcp. ChatGPT: Settings → Apps & Connectors → Create (developer mode) with the same URL.
  2. Sign in with a pairing code. The connector opens Pace's consent page. On your iPhone open Pace → Settings → Connect an agent; it shows an 8-character code that is valid for 10 minutes and works once. Type it on the consent page and tap Allow.
  3. Done. The assistant now holds a token bound to your account (read + write scope by default). No email, no password, no third-party login: the phone is the identity. Revoke any time from Pace → Settings → Connected agents, or unpair to delete everything.

Auth is OAuth 2.1 with PKCE, Protected Resource Metadata (RFC 9728), authorization-server metadata (RFC 8414), Client ID Metadata Documents, and Dynamic Client Registration as a fallback. Tokens are opaque and stored only as SHA-256.

Directory reviewers: you do not need an iPhone. The submission notes carry a reviewer code; enter it on the consent page in place of the pairing code (or send it as pairing_code to POST /v1/tokens/exchange for a REST token). It signs you into a demo account with a realistic week of alarms and a simulated iPhone that applies every change within about two seconds, so pending becomes applied and the next read reflects it. The demo account resets daily and cannot see any real user.

Example prompts

"When do I wake up tomorrow?" → get_next_wake "Show my wakes for the next week." → list_upcoming_wakes "Move Monday's wake to 7:30, I have a flight at 9:40." → move_occurrence "Skip Saturday's alarm." → skip_occurrence "Add a note to tomorrow's wake: dentist first." → set_occurrence_note "Wake me at 5:45 on the 5th for the hike." → propose_wake "Is that change on my phone yet?" → list_upcoming_wakes (pending intents are flagged until the phone applies them)

Tools

All scoped to the signed-in user; all times local to the user's zone (returned as tz). Occurrence ids are <alarm_id>@<YYYY-MM-DD> and stable.

ToolKindReturns
get_next_wakeread-onlythe next occurrence that will actually ring, plus seconds_until
list_upcoming_wakes(days=7)read-onlyevery occurrence in the window (max 31 days) incl. skipped, moved, proposed, pending
propose_wake(date, time, reason?)queues intenta new one-time wake; the phone creates it on next sync
move_occurrence(occurrence_id, time)queues intentthat day only; also un-skips
skip_occurrence(occurrence_id)queues intentsilences that day; never deletes the alarm
set_occurrence_note(occurrence_id, reason)queues intentnote shown on the lock screen when it rings

Write tools are annotated destructiveHint: false and idempotentHint: true: they add a proposal and never remove anything, and repeating a call with the same arguments returns the same intent (deduplicated: true). Resource pace://wakes/upcoming mirrors list_upcoming_wakes(7).

Refused, with an actionable error: a date in the past, an occurrence id that is not one of your upcoming wakes (unknown alarm, disabled, wrong weekday, already rang, more than 60 days out), a note over 280 characters, and any write from a read-only token.

Trust boundary

ActorCanCannot
Agent (bearer token)read your occurrences, queue intents for your accounttouch another user, edit alarms directly, ring, silence
Serverstore snapshots, queue intents, mint tokensschedule AlarmKit, decide autonomy
iPhone (device token)push its alarms, pull + ack intents, apply to AlarmKitbe driven by the server; it always decides

Data

Accounts are pseudonymous: a random id, your IANA time zone, the alarms your phone last pushed, and the intents agents queued. Not stored: name, email, phone number, location, IP addresses, or conversations, only tool arguments. Unpairing from the app hard-deletes everything, and every agent token dies with it. Full policy: privacy.