Using it with AI agents

POST /api/mcp is a Model Context Protocol server: the same API tokens, the same scopes, and a set of tools a model can call. It is what "put that on my to-do list" means when the thing being asked is an AI agent rather than the app.

The short version

Make a token (Settings → Integrations → New token, then the An AI assistant (MCP) button), then hand it to whatever you are using.

Claude Code, Codex, or anything else with a shell — one command:

claude mcp add --transport http ontoplano https://app.ontoplano.com/api/mcp \
  --header "Authorization: Bearer onto_YOUR_TOKEN_HERE"

Or just ask, in words. Paste this to the assistant, with your token in place of the last line, and let it do the setting up:

I use ontoplano — a life management app with an MCP server. Please connect to it
and use it whenever I ask you about my week, my todos, my diary, my notebooks,
my shopping list or my recipes.

  MCP endpoint:  https://app.ontoplano.com/api/mcp
  Transport:     streamable HTTP (stateless — no session, GET is not supported)
  Auth:          an Authorization: Bearer header

Once connected, list the tools you were offered and tell me what I asked you to
do today. Do not write anything into my account until I ask you to.

Token: onto_YOUR_TOKEN_HERE

Your own instance answers at https://your-host/api/mcp — the address is the one you type into the browser, with /api/mcp after it.

Two things the prompt is doing on purpose. It names what the app is for, so the assistant reaches for it instead of asking you to repeat yourself; and it says not to write anything yet, so the first thing it does is show you what it can see rather than what it has done.

Blocks and to-dos are different things

Worth knowing before you ask for anything, because it is the one distinction an assistant gets wrong: a to-do is something to do with no hour attached, and a block is an hour. "Ring the dentist" is a to-do; "deep work from 9 to 11" is a block.

An assistant that only has add_todo answers the second by writing the time into the title — deep work 09:00–11:00 — and your day still looks empty. With schedule:write it puts a real block on the day, and it can answer for the ones already there:

Skip the gym and the stretching today, and put deep work on from 9 to 11.

finish_block takes both answers. Skipped is a real answer, not a failure to record one — a week that can only be told about the parts that went well is a week that starts lying by the second one.

It can also change what is there, which matters more than it sounds:

Push the study block to four, and put down that I was actually organizing my bird pictures for the last hour and a half.

change_block moves and renames; cancel_block takes something off a day because it is not happening. Cancelled is not skipped. Skipped means you meant to do it and did not, and the weekly review asks about it; cancelled means the plan was wrong — the meeting moved, the class was called off. Without both, an assistant asked to move something has only one way to clear the old one off the grid, and it will use the wrong one: this is not hypothetical, it is what happened, and the day ended up holding a duplicate block and a skip that never took place.

Everything here is that day only. Moving this Thursday's gym never moves gym: the occurrence is detached and the weekly plan is left alone, which is the same thing alt-dragging it in the app does.

How it behaves

Four things are worth knowing before you grant a token:

It offers only what the token holds. tools/list is filtered by scope, so a token with today:read and nothing else is offered one tool. The scope is checked again on every call, because a client that was never offered a tool can still name one.

Nothing in it is new behaviour. Every tool calls the same service function the web page calls, so the ceilings, the validation and the ownership checks are the ones that already exist. A tool cannot be a way around a rule.

It is stateless. No session, no event stream, no state between calls — every request carries its own token and is answered on its own. A GET answers 405, because there is no server-initiated stream to open.

A refusal is an answer. A service saying "that is not a date" comes back as tool content the model can read and act on, not as a protocol error it can only give up on.

The tools are declared in one file — src/lib/server/mcp/tools.ts — and each carries the sentence a model reads to decide whether it is the thing it wants. The tools below lists every one, generated from that file, with the scope each needs.

Making the token

Settings → Integrations → New token. There is a button on that form called An AI assistant (MCP) which ticks exactly the scopes the tools need. Grant fewer if you want it to read and not write: the tools it was not granted are not offered to it at all, so an assistant with a read-only token does not know that add_todo exists.

The token is shown once, on the screen where you made it, with a link back to this page. It is revoked from the same place, and revoking it takes effect on the next request — there is no session to expire.

The tools

Every tool the server offers, with the exact description a model is handed — published from the same array that serves them, so the two cannot drift. A token is only offered the tools its scopes reach: a tool missing from tools/list is a permission not granted, not a feature that does not exist. The scopes themselves are on the permissions page.

today — Today's plan

What is on today: the blocks planned for it and the tasks pulled onto it. This is the answer to 'what am I meant to be doing', and the first thing to reach for before adding anything. Habits are not here — they are their own permission, and their own tool.

Needs today:read; read-only.

habits — Habits due today

The habits scheduled for today, each with its streak and whether it has been kept yet. Separate from the day's plan on purpose: whether somebody kept their habits is a more personal thing than what is on their calendar, so it is granted separately.

Needs habits:read; read-only.

tick_habit — Tick a habit

Tick a habit for a day: for something being built, the tick means it was done; for something being avoided, it means it happened. Name it or give the id habits gave; a name that matches two habits is refused rather than guessed. Ticking twice is not an error; the second call takes it back, which is how the app’s own tick behaves.

Needs habits:write; writes.

finish_block — Mark a block done or skipped

Answer for one block on the day: it happened, or it did not. Takes the id today gives for that block. Skipping is a real answer — say skipped when the person says they did not do it. It is NOT a way to clear something off the day: a skip goes into the week’s record and the review asks about it. To move a block use change_block; to take one off because it was never happening use cancel_block. todo takes an answer back, for one ticked by mistake.

Needs schedule:write; writes.

add_block — Put a block on a day

Add a one-off block to one day: a title, a start time and how long it runs. This is for "deep work from 9 to 11 today" — a thing with an hour. Use add_todo instead when there is no time attached, and change_block to move or rename something already on the day rather than adding a second copy of it. It does not touch the repeating week; this is that day only.

Needs schedule:write; writes.

change_block — Move or rename a block

Change one block on one day: its time, its day, how long it runs, or what it is called. This is "push the study block to four", "make it two hours", "that was actually client work". Takes the id today or upcoming gives. Only the fields you pass change. It affects that day only — moving this Thursday’s gym does not move gym — and it never edits the repeating week. Renaming keeps which part of life it belongs to and stops it being the named activity it was, because that is what saying it was something else means.

Needs schedule:write; writes.

cancel_block — Take a block off the day

Remove a block from a day because it is not happening — the meeting moved, the class was called off, it was put on the wrong day. This is NOT the same as marking it skipped: skipped means it was meant to happen and did not, which is a fact the weekly review asks about, and cancelled means it was never going to. Use finish_block with "skipped" for the first and this for the second. A repeating block is only removed from that one day.

Needs schedule:write; writes.

upcoming — The days ahead

Everything planned from today onwards — the blocks of the week, in order. Use it to answer questions about a day that is not today.

Needs schedule:read; read-only.

past — The days behind

What was on the days that have already happened, with what each one was answered — done, skipped, or nothing yet. Use it before correcting a week: it gives the ids finish_block needs. Ask for a week back with days: 7, or name the day it starts on.

Needs schedule:read; read-only.

search — Search everything written

One search over diary entries, notebooks, notes, ideas, goals, people, recipes and todos. Prefer this to guessing which room a thing is in.

Needs search:read; read-only.

todos — The todo list

Tasks with no date on them yet. A todo gains a date by being put on a day, which promotes it onto the week.

Needs tasks:read; read-only.

add_todo — Add a todo

Put a task on the todo list. Leave the date off unless the person said when — a todo with no date is the normal case here, not an unfinished one.

Needs tasks:write; writes.

finish_todo — Finish a todo

Mark a todo done, which is what "I did that" means here — it is not deleted, it moves to done and stays in the record. Ask todos first for the id.

Needs tasks:write; writes.

drop_todo — Delete a todo

Remove a todo entirely, because it is not going to happen and is not worth a record — "bin that one", "forget it". Different from finish_todo, which keeps it as something that was done. Gone for good; prefer finishing it when it actually happened.

Needs tasks:write; writes.

reopen_todo — Put a todo back on the list

Undo a finish or a drop: the todo goes back to not-done. Use it when something was ticked by mistake, or when a dropped thing turns out to matter after all. It keeps its notes, its day and everything linked to it.

Needs tasks:write; writes.

change_todo — Change a todo

Rewrite a todo’s title or notes. Only the fields given change. Moving it on or off a day is schedule_todo; done and not-done are finish_todo and reopen_todo.

Needs tasks:write; writes.

schedule_todo — Put a todo on a day

Give a todo a date, which moves it onto that day’s board. This is what "do it on Thursday" means here.

Needs tasks:write; writes.

unschedule_todo — Take a todo off its day

Take the date off a todo, which moves it back to the list of things with no time yet. This is "not today after all" — the todo is kept, it just stops being on a day.

Needs tasks:write; writes.

goals — Goals

What the person is working towards, by horizon, with the work counted against each. add_goal transcribes one they just said; close_goal says how one ended.

Needs tasks:read; read-only.

close_goal — Say how a goal ended

Close a goal: achieved, missed, or abandoned. Missed and abandoned are different — missed is a deadline that passed, abandoned is a decision to stop — and both are worth recording honestly rather than being rounded to one. Takes the id goals gives. There is no tool that opens a goal; that is the person’s to make.

Needs tasks:write; writes.

Attach todos or repeating blocks to a goal, so finishing them moves its progress. Adds to what is already linked; nothing is replaced. goals gives the goal id and what it already has on it.

Needs tasks:write; writes.

Detach todos or blocks from a goal. Only the ones named; everything else it counts stays.

Needs tasks:write; writes.

reopen_goal — Reopen a goal

Put a closed goal back to open. Its outcome note is cleared and the date it was closed on goes with it, so a reopened goal does not read as having been finished at some point in the past.

Needs tasks:write; writes.

change_goal — Change a goal

Rename a goal, or change its notes, horizon, start date, target or unit. Only the fields given change. Saying how it ended is close_goal, not this.

Needs tasks:write; writes.

diary — Recent diary entries

What has been written lately, newest first. An entry can belong to a notebook or to no notebook at all.

Needs notes:read; read-only.

write_entry — Write a diary entry

Add an entry. Markdown. Writing one when asked is the point of this tool — keep their words and their voice where you have them, and do not invent an entry nobody asked for. Put it in a notebook when it is about one subject; leave the notebook off for an ordinary day.

Needs notes:write; writes.

notebooks — Notebooks

The subjects being written against — a trip, a renovation, a book. Ask for these before writing an entry into one.

Needs notes:read; read-only.

add_notebook — Make a notebook

Make a notebook — a subject written against with no deadline: a book, a trip, a renovation. write_entry files notes into it by name.

Needs notes:write; writes.

remove_notebook — Remove an empty notebook

Delete a notebook that holds nothing — no notes, no tasks, no goals. One with anything in it is refused with what it holds: somebody’s writing is deleted by them in the app, never through a tool. For a notebook made by mistake.

Needs notes:write; writes.

share_notebook — Share a notebook with the family

Share one of the person’s notebooks with everybody on their family plan — they read it and write their own entries into it — or stop sharing with shared: false. Only its owner’s to flip, and only when they asked.

Needs notes:write; writes.

ideas — Ideas

Things caught before they evaporated, newest first. An idea is not a task: nobody has committed to doing it, which is what makes it cheap to write down.

Needs ideas:read; read-only.

add_idea — Catch an idea

Write an idea down without deciding where it belongs. The lowest-friction thing here; prefer it to a todo when the person has not said they will do it.

Needs ideas:write; writes.

remove_idea — Delete an idea

Delete an idea — for one added by mistake, or one that has been dealt with. It is gone, not archived, so prefer leaving it alone unless the person asked.

Needs ideas:write; writes.

change_idea — Change an idea

Rewrite an idea, or retag it. Only the fields given change — this is for a misheard word or a better tag, not for turning it into something else.

Needs ideas:write; writes.

shopping_list — The shopping list

What is to buy and what is already in the cupboard. An item is a thing, not a line: ticking it bought puts it back in the cupboard rather than deleting it.

Needs shopping:read; read-only.

add_to_shopping_list — Add to the shopping list

Put something on the list. If the cupboard already has it, this says so rather than adding a second one.

Needs shopping:write; writes.

tick_bought — Tick something bought

Mark an item bought, which moves it out of "to buy" and into the cupboard. The row stays: the same thing is bought again the next time it runs out.

Needs shopping:write; writes.

untick_bought — Put something back on the list

Undo a tick: the item comes out of the cupboard and back onto "to buy". Use it when something was marked bought by mistake, or when it has run out again. Nothing is lost either way — the row, its category and its price history are the same row.

Needs shopping:write; writes.

snooze_item — Put something aside for now

Take an item off the visible list without deleting it — for something not wanted this week. It keeps everything about itself and comes back with unsnooze_item. Prefer this to removing when somebody says "not now" rather than "never".

Needs shopping:write; writes.

unsnooze_item — Bring something back to the list

Wake an item that was put aside, so it shows on the list again. shopping_list says which items are snoozed.

Needs shopping:write; writes.

remove_from_shopping_list — Take something off the shopping list

Remove an item because it is not wanted — "take milk off", "we already have that". Not the same as tick_bought, which records that it was bought and keeps it in the history and the price record. Takes the id shopping_list gives.

Needs shopping:write; writes.

recipes — Recipes

Every recipe, with its ingredients. An ingredient here is a shopping item with an amount, which is what lets a meal on a day fill the shopping list.

Needs kitchen:read; read-only.

add_recipe — Add a recipe

Write a recipe down. Ingredients are one per line — "200 g flour", "2 eggs" — and each becomes a shopping item, so the list knows about them the day the meal is planned.

Needs kitchen:write; writes.

change_recipe — Change a recipe

Change a recipe’s title, method, servings, time or source, and add ingredients — one per line, quantity first. Only the fields given change, and existing ingredients stay.

Needs kitchen:write; writes.

cooked_recipe — Say a recipe was cooked

Record that a meal was made — recipes shows when each was last cooked, and this is what sets it. Name the ingredient ids that ran out and they land back on the shopping list, which is the loop the kitchen exists to close.

Needs kitchen:write; writes.

archive_recipe — Put a recipe away

Archive a recipe — out of the everyday list, not deleted — or bring one back with archived: false. For the dish nobody makes any more that somebody may yet ask for.

Needs kitchen:write; writes.

file_shopping_item — File an item into a section

Move a shopping item into a section — "put the milk under Dairy". Takes the item’s id from shopping_list and the section by name from shopping_categories; an empty section name unfiles it. A name matching no section is refused with the ones that exist.

Needs shopping:write; writes.

shopping_categories — The shopping list’s sections

How the shopping list is sectioned — produce, cleaning, whatever the person keeps. Read it before filing an item somewhere.

Needs shopping:read; read-only.

add_shopping_category — Add a shopping section

Make a new section for the shopping list — and say whether it holds food, because only food sections can feed recipes as ingredients.

Needs shopping:write; writes.

change_shopping_category — Rename a shopping section

Rename a section, or change whether it holds food. Only the fields given change; the items filed under it stay exactly where they are.

Needs shopping:write; writes.

remove_shopping_category — Delete a shopping section

Delete a section. Its items are not touched — they stay on the list, just unfiled. A section is a shelf label, and removing the label must not empty the shelf.

Needs shopping:write; writes.

record_price — Record what an item cost

Write down what was paid for a shopping item — "milk was 6,50 today". The list keeps a small price history per item, which is how it can notice drift. Takes the id shopping_list gives, and the price as the person said it.

Needs shopping:write; writes.

add_goal — Write down a goal they made

Transcribe a goal the person just committed to, in their own words — "apply to twenty companies this quarter". Never invent one, and never add a goal they did not say: a goal is a commitment, and the commitment is theirs. goal_areas lists the areas one can be filed under.

Needs tasks:write; writes.

log_goal_progress — Move a goal’s number

Record progress on a goal that counts something: pass value to set where it stands, or delta to add what just happened — "I sent three more CVs" is delta: 3. Exactly one of the two. goals shows the current number.

Needs tasks:write; writes.

goal_areas — The areas goals are filed under

The areas of life a goal can belong to — career, health, whatever the person keeps. Read it before filing a goal; add_goal_area makes a missing one.

Needs tasks:read; read-only.

add_goal_area — Add a goal area

Make a new area to file goals under. Only when the person named one that does not exist — goal_areas says what already does.

Needs tasks:write; writes.

all_habits — Every habit

The full list of habits, due today or not — id, name, type and which days each is scheduled. habits is today’s view with streaks; this is the one to read before adding or changing one.

Needs habits:read; read-only.

add_habit — Add a habit

Start tracking a habit: something to keep doing (good), to avoid (bad), or just to watch (neutral). Scheduled days come in the same shape all_habits shows for existing ones; leave them out for every day.

Needs habits:write; writes.

change_habit — Change a habit

Rename a habit or change its type, description or days. Only the fields given change; its history of kept days stays exactly as it was.

Needs habits:write; writes.

reminders — What will reach out, and when

The reminders set to fire — each hangs off a block, because a reminder here is "tell me before this starts". Include the past to see what already fired.

Needs schedule:read; read-only.

remind_before_block — Set a reminder on a block

Be told some minutes before a block starts — it reaches the phone even with the app closed. A reminder belongs to a block: for "remind me at three to call the dentist", first add_block the call at three, then set the reminder on it. Takes the id the day gives, like slot:42.

Needs schedule:write; writes.

dismiss_reminder — Dismiss a reminder

Wave one reminder off so it does not fire — for "no need to remind me about that any more". Takes the id reminders gives; the block it sat on is untouched.

Needs schedule:write; writes.

repeating_week — The week as it repeats

The blocks that make up every week — each with its weekday, time, length and category. Weekdays are numbered from Monday: 0 is Monday, 6 is Sunday. This is the template the days are generated from; today and upcoming show what it produced. Read it before changing Tuesdays rather than a Tuesday.

Needs schedule:read; read-only.

add_repeating_block — Put a block on every week

Add a block that repeats weekly — "gym on Tuesdays at seven". This changes every week from now on; add_block is the one for a single day. Weekdays count from Monday: 0 is Monday, 6 is Sunday. A block can be a bare category rather than a named thing — leave the title out and it shows as the category itself, which is what "put work in those hours" means.

Needs schedule:write; writes.

change_repeating_block — Change a repeating block

Change every future occurrence of a repeating block: its weekday, time, length, the text on it, its category or its reminder. This is "move gym to Wednesdays"; change_block is "move this Wednesday’s gym". Only the fields given change. Takes the id repeating_week gives.

Needs schedule:write; writes.

remove_repeating_block — Take a block out of the week

Remove a repeating block from every week to come. Its past occurrences and their record stay. For one day only, use cancel_block instead — this is the whole pattern.

Needs schedule:write; writes.

categories — The parts of a life

The categories blocks are filed under — the areas of this person’s life, each with its colour. Read it before writing a block, so the name is real rather than guessed.

Needs schedule:read; read-only.

activities — The named recurring things

Activities are the named things inside categories — "piano", not just "music". A block can name one instead of a bare category. add_activity and change_activity write them.

Needs schedule:read; read-only.

add_activity — Name a new recurring thing

Add an activity — a named thing inside a category, like "piano" inside "music" — so blocks can name it instead of the bare category.

Needs schedule:write; writes.

change_activity — Rename an activity, or say what it is

Change an activity: its name, the line describing it, or which category it belongs to. Takes the id activities gives. Only the fields you pass change. Blocks that name it follow the change; nothing on any day is moved.

Needs schedule:write; writes.

people — The people in their life

Everybody the person keeps a page for — name, relationship, birthday, contact details. These are other people’s facts held in this account, which is why they sit behind their own permission.

Needs people:read; read-only.

upcoming_birthdays — Whose birthday is coming

Birthdays in the days ahead, soonest first — the answer to "whose birthday is coming up". Only people with a birthday written down appear.

Needs people:read; read-only.

add_person — Add a person

Keep a page for somebody — name at minimum; birthday as YYYY-MM-DD, or --MM-DD when the year is unknown. A birthday written down announces itself on the morning, unless told not to.

Needs people:write; writes.

change_person — Change a person’s page

Correct or extend what is recorded about somebody — a birthday learnt, a number changed. Only the fields given change. Takes the id people gives.

Needs people:write; writes.

daily_wins — Three things that went well

The day’s three wins, as written. A practice, not a log: three lines a day, and blank ones are simply not written yet.

Needs notes:read; read-only.

record_win — Record a win

Write one of the day’s three good things, in the person’s own words, into the first empty line. Refused when all three are written — a day holds three, and the fourth is tomorrow’s first.

Needs notes:write; writes.

weekly_review — How a week actually went

A week read whole: planned against done, by category, with the three lines written about it. The heart of the app — this is what the Monday mail says, and what closing a week means. Defaults to the week now running.

Needs tasks:read; read-only.

write_review_lines — Write the week’s three lines

Replace the three lines of a week’s review — in the person’s own words, and only when they said them. These are what they will reread in a year; never compose them unasked.

Needs tasks:write; writes.

data_streams — The numbers being tracked

The account’s data streams — weight, mood, sleep, anything a plugin or a person logs over time — each with its slug, kind and unit. log_data_point writes into one by its slug.

Needs streams:read; read-only.

log_data_point — Log a reading

Write one point into a data stream — "I weigh 82 today", "slept 6 hours". Takes the stream’s slug as data_streams gives it; a slug that names nothing is refused with the list, never created on the quiet.

Needs streams:write; writes.

apply_idea — Mark an idea applied

Say an idea was acted on, with a note about what came of it — or take that back by calling it again. Applied is not deleted: the idea stays, wearing what happened.

Needs ideas:write; writes.

favorite_idea — Star an idea

Star an idea, or unstar it by calling this again. A star is the person’s to ask for — never decorate their inbox on your own judgement.

Needs ideas:write; writes.