Skip to main content
List, read, create and edit collaborative markdown documents, inspect their revision history, and archive or restore them.
Two different numbers describe a document. version is the collaborative sync token: it advances once per edit step — per keystroke for a person, per block for an agent write — so one large write can move it by hundreds. It exists only to be passed back as expected_version; never present it as a count. revision is the document’s history ordinal — the newest entry get_document_versions lists and the app’s history panel shows — and is the number to quote to a person. It only ever grows: old history entries are pruned, but the ordinal stays. A write moves version at once and revision as its history entry lands, so pin an exact state by citing both.

list_documents

Scope: documents:read (step-up) · Kind: read-only · Annotations: readOnlyHint: true, destructiveHint: false, openWorldHint: false
List the documents and pages you can reach. Narrow to one folder (folder_id, from list_folders) or a library section (scope: mine = ones you own, shared = shared with you, org = shared with the whole workspace); with neither, everything you can reach is merged. Each row carries scope, its folder_id when it is filed, and your role (owner | editor | commenter | viewer) — a viewer can read but not edit or comment. Optional query filters by title/content over the rows read. Folder and scope listings page: when next_cursor is a string, call again with it as cursor (same arguments) for the next page. The merged listing cannot page — truncated: true there means narrow with folder_id or scope rather than conclude a document is missing. Returns metadata only; use read_document for content.

Parameters

Pass at most one of scope and folder_id.
string
The documents and pages filed in this folder (its id from list_folders), newest edit first. Pages with cursor, and kind is applied in the index, so pages are full. A folder you can’t open answers Folder not found.
'mine' | 'shared' | 'org'
A library section. mine = artifacts you own; shared = artifacts explicitly shared with you; org = artifacts shared with the whole workspace.
string
Case-insensitive substring filter over title and body, applied to the rows already read. It is a funnel, not a ranked search; use search_knowledge for retrieval.
string
The next_cursor from the previous page of the same listing. Opaque and bound to the arguments that produced it: passing it with a different scope, folder_id, kind, query or include_archived is refused.
'document' | 'page' | 'table' | 'theme'
Keep only this kind of artifact. Applied to the rows read, so a filtered page may be shorter than limit; page with cursor for more.
boolean
default:"false"
Include archived artifacts.
number
default:"50"
Rows to return, 1 to 50. Clamped.

Returns

array
string | null
Where the next page starts. A string on the paged listings (mine, shared, org, folder_id) while more rows exist; null when the listing is exhausted, and always null on the merged listing, which cannot page.
boolean
true when more rows exist than were returned: on a paged listing that is the same as next_cursor being a string; on the merged listings it means narrow with folder_id or scope.
The text block has one line per row:
or No documents found.

Completion checklist

  • On mine, shared, org and folder_id, follow next_cursor until it is null before concluding a document does not exist; query filters each page, so a page can be short without being the last.
  • On the merged listing, truncated: true is inconclusive. Narrow with folder_id or a paged scope and list again.
  • list_folders turns a folder name into its folder_id.
  • The task is done when a read-back shows it: after any write, call read_document and check the content, not only the tool’s success.

Permissions and side effects

  • Needs documents:read.
  • Rows you have no role on are dropped, never surfaced.
  • A folder_id you cannot open fails with Folder not found, the same as a made-up id.
  • Reads only. Recorded in the agent activity history.

Errors

Example

Request
Response (structuredContent)

read_document

Scope: documents:read (step-up) · Kind: read-only · Annotations: readOnlyHint: true, destructiveHint: false, openWorldHint: false
Read a document as markdown (an HTML page returns its source). ALWAYS returns version — an opaque sync token, the value edit_document’s expected_version and replace_all writes are checked against (it moves with every edit step, so it is not a count of anything) — and revision, the document’s history ordinal (the newest entry get_document_versions lists). To pin an exact state, cite both: a write moves version at once but revision only as its history entry lands. Long documents window: pass offset to continue past 24000 characters.

Parameters

string
required
The artifact id.
number
default:"0"
Character offset to start from. Minimum 0.
number
default:"24000"
Characters to return, 1 to 24,000. Clamped.

Returns

string
string
The web-app link the document opens at (https://sidecars.ai/artifacts/<document_id>).
string
'document' | 'html_app' | 'table'
string
The requested slice. For an html_app this is the page’s HTML source.
number
The document’s sync token. Pass it as expected_version to edit_document; do not read it as a count.
number
The number of entries in the document’s history — the number a person recognises.
number
Total length of the full text.
number
The offset that was applied.
boolean
true when text remains after this slice. Call again with offset = offset + window.
boolean
The text block is:

Permissions and side effects

  • Needs documents:read and at least the viewer role on the artifact.
  • Reads only. Recorded in the agent activity history.

Errors

Limitations

  • Comment marks and other editor-only annotations are not part of the markdown.
  • For a table, the returned text is whatever markdown projection the table exposes; tables cannot be edited through MCP.
  • Reading a page here returns raw HTML. read_page is the page-specific tool and returns the same version.

Example

Request
Response (structuredContent)

create_document

Scope: documents:write (step-up) · Kind: write · Annotations: readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false
Create a new collaborative document, optionally filled with your markdown. In a session (session_id — from the session’s URL, /sessions/<id>; the document belongs to the session, so everyone who can open it can reach it, and its card is posted to the session) or in the person’s library with a scope: user (private to them, the default) or org (every workspace member can edit), optionally filed into a folder (folder_id). The caller becomes its owner. Returns — url is the link to share with people; version is the opaque sync token a later edit_document takes as expected_version; revision is the history ordinal a person recognises. Pass idempotency_key to make retries safe: a repeat with the same key within 10 minutes returns the original document instead of creating a twin.

Parameters

Pass session_id or a scope (optionally with folder_id), never both.
string
required
The document’s title. Must be non-empty.
string
Create inside this session: its id is the last part of the session’s URL, /sessions/<id>. The document belongs to the session (scope: "session"), so everyone who can open the session can reach it; people who can reply there can edit it. A document card is posted to the session.
'user' | 'org'
default:"user"
Where the document lives when no session_id is given. user is private to the caller; org is editable by every workspace member. No card is posted anywhere; the document gets its own chat.
string
With a scope: file the new document straight into this folder (from list_folders), so the folder’s members reach it. You must be its owner or a participant; a viewer is refused and nothing is created. Not with session_id.
string
Initial body. Written verbatim, up to 60,000 characters. Whitespace-only content is treated as empty.
string
Instead of content_markdown: the id from request_upload_url (format markdown) after the file was uploaded. Use it for a file already on disk or anything over about 8 KB. Written whole, up to 400,000 bytes. Never pass both.
string
Any string you choose. A second call with the same key within 10 minutes returns the original document (deduplicated: true) instead of creating another. Keyed on the key only, never the title, so two deliberate creates with one title both succeed.

Returns

string
string
The web-app link the document opens at (https://sidecars.ai/artifacts/<document_id>) — share this one, never a guessed path.
string
number
0 for an empty document; otherwise the sync token after the initial fill. On a deduplicated retry this is present only when the retry filled in missing content.
number
The document’s history ordinal after the create: 1 when an initial fill was written, 0 for an empty document. Always present, on deduplicated retries too.
boolean
Present and true when an idempotency_key matched a recent create.
boolean
Present and true when a deduplicated retry found the original empty and filled it with content_markdown.
Text: Created "<title>" (document_id: <id>, revision <n>) — <url>. or, on a retry, Document already created: "<title>" (document_id: <id>). Returning the original instead of creating a duplicate — ….

Permissions and side effects

  • Needs documents:write.
  • With session_id: you must be able to reply in that session (its owner, or invited with Can reply); Can view is not enough. The session must be in the workspace your token names and not archived. The check runs inside the same transaction as the insert.
  • With folder_id: you must be the folder’s owner or a participant. The check runs inside the same transaction as the insert.
  • The caller becomes the owner. The initial fill is a checkpointed write attributed to you via the host, and appears in get_document_versions.
  • With session_id, a card lands in the session. With a scope, nothing is posted anywhere.

Errors

Example

Request
Response (structuredContent)

edit_document

Scope: documents:write (step-up) · Kind: write, destructive · Annotations: readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false
Write verbatim markdown into a document while humans may be editing it live. Modes: append (add at the end), replace_section (replace the region an EXACT section_excerpt from read_document resolves to — a stale excerpt fails loudly with candidates, it can never clobber), replace_all (whole body; conflicts if expected_version moved), fill_empty (only if nobody typed yet). Human edits made mid-write are KEPT and reported in kept_blocks, never overwritten. Every write is checkpointed and undoable. dry_run resolves the target and writes nothing.

Parameters

string
required
'append' | 'replace_section' | 'replace_all' | 'fill_empty'
required
What to do with content_markdown.
string
The markdown to write, verbatim. Up to 60,000 characters; longer content stops early with stop_reason: "cap". Required unless upload_id is given.
string
Instead of content_markdown: the id from request_upload_url (format markdown) after the file was uploaded. Use it for a file already on disk or anything over about 8 KB. Written whole, up to 400,000 bytes. Never pass both.
string
replace_section only, and required there. An exact passage quoted from read_document. It is matched whitespace-insensitively and resolved to the contiguous blocks it covers; those blocks are replaced.
number
The version a prior read_document returned. Required for replace_all, where a mismatch is a hard conflict. On other modes it is optional and only reports drift.
boolean
default:"false"
Resolve the target and version checks, write nothing.

How expected_version is tiered

Returns

On success:
number
The document’s version after the write.
number
boolean
Present when the write did not complete.
'cap' | 'anchor_gone'
Why it stopped. cap: the 60,000-character budget was hit; continue with another call. anchor_gone: someone removed the section being written into; do not re-add it.
string
What the agent should do about the early stop.
boolean
Present when expected_version no longer matched (non-replace_all modes).
number
How many blocks changed since expected_version, when known.
object
Present when a person edited blocks while this write ran. Their edits are kept next to yours: { "count": n, "excerpts": ["…"] }.
On dry_run:
Text: Wrote <n> characters (<mode>). followed by Stopped early (<reason>). and/or Kept <n> block(s) a person edited while you were writing — their text stands next to yours. when applicable.

Permissions and side effects

  • Needs documents:write and the editor or owner role.
  • Checkpoints the document before writing; the checkpoint appears in get_document_versions and is undoable in the app.
  • Takes a short write lease. Concurrent agent writers queue briefly, then receive write_conflict with a retry hint.
  • Attributed to you via the host on version rows and cursors.
  • Works only on document artifacts; pages and tables are refused with a message naming the right tool.

Errors

A failed replace_section is never degraded into an append.

Recovering from refusals

Retrying without duplicating: a result with stopped_early: true wrote part of the content, so re-read and append only what is missing rather than the whole payload again. A call that timed out with no result may still have landed; read before retrying. A version_conflict on replace_section is not a stale excerpt: the excerpt still resolves, so the retry is the same call with the fresh version.

Example

Request
Response (structuredContent)
Here a person edited the Goals list while the agent was writing; the version had moved to 42 before the write, the excerpt still matched, and the person’s edit was kept.

get_document_versions

Scope: documents:read (step-up) · Kind: read-only · Annotations: readOnlyHint: true, destructiveHint: false, openWorldHint: false
Recent revisions of a document, newest first — automatic checkpoints (every agent write checkpoints before touching it, so this is the undo history) and named versions a person saved (pinned: true, with their label; never pruned). Each row’s revision is its ordinal in the history (r1 is the oldest), or null on a row older than the ordinal; doc_version is the sync token it snapshots. Metadata only; restoring and naming stay human acts in the app.

Parameters

string
required

Returns

number
The document’s current revision — the newest row’s ordinal, 0 when there are no rows.
array
The 20 most recent rows, newest first.
Text, one line per row:
or No revisions yet.

Permissions and side effects

  • Needs documents:read and any role on the artifact.
  • Reads only. Recorded in the agent activity history.

Errors

Limitations

  • Only the 20 most recent rows are returned; there is no paging.
  • Automatic checkpoints are pruned over time in the app; named versions are not.
  • There is no MCP tool to restore, name or diff versions.

Example

Request
Response (structuredContent)
instruction is a free-form label set by whatever produced the row; the values shown are illustrative.

archive_document

Scope: documents:write (step-up) · Kind: write, idempotent · Annotations: readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false
Archive a document, page or table (archived: true, the default) or restore one (archived: false). Archiving hides it from listings and refuses further edits; nothing is deleted, and the same call restores it. Idempotent. Needs the owner role, or editor with manage authority.

Parameters

string
required
The artifact id. A page_id from the page tools is the same id.
boolean
default:"true"
true to archive, false to restore.

Returns

string
string
'active' | 'archived'
The artifact’s status after the call.
boolean
false when the artifact was already in the requested state.
Text: Archived "<title>" (<id>)., Restored "<title>" (<id>)., or "<title>" (<id>) was already <status>.

Permissions and side effects

  • Needs documents:write and manage authority on the artifact: the owner, or an editor whose standing in the workspace allows managing it. The check runs inside the same transaction as the status change.
  • A status flip, never a delete. Content, versions and comment threads stay exactly where they are. An archived artifact drops out of list_documents (unless include_archived is set) and every edit tool refuses it with This document is archived; read_document, read_page and export_document still work.
  • Recorded in the artifact’s activity feed as you via the host.

Errors

Example

Request
Response (structuredContent)