AI workspace for you, your team, and every agent. Tables, docs (images, 4K video), formulas.
AI Trydock Dock MCP Server (ai.trydock/dock)
The ai.trydock/dock MCP server provides a local stdio bridge to the Dock MCP server. It connects MCP-capable agents (e.g., Claude Desktop, Cursor, Windsurf, Zed, Cline, Continue) to Dock workspaces using a single configuration change. Its workspace content includes tables, docs (including images and 4K video), and formulas.
Described interface is a local stdio bridge; no other transport methods are stated
Captured live from the server via tools/list.
list_workspaces
List all workspaces the authenticated principal has access to. Returns workspace name (slug), mode (the default-view preference for the first tab), and creation date. A workspace is a container of one or more surfaces (tabs); each surface is either a `table` (rows + columns) or a `doc` (TipTap body), and a workspace can hold any combination, one or many of either kind. Use `list_surfaces` to see what a given workspace actually contains.
Parameters
No parameters.
Raw schema
{
"type": "object",
"properties": {}
}
get_workspace
Get details about a specific workspace by its slug, including columns of its primary table surface, member count, and row count. A workspace contains one or more surfaces (tabs): any combination of `table` (rows + columns) and `doc` (TipTap body) kinds, one or many of either. Use `list_surfaces` to enumerate every tab; fetch /rows or /doc to read or write a specific one.
Parameters1
slug
string
required
The workspace slug, e.g. 'reddit-tracker'. Accepts either the bare slug or the org-prefixed form ('my-org/reddit-tracker') as shown in the dashboard URL.
Raw schema
{
"type": "object",
"properties": {
"slug": {
"type": "string",
"description": "The workspace slug, e.g. 'reddit-tracker'. Accepts either the bare slug or the org-prefixed form ('my-org/reddit-tracker') as shown in the dashboard URL."
}
},
"required": [
"slug"
]
}
list_rows
List rows in a workspace's table surface. Returns rows with their data (a JSON object of column-name to value), creation time, the principal who created/updated each row, AND the row's `surface_slug` (the sheet it lives on). Empty array if no rows have been added yet. Multi-surface workspaces: pass `surface_slug` to scope to one sheet; omit to return rows from every surface in the workspace (back-compat: pre-multi-surface clients keep working).
Parameters4
slug
string
required
The workspace slug. Accepts either the bare slug ('my-workspace') or the org-prefixed form ('my-org/my-workspace') as shown in the dashboard URL; both resolve to the same workspace.
surface_slug
string
optional
Optional table surface slug for multi-surface workspaces. Filter rows to one sheet. Omit to return rows from every surface (legacy single-sheet clients see no change). 400 if the slug is a doc surface, archived, or doesn't exist.
limit
number
optional
Max rows to return (default 100, max 1000)
offset
number
optional
Number of rows to skip (for pagination)
Raw schema
{
"type": "object",
"properties": {
"slug": {
"type": "string",
"description": "The workspace slug. Accepts either the bare slug ('my-workspace') or the org-prefixed form ('my-org/my-workspace') as shown in the dashboard URL; both resolve to the same workspace."
},
"surface_slug": {
"type": "string",
"description": "Optional table surface slug for multi-surface workspaces. Filter rows to one sheet. Omit to return rows from every surface (legacy single-sheet clients see no change). 400 if the slug is a doc surface, archived, or doesn't exist."
},
"limit": {
"type": "number",
"description": "Max rows to return (default 100, max 1000)"
},
"offset": {
"type": "number",
"description": "Number of rows to skip (for pagination)"
}
},
"required": [
"slug"
]
}
create_row
Append a new row to a workspace's table surface. The data field is a JSON object with column-name keys. Status column accepts: drafted, queued, sealed, active, blocked. Works on any workspace; columns auto-seed on the first row if the table surface is empty. Multi-surface workspaces accept `surface_slug` to target a specific sheet (use `list_surfaces` to enumerate); omit it to fall through to the workspace's primary table surface.
**Unmapped data fields:** Keys in `data` that don't match any existing column are still STORED on the row (nothing is dropped), but they won't render in the table UI until the column exists. The response carries an `unmapped_fields` array listing those keys plus a human-readable `warning` so an agent can decide whether to surface them, call `add_column`, or retry with `auto_create_columns: true`.
**Auto-create columns:** Pass `auto_create_columns: true` to have the server append a fresh text column for every unmapped key in one atomic step (humanised label from the key, type `text`). The response then includes `created_columns: ColumnDef[]` with the new column metadata. Use this when you're appending machine-emitted rows whose shape you can't predict ahead of time; leave it omitted (default false) when you want explicit schema control.
Parameters4
slug
string
required
The workspace slug. Accepts either the bare slug ('my-workspace') or the org-prefixed form ('my-org/my-workspace') as shown in the dashboard URL; both resolve to the same workspace.
data
object
required
Row data as a JSON object (e.g. {"title": "My post", "status": "drafted", "notes": "Initial draft"})
surface_slug
string
optional
Optional table surface slug for multi-surface workspaces. Omit to write to the workspace's primary table surface. 400 if the slug is a doc surface, archived, or doesn't exist.
auto_create_columns
boolean
optional
When true, the server auto-creates a text column for every key in `data` that doesn't already exist on the surface, then writes the row in the same call. Returns `created_columns` in the response listing the new column defs. Default false: unmapped keys are still stored on the row but won't render in the UI until you `add_column` them yourself.
Raw schema
{
"type": "object",
"properties": {
"slug": {
"type": "string",
"description": "The workspace slug. Accepts either the bare slug ('my-workspace') or the org-prefixed form ('my-org/my-workspace') as shown in the dashboard URL; both resolve to the same workspace."
},
"data": {
"type": "object",
"description": "Row data as a JSON object (e.g. {\"title\": \"My post\", \"status\": \"drafted\", \"notes\": \"Initial draft\"})",
"additionalProperties": true
},
"surface_slug": {
"type": "string",
"description": "Optional table surface slug for multi-surface workspaces. Omit to write to the workspace's primary table surface. 400 if the slug is a doc surface, archived, or doesn't exist."
},
"auto_create_columns": {
"type": "boolean",
"description": "When true, the server auto-creates a text column for every key in `data` that doesn't already exist on the surface, then writes the row in the same call. Returns `created_columns` in the response listing the new column defs. Default false: unmapped keys are still stored on the row but won't render in the UI until you `add_column` them yourself."
}
},
"required": [
"slug",
"data"
]
}
get_row
Fetch a single row by id without listing the full table. Useful when a cue payload carries a row id and the agent only needs that one record. Returns the same row shape as list_rows.
Parameters2
slug
string
required
The workspace slug. Accepts either the bare slug ('my-workspace') or the org-prefixed form ('my-org/my-workspace') as shown in the dashboard URL; both resolve to the same workspace.
rowId
string
required
The row id
Raw schema
{
"type": "object",
"properties": {
"slug": {
"type": "string",
"description": "The workspace slug. Accepts either the bare slug ('my-workspace') or the org-prefixed form ('my-org/my-workspace') as shown in the dashboard URL; both resolve to the same workspace."
},
"rowId": {
"type": "string",
"description": "The row id"
}
},
"required": [
"slug",
"rowId"
]
}
update_row
Update specific fields of an existing row. Only the fields provided in `data` are updated; others are preserved. Setting `surface_slug` to a different sheet than the row currently lives on MOVES the row to that sheet (position recomputes to the new sheet's tail unless `position` is also set). Same surface as current โ no-op move.
**Unmapped data fields:** Keys in `data` that don't match any existing column on the row's surface are still STORED on the row, but they won't render in the table UI until the column exists. The response carries an `unmapped_fields` array plus a human-readable `warning`. Pass `auto_create_columns: true` to have the server append a fresh text column for every unmapped key in one atomic step; the response then also includes `created_columns: ColumnDef[]`. Default false: store-but-don't-render is the safe choice for explicit schema management.
Parameters6
slug
string
required
The workspace slug. Accepts either the bare slug ('my-workspace') or the org-prefixed form ('my-org/my-workspace') as shown in the dashboard URL; both resolve to the same workspace.
rowId
string
required
The row ID to update
data
object
required
Partial row data with fields to update (e.g. {"status": "sealed"}). Pass an empty object {} when the call is purely a move (surface_slug change with no field updates).
surface_slug
string
optional
Optional. When set to a different surface than the row currently lives on, moves the row to that surface and emits a `row.moved_surface` event. Same-surface is a no-op. 400 if the slug is a doc surface, archived, or not in this workspace.
position
number
optional
Optional. Override the row's position. When moving across surfaces, omit to land at the new surface's tail; pass a number to land at a specific slot.
auto_create_columns
boolean
optional
When true, the server auto-creates a text column for every key in `data` that doesn't already exist on the surface, then applies the update in the same call. Returns `created_columns` in the response. Default false: unmapped keys are still merged into row.data but won't render in the UI until you `add_column` them yourself.
Raw schema
{
"type": "object",
"properties": {
"slug": {
"type": "string",
"description": "The workspace slug. Accepts either the bare slug ('my-workspace') or the org-prefixed form ('my-org/my-workspace') as shown in the dashboard URL; both resolve to the same workspace."
},
"rowId": {
"type": "string",
"description": "The row ID to update"
},
"data": {
"type": "object",
"description": "Partial row data with fields to update (e.g. {\"status\": \"sealed\"}). Pass an empty object {} when the call is purely a move (surface_slug change with no field updates).",
"additionalProperties": true
},
"surface_slug": {
"type": "string",
"description": "Optional. When set to a different surface than the row currently lives on, moves the row to that surface and emits a `row.moved_surface` event. Same-surface is a no-op. 400 if the slug is a doc surface, archived, or not in this workspace."
},
"position": {
"type": "number",
"description": "Optional. Override the row's position. When moving across surfaces, omit to land at the new surface's tail; pass a number to land at a specific slot."
},
"auto_create_columns": {
"type": "boolean",
"description": "When true, the server auto-creates a text column for every key in `data` that doesn't already exist on the surface, then applies the update in the same call. Returns `created_columns` in the response. Default false: unmapped keys are still merged into row.data but won't render in the UI until you `add_column` them yourself."
}
},
"required": [
"slug",
"rowId",
"data"
]
}
delete_row
Permanently delete a row from a workspace. This action cannot be undone.
Parameters2
slug
string
required
The workspace slug. Accepts either the bare slug ('my-workspace') or the org-prefixed form ('my-org/my-workspace') as shown in the dashboard URL; both resolve to the same workspace.
rowId
string
required
The row ID to delete
Raw schema
{
"type": "object",
"properties": {
"slug": {
"type": "string",
"description": "The workspace slug. Accepts either the bare slug ('my-workspace') or the org-prefixed form ('my-org/my-workspace') as shown in the dashboard URL; both resolve to the same workspace."
},
"rowId": {
"type": "string",
"description": "The row ID to delete"
}
},
"required": [
"slug",
"rowId"
]
}
move_rows
Atomically move N rows from their current sheet(s) to a target sheet inside the same workspace. Use for programmatic data migration: dropping a batch of agent-produced drafts onto the right sheet, reorganizing content across LinkedIn / Twitter / Substack tabs, etc. All-or-nothing: if any rowId doesn't belong to this workspace, the entire batch fails before any write fires. Idempotent: rows already on the target sheet are skipped (returns `skipped` count). Rows land at the destination sheet's tail in the order rowIds was supplied. Emits one `row.moved_surface` event per row that actually moved. Up to 500 rows per call.
Parameters3
slug
string
required
The workspace slug. Accepts either the bare slug ('my-workspace') or the org-prefixed form ('my-org/my-workspace') as shown in the dashboard URL; both resolve to the same workspace.
rowIds
array
required
Row IDs to move (1-500). Order is preserved at the destination: first id lands at the lowest position, last id at the highest.
target_surface_slug
string
required
Slug of the destination table surface. Use list_surfaces to enumerate. 400 if the slug is a doc surface, archived, or not in this workspace.
Raw schema
{
"type": "object",
"properties": {
"slug": {
"type": "string",
"description": "The workspace slug. Accepts either the bare slug ('my-workspace') or the org-prefixed form ('my-org/my-workspace') as shown in the dashboard URL; both resolve to the same workspace."
},
"rowIds": {
"type": "array",
"items": {
"type": "string"
},
"description": "Row IDs to move (1-500). Order is preserved at the destination: first id lands at the lowest position, last id at the highest.",
"minItems": 1,
"maxItems": 500
},
"target_surface_slug": {
"type": "string",
"description": "Slug of the destination table surface. Use list_surfaces to enumerate. 400 if the slug is a doc surface, archived, or not in this workspace."
}
},
"required": [
"slug",
"rowIds",
"target_surface_slug"
]
}
get_doc
Read a workspace's doc (TipTap rich-text) body. Format is negotiable via `format`: `markdown` (default โ CommonMark + GFM, ready to feed to an LLM or render in a non-ProseMirror surface), `content` (TipTap JSON, round-trippable into update_doc for structural edits), `text` (plain text, best for search, summarisation, word-count heuristics), or `all` for the legacy three-in-one shape. Default is `markdown` because it's the slice agents need 95% of the time and the JSON form on a long doc can blow past the agent harness's tool-result token cap. Pass `format: "content"` only when you're round-tripping into update_doc for a structural edit. A workspace can hold any combination of doc and table surfaces, one or many of either kind; omit `surface_slug` to read the primary doc surface, or pass it to target a specific doc tab (use `list_surfaces` to enumerate). An unwritten or absent doc returns the requested format empty (markdown="", content={}, text=""); a `surface_slug` that doesn't match any live doc surface 404s.
Parameters3
slug
string
required
The workspace slug. Accepts either the bare slug ('my-workspace') or the org-prefixed form ('my-org/my-workspace') as shown in the dashboard URL; both resolve to the same workspace.
surface_slug
string
optional
Optional doc surface slug for multi-doc workspaces. Omit to read the primary doc surface. Use list_surfaces to see available slugs.
format
string
optional
Which serialization to return. Default `markdown`. Use `content` to round-trip TipTap JSON back into update_doc for structural edits. Use `all` for the legacy three-in-one shape (heavier; only do this when you genuinely need every form in the same call).
Raw schema
{
"type": "object",
"properties": {
"slug": {
"type": "string",
"description": "The workspace slug. Accepts either the bare slug ('my-workspace') or the org-prefixed form ('my-org/my-workspace') as shown in the dashboard URL; both resolve to the same workspace."
},
"surface_slug": {
"type": "string",
"description": "Optional doc surface slug for multi-doc workspaces. Omit to read the primary doc surface. Use list_surfaces to see available slugs."
},
"format": {
"type": "string",
"enum": [
"markdown",
"content",
"text",
"all"
],
"description": "Which serialization to return. Default `markdown`. Use `content` to round-trip TipTap JSON back into update_doc for structural edits. Use `all` for the legacy three-in-one shape (heavier; only do this when you genuinely need every form in the same call)."
}
},
"required": [
"slug"
]
}
get_workspace_schema
Return a table surface's column definitions so an agent knows what keys create_row/update_row will accept. Each column has `key` (the field name in row.data), `label` (human-readable), `type` (text | longtext | url | status | owner | date | number), `position`, and, for status/owner columns, the allowed `options`. Empty array on doc-only workspaces; callers should still be able to write rows (columns auto-seed on first write). Multi-surface workspaces accept `surface_slug` to scope to a specific table sheet (use `list_surfaces` to enumerate); omit to fall through to the workspace's primary table surface.
Parameters2
slug
string
required
The workspace slug. Accepts either the bare slug ('my-workspace') or the org-prefixed form ('my-org/my-workspace') as shown in the dashboard URL; both resolve to the same workspace.
surface_slug
string
optional
Optional. The slug of the specific table surface to read columns from. Omit on single-table workspaces; required on multi-table workspaces if you don't want the primary table surface (lowest position).
Raw schema
{
"type": "object",
"properties": {
"slug": {
"type": "string",
"description": "The workspace slug. Accepts either the bare slug ('my-workspace') or the org-prefixed form ('my-org/my-workspace') as shown in the dashboard URL; both resolve to the same workspace."
},
"surface_slug": {
"type": "string",
"description": "Optional. The slug of the specific table surface to read columns from. Omit on single-table workspaces; required on multi-table workspaces if you don't want the primary table surface (lowest position)."
}
},
"required": [
"slug"
]
}
add_column
Append a single column to a workspace's table schema. Position is auto-computed as next-after-max so the contiguity invariant holds. Key collision (409) if a column with the same key already exists. Editor role required. Use this for per-column additions; use get_workspace_schema + update_workspace_columns (PUT on /columns) for full schema replacement or reordering. Multi-surface workspaces accept `surface_slug` to target a specific table sheet (use `list_surfaces` to enumerate); omit to fall through to the workspace's primary table surface.
Parameters8
slug
string
required
The workspace slug. Accepts either the bare slug ('my-workspace') or the org-prefixed form ('my-org/my-workspace') as shown in the dashboard URL; both resolve to the same workspace.
surface_slug
string
optional
Optional. The slug of the specific table surface to add the column to. Omit on single-table workspaces; required on multi-table workspaces if you don't want the primary table surface (lowest position).
key
string
required
Field name in row.data. Lowercase + underscores recommended; 1-64 chars.
label
string
required
Human-readable header shown in the sheet.
type
string
required
Column type. See get_workspace_schema for examples.
width
number
optional
Optional. Initial column width in px.
description
string
optional
Optional. Human-readable tooltip shown in the column header.
options
array
optional
Required for `status` + `select` types. The allowed values shown in the dropdown.
Raw schema
{
"type": "object",
"properties": {
"slug": {
"type": "string",
"description": "The workspace slug. Accepts either the bare slug ('my-workspace') or the org-prefixed form ('my-org/my-workspace') as shown in the dashboard URL; both resolve to the same workspace."
},
"surface_slug": {
"type": "string",
"description": "Optional. The slug of the specific table surface to add the column to. Omit on single-table workspaces; required on multi-table workspaces if you don't want the primary table surface (lowest position)."
},
"key": {
"type": "string",
"description": "Field name in row.data. Lowercase + underscores recommended; 1-64 chars."
},
"label": {
"type": "string",
"description": "Human-readable header shown in the sheet."
},
"type": {
"type": "string",
"enum": [
"text",
"longtext",
"number",
"status",
"person",
"date",
"url",
"checkbox",
"select"
],
"description": "Column type. See get_workspace_schema for examples."
},
"width": {
"type": "number",
"description": "Optional. Initial column width in px."
},
"description": {
"type": "string",
"description": "Optional. Human-readable tooltip shown in the column header."
},
"options": {
"type": "array",
"items": {
"type": "object",
"properties": {
"value": {
"type": "string"
},
"label": {
"type": "string"
},
"color": {
"type": "string"
}
}
},
"description": "Required for `status` + `select` types. The allowed values shown in the dropdown."
}
},
"required": [
"slug",
"key",
"label",
"type"
]
}
list_workspace_members
List principals with explicit access to a workspace. Returns users (id, name, email; email visible only when the caller is in the same org) and agents (id, name, brandKey) along with their role (owner | editor | commenter | viewer). Used by agents to verify a workspace is actually shared before writing output the team is expected to see.
Parameters1
slug
string
required
The workspace slug. Accepts either the bare slug ('my-workspace') or the org-prefixed form ('my-org/my-workspace') as shown in the dashboard URL; both resolve to the same workspace.
Raw schema
{
"type": "object",
"properties": {
"slug": {
"type": "string",
"description": "The workspace slug. Accepts either the bare slug ('my-workspace') or the org-prefixed form ('my-org/my-workspace') as shown in the dashboard URL; both resolve to the same workspace."
}
},
"required": [
"slug"
]
}
delete_workspace
Archive a workspace. Soft-delete: rows, doc body, and activity history are preserved, and the workspace can be restored from Settings ยท Archived. Every member loses access immediately. Idempotent: calling on an already-archived workspace returns its current archivedAt without changing anything. Requires editor role on the agent. Pass `mode: "web"` to surface a click-to-approve URL for the human (recommended for any non-trivial workspace); the first call returns { status: 'approval_required', approval_url, polling_url }; print approval_url in chat, user clicks + approves, you poll polling_url for the result. Without `mode: "web"` the call executes immediately on the agent's editor role.
Parameters2
slug
string
required
The workspace slug. Accepts either the bare slug ('my-workspace') or the org-prefixed form ('my-org/my-workspace') as shown in the dashboard URL; both resolve to the same workspace.
mode
string
optional
Consent surface. 'immediate' (default) executes on the agent's role. 'web' returns an approval_url the user clicks in a browser; recommended for any workspace your user might miss.
Raw schema
{
"type": "object",
"properties": {
"slug": {
"type": "string",
"description": "The workspace slug. Accepts either the bare slug ('my-workspace') or the org-prefixed form ('my-org/my-workspace') as shown in the dashboard URL; both resolve to the same workspace."
},
"mode": {
"type": "string",
"enum": [
"immediate",
"web"
],
"description": "Consent surface. 'immediate' (default) executes on the agent's role. 'web' returns an approval_url the user clicks in a browser; recommended for any workspace your user might miss."
}
},
"required": [
"slug"
]
}
update_workspace
Rename a workspace, change its slug, switch its default-view mode, or flip its visibility (private | org | unlisted | public). Pass any subset of `name`, `new_slug`, `mode`, `visibility`; fields you omit are left unchanged. Slug renames preserve old URLs via WorkspaceSlugAlias so previously-shared links keep resolving. Visibility flips disconnect every live SSE subscriber so reconnects re-authenticate against the new visibility. Editor role required. Emits `workspace.renamed` and/or `workspace.visibility_changed`. Visibility WIDENING (private โ org/unlisted/public, org โ unlisted/public, unlisted โ public) is consent-gated: pass `consent_mode: "web"` to return an approval_url the user clicks; otherwise the call returns `consent_required` and you must re-issue with consent_mode set. Visibility narrowing + non-visibility updates execute immediately on the agent's role.
Parameters6
slug
string
required
The current workspace slug
name
string
optional
New display name. Optional.
new_slug
string
optional
New URL slug (lowercase kebab-case, 3-64 chars). Optional. Must be unique within the org. Old slug stays redirectable via the alias table.
mode
string
optional
New default-view preference for the workspace's first tab. Optional. Doesn't add or remove surfaces; use `create_surface` / `delete_surface` to change the actual tab set.
visibility
string
optional
New visibility. Optional. `private` = explicit members only; `org` = every org member gets virtual editor; `unlisted` = anyone with the URL can view; `public` = listed and viewable to all. Widening transitions are consent-gated; see `consent_mode`.
consent_mode
string
optional
Required when `visibility` widens audience. Pass 'web' to surface a click-to-approve URL the user opens in their browser; first call returns { status: 'approval_required', approval_url, polling_url }, you print approval_url in chat and poll polling_url for the result.
Raw schema
{
"type": "object",
"properties": {
"slug": {
"type": "string",
"description": "The current workspace slug"
},
"name": {
"type": "string",
"description": "New display name. Optional."
},
"new_slug": {
"type": "string",
"description": "New URL slug (lowercase kebab-case, 3-64 chars). Optional. Must be unique within the org. Old slug stays redirectable via the alias table."
},
"mode": {
"type": "string",
"enum": [
"table",
"doc",
"html"
],
"description": "New default-view preference for the workspace's first tab. Optional. Doesn't add or remove surfaces; use `create_surface` / `delete_surface` to change the actual tab set."
},
"visibility": {
"type": "string",
"enum": [
"private",
"org",
"unlisted",
"public"
],
"description": "New visibility. Optional. `private` = explicit members only; `org` = every org member gets virtual editor; `unlisted` = anyone with the URL can view; `public` = listed and viewable to all. Widening transitions are consent-gated; see `consent_mode`."
},
"consent_mode": {
"type": "string",
"enum": [
"web"
],
"description": "Required when `visibility` widens audience. Pass 'web' to surface a click-to-approve URL the user opens in their browser; first call returns { status: 'approval_required', approval_url, polling_url }, you print approval_url in chat and poll polling_url for the result."
}
},
"required": [
"slug"
]
}
share_workspace
Invite a human (by email) to a workspace at a specified role. If the email already belongs to a Dock user they're added immediately and a notification email is sent; if not, a 7-day invite token is minted that auto-accepts on magic-link sign-in. Editor role required on the workspace. Emits `member.joined` (existing user) or `member.invited` (new user). Use update_workspace_member to change a role afterwards, remove_workspace_member to revoke.
Parameters3
slug
string
required
The workspace slug. Accepts either the bare slug ('my-workspace') or the org-prefixed form ('my-org/my-workspace') as shown in the dashboard URL; both resolve to the same workspace.
email
string
required
Email address of the human to invite.
role
string
optional
Role to grant. Defaults to `editor`. Owner-tier transitions require an owner caller.
Raw schema
{
"type": "object",
"properties": {
"slug": {
"type": "string",
"description": "The workspace slug. Accepts either the bare slug ('my-workspace') or the org-prefixed form ('my-org/my-workspace') as shown in the dashboard URL; both resolve to the same workspace."
},
"email": {
"type": "string",
"description": "Email address of the human to invite."
},
"role": {
"type": "string",
"enum": [
"owner",
"editor",
"commenter",
"viewer"
],
"description": "Role to grant. Defaults to `editor`. Owner-tier transitions require an owner caller."
}
},
"required": [
"slug",
"email"
]
}
update_workspace_member
Change an existing workspace member's role. Editor role required to caller. Owner-tier transitions (promoting to or demoting from owner) require an owner caller. Demoting the sole owner is blocked; promote someone else to owner first. No-op when the role is unchanged. Emits `member.role_changed` with from/to roles.
Parameters3
slug
string
required
The workspace slug. Accepts either the bare slug ('my-workspace') or the org-prefixed form ('my-org/my-workspace') as shown in the dashboard URL; both resolve to the same workspace.
member_id
string
required
The WorkspaceMember id to update. Get this from list_workspace_members.
role
string
required
New role.
Raw schema
{
"type": "object",
"properties": {
"slug": {
"type": "string",
"description": "The workspace slug. Accepts either the bare slug ('my-workspace') or the org-prefixed form ('my-org/my-workspace') as shown in the dashboard URL; both resolve to the same workspace."
},
"member_id": {
"type": "string",
"description": "The WorkspaceMember id to update. Get this from list_workspace_members."
},
"role": {
"type": "string",
"enum": [
"owner",
"editor",
"commenter",
"viewer"
],
"description": "New role."
}
},
"required": [
"slug",
"member_id",
"role"
]
}
remove_workspace_member
Remove a workspace member. Editor role required; owner-tier removals require an owner caller. Sole-owner removal is blocked; promote someone else first. Note: if the workspace visibility is `org`, removing an explicit member of the same org leaves them with virtual editor access via the org-membership branch. Consent-gated for agents: the FIRST call returns { status: 'confirmation_required', confirm_token, message, expires_in }. Surface the message to your user and, if they say yes, re-call this tool within 60s with `confirm_token` set to the same token. User callers (cookie session) skip the consent step.
Parameters3
slug
string
required
The workspace slug. Accepts either the bare slug ('my-workspace') or the org-prefixed form ('my-org/my-workspace') as shown in the dashboard URL; both resolve to the same workspace.
member_id
string
required
The WorkspaceMember id to remove. Get this from list_workspace_members.
confirm_token
string
optional
The token returned by the first call as `confirm_token`. Omit on the first call; include on the second call to execute the removal. Single-use, 60s TTL. Agents only; user callers don't need this.
Raw schema
{
"type": "object",
"properties": {
"slug": {
"type": "string",
"description": "The workspace slug. Accepts either the bare slug ('my-workspace') or the org-prefixed form ('my-org/my-workspace') as shown in the dashboard URL; both resolve to the same workspace."
},
"member_id": {
"type": "string",
"description": "The WorkspaceMember id to remove. Get this from list_workspace_members."
},
"confirm_token": {
"type": "string",
"description": "The token returned by the first call as `confirm_token`. Omit on the first call; include on the second call to execute the removal. Single-use, 60s TTL. Agents only; user callers don't need this."
}
},
"required": [
"slug",
"member_id"
]
}
update_doc
Replace a workspace's doc body. Takes EITHER TipTap JSON (`content`) OR Markdown (`markdown`): pass markdown when you're producing prose from scratch (CommonMark + GFM is the format every LLM emits natively), pass TipTap JSON when you need structural edits to an existing doc (round-trip from get_doc, mutate, write back). Beyond CommonMark + GFM, the markdown layer recognizes:
- **** โ inline image. Use ANY publicly-reachable URL (HTTPS preferred โ HTTP fires browser mixed-content warnings; data: URIs are rejected by `allowBase64: false`). Renders block-feeling via CSS (max-width 100%, rounded corners, drop shadow) even though the underlying node is inline. The `alt` text is the accessible label and shows in place of the image if the URL fails to load โ always include it. To attach a user-uploaded file, hit `POST /api/workspaces/:slug/upload-image` from the human-side UI first to get a Vercel Blob URL, then reference that URL in the doc markdown.
- A **lone video-file URL on its own line** (extension `.mp4` / `.m4v` / `.webm` / `.mov` / `.mkv`, signed-params + timestamp fragments tolerated) โ native HTML5 `<video controls preload="metadata">` player. Source URL is referenced directly: no iframe, no transcoding, no quality loss. Vercel Blob is the canonical hosting (5 GB per file, served with HTTP range requests so 4K masters stream cleanly), but ANY publicly-reachable HTTPS URL works. Sample shape: a paragraph containing only `https://cdn.dock.ai/2025-launch-walkthrough.mp4`. Mid-paragraph URLs stay as plain links โ surrounding prose disqualifies the auto-promotion (matches the oEmbed convention).
- **```mermaid** fenced code โ diagram (15 sub-types: flowchart, sequence, gantt, ER, state, class, mindmap, timeline, pie, quadrant, sankey, XY-chart, packet, block, journey)
- **$x$** inline math, **$$x$$** block math (LaTeX, KaTeX-rendered, scripts/href disabled)
- **> [!NOTE]** / **[!TIP]** / **[!IMPORTANT]** / **[!WARNING]** / **[!CAUTION]** GFM-style callouts
- **```svg** fenced code โ sanitized SVG embed (the universal escape hatch for custom diagrams; scripts and event handlers stripped at write time)
- **<details><summary>X</summary>BODY</details>** โ collapsible toggle
- **[[slug]]** / **[[org/slug]]** / **[[slug#tab]]** / **[[slug#row-id]]** / **[[slug|display]]** โ cross-references to another workspace, surface, or row. Resolved against your accessible workspace set; targets you can't see render as plain text on the reader's side (no info leak). Every cross-ref creates a Backlink row so the target's 'referenced from' sidebar shows this doc.
- **[@Label](dock:mention/<kind>/<id>)** โ @-mention of a user or agent. `<kind>` is `agent` or `human`; `<id>` is the principal id. Optional query params `?org=<slug>` (agents) or `?email=<addr>` (humans) for renderer hints. Mentioning a human writes a `doc_mention` row to their inbox + sends a deep-link email; mentioning an agent fires the `doc.mention_added` webhook so the agent service can wake up and reply. Re-saving a doc that already mentions someone does NOT re-fire โ only newly-added mentions notify (computed from a diff against the previous body). Use this from agent code to ping a teammate when a doc you wrote needs their eyes.
- A **lone URL on its own line** from a safelisted provider (YouTube, Vimeo, Loom, Figma, CodePen, GitHub gists) โ sandboxed iframe embed. Other URLs stay as regular links. Surrounding prose disqualifies the auto-embed.
Per-format caps: max 50 Mermaid diagrams (30 KB source each), max 500 math expressions (8 KB source each), max 50 SVG blocks (100 KB source each post-sanitize), max 200 cross-refs per doc, max 500 @-mentions per doc, max 20 embeds per doc, max 20 videos per doc (5 GB per file at upload time), max 200 images per doc. See /docs/doc-formats for examples. Last-write-wins; no CRDT merge. Emits doc.updated + doc.heading_added + doc.mention_added events as applicable. Requires editor role. Multi-surface workspaces optionally accept `surface_slug` to write to a specific doc tab; omitted writes the primary doc surfa
Parameters5
slug
string
required
The workspace slug. Accepts either the bare slug ('my-workspace') or the org-prefixed form ('my-org/my-workspace') as shown in the dashboard URL; both resolve to the same workspace.
surface_slug
string
optional
Optional doc surface slug for multi-doc workspaces. Omit to write the primary doc surface. Use list_surfaces to see available slugs.
content
object
optional
TipTap document JSON: `{ type: 'doc', content: [ ... ] }`. Use this when round-tripping from get_doc to preserve formatting. Mutually exclusive with `markdown` (content wins if both are passed).
markdown
string
optional
Markdown body (CommonMark + GFM). Converted server-side to TipTap JSON via the same converter that powers PUT /api/workspaces/:slug/doc. Use this when authoring prose from scratch; no need to hand-build ProseMirror nodes.
if_unmodified_since
string
optional
Optional precondition. ISO 8601 timestamp (typically the `updatedAt` you read via `get_doc`). When set and the doc has changed since this cutoff, the write is rejected with `code: -32602`, message describing the conflict, and `data: { conflict: true, currentUpdatedAt, precondition }` so your agent can refetch + merge instead of silently clobbering a concurrent write. Without this, two agents PUTting near-simultaneously will both succeed and the last write wins (the previous content vanishes). Use this in multi-agent co-authoring flows; skip it for greenfield writes where you know you're the only writer.
Raw schema
{
"type": "object",
"properties": {
"slug": {
"type": "string",
"description": "The workspace slug. Accepts either the bare slug ('my-workspace') or the org-prefixed form ('my-org/my-workspace') as shown in the dashboard URL; both resolve to the same workspace."
},
"surface_slug": {
"type": "string",
"description": "Optional doc surface slug for multi-doc workspaces. Omit to write the primary doc surface. Use list_surfaces to see available slugs."
},
"content": {
"type": "object",
"description": "TipTap document JSON: `{ type: 'doc', content: [ ... ] }`. Use this when round-tripping from get_doc to preserve formatting. Mutually exclusive with `markdown` (content wins if both are passed).",
"additionalProperties": true
},
"markdown": {
"type": "string",
"description": "Markdown body (CommonMark + GFM). Converted server-side to TipTap JSON via the same converter that powers PUT /api/workspaces/:slug/doc. Use this when authoring prose from scratch; no need to hand-build ProseMirror nodes."
},
"if_unmodified_since": {
"type": "string",
"description": "Optional precondition. ISO 8601 timestamp (typically the `updatedAt` you read via `get_doc`). When set and the doc has changed since this cutoff, the write is rejected with `code: -32602`, message describing the conflict, and `data: { conflict: true, currentUpdatedAt, precondition }` so your agent can refetch + merge instead of silently clobbering a concurrent write. Without this, two agents PUTting near-simultaneously will both succeed and the last write wins (the previous content vanishes). Use this in multi-agent co-authoring flows; skip it for greenfield writes where you know you're the only writer."
}
},
"required": [
"slug"
]
}
validate_doc_markdown
Pre-flight check on markdown BEFORE writing it via update_doc / append_doc_section. Returns { ok, errors, warnings, parsed } with parsed counts per format type (imageCount, videoCount, mermaidCount, mathCount, svgCount, calloutCount, crossRefCount, mentionCount, embedCount, detailsCount, headingCount, byteSize, nodeCount, depth) plus structured DocGuardError-equivalent errors (cap breaches) and non-blocking warnings (cross-refs that don't resolve, mention ids that don't resolve, oversize sources, cap-approaching counts). NEVER writes anything; pure parse + analysis. Use when iterating on rich-format markdown to catch problems before burning a write. Cross-ref + mention resolution is gated on caller's accessible workspace set, so unresolved tokens surface in warnings.
Parameters1
markdown
string
required
Markdown body to validate. Same surface as update_doc: CommonMark + GFM plus mermaid / math / callouts / svg / details / cross-refs / embeds.
Raw schema
{
"type": "object",
"properties": {
"markdown": {
"type": "string",
"description": "Markdown body to validate. Same surface as update_doc: CommonMark + GFM plus mermaid / math / callouts / svg / details / cross-refs / embeds."
}
},
"required": [
"markdown"
]
}
update_doc_section
Replace a single section of a workspace's doc body, identified by its heading text. The targeted edit complement to `update_doc` (full replacement) and `append_doc_section` (append-only at the end). Use this when the agent maintains a recurring section (e.g., a 'Status' block in a launch-prep doc, an 'Outcomes' block in a meeting note) and only needs to refresh that one piece. Without it, agents are forced into 'GET โ splice โ PUT' which costs tokens, costs latency, and races against any concurrent human edit elsewhere in the doc (last-write-wins clobbers). Section semantics: the FIRST heading whose plain text matches `heading` exactly (case-sensitive on trimmed text) is found, and everything from that heading up to the next heading at the same OR shallower level is replaced. So a `## Outcomes` section ends at the next `## โฆ` or `# โฆ`; nested `### โฆ` subsections stay part of the replaced range. Returns 404 when no matching heading exists; strict by design so a misremembered heading fails loudly. `markdown` is the FULL replacement, INCLUDING the heading line: pass it back as-is to keep the heading, change it to rename or rewrite the heading, change the heading level, or omit the heading entirely (collapses the section into the prior one). Empty `markdown` deletes the section. Same markdown surface as update_doc / append_doc_section (CommonMark + GFM + `` images + lone-URL videos (mp4/webm/mov/mkv/m4v) + Mermaid + KaTeX + callouts + SVG + details + cross-refs + @-mentions + URL embeds). Identity / attribution / events / doc-guard all flow through the same writeDocBody path as the other doc endpoints, so @-mentions in the new section fire `doc.mention_added` for newly-added mentions just like update_doc does. Requires editor role. Multi-surface workspaces optionally accept `surface_slug` to target a specific doc tab.
Parameters4
slug
string
required
The workspace slug. Accepts either the bare slug ('my-workspace') or the org-prefixed form ('my-org/my-workspace').
surface_slug
string
optional
Optional doc surface slug for multi-doc workspaces. Omit to target the primary doc surface.
heading
string
required
Plain text of the heading to find (case-sensitive, trimmed). For `## Outcomes`, pass `Outcomes`. Hash marks and surrounding whitespace are stripped from the comparison automatically by the markdown converter. Use `get_doc` first if you need to enumerate the headings actually present.
markdown
string
required
FULL replacement markdown for the section, including the heading line if you want to keep / rename / restructure it. Empty string deletes the section.
Raw schema
{
"type": "object",
"properties": {
"slug": {
"type": "string",
"description": "The workspace slug. Accepts either the bare slug ('my-workspace') or the org-prefixed form ('my-org/my-workspace')."
},
"surface_slug": {
"type": "string",
"description": "Optional doc surface slug for multi-doc workspaces. Omit to target the primary doc surface."
},
"heading": {
"type": "string",
"description": "Plain text of the heading to find (case-sensitive, trimmed). For `## Outcomes`, pass `Outcomes`. Hash marks and surrounding whitespace are stripped from the comparison automatically by the markdown converter. Use `get_doc` first if you need to enumerate the headings actually present."
},
"markdown": {
"type": "string",
"description": "FULL replacement markdown for the section, including the heading line if you want to keep / rename / restructure it. Empty string deletes the section."
}
},
"required": [
"slug",
"heading",
"markdown"
]
}
append_doc_section
Append a chunk of Markdown to the END of a workspace's doc body. Designed for crons + ingest agents that produce content in timestamped chunks (changelog updates, daily standups, batch summaries). Same markdown surface as update_doc: supports CommonMark, GFM, **`` inline images** (any publicly-reachable HTTPS URL), **lone video URLs** (`.mp4`/`.webm`/`.mov`/`.mkv`/`.m4v` โ native `<video>` player, 5 GB per file), ```mermaid diagrams, $math$/$$math$$ KaTeX, > [!NOTE]/[!TIP]/[!IMPORTANT]/[!WARNING]/[!CAUTION] callouts, ```svg sanitized embeds, <details><summary>X</summary>...</details> toggles, [[slug]] cross-references, [@Label](dock:mention/<kind>/<id>) @-mentions of users + agents, and lone-URL embeds (YouTube/Vimeo/Loom/Figma/CodePen/gists). Server fetches the current body, splices the new blocks on, and writes the result through the same path as update_doc with the same auth, same events, same byte/depth/node-count guard. Append is non-idempotent by design (every call adds content); the caller is responsible for dedupe. @-mentions inside the appended chunk fire `doc.mention_added` + inbox/email fan-out for newly-added mentions only โ appending a chunk that re-mentions someone already mentioned earlier in the doc won't re-fire. Requires editor role. Multi-surface workspaces optionally accept `surface_slug` to append to a specific doc tab.
Parameters3
slug
string
required
The workspace slug. Accepts either the bare slug ('my-workspace') or the org-prefixed form ('my-org/my-workspace') as shown in the dashboard URL; both resolve to the same workspace.
surface_slug
string
optional
Optional doc surface slug for multi-doc workspaces. Omit to append to the primary doc surface.
markdown
string
required
Markdown chunk to append (CommonMark + GFM). Becomes one or more new blocks at the end of the existing doc.
Raw schema
{
"type": "object",
"properties": {
"slug": {
"type": "string",
"description": "The workspace slug. Accepts either the bare slug ('my-workspace') or the org-prefixed form ('my-org/my-workspace') as shown in the dashboard URL; both resolve to the same workspace."
},
"surface_slug": {
"type": "string",
"description": "Optional doc surface slug for multi-doc workspaces. Omit to append to the primary doc surface."
},
"markdown": {
"type": "string",
"description": "Markdown chunk to append (CommonMark + GFM). Becomes one or more new blocks at the end of the existing doc."
}
},
"required": [
"slug",
"markdown"
]
}
get_html
Read an HTML surface's body. HTML surfaces (Surface.kind="html") store mockup or full-page content as three text fields (html, css, js) rendered together inside a sandboxed iframe. Use `list_surfaces` to enumerate html surfaces in a workspace. Omit `surface_slug` to read the primary html surface; pass it to target a specific tab. Empty (never-written) html surfaces return { html:"", css:"", js:"" }. 404 when `surface_slug` doesn't match a live html surface. Requires viewer role.
Parameters2
slug
string
required
The workspace slug. Accepts bare or org-prefixed form.
surface_slug
string
optional
Optional html surface slug. Omit to read the primary html surface.
Raw schema
{
"type": "object",
"properties": {
"slug": {
"type": "string",
"description": "The workspace slug. Accepts bare or org-prefixed form."
},
"surface_slug": {
"type": "string",
"description": "Optional html surface slug. Omit to read the primary html surface."
}
},
"required": [
"slug"
]
}
update_html
Write an HTML surface's body. Pass any of `html` / `css` / `js`; omitted fields stay unchanged. Pass empty string to clear. The surface renders in a sandboxed iframe on a separate origin (`render.trydock.ai`) with no access to Dock cookies, storage, or parent DOM โ you have free rein inside that boundary. Use any web technology the browser supports: external CDN fonts and CSS (Google Fonts, Tailwind CDN, Fontsource), JS libraries (three.js, GSAP, Chart.js, anime.js), inline `<script>`, Web Workers, WebGL, video, audio, canvas, dynamic DOM, complex CSS animations. Per-field caps: html 256 KB, css 200 KB, js 200 KB, total 600 KB. The sanitizer strips a small set of style smells: inline `on*=` event-handler attributes, `javascript:` and `data:text/html` URIs, `<meta http-equiv>` tags; use `addEventListener` and `<script>` instead. Layout: Dock renders the surface EDGE-TO-EDGE (full-bleed) inside the workspace โ the surface itself is the frame. Do NOT put `border-radius`, an outer border, or a drop-shadow on the root/outermost element unless the owner explicitly asked for that framing, or the specific design genuinely needs it; keep the page root flush and apply rounding to inner cards only. DESIGN LANGUAGE: Dock injects a base stylesheet into every surface โ semantic tokens + a small component kit โ that automatically follows each VIEWER's light/dark theme. PREFER these over hardcoded colors so the surface matches Dock and themes correctly for everyone (a surface with hardcoded dark colors looks broken for a light-mode teammate on a shared surface, and vice-versa). Tokens: var(--dock-canvas|surface|surface-muted|border|border-strong|text|text-2|text-muted|accent|accent-ink|data|data-strong|good|warn|crit), var(--dock-radius|shadow|gap); font is Inter via var(--dock-font). Component classes: .dock-card, .dock-stat/.dock-stat-value/.dock-stat-label, .dock-delta.up|.down, .dock-badge.good|warn|crit|neutral|accent (add a <span class="dot"></span>), .dock-btn(.primary), .dock-table (use td.name for the primary cell, .dock-num for tabular figures), .dock-grid, .dock-eyebrow, .dock-row, .dock-avatar, .dock-field + .dock-input, .dock-bars/.dock-bar(.hot). Put .dock-num on any number so it aligns. This is only a DEFAULT floor โ write your own CSS to override any of it; nothing in the baseline is !important, so a surface that brings its own styles always wins. Requires editor role.
Parameters5
slug
string
required
The workspace slug.
surface_slug
string
optional
Optional html surface slug. Omit to write the primary html surface.
html
string
optional
HTML body. Sanitized server-side (smells stripped, but `<script>` and `<link>` allowed at v2 โ load any CDN, write inline scripts, dynamic DOM). Use `validate_html` first for a pre-flight check.
css
string
optional
CSS source. Applied inside the sandbox iframe. At v2 you can also `<link rel="stylesheet">` external stylesheets from any HTTPS CDN โ useful for Tailwind CDN, Google Fonts, icon kits.
js
string
optional
JS source. Stored alongside html/css; at v2 you'll typically inline `<script>` in the html field instead (same execution context, fewer round-trips). 200 KB cap (matches the top-level `update_html` description; the server enforces `HTML_SURFACE_LIMITS.jsBytes` = 200_000).
Raw schema
{
"type": "object",
"properties": {
"slug": {
"type": "string",
"description": "The workspace slug."
},
"surface_slug": {
"type": "string",
"description": "Optional html surface slug. Omit to write the primary html surface."
},
"html": {
"type": "string",
"description": "HTML body. Sanitized server-side (smells stripped, but `<script>` and `<link>` allowed at v2 โ load any CDN, write inline scripts, dynamic DOM). Use `validate_html` first for a pre-flight check."
},
"css": {
"type": "string",
"description": "CSS source. Applied inside the sandbox iframe. At v2 you can also `<link rel=\"stylesheet\">` external stylesheets from any HTTPS CDN โ useful for Tailwind CDN, Google Fonts, icon kits."
},
"js": {
"type": "string",
"description": "JS source. Stored alongside html/css; at v2 you'll typically inline `<script>` in the html field instead (same execution context, fewer round-trips). 200 KB cap (matches the top-level `update_html` description; the server enforces `HTML_SURFACE_LIMITS.jsBytes` = 200_000)."
}
},
"required": [
"slug"
]
}
validate_html
Pre-flight check on html / css / js BEFORE writing via update_html. Returns { ok, errors, warnings, parsed } where parsed has byte counts per field and `dropped` (true if the sanitizer would strip anything from `html`). Errors cover cap breaches (`html_too_large`, `css_too_large`, `js_too_large`, `total_too_large`) and sanitizer rejection (`html_sanitize_rejected`, `html_sanitize_empty`). At v2 the sanitizer accepts `<script>` and `<link>` โ those used to be smells but are now first-class agent markup; isolation lives in the opaque render iframe, not the sanitizer. The smells still stripped: inline `on*=` attributes, `javascript:`/`data:text/html` URIs, `<meta http-equiv>` tags. NEVER writes anything. Use when iterating on a payload so you don't burn a write on something the surface would reject.
Create a new workspace in the caller's org. Works for both user and agent callers; agent-created workspaces attribute to the agent and enroll the agent's owning user as a co-owner so the human sees it in their dashboard. The new workspace is seeded with one primary surface matching `mode`: `doc` โ a Notes tab (for prose), `table` โ a Sheet tab (for records), `html` โ a Mockup tab (sandboxed HTML preview). Decide the surface before you create: prose (briefs, notes, summaries, drafts) โ `doc`; records with shared columns (tasks, leads, rows) โ `table`. If you omit `mode`, pass `initial_markdown` to signal a `doc`; with neither `mode` nor `initial_markdown`, an agent caller gets a guided error asking it to choose `doc` or `table` (so you never silently land on the wrong surface). An explicit `mode` is always honored. `html` is only picked when explicitly requested. Add more tabs of any kind later via `create_surface`. Agent-created workspaces default to org-visibility so sibling agents in the same org aren't 403'd. For prose content (briefs, summaries, changelogs) pass `initial_markdown` to seed the doc body in one call; the markdown is converted server-side, no need to hand-build ProseMirror JSON.
Parameters4
name
string
required
The workspace name. Required. Used to derive a slug if you don't pass one.
slug
string
optional
Optional URL-friendly slug (lowercase, kebab-case, 3-64 chars). Auto-derived from `name` if omitted; if the derived slug collides within your org, a -N suffix is appended.
mode
string
optional
Kind of the seeded primary surface โ choose by what you're about to write. `doc` mints a Notes tab: use it for PROSE (briefs, notes, summaries, drafts, status reports). `table` mints a Sheet tab: use it for RECORDS (tasks, leads, rows, anything with shared columns). `html` mints a Mockup tab (sandboxed HTML preview, for landing-page mockups + design previews), opt-in only. Pass this explicitly: when omitted, `initial_markdown` resolves the surface to a `doc`; with neither, an agent caller gets a guided error asking it to choose (no silent default to a Sheet, which would be the wrong surface if you meant prose). Add more tabs of any kind via `create_surface` later.
initial_markdown
string
optional
Optional Markdown body to seed the workspace's doc surface on create. CommonMark + GFM (tables, task lists, strikethrough). When provided AND mode is omitted, mode defaults to 'doc'. Skips the empty default-column scaffolding too. Ignored when mode='html' (no markdown equivalent for HTML surfaces โ use `update_html` after create). Use this for any prose-shaped output (briefs, summaries, status updates, changelog entries) instead of create + update_doc with hand-built JSON.
Raw schema
{
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The workspace name. Required. Used to derive a slug if you don't pass one."
},
"slug": {
"type": "string",
"description": "Optional URL-friendly slug (lowercase, kebab-case, 3-64 chars). Auto-derived from `name` if omitted; if the derived slug collides within your org, a -N suffix is appended."
},
"mode": {
"type": "string",
"enum": [
"table",
"doc",
"html"
],
"description": "Kind of the seeded primary surface โ choose by what you're about to write. `doc` mints a Notes tab: use it for PROSE (briefs, notes, summaries, drafts, status reports). `table` mints a Sheet tab: use it for RECORDS (tasks, leads, rows, anything with shared columns). `html` mints a Mockup tab (sandboxed HTML preview, for landing-page mockups + design previews), opt-in only. Pass this explicitly: when omitted, `initial_markdown` resolves the surface to a `doc`; with neither, an agent caller gets a guided error asking it to choose (no silent default to a Sheet, which would be the wrong surface if you meant prose). Add more tabs of any kind via `create_surface` later."
},
"initial_markdown": {
"type": "string",
"description": "Optional Markdown body to seed the workspace's doc surface on create. CommonMark + GFM (tables, task lists, strikethrough). When provided AND mode is omitted, mode defaults to 'doc'. Skips the empty default-column scaffolding too. Ignored when mode='html' (no markdown equivalent for HTML surfaces โ use `update_html` after create). Use this for any prose-shaped output (briefs, summaries, status updates, changelog entries) instead of create + update_doc with hand-built JSON."
}
},
"required": [
"name"
]
}
get_recent_events
Get recent activity events for a workspace. Who did what, when. Useful for understanding what's happened since you last looked.
Parameters2
slug
string
required
The workspace slug. Accepts either the bare slug ('my-workspace') or the org-prefixed form ('my-org/my-workspace') as shown in the dashboard URL; both resolve to the same workspace.
limit
number
optional
Max events to return (default 20)
Raw schema
{
"type": "object",
"properties": {
"slug": {
"type": "string",
"description": "The workspace slug. Accepts either the bare slug ('my-workspace') or the org-prefixed form ('my-org/my-workspace') as shown in the dashboard URL; both resolve to the same workspace."
},
"limit": {
"type": "number",
"description": "Max events to return (default 20)"
}
},
"required": [
"slug"
]
}
search
Search across everything the caller can already touch: workspace names, row cell values, and doc sections/paragraphs. Returns ranked hits (score 0-1) with a navigable URL per hit so the agent can open the exact row or doc section. Access-gated; never returns hits from workspaces the caller can't open. Use when the user references something by keyword ("find my launch-plan workspace", "which row mentions Redis?"). Faster than listing workspaces and iterating.
Parameters4
q
string
required
Search query. Case-insensitive substring match.
kind
string
optional
Narrow to one surface. 'all' (default) searches workspace names + row cells + doc sections. 'workspace' is fastest when the user is naming something, 'row' targets table data, 'doc-section' targets headings and paragraphs in doc-mode.
limit
number
optional
Max hits to return (default 20, max 100).
offset
number
optional
Hits to skip for pagination (default 0).
Raw schema
{
"type": "object",
"properties": {
"q": {
"type": "string",
"description": "Search query. Case-insensitive substring match."
},
"kind": {
"type": "string",
"enum": [
"all",
"workspace",
"row",
"doc-section"
],
"description": "Narrow to one surface. 'all' (default) searches workspace names + row cells + doc sections. 'workspace' is fastest when the user is naming something, 'row' targets table data, 'doc-section' targets headings and paragraphs in doc-mode."
},
"limit": {
"type": "number",
"description": "Max hits to return (default 20, max 100)."
},
"offset": {
"type": "number",
"description": "Hits to skip for pagination (default 0)."
}
},
"required": [
"q"
]
}
get_billing
Get the caller's org billing summary: current plan (free, pro, or scale), active counts and caps for every gated resource (agents, members, workspaces, rows per workspace, API calls per month, webhooks per month, messages per month bundle), monthly price in cents, card on file if any, next invoice date. Both humans and agents can call this. Use before upgrade_plan to check whether you're actually capped, and after to confirm the new plan landed.
Parameters
No parameters.
Raw schema
{
"type": "object",
"properties": {}
}
upgrade_plan
Move the caller's org to Pro ($19/mo flat, 10 agents, 20 members, 200 workspaces, 5k rows per workspace) or Scale ($49/mo flat, 30 agents, 60 members, 1,000 workspaces, 50k rows per workspace). The bill doesn't change as you add agents. If the org has no card on file, returns a Stripe Checkout URL for the human. If a card exists, a live plan switch (Pro โ Scale) is consent-gated. Two consent surfaces, you pick via `mode`: (1) `chat` (default): FIRST call returns { status: 'confirmation_required', confirm_token, message, expires_in }; surface the message to your user and re-call within 60s with `confirm_token` set. (2) `web`: FIRST call returns { status: 'approval_required', approval_url, polling_url, expires_at }; print the approval_url in chat for your user to click and approve in their browser, then poll `polling_url` for the result. No-card and same-plan paths execute on the first call (no money changes hands).
Parameters3
plan
string
optional
Target plan. Defaults to 'pro'.
mode
string
optional
Consent surface. 'chat' (default) uses the in-chat confirm_token round-trip. 'web' returns an approval_url the user clicks in a browser. Use 'web' if you're headless or your user prefers a click-to-approve flow.
confirm_token
string
optional
Chat-mode only. The token returned by the first call as `confirm_token`. Omit on the first call; include on the second call to execute the plan flip. Single-use, 60s TTL, bound to {org, caller, operation, params}.
Raw schema
{
"type": "object",
"properties": {
"plan": {
"type": "string",
"enum": [
"pro",
"scale"
],
"description": "Target plan. Defaults to 'pro'."
},
"mode": {
"type": "string",
"enum": [
"chat",
"web"
],
"description": "Consent surface. 'chat' (default) uses the in-chat confirm_token round-trip. 'web' returns an approval_url the user clicks in a browser. Use 'web' if you're headless or your user prefers a click-to-approve flow."
},
"confirm_token": {
"type": "string",
"description": "Chat-mode only. The token returned by the first call as `confirm_token`. Omit on the first call; include on the second call to execute the plan flip. Single-use, 60s TTL, bound to {org, caller, operation, params}."
}
}
}
downgrade_plan
Schedule a downgrade to Free at the end of the current billing period. The org keeps its current plan (Pro or Scale) and paid limits until the period ends. No-op when already on Free. Consent-gated. Two consent surfaces, you pick via `mode`: (1) `chat` (default): FIRST call returns { status: 'confirmation_required', confirm_token, message, expires_in }; surface to your user and re-call within 60s with `confirm_token` set. (2) `web`: FIRST call returns { status: 'approval_required', approval_url, polling_url }; print approval_url in chat, user clicks + approves, then poll polling_url for the result.
Parameters2
mode
string
optional
Consent surface. 'chat' (default) uses the in-chat confirm_token round-trip. 'web' returns an approval_url the user clicks in a browser.
confirm_token
string
optional
Chat-mode only. The token returned by the first call as `confirm_token`. Omit on the first call; include on the second call to execute the scheduled downgrade. Single-use, 60s TTL.
Raw schema
{
"type": "object",
"properties": {
"mode": {
"type": "string",
"enum": [
"chat",
"web"
],
"description": "Consent surface. 'chat' (default) uses the in-chat confirm_token round-trip. 'web' returns an approval_url the user clicks in a browser."
},
"confirm_token": {
"type": "string",
"description": "Chat-mode only. The token returned by the first call as `confirm_token`. Omit on the first call; include on the second call to execute the scheduled downgrade. Single-use, 60s TTL."
}
}
}
request_limit_increase
Ask Dock to raise a plan limit (agents, workspaces, rows, or other). We record the signal on the admin side; there's no reply loop. Use this when you hit a cap you can't resolve with upgrade_plan (e.g. you're already Pro but need a custom limit).
Parameters3
kind
string
required
Which limit to raise
desiredValue
number
optional
Optional: the specific limit you'd like
reason
string
optional
Optional: 1-2 sentences on the use case
Raw schema
{
"type": "object",
"properties": {
"kind": {
"type": "string",
"enum": [
"agents",
"workspaces",
"rows",
"other"
],
"description": "Which limit to raise"
},
"desiredValue": {
"type": "number",
"description": "Optional: the specific limit you'd like"
},
"reason": {
"type": "string",
"description": "Optional: 1-2 sentences on the use case"
}
},
"required": [
"kind"
]
}
list_surfaces
List the surfaces (tabs) inside a workspace. A workspace can hold any combination of `table` (rows + columns) and `doc` (TipTap body) surfaces, one or many of either kind; this tool tells you exactly what it has. Each surface has its own slug used in surface-scoped tool calls. Order matches the on-screen tab strip. Archived surfaces are hidden by default; pass `archived: true` to include them.
Parameters2
slug
string
required
The workspace slug. Accepts either the bare slug ('my-workspace') or the org-prefixed form ('my-org/my-workspace') as shown in the dashboard URL; both resolve to the same workspace.
archived
boolean
optional
Include archived surfaces too. Default false (live tabs only).
Raw schema
{
"type": "object",
"properties": {
"slug": {
"type": "string",
"description": "The workspace slug. Accepts either the bare slug ('my-workspace') or the org-prefixed form ('my-org/my-workspace') as shown in the dashboard URL; both resolve to the same workspace."
},
"archived": {
"type": "boolean",
"description": "Include archived surfaces too. Default false (live tabs only)."
}
},
"required": [
"slug"
]
}
create_surface
Create a new surface (tab) inside a workspace. `kind` picks `table`, `doc`, or `html`. Optional `slug` (lowercase kebab-case, 3-64 chars); when omitted the server slugifies `name` and appends a numeric suffix on collision. Optional `columns` overrides the default Title/Status/Notes triple for `table` kinds; ignored for `doc` and `html`. `html` surfaces start with an empty body โ write content via `update_html`. Editor role required. Emits `surface.created` so live listeners on the workspace stream see the new tab without a refetch.
Parameters5
slug
string
required
The workspace slug. Accepts either the bare slug ('my-workspace') or the org-prefixed form ('my-org/my-workspace') as shown in the dashboard URL; both resolve to the same workspace.
kind
string
required
Surface kind. `table` for rows + columns, `doc` for TipTap body, `html` for a sandboxed HTML mockup tab.
name
string
required
Display name shown on the tab. 1-64 chars.
surface_slug
string
optional
Optional URL-friendly slug for the surface (lowercase kebab-case, 3-64 chars). Auto-derived from `name` when omitted.
columns
array
optional
Optional initial columns for `table` kind. Same shape as get_workspace_schema returns. Defaults to Title/Status/Notes when omitted.
Raw schema
{
"type": "object",
"properties": {
"slug": {
"type": "string",
"description": "The workspace slug. Accepts either the bare slug ('my-workspace') or the org-prefixed form ('my-org/my-workspace') as shown in the dashboard URL; both resolve to the same workspace."
},
"kind": {
"type": "string",
"enum": [
"table",
"doc",
"html"
],
"description": "Surface kind. `table` for rows + columns, `doc` for TipTap body, `html` for a sandboxed HTML mockup tab."
},
"name": {
"type": "string",
"description": "Display name shown on the tab. 1-64 chars."
},
"surface_slug": {
"type": "string",
"description": "Optional URL-friendly slug for the surface (lowercase kebab-case, 3-64 chars). Auto-derived from `name` when omitted."
},
"columns": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": true
},
"description": "Optional initial columns for `table` kind. Same shape as get_workspace_schema returns. Defaults to Title/Status/Notes when omitted."
}
},
"required": [
"slug",
"kind",
"name"
]
}
update_surface
Rename, reslug, reorder, OR replace the column schema of a surface. Pass any subset of `name`, `new_surface_slug`, `position`, `columns`. Position is 0-based and is normalised across siblings so positions stay contiguous. Editor role required. Emits `surface.updated`.
**Column schema (`columns`)**: table surfaces only. Pass a full ColumnDef[] to REPLACE the existing schema atomically (no per-column add/remove churn, no row data loss โ existing row.data keys that are no longer mapped are preserved on disk and surface in future writes' `unmapped_fields`). Each ColumnDef = `{ key, label, type, position, width?, hidden?, description?, options? }`. Type โ text | longtext | url | status | owner | date | number; `options` is required on status/owner. Reject 400 with a `table-only` error if the surface is a doc or html kind. Use `get_workspace_schema` first to fetch the current shape, mutate it, send it back.
Parameters6
slug
string
required
The workspace slug. Accepts either the bare slug ('my-workspace') or the org-prefixed form ('my-org/my-workspace') as shown in the dashboard URL; both resolve to the same workspace.
surface_slug
string
required
The current slug of the surface to update.
name
string
optional
New display name. 1-64 chars.
new_surface_slug
string
optional
New slug for the surface (lowercase kebab-case, 3-64 chars). Must be unique within the workspace.
position
number
optional
0-based index in the tab strip. Other surfaces shift to keep positions contiguous.
columns
array
optional
Optional. Full replacement ColumnDef[] for the surface's table schema. Table surfaces only โ doc/html surfaces 400 with a table-only error. Each item: `{ key, label, type, position, width?, hidden?, description?, options? }`. type โ text|longtext|url|status|owner|date|number. Existing row.data keys not present in the new schema are preserved on disk but stop rendering in the UI (they'll surface as `unmapped_fields` on the next row write). Use get_workspace_schema โ mutate โ send the full array back; this is a REPLACE, not a merge.
Raw schema
{
"type": "object",
"properties": {
"slug": {
"type": "string",
"description": "The workspace slug. Accepts either the bare slug ('my-workspace') or the org-prefixed form ('my-org/my-workspace') as shown in the dashboard URL; both resolve to the same workspace."
},
"surface_slug": {
"type": "string",
"description": "The current slug of the surface to update."
},
"name": {
"type": "string",
"description": "New display name. 1-64 chars."
},
"new_surface_slug": {
"type": "string",
"description": "New slug for the surface (lowercase kebab-case, 3-64 chars). Must be unique within the workspace."
},
"position": {
"type": "number",
"description": "0-based index in the tab strip. Other surfaces shift to keep positions contiguous."
},
"columns": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": true
},
"description": "Optional. Full replacement ColumnDef[] for the surface's table schema. Table surfaces only โ doc/html surfaces 400 with a table-only error. Each item: `{ key, label, type, position, width?, hidden?, description?, options? }`. type โ text|longtext|url|status|owner|date|number. Existing row.data keys not present in the new schema are preserved on disk but stop rendering in the UI (they'll surface as `unmapped_fields` on the next row write). Use get_workspace_schema โ mutate โ send the full array back; this is a REPLACE, not a merge."
}
},
"required": [
"slug",
"surface_slug"
]
}
delete_surface
Archive a surface (soft-delete). Rows + doc body are preserved for restore. Idempotent: calling on an already-archived surface returns its current archivedAt unchanged. Cannot archive the only live surface in a workspace; create another first. Editor role required. Emits `surface.archived`.
Parameters2
slug
string
required
The workspace slug. Accepts either the bare slug ('my-workspace') or the org-prefixed form ('my-org/my-workspace') as shown in the dashboard URL; both resolve to the same workspace.
surface_slug
string
required
The slug of the surface to archive.
Raw schema
{
"type": "object",
"properties": {
"slug": {
"type": "string",
"description": "The workspace slug. Accepts either the bare slug ('my-workspace') or the org-prefixed form ('my-org/my-workspace') as shown in the dashboard URL; both resolve to the same workspace."
},
"surface_slug": {
"type": "string",
"description": "The slug of the surface to archive."
}
},
"required": [
"slug",
"surface_slug"
]
}
list_api_keys
List API keys. Agent callers see only the key they're authenticated with (a one-row response: id, prefix, lastUsedAt, the workspace it's bound to). User callers (cookie session) see every key for every agent they own. Plaintext is never returned; the key body is shown only once at create/rotate time.
Parameters
No parameters.
Raw schema
{
"type": "object",
"properties": {}
}
rotate_api_key
Atomically mint a new API key with the same agent / workspace / scopes / name and revoke the old one. Returns the new plaintext (`key`) once; store it before discarding the response. Subsequent requests with the OLD key return 401, so swap creds before retrying. Agents may rotate ONLY their own key (omit `id` to default to it); users may rotate any key they own. Use this for routine credential hygiene or after a suspected leak.
Parameters1
id
string
optional
API key id to rotate. Omit when called by an agent; defaults to the agent's own current key. Required for user callers to disambiguate when more than one key exists.
Raw schema
{
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "API key id to rotate. Omit when called by an agent; defaults to the agent's own current key. Required for user callers to disambiguate when more than one key exists."
}
}
}
revoke_api_key
Revoke an API key (soft-delete via `revokedAt`). Subsequent requests with the key return 401. Agents may revoke ONLY their own key; calling this is effectively a self-destruct, the response itself completes but the very next request will fail. Users may revoke any key they own. To swap creds without going dark in the gap, use `rotate_api_key` instead.
Parameters1
id
string
optional
API key id to revoke. Omit when called by an agent; defaults to the agent's own current key.
Raw schema
{
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "API key id to revoke. Omit when called by an agent; defaults to the agent's own current key."
}
}
}
request_revoke_agent_key
Ask the human owner to revoke ANOTHER agent's active API key (sibling agent). The MCP `revoke_api_key` tool is self-only by design; this is the cross-agent escalation path. Returns { status: 'approval_required', approval_url, polling_url, expires_in }: print approval_url in chat for the target agent's owner to click; poll polling_url for the result. Approval gate: the approving user must be the target agent's owner (Agent.ownerUserId match). Use this when you've spotted credential leakage, misbehaviour, or a stuck sibling that needs a clean kill; surface a useful `reason` so the human knows why.
Parameters2
target_agent_id
string
required
The id of the sibling agent whose key should be revoked. Get from list_workspace_members or list_workspaces; every member row carries the agent id.
reason
string
optional
1-2 sentences on why you're asking. Surfaces verbatim on the consent card so the owner knows what they're saying yes to. Capped at 500 chars.
Raw schema
{
"type": "object",
"properties": {
"target_agent_id": {
"type": "string",
"description": "The id of the sibling agent whose key should be revoked. Get from list_workspace_members or list_workspaces; every member row carries the agent id."
},
"reason": {
"type": "string",
"description": "1-2 sentences on why you're asking. Surfaces verbatim on the consent card so the owner knows what they're saying yes to. Capped at 500 chars."
}
},
"required": [
"target_agent_id"
]
}
request_rotate_agent_key
Ask the human owner to rotate ANOTHER agent's active API key (mint a new one + revoke the old). Same shape as request_revoke_agent_key: returns an approval_url, requires the target agent's owner to click. The new key plaintext is INTENTIONALLY not returned to the requesting agent; it's surfaced only to the human owner via Settings โ Agents, who hands it to the target agent out of band. Use when you've spotted leakage and the target needs a clean credential without going dark mid-task.
Parameters2
target_agent_id
string
required
The id of the sibling agent whose key should be rotated.
reason
string
optional
1-2 sentences on why. Surfaces on the consent card. Capped at 500 chars.
Raw schema
{
"type": "object",
"properties": {
"target_agent_id": {
"type": "string",
"description": "The id of the sibling agent whose key should be rotated."
},
"reason": {
"type": "string",
"description": "1-2 sentences on why. Surfaces on the consent card. Capped at 500 chars."
}
},
"required": [
"target_agent_id"
]
}
list_webhooks
List webhook endpoints registered on an org. Returns each webhook's id, url, subscribed events, active flag, and an 8-char `secretPreview` of the signing secret (full secret is only returned at create / rotate-secret time). Any org member (user or agent) can list. Use to audit what's subscribed before adding or removing endpoints.
Parameters1
org_slug
string
required
Org slug. The webhook collection is org-scoped, not workspace-scoped; one URL receives events from every workspace in the org.
Raw schema
{
"type": "object",
"properties": {
"org_slug": {
"type": "string",
"description": "Org slug. The webhook collection is org-scoped, not workspace-scoped; one URL receives events from every workspace in the org."
}
},
"required": [
"org_slug"
]
}
create_webhook
Register a new webhook endpoint on an org. The URL must be public (loopback / private ranges / cloud metadata are blocked at create-time AND re-validated by DNS at delivery-time). Events array filters which event kinds the endpoint receives: pick from row.* / comment.* / member.* / workspace.* / doc.*; an empty array means "none" so always pass at least one. Returns the signing `secret` exactly once (whsec_โฆ prefixed); store it on the receiver to verify HMAC signatures on incoming requests.
Parameters3
org_slug
string
required
Org slug
url
string
required
Public HTTPS URL to POST events to. Loopback (127.0.0.0/8, ::1), RFC1918 private ranges, link-local, and cloud-metadata addresses (169.254.169.254, etc.) are rejected. Max 2048 chars.
Toggle a webhook's `active` flag on or off. Inactive webhooks are skipped at delivery time (no retry queue, no log row) but the endpoint config is preserved so flipping back is one call. Use to silence a noisy receiver during maintenance without losing its URL + secret + event subscription.
Mint a fresh signing secret for a webhook. The new `secret` is returned exactly once; copy it to the receiver before the next event lands. After this call, deliveries are signed with the new secret only; receivers still validating against the old one will reject (401) until updated. Use after a suspected leak or as part of routine rotation hygiene.
Permanently delete a webhook endpoint. The URL stops receiving events immediately and the secret is destroyed; recreate from scratch if you need to re-add it. To pause without losing config, use update_webhook with active:false instead.
Send a direct message to another agent or human in the messaging substrate. Wires through cue.dock.svc, the same path the /live UI uses, so the recipient sees this message in their drawer (and, once they have a Dock-connected agent worker running, their agent harness's inbox). Address format is `<agent_slug>@<user_slug>`: `flint@socrates` targets the `flint` agent owned by user `socrates`; `self@<user_slug>` targets a human's synthetic self-agent (use this to message a human directly when you don't know which of their agents to ping). Use this when an agent legitimately needs to ask a teammate (human or agent) for help, hand off work, or follow up async; don't use it as a chat-ops side-channel for things that belong in workspace events. Sender identity follows the caller: agent callers send AS themselves, user callers send AS their self-agent (`self@<their_slug>`). Body cap is 32,000 chars. Returns `{ messageId, threadId, to }` on success. The recipient is resolved against the substrate's identity space, NOT against your accessible workspace set, this is messaging, not workspace write access. Pre-cue.dock.svc-deploy environments return `cue_not_configured` (caller treats as 'messaging not deployed yet').
Parameters4
to
string
required
Recipient address in the form `<agent_slug>@<user_slug>`. Examples: `flint@socrates` (agent), `self@govind` (human's self-agent โ use to DM a person directly). You may also pass an agent's stable id (`agt_...`) exactly as returned in `address_book`'s `agentId`. PREFER the id: an address is derived from the agent's slug, which does not follow a rename, so an address built from a teammate's current display name can fail to resolve. The id never changes.
body
string
required
Message text. Plain string, 1-32000 chars. `@<slug>` mentions inside the body CC the named agent on the message.
replyTo
string
optional
Optional cue message id to thread under. When set, the recipient's drawer renders this as a reply with an inline parent-preview. Get the id from a prior `send_message` response or from the recipient's inbox listing.
send_at
string
optional
Optional ISO-8601 UTC timestamp to schedule the message for future delivery (e.g. `2026-06-04T15:00:00Z`). Omit to send now. A past timestamp is treated as send-now. Honored only where scheduled send is enabled; otherwise ignored. `scheduled_at` is accepted as an alias.
Raw schema
{
"type": "object",
"properties": {
"to": {
"type": "string",
"description": "Recipient address in the form `<agent_slug>@<user_slug>`. Examples: `flint@socrates` (agent), `self@govind` (human's self-agent โ use to DM a person directly). You may also pass an agent's stable id (`agt_...`) exactly as returned in `address_book`'s `agentId`. PREFER the id: an address is derived from the agent's slug, which does not follow a rename, so an address built from a teammate's current display name can fail to resolve. The id never changes."
},
"body": {
"type": "string",
"description": "Message text. Plain string, 1-32000 chars. `@<slug>` mentions inside the body CC the named agent on the message."
},
"replyTo": {
"type": "string",
"description": "Optional cue message id to thread under. When set, the recipient's drawer renders this as a reply with an inline parent-preview. Get the id from a prior `send_message` response or from the recipient's inbox listing."
},
"send_at": {
"type": "string",
"description": "Optional ISO-8601 UTC timestamp to schedule the message for future delivery (e.g. `2026-06-04T15:00:00Z`). Omit to send now. A past timestamp is treated as send-now. Honored only where scheduled send is enabled; otherwise ignored. `scheduled_at` is accepted as an alias."
}
},
"required": [
"to",
"body"
]
}
create_support_ticket
File a support ticket. Mirrors to a GitHub issue in Dock's support repo and shows up in the user's dashboard at /settings/support. Use this for bugs (you hit an error), feature requests (Dock is missing something), billing (Stripe/subscription), questions (how do I X), or anything else. Prefer request_limit_increase when the user is simply hitting a plan cap.
Parameters5
kind
string
required
Ticket category.
title
string
required
Short headline (3-200 chars). Be specific: 'Table view loses focus on cell edit' beats 'broken'.
body
string
required
Detailed description (5-10000 chars). For bugs: include what you did, what happened, what you expected. For feature requests: the use case.
context
object
optional
Optional structured metadata echoed into the GitHub issue (workspace slug, URL, error trace, etc).
attachmentUrls
array
optional
Optional list of screenshot/attachment URLs to embed in the issue. URLs must be hosted on the Dock blob store; mint them via POST /api/support/upload first. Max 4.
Raw schema
{
"type": "object",
"properties": {
"kind": {
"type": "string",
"enum": [
"bug",
"feature",
"billing",
"question",
"other"
],
"description": "Ticket category."
},
"title": {
"type": "string",
"description": "Short headline (3-200 chars). Be specific: 'Table view loses focus on cell edit' beats 'broken'."
},
"body": {
"type": "string",
"description": "Detailed description (5-10000 chars). For bugs: include what you did, what happened, what you expected. For feature requests: the use case."
},
"context": {
"type": "object",
"description": "Optional structured metadata echoed into the GitHub issue (workspace slug, URL, error trace, etc)."
},
"attachmentUrls": {
"type": "array",
"items": {
"type": "string"
},
"description": "Optional list of screenshot/attachment URLs to embed in the issue. URLs must be hosted on the Dock blob store; mint them via POST /api/support/upload first. Max 4."
}
},
"required": [
"kind",
"title",
"body"
]
}
list_sheet_functions
List the Dock Sheets formula functions an agent can use in a cell carrier. Returns the canonical name, signature, one-sentence description, category (Math/Logic/Text/Date/Lookup/Predicates), rollout slice (v1/v2/v3/v4), and at least one worked example per function. Use this before writing a formula via update_row / create_row so you only reference functions that actually exist (no #NAME? errors). Also returns the alias map (e.g. CONCAT โ CONCATENATE) so you can pick the canonical name even when writing the alias the UI accepts. Optional filters: `category` narrows to one category, `slice` narrows to one rollout slice, `name` substring-matches names + descriptions + signatures. Public, no auth, no rate limit beyond global.
Parameters3
category
string
optional
Optional category filter.
slice
string
optional
Optional rollout-slice filter.
name
string
optional
Optional case-insensitive substring filter; matches function name, description, and signature.
Parse-check a formula expression server-side without writing anything. Returns { ok, error?, rewrittenFormula?, referencedFunctions, unknownFunctions }. Use BEFORE update_row / create_row when the formula references functions or syntax you're not 100% sure of: a `=SUMIFS(...)` with the wrong arg order or a misspelled `=AVERAG(...)` will round-trip into the cell as a stored carrier with no value, and the user will see #NAME? or #VALUE? on next view. Catch it here. `unknownFunctions` flags any identifier that isn't in the Dock Sheets catalog (including likely typos); `referencedFunctions` lists the canonical post-alias names the engine will see. Cheap, public, no auth, no workspace context needed.
Parameters1
formula
string
required
Formula expression to validate, including the leading '='. Example: '=SUMIF(B2:B10, ">0")'. Max 4000 chars.
Raw schema
{
"type": "object",
"properties": {
"formula": {
"type": "string",
"description": "Formula expression to validate, including the leading '='. Example: '=SUMIF(B2:B10, \">0\")'. Max 4000 chars."
}
},
"required": [
"formula"
]
}
evaluate_formula
Evaluate a formula expression against an actual Dock workspace's columns + rows, server-side, returning the same display value the UI's HyperFormula engine would render. Two modes: STANDALONE (omit `workspace_slug`) โ evaluates against an empty grid; useful for `=SUM(1, 2, 3)` or any formula with no cell references. IN-WORKSPACE (pass `workspace_slug`, optionally `at`) โ loads the workspace's grid, evaluates the formula as if pasted into the `at` cell (or A1 if omitted), resolves real refs against actual data. Returns { ok, displayValue, error? }. Workspace mode requires read access; standalone mode is public.
Parameters3
formula
string
required
Formula expression including '='. Max 4000 chars.
workspace_slug
string
optional
Optional workspace slug. Pass to evaluate against the workspace's actual rows + columns. Accepts bare or org-prefixed form.
at
object
optional
Optional anchor cell (only used with workspace_slug). The formula evaluates as if pasted into this cell; relative references resolve against it. Omit to anchor at the workspace's first cell.
Raw schema
{
"type": "object",
"properties": {
"formula": {
"type": "string",
"description": "Formula expression including '='. Max 4000 chars."
},
"workspace_slug": {
"type": "string",
"description": "Optional workspace slug. Pass to evaluate against the workspace's actual rows + columns. Accepts bare or org-prefixed form."
},
"at": {
"type": "object",
"properties": {
"rowId": {
"type": "string"
},
"colKey": {
"type": "string"
}
},
"required": [
"rowId",
"colKey"
],
"description": "Optional anchor cell (only used with workspace_slug). The formula evaluates as if pasted into this cell; relative references resolve against it. Omit to anchor at the workspace's first cell."
}
},
"required": [
"formula"
]
}
add_comment
Post a new comment on any target in a workspace: a row, a cell, a doc text range, an html element, an entire surface, or the workspace itself. Polymorphic target shape mirrors the REST POST /api/workspaces/:slug/comments. For threading, pass `parentId` to hang the new comment as a reply (the server flattens nested replies to single depth and auto-unresolves a resolved parent). Mentions are an array of `{ kind: 'user'|'agent', id, label }` triples; the server validates each mention's access to the workspace before accepting. Fires `comment.added` (and `comment.unresolved` when a reply reopens a resolved parent). For replies to existing comments where you don't want to reconstruct the target, prefer `reply_to_comment` which derives the target from the parent. Editor or commenter role required.
Parameters5
slug
string
required
The workspace slug ('my-workspace' or 'my-org/my-workspace').
Comment body (plain text or markdown). 1-5000 chars.
parentId
string
optional
Optional parent comment id. When passed, this comment becomes a reply in the thread. Nested replies flatten to single-depth (reply-to-reply re-points at the root). Re-opens a resolved parent.
mentions
array
optional
Optional `[{ kind, id, label }]` mentions. Each mention's principal must have workspace access. Fires inbox + email + webhook fan-out for newly-mentioned recipients only.
Raw schema
{
"type": "object",
"properties": {
"slug": {
"type": "string",
"description": "The workspace slug ('my-workspace' or 'my-org/my-workspace')."
},
"target": {
"type": "object",
"additionalProperties": true,
"description": "Polymorphic target. Shapes:\n { type: 'row', rowId: '<cuid>' }\n { type: 'cell', rowId: '<cuid>', columnKey: '<key>' }\n { type: 'doc_range', surfaceSlug: '<slug>', anchor: { from: <number>, to: <number>, text: '<plain>' } }\n { type: 'html_element', surfaceSlug: '<slug>', anchor: { selector: '<css>', text?: '<plain>' } }\n { type: 'surface', surfaceSlug: '<slug>' }\n { type: 'workspace' }"
},
"body": {
"type": "string",
"description": "Comment body (plain text or markdown). 1-5000 chars."
},
"parentId": {
"type": "string",
"description": "Optional parent comment id. When passed, this comment becomes a reply in the thread. Nested replies flatten to single-depth (reply-to-reply re-points at the root). Re-opens a resolved parent."
},
"mentions": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": true
},
"description": "Optional `[{ kind, id, label }]` mentions. Each mention's principal must have workspace access. Fires inbox + email + webhook fan-out for newly-mentioned recipients only."
}
},
"required": [
"slug",
"target",
"body"
]
}
list_comments
List comments in a workspace. Filter by `target_type` (row, cell, doc_range, html_element, surface, workspace), `target_id`, `surface` (returns every comment anchored to any element of one surface, useful for 'open threads on this tab'), `status` (open | resolved | all, default open), `mentioning_me: true` for comments that @-mention the caller, or `author: <principalId>` for comments by a specific user/agent. Returns up to 200 comments per call ordered by `createdAt` asc, with `surfaceSlug` denormalized for doc_range/html_element/surface targets so reply paths work even across archive boundaries. Use `get_comment_thread` to pull a single comment plus its replies + reactions.
Parameters9
slug
string
required
The workspace slug.
target_type
string
optional
Filter by comment target type.
target_id
string
optional
Filter by exact target id. For cells the id is `<rowId>:<columnKey>`; for doc_range/html_element/surface it's the Surface cuid. Combine with target_type for unambiguous filtering.
surface
string
optional
Surface slug filter. Returns every comment anchored anywhere inside this surface (doc_range / html_element / surface scope, plus row + cell comments on rows that live on the surface). 404 silently if the surface is archived (returns empty list).
status
string
optional
Resolution state filter. Default `open`.
mentioning_me
boolean
optional
When true, only return comments that @-mention the calling principal. Equivalent to REST `?mentioning=me`.
author
string
optional
Filter by author principal id. Useful for 'comments by Argus on this workspace' agent loops.
limit
number
optional
Max results (1-200, default 50).
offset
number
optional
Number of comments to skip for pagination.
Raw schema
{
"type": "object",
"properties": {
"slug": {
"type": "string",
"description": "The workspace slug."
},
"target_type": {
"type": "string",
"enum": [
"row",
"cell",
"doc_range",
"html_element",
"surface",
"workspace"
],
"description": "Filter by comment target type."
},
"target_id": {
"type": "string",
"description": "Filter by exact target id. For cells the id is `<rowId>:<columnKey>`; for doc_range/html_element/surface it's the Surface cuid. Combine with target_type for unambiguous filtering."
},
"surface": {
"type": "string",
"description": "Surface slug filter. Returns every comment anchored anywhere inside this surface (doc_range / html_element / surface scope, plus row + cell comments on rows that live on the surface). 404 silently if the surface is archived (returns empty list)."
},
"status": {
"type": "string",
"enum": [
"open",
"resolved",
"all"
],
"description": "Resolution state filter. Default `open`."
},
"mentioning_me": {
"type": "boolean",
"description": "When true, only return comments that @-mention the calling principal. Equivalent to REST `?mentioning=me`."
},
"author": {
"type": "string",
"description": "Filter by author principal id. Useful for 'comments by Argus on this workspace' agent loops."
},
"limit": {
"type": "number",
"description": "Max results (1-200, default 50)."
},
"offset": {
"type": "number",
"description": "Number of comments to skip for pagination."
}
},
"required": [
"slug"
]
}
get_comment_thread
Fetch a single comment with its replies + reactions in one round trip. Pass any comment id in the thread (root or reply). Returns `{ comment, replies }` where each entry includes aggregated reactions (`emoji`, `count`, `mine`). Use this when an agent receives a `comment.added` webhook with a `parentId` and needs full context before composing a reply.
Parameters1
comment_id
string
required
Comment id (any node in the thread).
Raw schema
{
"type": "object",
"properties": {
"comment_id": {
"type": "string",
"description": "Comment id (any node in the thread)."
}
},
"required": [
"comment_id"
]
}
reply_to_comment
Convenience wrapper around `add_comment` for the common reply case. Pass the parent comment id and the body; the handler reconstructs the target from the parent (no need for the agent to remember whether the parent was a row, cell, doc_range, html_element, surface, or workspace comment). Re-opens a resolved parent. Same threading rules as add_comment: nested replies flatten to single depth, so reply-to-reply re-points at the root.
Parameters3
comment_id
string
required
Parent comment id. Reply is posted as a child of this thread; if the parent itself is a reply, the new comment re-points to the thread root.
body
string
required
Reply body (1-5000 chars).
mentions
array
optional
Optional `[{ kind, id, label }]` mentions on the reply. Same validation + fan-out rules as add_comment.
Raw schema
{
"type": "object",
"properties": {
"comment_id": {
"type": "string",
"description": "Parent comment id. Reply is posted as a child of this thread; if the parent itself is a reply, the new comment re-points to the thread root."
},
"body": {
"type": "string",
"description": "Reply body (1-5000 chars)."
},
"mentions": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": true
},
"description": "Optional `[{ kind, id, label }]` mentions on the reply. Same validation + fan-out rules as add_comment."
}
},
"required": [
"comment_id",
"body"
]
}
resolve_comment
Mark a comment thread resolved. Idempotent: calling on an already-resolved thread returns the existing `resolvedAt` unchanged. Fires `comment.resolved`. Pair with `unresolve_comment` for the reverse. Used by agents to close a feedback thread once they've iterated on the change the reviewer asked for.
Parameters1
comment_id
string
required
Comment id to resolve (use the thread root, resolving a reply targets the reply itself, not the thread).
Raw schema
{
"type": "object",
"properties": {
"comment_id": {
"type": "string",
"description": "Comment id to resolve (use the thread root, resolving a reply targets the reply itself, not the thread)."
}
},
"required": [
"comment_id"
]
}
unresolve_comment
Re-open a previously-resolved comment thread. Idempotent on already-unresolved comments. Fires `comment.unresolved` with `reason: 'manual'`. (Auto-unresolve on reply fires the same event with `reason: 'reply'` and is handled by `add_comment` / `reply_to_comment`.)
Add or remove an emoji reaction to a comment. Reactions are per-principal: each (commentId, principalId, emoji) combination is unique. `action: 'add'` is idempotent (re-adding the same emoji is a no-op); `action: 'remove'` deletes the row if present. Fires `comment.reaction_added` / `comment.reaction_removed`. Use this for lightweight agent acknowledgement (๐ on a request before reading, ๐ to mark in-progress, โ when done), cheaper than a full reply.
Parameters3
comment_id
string
required
Comment to react to.
emoji
string
required
Emoji character (e.g. '๐', 'โ ', '๐').
action
string
optional
Whether to add or remove the reaction. Default `add`.
List the folder + file children of a Files surface (kind='files'). Folders sorted first by position then name; files sorted by name. Returns folders[], files[] with cuids agents can pass to `get_file` / `delete_file`. `parent_folder_id` defaults to null (= root of the surface); pass a folder id to descend into a sub-folder. Gated behind FILES_SURFACE_ENABLED + per-user allowlist (in beta on socrates@vector.build; other accounts get -32000 'not available').
Parameters3
slug
string
required
The workspace slug. Accepts either the bare slug or the org-prefixed form ('my-org/my-workspace') as shown in the dashboard URL.
surface_slug
string
required
Files-kind surface slug within the workspace. Use list_surfaces to enumerate; the Files surface kind is 'files'.
parent_folder_id
string
optional
Folder id to descend into. Omit (or pass null) for the surface root.
Raw schema
{
"type": "object",
"properties": {
"slug": {
"type": "string",
"description": "The workspace slug. Accepts either the bare slug or the org-prefixed form ('my-org/my-workspace') as shown in the dashboard URL."
},
"surface_slug": {
"type": "string",
"description": "Files-kind surface slug within the workspace. Use list_surfaces to enumerate; the Files surface kind is 'files'."
},
"parent_folder_id": {
"type": "string",
"description": "Folder id to descend into. Omit (or pass null) for the surface root.",
"nullable": true
}
},
"required": [
"slug",
"surface_slug"
]
}
get_file
Fetch metadata + a download URL for a single file by id. The `download_url` field is a direct Vercel Blob URL valid until the file is hard-deleted (Phase 5; Phase 6 wires a files.trydock.ai signed-URL minter with 5-min TTL + auth re-check). Useful for an agent reading file contents server-side (HTTP GET the URL) or surfacing a download link in a reply. Gated behind FILES_SURFACE_ENABLED + per-user allowlist.
Parameters2
slug
string
required
The workspace slug. Accepts either the bare slug or the org-prefixed form ('my-org/my-workspace') as shown in the dashboard URL.
file_id
string
required
The file cuid (from list_files). Surface + workspace are derived from the file row, so no surface_slug arg is needed.
Raw schema
{
"type": "object",
"properties": {
"slug": {
"type": "string",
"description": "The workspace slug. Accepts either the bare slug or the org-prefixed form ('my-org/my-workspace') as shown in the dashboard URL."
},
"file_id": {
"type": "string",
"description": "The file cuid (from list_files). Surface + workspace are derived from the file row, so no surface_slug arg is needed."
}
},
"required": [
"slug",
"file_id"
]
}
delete_file
Soft-delete a file by id. Moves to a 30-day trash window before the cleanup cron hard-deletes + refunds the storage quota. Restorable via the REST PATCH endpoint (`PATCH /api/workspaces/{slug}/files/{id} body: {restore:true}`); a PATCH-equivalent MCP tool ships in Phase 6. Editor role required. Gated behind FILES_SURFACE_ENABLED + per-user allowlist.
Parameters2
slug
string
required
The workspace slug. Accepts either the bare slug or the org-prefixed form ('my-org/my-workspace') as shown in the dashboard URL.
file_id
string
required
The file cuid (from list_files).
Raw schema
{
"type": "object",
"properties": {
"slug": {
"type": "string",
"description": "The workspace slug. Accepts either the bare slug or the org-prefixed form ('my-org/my-workspace') as shown in the dashboard URL."
},
"file_id": {
"type": "string",
"description": "The file cuid (from list_files)."
}
},
"required": [
"slug",
"file_id"
]
}
share_file
Mint a public share token for a file. Returns a `url` of the form `https://trydock.ai/share/files/<token>` that anyone (no auth) can open to view + download the file. The token is 32 random bytes (~256 bits of entropy) so guessing is infeasible. Revoke later with `revoke_file_share`. Editor role required. Gated behind FILES_SURFACE_ENABLED + per-user allowlist. Use when a workflow needs to hand the file off to an external system that can't authenticate.
Parameters2
slug
string
required
The workspace slug. Accepts either the bare slug or the org-prefixed form ('my-org/my-workspace') as shown in the dashboard URL.
file_id
string
required
The file cuid (from list_files).
Raw schema
{
"type": "object",
"properties": {
"slug": {
"type": "string",
"description": "The workspace slug. Accepts either the bare slug or the org-prefixed form ('my-org/my-workspace') as shown in the dashboard URL."
},
"file_id": {
"type": "string",
"description": "The file cuid (from list_files)."
}
},
"required": [
"slug",
"file_id"
]
}
revoke_file_share
Soft-revoke a share token minted via `share_file`. The public `/share/files/<token>` URL stops resolving immediately. Idempotent: revoking an already-revoked token returns `alreadyRevoked: true` without error. Editor role required. Gated behind FILES_SURFACE_ENABLED + per-user allowlist.
Parameters3
slug
string
required
The workspace slug.
file_id
string
required
The file cuid.
token_id
string
required
The share token id returned by `share_file` (NOT the `url` token). Same id appears in the `list_file_shares` response.
Raw schema
{
"type": "object",
"properties": {
"slug": {
"type": "string",
"description": "The workspace slug."
},
"file_id": {
"type": "string",
"description": "The file cuid."
},
"token_id": {
"type": "string",
"description": "The share token id returned by `share_file` (NOT the `url` token). Same id appears in the `list_file_shares` response."
}
},
"required": [
"slug",
"file_id",
"token_id"
]
}
list_recent_files
List the 50 most recently updated files in a Files surface, sorted by `updatedAt` descending. Flat surface-wide list; ignores folder structure. Useful for an agent answering 'what changed lately' or 'show me yesterday's uploads' without paging through the folder tree. Folders are omitted from this view. Gated behind FILES_SURFACE_ENABLED + per-user allowlist.
Parameters2
slug
string
required
The workspace slug. Accepts either the bare slug or the org-prefixed form ('my-org/my-workspace') as shown in the dashboard URL.
surface_slug
string
required
Files-kind surface slug within the workspace.
Raw schema
{
"type": "object",
"properties": {
"slug": {
"type": "string",
"description": "The workspace slug. Accepts either the bare slug or the org-prefixed form ('my-org/my-workspace') as shown in the dashboard URL."
},
"surface_slug": {
"type": "string",
"description": "Files-kind surface slug within the workspace."
}
},
"required": [
"slug",
"surface_slug"
]
}
address_book
List the agents you can message: every agent signed by your owner, each with its address and live status. Each entry has `agentId` (the agent's stable `agt_...` id โ pass this as send_message's `to`, and PREFER it: it is immutable, whereas `address` is derived from a slug that does not follow a rename), `address` (the `slug@owner` string, also accepted by send_message), `online` (true means connected and reading right now, so your message wakes it within about a second; false means it is offline and the message queues until it reconnects), plus `name` and `brandKey`. Also returns `ownerAddress` (`self@<owner>`) for messaging the owning human directly. Owner-scoped: it lists your teammates under the same owner, not other people's agents. Takes no arguments.
Local stdio bridge to the Dock MCP server. Point any
MCP-capable agent (Claude Desktop, Cursor, Windsurf, Zed, Cline, Continue)
at your Dock workspaces in one config change.
Do you need this package?
If your agent supports remote MCP connectors (Claude.ai web,
Claude.ai Projects), you don't need this โ add
https://trydock.ai/api/mcp as a custom connector and follow the OAuth
flow. See the MCP reference.
This package is for agents that only speak local stdio MCP โ the
dominant pattern for Claude Desktop and most code-editor agents today.
Quickstart
Get an API key in Dock's Settings โ API keys
(trydock.ai).
Add the config for your agent below.
Restart the agent. You'll see Dock's 8 tools appear.
Configs
All clients follow the same pattern: run npx -y @trydock/mcp, pass
DOCK_API_KEY in env. Per-client JSON snippets are in
configs/:
The bridge is a ~100-line Node process (src/bridge.js).
Every JSON-RPC message your agent writes on stdin is forwarded over HTTPS
to https://trydock.ai/api/mcp with your DOCK_API_KEY as Bearer auth.
The hosted server owns authentication, rate limits, audit, and tool
execution. The bridge stores no state and logs no request bodies.
Environment variables:
Variable
Required?
Purpose
DOCK_API_KEY
yes
Bearer token (get one from Settings โ API keys)
DOCK_MCP_URL
no
Override the upstream endpoint (staging / self-host). Default: https://trydock.ai/api/mcp
Security
Your API key is only held in the agent's environment and passed on
each HTTPS call. It's never logged, never written to disk by this
bridge.
Rotate keys any time in Dock's Settings โ API keys. Old keys return
401 immediately on revocation.
Keys are stored as SHA-256 hashes on Dock's side, not plaintext. A
Dock DB leak wouldn't expose usable credentials.
See the full security doc for the
threat model, the revocation runbook, and what data is audited.