# Write Tools

Write tools require a **write-scoped token**. Calling these with a read-scoped token returns an error and makes no changes.

Every write tool also accepts optional `contextRunId`. In a workspace using **Strict context**, actual mutations require a non-expired ID returned by this same agent's `prepare_context` call. Destructive confirmation and normal authorization remain separate requirements. In Manual/Smart mode the field is optional.

`create_page`, `update_page`, and `create_database` additionally accept optional `knowledge` (`conceptType`, `description`, `tags`, `sources`, `status`, `staleAfter`); each `bulk_create_pages` entry accepts the labelling subset (`conceptType`, `tags`, `sources`). `sources` is a list of `{ resource, title? }` — typically repository-relative file paths. Agent-created or agent-updated items are recorded as machine-generated drafts; only a signed-in Remnus user can review the exact revision.

`create_page`, `bulk_create_pages`, `update_page`, `bulk_update_pages`, `create_database`, `create_dashboard` and `update_dashboard` accept `icon` — an emoji (`"🗺️"`) or `"lucide:Name"` for one of the icons the sidebar can draw (for example `lucide:Map`, `lucide:Layers`, `lucide:Target`) — and `iconColor`: `default`, `red`, `orange`, `yellow`, `green`, `teal`, `blue`, `purple` or `pink` (applies to Lucide icons). An unknown Lucide name is refused with the list of valid ones, and image URLs can't be set over MCP. In updates, `null` clears.

---

## create_page

Create a new standalone page or a database row.

**Parameters**

| Parameter | Type | Required | Description |
|---|---|---|---|
| `title` | string | ✓ | Page title |
| `content` | string | | Initial markdown content |
| `parentId` | string | | Parent workspace item ID — creates a nested standalone page |
| `databaseId` | string | | Database ID — creates a row instead of a standalone page |
| `properties` | object | | Initial property values for database rows |
| `icon` | string | | Emoji or `lucide:Name` |
| `iconColor` | string | | Color for a Lucide icon |
| `knowledge` | object | | OKF-aligned retrieval/lifecycle metadata |
| `contextRunId` | string | | Context preflight ID (required in Strict mode) |

Pass either `parentId` (standalone page) or `databaseId` (database row), not both.

**Linking to another item** — write `<a data-page-link href="/page/<id>">Title</a>` in the markdown (`/db/<databaseId>` for a database, `/db/<databaseId>/<rowId>` for a row). That is the form the editor produces, and the one `get_related_pages` and context packs follow; wiki-style `[[Title]]` is plain text. Dashboards stay out of the link graph by design — link one with a plain `[Title](/dashboard/<id>)`. The same applies to `update_page` and `bulk_create_pages` content.

**Returns** — `{ id, type }`

---

## update_page

Update an existing page or database row. Properties are **merged** — existing properties that are not included in the call are preserved.

**Parameters**

| Parameter | Type | Required | Description |
|---|---|---|---|
| `pageId` | string | ✓ | Workspace item ID or database row ID |
| `title` | string | | New title |
| `content` | string | | New markdown content (replaces existing) |
| `properties` | object | | Properties to merge into the row |
| `icon` | string \| null | | Emoji or `lucide:Name`; `null` clears |
| `iconColor` | string \| null | | Color for a Lucide icon; `null` clears |
| `knowledge` | object | | OKF-aligned retrieval/lifecycle metadata |
| `contextRunId` | string | | Context preflight ID (required in Strict mode) |

**Returns** — `{ updated: true, id }`

---

## bulk_update_pages

Update multiple pages or database rows in a single call.

**Parameters**

| Parameter | Type | Required | Description |
|---|---|---|---|
| `updates` | array | ✓ | Array of update objects — each has `pageId` plus optional `title`, `content`, `properties`, `icon`, `iconColor` |

**Returns** — array of per-item results.

---

## bulk_create_pages

Create up to 100 standalone pages and/or database rows in one call — the fast way to fill a database or lay out a section. Entries are created **in order**, and the call is **not atomic**: each entry reports its own `ok`/`error`, and one bad entry never stops the rest.

**Parameters**

| Parameter | Type | Required | Description |
|---|---|---|---|
| `pages` | array | ✓ | 1–100 entries, created in order |
| `contextRunId` | string | | Context preflight ID (required in Strict mode) |

**Entry**

| Field | Type | Description |
|---|---|---|
| `title` | string | Plain-text title (required) |
| `content` | string | Markdown content |
| `databaseId` | string | Creates a row in this database |
| `properties` | object | Row properties (column names or ids) |
| `parentId` | string | Nest a page under an existing item |
| `ref` | string | A label later entries in the same call can point at |
| `parentRef` | string | Nest a page under a page created earlier in this call |
| `icon` / `iconColor` | string | See the icon note above |
| `knowledge` | object | `conceptType`, `tags`, `sources` — labels `prepare_context` ranks on |

`parentRef` can't be combined with `parentId` or `databaseId`, and only pages — not rows — can be a `parentRef` target.

```json
{
  "pages": [
    { "ref": "systems", "title": "Systems", "icon": "lucide:Layers", "iconColor": "blue" },
    { "parentRef": "systems", "title": "Combat", "icon": "⚔️" },
    { "parentRef": "systems", "title": "Itemization", "icon": "🎒" }
  ]
}
```

**Returns** — `{ requested, succeeded, failed, results: [{ index, ok, id?, type?, ref?, error? }] }`

---

## delete_page

Delete a workspace page, database, dashboard, or database row. Requires `confirm: true` to execute. Without it, the tool returns a description of what would be deleted and makes no changes.

**Parameters**

| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| `pageId` | string | ✓ | | Item to delete |
| `confirm` | boolean | | `false` | Set to `true` to confirm deletion |

Always call once without `confirm` first to verify the target before confirming.

**Returns** — `{ deleted: true, id }` on confirmation; a preview string otherwise.

---

## bulk_delete_pages

Delete multiple workspace pages, databases, or database rows in one call. Requires `confirm: true` to execute. Without it, the tool returns a preview of what would be deleted and makes no changes.

Deletions run concurrently and each entry reports its own `ok`/`error`, so one bad id cannot sink the rest of the batch — unlike `bulk_update_pages`, whose `Promise.all` implementation can silently drop entries on a partial failure.

**Parameters**

| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| `pageIds` | string[] | ✓ | | Workspace item IDs or database row IDs to delete (max 100) |
| `confirm` | boolean | | `false` | Set to `true` to confirm deletion |
| `contextRunId` | string | | | Context preflight ID (required in Strict mode) |

Always call once without `confirm` first to verify the targets before confirming.

**Returns** — on `confirm: true`: `{ deleted: true, requested, succeeded, failed, results: [{ id, ok, error? }] }`. Preview (no `confirm`): `{ deleted: false, requested, items: [{ id, title, type }], preview }`.

---

## move_item

Move a sidebar item (page or database) to a new parent within the workspace.

**Parameters**

| Parameter | Type | Required | Description |
|---|---|---|---|
| `itemId` | string | ✓ | Workspace item ID to move |
| `newParentId` | string \| null | | New parent item ID — pass `null` to move to workspace root |

**Returns** — updated item object.

---

## bulk_move_items

Move multiple items in one call. Pass `newParentId` to reparent workspace items (pages/databases) within the sidebar — the same semantics as `move_item`, batched. Pass `targetDatabaseId` to move database rows to a **different** database. Exactly one of `newParentId` or `targetDatabaseId` must be given.

The cross-database move is refused entirely (no rows moved) if the target database's columns don't cover the source columns by name and type — the error names the missing/mismatched columns rather than silently dropping data.

**Parameters**

| Parameter | Type | Required | Description |
|---|---|---|---|
| `itemIds` | string[] | ✓ | IDs to move (max 100) |
| `newParentId` | string \| null | | Sidebar mode: new parent item ID, or `null` for workspace root |
| `targetDatabaseId` | string | | Cross-database mode: destination database ID for row(s) |
| `contextRunId` | string | | Context preflight ID (required in Strict mode) |

**Returns** — `{ requested, succeeded, failed, results: [{ id, ok, error? }] }`

---

## create_database

Create a new database with a custom schema. A `Title` text column is always prepended automatically.

**Parameters**

| Parameter | Type | Required | Description |
|---|---|---|---|
| `name` | string | ✓ | Database name |
| `parentId` | string | | Parent workspace item ID (omit for root) |
| `schema` | array | | Column definitions (omit for default Title + Status schema) |
| `icon` | string | | Emoji or `lucide:Name` |
| `iconColor` | string | | Color for a Lucide icon |
| `views` | array | | Up to 5 views created with the database: `{ name, type: "table" \| "kanban" \| "calendar", groupByCol?, dateCol?, icon?, iconColor? }`. A Table view always exists. Each view reports its own result; a view that can't be created never undoes the database. |

**Column definition**

```json
{
  "name": "Status",
  "type": "select",
  "options": [
    { "value": "Backlog", "color": "default" },
    { "value": "In Progress", "color": "orange" },
    { "value": "Done", "color": "green" }
  ]
}
```

Column types: `text`, `number`, `select`, `multi_select`, `status`, `user`, `multi_user`, `date`, `datetime`, `checkbox`, `url`, `email`, `phone`

- `status` — like `select`, but each option may include a `group`: `"todo"` | `"in_progress"` | `"complete"` (renders as a progress-ring glyph).
- `user` / `multi_user` — store workspace member user ids (no `options` needed); resolved to member name + avatar in the UI.
- An option is a plain string or an object whose text is `value` (`name` and `label` are read the same way, so Notion-shaped options work). An option with no text, or a `group` other than the three above (`"to do"`, `"in progress"` and `"done"` are accepted as aliases), is refused with an error naming the column — never stored as a placeholder.

**Returns** — `{ id, databaseId, views? }`

---

## update_database_schema

Add or remove columns from an existing database. Removing columns is **destructive** (all data in that column is lost) and requires `confirm: true`.

**Parameters**

| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| `databaseId` | string | ✓ | | Database ID |
| `addColumns` | array | | | Columns to add — same format as `create_database` schema |
| `removeColumnIds` | array | | | Column IDs to remove (find IDs via `get_database_schema`) |
| `confirm` | boolean | | `false` | Required when removing columns |

The `Title` column cannot be removed.

**Returns** — updated schema.

---

## create_database_view

Add a new saved view (table, kanban, or calendar) to a database. Kanban groups rows by a select/status column; calendar places cards by a date/datetime column. Use `get_database_schema` first to see column ids/names.

**Parameters**

| Parameter | Type | Required | Description |
|---|---|---|---|
| `databaseId` | string | ✓ | Database ID |
| `name` | string | ✓ | Name for the new view |
| `type` | string | ✓ | `"table"` \| `"kanban"` \| `"calendar"` |
| `groupByCol` | string | | Kanban only: select/status column id or name. Auto-picks the first status/select column if omitted. |
| `dateCol` | string | | Calendar only: date/datetime column id or name. Auto-picks the first date/datetime column if omitted. |
| `icon` | string | | Emoji, `"lucide:Name"`, or image URL for the view tab |
| `iconColor` | string | | Theme color for a lucide icon |

A kanban view with no select/status column, or a calendar view with no date/datetime column, errors unless one is passed explicitly.

**Returns** — `{ created: true, view }`

---

## update_database_view

Rename a view, change its icon, or patch fields within its existing config (`filters`, `sorts`, `groupByCol`, `dateCol`, `cardProperties`, etc — merged into the current config). A view's type (table/kanban/calendar) can't be changed after creation; create a new view instead.

**Parameters**

| Parameter | Type | Required | Description |
|---|---|---|---|
| `databaseId` | string | ✓ | Database ID |
| `viewId` | string | ✓ | View ID (from `get_database_schema`) |
| `name` | string | | New view name |
| `icon` | string | | Emoji, `"lucide:Name"`, or image URL |
| `iconColor` | string | | Theme color for a lucide icon |
| `config` | object | | Partial config fields to merge in, e.g. `{ "groupByCol": "col_abc123" }` |

**Returns** — `{ updated: true, view }`

---

## delete_database_view

Delete a saved view. Requires `confirm: true`. A database must always keep at least one view — deleting the last one errors.

**Parameters**

| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| `databaseId` | string | ✓ | | Database ID |
| `viewId` | string | ✓ | | View ID (from `get_database_schema`) |
| `confirm` | boolean | | `false` | Set to `true` to confirm deletion |

**Returns** — `{ deleted: true }`

---

## create_dashboard

Create a [dashboard](/wiki/dashboards.md) — metric, chart, list, table-view and text blocks that read live from this workspace's databases — in one call. The block catalog and ready-made templates are the resource [`remnus://dashboard/catalog`](/wiki/resources.md#remnusdashboardcatalog); they are deliberately not in this tool's schema.

**Parameters**

| Parameter | Type | Required | Description |
|---|---|---|---|
| `title` | string | ✓ | Dashboard name |
| `parentId` | string | | A **page** to nest under (only pages hold children); omit for root |
| `icon` / `iconColor` | string | | See the icon note above |
| `blocks` | array | | Up to 40 block objects, see the catalog. `id` may be omitted — one is generated and returned |
| `contextRunId` | string | | Context preflight ID (required in Strict mode) |

Column fields take a column **name or id** (case-insensitive) and store the id; `databaseId` may be the database id or its item id. A database the workspace does not have — another workspace's included — refuses the whole call.

**Returns** — `{ id, url, blocks, warnings? }`: the in-app `url` to show a human, every block id in order, and `warnings` for blocks that will render empty or broken (unknown column, a filter value that is no option, no matching rows). An invalid block is an error naming the entry, the field and what was expected.

---

## update_dashboard

Patch a dashboard **by block id** — send only what changes, never the whole spec.

**Parameters**

| Parameter | Type | Required | Description |
|---|---|---|---|
| `dashboardId` | string | ✓ | The dashboard's item id |
| `title` | string | | New name |
| `icon` / `iconColor` | string \| null | | `null` clears |
| `add` | array | | New blocks, appended |
| `update` | array | | `{ id, ...fields }` merged into that block; `null` removes a field; `type` cannot change |
| `remove` | string[] | | Block ids to delete |
| `order` | string[] | | Block ids in their new order; unlisted blocks follow in their current order |
| `contextRunId` | string | | Context preflight ID (required in Strict mode) |

All-or-nothing, applied remove → update → add → order: one bad entry refuses the call and says which. Blocks the call does not touch are passed through untouched, even ones this build cannot read. Concurrent patches to different blocks both land (compare-and-swap on the stored spec).

**Returns** — same shape as `create_dashboard`. Emptiness warnings cover only the blocks this call touched; a broken block is reported wherever it is.

---

## add_comment

Add a comment to a page or database row, in a thread separate from its markdown body — a place to leave running notes or a closure note as you work. Comments you add here cannot be edited or deleted by you afterward; use `update_page` for content you need to revise.

**Parameters**

| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
| `pageId` | string | ✓ | | Workspace item ID or database row ID |
| `body` | string | ✓ | | Comment text (max 4,000 characters) |
| `kind` | `"note"` \| `"closure"` | | `"note"` | Marks the comment as a running note or a closing note |

**Returns** — `{ id, createdAt }`
