Reference

MCP server

Dock speaks the Model Context Protocol over HTTPS. Any MCP-capable client can add Dock as a remote connector with OAuth 2.1 + Dynamic Client Registration. No manual config.

Endpoint

https://trydock.ai/api/mcp

OAuth metadata lives at the standard well-known paths:

  • /.well-known/oauth-authorization-server
  • /.well-known/oauth-protected-resource

Adding Dock to a client

Claude.ai (Chat web + Projects)

  1. Settings → Connectors → Add custom connector
  2. Paste https://trydock.ai/api/mcp
  3. Click Connect. Approve in the Dock consent screen.
  4. Done. The agent can now call Dock tools in every Chat and Project.

Claude Code (remote MCP)

mcp.json / settings.jsonjson
{
  "mcpServers": {
    "dock": {
      "url": "https://trydock.ai/api/mcp",
      "auth": { "type": "bearer", "token": "dk_..." }
    }
  }
}

Claude Desktop, Cursor, Windsurf, Zed, Cline, Continue (local stdio)

Most desktop agents still prefer local stdio MCP. Use the official bridge @trydock/mcp — a thin Node process that forwards stdio JSON-RPC to Dock's hosted HTTPS endpoint. Same auth (Bearer API key), same tools, no OAuth dance.

Works for Claude Desktop, Cursor, Windsurf, Clinejson
{
  "mcpServers": {
    "dock": {
      "command": "npx",
      "args": ["-y", "@trydock/mcp"],
      "env": {
        "DOCK_API_KEY": "dk_..."
      }
    }
  }
}

Per-client config file paths + Zed/Continue variants live in the bridge repo: try-dock-ai/mcp/configs. Tool schemas are in try-dock-ai/mcp/schemas — audit them before installing.

Agent frameworks (LangChain, Mastra, CrewAI…)

Each framework has its own MCP adapter. Point it at https://trydock.ai/api/mcp with a Bearer token, or spawn npx -y @trydock/mcp as a stdio transport if the framework expects local MCP.

Step-by-step setup for your specific client lives in the full integrations directory: exact config snippets for Claude Code, Cursor, Continue, and 50+ other clients.

Tools

list_workspaces
List every workspace this agent can access.
Arguments
{}
get_workspace
Fetch a workspace by slug, with columns and member count.
Arguments
{ "slug": "content-pipeline" }
list_rows
List rows. Pass `surface_slug` to filter to one sheet on multi-sheet workspaces; omit to return rows from every sheet. Each row in the response carries its own `surface_slug` so agents know which sheet it lives on.
Arguments
{ "slug": "content-pipeline", "surface_slug": "linkedin", "limit": 50 }
get_row
Fetch a single row by id — useful when a cue payload hands you one and a full list would be wasteful. Response carries `surface_slug` so the agent knows which sheet the row lives on.
Arguments
{ "slug": "content-pipeline", "rowId": "row_01HX..." }
create_row
Append a row. Optional `surface_slug` picks which sheet — omit to fall through to the workspace's primary table surface. Position is per-sheet.
Arguments
{ "slug": "content-pipeline", "data": { "title": "...", "status": "drafted" }, "surface_slug": "linkedin" }
update_row
Partial-merge update. Setting `surface_slug` to a different sheet MOVES the row there (position recomputes to the new sheet's tail unless `position` is also set).
Arguments
{ "slug": "content-pipeline", "rowId": "row_01HX...", "data": { "status": "sealed" }, "surface_slug": "outbox" }
delete_row
Delete a row by id.
Arguments
{ "slug": "content-pipeline", "rowId": "row_01HX..." }
move_rows
Atomically move N rows from their current sheet(s) to a target sheet inside the same workspace. All-or-nothing — any rowId outside the workspace fails the entire batch. Idempotent: rows already on the target are skipped. Up to 500 rows per call. Emits one `row.moved_surface` event per row that actually moved.
Arguments
{ "slug": "content-pipeline", "rowIds": ["row_01HX...", "row_01HY..."], "target_surface_slug": "linkedin" }
get_doc
Read a workspace's doc (TipTap) body. Returns three forms in one response: TipTap JSON (round-trip to preserve formatting), CommonMark+GFM markdown (feed to an LLM or render anywhere), and plain text (summarise or search). Pick whichever fits — the other two come free.
Arguments
{ "slug": "content-pipeline" }
update_doc
Replace a workspace's doc body. Last-write-wins. Emits the same doc.updated + heading/mention events as the dashboard editor. Editor role required.
Arguments
{ "slug": "content-pipeline", "content": { "type": "doc", "content": [ ... ] } }
get_workspace_schema
Return the column definitions so you know what keys create_row / update_row will accept. Empty on doc-first workspaces (columns auto-seed on first write).
Arguments
{ "slug": "content-pipeline" }
list_workspace_members
List principals with access — users + agents, role, and for agents brandKey. Verify a workspace is shared before writing output the team needs to see.
Arguments
{ "slug": "content-pipeline" }
delete_workspace
Archive a workspace (soft-delete). Rows, doc, and history are preserved; restore from Settings · Archived. Idempotent. Editor role required.
Arguments
{ "slug": "content-pipeline" }
update_workspace
Rename, change slug, switch default-view mode, or flip visibility. Pass any subset of name / new_slug / mode / visibility. Slug renames keep old URLs resolvable via WorkspaceSlugAlias. Editor role required.
Arguments
{ "slug": "content-pipeline", "name": "New name", "visibility": "org" }
share_workspace
Invite a human (by email) at a chosen role. Existing Dock users are added immediately; new emails get a 7-day invite that auto-accepts on magic-link sign-in. Editor role required.
Arguments
{ "slug": "content-pipeline", "email": "teammate@example.com", "role": "editor" }
update_workspace_member
Change a member's role. Owner-tier transitions require an owner caller. Sole-owner demote is blocked.
Arguments
{ "slug": "content-pipeline", "member_id": "wm_01HX...", "role": "commenter" }
remove_workspace_member
Remove a member. Consent-gated for agents (first call returns confirmation_required + 60s confirm_token; second call with the token executes). User callers (cookie session) skip the consent step. Sole-owner removal is blocked.
Arguments
{ "slug": "content-pipeline", "member_id": "wm_01HX..." }
create_workspace
Create a new workspace on the fly.
Arguments
{ "name": "New", "slug": "new", "mode": "table" }
get_recent_events
Read the last N events for a workspace. Useful for catch-up.
Arguments
{ "slug": "content-pipeline", "limit": 50 }
search
Keyword search across workspaces, rows, and doc sections. Returns ranked hits with navigable URLs. Access-gated — never surfaces hits from workspaces you can't open. Use when the user references something by name instead of listing every workspace and iterating.
Arguments
{ "q": "launch plan", "kind": "all", "limit": 20 }
get_billing
Get the caller's org billing summary: current plan, active agent count, card on file, next invoice date. Safe to call from any agent.
Arguments
{}
upgrade_plan
Move the org to Pro ($19/mo) or Scale ($49/mo). No card on file → Stripe Checkout URL for the human. Card exists → live plan switch, consent-gated: first call returns a confirm_token, re-call within 60s with the token to execute. See Dangerous operations below.
Arguments
{ "plan": "pro" }  // first call; add confirm_token on the second
downgrade_plan
Schedule a downgrade to Free at the end of the current billing period. The org keeps its paid caps until the period ends. Consent-gated like upgrade_plan — two-call handshake with a confirm_token.
Arguments
{}  // first call; add confirm_token on the second
request_limit_increase
Ask Dock to raise a plan limit (agents, workspaces, rows, or other) when upgrade_plan can't solve it. We record the signal; no reply loop.
Arguments
{ "limit": "workspaces", "reason": "Running one workspace per client; need 500." }
list_surfaces
List the tabs (surfaces) inside a workspace. Each is a `table` (rows + columns) or `doc` (TipTap body) with its own slug. Pass `archived: true` to include archived tabs.
Arguments
{ "slug": "content-pipeline" }
create_surface
Add a new tab to a workspace. `kind` picks `table` or `doc`. `surface_slug` and `columns` are optional — server fills sensible defaults. Editor role.
Arguments
{ "slug": "content-pipeline", "kind": "table", "name": "Q3 brief" }
update_surface
Rename, reslug, or reorder a surface. Pass any subset of `name`, `new_surface_slug`, `position`. Editor role.
Arguments
{ "slug": "content-pipeline", "surface_slug": "q3-brief", "name": "Q3 launch brief" }
delete_surface
Archive a surface (soft-delete). Idempotent. Cannot archive the only live surface in a workspace. Editor role.
Arguments
{ "slug": "content-pipeline", "surface_slug": "q3-brief" }
list_api_keys
List API keys. Agent callers see only their own (one row); user callers see every key for every agent they own. Plaintext is never returned — only the prefix and metadata.
Arguments
{}
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 exactly once. Agents may rotate only their own key (omit `id` to default to it); users may rotate any key they own.
Arguments
{ "id": "key_01HX..." }  // omit when called by an agent
revoke_api_key
Soft-delete an API key. Subsequent requests with the key 401. Agents may revoke only their own key (effectively a self-destruct); users may revoke any key they own.
Arguments
{ "id": "key_01HX..." }  // omit when called by an agent
list_webhooks
List webhook endpoints registered on an org. Returns id, url, events, active flag, and an 8-char secret preview. Any org member can list.
Arguments
{ "org_slug": "vector" }
create_webhook
Register a new webhook endpoint on an org. URL must be public (loopback / private ranges / cloud metadata are blocked). Returns the signing secret exactly once — store it on the receiver to verify HMAC signatures.
Arguments
{ "org_slug": "vector", "url": "https://hooks.example.com/dock", "events": ["row.created", "row.updated"] }
update_webhook
Toggle a webhook's active flag on or off. Inactive webhooks are skipped at delivery time. Use to silence a noisy receiver during maintenance without losing config.
Arguments
{ "org_slug": "vector", "webhook_id": "wh_01HX...", "active": false }
rotate_webhook_secret
Mint a fresh signing secret for a webhook. Returned exactly once — copy to the receiver before the next event lands or signature verification fails.
Arguments
{ "org_slug": "vector", "webhook_id": "wh_01HX..." }
delete_webhook
Permanently delete a webhook endpoint. URL stops receiving events immediately and the secret is destroyed. To pause without losing config, use update_webhook with active:false instead.
Arguments
{ "org_slug": "vector", "webhook_id": "wh_01HX..." }
create_support_ticket
File a support ticket (bug, feature request, billing, question). Mirrors to a GitHub issue and shows up in /docs/support/contact.
Arguments
{ "kind": "bug", "title": "Row edits 409 on retry", "body": "Steps: ..." }

A small set of tools never executes on the first call. They return a one-shot confirm_token and a summary message, and only run when the agent re-issues the same call with confirm_token set. The agent is expected to surface the summary to its user in chat (Claude Code, Cursor, etc.) and re-call only after the user says yes.

Consent-gated tools today: upgrade_plan, downgrade_plan. The pattern extends to any future tool that moves money or permanently changes org state.

Handshake shape

First call — no confirm_token:

// request
tools/call { "name": "upgrade_plan", "arguments": { "plan": "scale" } }

// response
{
  "status": "confirmation_required",
  "message": "Agent is requesting a plan switch: pro → scale ($49/mo flat, prorated mid-cycle). Confirm by re-calling upgrade_plan with confirm_token set to the token below, within 60s.",
  "confirm_token": "H3kZ...base64url...",
  "expires_in": 60,
  "operation": "upgrade_plan",
  "params": { "plan": "scale" }
}

Second call — same arguments plus the token:

tools/call {
  "name": "upgrade_plan",
  "arguments": { "plan": "scale", "confirm_token": "H3kZ...base64url..." }
}

Guarantees

  • Single-use. A consumed token is permanently spent. Re-use returns a JSON-RPC error with data.reason = "already_consumed".
  • 60-second TTL. Expired tokens return data.reason = "expired". Re-call without a token to mint a fresh one.
  • Param-bound. A token minted for { plan: "pro" } cannot be consumed as { plan: "scale" }. Returns data.reason = "params_mismatch".
  • Caller-bound. Token A minted by agent Alpha cannot be consumed by agent Bravo. Returns data.reason = "principal_mismatch".
  • Org-bound. Tokens are scoped to the org the first call authenticated against — cross-org replay is rejected.

Fast paths that skip the gate

Some upgrade_planbranches don't move money and don't flip a live subscription. Those execute on the first call, no token required:

  • No card on file. The tool returns a Stripe Checkout URL. The human entering a card in Checkout IS the consent signal.
  • Design-partner orgs. Already on Scale at $0 — no-op.
  • Same-plan resume. Cancelling a previously scheduled downgrade reverses a user-initiated request; nothing new gets charged.

Agent identity

When you authorize an MCP connector via OAuth, Dock auto-creates an agent identity for that connector in your org. The agent's name comes from the OAuth client's self-declared name (Claude Code, Cursor, Windsurf, etc.) and a small brand favicon overlays the agent's orb so the connector is recognizable at a glance. Activity log shows "Claude Code added 3 rows" instead of "Govind added 3 rows".

The agent inherits your workspace access — the workspaces you’re already a member of when you authorize, plus any new workspaces shared with you afterward, including ones that live in other teams. Trusting a person means trusting their tools, so anywhere you have access via a WorkspaceMember row, the agent can read and write at your role. The owner of the share manages you; you manage your agents. If the share is revoked or your role is downgraded, your agents follow in lock-step. Find or rename your agent under Settings → Agents; the brand badge stays even after a rename. Removing the agent revokes every key bound to it across every workspace.

Two different users authorizing the same connector get two separate agents (so attribution still tells you who ran it). One agent per (user, OAuth client) pair. Re-authorization is idempotent: it returns the existing agent and un-archives if the user previously removed it.

Cap behavior: OAuth-promoted agents count toward your org's agent cap. If you're at the limit, the OAuth flow returns 403 agent_cap_reached instead of issuing a token that would silently fall back to attributing activity to you. Upgrade or remove an agent to proceed.

How attribution works

The principal Dock stamps on every event, row write, and doc edit is picked based on the auth path the request used. Three cases:

  • Agent API key (Bearer dk_*) → attributes to the agent. Any MCP tool call, any REST write made with the key flows through as principalType: "agent", principalId: <agent.id>.
  • OAuth bearer token (Bearer dock_at_*) → attributes to the OAuth-promoted agent for that (user, client) pair. If the user's org was at the agent cap at OAuth time and no agent was promoted, falls back to user attribution for that token.
  • Session cookie (dashboard) → attributes to the user.

Verify the attribution from any agent by reading get_recent_events — each event carries principalType + principalName so the agent sees itself in the feed the same way the dashboard does.

Protocol notes

  • Transport: HTTP JSON-RPC (one request per call, no streaming).
  • Auth: OAuth 2.1 with PKCE + Dynamic Client Registration for interactive clients. Bearer tokens (dk_...) for headless / programmatic clients.
  • Errors follow the JSON-RPC error spec. An x-request-id header is also returned for cross-ref with server logs.
  • MCP tool responses mirror the REST endpoint shapes. A row returned from create_row is byte-for-byte the same as POST /rows.

Manual JSON-RPC example

curl: list toolsbash
curl -X POST https://trydock.ai/api/mcp \
  -H "Authorization: Bearer dk_..." \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
curl: call a toolbash
curl -X POST https://trydock.ai/api/mcp \
  -H "Authorization: Bearer dk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc":"2.0","id":2,"method":"tools/call",
    "params":{ "name":"create_row",
      "arguments":{"slug":"content-pipeline",
        "data":{"title":"Via MCP","status":"drafted"}}}
  }'
  • OAuth + DCR setup — how MCP-aware clients self-register and get tokens.
  • Connecting clients — per-client config (Claude Desktop, Cursor, Windsurf, Cline, etc.).
  • Tool catalog — every MCP tool, auto-generated from src/lib/mcp-tools.ts.
  • Dangerous-ops handshake — two-call confirm for irreversible tools (upgrade / downgrade plan).
  • Server card — JSON catalog at /.well-known/mcp/server-card.json for indexers.
  • Agent overview — agent identity + signed-agent inheritance.
  • REST API — every MCP tool has a REST equivalent.