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 carriesscope, itsfolder_idwhen it is filed, and yourrole(owner | editor | commenter | viewer) — a viewer can read but not edit or comment. Optionalqueryfilters by title/content over the rows read. Folder andscopelistings page: whennext_cursoris a string, call again with it ascursor(same arguments) for the next page. The merged listing cannot page —truncated: truethere 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 ofscope 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.No documents found.
Completion checklist
- On
mine,shared,organdfolder_id, follownext_cursoruntil it isnullbefore concluding a document does not exist;queryfilters each page, so a page can be short without being the last. - On the merged listing,
truncated: trueis inconclusive. Narrow withfolder_idor a pagedscopeand list again. list_foldersturns a folder name into itsfolder_id.- The task is done when a read-back shows it: after any write, call
read_documentand 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_idyou cannot open fails withFolder not found, the same as a made-up id. - Reads only. Recorded in the agent activity history.
Errors
Example
Requestread_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 returnsversion— 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) — andrevision, the document’s history ordinal (the newest entry get_document_versions lists). To pin an exact state, cite both: a write movesversionat once butrevisiononly as its history entry lands. Long documents window: passoffsetto 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
Permissions and side effects
- Needs
documents:readand at least theviewerrole 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_pageis the page-specific tool and returns the sameversion.
Example
Requestcreate_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 ascope:user(private to them, the default) ororg(every workspace member can edit), optionally filed into a folder (folder_id). The caller becomes its owner. Returns —urlis the link to share with people;versionis the opaque sync token a later edit_document takes as expected_version;revisionis the history ordinal a person recognises. Passidempotency_keyto make retries safe: a repeat with the same key within 10 minutes returns the original document instead of creating a twin.
Parameters
Passsession_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.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 ascope, nothing is posted anywhere.
Errors
Example
Requestedit_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 EXACTsection_excerptfrom read_document resolves to — a stale excerpt fails loudly with candidates, it can never clobber),replace_all(whole body; conflicts ifexpected_versionmoved),fill_empty(only if nobody typed yet). Human edits made mid-write are KEPT and reported inkept_blocks, never overwritten. Every write is checkpointed and undoable.dry_runresolves 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": ["…"] }.dry_run:
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:writeand theeditororownerrole. - Checkpoints the document before writing; the checkpoint appears in
get_document_versionsand is undoable in the app. - Takes a short write lease. Concurrent agent writers queue briefly, then
receive
write_conflictwith a retry hint. - Attributed to you via the host on version rows and cursors.
- Works only on
documentartifacts; 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
Requestget_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 theirlabel; never pruned). Each row’srevisionis its ordinal in the history (r1 is the oldest), ornullon a row older than the ordinal;doc_versionis 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.
No revisions yet.
Permissions and side effects
- Needs
documents:readand 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
Requestinstruction 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.Archived "<title>" (<id>)., Restored "<title>" (<id>)., or
"<title>" (<id>) was already <status>.
Permissions and side effects
- Needs
documents:writeand 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(unlessinclude_archivedis set) and every edit tool refuses it withThis document is archived;read_document,read_pageandexport_documentstill work. - Recorded in the artifact’s activity feed as you via the host.