Extension and MCP API reference
Client API version 1. An extension declares the scopes it needs in its manifest; every call checks them, and a call outside them fails with a scope error. The same scopes authorize MCP clients and scoped REST tokens.
Scopes
| Scope | Covers |
|---|---|
read:notes |
Read pages, their bodies, tags and links |
write:notes |
Change pages, their bodies, tags and links |
read:tasks |
Read tasks, subtasks, captures and the parser |
write:tasks |
Change tasks, subtasks, captures and the parser |
read:projects |
Read projects |
write:projects |
Change projects |
read:routines |
Read routines, their steps and runs |
write:routines |
Change routines, their steps and runs |
read:tags |
Read the workspace's tags |
write:tags |
Change the workspace's tags |
read:calendar |
Read calendar events |
write:calendar |
Change calendar events |
read:focus |
Read focus sessions |
write:focus |
Change focus sessions |
read:notifications |
Read notifications |
write:notifications |
Change notifications |
read:workspace |
Read the workspace's name, members' names, preferences, today, the week report and the change feed |
write:workspace |
Change the workspace's name, members' names, preferences, today, the week report and the change feed |
read:settings |
Read preferences, devices, passkeys, webhooks, members, invitations and connections |
write:settings |
Change preferences, devices, passkeys, webhooks, members, invitations and connections |
read:extensions |
Read the records every extension stores, such as recipes and people |
write:extensions |
Change the records every extension stores, such as recipes and people |
publish:notes |
Publish and unpublish pages |
A token made before scopes existed, or minted without a list, holds every
scope. Mint a scoped token with POST /api/v1/settings/devices
({ "name", "scopes": [...], "kind": "extension", "extension_id" }); the
list must be a subset of the caller's own scopes. A scoped token is bound
to the workspace it was minted in and is refused in any other.
Client API (aquamarine)
Every method is asynchronous and returns a promise. Arguments are listed in order. Records have the shape the REST API answers.
aquamarine.notes
| Method | Scope | Returns | Description |
|---|---|---|---|
notes.list() |
read:notes |
Note[] without bodies | Every active page of the workspace, newest first. |
notes.get(id) |
read:notes |
Note with body | One page, loading its body when the app has not yet. |
notes.search(query) |
read:notes |
Note[] | Pages whose title or synced body contains the query. |
notes.create({ title, body }) |
write:notes |
Note | Makes a page on the server. |
notes.update(id, { title, body, tag_names }) |
write:notes |
Note | Changes a page on the server. |
aquamarine.tasks
| Method | Scope | Returns | Description |
|---|---|---|---|
tasks.list() |
read:tasks |
Task[] | The tasks the app has synced, open ones first. |
tasks.get(id) |
read:tasks |
Task | One task. |
tasks.create({ title, notes, due_on, scheduled_at, priority, project_id, tags }) |
write:tasks |
Task | Makes a task on the server. |
tasks.update(id, attrs) |
write:tasks |
Task | Changes a task on the server. |
tasks.complete(id) |
write:tasks |
Task | Marks a task done. |
tasks.reopen(id) |
write:tasks |
Task | Reopens a done task. |
aquamarine.routines
| Method | Scope | Returns | Description |
|---|---|---|---|
routines.list() |
read:routines |
Routine[] with steps | Every routine the app has synced. |
routines.get(id) |
read:routines |
Routine | One routine with its steps. |
routines.start(id) |
write:routines |
RoutineRun | Starts a run of the routine. |
aquamarine.tags
| Method | Scope | Returns | Description |
|---|---|---|---|
tags.list() |
read:tags |
{ name, count }[] | Tags in use on synced pages and tasks, most used first. |
aquamarine.workspace
| Method | Scope | Returns | Description |
|---|---|---|---|
workspace.info() |
read:workspace |
{ name, user, timezone } | The workspace's name, the signed-in person's name and the workspace timezone. |
workspace.layout() |
read:workspace |
Layout | The saved pane layout (the layout preference). |
workspace.view() |
read:workspace |
{ route, pageId } | What the active tab shows. |
workspace.open(route) |
none | Opens a route in the active tab, such as today or page/<id>. Only works while one of the extension's commands runs, so the person started it. |
aquamarine.settings
| Method | Scope | Returns | Description |
|---|---|---|---|
settings.get() |
none | object | The extension's own saved settings on this device. |
settings.set(values) |
none | object | Replaces the extension's saved settings on this device. |
aquamarine.records
| Method | Scope | Returns | Description |
|---|---|---|---|
records.list(collection, { query, filter, limit, offset }) |
none | Record[] | The extension's own records in a collection its manifest declares, newest first. query matches any text; filter keeps records whose data contains it; offset skips that many matches, for the next page. |
records.get(collection, id) |
none | Record | One of the extension's records. |
records.create(collection, data) |
none | Record | Stores a record; the server checks data against the collection's schema. |
records.update(collection, id, data) |
none | Record | Merges data into the record; a field set to null is removed. |
records.delete(collection, id) |
none | Deletes one of the extension's records. |
Registrations
| Call | Arguments | Description |
|---|---|---|
aquamarine.widgets.register |
{ kind, name, render } |
Adds a widget to the tray. render({ view, note }) returns HTML; it runs again when the workspace or the active tab changes. view is what the active tab shows when the extension holds read:workspace, else an empty route. note is the open page when the extension holds read:notes. Elements with data-command="<id>" run that command when clicked. |
aquamarine.commands.register |
{ id, name, run } |
Adds a command to the palette (Ctrl+K), shown as ": ". |
aquamarine.views.register |
{ id, name, icon, render, action } |
Adds a tab to the sidebar. render({ path }) returns HTML for the view; path is what follows the view in the route ext/<extension>/<view>/<path>. Views may use forms and tables; the app cleans the HTML as it does a widget's, removing href (use data-url for a web page, opened in the browser), styles and any form field named name or id after a form property. A field named x[] arrives as a list. An element with data-go="<path>" opens that path; a form with data-action="<name>" calls action(name, fields, { path }) on submit, and a button, select or input with it calls action(name, { value }, { path }) on click or change, where value is its data-value or its value. A checkbox's value is true or false, with its data-value as item. action may return { go: path, toast }, where toast is text for the status line or { text, undo: { action, value, label } } for one with a button that runs that action. An element with data-key="<key>" is clicked when that key is pressed outside a text field, and one with data-note="<page id>" opens that page. The view renders again after an action and when the extension's records change. icon is one of chef-hat, users, book, heart, star, box. |
aquamarine.ui.crud |
{ id, name, icon, collection, singular, title, subtitle, columns, sorts, filters, pageSize, fields, defaults, detail, beforeSave, action } |
Registers a whole view over one collection: a searchable list, a record page, a form for new and edited records built from the collection's schema, and Delete with a confirmation and Undo. See components. |
aquamarine.ui.list |
{ id, placeholder, text, sorts, filters, pageSize } |
A search box, sort menu and filter menus for a view's list, run by the app so the view's action never sees their actions. Returns { apply(items), search, sort, filter(id), clearAction }; apply answers { items, total, active, toolbar, more }. See components. |
aquamarine.ui.table |
items, { id, columns, row, empty } |
A table whose column headers sort it when it has an id. Each column is { id, label, value(item), html(item), align, sort }. See components. |
aquamarine.ui.form |
collection or schema, values, { action, submit, cancel, title, fields, errors, error, confirm } |
A form built from a JSON Schema or a declared collection's schema. Read what it sends with ui.readForm. See components. |
aquamarine.ui.readForm |
collection or schema, fields, { fields } |
Turns the fields a ui.form sent into { data, errors, ok } that match the schema; an emptied field is null. |
aquamarine.ui.html |
template literal |
A tagged template that escapes every value except the output of another ui call or ui.raw(text). Lists are joined; null, undefined and booleans render nothing. ui.escape(text) escapes one value. |
aquamarine.ui.confirm |
{ label, action, value, message, body, confirm, danger, small } |
A button that asks first, in the app's own dialog, then calls action. |
aquamarine.ui.date |
value, { relative } |
A <time> for an ISO date or time: "5 Nov", or "in 3 days" with relative, with the full date on hover. |
aquamarine.ui.stats |
[{ label, value, hint }] |
A row of number tiles. |
aquamarine.ui.progress |
value, max, { label, text } |
A progress bar with "value / max" or text beside it. |
aquamarine.ui.checklist |
items, { action, label(item), checked(item), value(item) } |
Checkboxes that call action(name, { value, item }) when ticked or unticked. |
aquamarine.ui.note |
id, title |
A link that opens one of the workspace's pages. |
aquamarine.ui.notePicker |
name, pageId, { placeholder, required } |
A form field for choosing a page by title; it sends the page's id. The extension sees only the page it is given. |
aquamarine.ui.tagPicker |
name, tags, { placeholder } |
A form field for tags, suggesting the workspace's own; it sends name[], a list. |
aquamarine.ui.sortable |
items, { action, label(item), value(item) } |
A list the person reorders by dragging or with Alt+Up and Alt+Down; action gets { order }, the items' values in their new order. |
aquamarine.ui.chart |
{ type, data, label, format } |
A bar, line or spark chart the app draws, with a table for screen readers. data is numbers or { label, value }. Works in widgets too. |
aquamarine.events.on |
name, handler |
Listens for change (the synced records changed) or view (the active tab changed; the handler gets { route, pageId }; needs read:workspace). Returns a function that stops listening. |
Extension records
A manifest's collections name the records an extension keeps, each with
a JSON Schema every record must match; its tools turn actions on them
into MCP tools. The app registers both with the server when the extension
is enabled. Records are stored encrypted, scoped to the workspace.
| Request | Answers |
|---|---|
GET /api/v1/extensions |
The registered extensions, with their collections and MCP tool names |
PUT /api/v1/extensions/<id> |
Registers { name, version, collections, tools }; needs write:settings |
DELETE /api/v1/extensions/<id> |
Removes the registration and its tools; the records stay |
GET /api/v1/extensions/<id>/records/<collection> |
Records, newest first; query matches any text, filter (a JSON object) keeps records that contain it, limit up to 500, offset skips that many matches |
POST /api/v1/extensions/<id>/records/<collection> |
Creates a record from { data } |
GET /api/v1/extensions/<id>/records/<collection>/<record id> |
One record |
PATCH /api/v1/extensions/<id>/records/<collection>/<record id> |
Merges { data } into the record; a null field removes it |
DELETE /api/v1/extensions/<id>/records/<collection>/<record id> |
Deletes the record |
A record answers { id, extension_id, collection, data, created_at, updated_at }.
An extension's own token reaches its own records without a scope and no
one else's; any other token needs read:extensions or write:extensions.
A collection holds at most 5000 records of at most 256 KB each.
Each declared tool becomes the MCP tool <id>_<name> (dashes in the id
become underscores), shown to tokens with read:extensions for list
and get and write:extensions for the rest. list takes query,
filter, limit and offset; get and delete take id; create takes the
collection's schema; update takes id and any of the schema's fields.
MCP server
POST /mcp on the Rails app is a streamable HTTP MCP endpoint: one JSON-RPC
message per request, answered in full. Send Authorization: Bearer <token>
and X-Aquamarine-Account: <workspace id> (the header is optional with one
workspace or a scoped token). Only bearer tokens sign in. A call whose
scope the token lacks is refused with HTTP 403 and { "error", "scopes" }
before it reaches the server; tools/list and resources/templates/list
show only what the token can use.
{ "mcpServers": { "aquamarine": { "type": "http", "url": "https://<server>/mcp",
"headers": { "Authorization": "Bearer <token>", "X-Aquamarine-Account": "<workspace id>" } } } }
Tools
| Tool | Scope | Arguments | Description |
|---|---|---|---|
notes_list |
read:notes |
query?, limit? |
List active notes, newest first, without bodies. query searches titles and bodies. |
notes_get |
read:notes |
id |
A note with its body, tags and links. |
notes_create |
write:notes |
title, body?, parent_id?, tags? |
Create a note. |
notes_update |
write:notes |
id, title?, body?, parent_id?, tags? |
Change a note's title, body, parent or tags. |
notes_publication_get |
read:notes |
id |
A note's publication, or null, and the links that would not work for visitors if it were published now. |
notes_publish |
publish:notes |
id, confirm?, display?, password?, expires_at?, slug?, domain_id? |
Publish a note, or republish it with its current text. Keeps the URL and settings. When links would not work for visitors it fails with the list; call again with confirm: true to publish anyway. |
notes_unpublish |
publish:notes |
id, confirm_takedown? |
Take a published note offline. When other published notes link to it, it fails with their list; call again with confirm_takedown: true. |
tasks_list |
read:tasks |
status?, date? |
List top-level tasks. status is inbox, planned or done; date lists the tasks scheduled on a day. |
tasks_get |
read:tasks |
id |
A task with its subtasks. |
tasks_create |
write:tasks |
title, notes?, priority?, due_on?, scheduled_at?, duration_minutes?, project_id?, page_id?, parent_id?, tags? |
Create a task. |
tasks_update |
write:tasks |
id, title?, notes?, priority?, due_on?, scheduled_at?, duration_minutes?, project_id?, page_id?, parent_id?, tags? |
Change a task's fields. |
tasks_complete |
write:tasks |
id |
Mark a task done. |
tasks_reopen |
write:tasks |
id |
Reopen a done task. |
routines_list |
read:routines |
none | List active routines with their steps. |
routines_get |
read:routines |
id |
A routine with its steps and the id of its running run. |
routines_start |
write:routines |
id, finish_by? |
Start a routine, or answer the run already going. |
search |
any read: scope |
query |
Search notes, tasks, projects and routines; only the families the token can read are searched. |
Resources
| URI | Scope | Content |
|---|---|---|
aquamarine://notes/<id> |
read:notes |
One note as JSON |
aquamarine://tasks/<id> |
read:tasks |
One task as JSON |
aquamarine://routines/<id> |
read:routines |
One routine as JSON |
resources/list lists the active notes of a token holding read:notes.