VISION for AI agents
Hand an AI assistant the keys to your hive — scoped to the apps you choose, logged action by action, revocable in one click.
Everything you can do in VISION's five apps, an agent can do through two surfaces that share one set of credentials:
MCP server — for AI assistants
/api/mcp. About forty tools written for a model to drive: friendly names, validation messages that list the valid choices, and the app's own rules (subtask cascades, snooze capping, absorb-never-deletes) built in. Works with Claude, ChatGPT, Cursor, Claude Code, and any client that speaks the Model Context Protocol.
REST — for scripts and integrations
The same /api/* routes the VISION apps themselves use, with a bearer token. Every route is described in the REST reference and in OpenAPI 3.1.
Changes an agent makes land in your open VISION tabs live, exactly as if you'd made them on another device. Data stays encrypted at rest; agents see plaintext only because they're acting as you.
Your data leaves VISION when an agent reads it. Whatever a connected assistant reads is sent to that assistant's provider (Anthropic, OpenAI, …) and handled under their policies, not VISION's. Grant only the apps the job needs.
Quick start
The MCP server URL for this site is:
/api/mcp Claude (claude.ai, Claude Desktop, mobile)
- In Claude's settings, open Connectors and add a custom connector.
- Paste the URL above. Leave any client ID / secret fields empty — Claude registers itself.
- Claude opens a VISION page. Sign in if asked, choose which apps it may read or change, and select Allow.
- Ask something: “What's overdue in Horizon?” or “Turn my morning chores into a Loupe checklist.”
Claude Code
With sign-in (opens the same consent page in your browser):
claude mcp add --transport http vision /api/mcp
# then, inside Claude Code:
/mcp # pick "vision" → Authenticate Or with a personal access token, no browser needed:
claude mcp add --transport http vision /api/mcp \
--header "Authorization: Bearer vsp_…" ChatGPT and other assistants
Any assistant that can add a remote MCP server (sometimes called a connector, app, or integration) works the same way: give it the URL, choose OAuth when it asks how to authenticate, and approve on the VISION page it opens. If a client only accepts a static key, create a personal access token and supply it as a bearer token in the Authorization header.
Clients configured with JSON (Cursor, Windsurf, VS Code, Claude Desktop config file)
{
"mcpServers": {
"vision": {
"type": "http",
"url": "/api/mcp",
"headers": { "Authorization": "Bearer vsp_…" }
}
}
} Some clients name the key serverUrl or omit type; most also accept the URL alone and run the OAuth flow.
Your own code
REST:
curl /api/tasks?done=0 \
-H "Authorization: Bearer vsp_…" MCP over plain HTTP (JSON-RPC 2.0, one POST per message):
curl /api/mcp \
-H "Authorization: Bearer vsp_…" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call",
"params":{"name":"horizon_create_tasks",
"arguments":{"tasks":[{"title":"Renew passport","dueDate":"2026-12-01"}]}}}' An agent built on the Claude API can attach the server directly with the MCP connector (an mcp_servers entry carrying the URL and a token), or your code can call tools/list once and hand the definitions to any model that does tool use.
Authentication
Every request carries Authorization: Bearer <token>. There are two kinds of token and they behave identically once issued.
Personal access tokens vsp_…
Created in Preferences → AI agents. You name it, pick per-app access, and choose an expiry (30 days, 90 days, a year, or never). The token is displayed once; VISION stores only its SHA-256 hash, so a lost token can't be recovered — revoke it and make another.
The token also remembers the timezone of the browser that made it. That's what “today” means to tools like vision_overview and how a bare date like 2026-11-02 becomes a moment in time. Traveling? Make a token in the new timezone.
OAuth 2.1 vsa_… / vsr_…
What MCP clients use when you paste only the URL. Standard, discoverable, no client pre-registration:
| Step | Endpoint | Details |
|---|---|---|
| Discover | /.well-known/oauth-protected-resource | RFC 9728. Also served at …/api/mcp. Points to the authorization server (this same origin). An unauthenticated MCP request gets 401 with WWW-Authenticate: Bearer resource_metadata="…". |
/.well-known/oauth-authorization-server | RFC 8414 metadata. | |
| Register | POST /api/oauth/register | RFC 7591 dynamic client registration. Public clients (none) or confidential (client_secret_post/_basic). Redirect URIs must be https, http on a loopback address (any port), or a private-use scheme like cursor://. 20 registrations per IP per hour. |
| Authorize | GET /authorize | The consent page. Requires response_type=code and PKCE code_challenge_method=S256. You sign in with your passkey/PIN, see the app's self-declared name and where you'll be returned, and choose access per app. Approving issues a one-time code (10 minutes). scope preselects choices; you have the final say. |
| Exchange | POST /api/oauth/token | authorization_code with code_verifier → access token (1 hour) + refresh token (90 days, sliding). Form-encoded or JSON. |
| Refresh | POST /api/oauth/token | refresh_token grant. Both tokens rotate on every refresh. The refresh token you just spent stays good for 2 minutes, so a response lost to a dropped connection doesn't strand the client. |
| Revoke | POST /api/oauth/revoke | RFC 7009. Either token ends the whole grant. Always 200. |
Each approved app shows up once in Preferences, however many times it refreshes.
Scopes
Access is granted per app, at one of two levels. write includes read.
| Scope | Allows |
|---|---|
horizon:read / :write | Tasks, subtasks, domains, recurring templates, metric logs, inbox links, printable lists. Domains are also readable with Refract. |
luminate:read / :write | Notes, folders, PDF notes and their annotations. |
pupil:read / :write | Team members, goals, certifications, notes, activity. |
loupe:read / :write | Checklists, items, folders, runs, revisions, stats. Absorbing tasks also needs Horizon read to check them. |
refract:read / :write | Weekly entries, check-ins, follow-up questions, suggestions, insights, this week's achievements. |
On REST, a GET needs read on the route's app and every other method needs write. On MCP, tools/list only returns tools the connection can use. A token can never exceed your own account's app access, and narrowing an account's apps narrows its tokens too.
Security model
- An agent token is never an administrator. Even the owner's tokens can't reach accounts, invites, passkeys, PINs, devices, other tokens, the email-ingest key, or the error log. Those routes answer
403to any token. - Hashes only. Tokens, refresh tokens, authorization codes, and client secrets are stored as SHA-256 hashes.
- Everything is logged. Each MCP tool call and each REST change records the action, its status, and the time — never the content — under the connection in Preferences, for 30 days.
- Revocation is fast, not instant. A revoked token stops within 30 seconds (tokens are cached briefly at the edge to keep a chatty agent from costing a database read per call).
- Long-lived on purpose. Web sign-ins end at midnight; agent tokens don't, because an assistant that needs a passkey every morning is one nobody uses. Scopes, expiry, the activity log, and revocation are what stand in for the daily cutoff.
- Approval needs a real sign-in. The consent page accepts only a passkey/PIN web session — a token can't approve a grant, and can't create another token.
- CORS is open on agent endpoints (
/api/mcp,/api/oauth/*, discovery, docs) so browser-based clients work. That's safe because those endpoints take bearer tokens only; no cookie authenticates there. - Deleting an account deletes its tokens, grants, codes, and activity with everything else.
- Rate limits. 120 MCP tool calls and 300 REST requests per minute per connection.
The MCP server
| Endpoint | /api/mcp |
|---|---|
| Transport | Streamable HTTP, stateless. Every JSON-RPC message is its own POST; responses are application/json (never an event stream). No Mcp-Session-Id. GET/DELETE answer 405. Notifications answer 202. Batches (arrays) are accepted. |
| Protocol versions | 2025-06-18, 2025-03-26, 2024-11-05 |
| Capabilities | tools, prompts. resources/list answers empty. |
| Instructions | The initialize result carries orientation text for the model — the five apps, the date and timezone conventions, and this connection's scopes. Read it. |
| Errors | A tool that can't do what was asked returns a normal result with isError: true and a sentence the model can act on (“unknown domain "nope". Valid domains: work (Work), …”). JSON-RPC errors are reserved for protocol problems: unknown method -32601, unknown tool -32602, unparseable body -32700. |
| Results | Every tool returns a JSON object as both structuredContent and a text block. Timestamps in results are ISO 8601 UTC; dates are YYYY-MM-DD in your timezone. |
How the tools are built
Each curated tool calls VISION's own route handlers in-process. That's why an agent-created task syncs to your open tabs, gets indexed for search, and is encrypted at rest, the same as one you typed — and why the tools obey rules the apps enforce in the browser:
- Completing a task stops its timer (keeping the time), ends its snooze, and credits its recurring source — and a task holding open subtasks won't complete unless the call says
cascade: true. - Snoozing a task with a due date caps the snooze at that due date's morning.
- Moving a task can't put it inside its own subtask.
- Absorbing tasks into a Loupe checklist refuses tasks that hold subtasks.
- New tasks take your Preferences defaults (domain, importance, category, festering), and likely duplicates are reported back without blocking.
For anything the curated tools don't cover, vision_api_reference documents every REST route and vision_api_request calls one, under the same scopes.
Tool reference
Generated from the server's live tool definitions.
Prompts
Starting points the server offers through prompts/list. Clients that support MCP prompts show them as slash commands or a menu. Each one tells the model to propose before changing anything.
Concepts
What a model needs to know to act well in each app — the parts that aren't obvious from field names.
Horizon — tasks
- A task is a hexagon in a field, not a row in a list.
importance(1-5) sets its size; there is no manual order. - Subtasks (“dwells”). Any task can hold others via
parentId. The parent behaves like a folder you can step into. Completing a parent completes everything inside it; deleting it deletes everything inside it. - Due date vs. snooze are deliberately separate.
dueDateis a deadline — attention rises as it nears and, iffestersis on, the hex escalates the longer it's overdue.snoozeUntildefers a task: it greys out and counts as smallest until then. “Due Friday, snoozed until Wednesday” is a normal state. A snooze set past the due date is capped at the due date's morning; an already-overdue task can still snooze. - Domains vs. categories. A domain (
areain REST; Work, Personal, Health, …) is the life area and sets the color. A category (todo, idea, event, note, storage, plus your custom ones) sets the icon.refractandemailcategories are applied automatically to tasks that came from Refract or incoming mail. - Titles render
**bold**,*italic*,__underline__, and~~strike~~— nothing else (no links or code). Notes are full markdown. - “Open” means what the hive shows: not done, and inside no completed task. Older clients could complete a parent without completing what was inside it, which left subtasks open underneath a finished task. The app hides those, and so do the tools.
vision_overviewreports how many there are, andhorizon_list_taskswithstatus: "hidden"lists them. The raw RESTGET /api/tasks?done=0still includes them, so filter on parent state if you use it directly. - Absorbed tasks have been moved onto a Loupe checklist. They're hidden from every Horizon view and search, but not done and not deleted.
- Recurring templates (“checklists” in REST, unrelated to Loupe) spawn tasks on a recurrence while a Horizon tab is open. There's no server-side scheduler.
- Overdue isn't a failure state in VISION's language. Agents should describe it plainly, without alarm.
Luminate — notes
- Markdown notes in folders that nest without limit. Deleting a folder moves its contents up a level; it never deletes notes.
- Dialect:
__text__is underline. Use**text**for bold. A line starting[ ]or- [ ]is a live checkbox. Also supported: tables, footnotes,==highlight==,~sub~/^sup^,:emoji:, single-line callouts (> [!NOTE]), YAML front matter. - Images live inside the body as base64
data:URIs, so a note with pictures can be long.luminate_get_notetakesmaxChars. - A PDF note's body is the PDF's extracted text; the PDF itself and its margin annotations are separate.
- Replacing a body replaces all of it. Agents should prefer
appendText/prependTextunless they've read the whole note.
Pupil — team
- One profile per team member: phone, pay rate, hired date, a weekly availability grid, and a timeline.
- The timeline is goals, certifications, and dated notes, newest first. Pay-rate, availability, hired-date, and goal due-date changes add a one-line activity entry automatically.
- Certifications are types (“Forklift”) plus per-member records with certified and expiry dates.
pupil_record_certificationmatches types by name and creates missing ones.
Loupe — checklists
- Why it exists: recurring, self-imposed tasks don't belong in Horizon. A checklist holds them, and Horizon getting emptier is the point.
- Shape: a pause point (the moment that triggers it, in your words), a mode, a kind, an optional cadence, and 5-9 items. More or fewer is allowed; the editor shows the fit.
- Mode has no default.
do_confirm: do the work from memory, then verify against the list.read_do: read an item, do it, read the next. A checklist without a mode can't be run until you pick one. - Kind:
normal, ornon_normal— the page you reach for when something's already wrong. Non-normals never have a cadence. - Items are challenge/response: “GEAR ··· DOWN, THREE GREEN.” A killer item is one that's both easy to skip and costly when skipped.
- Runs belong to you. A run records what you checked, skipped, and caught (an item that surfaced a real problem — the value metric). The curated tools read runs but don't perform them; a checklist an agent ticks off catches nothing.
- The revision loop. Evidence (runs, median time, skip rate, catches per item) accrues against the current version. Cutting a revision snapshots the card and its evidence and starts a new version with fresh evidence. Items that go many runs without a catch are candidates to remove; subtraction is the default posture.
- Absorbing moves Horizon tasks onto a checklist as items. The tasks are hidden, never deleted; removing the item or deleting the checklist puts them back exactly as they were.
Refract — weekly journal
- One entry per week, keyed by its Monday. Sunday belongs to the week that's ending.
- Monday-Saturday: one check-in question a day, each weekday with its own fixed prompt. From Sunday: the week's four reflection prompts open — worst part, best part, what's still bothering you, what you're proud of. The server refuses reflection answers before then.
- “Reflect” and “Refract” are AI passes (follow-up questions and suggested to-dos; then a locked summary). They're available on REST; a refracted week is locked until you unlock it in the app.
- This is a journal. The tools tell models to record only words you actually gave them.
Conventions
| Ids | Client-generated everywhere. When creating through REST, send a fresh UUID as id. (MCP tools do this for you.) |
|---|---|
| Time | REST timestamps are epoch milliseconds. Dates are YYYY-MM-DD with no timezone — they mean the calendar day you see. |
| Datetimes into tools | A bare date means that morning (your snooze wake hour, default 8:00). A datetime without an offset (2026-11-02T15:00) is read in the token's timezone. One with an offset or Z is taken as given. |
| Patches | REST PATCH bodies change only the keys present. Unknown keys are ignored. |
| Errors | {"error": "…"} with 400 (bad input), 401 (missing, expired, or revoked token), 403 (outside the token's scopes, or not available to tokens), 404, 409 (conflicts such as a closed Loupe run), 429 (rate limit). |
| Live sync | Every change broadcasts to your open VISION tabs. Agents don't need to do anything for that to happen. |
| Deletion | Permanent. There is no trash. |
| Encryption | Text you write is AES-256-GCM encrypted at rest and decrypted for the API, so tokens see and send plaintext. |
REST reference
Machine-readable: OpenAPI 3.1 · route catalog JSON · MCP tool manifest JSON.
These are the routes VISION's own pages use, so they can change with the apps. The MCP tools are the stable surface; build long-lived integrations on them where you can.
Recipes
Plan the day
“What should I do today?” → vision_overview (date, overdue, due today, today's checklists) → horizon_list_tasks with overdue: true → a proposal → after you agree, horizon_update_task with snoozeUntil on what can wait.
Capture a project in one call
horizon_create_tasks {
"tasks": [
{ "title": "Move apartments", "ref": "move", "dueDate": "2026-12-15", "importance": 5 },
{ "title": "Book movers", "parentRef": "move", "dueDate": "2026-11-20" },
{ "title": "Forward mail", "parentRef": "move" },
{ "title": "Return router", "parentRef": "move", "snoozeUntil": "2026-12-10" }
]
} Drain chores into a checklist
horizon_list_tasks → pick the recurring ones → loupe_create_checklist (name, pause point, items) → loupe_absorb_tasks with the task ids. Horizon gets lighter; nothing is lost.
Revise a checklist on evidence
loupe_get_checklist → look for items with zero catches over many runs and high skip rates → propose removals → loupe_remove_item → loupe_cut_revision with a note saying what changed and why.
Meeting notes to action items
luminate_get_note → extract commitments → horizon_create_tasks → luminate_update_note with appendText listing what was filed.
One-on-one prep
pupil_get_member → open goals, certifications expiring soon, recent notes → after the conversation, pupil_add_note and pupil_update_goal.
Daily check-in by voice
“Log my check-in: …” → refract_save_checkin with your words verbatim. The result includes the day's prompt.
Troubleshooting
| Symptom | Cause and fix |
|---|---|
401 from /api/mcp | Token missing, mistyped, expired, or revoked. OAuth clients should refresh or reconnect; with a personal token, check Preferences → AI agents for its expiry. |
| A tool you expect isn't listed | The connection lacks that app or that level (write tools need write). Edit or recreate the token, or reconnect the app and choose more access. |
this route is not available to API tokens | Account administration and credential routes are deliberately unreachable. Do those in the VISION app. |
| “Today” is off by a day | The token's timezone is where it was created. Make a new token from a browser in your current timezone. |
| The assistant counts more open tasks than the hive shows | The extra ones are subtasks left open inside tasks that were completed before completion cascaded. They're hidden in the app and excluded from the tools' counts; horizon_list_tasks with status: "hidden" shows them, and completing them tidies the record. |
| A task “disappeared” | It may be absorbed into a Loupe checklist (look at items with a sourceTaskId), snoozed (snoozed: "only"), or completed (status: "done"). |
| The consent page says the return address doesn't match | The app's redirect URI changed since it registered. Remove and re-add the connector so it registers again. |
429 | Rate limit: 120 tool calls or 300 REST requests a minute per connection. Back off and retry. |
Found a security issue? See security.txt. Privacy questions: privacy policy.