---
name: brain
displayName: Brain
description: Full reference for Brain: the Notion-like workspace platform. Use this skill whenever the user asks you to create, query, or modify databases, pages, docs, agents, skills, sites, or any other Brain resource. The s16_* tools below are exposed by Brain's MCP server.
type: general
version: 1.0.0
tags: [brain, mcp, reference, platform]
---

# Brain platform reference

Brain is the AI workspace for your entire organization. Workspaces hold databases (EAV-schema, dynamic columns), docs (Tiptap rich-text), agents (sandboxed JS scripts), skills (SKILL.md prompts), and sites (publishable micro-pages). The tools listed below are exposed by Brain's MCP server.

## Core concepts

- **Workspace**: the top-level tenant; every resource belongs to exactly one workspace.
- **Database**: like a Notion database. Has properties (columns), pages (rows), views (table / board / list / calendar / gallery / timeline), and templates.
- **Page**: a row in a database. Has a properties_cache JSONB for fast reads, and a rich-text body.
- **Doc**: a standalone Tiptap document, not tied to any database.
- **Sidebar tree**: a single adjacency-list table linking docs, databases, and folders into a hierarchy. Has a separate "Private" section.
- **Agent**: a JS script that runs in a node:vm sandbox. Triggered manually, on schedule, on data changes, via webhook, or via Gmail. Uses the s16 API inside the sandbox.
- **Skill**: a SKILL.md file with YAML frontmatter that turns into an MCP prompt. Skills can be workspace-scoped, installed from marketplace, or system-level.
- **Site**: a publishable mini-app with a virtual filesystem (tree/files JSONB) that executes in a null-origin iframe.

## Authentication

Use s16_authenticate(returnUrl) and s16_complete_authentication when no Brain session exists. After auth, the MCP server holds the user context for every subsequent call.

## Common workflows

**Create a database with rows:**
1. s16_create_database: give it a name; primary title property is auto-created
2. s16_create_property: add the columns you want (text, select, relation, etc.)
3. s16_create_page: for each row

**Build a subagent:**
1. s16_create_subagent: name, systemPrompt, model, tools
2. s16_update_subagent (you write the JS — a non-empty compiledScript on a draft vm subagent auto-activates it)
3. s16_set_trigger: pick event / cron / webhook / gmail
4. s16_run_subagent_by_name and s16_await_run to test

**Publish a site:**
1. s16_create_site
2. s16_create_site_page with files JSONB (a virtual filesystem)
3. The page becomes accessible at /s/<shareId>

## Tools

The s16_* tools below are the only way to read or write data in Brain. Pick the right one by skimming the descriptions. Each tool's input shape and an example payload is listed.


### agents

#### `s16_create_agent`

Create an agent — a named bundle of subagents, a site, and databases that work together as one solution (e.g. a Telegram bot and everything that powers it). Returns the new agent; add members with s16_add_agent_item.

**Input:**
  - `name`: string . Agent name (e.g. "Orbit bot")
  - `description`?: string . What this bundle does

```json
{
  "tool": "s16_create_agent",
  "arguments": {
    "name": "name"
  }
}
```

#### `s16_delete_agent`

Delete an agent. For a user-authored bundle this ALSO deletes its member subagents (each subagent's runs/triggers/versions cascade); member sites and databases are NOT deleted (only their membership rows go). For a CATEGORY grouping (auto-created, named after a subagent category) it removes ONLY the grouping — the subagents stay. NEVER use delete to RENAME or RELABEL a category/agent — that destroys subagents; use s16_update_agent to rename a bundle, or s16_update_subagent({ category }) to move/relabel a subagent. Returns { success, changed, removedAgents } — changed:false means the agent was already gone; removedAgents:0 for a category grouping.

**Input:**
  - `workflowId`: string (uuid) . Agent id

```json
{
  "tool": "s16_delete_agent",
  "arguments": {
    "workflowId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_get_agent`

Get an agent with its members (items: each { id, itemType: subagent|site|database, itemId, name, position } — name is the resolved display name of the subagent/site/database, or null). Use s16_agent_graph to see how the members wire together.

**Input:**
  - `workflowId`: string (uuid) . Agent id

```json
{
  "tool": "s16_get_agent",
  "arguments": {
    "workflowId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_list_agents`

List all agents (resource bundles) in the workspace.

```json
{
  "tool": "s16_list_agents",
  "arguments": {}
}
```

#### `s16_publish_agent`

Publish an agent to the global marketplace so any workspace can install it as a unit. PLATFORM ADMIN only. The agent must be in your workspace; sets is_public=true, scope=global. Returns the updated agent.

**Input:**
  - `workflowId`: string (uuid) . Agent id (must belong to your workspace)

```json
{
  "tool": "s16_publish_agent",
  "arguments": {
    "workflowId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_update_agent`

Rename an agent or change its description. Use THIS (not delete) to rename an agent/category bundle — renaming never removes subagents. Lets you fix a mistyped bundle without destroy-and-rebuild. Returns the updated agent.

**Input:**
  - `workflowId`: string (uuid) . Agent id
  - `name`?: string . New name
  - `description`?: string . New description

```json
{
  "tool": "s16_update_agent",
  "arguments": {
    "workflowId": "00000000-0000-0000-0000-000000000000"
  }
}
```

### blocks

#### `s16_create_block`

Create a single block inside a page. Low-level — to author rich content prefer s16_update_page_content (HTML/markdown). Returns the new block.

**Input:**
  - `pageId`: string (uuid) . Page ID
  - `parentBlockId`?: string (uuid) . Optional parent block ID
  - `type`: enum(paragraph | heading_1 | heading_2 | heading_3 | bulleted_list | numbered_list | to_do | toggle | code | quote | divider | image | file | table | embed_database | callout | bookmark) . Block type: paragraph, heading_1/2/3, bulleted_list, numbered_list, to_do, toggle, code, quote, divider, image, file, table, embed_database, callout, bookmark
  - `content`?: record . Free-form JSONB payload whose keys depend on type. Text-bearing blocks (paragraph/heading/quote/callout) carry rich text under content.html. {} for divider.
  - `position`?: number . Optional block position

```json
{
  "tool": "s16_create_block",
  "arguments": {
    "pageId": "00000000-0000-0000-0000-000000000000",
    "type": "paragraph"
  }
}
```

#### `s16_delete_block`

Delete a block from a page. Returns { success }.

**Input:**
  - `blockId`: string (uuid) . Block ID

```json
{
  "tool": "s16_delete_block",
  "arguments": {
    "blockId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_list_blocks`

List blocks for a page in editor order. Returns block rows ({ id, type, content, position, ... }). For reading/writing page body as HTML prefer s16_get_page_content / s16_update_page_content.

**Input:**
  - `pageId`: string (uuid) . Page ID

```json
{
  "tool": "s16_list_blocks",
  "arguments": {
    "pageId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_update_block`

Update a block's type, content, or position. Returns the updated block.

**Input:**
  - `blockId`: string (uuid) . Block ID
  - `type`?: enum(paragraph | heading_1 | heading_2 | heading_3 | bulleted_list | numbered_list | to_do | toggle | code | quote | divider | image | file | table | embed_database | callout | bookmark) . Block type
  - `content`?: record . Free-form JSONB payload whose keys depend on type. Text-bearing blocks carry rich text under content.html. Replaces the existing content.
  - `position`?: number . Block position

```json
{
  "tool": "s16_update_block",
  "arguments": {
    "blockId": "00000000-0000-0000-0000-000000000000"
  }
}
```

### cells

#### `s16_update_cell`

Update a single cell value on a row. Use property ID (UUID). Value types by property type: text/title/url/email/phone→string, number→number, checkbox→boolean, date→ISO string, select/status→option label string, multi_select→comma-separated labels, relation→comma-separated target page UUIDs or string[] of UUIDs (UUIDs, not titles — resolve names via s16_list_pages first), files→array of { name, url, size?, mimeType? } objects (use s16_upload_file first to get URLs). Prefer s16_bulk_update_cells to set several fields at once. Returns { success }.

**Input:**
  - `pageId`: string (uuid) . Page ID
  - `propertyId`: string (uuid) . Property ID (UUID from s16_get_database)
  - `value`: union . New value

```json
{
  "tool": "s16_update_cell",
  "arguments": {
    "pageId": "00000000-0000-0000-0000-000000000000",
    "propertyId": "00000000-0000-0000-0000-000000000000",
    "value": null
  }
}
```

### credentials

#### `s16_create_credential`

Create a service credential (API key, bot token, OAuth token, etc.) (workspace admin/owner only). Credentials are stored as JSON and accessible in agent scripts via brain.getCredential(service, title?). Common services: telegram, airtable, slack, github, openai, stripe, twilio.

**Input:**
  - `service`: string . Service identifier (e.g. "telegram", "airtable", "slack")
  - `title`?: string . Human-readable title (e.g. "Orbit Bot", "S16vc CRM 2.0")
  - `credentials`: record . Credentials JSON. Telegram: { botToken, botUsername? }. Airtable: { apiKey, baseId }. Generic: { apiKey } or any key-value pairs.
  - `connectionType`?: enum(manual | oauth) . manual or oauth
  - `provider`?: enum(google | krisp | generic_oauth) . OAuth provider ID
  - `capabilities`?: array<enum> . Credential capabilities
  - `accountIdentifier`?: string . Optional email/account identifier
  - `visibility`?: enum(public | private) . public — anyone in workspace and any agent can use; private — only creator and agents owned by creator. Defaults to private.

```json
{
  "tool": "s16_create_credential",
  "arguments": {
    "service": "service",
    "credentials": null
  }
}
```

#### `s16_delete_credential`

Delete a service credential (workspace admin/owner only). Irreversible.

**Input:**
  - `id`: string (uuid) . Credential ID

```json
{
  "tool": "s16_delete_credential",
  "arguments": {
    "id": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_get_credential`

Get a single credential with provider-aware metadata (workspace admin/owner only).

**Input:**
  - `credentialId`: string (uuid) . Credential ID

```json
{
  "tool": "s16_get_credential",
  "arguments": {
    "credentialId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_get_credential_auth_session`

Get the current status of a credential OAuth session (workspace admin/owner only).

**Input:**
  - `sessionId`: string (uuid) . OAuth session ID

```json
{
  "tool": "s16_get_credential_auth_session",
  "arguments": {
    "sessionId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_list_credential_providers`

List available credential providers and their supported capabilities (workspace admin/owner only).

```json
{
  "tool": "s16_list_credential_providers",
  "arguments": {}
}
```

#### `s16_update_credential`

Update an existing service credential (workspace admin/owner only). The credentials field REPLACES the entire stored JSON object — pass the full set, not a partial patch.

**Input:**
  - `id`: string (uuid) . Credential ID
  - `title`?: string . New title
  - `credentials`?: record . New credentials JSON (replaces entire object)
  - `status`?: enum(active | inactive) . Status
  - `connectionType`?: enum(manual | oauth) . manual or oauth
  - `provider`?: enum(google | krisp | generic_oauth) . OAuth provider ID
  - `capabilities`?: array<enum> . Credential capabilities
  - `accountIdentifier`?: string . Optional email/account identifier
  - `visibility`?: enum(public | private) . public — anyone in workspace and any agent can use; private — only creator and agents owned by creator

```json
{
  "tool": "s16_update_credential",
  "arguments": {
    "id": "00000000-0000-0000-0000-000000000000"
  }
}
```

### databases

#### `s16_create_database`

Create a new database in the workspace. Automatically creates the primary title property and a default table view. Returns the new database row.

**Input:**
  - `name`: string . Database name
  - `parentPageId`?: string (uuid) . Nest this database under an existing page (embedded/inline DB). Omit for a top-level database; databases with a parentPageId are excluded from s16_list_databases.
  - `isPrivate`?: boolean . Whether the database is private to its creator

```json
{
  "tool": "s16_create_database",
  "arguments": {
    "name": "name"
  }
}
```

#### `s16_delete_database`

Delete a database and all its rows (workspace admin/owner only; irreversible). Returns { success }.

**Input:**
  - `databaseId`: string (uuid) . Database ID

```json
{
  "tool": "s16_delete_database",
  "arguments": {
    "databaseId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_get_database`

Get a database schema: properties (id, name, type, isPrimary), views, and templates (id, name, isDefault). Use property IDs for filters/sorts in s16_list_pages. Use template IDs with s16_get_template/s16_apply_template.

**Input:**
  - `databaseId`: string (uuid) . Database ID

```json
{
  "tool": "s16_get_database",
  "arguments": {
    "databaseId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_list_databases`

List all databases in the workspace. Returns only id and name. Use s16_get_database to get properties.

```json
{
  "tool": "s16_list_databases",
  "arguments": {}
}
```

#### `s16_reorder_databases`

Reorder top-level databases in the workspace sidebar. Returns the reordered database list.

**Input:**
  - `orderedIds`: array<string> . Database IDs in final order

```json
{
  "tool": "s16_reorder_databases",
  "arguments": {
    "orderedIds": []
  }
}
```

#### `s16_set_database_default_template`

Set or clear the default template applied to new rows in a database (templateId null clears it). Returns the updated database.

**Input:**
  - `databaseId`: string (uuid) . Database ID
  - `templateId`?: string (uuid) . Template page ID or null to clear

```json
{
  "tool": "s16_set_database_default_template",
  "arguments": {
    "databaseId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_update_database`

Update database metadata: name, icon, property order, and privacy. Returns the updated database.

**Input:**
  - `databaseId`: string (uuid) . Database ID
  - `name`?: string . New database name
  - `icon`?: string . Icon: emoji ("🚀"), Phosphor name ("icon:Rocket"), or image URL
  - `propertyOrder`?: array<string> . Ordered property IDs
  - `isPrivate`?: boolean . Whether the database is private

```json
{
  "tool": "s16_update_database",
  "arguments": {
    "databaseId": "00000000-0000-0000-0000-000000000000"
  }
}
```

### docs

#### `s16_create_doc`

Create a new doc in the workspace. Can set title and rich content in one call (markdown default, or HTML). For images/files/charts pass HTML embeds — full spec: s16_guide({ category: "rich_embeds" }). Returns the new doc plus a url.

**Input:**
  - `title`: string . Doc title
  - `parentId`?: string (uuid) . Parent doc ID
  - `content`?: string . Doc body content in markdown or HTML
  - `contentFormat`?: enum(markdown | html) . Content format
  - `isPrivate`?: boolean . Whether the doc is private

```json
{
  "tool": "s16_create_doc",
  "arguments": {
    "title": "title"
  }
}
```

#### `s16_delete_doc`

Archive a doc and its subtree to trash. Reversible via s16_restore_doc. Returns { success }.

**Input:**
  - `docId`: string (uuid) . Doc ID

```json
{
  "tool": "s16_delete_doc",
  "arguments": {
    "docId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_delete_doc_permanently`

Permanently delete a doc and its subtree (irreversible). Returns { success }.

**Input:**
  - `docId`: string (uuid) . Doc ID

```json
{
  "tool": "s16_delete_doc_permanently",
  "arguments": {
    "docId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_get_doc`

Get a doc with its metadata (id, title, parentId, icon, coverUrl, shareId, config, ...) plus a url. Use s16_get_doc_content for the HTML body.

**Input:**
  - `docId`: string (uuid) . Doc ID

```json
{
  "tool": "s16_get_doc",
  "arguments": {
    "docId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_get_doc_ancestors`

Get the ancestor chain of a doc, root first. Returns [{ id, title, parentId }] (the doc itself excluded).

**Input:**
  - `docId`: string (uuid) . Doc ID

```json
{
  "tool": "s16_get_doc_ancestors",
  "arguments": {
    "docId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_get_doc_backlinks`

Find docs whose body links to the target doc (by docId substring match). Returns an array of doc rows, newest first.

**Input:**
  - `docId`: string (uuid) . Doc ID
  - `limit`?: number . Max results

```json
{
  "tool": "s16_get_doc_backlinks",
  "arguments": {
    "docId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_get_doc_content`

Get the rich content (HTML) of a doc. Returns full HTML body.

**Input:**
  - `docId`: string (uuid) . Doc ID

```json
{
  "tool": "s16_get_doc_content",
  "arguments": {
    "docId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_get_doc_images`

List image files embedded in a doc, each with a directly fetchable URL (signed, ~5 min, for private files). Returns [{ fileId, url, name, mimeType, size }].

**Input:**
  - `docId`: string (uuid) . Doc ID

```json
{
  "tool": "s16_get_doc_images",
  "arguments": {
    "docId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_list_doc_children`

List the direct child docs of a doc, in sidebar order. Returns an array of doc rows.

**Input:**
  - `docId`: string (uuid) . Parent doc ID

```json
{
  "tool": "s16_list_doc_children",
  "arguments": {
    "docId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_list_docs`

List docs in the workspace (top-level, or children of parentId). Returns [{ id, title, parentId, createdAt, updatedAt, url }].

**Input:**
  - `parentId`?: string (uuid) . Parent doc ID (omit for root docs)

```json
{
  "tool": "s16_list_docs",
  "arguments": {}
}
```

#### `s16_move_doc`

Move a doc under another parent, or to the root (parentId null). Returns the updated doc.

**Input:**
  - `docId`: string (uuid) . Doc ID
  - `parentId`?: string (uuid) . New parent doc ID or null

```json
{
  "tool": "s16_move_doc",
  "arguments": {
    "docId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_set_doc_content_from_file`

Replace a doc body with the content of a previously uploaded file (see s16_upload_file / s16_create_upload_url). The server reads the file directly, so there is no tool-input size limit — use this for very large docs (long reports, DDQ responses, etc.) that do not fit in a single tool call. The file should contain markdown or HTML text. Content format is inferred from the file name / MIME type (.md or text/markdown → markdown, otherwise HTML) unless contentFormat is given. Replaces all existing content.

**Input:**
  - `docId`: string (uuid) . Doc ID
  - `fileId`: string (uuid) . File ID of an uploaded markdown/HTML file
  - `contentFormat`?: enum(markdown | html) . Override content format. Defaults to inference from the file name / MIME type.

```json
{
  "tool": "s16_set_doc_content_from_file",
  "arguments": {
    "docId": "00000000-0000-0000-0000-000000000000",
    "fileId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_set_doc_sharing`

Enable or disable public sharing for a doc. Returns the updated doc (shareId set when public).

**Input:**
  - `docId`: string (uuid) . Doc ID
  - `isPublic`: boolean . Whether the doc should be public

```json
{
  "tool": "s16_set_doc_sharing",
  "arguments": {
    "docId": "00000000-0000-0000-0000-000000000000",
    "isPublic": false
  }
}
```

#### `s16_update_doc`

Update a doc: title, icon, cover, config, hierarchy, archived/private flags, and/or body content (markdown default or HTML; replaces existing). For images/files/charts pass HTML embeds — full spec: s16_guide({ category: "rich_embeds" }). Returns the updated doc.

**Input:**
  - `docId`: string (uuid) . Doc ID
  - `title`?: string . New title
  - `icon`?: string . Icon: emoji ("🚀"), Phosphor name ("icon:Rocket"), or image URL
  - `coverUrl`?: string . Cover image URL
  - `config`?: record . Doc config merge patch, e.g. { fullWidth: true }
  - `parentId`?: string (uuid) . Parent doc ID
  - `position`?: number . Sibling position
  - `isArchived`?: boolean . Archived state
  - `isPrivate`?: boolean . Private state
  - `content`?: string . Doc body content in markdown or HTML (replaces existing)
  - `contentFormat`?: enum(markdown | html) . Content format

```json
{
  "tool": "s16_update_doc",
  "arguments": {
    "docId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_update_doc_content`

Set the rich content of a doc, replacing all existing content. Accepts markdown (converted to HTML) or raw HTML. Returns { success, docId, contentLength }. For images/files/charts pass HTML embeds — full spec: s16_guide({ category: "rich_embeds" }).

Supported markdown syntax:
- Headings: # H1, ## H2, ### H3
- Bold: **text**, Italic: *text*, Strikethrough: ~~text~~
- Inline code: `code`, Links: [text](url)
- Bullet list: - item
- Numbered list: 1. item
- Task list (to-do): - [ ] unchecked, - [x] checked
- Blockquote: > text
- Code block: ```lang ... ```
- Horizontal rule: ---
- Collapsible block (toggle): >>> Title\ncontent lines\n<<< (use ">>> closed: Title" to start collapsed)

**Input:**
  - `docId`: string (uuid) . Doc ID
  - `content`: string . Content in markdown or HTML format
  - `contentFormat`?: enum(markdown | html) . Content format: markdown (default) or html

```json
{
  "tool": "s16_update_doc_content",
  "arguments": {
    "docId": "00000000-0000-0000-0000-000000000000",
    "content": "content"
  }
}
```

### files

#### `s16_create_file`

Register a file record (step 3 of the s16_create_upload_url flow — pass the id + s3Key it returned). Returns the created file record.

**Input:**
  - `id`?: string (uuid) . Pre-allocated file id from s16_create_upload_url
  - `name`: string . File name
  - `url`: string (url) . File URL. A direct S3 bucket URL (the publicUrl of a public create_upload_url) registers a PUBLIC, embed-anywhere file; a /file/serve proxy URL registers a PRIVATE file.
  - `s3Key`?: string . S3 object key from s16_create_upload_url
  - `size`?: number . Optional file size in bytes
  - `mimeType`?: string . Optional MIME type
  - `public`?: boolean . Force public/private, overriding the URL-shape inference.

```json
{
  "tool": "s16_create_file",
  "arguments": {
    "name": "name",
    "url": "https://example.com"
  }
}
```

#### `s16_delete_file`

Delete a file record. Returns { success }.

**Input:**
  - `fileId`: string (uuid) . File ID

```json
{
  "tool": "s16_delete_file",
  "arguments": {
    "fileId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_get_file_url`

Get a directly fetchable, temporary URL for a workspace file by id (signed, ~5 min, for private files). Lets you hand a Brain file to an external service (Telegram, email) without making the whole doc public. Returns { url, mimeType, name }.

**Input:**
  - `fileId`: string (uuid) . File ID

```json
{
  "tool": "s16_get_file_url",
  "arguments": {
    "fileId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_list_files`

List files uploaded in the current workspace. Returns file rows ({ id, name, url, size, mimeType, ... }); url is an auth-gated proxy — use s16_get_file_url for a fetchable signed URL.

```json
{
  "tool": "s16_list_files",
  "arguments": {}
}
```

#### `s16_upload_file`

Upload a small file from base64-encoded data to S3 storage. Returns the created file record with its hosted URL. Use the returned file record to set a "files" property value via s16_update_cell or s16_bulk_update_cells (pass an array of { name, url, size?, mimeType? } objects). NOTE: tool inputs are limited (~100 KB raw, ~150 KB after base64). For anything larger (decks, PDFs, contracts, models) use s16_create_upload_url + s16_create_file instead — that path streams the file directly to S3 without going through the MCP request body.

**Input:**
  - `filename`: string . File name including extension (e.g. "deck.pdf")
  - `contentType`?: string . MIME type (e.g. "application/pdf"). Defaults to application/octet-stream.
  - `base64`: string . Base64-encoded file content
  - `public`?: boolean . Make the file publicly embeddable (no auth). Defaults to TRUE for images so <img> embeds render everywhere — incl. share pages, emails, Telegram, off-platform; non-images default private.

```json
{
  "tool": "s16_upload_file",
  "arguments": {
    "filename": "filename",
    "base64": "base64"
  }
}
```

### pages

#### `s16_archive_page`

Mark a row archived (sets isArchived; hidden from default views, still queryable and editable). Reversible. NOT trash — use s16_delete_page to trash. Returns { success }.

**Input:**
  - `pageId`: string (uuid) . Page ID

```json
{
  "tool": "s16_archive_page",
  "arguments": {
    "pageId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_count_pages`

Count rows in a database with optional ANDed filters and search. Returns { count }.

**Input:**
  - `databaseId`: string (uuid) . Database ID
  - `filters`?: array<object> . Column filters as [{ propertyId, operator, value }], all ANDed
  - `search`?: string . Search term

```json
{
  "tool": "s16_count_pages",
  "arguments": {
    "databaseId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_create_page`

Create a new row in a database. Can set title, property values, and rich content in one call. For cells, use property NAME (case-insensitive) as key. Value types: text/title/url/email/phone→string, number→number, checkbox→boolean, date→ISO string, select/status→option label string, multi_select→comma-separated labels or string[], relation→comma-separated target page UUIDs or string[] of UUIDs. Returns the new page fields at top level plus { warnings, url } (responseFormat "minimal" → { id }, "cells" → { id, propertiesCache }; both avoid echoing the table schema, ~13K tokens/call on wide tables).

**Input:**
  - `databaseId`: string (uuid) . Database ID
  - `title`?: string . Page title (primary property value)
  - `cells`?: record . Property values as { propertyName: value }. Use property NAME (case-insensitive). For relation: pass string[] of UUIDs. Example: { "Status": "Active", "Company": ["uuid1", "uuid2"] }
  - `content`?: string . Page body in markdown or HTML. For images/files/charts use contentFormat "html" with <div data-type="file-embed">/<div data-type="chart-embed">. Full embed spec: s16_guide({ category: "rich_embeds" }). Fails if the page is open in a live editor — retry after editors disconnect.
  - `contentFormat`?: enum(markdown | html) . Content format. MUST be "html" when content includes <div data-type="file-embed"> or <div data-type="chart-embed"> — markdown mode strips them.
  - `responseFormat`?: enum(minimal | cells | full) . Response verbosity. "full" (default): complete page object with per-cell schema and content. "cells": id + row values keyed by property ID, no schema. "minimal": { id } only — avoids echoing the table schema (~13K tokens/call on wide tables), best for bulk writes.

```json
{
  "tool": "s16_create_page",
  "arguments": {
    "databaseId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_delete_page`

Move a row to trash (sets deletedAt; blocks further edits until restored). Distinct from s16_archive_page. No MCP restore tool — restore from trash in the UI. Returns { success }.

**Input:**
  - `pageId`: string (uuid) . Page ID

```json
{
  "tool": "s16_delete_page",
  "arguments": {
    "pageId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_get_page`

Get a row by ID. Returns the page with cellValues (per-cell with property), blocks (body), propertiesCache (FLAT), isDeleted and url. For body HTML use s16_get_page_content.

**Input:**
  - `pageId`: string (uuid) . Page ID

```json
{
  "tool": "s16_get_page",
  "arguments": {
    "pageId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_get_page_content`

Get the rich content (HTML) of a page/row. Returns the full HTML body of the page editor.

**Input:**
  - `pageId`: string (uuid) . Page ID

```json
{
  "tool": "s16_get_page_content",
  "arguments": {
    "pageId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_get_page_images`

List image files embedded in a page/row, each with a directly fetchable URL (signed, ~5 min, for private files). Use this instead of regexing page HTML or hitting /file/serve yourself. Returns [{ fileId, url, name, mimeType, size }].

**Input:**
  - `pageId`: string (uuid) . Page/row ID

```json
{
  "tool": "s16_get_page_images",
  "arguments": {
    "pageId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_list_pages`

List rows in a database with search, ANDed filters, sort and field projection. Returns { items, nextCursor }; each item has { id, propertiesCache (FLAT, may be stale — pass withCells to refresh given properties), url, ... }. Note: relation cells in propertiesCache hold TITLES; get target UUIDs via s16_list_relations.

**Input:**
  - `databaseId`: string (uuid) . Database ID
  - `limit`?: number . Max pages (default 50)
  - `cursor`?: number . Offset cursor for pagination
  - `search`?: string . Full-text search across all cell values
  - `searchField`?: string (uuid) . Restrict search to this property ID
  - `filters`?: array<object> . Column filters as [{ propertyId, operator, value }], all ANDed
  - `sorts`?: array<object> . Sort order as [{ propertyId, direction }]
  - `fields`?: array<string> . Return only these property IDs from propertiesCache (primary always included)
  - `withCells`?: array<string> . Load fresh cell values from DB for these property IDs

```json
{
  "tool": "s16_list_pages",
  "arguments": {
    "databaseId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_set_page_sharing`

Enable or disable public sharing for a row. Returns the updated page (shareId set when public).

**Input:**
  - `pageId`: string (uuid) . Page ID
  - `isPublic`: boolean . Whether the page should be public

```json
{
  "tool": "s16_set_page_sharing",
  "arguments": {
    "pageId": "00000000-0000-0000-0000-000000000000",
    "isPublic": false
  }
}
```

#### `s16_update_page`

Update an existing row: set cell values and/or replace page content. Use property NAME (case-insensitive) for cells. Content can be markdown (default) or HTML. Returns { page, warnings } (page fields are nested under "page", unlike s16_create_page which is flat); responseFormat "minimal" → { id }, "cells" → { id, propertiesCache } to keep responses small for bulk writes.

**Input:**
  - `pageId`: string (uuid) . Page ID
  - `databaseId`: string (uuid) . Database ID (needed to resolve property names)
  - `cells`?: record . Property values as { propertyName: value }
  - `content`?: string . Page body in markdown or HTML (replaces existing). For images/files/charts use contentFormat "html". Full embed spec: s16_guide({ category: "rich_embeds" }). Fails if the page is open in a live editor — retry after editors disconnect.
  - `contentFormat`?: enum(markdown | html) . Content format. MUST be "html" when content includes <div data-type="file-embed"> or <div data-type="chart-embed"> — markdown mode strips them.
  - `icon`?: string . Page icon
  - `coverUrl`?: string (url) . Page cover URL
  - `config`?: record . Page config merge patch, e.g. { fullWidth: true }
  - `responseFormat`?: enum(minimal | cells | full) . Response verbosity. "full" (default): complete page object with per-cell schema and content. "cells": id + row values keyed by property ID, no schema. "minimal": { id } only — avoids echoing the table schema (~13K tokens/call on wide tables), best for bulk writes.

```json
{
  "tool": "s16_update_page",
  "arguments": {
    "pageId": "00000000-0000-0000-0000-000000000000",
    "databaseId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_update_page_content`

Set the rich content of a row, replacing all existing content. Accepts markdown (converted to HTML) or raw HTML. Returns { success, pageId, contentLength }; fails if the page is open in a live editor — retry after editors disconnect.

Supported markdown syntax:
- Headings: # H1, ## H2, ### H3
- Bold: **text**, Italic: *text*, Strikethrough: ~~text~~
- Inline code: `code`, Links: [text](url)
- Bullet list: - item
- Numbered list: 1. item
- Task list (to-do): - [ ] unchecked, - [x] checked
- Blockquote: > text
- Code block: ```lang ... ```
- Horizontal rule: ---
- Collapsible block (toggle): >>> Title\ncontent lines\n<<< (use ">>> closed: Title" to start collapsed)

For images / files / charts, pass contentFormat "html" and emit <div data-type="file-embed">/<div data-type="chart-embed"> elements. Full embed spec + attributes + gotchas: s16_guide({ category: "rich_embeds" }).

**Input:**
  - `pageId`: string (uuid) . Page ID
  - `content`: string . Content in markdown or HTML format. Use HTML for images/files/charts (see tool description).
  - `contentFormat`?: enum(markdown | html) . Content format: "markdown" (default) or "html". MUST be "html" when content includes <div data-type="file-embed"> or <div data-type="chart-embed">.

```json
{
  "tool": "s16_update_page_content",
  "arguments": {
    "pageId": "00000000-0000-0000-0000-000000000000",
    "content": "content"
  }
}
```

### platform

#### `s16_add_agent_item`

Add a member (subagent, site, or database — referenced by its own id) to an agent. Idempotent: re-adding the same member is a no-op. The member must belong to the same workspace. Pass an optional position to control ordering.

**Input:**
  - `workflowId`: string (uuid) . Agent id
  - `itemType`: enum(subagent | site | database) . subagent | site | database
  - `itemId`: string (uuid) . Id of the subagent / site / database to add
  - `position`?: number . Optional 0-based ordering position among members

```json
{
  "tool": "s16_add_agent_item",
  "arguments": {
    "workflowId": "00000000-0000-0000-0000-000000000000",
    "itemType": "subagent",
    "itemId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_add_relation`

Link a target row to a source row through a relation property. If the relation is two-way (has inversePropertyId), the inverse side is updated automatically. Returns { relations, page } (the source row's updated relation list and its page).

**Input:**
  - `sourcePageId`: string (uuid) . Source page ID
  - `targetPageId`: string (uuid) . Target page ID
  - `propertyId`: string (uuid) . Relation property ID

```json
{
  "tool": "s16_add_relation",
  "arguments": {
    "sourcePageId": "00000000-0000-0000-0000-000000000000",
    "targetPageId": "00000000-0000-0000-0000-000000000000",
    "propertyId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_admin_add_credits`

Add credits to any workspace (platform admin only). Resolves the workspace by id (UUID) or slug, records a manual top-up entry for audit, and updates the credit balance. Returns { workspace, creditsAdded, amountUsdEquivalent, balance: { balanceCredits, lockedCredits }, topupId }.

**Input:**
  - `workspace`: string . Workspace id (UUID) or slug
  - `credits`: number . Number of credits to add (must be positive)
  - `note`?: string . Free-form audit note saved alongside the top-up record

```json
{
  "tool": "s16_admin_add_credits",
  "arguments": {
    "workspace": "workspace",
    "credits": 0
  }
}
```

#### `s16_agent_graph`

Get the dependency graph of an agent: nodes (subagents, tools, databases, credentials, secrets, sub-agents, triggers, the site) and edges showing how the bundle wires together (who calls whom, who shares a database/credential, who feeds the site).

**Input:**
  - `workflowId`: string (uuid) . Agent id

```json
{
  "tool": "s16_agent_graph",
  "arguments": {
    "workflowId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_agent_readiness`

Check whether an agent is ready to run: aggregates its member subagents’ missing credentials, secrets, and databases. Returns { ready, empty, missing: { credentials, secrets, databases } }. `empty:true` (with ready:false) means the bundle has no member subagents yet.

**Input:**
  - `workflowId`: string (uuid) . Agent id

```json
{
  "tool": "s16_agent_readiness",
  "arguments": {
    "workflowId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_ai_chat`

Ask the full Brain AI chat a question and get its answer — the SAME agentic assistant as the in-app chat, headless. It autonomously reads/computes/acts over the workspace (it runs code over the whole Brain API as you, and only mutates data when the message clearly asks), and can produce charts/images. Use this to power a bot or external service that should "do everything the AI chat can" with one call, instead of orchestrating tools yourself. There is no server-side memory: pass prior turns as `history` for a multi-turn conversation. Returns { text, attachments, calledMethods, model } — send `text` back to your user. Billed to the workspace.

**Input:**
  - `prompt`: string . The user message / question to answer.
  - `model`?: string . Model id (e.g. 'anthropic/claude-sonnet-4.6'). Omit or 'auto' to route by task. List options with s16_list_models.
  - `reasoningEffort`?: enum(minimal | low | medium | high | xhigh | max) . Reasoning effort for models that support it.
  - `context`?: string . Extra grounding text to give the assistant.
  - `history`?: array<object> . Prior conversation turns YOU keep, oldest first — for multi-turn memory.
  - `images`?: array<string> . Up to 4 image URLs for the assistant to look at (vision).
  - `files`?: array<object> . Up to 5 text files (url + name) for the assistant to read.
  - `skillInstructions`?: string . A skill body to steer the assistant style/behaviour.
  - `imageModel`?: string . Image-generation model id (for when the assistant makes an image).

```json
{
  "tool": "s16_ai_chat",
  "arguments": {
    "prompt": "prompt"
  }
}
```

#### `s16_append_doc_content`

Append rich content to the END of a doc body without replacing existing content. Accepts markdown (converted to HTML) or raw HTML. Use this to build a large doc across several calls when the full content does not fit in a single tool call — push successive block-aligned chunks (e.g. ~10K tokens each). Chunk only at block boundaries (whole paragraphs/headings/tables), never mid-element. Appending to an empty doc behaves like setting its content. Supports the same markdown syntax as s16_update_doc_content.

**Input:**
  - `docId`: string (uuid) . Doc ID
  - `content`: string . Content to append, in markdown or HTML format
  - `contentFormat`?: enum(markdown | html) . Content format: markdown (default) or html

```json
{
  "tool": "s16_append_doc_content",
  "arguments": {
    "docId": "00000000-0000-0000-0000-000000000000",
    "content": "content"
  }
}
```

#### `s16_await_run`

Wait for a subagent run to settle — polls every 1s until status is success | failed | cancelled, or the timeout elapses (default 120s, max 300s). Returns the full run incl. status, output, scriptLogs, tokensUsed (still-running if it timed out).

**Input:**
  - `runId`: string (uuid) . Agent run ID to wait for
  - `timeoutSeconds`?: number . Max seconds to wait (default 120)

```json
{
  "tool": "s16_await_run",
  "arguments": {
    "runId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_bulk_delete_pages`

Move multiple rows to trash (soft delete, sets deletedAt). No MCP restore tool — restore from trash in the UI. Returns { success, deleted } (count actually trashed).

**Input:**
  - `pageIds`: array<string> . Array of Page IDs (max 100)

```json
{
  "tool": "s16_bulk_delete_pages",
  "arguments": {
    "pageIds": []
  }
}
```

#### `s16_bulk_update_cells`

Update several cell values on a row in one call. Use property NAME (case-insensitive) as key. Value types: text/title/url/email/phone→string, number→number, checkbox→boolean, date→ISO string, select/status→option label string, multi_select→comma-separated labels or string[], relation→comma-separated target page UUIDs or string[] of UUIDs (UUIDs, not titles), files→array of { name, url, size?, mimeType? } objects. Returns { success, updated, errors? } (errors lists per-property warnings for unresolved names or rejected values).

**Input:**
  - `pageId`: string (uuid) . Page ID
  - `databaseId`: string (uuid) . Database ID (needed to resolve property names)
  - `cells`: record . Property values as { propertyName: value }. Use property NAME (case-insensitive).

```json
{
  "tool": "s16_bulk_update_cells",
  "arguments": {
    "pageId": "00000000-0000-0000-0000-000000000000",
    "databaseId": "00000000-0000-0000-0000-000000000000",
    "cells": null
  }
}
```

#### `s16_call_mcp_tool`

Call a tool exposed by a remote MCP credential (workspace admin/owner only). Inspect s16_list_mcp_tools first and then call the selected tool by name.

**Input:**
  - `credentialId`: string (uuid) . Credential ID for the remote MCP connection
  - `toolName`: string . Remote MCP tool name
  - `args`?: record . Arguments passed to the remote tool

```json
{
  "tool": "s16_call_mcp_tool",
  "arguments": {
    "credentialId": "00000000-0000-0000-0000-000000000000",
    "toolName": "toolName"
  }
}
```

#### `s16_cancel_run`

Cancel a running subagent run: terminates the worker thread and sets run status to "cancelled". Safe to call on an already-finished run (no-op). Returns { cancelled } (false if nothing was running to cancel).

**Input:**
  - `runId`: string (uuid) . Agent run ID to cancel

```json
{
  "tool": "s16_cancel_run",
  "arguments": {
    "runId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_convert_relation_to_two_way`

Convert a one-way relation property to two-way: creates an inverse column on the target database and backfills existing relation data. Check isTwoWay in s16_get_database output first. Returns { success, inverseProperty: { id, name, databaseId } }.

**Input:**
  - `propertyId`: string (uuid) . One-way relation property ID to convert

```json
{
  "tool": "s16_convert_relation_to_two_way",
  "arguments": {
    "propertyId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_create_subagent`

Create a new subagent, then give it logic by writing its compiledScript via s16_update_subagent — you write the JS (read s16_guide({ category: "agents" }) first so you use real brain.* methods, not invented ones). A vm subagent runs that script as cheap sandboxed JS; a subagent left WITHOUT a compiledScript falls back to an expensive LLM tool-calling loop on every run (driven by systemPrompt). Writing a non-empty compiledScript to a draft vm subagent AUTO-ACTIVATES it (status draft→active); after that, verify it with s16_smoke_test_subagent (or s16_run_subagent + s16_await_run) before declaring it ready. Use skillIds to attach reusable knowledge. The new subagent is auto-wrapped in its own agent (bundle); when several subagents cooperate on one solution, add them to ONE shared agent (bundle) with s16_add_agent_item rather than leaving separate one-subagent bundles. Returns the created subagent.

Execution backend: subagents default to 'vm' (sandboxed JS via the FLAT brain.* API — best for Brain data CRUD, HTTP, AI, transforms, scheduled jobs). Set executionBackend:'computer' (with agenticConfig) ONLY when the subagent needs a real OS — a shell, writable filesystem, package install (pip/npm), git, a headless browser (Playwright), or long-running binaries; it runs in a gVisor pod (platform admin only, ~55s cold start). If the vm brain.* sandbox can do the job, use vm. A compiledScript is vm-only — do NOT set one on a computer-tier subagent (it always runs an agentic loop).

**Input:**
  - `name`: string . Subagent name
  - `category`?: string . Optional: a sidebar GROUP LABEL for the subagent (NOT its name). Changing it re-groups the subagent; clearing it (null) ungroups it. This never deletes the subagent. To "rename a category", update this field on its subagents.
  - `description`?: string . Subagent description
  - `systemPrompt`?: string . System prompt for the subagent
  - `model`?: string . OpenRouter model ID (e.g. anthropic/claude-sonnet-4.6); discover current models via s16_list_models
  - `modelParams`?: record . Model params: { temperature, max_tokens, ... }
  - `tools`?: array<any> . Custom tool definitions (OpenAI function calling format)
  - `inputSchema`?: record . JSON Schema for structured input
  - `skillIds`?: array<string> . Skill IDs to assign
  - `isLocked`?: boolean . If true, only owner can edit
  - `status`?: enum(active | draft | archived) . Subagent status
  - `timeout`?: number . Execution timeout in seconds (default 3600 = 1 hour, max 86400 = 24 hours). For computer-tier subagents this one value also caps the pod TTL and EACH run_code call (which otherwise defaults to 120s) — raise it (e.g. 1800) for long tasks or when the subagent uses s16.askUser, which blocks a run_code call until the user replies.
  - `executionBackend`?: enum(vm | computer) . 'vm' (default sandboxed JS) or 'computer' (gVisor pod with real shell/fs/browser; platform-admin only)
  - `agenticConfig`?: object . Agentic runtime config (model, capabilities, sizeTier, persistent, allowDestructive, …). Required shape for computer-tier subagents.

```json
{
  "tool": "s16_create_subagent",
  "arguments": {
    "name": "name"
  }
}
```

#### `s16_create_upload_url`

Generate a presigned PUT URL for uploading a file directly to S3 storage. Use this for files of ANY size — the file content does not pass through the MCP request, so there is no ~100KB tool-input limit (unlike s16_upload_file). Workflow: 1) Call s16_create_upload_url to get { fileId, uploadUrl, publicUrl, key, requiredHeaders }. 2) PUT the file bytes directly to uploadUrl with the listed requiredHeaders — always echo the returned x-amz-acl header (e.g. `curl -X PUT -H "Content-Type: ..." -H "x-amz-acl: public-read" --data-binary @photo.png "<uploadUrl>"` for a public file, or `x-amz-acl: private` for a private one). 3) Call s16_create_file with { id: fileId, name, url: publicUrl, s3Key: key, size, mimeType } to register the upload. 4) Use that record to fill a "files" property via s16_update_cell or s16_bulk_update_cells. The signed URL expires in 600 seconds. When public:true, the requiredHeaders carry x-amz-acl: public-read and the publicUrl is a DIRECT, embed-anywhere S3 URL (renders in <img> off-platform). Otherwise the upload is private and the publicUrl is an auth-gated proxy that resolves a fresh short-lived signed URL per request. Defaults to public for images, private otherwise.

**Input:**
  - `filename`: string . File name including extension (e.g. "deck.pdf")
  - `contentType`?: string . MIME type (e.g. "application/pdf"). Defaults to application/octet-stream.
  - `public`?: boolean . Make the file publicly embeddable (no auth). Defaults to TRUE for images so <img> embeds render everywhere — incl. share pages, emails, Telegram, off-platform; non-images default private.

```json
{
  "tool": "s16_create_upload_url",
  "arguments": {
    "filename": "filename"
  }
}
```

#### `s16_delete_subagent`

Delete a subagent (irreversible; locked subagents: owner only). Deleting the last subagent in a category now removes the empty category grouping automatically. Returns { success }.

**Input:**
  - `agentId`: string (uuid) . Agent ID

```json
{
  "tool": "s16_delete_subagent",
  "arguments": {
    "agentId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_delete_trigger`

Delete a subagent trigger by id (irreversible). Returns { success }.

**Input:**
  - `triggerId`: string (uuid) . Trigger ID

```json
{
  "tool": "s16_delete_trigger",
  "arguments": {
    "triggerId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_duplicate_doc`

Duplicate a doc and its whole subtree. Returns the new root doc.

**Input:**
  - `docId`: string (uuid) . Doc ID

```json
{
  "tool": "s16_duplicate_doc",
  "arguments": {
    "docId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_duplicate_page`

Duplicate a row including its cell values and page settings. Returns the new page (with id and FLAT propertiesCache).

**Input:**
  - `pageId`: string (uuid) . Page ID

```json
{
  "tool": "s16_duplicate_page",
  "arguments": {
    "pageId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_duplicate_property`

Duplicate an existing property/column (config only, not cell values). Returns the new property.

**Input:**
  - `propertyId`: string (uuid) . Property ID

```json
{
  "tool": "s16_duplicate_property",
  "arguments": {
    "propertyId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_edit_site_page_file`

Replace a unique substring in a file. The oldString must occur EXACTLY ONCE — include surrounding context to make it unique.

**Input:**
  - `sitePageId`: string (uuid) . Site page ID
  - `path`: string . File path
  - `oldString`: string . Exact substring to replace (must be unique in the file)
  - `newString`: string . Replacement string

```json
{
  "tool": "s16_edit_site_page_file",
  "arguments": {
    "sitePageId": "00000000-0000-0000-0000-000000000000",
    "path": "path",
    "oldString": "oldString",
    "newString": "newString"
  }
}
```

#### `s16_get_run`

Get one subagent run by id. Returns the full run incl. status, messages, scriptLogs, output, tokensUsed, startedAt/finishedAt.

**Input:**
  - `runId`: string (uuid) . Agent run ID

```json
{
  "tool": "s16_get_run",
  "arguments": {
    "runId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_get_subagent`

Get full subagent details including system prompt, model, tools, triggers. The response includes compiledScriptHash — pass it as expectedCompiledScriptHash to s16_update_subagent when you modify compiledScript, so a stale rewrite cannot overwrite newer changes.

**Input:**
  - `agentId`: string (uuid) . Agent ID

```json
{
  "tool": "s16_get_subagent",
  "arguments": {
    "agentId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_get_subagent_webhook_url`

Get the webhook URL for a subagent that has a webhook trigger configured. Returns { agentId, agentName, webhookUrl, mode } (mode = async | sync); errors if the subagent has no webhook trigger.

**Input:**
  - `agentId`: string (uuid) . Agent ID

```json
{
  "tool": "s16_get_subagent_webhook_url",
  "arguments": {
    "agentId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_guide`

Reference guide for Brain platform capabilities. Categories: "agents" (execution, computer tier, sandbox API, triggers, legacy/MCP tools, model selection), "page_templates" (content, styles, config, embeds, variables, MCP workflow), "components" (workspace HTML/CSS/JS blocks: code, sandbox/data API, embedding, security, MCP CRUD), "rich_embeds" (insert images/files/charts/components into a page or doc body), "data_model" (EAV concepts, cell value types, and the MCP s16_* ↔ sandbox brain.* name map). NOTE: MCP tools you call are s16_*; brain.* names are agent-script methods, NOT MCP tools. Call without sections to get the full guide for a category.

**Input:**
  - `category`: enum(agents | page_templates | components | rich_embeds | data_model) . Guide category
  - `format`?: enum(markdown | json) . Output format (default: markdown)
  - `sections`?: array<string> . Limit to specific sections. Agents: overview, execution, computerTier, legacyTools, sandboxApi, richEmbeds, mcpTools, modelSelection. Page templates: overview, content, styles, config, embeds, variables, mcpTools. Components: overview, code, sandboxApi, dataApi, embedding, security, mcpTools. Rich embeds: fileEmbed, chartEmbed, componentEmbed, filePrivacyModel. Data model: concepts, cellValueTypes, mcpVsSandbox

```json
{
  "tool": "s16_guide",
  "arguments": {
    "category": "agents"
  }
}
```

#### `s16_install_agent`

Install a published marketplace agent into THIS workspace as a unit. Deep-clones its subagents (as drafts, triggers off) and its site; databases are NOT cloned (workspace-specific) and come back as unmappedDatabases you must map with s16_remap_agent_database. Returns { workflow, unmappedDatabases: [{ sourceDbId, sourceName, referencedByAgentIds }] }.

**Input:**
  - `workflowId`: string (uuid) . Id of the PUBLISHED agent to install (from s16_marketplace_agents)

```json
{
  "tool": "s16_install_agent",
  "arguments": {
    "workflowId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_install_site_component`

Clone a public or system component into the current workspace. Returns the new workspace-scoped component.

**Input:**
  - `siteComponentId`: string (uuid) . ID of the public or system component to install

```json
{
  "tool": "s16_install_site_component",
  "arguments": {
    "siteComponentId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_install_skill`

Install a public skill from the marketplace into the workspace (workspace admin/owner only).

**Input:**
  - `skillId`: string (uuid) . Skill ID to install

```json
{
  "tool": "s16_install_skill",
  "arguments": {
    "skillId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_list_all_docs`

List all non-archived docs in the workspace as a flat collection. Returns each doc row plus a url.

```json
{
  "tool": "s16_list_all_docs",
  "arguments": {}
}
```

#### `s16_list_credentials`

List all service credentials in the workspace (workspace admin/owner only). Metadata only — the encrypted blob is omitted. Filter by service optionally. Returns [{ id, service, title, status, connectionType, provider, capabilities, accountIdentifier, visibility, createdBy, createdAt, updatedAt }].

**Input:**
  - `service`?: string . Filter by service (e.g. "telegram")

```json
{
  "tool": "s16_list_credentials",
  "arguments": {}
}
```

#### `s16_list_mcp_tools`

List tools available through a remote MCP credential (workspace admin/owner only). Use credentialId to choose the exact connected account.

**Input:**
  - `credentialId`: string (uuid) . Credential ID for the remote MCP connection

```json
{
  "tool": "s16_list_mcp_tools",
  "arguments": {
    "credentialId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_list_models`

List the AI models available to agents / brain.ai, with rich filters and sorting — so you (or an agent) pick a model that actually supports the input and fits the budget instead of guessing. Each model returns id, displayName, provider, mode, contextWindow, maxOutputTokens, inputs (accepted modalities), supports (booleans), and pricing in USD per 1M tokens.

**Input:**
  - `provider`?: union . Filter by provider: openai | anthropic | google | xai (the only supported providers; any other value matches nothing).
  - `mode`?: union . Filter by mode: chat | image_generation
  - `supports`?: array<enum> . Required capabilities: vision | audio | documents | tools | streaming
  - `supportsMatch`?: enum(all | any) . How to combine `supports`: "all" = AND (default), "any" = OR
  - `maxInputPerMTok`?: number . Max input price (USD per 1M tokens)
  - `maxOutputPerMTok`?: number . Max output price (USD per 1M tokens)
  - `minContextWindow`?: number . Minimum context window (tokens)
  - `search`?: string . Substring match on id + display name
  - `sortBy`?: enum(inputPrice | outputPrice | totalPrice | context | maxOutput | name | provider) . Sort key
  - `sortDir`?: enum(asc | desc) . Sort direction (default asc)
  - `limit`?: number
  - `offset`?: number

```json
{
  "tool": "s16_list_models",
  "arguments": {}
}
```

#### `s16_list_runs`

List recent subagent runs in the workspace, newest first; optionally filter by agentId. Returns an array of { id, agentId, status, triggerType, tokensUsed, createdAt, finishedAt } — fetch the full run (messages/logs/output) with s16_get_run.

**Input:**
  - `agentId`?: string (uuid) . Filter by agent ID
  - `limit`?: number . Max runs (default 30)

```json
{
  "tool": "s16_list_runs",
  "arguments": {}
}
```

#### `s16_list_secrets`

List all secret keys in the workspace (workspace admin/owner only). Values are NOT returned. Returns the key names only.

```json
{
  "tool": "s16_list_secrets",
  "arguments": {}
}
```

#### `s16_list_subagent_history`

List the change history of a subagent (who edited it, when, and which fields changed). Each entry includes changedFields, per-field {from, to} diff (large values truncated), source (app | mcp | restore | system), and the editor user info. Optionally filter by userId to see only one editor's changes.

**Input:**
  - `agentId`: string (uuid) . Agent ID
  - `limit`?: number . Max entries to return (default 50, max 200)
  - `userId`?: string (uuid) . Optional: only return changes made by this user

```json
{
  "tool": "s16_list_subagent_history",
  "arguments": {
    "agentId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_list_subagent_versions`

List the saved versions (restorable snapshots) of a subagent. Each entry has a version id, createdAt, optional changeDescription and editor. Use the version id with s16_restore_subagent_version. (Differs from s16_list_subagent_history, which returns the per-edit field diff.)

**Input:**
  - `agentId`: string (uuid) . Agent ID
  - `limit`?: number . Max versions to return (default 30)

```json
{
  "tool": "s16_list_subagent_versions",
  "arguments": {
    "agentId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_list_subagents`

List all subagents (single sandboxed scripts) in the workspace. Returns an array of { id, name, description, category, model, status, isLocked, accessLevel, triggers } (triggers = count) — call s16_get_subagent for the full systemPrompt/tools/compiledScript.

```json
{
  "tool": "s16_list_subagents",
  "arguments": {}
}
```

#### `s16_list_trash_docs`

List archived docs in workspace trash, newest first. Returns an array of doc rows; restore with s16_restore_doc.

**Input:**
  - `limit`?: number . Max trash docs

```json
{
  "tool": "s16_list_trash_docs",
  "arguments": {}
}
```

#### `s16_marketplace_agents`

List the global agent marketplace: every published bundle, with { id, name, description, icon, downloads, memberCounts: { agents, sites, databases } } (memberCounts.agents counts member subagents). Install one with s16_install_agent.

```json
{
  "tool": "s16_marketplace_agents",
  "arguments": {}
}
```

#### `s16_marketplace_skills`

List public skills shared by other members of the caller’s workspace (workspace admin/owner only). Pass global=true to list global (platform-curated) skills instead.

**Input:**
  - `global`?: boolean . When true, list global platform skills instead of the workspace marketplace.

```json
{
  "tool": "s16_marketplace_skills",
  "arguments": {}
}
```

#### `s16_owner_describe_schema`

List all tables and columns in the public schema (platform admin only). Returns { rows: [{ table_name, column_name, data_type, is_nullable, ... }], rowCount }.

**Input:**
  - `tableName`?: string . Filter by exact table name

```json
{
  "tool": "s16_owner_describe_schema",
  "arguments": {}
}
```

#### `s16_owner_query`

Execute a read-only SQL query on the platform database (platform admin only). Supports SELECT/WITH/EXPLAIN/SHOW. Read-only transaction with 10s timeout, auto LIMIT 1000 when missing, max 5000 rows. Returns { rows, rowCount, truncated, durationMs }.

**Input:**
  - `query`: string . Read-only SQL query

```json
{
  "tool": "s16_owner_query",
  "arguments": {
    "query": "query"
  }
}
```

#### `s16_read_site_page_file`

Read the contents of a single file in the site page virtual file system.

**Input:**
  - `sitePageId`: string (uuid) . Site page ID
  - `path`: string . File path (e.g. "index.html", "styles.css")

```json
{
  "tool": "s16_read_site_page_file",
  "arguments": {
    "sitePageId": "00000000-0000-0000-0000-000000000000",
    "path": "path"
  }
}
```

#### `s16_reject_waitlist_user`

Reject a waitlisted user — deletes the account entirely (irreversible). Identify by userId OR email. Refuses to delete users that are NOT on the waitlist. Restricted to platform admins. Returns { rejected, user } or { rejected:false, reason }.

**Input:**
  - `userId`?: string (uuid)
  - `email`?: string (email)

```json
{
  "tool": "s16_reject_waitlist_user",
  "arguments": {}
}
```

#### `s16_remap_agent_database`

Map a dangling database reference of an installed agent to a real database in this workspace. Rewrites the cloned member subagents’ compiled scripts (swapping the source DB id for the target) and adds the target database as an agent member. Run once per unmappedDatabases entry. Returns { rewrittenAgentIds, databaseMemberAdded }.

**Input:**
  - `workflowId`: string (uuid) . Installed agent id
  - `sourceDatabaseId`: string (uuid) . The dangling source DB id (from install’s unmappedDatabases)
  - `targetDatabaseId`: string (uuid) . A database in THIS workspace to map it to

```json
{
  "tool": "s16_remap_agent_database",
  "arguments": {
    "workflowId": "00000000-0000-0000-0000-000000000000",
    "sourceDatabaseId": "00000000-0000-0000-0000-000000000000",
    "targetDatabaseId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_remove_agent_item`

Remove a member (subagent, site, or database — by its own id + type) from an agent. Does NOT delete the underlying resource, only its membership. Returns { success, changed } — changed:false means no such member existed.

**Input:**
  - `workflowId`: string (uuid) . Agent id
  - `itemType`: enum(subagent | site | database) . subagent | site | database
  - `itemId`: string (uuid) . Id of the subagent / site / database to remove

```json
{
  "tool": "s16_remove_agent_item",
  "arguments": {
    "workflowId": "00000000-0000-0000-0000-000000000000",
    "itemType": "subagent",
    "itemId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_remove_relation`

Unlink a target row from a source row for a relation property. If the relation is two-way, the inverse side is updated automatically. Returns { success }.

**Input:**
  - `sourcePageId`: string (uuid) . Source page ID
  - `targetPageId`: string (uuid) . Target page ID
  - `propertyId`: string (uuid) . Relation property ID

```json
{
  "tool": "s16_remove_relation",
  "arguments": {
    "sourcePageId": "00000000-0000-0000-0000-000000000000",
    "targetPageId": "00000000-0000-0000-0000-000000000000",
    "propertyId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_restore_doc`

Restore a trashed doc and its subtree. Returns { success }.

**Input:**
  - `docId`: string (uuid) . Doc ID

```json
{
  "tool": "s16_restore_doc",
  "arguments": {
    "docId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_restore_site_component_version`

Restore a site component to a previous version. Non-destructive: the current state is snapshotted first, then the chosen version is applied and a "restore" entry is logged.

**Input:**
  - `versionId`: string (uuid) . The version ID to restore

```json
{
  "tool": "s16_restore_site_component_version",
  "arguments": {
    "versionId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_restore_skill_version`

Restore a skill to a previous version. Non-destructive: the current state is snapshotted first, then the chosen version is applied and a "restore" entry is logged.

**Input:**
  - `versionId`: string (uuid) . The version ID to restore

```json
{
  "tool": "s16_restore_skill_version",
  "arguments": {
    "versionId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_restore_subagent_version`

Restore a subagent to a previous version. Non-destructive: the current state is snapshotted first, then the chosen version is applied and a "restore" entry is logged. Get version ids from s16_list_subagent_versions.

**Input:**
  - `versionId`: string (uuid) . The version ID to restore

```json
{
  "tool": "s16_restore_subagent_version",
  "arguments": {
    "versionId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_run_subagent`

Run a subagent manually (the subagent must have status:active — writing a non-empty compiledScript auto-activates a draft vm subagent; drafts/archived are rejected). Subagents with a compiledScript execute that JS; subagents without one run an LLM tool-calling loop. Returns immediately with a run object { id, status } — call s16_await_run to wait for completion and read output/logs. To verify a not-yet-active subagent without flipping its status, use s16_smoke_test_subagent.

**Input:**
  - `agentId`: string (uuid) . Agent ID
  - `inputPrompt`?: string . Text prompt for the subagent
  - `inputData`?: record . Structured input data (JSON)

```json
{
  "tool": "s16_run_subagent",
  "arguments": {
    "agentId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_run_subagent_by_name`

Find a subagent by name (exact match first, then case-insensitive substring) and run it — handy when you know the name but not the UUID. The matched subagent must have status:active. Returns the run object (with agentName); pass await:true to poll up to 120s and return the settled run incl. logs/output.

**Input:**
  - `name`: string . Subagent name or substring to match (e.g. "Buffer", "Transcript Importer")
  - `inputPrompt`?: string . Text prompt for the subagent
  - `inputData`?: record . Structured input data (JSON)
  - `await`?: boolean . If true, wait for the run to complete (up to 120s) and return full result including logs and output. Default: false

```json
{
  "tool": "s16_run_subagent_by_name",
  "arguments": {
    "name": "name"
  }
}
```

#### `s16_search_semantic`

Semantic (meaning-based) search across pages and docs. Best for concepts, themes and "similar to" queries (e.g. "deals where the founder worked at a hyperscaler"). Use s16_search_workspace for exact names/proper nouns. Returns an array keyed by kind (same shape as s16_search_workspace) with a numeric score: page rows ({ kind:"page", pageId, databaseId, matchedText, score }) and doc rows ({ kind:"doc", docId, docTitle, matchedText, score }).

**Input:**
  - `query`: string . Natural-language query
  - `limit`?: number . Max results
  - `sourceTypes`?: array<enum> . Restrict to pages and/or docs
  - `databaseIds`?: array<string> . Restrict to specific databases

```json
{
  "tool": "s16_search_semantic",
  "arguments": {
    "query": "query"
  }
}
```

#### `s16_set_trigger`

Create a subagent trigger. Types: event (DB changes), cron (scheduled), webhook (HTTP endpoint), gmail (polls for new emails), agent_change (fires when ANOTHER subagent is edited). Webhook triggers generate a unique URL. Gmail triggers require a Google OAuth credential with email capability. The agent_change trigger lets one subagent react to edits on a target subagent — useful for audit/notify/sync pipelines.

**Input:**
  - `agentId`: string (uuid) . Agent ID
  - `type`: enum(event | cron | webhook | gmail | agent_change) . Trigger type
  - `config`?: record . Trigger config. Event: { database_id, event: "row.created"|"row.updated"|"row.deleted", condition?: { property_id?, page_id?, old_value?, new_value?, old_value_includes?, new_value_includes? } }. Keys MUST be snake_case (database_id, event, and inside condition property_id/new_value) — the config is stored WITHOUT validation, so a camelCase databaseId, an eventType key, or a value like cell_updated is accepted as a "successful" trigger but matches nothing at fire time and the subagent SILENTLY NEVER RUNS. To trigger on a specific column value change, use event: "row.updated" with condition.property_id + condition.new_value (or new_value_includes). Note: "property.changed" is a real event but fires only on column-DEFINITION edits (creating/renaming/deleting a property or select option), NOT on cell-value changes — for a value change use "row.updated" + condition.property_id. Cron: { expression: string, timezone?: string, inputPrompt?: string (≤8KB, optional default prompt), inputData?: object (≤64KB JSON, optional default params merged into context.inputData at fire time — use this for multi-tenant scheduled subagents: one engine, N crons, each carrying its own params; do NOT put secrets here, store them via brain.setSecret and reference by key) }. Webhook: {} (no config needed). Gmail: { credential_title?: string, label_ids?: ["INBOX"], query?: string, poll_interval_seconds?: number (default 120, min 30) }. agent_change: { agentId: <UUID of the subagent to watch>, fields?: string[] (optional whitelist — only fire if at least one of these changed; valid keys: name, category, description, systemPrompt, model, modelParams, tools, inputSchema, skillIds, timeout, compiledScript, isLocked, accessLevel, status), excludeSources?: ("app"|"mcp"|"restore"|"system")[] (skip events from these sources to avoid loops). The subagent listens to changes on the TARGET subagent and runs with triggerContext = { agentId, userId, source, changedFields }. Self-loop is auto-prevented (a subagent never triggers itself).
  - `isActive`?: boolean . Whether trigger is active
  - `webhookMode`?: enum(async | sync) . Webhook only: async (200 OK immediately) or sync (waits for script, returns s16.respond() result). Default: async

```json
{
  "tool": "s16_set_trigger",
  "arguments": {
    "agentId": "00000000-0000-0000-0000-000000000000",
    "type": "event"
  }
}
```

#### `s16_smoke_test_subagent`

Smoke-test a subagent: run it ONCE with synthetic/empty input and a short timeout, then report whether it ran without crashing. Call this right after compiling and BEFORE telling the user the subagent is ready — compiling does not execute the script, so "unknown brain.* method" and other runtime errors only surface on a real run. Works even on a draft/inactive subagent (the active gate is bypassed for this test run). Returns { ok, status, timedOut, runId, error, output, logs }; if ok is false, read error/logs, fix the subagent, recompile, and test again. If the subagent declares a missing credential/secret/database, the smoke test returns a not-ready error instead of running — connect the missing dependency first, then re-test.

**Input:**
  - `agentId`: string (uuid) . Agent ID to smoke-test
  - `inputData`?: record . Optional structured input for the test run (defaults to {})
  - `inputPrompt`?: string . Optional text prompt for the test run
  - `timeoutSeconds`?: number . Max seconds to wait for the test run (default 60)

```json
{
  "tool": "s16_smoke_test_subagent",
  "arguments": {
    "agentId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_start_credential_oauth`

Start an OAuth connection flow for a credential provider (workspace admin/owner only). Returns { sessionId, authorizationUrl }.

**Input:**
  - `provider`: enum(google | krisp | generic_oauth) . OAuth provider ID
  - `credentialId`?: string (uuid) . Optional existing credential ID to update
  - `title`?: string . Optional connection title
  - `service`?: string . Optional service key override
  - `capabilities`?: array<enum> . Requested capabilities
  - `scopes`?: array<string> . Explicit OAuth scopes
  - `providerConfig`?: object . Optional provider override config

```json
{
  "tool": "s16_start_credential_oauth",
  "arguments": {
    "provider": "google"
  }
}
```

#### `s16_uninstall_skill`

Uninstall a skill from the workspace (workspace admin/owner only).

**Input:**
  - `skillId`: string (uuid) . Skill ID

```json
{
  "tool": "s16_uninstall_skill",
  "arguments": {
    "skillId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_unpublish_agent`

Remove an agent from the global marketplace (reverses s16_publish_agent). PLATFORM ADMIN only. Sets is_public=false, scope=workspace. Already-installed copies are unaffected. Returns the updated agent.

**Input:**
  - `workflowId`: string (uuid) . Agent id (must belong to your workspace)

```json
{
  "tool": "s16_unpublish_agent",
  "arguments": {
    "workflowId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_update_subagent`

Update an existing subagent. To give it logic, set compiledScript directly — you write the JS (read s16_guide({ category: "agents" }) for the sandbox API). Writing a non-empty compiledScript to a draft vm subagent auto-activates it (status draft→active). Editing systemPrompt/model/tools does NOT change the compiledScript — a vm subagent keeps running its existing script; only a subagent WITHOUT a compiledScript uses systemPrompt at run time (an LLM tool-calling loop). Locked subagents: owner-only. IMPORTANT: when setting compiledScript on a subagent that already has one, you MUST first call s16_get_subagent and pass its compiledScriptHash as expectedCompiledScriptHash — this is rejected otherwise. It prevents overwriting newer changes with a script generated from a stale copy. Always base your new script on the CURRENT compiledScript from s16_get_subagent.

Execution backend: keep 'vm' (sandboxed JS) unless the subagent needs a real OS — a shell, writable filesystem, package install, git, a browser, or long-running binaries — in which case set executionBackend:'computer' with agenticConfig (gVisor pod, platform-admin only). See s16_guide({ category: "agents" }).

**Input:**
  - `agentId`: string (uuid) . Agent ID
  - `name`?: string . Subagent name
  - `category`?: string . A sidebar GROUP LABEL for the subagent (NOT its name). Changing it re-groups the subagent; clearing it (null) ungroups it. This never deletes the subagent. To "rename a category", update this field on its subagents.
  - `description`?: string . Description
  - `systemPrompt`?: string . System prompt
  - `model`?: string . Model ID
  - `modelParams`?: record . Model params
  - `tools`?: array<any> . Tool definitions
  - `inputSchema`?: record . Input schema
  - `skillIds`?: array<string> . Skill IDs to assign
  - `isLocked`?: boolean . Lock status
  - `status`?: enum(active | draft | archived) . Status
  - `compiledScript`?: string . Manually set the compiled JS script. Pass null to clear. Base it on the CURRENT script from s16_get_subagent.
  - `expectedCompiledScriptHash`?: string . The compiledScriptHash from your most recent s16_get_subagent. Required when overwriting an existing compiledScript — the write is rejected if the live script has changed since (optimistic concurrency / If-Match).
  - `timeout`?: number . Execution timeout in seconds (default 3600 = 1 hour, max 86400 = 24 hours). For computer-tier subagents this one value also caps the pod TTL and EACH run_code call (which otherwise defaults to 120s) — raise it (e.g. 1800) for long tasks or when the subagent uses s16.askUser, which blocks a run_code call until the user replies.
  - `executionBackend`?: enum(vm | computer) . 'vm' (default) or 'computer' (gVisor real-shell tier; platform-admin only)
  - `agenticConfig`?: object . Agentic runtime config for computer-tier subagents (model, capabilities, sizeTier, persistent, allowDestructive, …).

```json
{
  "tool": "s16_update_subagent",
  "arguments": {
    "agentId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_write_site_page_file`

Create or overwrite a single file in the site page virtual file system. Use for new files or large rewrites.

**Input:**
  - `sitePageId`: string (uuid) . Site page ID
  - `path`: string . File path
  - `content`: string . File content (max 256 KB)

```json
{
  "tool": "s16_write_site_page_file",
  "arguments": {
    "sitePageId": "00000000-0000-0000-0000-000000000000",
    "path": "path",
    "content": "content"
  }
}
```

### platform_admin

#### `s16_approve_waitlist_user`

Approve a waitlisted user — clears is_waitlisted, provisions a personal workspace and sets user as its owner. Idempotent: skips workspace creation if user already has a membership. Identify by userId OR email. Restricted to platform admins. Returns { approved, user, workspace, wasAlreadyApproved }.

**Input:**
  - `userId`?: string (uuid)
  - `email`?: string (email)

```json
{
  "tool": "s16_approve_waitlist_user",
  "arguments": {}
}
```

#### `s16_list_waitlist`

List all users awaiting admin approval (is_waitlisted = true). Restricted to platform admins. Returns { count, users: [{ id, email, name, accountType, companySize, industry, hasEngineer, hasCreatedAgents, createdAt }] }.

```json
{
  "tool": "s16_list_waitlist",
  "arguments": {}
}
```

### properties

#### `s16_create_property`

Create a new property/column in a database. For relation properties, set config.relatedDatabaseId (required) and config.twoWay (default: true) to auto-create an inverse column on the target DB. For lookup properties, set config.relationPropertyId (the relation column to follow) and config.targetPropertyId (the column to pull from the related table); a single-value lookup can then be read as a scalar inside a formula via prop("Lookup Name") (e.g. prop("New PPS") * prop("FX Rate Value")), and multi-value lookups aggregate with first/last/sum/average/count/min/max.

**Input:**
  - `databaseId`: string (uuid) . Database ID
  - `name`: string . Property name
  - `description`?: string . Optional help text shown in column header tooltip and inline cell editors
  - `type`: enum(title | text | number | select | multi_select | date | checkbox | url | email | phone | relation | lookup | rollup | formula | created_time | last_edited_time | created_by | last_edited_by | files | person | status | property_last_edited_time | property_last_edited_by) . Property type
  - `config`?: record . Property config. For relations: { relatedDatabaseId, twoWay?, inverseName? }

```json
{
  "tool": "s16_create_property",
  "arguments": {
    "databaseId": "00000000-0000-0000-0000-000000000000",
    "name": "name",
    "type": "title"
  }
}
```

#### `s16_delete_property`

Delete a property/column from a database (drops its values; irreversible). Returns { success }.

**Input:**
  - `propertyId`: string (uuid) . Property ID

```json
{
  "tool": "s16_delete_property",
  "arguments": {
    "propertyId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_delete_property_option`

Delete a select/multi_select/status option and remove its usages from existing rows. Returns { success }.

**Input:**
  - `propertyId`: string (uuid) . Property ID
  - `value`: string . Option label/value to remove

```json
{
  "tool": "s16_delete_property_option",
  "arguments": {
    "propertyId": "00000000-0000-0000-0000-000000000000",
    "value": "value"
  }
}
```

#### `s16_rename_property_option`

Rename a select/multi_select/status option and propagate the change into stored cell values. Returns { success }.

**Input:**
  - `propertyId`: string (uuid) . Property ID
  - `oldValue`: string . Current option label/value
  - `newValue`: string . New option label/value

```json
{
  "tool": "s16_rename_property_option",
  "arguments": {
    "propertyId": "00000000-0000-0000-0000-000000000000",
    "oldValue": "oldValue",
    "newValue": "newValue"
  }
}
```

#### `s16_update_property`

Update a database property: rename, set description (help text), change config flags (sensitive, wrapContent, displayAs, format, precision), update select/multi_select options, or replace the formula expression of a formula-typed property. `format` and `precision` apply to both `number` and `formula` types (formula honours them only when the formula returns a number). Config flags are merged with existing config (not replaced).

**Input:**
  - `propertyId`: string (uuid) . Property ID
  - `name`?: string . New property name
  - `description`?: string . Help text shown in column header tooltip and inline editors. Pass null to clear.
  - `sensitive`?: boolean . Mark property as sensitive
  - `wrapContent`?: boolean . Wrap long content in cells
  - `displayAs`?: string . Display format override (e.g. "progress_bar")
  - `format`?: enum(number | number_with_commas | percent | dollar | euro | pound | yen | ruble | rupee | won | yuan) . Number display format. Applies to `number` and `formula` (when the formula returns a number). Values: number, number_with_commas, percent, dollar, euro, pound, yen, ruble, rupee, won, yuan. Pass null to clear back to "number".
  - `precision`?: number . Decimal precision (0–20). Applies to `number` and numeric `formula` columns. Pass null to clear (renders full float precision).
  - `limit`?: number . Selection limit for relation or multi_select. null removes the limit.
  - `options`?: array<object> . Replace select/multi_select/status options entirely
  - `formula`?: string . Formula expression for formula-typed properties (e.g. `dateBetween(now(), prop("Created time"), "workdays")`). Only applies when property.type === "formula".

```json
{
  "tool": "s16_update_property",
  "arguments": {
    "propertyId": "00000000-0000-0000-0000-000000000000"
  }
}
```

### public_shares

#### `s16_count_public_pages`

Count rows in a publicly shared database context using a share ID. Returns { count }.

**Input:**
  - `databaseId`: string (uuid) . Database ID
  - `shareId`: string . Public share ID
  - `filters`?: array<object> . Column filters as [{ propertyId, operator, value }], all ANDed
  - `search`?: string . Search term

```json
{
  "tool": "s16_count_public_pages",
  "arguments": {
    "databaseId": "00000000-0000-0000-0000-000000000000",
    "shareId": "shareId"
  }
}
```

#### `s16_get_public_database`

Get a database schema through a public share link (parity with shared page/doc UI flows). Returns the database with its properties and views.

**Input:**
  - `databaseId`: string (uuid) . Database ID
  - `shareId`: string . Public share ID from a shared page or doc

```json
{
  "tool": "s16_get_public_database",
  "arguments": {
    "databaseId": "00000000-0000-0000-0000-000000000000",
    "shareId": "shareId"
  }
}
```

#### `s16_get_public_doc`

Get a public doc through its share ID. Returns the doc with its content, or "Doc not found" if the share is invalid or access is denied.

**Input:**
  - `shareId`: string . Public share ID

```json
{
  "tool": "s16_get_public_doc",
  "arguments": {
    "shareId": "shareId"
  }
}
```

#### `s16_get_public_page`

Get a public row through its share ID. Returns the page with its cell values and body, or "Page not found" if the share is invalid or access is denied.

**Input:**
  - `shareId`: string . Public share ID

```json
{
  "tool": "s16_get_public_page",
  "arguments": {
    "shareId": "shareId"
  }
}
```

#### `s16_list_public_pages`

List rows in a publicly shared database context using a share ID. Returns { items, nextCursor }.

**Input:**
  - `databaseId`: string (uuid) . Database ID
  - `shareId`: string . Public share ID
  - `limit`?: number . Max pages (default 50)
  - `cursor`?: number . Offset cursor for pagination
  - `search`?: string . Full-text search across all cell values
  - `searchField`?: string (uuid) . Restrict search to this property ID
  - `filters`?: array<object> . Column filters as [{ propertyId, operator, value }], all ANDed
  - `sorts`?: array<object> . Sort order as [{ propertyId, direction }]
  - `fields`?: array<string> . Projected property IDs

```json
{
  "tool": "s16_list_public_pages",
  "arguments": {
    "databaseId": "00000000-0000-0000-0000-000000000000",
    "shareId": "shareId"
  }
}
```

### relations

#### `s16_list_relations`

List the linked rows for a relation property on a source page. Use this to get related rows' target UUIDs (relation cells in propertiesCache hold only TITLES). Returns [{ id, targetPageId, title, icon, targetPage: { id, propertiesCache } }].

**Input:**
  - `sourcePageId`: string (uuid) . Source page ID
  - `propertyId`: string (uuid) . Relation property ID

```json
{
  "tool": "s16_list_relations",
  "arguments": {
    "sourcePageId": "00000000-0000-0000-0000-000000000000",
    "propertyId": "00000000-0000-0000-0000-000000000000"
  }
}
```

### secrets

#### `s16_delete_secret`

Delete a secret from the workspace (workspace admin/owner only). Irreversible.

**Input:**
  - `key`: string . Secret key to delete

```json
{
  "tool": "s16_delete_secret",
  "arguments": {
    "key": "key"
  }
}
```

#### `s16_set_secret`

Store a GENERIC encrypted workspace value (workspace admin/owner only), readable in agent scripts via brain.getSecret(key). For any service token / API key (Telegram, Airtable, provider keys) PREFER s16_create_credential — a credential is private by default, visible in the Integrations UI, and reusable across agents; a secret cannot be scoped per-user and is invisible in the UI. Overwrites if the key already exists.

**Input:**
  - `key`: string . Secret key name for a generic value (e.g. WEBHOOK_SIGNING_SECRET). For service tokens like Telegram/Airtable use s16_create_credential instead.
  - `value`: string . Secret value

```json
{
  "tool": "s16_set_secret",
  "arguments": {
    "key": "key",
    "value": "value"
  }
}
```

### sites

#### `s16_create_site`

Create a new website. A home page (slug=home) is auto-created with a starter index.html. Returns { id, slug, name }.

**Input:**
  - `name`: string . Display name of the site
  - `slug`: string . URL-safe lowercase identifier (e.g. "my-site")
  - `icon`?: string . Emoji icon
  - `description`?: string . Short description
  - `settings`?: record . Site settings (themeColor, baseFontFamily, navItems, etc.)

```json
{
  "tool": "s16_create_site",
  "arguments": {
    "name": "name",
    "slug": "slug"
  }
}
```

#### `s16_create_site_component`

Create a workspace-scoped site component — the reusable node-tree/code marketplace artefact. For page content prefer the per-page file tools (s16_write_site_page_file). Returns the created component.

**Input:**
  - `name`: string . Component identifier (PascalCase)
  - `displayName`?: string
  - `category`?: string
  - `description`?: string
  - `icon`?: string
  - `thumbnailUrl`?: string
  - `version`?: string
  - `propsSchema`?: array<record>
  - `defaultProps`?: record
  - `tree`?: record
  - `code`?: string
  - `tags`?: array<string>

```json
{
  "tool": "s16_create_site_component",
  "arguments": {
    "name": "name"
  }
}
```

#### `s16_create_site_page`

Create a new page in a site. If files are omitted a starter index.html is used. Returns the created page.

**Input:**
  - `siteId`: string (uuid) . Site ID
  - `slug`: string . URL path segment (e.g. "about")
  - `title`?: string . Page title (default "Untitled")
  - `isHome`?: boolean . Mark as home page (clears previous home)
  - `files`?: record . Initial virtual file system { path: content }
  - `entryPath`?: string . Entry HTML file path (default "index.html")

```json
{
  "tool": "s16_create_site_page",
  "arguments": {
    "siteId": "00000000-0000-0000-0000-000000000000",
    "slug": "slug"
  }
}
```

#### `s16_delete_site`

Permanently delete a site and all its pages. This is irreversible.

**Input:**
  - `siteId`: string (uuid) . Site ID

```json
{
  "tool": "s16_delete_site",
  "arguments": {
    "siteId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_delete_site_asset`

Delete a site asset record. Does not delete the underlying file from storage.

**Input:**
  - `siteAssetId`: string (uuid) . Site asset ID

```json
{
  "tool": "s16_delete_site_asset",
  "arguments": {
    "siteAssetId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_delete_site_component`

Permanently delete a workspace-scoped site component. System components cannot be deleted.

**Input:**
  - `siteComponentId`: string (uuid) . Site component ID

```json
{
  "tool": "s16_delete_site_component",
  "arguments": {
    "siteComponentId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_delete_site_page`

Delete a site page permanently. If the deleted page was the home page, the next page in position order is promoted to home automatically.

**Input:**
  - `sitePageId`: string (uuid) . Site page ID

```json
{
  "tool": "s16_delete_site_page",
  "arguments": {
    "sitePageId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_delete_site_page_file`

Delete a file from the site page virtual file system. Cannot delete the entry file.

**Input:**
  - `sitePageId`: string (uuid) . Site page ID
  - `path`: string . File path

```json
{
  "tool": "s16_delete_site_page_file",
  "arguments": {
    "sitePageId": "00000000-0000-0000-0000-000000000000",
    "path": "path"
  }
}
```

#### `s16_get_site`

Get site details plus its pages list (id, slug, title, isHome, isPublished). Use s16_get_site_page for full page files.

**Input:**
  - `siteId`: string (uuid) . Site ID

```json
{
  "tool": "s16_get_site",
  "arguments": {
    "siteId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_get_site_component`

Get full site component details including propsSchema, defaultProps, tree (JSON), and code (JS).

**Input:**
  - `siteComponentId`: string (uuid) . Site component ID

```json
{
  "tool": "s16_get_site_component",
  "arguments": {
    "siteComponentId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_get_site_page`

Get full site page data. Returns { id, siteId, slug, title, files, entryPath, filesVersion, customCss, customJs, seo, isHome, isPublished, shareId, ... } — files is the virtual file system { path: content }.

**Input:**
  - `sitePageId`: string (uuid) . Site page ID

```json
{
  "tool": "s16_get_site_page",
  "arguments": {
    "sitePageId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_list_site_assets`

List site assets uploaded to the workspace. Optionally filter by site.

**Input:**
  - `siteId`?: string (uuid) . Filter assets belonging to a specific site

```json
{
  "tool": "s16_list_site_assets",
  "arguments": {}
}
```

#### `s16_list_site_component_history`

List the change history of a site component (who edited it, when, and which fields changed). Each entry includes changedFields, per-field {from, to} diff (large values truncated), source (app | mcp | restore | system), and the editor user info. Optionally filter by userId.

**Input:**
  - `siteComponentId`: string (uuid) . Site component ID
  - `limit`?: number . Max entries to return (default 50, max 200)
  - `userId`?: string (uuid) . Optional: only return changes made by this user

```json
{
  "tool": "s16_list_site_component_history",
  "arguments": {
    "siteComponentId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_list_site_component_versions`

List the saved versions (restorable snapshots) of a site component. Each entry has a version id, createdAt, optional changeDescription and editor. Use the version id with s16_restore_site_component_version. (Differs from s16_list_site_component_history, which returns the per-edit field diff.)

**Input:**
  - `siteComponentId`: string (uuid) . Site component ID
  - `limit`?: number . Max versions to return (default 30)

```json
{
  "tool": "s16_list_site_component_versions",
  "arguments": {
    "siteComponentId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_list_site_components`

List site components available to the workspace (system, public, and workspace-scoped). Supports filtering by scope, category, or search term.

**Input:**
  - `scope`?: enum(system | workspace | public) . Filter by scope (omit for all accessible)
  - `category`?: string . Filter by category name
  - `search`?: string . Search in name, displayName, description

```json
{
  "tool": "s16_list_site_components",
  "arguments": {}
}
```

#### `s16_list_site_page_files`

List all file paths in a site page virtual file system. Returns { paths, entryPath, filesVersion }.

**Input:**
  - `sitePageId`: string (uuid) . Site page ID

```json
{
  "tool": "s16_list_site_page_files",
  "arguments": {
    "sitePageId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_list_site_pages`

List all pages for a site. Returns metadata and entry path. Use s16_get_site_page for the full files dictionary.

**Input:**
  - `siteId`: string (uuid) . Site ID

```json
{
  "tool": "s16_list_site_pages",
  "arguments": {
    "siteId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_list_sites`

List all sites in the workspace. Returns id, name, slug, icon, isPublished, isArchived. Use includeArchived to also return archived sites.

**Input:**
  - `includeArchived`?: boolean . Include archived sites (default false)

```json
{
  "tool": "s16_list_sites",
  "arguments": {}
}
```

#### `s16_publish_site_page`

Publish or unpublish a site page. When publishing, a shareId is generated if one does not exist. Returns { isPublished, shareId, publicUrl }.

**Input:**
  - `sitePageId`: string (uuid) . Site page ID
  - `publish`: boolean . true to publish, false to unpublish

```json
{
  "tool": "s16_publish_site_page",
  "arguments": {
    "sitePageId": "00000000-0000-0000-0000-000000000000",
    "publish": false
  }
}
```

#### `s16_set_site_page_files`

Replace the entire virtual file system of a site page. Optionally update entryPath.

**Input:**
  - `sitePageId`: string (uuid) . Site page ID
  - `files`: record . Full files dictionary { path: content }
  - `entryPath`?: string . Entry HTML file path

```json
{
  "tool": "s16_set_site_page_files",
  "arguments": {
    "sitePageId": "00000000-0000-0000-0000-000000000000",
    "files": null
  }
}
```

#### `s16_update_site`

Update site metadata. Only provided fields are changed. Use s16_get_site first to read current values.

**Input:**
  - `siteId`: string (uuid) . Site ID
  - `name`?: string . Display name
  - `slug`?: string . URL-safe lowercase identifier
  - `icon`?: string . Emoji icon (null to clear)
  - `description`?: string . Short description
  - `settings`?: record . Site settings object (replaces entirely)
  - `globalCss`?: string . Global CSS injected on all pages
  - `globalJs`?: string . Global JS injected on all pages
  - `faviconUrl`?: string . Favicon URL
  - `isPublished`?: boolean . Publish/unpublish the site
  - `isArchived`?: boolean . Archive/restore the site

```json
{
  "tool": "s16_update_site",
  "arguments": {
    "siteId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_update_site_component`

Update a workspace-scoped site component. Only provided fields are changed. System components cannot be modified.

**Input:**
  - `siteComponentId`: string (uuid) . Site component ID
  - `name`?: string
  - `displayName`?: string
  - `category`?: string
  - `description`?: string
  - `icon`?: string
  - `thumbnailUrl`?: string
  - `version`?: string
  - `propsSchema`?: array<record>
  - `defaultProps`?: record
  - `tree`?: record
  - `code`?: string
  - `tags`?: array<string>

```json
{
  "tool": "s16_update_site_component",
  "arguments": {
    "siteComponentId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_update_site_page`

Update site page metadata (slug, title, css, js, seo, isHome, isPublished). For file edits use s16_write_site_page_file or s16_edit_site_page_file.

**Input:**
  - `sitePageId`: string (uuid) . Site page ID
  - `slug`?: string . URL path segment
  - `title`?: string . Page title
  - `customCss`?: string . Page-level CSS override
  - `customJs`?: string . Page-level JS override
  - `seo`?: record . SEO object: { title?, description?, ogImageUrl?, noindex?, canonicalUrl? }
  - `isHome`?: boolean . Mark as home page
  - `isPublished`?: boolean . Publish/unpublish the page

```json
{
  "tool": "s16_update_site_page",
  "arguments": {
    "sitePageId": "00000000-0000-0000-0000-000000000000"
  }
}
```

### skills

#### `s16_create_skill`

Create a new skill in the workspace (workspace admin/owner only). A skill is a SKILL.md file with instructions/knowledge that can be distributed via MCP.

**Input:**
  - `name`: string . Skill slug (lowercase, hyphens, e.g. analyze-deal)
  - `displayName`: string . Display name
  - `description`?: string . Skill description
  - `skillMd`: string . SKILL.md content (frontmatter + markdown)
  - `files`?: record . Supporting files: { "reference.md": "content...", "scripts/helper.py": "..." }
  - `tags`?: array<string> . Tags for marketplace
  - `version`?: string . Semver version
  - `type`?: enum(general | editor) . Skill type: general (MCP prompt) or editor (available in Tiptap editor AI)

```json
{
  "tool": "s16_create_skill",
  "arguments": {
    "name": "name",
    "displayName": "displayName",
    "skillMd": "skillMd"
  }
}
```

#### `s16_delete_skill`

Delete a skill (workspace admin/owner only). Irreversible.

**Input:**
  - `skillId`: string (uuid) . Skill ID

```json
{
  "tool": "s16_delete_skill",
  "arguments": {
    "skillId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_list_skill_history`

List the change history of a skill (who edited it, when, and which fields changed). Each entry includes changedFields, per-field {from, to} diff (large values truncated), source (app | mcp | restore | system), and the editor user info. Optionally filter by userId.

**Input:**
  - `skillId`: string (uuid) . Skill ID
  - `limit`?: number . Max entries to return (default 50, max 200)
  - `userId`?: string (uuid) . Optional: only return changes made by this user

```json
{
  "tool": "s16_list_skill_history",
  "arguments": {
    "skillId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_list_skill_versions`

List the saved versions (restorable snapshots) of a skill. Each entry has a version id, createdAt, optional changeDescription and editor. Use the version id with s16_restore_skill_version. (Differs from s16_list_skill_history, which returns the per-edit field diff.)

**Input:**
  - `skillId`: string (uuid) . Skill ID
  - `limit`?: number . Max versions to return (default 30)

```json
{
  "tool": "s16_list_skill_versions",
  "arguments": {
    "skillId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_list_skills`

List skills: workspace-owned and installed from marketplace (workspace admin/owner only). Returns { own, installed }.

```json
{
  "tool": "s16_list_skills",
  "arguments": {}
}
```

#### `s16_publish_skill`

Publish a skill. Pass global=true to publish as a platform-wide global skill (platform admin only). Default publishes to the workspace marketplace (requires workspace admin or owner role).

**Input:**
  - `skillId`: string (uuid) . Skill ID
  - `global`?: boolean . When true, publish as a platform-wide global skill. Requires platform-admin email.

```json
{
  "tool": "s16_publish_skill",
  "arguments": {
    "skillId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_update_skill`

Update an existing skill (workspace admin/owner only). Owner-access skills: owner only.

**Input:**
  - `skillId`: string (uuid) . Skill ID
  - `name`?: string . Skill slug
  - `displayName`?: string . Display name
  - `description`?: string . Description
  - `skillMd`?: string . SKILL.md content
  - `files`?: record . Supporting files
  - `tags`?: array<string> . Tags
  - `version`?: string . Version
  - `type`?: enum(general | editor) . Skill type: general (MCP prompt) or editor (available in Tiptap editor AI)

```json
{
  "tool": "s16_update_skill",
  "arguments": {
    "skillId": "00000000-0000-0000-0000-000000000000"
  }
}
```

### templates

#### `s16_apply_template`

Apply a template to create a single new page. The page is created empty, so {{current_page.property*}} variables resolve to empty strings (only date/title variables fill); use s16_apply_template_to_all to resolve property values per existing page. Sets icon/cover/config/styles from the template. Returns { pageId }.

**Input:**
  - `templateId`: string (uuid) . Template ID

```json
{
  "tool": "s16_apply_template",
  "arguments": {
    "templateId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_apply_template_to_all`

Apply a template to ALL existing non-archived pages in the database. WARNING: destructive — overwrites page content, config, styles, icon, cover, and deletes existing Yjs documents. Variables are resolved per-page using each page's property cache.

**Input:**
  - `templateId`: string (uuid) . Template ID

```json
{
  "tool": "s16_apply_template_to_all",
  "arguments": {
    "templateId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_create_template`

Create a new template for a database. After creation, use s16_set_database_default_template to make it default. Call s16_guide({ category: "page_templates" }) for full reference on content HTML, styles, config, embeds, and variables.

**Input:**
  - `databaseId`: string (uuid) . Database ID
  - `name`: string . Template name
  - `icon`?: string . Emoji icon for pages created from this template
  - `title`?: string . Default page title. Supports {{variables}} like {{current_date.date}}
  - `content`?: record . Content object: { html: "<p>Hello</p>" }. Use semantic HTML only (p, h1-h3, ul, ol, table, blockquote). No div layouts. Embed databases via <div data-embed-id="EMBED_ID"></div>
  - `config`?: record . Config: { layout?: "narrow"|"full", cover?: "url", icon?: "emoji", showProperties?: ["uuid1","uuid2"] }
  - `styles`?: record . Styles: { background?, fontFamily?, textColor?, maxWidth?, padding?, titleFontSize?, titleColor?, titleFontFamily?, titleAlign?, headerBackground?, headerPadding?, customCSS? }
  - `embeds`?: array<record> . Embedded databases: [{ id: "unique-id", databaseId: "uuid", title?: "Heading shown above the embed", viewType: "table"|"board"|"list"|"gallery", filters: [{propertyId, operator, value, conjunction?: "and"|"or"}], sorts: [{propertyId, direction}] }]. Reference in HTML via <div data-embed-id="id"></div>. Filter values support {{variables}}.

```json
{
  "tool": "s16_create_template",
  "arguments": {
    "databaseId": "00000000-0000-0000-0000-000000000000",
    "name": "name"
  }
}
```

#### `s16_delete_template`

Delete a template from a database. If this template is the default, clear it first with s16_set_database_default_template(templateId: null).

**Input:**
  - `templateId`: string (uuid) . Template ID

```json
{
  "tool": "s16_delete_template",
  "arguments": {
    "templateId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_get_template`

Get a template by ID with full details: name, icon, title, content (HTML), config (layout/cover/icon/showProperties), styles (background/fonts/colors/CSS), and embeds (embedded database views with filters). Call s16_guide({ category: "page_templates" }) for full reference on content HTML, styles, config, embeds, and variables.

**Input:**
  - `templateId`: string (uuid) . Template ID

```json
{
  "tool": "s16_get_template",
  "arguments": {
    "templateId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_list_templates`

List all templates for a database. Returns id, name, icon, isDefault flag, and a short content preview. Use s16_get_template for full details.

**Input:**
  - `databaseId`: string (uuid) . Database ID

```json
{
  "tool": "s16_list_templates",
  "arguments": {
    "databaseId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_update_template`

Update an existing template. Only provided fields are changed; omitted fields stay as-is. Use s16_get_template first to read current values. Call s16_guide({ category: "page_templates" }) for full reference on content HTML, styles, config, embeds, and variables.

**Input:**
  - `templateId`: string (uuid) . Template ID
  - `name`?: string . Template name
  - `icon`?: string . Emoji icon (null to clear)
  - `title`?: string . Default page title. Supports {{variables}}. null to clear
  - `content`?: record . Content: { html: "<p>...</p>" }. Replaces entire content.
  - `config`?: record . Config: { layout?, cover?, icon?, showProperties? }. Replaces entire config.
  - `styles`?: record . Styles: { background?, fontFamily?, textColor?, maxWidth?, padding?, titleFontSize?, titleColor?, titleFontFamily?, titleAlign?, headerBackground?, headerPadding?, customCSS? }. Replaces entire styles.
  - `embeds`?: array<record> . Embedded databases array (same shape as s16_create_template: { id, databaseId, title?, viewType, filters:[{propertyId, operator, value, conjunction?}], sorts }). Replaces the entire embeds list.

```json
{
  "tool": "s16_update_template",
  "arguments": {
    "templateId": "00000000-0000-0000-0000-000000000000"
  }
}
```

### views

#### `s16_create_view`

Create a new database view (table/board/list/calendar/gallery/timeline). Returns the new view.

**Input:**
  - `databaseId`: string (uuid) . Database ID
  - `name`: string . View name
  - `type`?: enum(table | board | list | calendar | gallery | timeline) . View type

```json
{
  "tool": "s16_create_view",
  "arguments": {
    "databaseId": "00000000-0000-0000-0000-000000000000",
    "name": "name"
  }
}
```

#### `s16_delete_view`

Delete a database view. Returns { success }.

**Input:**
  - `viewId`: string (uuid) . View ID

```json
{
  "tool": "s16_delete_view",
  "arguments": {
    "viewId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_list_views`

List all views for a database. Returns full view rows including type, filters, sorts, visibleProperties, groupBy and config.

**Input:**
  - `databaseId`: string (uuid) . Database ID

```json
{
  "tool": "s16_list_views",
  "arguments": {
    "databaseId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_reorder_views`

Reorder views inside a database. Returns the reordered view list.

**Input:**
  - `databaseId`: string (uuid) . Database ID
  - `orderedIds`: array<string> . View IDs in final order

```json
{
  "tool": "s16_reorder_views",
  "arguments": {
    "databaseId": "00000000-0000-0000-0000-000000000000",
    "orderedIds": []
  }
}
```

#### `s16_share_view`

Toggle public sharing for a database view. When on, returns the shareId that can be opened at /share/<id>.

**Input:**
  - `viewId`: string (uuid) . View ID
  - `on`: boolean . true = make public, false = unshare

```json
{
  "tool": "s16_share_view",
  "arguments": {
    "viewId": "00000000-0000-0000-0000-000000000000",
    "on": false
  }
}
```

#### `s16_update_view`

Update an existing view: name, type, filters, sorts, visibleProperties, propertyWidths, groupBy, cardFields, cardOrder, config, and order. config is merged with the existing config (not replaced). Returns the updated view.

**Input:**
  - `viewId`: string (uuid) . View ID
  - `name`?: string . View name
  - `type`?: enum(table | board | list | calendar | gallery | timeline) . View type
  - `filters`?: array<object> . View filters as [{ propertyId, operator, value, conjunction? }]
  - `sorts`?: array<object> . View sorts as [{ propertyId, direction }]
  - `visibleProperties`?: array<string> . Visible property IDs
  - `propertyWidths`?: record . Property width map { propertyId: pixels }
  - `groupBy`?: string (uuid) . Group by property ID (null to clear)
  - `cardFields`?: array<string> . Board/gallery card field IDs
  - `cardOrder`?: string . Card ordering preset
  - `config`?: record . View config merge patch, e.g. { freezeFirstColumn: true, cardCover: "..." }
  - `order`?: number . Manual view order

```json
{
  "tool": "s16_update_view",
  "arguments": {
    "viewId": "00000000-0000-0000-0000-000000000000"
  }
}
```

### workspace

#### `s16_clear_workspace_plan_price`

Clear a custom plan price for a workspace, archiving the Stripe Price (platform admin only). Active subscriptions keep their current price until the next checkout.

**Input:**
  - `workspaceId`: string (uuid) . Target workspace UUID
  - `planId`: string . Plan id

```json
{
  "tool": "s16_clear_workspace_plan_price",
  "arguments": {
    "workspaceId": "00000000-0000-0000-0000-000000000000",
    "planId": "planId"
  }
}
```

#### `s16_get_workspace_plan_prices`

List custom plan price overrides for a workspace (platform admin only). Returns a map of planId → { priceUsd, stripePriceId, createdAt }.

**Input:**
  - `workspaceId`: string (uuid) . Target workspace UUID

```json
{
  "tool": "s16_get_workspace_plan_prices",
  "arguments": {
    "workspaceId": "00000000-0000-0000-0000-000000000000"
  }
}
```

#### `s16_list_workspace_members`

List members of the current workspace. Returns { id, name, avatarUrl } per member — email is intentionally omitted as private metadata. Use the returned `id` (or `name`) as the value when assigning a Person property via s16_update_cell / s16_bulk_update_cells.

**Input:**
  - `search`?: string . Case-insensitive name filter. Returns all members if omitted.

```json
{
  "tool": "s16_list_workspace_members",
  "arguments": {}
}
```

#### `s16_search_workspace`

Keyword/text search across pages AND docs in the current workspace. Best for exact names and proper nouns; for concept/similarity queries use s16_search_semantic. Returns an array keyed by kind: page rows ({ kind:"page", pageId, databaseId, databaseName, matchedText, propertiesCache, url }) and doc rows ({ kind:"doc", docId, docTitle, matchedText, url }).

**Input:**
  - `query`: string . Search query
  - `limit`?: number . Max results

```json
{
  "tool": "s16_search_workspace",
  "arguments": {
    "query": "query"
  }
}
```

#### `s16_set_workspace_plan_price`

Set a custom plan price for a workspace, creating a Stripe Price (platform admin only). Applies at the next checkout — does not retroactively update an existing active subscription.

**Input:**
  - `workspaceId`: string (uuid) . Target workspace UUID
  - `planId`: string . Plan id (e.g. "business")
  - `priceUsd`: number . Custom price in USD per month

```json
{
  "tool": "s16_set_workspace_plan_price",
  "arguments": {
    "workspaceId": "00000000-0000-0000-0000-000000000000",
    "planId": "planId",
    "priceUsd": 0
  }
}
```

