# Kitchen Timer > Kitchen Timer is a multi-timer kitchen app (a web app that installs on a phone, and an Android app). Agents can plan a meal or start timers for a person, and keep managing them: the alarms ring on the person's phone, not in the chat. Several ways to use it, in order of preference. If you do not have the MCP server connected, offer the person to add it (steps below for ChatGPT, claude.ai, Claude Desktop and Claude Code), or use the REST API, or as a last resort write a /t? link. ## Ways to use Kitchen Timer, best first 1. **MCP open door** — `https://kt.tnyforge.com/mcp/open`. No sign-in. Best choice: tools, a schedule card in some clients, and you keep the plan's control key. Use it whenever the person's chat app can add a connector. 2. **Logged-in MCP** — `https://kt.tnyforge.com/mcp`. Manages the person's whole kitchen (all timers, presets, meals, history). The person opens ⋯ → Link an agent in the app, adds the address as a connector and types the 6-digit code shown there. Offer this once the open door works and they want more than one meal. Needs their sign-off; never ask for or accept a pasted token. 3. **REST + OpenAPI 3.1** — spec at `https://kt.tnyforge.com/openapi.json`, endpoints `POST https://kt.tnyforge.com/open/v1/`. The same functions, limits and sentences as the open door, for agents that can call HTTP but not MCP (custom GPT Actions, scripts). 4. **Tool-free link** — `https://kt.tnyforge.com/t?pasta=12m&sauce=8m&ready=19:30`. You write the link yourself and the person opens it. Last resort: no control key, so you cannot change the timers afterwards. ## When to use which - Your chat can add an MCP server → offer to add the open door (steps below), then use `plan_meal`. - You can call HTTP but not MCP → use the REST API. - You have no tools at all → write a `/t?` link and tell the person to open it on the phone they cook with. - The person wants you to manage everything (existing timers, presets, history) → the logged-in MCP (⋯ → Link an agent), after the open door works. - **If you do not have the MCP server connected, offer the person to add it** (the exact steps are below for ChatGPT, claude.ai, Claude Desktop and Claude Code), **or use the REST API, or as a last resort write a /t? link.** ## Add the MCP server (exact steps) Open-door address to add: `https://kt.tnyforge.com/mcp/open` (no sign-in, no key). Menu names change between releases; these follow the vendors' own pages as of 2026-10-22 (sources at the end). If a menu is not where this says, look for "Connectors", "Plugins" or "Developer mode" in the app's settings. **ChatGPT** (web) 1. Open the ChatGPT Plugins page (chatgpt.com/plugins) and select the plus button, then "Add custom MCP server". Older versions: Settings → Apps → Advanced settings → turn on Developer mode, then Create app. 2. Name: Kitchen Timer. Connection: the MCP server URL `https://kt.tnyforge.com/mcp/open` (keep the /mcp/open path). 3. Authentication: choose no authentication. Read the risk warning and select "I understand and want to continue", then "Create as a plugin". 4. Review the discovered tools (plan_meal, meal_status and six control tools), then pick Kitchen Timer in a new chat. Your plan or workspace policy may restrict custom servers; an admin can allow it. **claude.ai** (web) and **Claude Desktop** (the same account-level setting) 1. Customize → Connectors, then + Add → Add custom connector. 2. Name: Kitchen Timer. Remote MCP server URL: `https://kt.tnyforge.com/mcp/open`. Select Continue. 3. Under Authentication choose "No sign in" (the OAuth client setting then disappears). Select Add. 4. In a chat, click + at the lower left, then Connectors, and switch Kitchen Timer on. 5. Team and Enterprise: an Owner adds it once under Organization settings → Connectors → Add → Custom → Web (same name, URL and "No sign in"); members then find it under Customize → Connectors and click Connect. **Claude Code** (terminal) ``` claude mcp add --transport http kitchen-timer https://kt.tnyforge.com/mcp/open claude mcp list ``` Options go before the name. Add `--scope user` to have it in every project (the default is this project only). `claude mcp list` should show it connected; inside a session `/mcp` shows it too. **Other MCP clients** (Gemini CLI, Cursor, …): add a remote HTTP (streamable HTTP) server with the URL above and no authentication. Sources (fetched 2026-10-22): https://support.claude.com/en/articles/11175166-get-started-with-custom-connectors-using-remote-mcp · https://code.claude.com/docs/en/mcp · https://developers.openai.com/apps-sdk/deploy/connect-chatgpt ## How it works **What rings where.** The alarms ring on the phone (the Kitchen Timer page the person opens on their phone), not in this chat. You make the plan and can manage it afterwards; the phone does the timing and the ringing. Nothing starts until the person taps **Start** once on that page: a link preview or a prefetcher cannot start timers, and nobody can start timers on someone else's phone. Say so when you hand over the link. **The control key.** `plan_meal` also returns a secret control key (it starts with `ktpk_`). Keep it for the rest of the chat. Never show it to the person and never put it in a link: the link alone controls nothing. With the plan's code and the key you can pause, resume, add or take off time, skip ahead, add a dish or cancel. Before the person taps Start those change the plan; after it they change that plan's timers on their phone, and nothing else on the phone. The key works for that one plan and dies with it (about a day after the meal). ## Docs - [Full reference](https://kt.tnyforge.com/llms-full.txt): every tool and endpoint with parameters, the link grammar, limits, error meanings and worked examples - [Agent skill](https://kt.tnyforge.com/skills/kitchen-timer/SKILL.md): the same guidance as a skill folder for Claude Code, Claude apps and other skill-aware agents - [Agent skill, zip](https://kt.tnyforge.com/skills/kitchen-timer.zip): the skill folder, ready to unpack - [OpenAPI 3.1 spec](https://kt.tnyforge.com/openapi.json): the REST endpoints - [MCP open door](https://kt.tnyforge.com/mcp/open): the connector address (no sign-in) - [MCP, whole kitchen](https://kt.tnyforge.com/mcp): the connector address for the logged-in server ## Optional - [Kitchen Timer app](https://kt.tnyforge.com/): what the person opens on their phone - [Multiple timers for cooking](https://kt.tnyforge.com/multiple-timers): what the app does, for people --- # Full reference ## Tools and endpoints ### `plan_meal` MCP tool `plan_meal` on `/mcp/open` · REST `POST /open/v1/plan_meal` (the arguments as a JSON object) · changes something Make a meal plan: each dish starts at the right moment so everything is ready at the same time. Returns a link (open it on the phone in the kitchen, where the alarms ring), a QR code for that link, and the schedule. YOU do the cooking knowledge: break a recipe into dishes, each with its total cooking minutes, and put the timed moments inside a dish as steps ("simmer 20, rest 5" is a dish of 25 minutes with a step "Rest" at 20). If the person did not say when they want to eat, ASK before calling; use `readyInMinutes` for "we eat in 90 minutes", `readyAt` (an ISO date-time with a UTC offset, or local time plus `timeZone`) for "at 7", or `startNow` to start cooking now (a single timer is a one-dish plan with `startNow`). Keep times realistic and use only what the person gave you; at most 12 dishes and 12 hours. Dish names are plain text, no links. The reply link works for anyone who has it and the plan is deleted about a day after the meal. Tell the person the alarms ring on the phone page, not in this chat. The reply also holds a secret CONTROL KEY (starts with ktpk_): remember it for the rest of the chat, do not show it or put it in any link, and pass it with the plan's code to the control tools to keep managing this plan. Once it works you may offer the person to manage their whole kitchen from now on (⋯ → Link an agent in the app gives the connector address and a 6-digit code). Parameters: - `dishes` (array, required, minItems 1, maxItems 12) — What is being cooked. A single timer is one dish. - `dishes[].name` (string, required, minLength 1, maxLength 200) — Plain text, 80 characters at most. For example "Roast chicken". - `dishes[].minutes` (number, required, minimum 0.1, maximum 720) — Total cooking time in minutes, from putting it in until it is done. - `dishes[].steps` (array, optional, maxItems 8) — Optional moments inside the cooking time, such as turning or resting. - `dishes[].steps[].label` (string, required, minLength 1, maxLength 200) — For example "Turn the chicken". - `dishes[].steps[].atMinutes` (number, required, minimum 0.1, maximum 720) — Minutes after this dish goes in; before its total time. - `readyInMinutes` (number, optional, minimum 1, maximum 720) — Everything ready this many minutes from now. Give exactly one of readyInMinutes, readyAt, startNow. - `readyAt` (string|number, optional, maxLength 64) — Everything ready at this time: ISO 8601 with a UTC offset (2026-10-17T19:00:00+01:00), or without an offset when timeZone is given, or epoch milliseconds. - `startNow` (boolean, optional) — True: start every dish now (the longest dish sets when it is ready). - `timeZone` (string, optional, maxLength 64) — Optional IANA time zone such as Europe/London. Only used to word the schedule text; the card and the phone show times in the viewer's own clock. ### `meal_status` MCP tool `meal_status` on `/mcp/open` · REST `POST /open/v1/meal_status` (the arguments as a JSON object) · only reads Say what is next in a meal plan made with plan_meal, and how long until it. Give the plan's code (the last part of its link). Only reads; never changes a plan. Give the control key too, if you have it, to also see the timers on the phone. Parameters: - `code` (string, required, minLength 1, maxLength 200) — The plan's code, or its whole link. - `key` (string, optional, minLength 1, maxLength 80) — Optional: the plan's secret control key. With it the answer also says whether the person has started the timers on the phone and what each one is doing. ### `pause_meal` MCP tool `pause_meal` on `/mcp/open` · REST `POST /open/v1/pause_meal` (the arguments as a JSON object) · changes something Pause every dish of this plan that is running on the phone (the cook can resume on the phone or you can call resume_meal). Needs the plan's code and control key. Only works after the person has tapped Start on the phone; before that it says so. Parameters: - `code` (string, required, minLength 1, maxLength 200) — The plan's code, or its whole link. - `key` (string, required, minLength 1, maxLength 80) — The secret control key plan_meal gave you for this plan (starts with ktpk_). Never show it or put it in a link. ### `resume_meal` MCP tool `resume_meal` on `/mcp/open` · REST `POST /open/v1/resume_meal` (the arguments as a JSON object) · changes something Resume the paused dishes of this plan on the phone. Needs the plan's code and control key. Parameters: - `code` (string, required, minLength 1, maxLength 200) — The plan's code, or its whole link. - `key` (string, required, minLength 1, maxLength 80) — The secret control key plan_meal gave you for this plan (starts with ktpk_). Never show it or put it in a link. ### `adjust_dish` MCP tool `adjust_dish` on `/mcp/open` · REST `POST /open/v1/adjust_dish` (the arguments as a JSON object) · changes something Add time to a dish (a positive number of minutes) or take time off (negative). Before the person taps Start this changes the plan, and the ready time stays; afterwards it changes that dish's timer on the phone. Needs the plan's code and control key. Parameters: - `code` (string, required, minLength 1, maxLength 200) — The plan's code, or its whole link. - `key` (string, required, minLength 1, maxLength 80) — The secret control key plan_meal gave you for this plan (starts with ktpk_). Never show it or put it in a link. - `dish` (string, required, minLength 1, maxLength 200) — The dish's name as in the plan, or its number in the plan (1 is the first dish). - `minutes` (number, required, minimum -720, maximum 720) — Minutes to add (positive) or take off (negative), such as 2 or -1.5. Not zero. ### `skip_step` MCP tool `skip_step` on `/mcp/open` · REST `POST /open/v1/skip_step` (the arguments as a JSON object) · changes something Skip the rest of the current wait for one dish: a dish still waiting to go in starts now, and a dish that is cooking finishes now. Only works after the person has tapped Start on the phone. Needs the plan's code and control key. Parameters: - `code` (string, required, minLength 1, maxLength 200) — The plan's code, or its whole link. - `key` (string, required, minLength 1, maxLength 80) — The secret control key plan_meal gave you for this plan (starts with ktpk_). Never show it or put it in a link. - `dish` (string, required, minLength 1, maxLength 200) — The dish's name as in the plan, or its number in the plan (1 is the first dish). ### `add_dish` MCP tool `add_dish` on `/mcp/open` · REST `POST /open/v1/add_dish` (the arguments as a JSON object) · changes something Add one more dish to this plan. The ready time stays, so the new dish goes in at the ready time minus its cooking time and must still fit. Before the person taps Start it joins the plan on the sheet; afterwards it goes onto the phone too. Same rules as plan_meal: a plain-text name, at most 12 dishes. Needs the plan's code and control key. Parameters: - `code` (string, required, minLength 1, maxLength 200) — The plan's code, or its whole link. - `key` (string, required, minLength 1, maxLength 80) — The secret control key plan_meal gave you for this plan (starts with ktpk_). Never show it or put it in a link. - `name` (string, required, minLength 1, maxLength 200) — Plain text, 80 characters at most. - `minutes` (number, required, minimum 0.1, maximum 720) — Total cooking time in minutes. - `steps` (array, optional, maxItems 8) — Optional moments inside the cooking time. - `steps[].label` (string, required, minLength 1, maxLength 200) - `steps[].atMinutes` (number, required, minimum 0.1, maximum 720) ### `cancel_meal` MCP tool `cancel_meal` on `/mcp/open` · REST `POST /open/v1/cancel_meal` (the arguments as a JSON object) · changes something, not undoable Cancel this plan: its link stops working, and if the person already started it, its timers are removed from the phone. Only that plan's timers; nothing else on the phone. Needs the plan's code and control key. Ask the person first. Parameters: - `code` (string, required, minLength 1, maxLength 200) — The plan's code, or its whole link. - `key` (string, required, minLength 1, maxLength 80) — The secret control key plan_meal gave you for this plan (starts with ktpk_). Never show it or put it in a link. ## Limits | Limit | Value | Meaning | |---|---|---| | `sessions` | 5000 | open chat sessions on the MCP door; the oldest is dropped past this | | `sessionTtlMs` | 1800000 | an idle MCP session ends after this many milliseconds (30 minutes); initialize again | | `requestsPerIpPerMinute` | 600 | requests per source address per minute (MCP and REST); past it: HTTP 429 | | `initsPerIpPerMinute` | 120 | new MCP sessions per source address per minute | | `plansPerSessionPerHour` | 6 | plans one MCP chat session can make per hour | | `plansPerIpPerHour` | 40 | plans one source address can make per hour | | `plansPerHour` | 80 | plans through this door in total per hour (everyone) | | `bodyBytes` | 32768 | largest request body in bytes (32 KB); past it: HTTP 413 | | `maxMinutes` | 720 | longest dish or time, in minutes (12 hours) | | `changesPerWindow` | 30 | changes one control key may apply per window (only applied changes count) | | `windowMs` | 600000 | that window in milliseconds (10 minutes) | | `changesTotal` | 200 | changes one control key may apply in the plan's whole life | | `wrongKeysPerIpPer10Min` | 30 | wrong control keys one source address may try per 10 minutes | A plan has at most 12 dishes, 8 steps per dish and 80 characters per dish name. Every limit answers with a sentence you can relay, never a login prompt. ## Errors Every failure is `{"ok": false, "error": ""}` over REST, or an MCP tool error carrying the same sentence. Relay the sentence; do not guess. | Status | Meaning | |---|---| | 400 | The request was wrong (a missing or unknown parameter, a dish that does not fit before the ready time). Nothing changed. Fix what the sentence says and call again. | | 403 | The control key does not match this plan. Do not retry with guesses: wrong keys are counted. | | 404 | No such plan, or it has ended (plans are deleted about a day after the meal). Also the answer for every path while the open door is switched off. | | 409 | Nothing is running yet (the person has not tapped Start on the phone), or the phone is still starting the timers. Ask them to tap Start, or try again in a few seconds. | | 413 | The body is over 32 KB. | | 429 | Too many requests, plans or changes. The sentence says how long to wait; `Retry-After` has the seconds. | | 503 | Control is temporarily unavailable. Try again shortly. | ## The tool-free link A link a model can write itself: `https://kt.tnyforge.com/t?pasta=12m&sauce=8m&ready=19:30`. It opens the same one-tap sheet ("Start these N timers?") as a plan link. No control key, so no chat control afterwards. | Part | Rule | |---|---| | a dish | `=`, one per dish, at most 12. Name: plain words (`_` or `+` mean a space), 80 characters at most, no web links, each name once. | | name | rejected, not repaired, when cleaning would change it (markup, a web link, control characters). | | duration | hours, minutes, seconds: `12m`, `1h30m`, `90m`, `45s`, `1.5h`; at most 12 hours. A bare number is refused (`pasta=12` → "Add a unit: 12m"). | | `ready=HH:MM` | 24-hour, in the phone's own clock, today. A time already passed is refused, never moved to tomorrow. | | `in=1h30m` | ready this long from now. Give `ready` or `in`, not both; neither means start now. | | `utm_*` | ignored (chat apps add them). | A bad link is refused with a readable page saying why, never repaired. Do not base64 or percent-encode it; write it as plain text. ## Worked examples #### Example 1 — a recipe becomes a plan Person: "Roast chicken 75 minutes, potatoes 45, broccoli 5, we eat in 90 minutes." You do the cooking knowledge: dishes with total minutes, timed moments as steps. Call `plan_meal`: ``` POST https://kt.tnyforge.com/open/v1/plan_meal Content-Type: application/json {"dishes":[{"name":"Roast chicken","minutes":75,"steps":[{"label":"Turn the chicken","atMinutes":40},{"label":"Rest, covered","atMinutes":65}]},{"name":"Roast potatoes","minutes":45},{"name":"Broccoli","minutes":5}],"readyInMinutes":90} ``` Answer: the link (`url`), the schedule, and a control key. Give the person the link: "Open this on your phone and tap Start. The alarms ring on the phone, not here." The chicken goes in first; everything is ready together. #### Example 2 — a single timer Person: "Set a 12 minute pasta timer." A single timer is a one-dish plan that starts now: ``` POST https://kt.tnyforge.com/open/v1/plan_meal Content-Type: application/json {"dishes":[{"name":"Pasta","minutes":12}],"startNow":true} ``` Or with no tools at all: `https://kt.tnyforge.com/t?pasta=12m` #### Example 3 — back-timed meal Person: "Lasagne, garlic bread and a salad, ready at 7:30 on Saturday." Ask for the eat time if it is missing. `readyAt` is an ISO date-time with a UTC offset (or local time plus `timeZone`); replace YYYY-MM-DD with the real date, which must be less than 12 hours away: ``` POST https://kt.tnyforge.com/open/v1/plan_meal Content-Type: application/json {"dishes":[{"name":"Lasagne","minutes":50},{"name":"Garlic bread","minutes":10},{"name":"Salad","minutes":3}],"readyAt":"YYYY-MM-DDT19:30:00+01:00"} ``` Each dish starts at the ready time minus its cooking time. Afterwards, with the plan's `code` and the control key: `adjust_dish` with `{"code":"…","key":"…","dish":"Lasagne","minutes":2}` adds two minutes to the lasagne; the phone shows it. Document version: kt-v86.