Kairotope

Developer docs

Kairotope plans a person's errands and commitments against real opening hours and live traffic, and remembers their places (home, their pharmacy, their kids' schools) and preferences. AI assistants reach it through an MCP server; other software can use the equivalent REST API. Both call the same planning engine.

Connecting

Endpointhttps://kairotope.com/mcp
TransportMCP streamable HTTP (stateless; every request is a POST with a JSON response, no session to keep)
ProtocolModel Context Protocol, server name kairotope. Tools only (no resources or prompts). The server sends usage instructions in its initialize result.
AuthAuthorization: Bearer kt_… on every request (see below)

Authentication

Every call is tied to one person's account (their household's saved places, preferences and plans) by a token. There are two ways to get one, and both reach the same account.

Sign in with Kairotope (OAuth 2.1)

For assistants that connect by OAuth, such as Meta Muse, ChatGPT and Claude. Point the assistant at https://kairotope.com/mcp; it discovers everything else from the first 401.

Protected resource metadata/.well-known/oauth-protected-resource/mcp (RFC 9728)
Authorization server metadata/.well-known/oauth-authorization-server (RFC 8414); issuer https://kairotope.com
Client registrationA client metadata document URL as client_id, or dynamic registration at /oauth/register (RFC 7591)
GrantAuthorization code with PKCE (S256 only); the response carries iss. Scope kairotope.
TokensAccess tokens last 1 hour. Refresh tokens rotate on every use; an old one stops working.

The person signs in with their email and presses Allow. The connection appears in their account under Assistants, where they can disconnect it, which also ends its refresh token.

Connector token

For assistants that take a pasted credential, such as Meta Muse, and for the REST API.

  1. The person signs in at kairotope.com with their email (a one-time link; no password). The same link creates a free account.
  2. In their account, under Assistants, they create a token. It starts with kt_ and is shown once. Kairotope stores only a hash of it.
  3. They give it to their assistant through its secure credential prompt. The assistant sends it as a bearer token.

Connector tokens don't expire, and can be revoked at any time in the account, under Assistants. On /mcp, a missing, invalid, expired or revoked token gets HTTP 401 with WWW-Authenticate: Bearer resource_metadata="https://kairotope.com/.well-known/oauth-protected-resource/mcp", scope="kairotope", which is where OAuth clients start.

Tools

Every tool returns the response envelope as structuredContent, with its message also as text content.

plan_errands

Turn a request like "pick up my prescription, get groceries and be at dinner by 7" into an ordered, traffic-aware plan with a leave-by time. Call once per request.

ParameterTypeDescription
intention requiredstringThe user's request in their own words. Saved places can be named ("my pharmacy", "home").
datestringYYYY-MM-DD if known; otherwise inferred from the request.
start_locationstringOnly when starting somewhere other than the saved home.
commitmentsarrayFixed calendar events the plan must respect: { title, start, end?, location? }. start/end are ISO 8601 or HH:MM local.

revise_plan

Apply a change to an earlier plan ("skip the shoe return", "we're leaving 20 minutes late"). Returns a new plan with a new planId.

ParameterTypeDescription
plan_id requiredstringThe plan to change.
change requiredstringWhat to change, in the user's words.

confirm_plan

The user explicitly accepted the plan ("yes", "confirm", "sounds good"). Never call it on silence or a topic change. Marks it as the one they're following and schedules a one-tap "how did it go?" email for that evening. Every plan's message ends by inviting the user to confirm.

ParameterTypeDescription
plan_idstringThe plan to confirm. Leave out to confirm the household's latest plan (made in the last 12 hours).

list_plans

The user's upcoming plans from Kairotope's records: one per day, marked confirmed (locked in) or draft (planned, never confirmed), with the leave time and stops. Use it for "what are my plans?" or "what's on tomorrow?" rather than your own memory. No parameters. Also GET /api/v1/plans. On the day, today's plan also carries latestAlert: the newest traffic alert Kairotope emailed for a drive still to come.

check_plan

Whether a plan still holds: the drives still to come, each with its planned leave time and its latest live-traffic drive time, and any traffic alert. It answers from Kairotope's own live-traffic checks, which start an hour before each drive on a plan's day (trial and Plus). It makes no new plan and uses none of the allowance, so call it for "am I still on time?" and for any check you schedule before the user leaves. Never re-plan just to re-check. Returns ok with check: planId, date, status, liveTraffic, drives and, when there is one, alert. From a scheduled check, message the user only when check.alert is set. Also GET /api/v1/plans/check.

ParameterTypeDescription
datestringYYYY-MM-DD. Leave it out for today's plan, else the next upcoming one.

cancel_plan

The user dropped a plan before its day ("cancel Saturday", "never mind tomorrow"). Cancelled plans leave list_plans and get no check-in email. Also POST /api/v1/plans/{planId}/cancel.

ParameterTypeDescription
plan_idstringThe plan to cancel (from list_plans or a plan result), with its revisions.
datestringYYYY-MM-DD: cancel every plan for that day. With neither, cancels the only upcoming plan, or asks which.

report_outcome

How the plan went, when the user says so.

ParameterTypeDescription
plan_id requiredstringThe plan.
status requiredstringcompleted, partial, abandoned (the day didn't go to plan) or cancelled (dropped before the day; same as cancel_plan).
stop_seqintegerA single stop this is about.
ratinginteger1–5.
notestringAnything else the user said.

remember

Save a place or preference for future plans. Nothing is saved without the user saying so. Labels are the user's own names for places: "my gym" saved at 123 Main St means "stop at the gym" plans to 123 Main St. "my gym", "our gym", "the gym" and "gym" are the same place, and "home", "my house" or "our home" all mean the household's one home.

Every assistant a person connects shares one account. If something different is already saved under that name (a place more than 200 m away, or a different preference value), maybe by another assistant, nothing changes: the result is confirm_replace with a question saying what's saved, where it came from and what would replace it. Ask it; only on a yes, call again with replace: true and the result's confirm_token (it expires after 10 min). replace: true without a valid token is ignored and brings the question back. An assistant changing a place it saved itself isn't asked: that's the user correcting it there. Preferences always ask, since they don't record which assistant set them.

ParameterTypeDescription
type requiredenumplace or preference.
labelstringPlace: the user's name for it ("home", "my pharmacy", "Sam's school").
wherestringPlace: an address or business name ("CVS on Parmer Ln").
kindstringPlace: home, pharmacy, grocery, school, work, gym, doctor, restaurant or other.
keystringPreference name, e.g. max_drive_minutes.
valuestring, number or booleanPreference value.
replacebooleanOnly after the user said yes to a confirm_replace question: replace what's saved. Never on the first call.
confirm_tokenstringWith replace: the confirm_token from the confirm_replace result the user said yes to.

forget

Delete a saved place or preference.

ParameterTypeDescription
name requiredstringThe place label or preference name.

get_household

List the user's saved places and preferences. No parameters.

ping

Connectivity and latency check. Doesn't plan anything or count against limits.

ParameterTypeDescription
sleep_msintegerWait this long before answering (0–25,000).

Responses

Every tool answers with the same envelope. kind says what happened:

kindMeaningWhat to do
planA plan, in plan, with its planId.Relay it. Offer any suggestions.
clarifySomething needs the user's answer, in questions.Ask, then call plan_errands again with the answers.
infeasibleIt can't all fit; reasons says why.Explain, and offer to drop or move something.
limitThe household's allowance is used up for now, or a Free household tried to change a plan.Relay message: it says what to do and when.
not_foundNo such plan, place or preference.Check the id or name.
confirm_replaceSomething different is already saved under that name; nothing changed. question says what's saved and what would replace it (REST: HTTP 409).Ask the user question. On a yes, call remember again with replace: true and its confirm_token.
okDone (saved, forgotten, confirmed, recorded, or the household summary).Confirm back to the user.
probeA connectivity test was recognized as one and not planned.Use ping for connectivity checks.
errorSomething went wrong; message says what. The tool result is marked isError.Relay message.

Other envelope fields: localNow and today (the household's current local time and date, on every result), plans (from list_plans), message (a short summary that's safe to relay as is), suggestions (places worth saving, each with a question to ask), reused (true when an identical request made moments earlier was answered from the stored plan), mapUrl (on plans, confirm_plan, check_plan and each of list_plans's plans, when plan pages are on for the account: a private link to the plan on a map, with each stop's times and directions; give it to the user as is; it works without signing in until 12 hours after the plan's last stop, and is also in message), and nextStep (an instruction for the assistant, not to read out: on plans, offer to lock the plan in and call confirm_plan if the user agrees). Over MCP, nextStep also arrives as a second text block.

Notices. Assistants can't receive a push from a connector, so Kairotope emails traffic alerts on the day of a plan, and any result may also carry notices: things the user should hear before your answer. A traffic_alert notice is an alert Kairotope emailed (with sentAt) that the user hasn't yet seen through an assistant, while its drive is still to come. A checkin notice asks how a finished day went, for up to 3 days after it: ask the user, then call report_outcome with its planId. Each notice has a message to relay and a nextStep for you, and comes once per account, on whichever assistant calls first. Results with kind error, not_found, limit or probe carry none. Over MCP, notices arrive as the first text block.

The plan object

FieldDescription
statuson_track, or at_risk when a buffer is thin.
leaveByWhen to leave the start location (ISO 8601 with offset).
stopsIn order: seq, label, placeName, address, arriveAt, departAt, legMinutes (drive from the previous stop), freeMinutesBefore, isFixed (a commitment that can't move), note.
totalDriveMinutesAll driving in the plan.
minutesSavedVsNaiveDriving saved compared with doing the errands in the order listed.
unscheduledErrands that couldn't fit, each with a reason. Always tell the user.
warningsAssumptions and cautions, e.g. which branch was chosen or rain on the drive.
explanationA plain-language summary. Every time and number in it comes from the fields above.

Example

Request (MCP tools/call):

{
  "jsonrpc": "2.0", "id": 1, "method": "tools/call",
  "params": {
    "name": "plan_errands",
    "arguments": {
      "intention": "Pick up my prescription, get groceries and be at dinner at The Oasis by 7",
      "date": "2026-10-03"
    }
  }
}

Result (structuredContent, abridged):

{
  "kind": "plan",
  "planId": "3f6c…",
  "message": "Leave at 5:12 PM. Walgreens first, since it closes at 6:00 PM… Say “confirm” to lock this plan in, and I'll check in that evening to see how it went.",
  "plan": {
    "planId": "3f6c…",
    "status": "on_track",
    "leaveBy": "2026-10-03T17:12:00-05:00",
    "stops": [
      { "seq": 1, "label": "Pick up prescription", "placeName": "Walgreens",
        "arriveAt": "2026-10-03T17:21:00-05:00", "departAt": "2026-10-03T17:31:00-05:00",
        "legMinutes": 9, "isFixed": false, "note": "Pharmacy closes at 6:00 PM." },
      { "seq": 2, "label": "Get groceries", "placeName": "H-E-B",
        "arriveAt": "2026-10-03T17:39:00-05:00", "departAt": "2026-10-03T18:09:00-05:00",
        "legMinutes": 8, "isFixed": false },
      { "seq": 3, "label": "Dinner", "placeName": "The Oasis",
        "arriveAt": "2026-10-03T18:50:00-05:00", "departAt": "2026-10-03T20:20:00-05:00",
        "legMinutes": 31, "isFixed": true }
    ],
    "totalDriveMinutes": 48,
    "minutesSavedVsNaive": 6,
    "unscheduled": [],
    "warnings": ["I assumed your prescription is at Walgreens (1505 W Whitestone Blvd)."],
    "explanation": "Leave at 5:12 PM. Walgreens first, since it closes at 6:00 PM…"
  },
  "suggestions": [
    { "label": "my pharmacy", "where": "Walgreens, 1505 W Whitestone Blvd", "kind": "pharmacy",
      "question": "Is the Walgreens on W Whitestone Blvd your usual pharmacy?" }
  ]
}

Guidance for assistants

The server sends these rules in its instructions. They're what makes plans correct across conversations:

  1. Call plan_errands once per request. For changes, use revise_plan rather than re-sending the request.
  2. When the user says where they live, or which pharmacy, store or school they use, call remember. Don't keep it only in your own memory or paste addresses into requests.
  3. If a result has suggestions, ask each question, and call remember only when the user says yes.
  4. Relay times, stop order, warnings and unscheduled exactly as returned. Don't recompute or round them.
  5. Plans are drafts until confirmed. After presenting one, ask the user explicitly whether to lock it in, and call confirm_plan (no arguments needed) only on an explicit yes.
  6. To drop a plan, call cancel_plan.
  7. If a result's kind is error or not_found, it did not happen: tell the user, never say it worked.
  8. Use ping for connectivity checks, never plan_errands.
  9. If a result's kind is limit, relay its message and don't retry.
  10. For "what are my plans?", call list_plans and answer from it: it knows which plans are confirmed and which are only drafts.
  11. Use localNow (every result has it) for "today", "tomorrow" and how far away things are.
  12. If a result has notices, pass them on before your answer: alerts with their times exactly as written; for a check-in, ask how the day went and call report_outcome.
  13. To see whether a plan still holds, including from a check you schedule yourself, call check_plan, never plan_errands or revise_plan. From a scheduled check, message the user only when it returns an alert.

Plans, limits and coverage

Every new account starts with a free trial of the full product. Each plan and each revision counts toward a household's allowance. Repeated identical requests, connectivity checks and ping don't.

PlanAllowanceWhat's included
Free trial20 plans over 30 days (at most 5 plans in a day)Everything: live traffic at your departure times, rush-hour and weather adjustments, plan changes, and traffic alerts by email on the day of a plan.
Free4 plans a month, from when the trial endsPlans with typical traffic (not checked against live traffic) and no weather adjustments. Plans can't be changed with revise_plan; ask for a new plan instead. No traffic alerts on the day.
Plus ($6.99 a month or $69.90 a year, pre-launch pricing)Unlimited, with fair use of 10 plans in a day and 60 plans a monthEverything in the trial, for one person.

Prices and what each tier includes are on the pricing page. When an allowance runs out, calls return kind: "limit" with a message to relay, saying when more plans are available. Plans made on Free say in their message and warnings that they use typical traffic. get_household includes the household's tier (in plan) and what's left of its allowance.

REST API

The same operations are available over HTTPS at https://kairotope.com/api/v1 with the same bearer token, for software that doesn't speak MCP. The OpenAPI 3.1 description is at /api/v1/openapi.json.

Data handling

Questions or problems: hello@kairotope.com