MCP reference
Every tool on the Fermium MCP server, with parameters, the response shape, and example prompts.
Server: https://fermium.app/mcp, streamable HTTP, stateless. Every request carries Authorization: Bearer <token>; the token comes from the OAuth flow your client runs, or from a static dsk_ token. Without one the server answers 401 with a WWW-Authenticate header that points at the OAuth discovery document. See Authentication.
Setup for the common clients is in Connect Codex, Connect Claude Code, and Other clients.
Agent tool names
Agents were previously called Brains. Tool discovery now advertises Agent names:
| Current tool | Previous callable name |
|---|---|
list_agents |
list_brains |
load_agent |
load_brain |
create_agent |
create_brain |
agent_read, agent_write, agent_search |
brain_read, brain_write, brain_search |
list_agent_connections, request_agent_tool_call |
list_brain_connections, request_brain_tool_call |
run_agent |
run_brain |
The same substitution applies to the other non-legacy tools with brain or
brains in their names. On these renamed tools, pass agent instead of brain.
For example, load_agent({"agent": "support"}). Authorization, proposals and
tool-call approvals use the same checks as before.
Previous names and their brain argument remain callable for saved clients.
Response field names such as brains, brain and from_brain, stored filenames
such as BRAIN.md, and REST paths retain their existing spelling. Tools whose
names did not change, including propose_change, list_proposals and publishing
tools, also retain their existing parameters. The reference sections below keep
the previous signatures for compatibility; use the advertised schema for a
current Agent alias. Older legacy_brain navigation tools are callable but are
no longer advertised.
File activity tools
get_file_activity, get_folder_activity, list_high_traffic_files,
list_file_activity_events and activity_status provide permission-filtered
totals, charts, retained events and lightweight change polling. See
Activity API and MCP for parameters and examples.
Server instructions
The server provides usage guidance when the client connects. Agents can call
usage_guide for supported publishing, sharing, document-editing and Agent workflows.
Use list_docs before publishing something that may already exist; use
list_agents and load_agent before working with an Agent's context.
Publishing defaults to restricted access. Sharing requires the appropriate grants; moving a document does not change its link or grant access. Connected Agent sources never widen the caller's document permissions. Follow the responses' version, proposal and approval states rather than assuming a requested change is already live.
The document object
Every tool that touches a document returns the same object:
{
"doc_id": "01J7ZK3N9QW0X4B2C6D8E0F1G2",
"slug": "xBVNwKdi7ukQhXrWhQuz4g",
"title": "Q3 roadmap",
"url": "https://fermiumusercontent.app/d/xBVNwKdi7ukQhXrWhQuz4g/",
"visibility": "unlisted",
"listing": "private",
"retention_days": 0,
"expires_at": null,
"version": 3,
"total_bytes": 48211,
"created_at": "2026-09-15T14:02:11.482910+00:00",
"updated_at": "2026-09-15T16:40:03.118204+00:00",
"kind": "html",
"folder": "Planning/2026",
"tags": ["q3-review"],
"alias": "q3-roadmap",
"icon": "lucide:rocket/amber",
"legal_hold": false,
"root_id": 4,
"root": { "id": 4, "slug": "engineering", "name": "Engineering", "kind": "shared" },
"doc_type": null,
"attrs": { "language": "python", "source_path": "source.py" },
"files": ["index.html", "style.css"]
}
folder is the path the document lives in; "" means the top of its root. root says which root that is: null (with root_id: null) for a document in someone's My documents, otherwise {id, slug, name, kind}. doc_type is null for ordinary documents and instructions, index, skill, workflow or memory for an agent file. alias is the document's human link name, or null. icon is the document's icon in the grammar below, or null for the kind glyph. legal_hold is true while an Enterprise legal hold freezes the document. attrs appears only when the kind has attributes (snippets: language, source_path; markdown: source_path; file shares: filename, display, optional description, poster, width, height, duration). files appears on tools that return the file list. url uses your custom domain when one is set as the link host.
Shared parameters
The publish_* tools and create_upload accept the same optional settings:
| Parameter | Type | Default | Notes |
|---|---|---|---|
title |
string | kind-specific | HTML defaults to "Untitled"; markdown uses the first heading; snippets "Snippet"; files the file name |
visibility |
restricted, unlisted, password, workspace |
restricted |
Restricted requires sign-in plus explicit access, on every plan. workspace is a legacy paid mode with the same grant checks. Unlisted and password links are deliberate external publication choices. |
password |
string | required when visibility is password |
|
listing |
private, team |
private |
Placement only; never grants permission |
retention_days |
integer >= 0 | 30 | 0 keeps forever; a workspace policy may bound it |
folder |
string | personal root or Members/<member name> in a shared root |
where the document lives, a path like Customers/Acme; created on the fly |
root |
string | My documents | "general" or a shared root's slug from list_roots; needs edit there. The document then belongs to the root. "~" or omitted is the user's My documents |
tags |
string list | [] |
topics that cut across folders; lowercase slugs, max 10 |
idempotency_key |
string | a retry with the same key returns the first document instead of creating another | |
nav |
object | publish_* only, for agents of the earlier form. {group, navigation?, label?, icon?, position?, visibility?} places the new document under a top-level group of an agent's member tree in the same call; the group is created when missing, and navigation defaults to the agent the wiki publishes, or the workspace's default agent. Only when the user asked for the document to appear in the agent or the wiki. The response carries the entry as nav, or nav_error when placement was refused (the document is published either way) |
|
brain |
string | publish_* only, for agents of the earlier form. An agent's slug or id; places the new document in that agent's first group, or under nav.group when both are given. Same rule: only when the user asked for the document to be part of the agent |
Folder paths: segments of 1 to 64 letters, digits, spaces, ., _ or -, starting with a letter or digit and not ending in .; / between segments; at most 8 levels and 512 characters; case preserved. A bad segment is rejected with a message naming it.
Publishing
publish_html
Publish a self-contained HTML page up to 20 MiB of UTF-8, including embedded assets. The page becomes index.html; add assets with put_file. All files together must fit within the 20 MiB document total.
| Parameter | Type | Required |
|---|---|---|
html |
string | yes |
| shared parameters | no |
Prompt: "Publish this dashboard as a page in Ops/Dashboards, keep it forever, tag it runbook."
publish_markdown
Publish markdown as a rendered page with source.md kept next to it. Tables, highlighted fenced code, task lists, strikethrough, mermaid fences and [[wiki links]] render.
| Parameter | Type | Required |
|---|---|---|
content |
string | yes |
| shared parameters | no |
Prompt: "Write up the incident as a markdown postmortem and publish it for the team." The agent adds listing: "team".
publish_snippet
Publish code as a highlighted page with line numbers and a raw link.
| Parameter | Type | Required | Notes |
|---|---|---|---|
content |
string | yes | must not be empty |
language |
string | no | a Pygments lexer name; omit or auto to detect |
| shared parameters | no |
Prompt: "Share this SQL query as a snippet." The response includes attrs.language.
publish_file
Publish one file up to 20 MiB, base64-encoded. Images get a viewer page, media a player page, PDFs are served directly, other allowed types get a download page. .md files render as markdown documents unless render is false.
| Parameter | Type | Required | Notes |
|---|---|---|---|
filename |
string | yes | plain name with an allowed extension: png jpg jpeg gif webp svg ico mp4 webm m4v mov mkv mp3 m4a aac wav ogg opus flac pdf txt md markdown csv json zip js css |
content_base64 |
string | yes | the file's bytes |
render |
boolean | no | default true; only affects .md |
description |
string | no | markdown, up to 16 KiB, shown under the viewer; not allowed on PDFs |
| shared parameters | no |
Returns the document with files. Prompt: "Publish this screenshot so I can send it to the client."
create_upload
Mint a signed upload URL, then send a local file directly from disk with a raw HTTPS PUT. File bytes never pass through the MCP tool call. New .html/.htm uploads become rendered HTML pages with the same sandbox as publish_html, up to 20 MiB. New JS/CSS files get download pages, also capped at 20 MiB. Other file shares, including PDF, video and audio, keep the 2 GiB limit.
| Parameter | Type | Required | Notes |
|---|---|---|---|
filename |
string | yes | new files: html htm png jpg jpeg gif webp svg ico pdf txt md markdown csv json zip js css mp4 webm m4v mov mkv mp3 m4a aac wav ogg opus flac |
doc_id |
string | no | replace a file in this existing document; omit new-document metadata |
path |
string | with doc_id |
target path; index.html replaces an HTML page, or use an asset/source path |
base_version |
integer | with doc_id |
document version from the latest content you read |
lock_token |
string | no | token from lock_doc; must still be valid when the PUT commits |
title, description |
string | no | new documents only; description is for file shares, capped at 2,000 characters; HTML has no separate description |
visibility |
restricted, unlisted or workspace |
no | new documents only; defaults to restricted; password is not allowed at upload time |
listing, retention_days, tags, folder, root |
no | new documents only, as above |
Response:
{
"upload_url": "https://fermium.app/u/<ticket>",
"method": "PUT",
"expires_in_minutes": 60,
"max_upload_bytes": 2147483648,
"filename": "fight.mp4",
"curl": "curl --fail-with-body -sS -T \"<local-file-path>\" \"https://fermium.app/u/<ticket>\"",
"note": "The PUT response body is the published doc (JSON), including its shareable url."
}
The agent runs the curl line (or you do). The PUT response is the finished document. Re-PUTting the same URL returns the same document; the ticket embeds an idempotency key. Treat the URL as a secret until it expires. Prompt: "Upload the recording at ~/Videos/demo.mp4 with a description linking to the ticket."
For a large page already on disk, call create_upload with filename: "blueprint.html". To replace an existing page, also pass doc_id, path: "index.html" and base_version from your latest read. The URL, sharing and placement stay intact, and the upload adds a version. Editable files are limited to 20 MiB each and together within an HTML document. Generated viewer pages and primary file-share payloads cannot be replaced.
For coordinated edits, acquire a lease with lock_doc, pass its token to create_upload, upload, then unlock_doc. Renew the lease if needed. Access, lock and version are checked again after transfer. A 423 response tells the agent to wait 5 seconds, read the latest content/version and reapply edits before requesting a fresh URL; a 409 also requires refreshing and merging. Download large files through authenticated GET /api/docs/{doc_id}/files/{path} and use its X-Doc-Version. Successful ticket retries return uploaded_version and cannot overwrite later edits.
Updating
All update tools keep the URL. Identical content is a no-op and the response includes changed: false. The owner and, on Business and above, anyone with edit access may update.
update_html
Replace the page with up to 20 MiB of UTF-8. Supporting files count toward the same 20 MiB document total.
| Parameter | Type | Required |
|---|---|---|
html |
string | yes |
doc_id |
string | yes |
title |
string | no |
message |
string | no |
HTML documents only. message is a one-line note stored with the version, like a commit message; it shows in file_history and file_blame. Prompt: "Swap the second chart for a table and update the same link."
update_markdown
| Parameter | Type | Required |
|---|---|---|
doc_id |
string | yes |
content |
string | yes |
title |
string | no |
message |
string | no |
Markdown documents only. message as on update_html.
update_snippet
| Parameter | Type | Required | Notes |
|---|---|---|---|
doc_id |
string | yes | |
content |
string | yes | |
language |
string | no | omit to keep the stored language; auto to re-detect |
title |
string | no | |
message |
string | no | as on update_html |
Snippets only.
set_description
Set, replace or clear ("") the markdown description on a file share. Re-renders the viewer page in place without re-uploading the file. Up to 16 KiB. Not available on PDFs.
| Parameter | Type | Required |
|---|---|---|
doc_id |
string | yes |
description |
string | yes |
generate_poster
Grab a frame from a video file share, attach it as poster.jpg, and re-render the page so the player shows it and pasted links unfurl with a thumbnail. Runs automatically after video uploads; call it to backfill or refresh.
| Parameter | Type | Required |
|---|---|---|
doc_id |
string | yes |
Files inside a document
put_file
Add or replace a supporting file. Exactly one of content or content_base64. Replacing a path writes a new version; identical content is a no-op. Returns the document with files and changed.
| Parameter | Type | Required | Notes |
|---|---|---|---|
doc_id |
string | yes | |
path |
string | yes | relative, no .., at most 5 levels, e.g. img/chart.png |
content |
string | one of | text |
content_base64 |
string | one of | binary, up to 20 MiB decoded |
Prompt: "Add the chart image to the page as img/chart.png and reference it."
list_files
| Parameter | Type | Required |
|---|---|---|
doc_id |
string | yes |
Returns the document plus files: [{path, size, sha256, content_type}].
delete_file
| Parameter | Type | Required |
|---|---|---|
doc_id |
string | yes |
path |
string | yes |
index.html and a document's primary source or shared file cannot be deleted.
Search and read
Background and limits: Search and edit.
search_docs
Grep the contents of every text file in every document the user can see: their own, the shared roots they can open, and team-listed documents in the workspace.
| Parameter | Type | Default | Notes |
|---|---|---|---|
q |
string | required | literal substring by default; at most 256 characters |
regex |
boolean | false | treat q as a Python regular expression |
case_sensitive |
boolean | false | |
folder |
string | "" for the root; omit for everywhere |
|
recursive |
boolean | true | with folder, include subfolders |
root |
string | narrow to one root: "~" for My documents, "general" or a shared root's slug |
|
tag |
string | ||
kinds |
string list | from html, markdown, snippet, file |
|
path_glob |
string | pattern on the file path inside a document, e.g. *.md, src/*.ts |
|
include_team |
boolean | true | false for the user's own documents only |
limit |
integer | 50 | matching files to return, at most 200 |
context |
integer | 1 | lines either side of each hit, at most 10 |
Response:
{
"query": "pgbouncer",
"count": 1,
"results": [
{
"doc_id": "01J7…", "title": "Payments on-call", "url": "https://fermiumusercontent.app/d/…/",
"folder": "Runbooks", "kind": "markdown", "mine": true, "owner_email": "ada@acme.com",
"path": "source.md",
"matches": [{ "line": 42, "text": "Restart pgbouncer before failing over.", "before": ["## Failover"], "after": [""] }]
}
]
}
At most 20 matching lines per document. The generated index.html of markdown, snippet and file documents is skipped. Prompt: "Which runbook mentions pgbouncer?"
grep_doc
Search inside one document the user can see: all its text files, or one with path.
| Parameter | Type | Default |
|---|---|---|
doc_id |
string | required |
pattern |
string | required |
path |
string | |
regex |
boolean | false |
case_sensitive |
boolean | false |
context |
integer | 2 |
Returns {doc_id, title, url, version, files: [{path, matches}], total_matches, truncated}. Prompt: "Find every TODO in this document before I edit it."
get_file
Read a text file out of a document, whole or by 1-based inclusive line range. Works on the user's documents and on team-listed ones. Text files up to 1 MiB.
| Parameter | Type | Required |
|---|---|---|
doc_id |
string | yes |
path |
string | yes |
start_line |
integer | no |
end_line |
integer | no |
Returns {doc_id, path, content, total_lines, start_line, end_line, version, size}. Prompt: "Show me lines 30 to 60 of the runbook's source."
Editing
edit_file
Exact find-and-replace inside one text file. old_string must occur exactly once unless replace_all is true; zero or several matches fail with the count. One edit is one new version; the rendered page is regenerated (source.md re-renders, source.<ext> re-highlights, an HTML document's index.html is replaced). The generated index.html of a markdown, snippet or file document is refused with a message naming the file to edit instead.
| Parameter | Type | Required | Default |
|---|---|---|---|
doc_id |
string | yes | |
path |
string | yes | |
old_string |
string | yes | must not be empty |
new_string |
string | yes | must differ |
replace_all |
boolean | no | false |
Returns the document object plus path, replacements, changed, version. Prompt: "Change the on-call phone number in the runbook to +1 555 0100."
apply_patch
Apply a unified diff, exactly what git diff prints: ---/+++ headers with optional a/ b/ prefixes, @@ hunks, several files, /dev/null for creations and deletions. Context must match exactly; nothing is written unless every hunk applies, and the error names the file, hunk and line. One patch per file per call. Cannot delete index.html or a document's primary content.
| Parameter | Type | Required |
|---|---|---|
doc_id |
string | yes |
diff |
string | yes |
Returns the document object plus files: [{path, action, changed}] and version, where action is create, modify or delete. Prompt: "Apply this diff to the dashboard."
list_versions
| Parameter | Type | Required |
|---|---|---|
doc_id |
string | yes |
Returns {doc_id, versions: [{version, created_at, created_via, total_bytes, current, files: [{path, size, sha256}]}]}, newest first.
restore_version
Roll back by writing a new version equal to an earlier one. History is not rewritten; identical content is a no-op.
| Parameter | Type | Required |
|---|---|---|
doc_id |
string | yes |
version |
integer | yes |
Returns the document object with files, plus restored_from and changed. Prompt: "Undo the last change to the roadmap."
edit_file, apply_patch and restore_version count against the publish quota of 120 per hour.
Concurrency-aware editing
The tools above write whatever the document holds now, which is right when the agent is the only writer. When a person may be typing in the browser editor at the same time, or another agent shares the document, use these. Background: edit leases.
write_file
Replace one whole file, refusing to clobber a change the agent has not seen. The path picks the updater: source.md re-renders the markdown page, source.<ext> re-highlights a snippet, index.html of an HTML document updates directly, anything else is stored as a supporting file. The generated index.html of a markdown or snippet document is refused.
| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
doc_id |
string | yes | ||
path |
string | yes | ||
content |
string | yes | text, or base64 with encoding |
|
base_version |
integer | no | the version the agent read with get_file or list_docs; the write lands only if the document is still at that version |
|
lock_token |
string | no | from lock_doc; without it a write into a document another user has leased is refused |
|
encoding |
utf8, base64 |
no | utf8 |
base64 for binary supporting files |
Returns the document object plus path, changed, version, sha256 and size. When base_version is stale the tool fails with a message that carries the current version, who changed it, and a unified diff of that file from the agent's version to the current one, ending with Re-read with get_file and retry with base_version=<n>. When another user holds the lease it fails with their email, the lease expiry, and recovery steps: wait 5 seconds, fetch the latest content and version, reapply the intended edits to that content, and retry with its base_version. If still locked, back off and refresh again before the next write. Use get_file, or an authenticated raw download for large files.
Prompt: "Rewrite the runbook's failover section; Ada may be editing it right now." The agent reads with get_file, writes with base_version, and merges if refused.
lock_doc
Take the edit lease on a document before a multi-step change. Locks stop other writers only; reading, viewing and syncing are never blocked. The caller's own lease never blocks them.
| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
doc_id |
string | yes | owner or editor | |
ttl_seconds |
integer | no | 600 | 1 to 600; leases are short so a crashed client never blocks a document for long |
path |
string | no | a hint shown to other editors about which file is being worked on |
Returns {doc_id, token, expires_at, acquired_at, holder: {email, user_id}, path, mine: true}. If another user holds the lease the call fails with their email, the expiry, and instructions to wait 5 seconds and base the next write on freshly fetched content and its version. Taking a lease the caller already holds replaces it with a fresh token and expiry.
renew_lock
| Parameter | Type | Required | Default |
|---|---|---|---|
doc_id |
string | yes | |
token |
string | yes | |
ttl_seconds |
integer | no | 600 |
Extends the caller's lease; the token stays the same. A lease that expired but was not taken by anyone else can still be renewed.
unlock_doc
| Parameter | Type | Required |
|---|---|---|
doc_id |
string | yes |
token |
string | yes |
Returns {doc_id, released, forced, lock}. Release as soon as the last write lands so a person can pick the document back up. Breaking someone else's lease is not available to agents; the document's owner can do it through the REST API with force.
list_changes
What changed since the agent last looked: the user's documents, plus team-listed documents and the shared roots they can see with include_team, oldest first, each with its manifest so a client can fetch only files whose hash differs. The contract for building a mirror on this feed is in Building a sync client.
| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
since |
string | no | the next_cursor from the previous call; omit for everything |
|
folder |
string | no | narrow to a folder | |
recursive |
boolean | no | true | with folder, include subfolders |
include_team |
boolean | no | false | adds team-listed documents and, when root is omitted, every shared root the user can see |
root |
string | no | "~": My documents only. A shared root's slug: that root only, whatever include_team says. Rows carry root |
|
limit |
integer | no | 200 | at most 200 |
{
"changes": [
{ "doc_id": "01J7…", "title": "Payments on-call", "folder": "Runbooks", "tags": ["runbook"], "kind": "markdown", "version": 7,
"updated_at": "2026-09-16T09:12:40+00:00", "changed_at": "2026-09-16T09:12:40+00:00", "deleted": false, "deleted_at": null,
"owner_email": "ada@acme.com", "listing": "team",
"manifest": { "index.html": { "sha256": "…", "size": 18211, "content_type": "text/html; charset=utf-8" }, "source.md": { "sha256": "…", "size": 4210, "content_type": "text/markdown; charset=utf-8" } } }
],
"next_cursor": "MjAyNi0wOS0xNlQwOToxMjo0MHwwMUo3…",
"has_more": false
}
changed_at moves on content writes and on metadata changes (rename, move, tags, visibility, listing, description, alias, legal hold, delete), so a mirror sees a rename even though updated_at did not move. Deleted documents come through with deleted: true and an empty manifest for 30 days after deletion. Prompt: "What changed in Runbooks since yesterday?"
Orientation: tree and bundles
Two tools for getting the lay of the land without paging through list_docs. Both include only documents the user may open, and count the rest in skipped. Background: Folders, tags, and stars.
list_tree
The file tree under a folder, depth levels deep, in one call.
| Parameter | Type | Default | Notes |
|---|---|---|---|
folder |
string | personal root or Members/<member name> in a shared root |
|
depth |
integer | 1 | immediate children; 0 walks everything, at most 8 levels |
include_team |
boolean | false | add other members' documents the user may open; rows then carry own and, for someone else's document, owner |
root |
string | My documents | "general" or a shared root's slug for that root's tree |
{
"folder": "Runbooks", "depth": 1, "include_team": false,
"folders": [
{ "path": "Runbooks/Payments", "name": "Payments", "icon": "lucide:wrench/amber", "description": null, "doc_count": 4, "truncated": true }
],
"docs": [
{ "doc_id": "01J7…", "title": "Payments on-call", "kind": "markdown", "icon": null, "alias": "payments-oncall", "size": 4210, "updated_at": "2026-09-16T09:12:40+00:00", "url": "https://fermiumusercontent.app/d/…/", "tags": ["runbook"], "visibility": "workspace" }
],
"doc_count": 5,
"skipped": 0
}
doc_count on a folder counts visible documents under it recursively. A folder at the depth boundary carries truncated: true instead of docs and children. Up to 5,000 documents are considered per call. Prompt: "What is under Runbooks?"
download_bundle
Mirror a folder locally in one call: a signed, short-lived URL to a zip of the current version of every document under the folder that the user may open.
| Parameter | Type | Default |
|---|---|---|
folder |
string | personal root or Members/<member name> in a shared root |
recursive |
boolean | true |
include_team |
boolean | false |
root |
string | My documents; a shared root's slug bundles that root |
{
"url": "https://fermium.app/api/bundles/<token>",
"expires_at": "2026-09-16T09:22:40+00:00",
"bytes": 1048576, "doc_count": 12, "file_count": 31, "skipped": 1,
"folder": "Runbooks", "recursive": true, "filename": "Runbooks.zip",
"curl": "curl -sS -L -o Runbooks.zip \"https://fermium.app/api/bundles/<token>\""
}
The URL is good for 10 minutes and one download, and is bound to the user who requested it. Inside the zip: <folder>/<title>/<files> for each document plus a meta.json (id, title, kind, alias, folder, tags, version, visibility, url, updated_at, files) and an index.json at the root listing every document with its version and the count of documents skipped by access rules. A bundle may hold at most 500 MiB uncompressed (the call fails with a 413 before anything is read; bundle a subfolder or pass recursive: false), and an account may mint at most 6 bundles per hour (429). Nothing to bundle is an error rather than an empty zip. Each bundle is recorded in the audit log as doc.bundle.
Agents are told to mirror, grep at disk speed, and then write back through edit_file or apply_patch so versions and links stay intact. For a mirror that stays in step, use the change feed instead; see Building a sync client.
File history, diff and blame
Background: Versions and history. All three need the same access as viewing the document. Text files up to 1 MiB get line counts, diffs and blame; binaries get sizes and hashes.
Every version records actor (who wrote it), via (mcp, ui, upload, system) and an optional message, which agents set with the message parameter on update_html, update_markdown and update_snippet.
file_history
Which versions changed one file, newest first.
| Parameter | Type | Required |
|---|---|---|
doc_id |
string | yes |
path |
string | yes |
{
"doc_id": "01J7…", "path": "source.md", "current_version": 7, "text": true,
"versions": [
{ "version": 7, "created_at": "2026-09-16T09:12:40+00:00", "actor": "ada@acme.com", "created_by_user_id": 3, "via": "ui", "message": null, "change": "modified", "size": 4210, "sha256": "…", "added": 3, "removed": 1 },
{ "version": 2, "created_at": "2026-09-15T16:40:03+00:00", "actor": "bo@acme.com", "created_by_user_id": 7, "via": "mcp", "message": "Add failover section", "change": "added", "size": 3980, "sha256": "…", "added": 120, "removed": 0 }
]
}
change is added, modified or removed. text turns false, and added and removed are null, once the file is not text in some version. Prompt: "Who last touched the runbook's source?"
file_diff
| Parameter | Type | Required | Notes |
|---|---|---|---|
doc_id |
string | yes | |
path |
string | yes | |
from_version |
integer | yes | |
to_version |
integer | no | defaults to the current version |
Returns {doc_id, path, from_version, to_version, from, to, identical, text, unified, hunks, added, removed}. from and to each carry {version, created_at, actor, created_by_user_id, via, message, size, sha256}. unified is the diff as git diff prints it, with a/<path>@v<n> and b/<path>@v<n> headers; hunks is the same change as side-by-side rows, [{old_start, old_count, new_start, new_count, rows: [{type, old_no, old, new_no, new}]}] where type is equal, add, remove or change. For a binary file text is false and unified is null. Prompt: "What changed in the dashboard between version 4 and now?"
file_blame
| Parameter | Type | Required | Notes |
|---|---|---|---|
doc_id |
string | yes | |
path |
string | yes | |
version |
integer | no | defaults to the current version |
{
"doc_id": "01J7…", "path": "source.md", "version": 7, "current_version": 7,
"lines": [
{ "line_no": 1, "text": "# Payments on-call", "version": 2, "actor": "bo@acme.com", "created_at": "2026-09-15T16:40:03+00:00", "message": "Add failover section" },
{ "line_no": 2, "text": "Restart the pooler before failing over.", "version": 7, "actor": "ada@acme.com", "created_at": "2026-09-16T09:12:40+00:00", "message": null }
],
"versions": [ { "version": 2, "created_at": "…", "actor": "bo@acme.com", "created_by_user_id": 7, "via": "mcp", "message": "Add failover section" }, { "version": 7, "…": "…" } ]
}
Text files only; a file that is not text in some version fails with a message naming the version. Prompt: "Which lines of the pricing page did a person change by hand?" The agent checks blame before overwriting them.
Icons
Background and the grammar: Icons.
set_icon
| Parameter | Type | Required | Notes |
|---|---|---|---|
target |
string | yes | doc:<id>, folder:<path>, tag:<slug>, nav:<entry_id> or navigation:<slug> |
icon |
string | yes | lucide:<name>[/<colour>], emoji:U+1F680 or a literal emoji, or svg:<id> |
Returns {target, icon} with the stored form (an emoji comes back as code points; a bare curated name comes back as lucide:<name>). Unknown names and colours are rejected with a message. Who may set what: documents by the owner or an editor; folders by a member with documents in the folder or a workspace owner or admin; tags by any member; agent members and agents by owners and admins. Prompt: "Give the Acme folder a briefcase icon in blue."
clear_icon
| Parameter | Type | Required |
|---|---|---|
target |
string | yes |
Returns {target, icon: null}; the target falls back to its default glyph.
upload_icon
Team and above. Add an SVG to the workspace library.
| Parameter | Type | Required | Notes |
|---|---|---|---|
name |
string | yes | 1 to 64 characters of letters, digits, spaces, ., _ and - |
svg |
string | yes | the file's text, at most 32 KiB |
The SVG is validated rather than rewritten: scripts, event-handler attributes, <foreignObject> and similar, DOCTYPE and entity declarations, and any href or url() that leaves the file are rejected with the reason. Returns {id, icon: "svg:<id>", name, url, bytes, sha256, uploaded_by, created_at}; pass icon to set_icon. Uploading identical bytes returns the existing entry.
list_icons
No parameters. Returns {lucide: [names], colors: [12 names], library: [{id, icon, name, url, bytes, sha256, uploaded_by, created_at}], library_available, grammar}. Call it before set_icon with a name you are not sure ships.
Agents as agent memory
Background: Agents, Skills and Workflows. An agent is a set of typed markdown files (BRAIN.md, INDEX.md, skills/*.md, workflows/*.md, memory/**/*.md) plus a journal, sources and includes. The tools are written for any harness. Two rules hold for every call here:
- Protected files change by proposal.
BRAIN.md, skills and workflows are never changed by these tools directly; a write files a proposal that a person with write access decides in the app. Agents cannot decide proposals. - Sources only narrow. Source documents appear only when the calling user may open them.
brain is a slug from list_brains; "personal" (also "me", "my", "~") is the user's own private agent. An agent the user cannot read answers "no agent" as if it did not exist.
The agent object:
{
"id": 7, "slug": "support", "name": "Support", "kind": "brain", "personal": false, "owner": null,
"icon": "lucide:life-buoy", "description": "How we answer customers: policies, macros, escalation.",
"journal_budget": 40000, "legacy_navigation_id": null,
"files_count": 12,
"counts": { "skills": 2, "workflows": 1, "memory": 7, "sources": 3, "includes": 1 },
"pending_proposals": 1,
"created_at": "…", "updated_at": "…",
"my_level": "edit", "can_write": true, "can_manage": false
}
kind is brain or personal_brain; owner is the owner's email for a personal agent. my_level is view (read and propose) or edit (write and decide). legacy_navigation_id is set on an agent converted from the earlier form.
list_brains
No parameters. Returns {brains: [brain], can_create}: the workspace agents the user can read, then their personal agent. Agents call it at the start of a session and pick the agent whose name or description matches the task. Prompt: "Which agents do we have?"
load_brain
Everything an agent needs to start working on a concern, in one payload. Several agents can be loaded in one session.
| Parameter | Type | Default | Notes |
|---|---|---|---|
brain |
string | "personal" |
|
budget |
integer | 60000 | character budget for the payload; at least 2000 |
{
"brain": { "slug": "support", "…": "…" },
"how_to": "Read instructions_markdown (BRAIN.md) first and follow it. …",
"instructions_markdown": "# Support\n\n…",
"index_markdown": "# Support: index\n\n…",
"skills": [ { "path": "skills/reply-style.md", "title": "Reply style", "markdown": "# Reply style\n\n…", "from_brain": "support" },
{ "path": "skills/brand.md", "title": "Brand", "markdown": "…", "from_brain": "brand" } ],
"workflows": [ { "path": "workflows/weekly-digest.md", "title": "Weekly customer digest",
"summary": "The Monday summary of open escalations.", "triggers": ["weekly digest"], "from_brain": "support" } ],
"memory": [ { "path": "memory/customers/acme.md", "title": "Acme", "updated_at": "…", "excerpt": "# Acme\n\nRenews in March…" } ],
"recent_notes": [ { "id": 311, "ts": "…", "topic": "acme", "text": "Acme wants the invoice split by team.", "actor": "ada@acme.com" } ],
"sources": [ { "id": 5, "kind": "folder", "target": "general:Support", "label": "general/Support", "note": "Policies and macros.", "exists": true } ],
"source_index": [ { "doc_id": "01J9…", "title": "Refund policy", "alias": "refund-policy", "kind": "markdown", "root": "general",
"folder": "Support/Policies", "tags": ["customer-facing"], "updated_at": "…", "via": ["folder:general:Support"] } ],
"includes": [ { "slug": "brand", "name": "Brand" } ],
"includes_withheld": 0,
"pending_proposals": 1,
"consolidation": { "needed": false, "unconsolidated_chars": 8120, "unconsolidated_notes": 14, "budget": 40000, "hint": "No consolidation needed." },
"truncated": []
}
skills carry the Agent's own guidance followed by included guidance in include order; earlier entries take precedence on conflict. workflows describe jobs the agent may recognize; read the recipe with brain_read. memory provides excerpts and file paths, and recent_notes provides recent unconsolidated notes. source_index lists only documents the caller may open, with root ("~" for a personal document) and via, the matching sources; read them with get_file. includes_withheld counts included Agents unavailable to the caller.
The payload may omit excerpts or bodies to fit the requested budget. Check truncated; a skill with markdown: null and truncated: true needs a follow-up brain_read. If the source index is shortened, source_index_total gives its full count. Do not treat an omitted body as an empty file. The load is recorded in the Agent's journal. Prompt: "Load the support agent, then answer…"
brain_read
| Parameter | Type | Required | Notes |
|---|---|---|---|
brain |
string | yes | |
path |
string | yes | BRAIN.md, INDEX.md, skills/<name>.md, workflows/<name>.md or memory/<topic>.md |
Returns {brain, path, name, type, protected, doc_id, title, version, bytes, updated_at, updated_by, markdown, can_write, write_mode}. type is instructions, index, skill, workflow or memory. write_mode is direct for the index and memory and proposal for protected files. Pass version as base_version when writing the file back. Reads are recorded in the journal. Source documents are not agent files; read those with get_file.
brain_write
Write a whole file; creates it when new.
| Parameter | Type | Required | Notes |
|---|---|---|---|
brain |
string | yes | |
path |
string | yes | an agent path, as for brain_read. Segments use letters, digits, ., _ and -; skills/ and workflows/ hold files directly; memory/ may nest four folders deep |
content |
string | yes | the complete markdown |
message |
string | no | one line on what changed and why; shown in history, the journal and the proposal |
base_version |
integer | no | the version from brain_read; when the file has moved on the call fails with a 409 and a diff instead of overwriting |
For INDEX.md and memory/ the file is saved at once as a new version (write access needed) and the call returns {brain, mode: "direct", written: true, changed, path, type, version, …}; changed is false when the content was identical. For a protected path the same call files a proposal and returns {brain, path, type, mode: "proposal", proposal, note}; the file is not live yet, and agents are told to say so and not to retry. Prompt: "Write up what we learned about the billing migration in the support agent."
brain_remember
Append one note to the journal: a durable fact, decision, preference or correction.
| Parameter | Type | Required | Notes |
|---|---|---|---|
brain |
string | yes | "personal" for facts about the user |
note |
string | yes | at most 4,000 characters |
topic |
string | no | a short label that groups notes, up to 120 characters |
Needs write access. Returns {event, consolidation}: the journal event (see brain_log) and the consolidation status. Prompt: "Remember that Acme renews in March."
brain_search
| Parameter | Type | Default |
|---|---|---|
q |
string | required |
brain |
string | "personal" |
limit |
integer | 10, at most 50 |
Searches the Agent's files, included skills/workflows and source documents the caller may open. Returns relevant keyword/text matches and matching INDEX.md lines. Each result includes a snippet and read_with, the tool call to retrieve its content. This is not a semantic-search endpoint.
{
"brain": "support", "query": "refund window", "count": 2,
"index_hits": [ { "line": 14, "text": "- memory/refunds.md: refund window, exceptions, who approves" } ],
"results": [
{ "doc_id": "01JA…", "path": "memory/refunds.md", "score": -3.2, "snippet": "…the refund window is 30 days…", "snippet_html": "…",
"title": "Refunds", "retriever": "fts", "where": "brain", "from_brain": "support", "type": "memory",
"read_with": "brain_read{brain: 'support', path: 'memory/refunds.md'}" },
{ "doc_id": "01J9…", "path": "source.md", "score": -2.1, "snippet": "…", "snippet_html": "…",
"title": "Refund policy", "retriever": "fts", "where": "source", "alias": "refund-policy", "kind": "markdown",
"folder": "Support/Policies", "url": "https://…/d/…/", "read_with": "get_file{doc_id: '01J9…', path: 'source.md'}" }
]
}
Lower score is better. retriever is fts or grep. Searches are recorded in the journal. Prompt: "What does the support agent say about refunds?"
brain_activity
Local preview: factual event totals by UTC month and day, newest period first. Requires read access to the Agent. Counts cover the complete matching period, independently of how many event details a client has loaded.
| Parameter | Type | Default | Notes |
|---|---|---|---|
brain |
string | required | Agent slug or personal |
kinds |
string list | all | Same event kinds as brain_log |
before_month |
string | none | Exclusive month cursor, YYYY-MM |
limit |
integer | 6 | Number of months, at most 12 |
Returns {brain, timezone: "UTC", months, next_before_month}. Each month is
{month, count, counts, days}; each day is {day, count, counts}. counts maps
event kinds to their counts. Empty periods are omitted. Pass next_before_month
as before_month to load older periods; null means no more matching history.
Keep the same kinds filter while paging.
For example, “What changed in the Delivery Agent this month?” can start with
brain_activity using kinds: ["write", "propose", "decide"], then read the relevant
events through brain_log. Totals describe recorded events; they are not generated
session summaries or a count of completed tasks.
brain_log
The journal, newest first: an agent's short-term memory.
| Parameter | Type | Default | Notes |
|---|---|---|---|
brain |
string | required | |
since |
string | inclusive ISO 8601 time or date | |
until |
string | exclusive ISO 8601 time or date | |
before_id |
integer | pagination cursor from next_before_id |
|
kinds |
string list | all | from note, load, read, write, search, propose, decide, consolidate |
limit |
integer | 50 | at most 500 |
unconsolidated |
boolean | false | with kinds: ["note"], the notes still waiting to be folded into memory |
Returns {brain, count, events, consolidation}. An event is {id, ts, kind, actor, topic, text, path, doc_id, diff, meta, consolidated, consolidated_at, memory_path}. A write carries a small unified diff, the commit message as text, and meta with version, added and removed. Prompt: "What did we change in the support agent yesterday?"
The response also includes next_before_id: pass it as before_id with the
same time/kind filters to read the next page. null means no more matching events.
To expand one UTC day, use since: "2026-09-29T00:00:00Z" and
until: "2026-09-30T00:00:00Z". A page's count is the number of events returned;
use brain_activity for the entire period's count.
brain_consolidate_mark
Finish a consolidation after the notes were folded into a memory file with brain_write.
| Parameter | Type | Required | Notes |
|---|---|---|---|
brain |
string | yes | |
event_ids |
integer list | yes | note ids from brain_log or load_brain |
memory_path |
string | yes | the file under memory/ that now holds their content; it must exist |
Returns {marked, skipped, memory_path, consolidation}. Marked notes stop counting against the journal budget and drop out of recent_notes; nothing is deleted. Needs write access.
brain_export
| Parameter | Type | Required |
|---|---|---|
brain |
string | yes |
Returns {brain, filename, files, chars, markdown}: the instructions, index, skills, workflows (included agents' files marked with their origin) and memory as one markdown document, for harnesses without MCP. A snapshot; edits to it do not flow back. The journal and the source documents are not part of it.
propose_change
File a proposal for a protected file, including a new skill or workflow. brain_write on a protected path does the same thing.
| Parameter | Type | Required | Notes |
|---|---|---|---|
brain |
string | yes | read access is enough |
path |
string | yes | BRAIN.md, skills/<name>.md or workflows/<name>.md |
content |
string | yes | the complete new text of the file |
message |
string | no | why; up to 512 characters |
base_version |
integer | no | as for brain_write |
Returns the proposal:
{
"id": 18, "brain": "support", "brain_name": "Support", "path": "BRAIN.md", "type": "instructions",
"new_file": false, "status": "pending", "message": "Tone rule contradicted the brand skill.",
"actor": "ada@acme.com", "created_by": "ada@acme.com", "created_at": "…",
"base_version": 4, "current_version": 4, "added": 3, "removed": 2, "hunk_count": 1,
"decided_by": null, "decided_at": null, "decision_note": null, "accepted_hunks": null, "result_version": null,
"link": "/app/agents/support?tab=review&proposal=18"
}
Everyone with write access to the agent is notified in the app. A proposal identical to the current file is refused, and a free-write path is refused with a pointer to brain_write. Prompt: "The tone rule in the support instructions is wrong, propose a fix."
list_proposals
| Parameter | Type | Default | Notes |
|---|---|---|---|
brain |
string | required | |
status |
string | pending |
pending, approved, partial, rejected, stale or all |
Returns {brain, status, count, proposals: [proposal], can_decide}. partial means some hunks were accepted; stale means the file changed and the proposal no longer applies, so read the file and propose again. can_decide reflects the user's write access; the decision itself is made by a person in the app.
create_brain
Workspace owners and admins only.
| Parameter | Type | Required | Notes |
|---|---|---|---|
name |
string | yes | up to 120 characters |
description |
string | no | up to 512 characters |
slug |
string | no | defaults to the name; personal, me, my, ~ and anything starting with personal- are reserved; shares one namespace with shared roots |
Creates the agent with a starter BRAIN.md and INDEX.md and returns the agent object. 409 when the slug exists. The personal agent needs no creation. Prompt: "Create an agent for the Q3 launch."
add_brain_source
| Parameter | Type | Required | Notes |
|---|---|---|---|
brain |
string | yes | write access needed |
kind |
root, folder, tag, doc |
yes | |
target |
string | yes | root: a shared root's slug, or ~ for personal documents. folder: <root slug or ~>:<folder path>, e.g. general:Support or ~:customers/acme. tag: the tag. doc: a document id or alias |
note |
string | no | a line for agents on what the source is for, up to 255 characters |
Returns {id, kind, target, label, note, exists}. Adding the same source twice returns the existing one. Agent files cannot be sources. Prompt: "Let the support agent read the Support folder in General."
Connected Agent tools
The Agent sessions and hosted work reference covers generic agent-assisted setup, hosted runs, private/shared sessions and knowledge proposals.
The relay accepts compatible remote MCP servers; it is not specific to Linear or
project management. An Agent owner or workspace admin can set up connection metadata
through the app or MCP; people sign in or add credentials securely in the app.
Paste a Claude/Codex setup command or configuration, or use manual setup.
Agents can prepare credential-free instructions with prepare_brain_connection_from_setup.
For API tokens, prepare_brain_connection_credentials returns a short-lived,
single-use curl receiver. Send the values locally through stdin or a JSON file;
the agent only needs the connection and key names. See the
secure connection setup guide.
Supported transports are Streamable HTTP and SSE; authentication can use OAuth
sign-in, bearer/API tokens, custom headers or no credential. Local command-based
servers and private-network gateways are not hosted by this connection flow.
See setup and reconnect behavior.
The Agent's access list controls use of its attached tools. The connection's account label tells you which linked account acts upstream; stored credentials are never returned. Workspace policy and the selected tool rules still apply to every action.
list_brain_connections
| Parameter | Type | Default |
|---|---|---|
brain |
string | "personal" |
Returns {connections, can_manage, policy, ai_configured, capabilities}. Each
connection includes its id, name, account_label, enabled state, current
revision, discovered tools and tool_rules. Tool entries include their name,
description and input schema. has_credentials indicates a stored credential;
it does not expose its value. can_manage describes the caller's management
permission. Agents can configure/discover connections using the
setup tools;
credential entry and tool-policy changes remain signed-in human controls.
Load the Agent first, select an enabled connection, and use the discovered schema to form the request. Tool names and argument fields depend on the connected server.
request_brain_tool_call
| Parameter | Type | Required | Meaning |
|---|---|---|---|
brain |
string | yes | Agent slug |
connection_id |
string | yes | ID from list_brain_connections |
tool |
string | yes | Exact discovered tool name |
arguments |
object | yes | Values matching that tool's input schema |
intent |
string | yes | What the user wants to accomplish and why this action is needed |
request_key |
string | yes | A unique UUID for this intended action; reuse only to recover that same request |
Returns a call record with id, brain, connection_id, tool, status,
decision, evidence, reviewed, created_at, expires_at and audit_url.
An executed call can include result. Evidence contains the reviewable intent and
policy assessment; it is not a transcript of hidden model reasoning.
| Decision | Client behavior |
|---|---|
allow |
The request may execute immediately; inspect status and any result |
review |
Execution is allowed with a visible review flag; show that flag to the user |
approval_required |
Wait for an authorized human decision; no upstream action has run yet |
deny |
Explain the restriction; do not retry with a different intent to bypass it |
An intended use does not replace permission. A request such as “compare the brief with this project” does not authorize creating issues unless the user also requested that action. If the response is interrupted, reuse the same request key and exact request to find its record. Changing the action requires a new request key.
get_brain_tool_call
Takes call_id (required string). Returns the caller's request, its current decision,
review status and audit link; a completed result is available to its original
requester. An authorized reviewer can inspect review information without receiving
another person's result.
Use this to resume after a pending approval. pending means waiting for review;
approved means ready for the original requester to execute. completed and
failed describe a recorded outcome. unknown means an interrupted call may have
affected the provider: reconcile with the provider before requesting another action.
execute_brain_tool_call
Takes call_id (required string). The original requester executes the approved,
unexpired action. Approval itself does not dispatch it. Fermium checks current
access and whether the approved request is still valid; changed permissions,
connection settings, instructions or policy can require a new request.
Admins and Auditors make decisions in the app. Workspace policy can require an independent approver, including when the requester is an admin or Auditor. Agents cannot approve their own actions through these tools.
Example: “Load the Delivery Agent. Compare the client brief with the connected project and draft the missing issues. Request their creation, then wait for the required approval.” The agent uses the discovered tools and reports the proposed work, sources, approval state and eventual result. The same individual tools remain available alongside optional hosted work.
Earlier agents
These tools work on agents of the earlier form: a curated member tree with a pinned instructions document and attached skill documents, which also drives the earlier form of the wiki. They keep working next to agents as agent memory. Three of them were renamed when the names list_brains, brain_search and create_brain moved to the new agents: they are now list_legacy_brains, legacy_brain_search and create_legacy_brain. Reading is open to every member; create_legacy_brain, set_brain_instructions, attach_skill and detach_skill need the workspace owner or admin role. Every payload includes only documents the user may open; skipped counts the rest. Agent limits by plan: Free 1, Team 5, Business and above unlimited.
The agent object:
{
"id": 2, "slug": "support", "name": "Support", "icon": "lucide:life-buoy", "version": 9,
"description": "How we answer customers: policies, macros, escalation.",
"is_default": false, "wiki_default": true,
"instructions": { "doc_id": "01J7…", "title": "Support brain instructions", "alias": "support-brain", "url": "https://fermiumusercontent.app/d/…/", "folder": "Brains/support", "updated_at": "2026-09-16T09:12:40+00:00" },
"skills": [ { "doc_id": "01J8…", "title": "Reply style", "alias": "reply-style", "url": "https://…/d/…/", "folder": "skills", "updated_at": "…", "position": 0 } ],
"all_workflows": true,
"workflows": [ { "id": 3, "slug": "all-hands-deck", "name": "All-hands deck" } ],
"member_count": 14, "entry_count": 17,
"created_at": "…", "updated_at": "…"
}
is_default marks the workspace's default agent; wiki_default says whether the wiki publishes this agent. instructions is null for an agent with no instructions document. all_workflows says whether the agent hands out every workflow in the workspace (the default) or only the picked set; workflows is the list it hands out either way. See Workflows.
list_legacy_brains
No parameters. Returns {brains: [brain], default, can_manage, limit}. default is the default agent's slug; limit is null when unlimited. The default agent is created on first use if the workspace has none. Prompt: "Which agents do we have?"
use_brain
Load an agent at the start of a task in its domain: the instructions, the skills, the workflows and an index of the members, in one payload.
| Parameter | Type | Default | Notes |
|---|---|---|---|
brain |
string | the default agent | slug or id |
include_index |
boolean | true | false when only the rules are needed |
max_chars |
integer | 60000 | character budget for the payload; at least 2000 |
{
"brain": { "slug": "support", "…": "…" },
"instructions_markdown": "# Support brain\n\n…",
"skills": [ { "doc_id": "01J8…", "title": "Reply style", "alias": "reply-style", "url": "https://…/d/…/", "markdown": "# Reply style\n\n…" } ],
"workflows": [
{ "id": 3, "slug": "all-hands-deck", "name": "All-hands deck", "description": "The monthly all-hands slide deck.",
"triggers": ["all hands deck", "monthly all-hands"],
"inputs": [ { "name": "month", "description": "The month the deck covers", "required": true, "example": "September 2026" } ] }
],
"index": [
{ "kind": "group", "label": "Getting started", "children": [
{ "kind": "doc", "label": "Refund policy", "doc_id": "01J9…", "alias": "refund-policy", "doc_kind": "markdown", "folder": "Policies", "tags": ["customer-facing"], "updated_at": "…", "url": "https://…/d/…/" }
]},
{ "kind": "folder", "label": "Runbooks", "folder": "Runbooks", "children": [ { "kind": "doc", "…": "…" } ] },
{ "kind": "tag", "label": "#macros", "tag": "macros", "children": [ { "kind": "doc", "…": "…" } ] },
{ "kind": "url", "label": "Status page", "url": "https://status.acme.com" }
],
"skipped": { "instructions": false, "skills": 0, "docs": 1 },
"truncated": [],
"search_hint": "brain_search{brain: \"support\", q: \"...\"} searches only this brain's documents …"
}
Skills come in their attached order. workflows lists the enabled workflows the agent hands out (every workflow in the workspace, or the picked set in order), each as {id, slug, name, description, triggers, inputs}: enough to recognise a job; the full recipe comes from use_workflow. Folder and tag members are expanded to the live documents under them; dividers and empty sections are dropped. When the payload exceeds max_chars the index is flattened to a list of documents and then shortened, then the last skills are cut or dropped, then the instructions are cut; truncated names each step taken. The workflow list is never trimmed. Agents are instructed to read instructions_markdown first, follow every skill, use index as the map of what exists, read a document with get_file(doc_id, "source.md"), and call use_workflow before doing a job that matches a workflow's triggers. Prompt: "Use the support agent, then answer…"
legacy_brain_search
Ranked full-text search inside one agent of the earlier form: its curated documents, the documents under its folder and tag members, its instructions and its skills, restricted to what the user may open.
| Parameter | Type | Default |
|---|---|---|
q |
string | required |
brain |
string | the default agent |
limit |
integer | 10 |
Returns {brain, query, count, results: [{doc_id, path, title, alias, kind, folder, url, snippet, …}]}, the fields of a ranked search result plus the document's alias, kind and folder. Agents are told to prefer it over search_docs when the question belongs to an agent's domain, then read the hit with get_file or grep_doc. Prompt: "What does the support agent say about refunds?"
create_legacy_brain
| Parameter | Type | Required | Notes |
|---|---|---|---|
name |
string | yes | up to 120 characters |
description |
string | no | one line, up to 512 characters, shown in listings |
slug |
string | no | defaults to the name; letters, digits and dashes |
icon |
string | no | icon grammar |
Creates the agent with a starter BRAIN.md already set as its instructions: a team-listed markdown document kept forever, in the folder Brains/<slug> with the alias <slug>-brain. Returns the agent object. Fails past the plan limit with a message naming the plan, and with a 409 when the slug exists. Prompt: "Create an agent called Sales enablement for the pricing and objection docs."
set_brain_instructions
| Parameter | Type | Required | Notes |
|---|---|---|---|
brain |
string | yes | slug or id |
doc_id |
string | yes | a markdown document's id or alias |
Points the agent at another instructions document; the previous BRAIN.md is unlinked, not deleted. A document that is not markdown is refused. An instructions document cannot be deleted while an agent uses it; delete_doc fails with a message naming the agent. Returns the agent object.
attach_skill
| Parameter | Type | Required | Notes |
|---|---|---|---|
brain |
string | yes | slug or id |
doc_id |
string | yes | a markdown document's id or alias |
position |
integer | no | index among the skills; appended when omitted. Earlier skills win on conflict |
Returns {brain, skill: {doc_id, title, alias, url, folder, updated_at, position}, skills: [...]}. Attaching a document that is already a skill returns it unchanged, or moves it when position is given. An agent holds at most 50 skills. Agents attach only what the user asked for. Prompt: "Attach the brand guidelines as a skill of the marketing agent."
detach_skill
| Parameter | Type | Required |
|---|---|---|
brain |
string | yes |
doc_id |
string | yes |
Returns {brain, doc_id, detached: true, skills: [...]}. The document itself is untouched.
Workflows
Background: Workflows. These tools work on workflows of the earlier form, defined on the Workflows page and handed out through use_brain. A workflow in an agent that agents load is a file, workflows/<name>.md: it is listed by load_brain and read with brain_read, and needs none of the tools below except duplicate_doc. A workflow of the earlier form is a recipe for one recurring job: a WORKFLOW.md steps document plus trigger phrases, inputs to ask for, templates to duplicate, skills to apply on top of the agent's, and output rules. Reading is open to every member; create_workflow, set_workflow_templates and set_workflow_skills need the workspace owner or admin role. Every recipe includes only documents the user may open; skipped counts the rest. Workflow limits by plan: Free 3, Team 25, Business and above unlimited.
The workflow object:
{
"id": 3, "slug": "all-hands-deck", "name": "All-hands deck", "icon": "lucide:presentation",
"description": "The monthly all-hands slide deck.",
"doc": { "doc_id": "01JA…", "title": "All-hands deck workflow", "url": "https://fermiumusercontent.app/d/…/", "alias": "all-hands-deck-workflow" },
"triggers": ["all hands deck", "monthly all-hands"],
"inputs": [
{ "name": "month", "description": "The month the deck covers", "required": true, "example": "September 2026" },
{ "name": "wins", "description": "Team wins to call out", "required": false, "example": null }
],
"output": { "kind": "html", "folder": "Leadership/All hands", "title_pattern": "All hands {month} {year}", "tags": ["all-hands"], "brain": "leadership", "listing": "team", "visibility": "workspace" },
"enabled": true,
"templates": [ { "doc_id": "01JB…", "title": "All-hands deck template", "url": "https://…/d/…/", "alias": "all-hands-template", "role": "deck", "kind": "html" } ],
"skills": [ { "doc_id": "01JC…", "title": "Deck rules", "url": "https://…/d/…/", "alias": "deck-rules" } ],
"use_count": 12,
"created_at": "…", "updated_at": "…"
}
doc is the steps document, created with the workflow in the folder Workflows/<slug> with the alias <slug>-workflow, team-listed and kept forever; it is edited like any markdown document and cannot be deleted while the workflow exists. output is null when the workflow leaves placement to the agent; its keys are kind (html, markdown, snippet or file), folder, title_pattern (text the agent follows; the app suggests {<input name>}, {date}, {month} and {year} placeholders), tags, brain (a slug), listing (private or team) and visibility (unlisted, password or workspace). enabled: false removes the workflow from what use_brain hands out; it still lists here.
list_workflows
| Parameter | Type | Required | Notes |
|---|---|---|---|
brain |
string | no | slug or id: only the workflows that agent hands out, in its order |
Returns {workflows: [workflow], can_manage, limit}; limit is null when unlimited. With brain, adds brain (the slug) and all_workflows. Disabled workflows are included with enabled: false. use_brain already lists an agent's workflows, so agents rarely need this on its own. Prompt: "Which workflows do we have?"
use_workflow
Load a workflow's recipe before doing a recurring job. Agents are told to call it when a request matches a workflow's triggers, or when the user names the workflow, and to call it once per job.
| Parameter | Type | Default | Notes |
|---|---|---|---|
workflow |
string | required | slug or id |
brain |
string | none | the agent the agent loaded; brings its skills and instructions |
include_templates |
boolean | true | false to leave the templates out |
max_chars |
integer | 60000 | character budget for the payload; at least 2000 |
{
"workflow": { "slug": "all-hands-deck", "…": "…" },
"brain": "leadership",
"steps_markdown": "# All-hands deck workflow\n\n## Purpose\n\n…",
"inputs": [ { "name": "month", "description": "The month the deck covers", "required": true, "example": "September 2026" } ],
"output": { "kind": "html", "folder": "Leadership/All hands", "title_pattern": "All hands {month} {year}", "tags": ["all-hands"], "brain": "leadership", "listing": "team", "visibility": "workspace" },
"templates": [
{ "doc_id": "01JB…", "title": "All-hands deck template", "kind": "html", "role": "deck", "url": "https://…/d/…/", "alias": "all-hands-template", "markdown": null, "files": ["index.html", "assets/logo.svg"] },
{ "doc_id": "01JD…", "title": "Speaker notes outline", "kind": "markdown", "role": "outline", "url": "https://…/d/…/", "alias": null, "markdown": "# Speaker notes\n\n…", "files": ["index.html", "source.md"] }
],
"skills": [
{ "doc_id": "01J8…", "title": "Chart formatting", "alias": "chart-formatting", "url": "https://…/d/…/", "source": "brain", "markdown": "# Chart formatting\n\n…" },
{ "doc_id": "01JC…", "title": "Deck rules", "alias": "deck-rules", "url": "https://…/d/…/", "source": "workflow", "markdown": "# Deck rules\n\n…" }
],
"instructions_markdown": "# Leadership brain\n\n…",
"skipped": { "steps": false, "instructions": false, "templates": 0, "skills": 0 },
"truncated": [],
"search_hint": "Duplicate a template with duplicate_doc{doc_id, title, folder} and fill the copy; publish to the output rules …"
}
steps_markdown is the WORKFLOW.md source. A markdown or snippet template carries its source in markdown; other kinds carry null and are copied by id. skills come with the agent's first, then the workflow's own, deduplicated, each marked with its source. instructions_markdown is the agent's BRAIN.md when brain is given, else null. When the payload exceeds max_chars the templates' text is cut from the last one back, then the skills, then the instructions, then the steps; truncated names each cut. Every call counts one use of the workflow and records workflow.used in the audit log. Agents are instructed to read the steps first, ask for every required input before starting, duplicate templates with duplicate_doc rather than starting from scratch or editing the template, keep the skills in front of them, and publish to output (move_doc, the title on the update tools, set_tags, set_listing, set_visibility; brain only when the user asked). Prompt: "Put together the all hands deck for October."
create_workflow
| Parameter | Type | Required | Notes |
|---|---|---|---|
name |
string | yes | up to 120 characters |
description |
string | no | one line, up to 512 characters |
triggers |
list of strings | no | up to 20 phrases of up to 120 characters; a comma-separated string is accepted |
inputs |
list | no | up to 30 of {name, description?, required?, example?}; required defaults to true; a bare string is a required input with that name |
output |
object | no | {kind?, folder?, title_pattern?, tags?, brain?, listing?, visibility?}; unknown keys are dropped, bad values refused |
slug |
string | no | defaults to the name; letters, digits and dashes |
icon |
string | no | icon grammar |
Creates the workflow with its starter WORKFLOW.md (sections Purpose, Inputs, Steps, Output, Checks) and returns the workflow object. Write the steps with update_markdown or edit_file on doc.doc_id, then attach templates and skills with the two tools below. Fails past the plan limit with a message naming the plan, and when the slug is already in use. Agents create only what the user asked for. Prompt: "Define an all-hands deck workflow that triggers on 'all hands deck', asks for the month and the three headline metrics, and publishes to Leadership/All hands."
set_workflow_templates
| Parameter | Type | Required | Notes |
|---|---|---|---|
workflow |
string | yes | slug or id |
templates |
list | yes | [{doc_id, role?}] in order; a bare string is a document id or alias with no role; [] clears |
Replaces the template list. Any document kind; role (up to 64 characters) says what the document is for inside the job: deck, outline, email. At most 20. Returns {workflow, templates: [{doc_id, title, url, alias, role, kind}]}. Prompt: "Make the Q3 deck the template of the all-hands workflow, role deck."
set_workflow_skills
| Parameter | Type | Required | Notes |
|---|---|---|---|
workflow |
string | yes | slug or id |
doc_ids |
list of strings | yes | markdown document ids or aliases, in order; [] clears |
Replaces the workflow's own skills, applied after the agent's when the workflow runs. Markdown documents only; at most 20. Returns {workflow, skills: [{doc_id, title, url, alias}]}. Prompt: "Add the deck rules as a skill of the all-hands workflow."
duplicate_doc
Copy a document the user may open into a new document they own. This is how an agent starts from a template.
| Parameter | Type | Required | Notes |
|---|---|---|---|
doc_id |
string | yes | id or alias of the source |
title |
string | yes | the copy's title |
folder |
string | no | defaults to the source's folder |
tags |
list of strings | no | defaults to none; the source's tags are not copied |
brain |
string | no | place the copy in this agent's first group; only when the user asked |
The copy has the same kind and files as the source's current version (a markdown template keeps its source and is re-rendered with the new title; an HTML deck keeps its pages and assets), the source's retention, no alias, restricted visibility and private listing. Returns the document object plus source_doc_id, and nav with the placement when brain was given. Counts against the publish quota and records doc.duplicated in the audit log. Agents then edit the copy with update_markdown, update_html or edit_file, never the template. Prompt: "Start from the deck template and call it All hands October 2026."
Agent members and the wiki
Background: Wiki. These tools edit the member tree of an agent of the earlier form, which is the left sidebar of the earlier agent wiki. To build a wiki today, publish a folder with set_folder_meta instead; the tool and payload names keep the older word navigation for the tree. Reading is open to every member; every tool that writes needs the workspace owner or admin role. Agent limits by plan: Free 1, Team 5, Business and above unlimited.
list_navigations
No parameters. Returns {navigations: [{id, slug, name, icon, version, entry_count, is_default, created_at, updated_at}], can_manage, limit}. limit is null when unlimited.
get_navigation
| Parameter | Type | Required | Notes |
|---|---|---|---|
nav |
string | no | slug or id; omit for the agent the wiki publishes, else the workspace's default agent |
Returns the tree as the editor sees it: every entry with id, kind (group, doc, folder, tag, url, divider), label, target, icon, icon_url, visibility, group, collapsed, show_sign_in_hint, sort, position, children, and broken: true when a document target no longer resolves. Document nodes add doc_id, title, alias, ref, url, doc_kind, doc_visibility, folder, tags, updated_at and label_is_custom. Folder and tag entries come expanded with their live documents under children with dynamic: true. The top level carries version, which set_navigation expects back, and orphans: documents inside a folder or tag a dynamic entry covers that have no curated entry of their own, each with doc_id, title, alias, ref, folder, tags, kind, icon, url, updated_at and via.
create_navigation
| Parameter | Type | Required | Notes |
|---|---|---|---|
name |
string | yes | up to 120 characters |
slug |
string | no | defaults to the name; letters, digits and dashes; appears in /w/~<slug>/ |
icon |
string | no | icon grammar |
Creates an agent of the earlier form with an empty tree and no instructions document; create_legacy_brain does the same and adds the starter BRAIN.md. Returns the navigation object. Fails past the plan limit with a message naming the plan, and with a 409 when the slug exists.
delete_navigation
| Parameter | Type | Required |
|---|---|---|
nav |
string | yes |
Deletes the agent: its member tree and skill links go, and its instructions document is unlinked, not deleted. The workspace's default agent is refused. Documents are untouched; a wiki that published it falls back to none. Returns {navigation, deleted: true}.
set_navigation
Replace the whole member tree in one call, the natural shape for an agent that maintains an agent's members from a spec.
| Parameter | Type | Required | Notes |
|---|---|---|---|
nav |
string | yes | slug or id |
tree |
list of entries | yes | |
version |
integer | no | the version read with get_navigation |
An entry is {kind?, target?, label?, icon?, visibility?, group?, collapsed?, show_sign_in_hint?, sort?, children?, id?}:
targetuses the link grammar:doc:<id>, an alias,#tag(dynamic tag),folder:<path>(dynamic folder) orhttps://.... Kind is inferred from the target. An explicit document id that does not resolve is an error; an alias that does not resolve yet is allowed and renders broken until a document takes it.- An entry with a
labeland no target is a group and may carrychildren; only groups hold children.{kind: "divider"}draws a rule. - Groups and links need a
label; leave it off document entries so renames follow. visibility:public(default),signed_in, orgroupwithgroup: "<slug>"(Business and above;visibility: "group:<slug>"works too).sorton folder and tag entries:titleorupdated.- Entries that carry an
idfromget_navigationkeep their row, so icons set onnav:<id>survive; everything else is recreated, and rows not mentioned are removed.
Limits: 4 levels deep, 500 entries, labels up to 160 characters, link URLs up to 600 characters. If version does not match, nothing is written and the response is {conflict: true, status: 409, error, current: <tree>}; merge and retry. Otherwise returns the new tree as get_navigation would.
[
{ "label": "Getting started", "icon": "lucide:rocket", "children": [
{ "target": "welcome" },
{ "target": "doc:01J7ZK3N9QW0X4B2C6D8E0F1G2", "label": "Install the CLI" }
]},
{ "target": "folder:Runbooks", "label": "Runbooks", "sort": "updated", "visibility": "signed_in" },
{ "target": "#customer-facing", "icon": "lucide:briefcase/green" },
{ "kind": "divider" },
{ "label": "Resources", "children": [
{ "target": "https://status.acme.com", "label": "Status page" }
]}
]
Prompt: "Rebuild the docs agent's members from this outline."
add_nav_entry
| Parameter | Type | Required | Notes |
|---|---|---|---|
nav |
string | no | slug or id; omit for the default |
target |
string | no | as in set_navigation; omit for a group or divider |
parent |
string | no | an entry id or a group label such as Getting started; omit for the top level |
label |
string | no | required for groups and links |
icon |
string | no | |
position |
integer | no | index among siblings; appended when omitted |
visibility |
string | no | public, signed_in, group |
group |
string | no | group slug with visibility: group |
kind |
string | no | group or divider when there is no target; otherwise inferred |
sort |
string | no | title or updated on folder and tag entries |
Returns the entry {id, parent_id, position, kind, target, target_doc_id, label, icon, visibility, group, collapsed, show_sign_in_hint, sort} plus navigation and the new version. Prompt: "Add the on-call rotation under Runbooks in the wiki."
update_nav_entry
| Parameter | Type | Required | Notes |
|---|---|---|---|
nav |
string | yes | |
entry_id |
integer | yes | |
label |
string | no | "" drops a custom label so the document title shows again |
icon |
string | no | "" clears |
target |
string | no | must stay the same kind; to change kind, remove and add |
visibility, group |
string | no | |
collapsed, show_sign_in_hint |
boolean | no | |
sort |
string | no | "" resets |
Only the fields passed change. Returns the entry with navigation and version.
move_nav_entry
| Parameter | Type | Required | Notes |
|---|---|---|---|
nav |
string | yes | |
entry_id |
integer | yes | moves with its children |
parent |
string | no | entry id or group label; omit for the top level |
position |
integer | no | index among the new siblings; end when omitted |
An entry cannot be moved inside itself, and the result must stay within 4 levels.
remove_nav_entry
| Parameter | Type | Required |
|---|---|---|
nav |
string | yes |
entry_id |
integer | yes |
Removes the entry and any curated children under it. Documents are never touched. Returns {navigation, entry_id, deleted: true, version}.
publish_home
Turn the wiki on, or reconfigure it, and get its public URL. Read get_wiki_site first so a curator's settings are not overwritten. Fields left out keep their value; pass "" to clear one.
| Parameter | Type | Required | Default | Notes |
|---|---|---|---|---|
title |
string | no | the workspace name | up to 120 characters |
tagline |
string | no | up to 255 characters | |
navigation |
string | no | slug or id of the agent to publish | |
home_doc |
string | no | alias or document id; without one the root shows an overview of the tree | |
indexable |
boolean | no | only honoured with an active custom domain on a paid plan; the shared host is never indexed | |
domain |
string | no | hostname or id of an active custom domain of the workspace; a domain that is not active yet is refused | |
enabled |
boolean | no | true | |
footer_md |
string | no | markdown, up to 4,000 characters | |
accent |
string | no | a hex colour such as #2563eb |
Returns {workspace, enabled, title, tagline, footer_md, navigation, navigation_id, home_doc: {doc_id, title, alias, url}, logo_doc_id, domain, custom_domain_id, indexable, accent, url, updated_at}. url is https://fermiumusercontent.app/w/@<workspace>/ or https://<domain>/w/. Enabling the site exposes nothing the published agent does not reach. Prompt: "Turn on the wiki as Acme Docs with the handbook as the home page."
get_wiki_site
No parameters. The same payload as publish_home plus can_manage.
Links and aliases
Background: Links between documents.
set_alias
Give a document a stable human name, unique within the workspace, that [[alias]] and /x/alias links resolve to. Owner only.
| Parameter | Type | Required | Default |
|---|---|---|---|
doc_id |
string | yes | |
alias |
string | yes | lowercase slug, 1 to 64 characters; spaces and underscores become dashes |
force |
boolean | no | false; move the alias from the document that holds it |
Returns the document object plus link: "/x/<alias>". If another live document holds the alias and force is false, the error names the holder: alias 'q3-roadmap' already points at 'Q3 roadmap (old)' (01J7…); pass force=true to move it. Prompt: "Make q3-roadmap point at this document instead."
clear_alias
| Parameter | Type | Required |
|---|---|---|
doc_id |
string | yes |
Returns the document object. Existing [[alias]] links render visibly broken afterwards; prefer set_alias with force when a newer document takes over.
resolve_link
Check what a reference points at before writing it. ref is an alias, doc:<id>, a bare document id, or @<workspace-slug>/<alias>; [[ref]] and /x/ref spellings are accepted too. Bare aliases resolve in the user's workspace.
| Parameter | Type | Required |
|---|---|---|
ref |
string | yes |
{ "ref": "q3-roadmap/img/chart.png", "found": true, "doc_id": "01J7…", "title": "Q3 roadmap", "url": "https://fermiumusercontent.app/d/…/img/chart.png", "path": "img/chart.png", "visibility": "workspace", "alias": "q3-roadmap" }
Not found: {"ref": "...", "found": false, "detail": "no live document with that alias or id in this workspace; publish it first, or call set_alias on the doc that should own the name"}. Prompt: "Link to the roadmap from this page." The agent resolves first, then writes the link.
list_links
Outbound [[refs]] from a document the user owns, and every live document that links to it.
| Parameter | Type | Required |
|---|---|---|
doc_id |
string | yes |
{
"doc_id": "01J7…", "alias": "postmortem-2026-09",
"outbound": [{ "ref": "q3-roadmap", "path": null, "href": "/x/q3-roadmap", "resolved": true, "doc_id": "01J8…", "title": "Q3 roadmap", "alias": "q3-roadmap", "url": "https://…/d/…/" }],
"inbound": [{ "doc_id": "01J9…", "title": "Weekly notes", "alias": null, "url": "https://…/d/…/", "ref": "postmortem-2026-09", "path": null }]
}
Prompt: "What links here? I want to re-point the alias."
Finding and reading
list_docs
Your documents, newest-updated first.
| Parameter | Type | Default | Notes |
|---|---|---|---|
limit |
integer | 50 | capped at 500 |
offset |
integer | 0 | |
folder |
string | only documents directly in this folder; "" is the root; omit for every folder |
|
recursive |
boolean | false | with folder, include its subfolders |
tag |
string | only documents carrying this tag, in any folder | |
root |
string | "general" or a shared root's slug lists that root instead: what the user may open there |
folder and tag combine. Returns {"docs": [...]}. Prompt: "What is in Customers/Acme? Update the proposal instead of making a new one."
get_link
| Parameter | Type | Required |
|---|---|---|
doc_id |
string | yes |
Returns the document object. Prompt: "Give me the link to the roadmap again."
usage_guide
No parameters. Returns the full best-practices guide as markdown. Agents call this when unsure which tool fits.
Folders
A folder is where a document lives, exactly one path. Tags are separate: a topic that cuts across folders (see set_tags). Stars have no MCP tool; they are personal shortcuts set in the app. Background in Folders, tags, and stars.
A folder object looks like this:
{ "path": "Customers/Acme", "name": "Acme", "parent": "Customers", "doc_count": 3, "explicit": true }
doc_count counts documents directly in the folder, not in subfolders. explicit is true when the folder was created on its own (with create_folder, or left behind by a rename); folders implied by a document's path are listed too, with explicit: false.
move_doc
Move a document into a folder, and optionally into another root. The URL and every link to it stay the same; only its place in the tree changes. The destination is created if needed. Returns the document object with its new folder and root.
| Parameter | Type | Required | Notes |
|---|---|---|---|
doc_id |
string | yes | |
folder |
string | yes | "" for the top of the root |
root |
string | no | omit to stay in the current root. "general" or a shared root's slug hands the document to that root; "~" takes it back into My documents |
In My documents only the owner moves a document. Into a shared root: the owner, with edit on the target folder; the document then belongs to the root and stays when its author leaves. Inside or between shared roots: edit on the document and on the target folder. Back to My documents: the author, or a root manager, who then becomes its owner. Agent files cannot be moved with this tool.
Prompts: "Move the Q3 roadmap into Planning/2026." "Move my incident write-up into the Engineering root under Incidents."
list_folders
One optional parameter, root. Without it returns {"folders": [...]}: every folder you have in My documents, explicit or implied, with their ancestors, sorted by path case-insensitively. With a shared root's slug it returns {"root", "folders"} for that root: only folders the user can see, each also carrying index_doc_id, view_mode, published, my_level, root, shared and restricted (the folder has its own access list). Agents use it to pick where a new document belongs before publishing. Prompt: "Where do I keep customer proposals?"
create_folder
Create an empty folder. Publishing into a path creates it implicitly, so this is only needed for an empty folder that should exist now. Returns the folder object.
| Parameter | Type | Required | Notes |
|---|---|---|---|
path |
string | yes | |
root |
string | no | a shared root's slug to create it there; needs edit |
rename_folder
Rename or move a folder and everything under it: documents, subfolders and stars. Document URLs do not change. Renaming to an existing path merges into it. Refused: renaming the root, an empty new_path, and moving a folder inside itself. Returns the folder object at new_path.
| Parameter | Type | Required |
|---|---|---|
path |
string | yes |
new_path |
string | yes |
Prompt: "Rename Clients to Customers."
delete_folder
Delete a folder. Without force it must be empty (no documents, no subfolders) and the call fails with folder is not empty; move its contents first or pass force=true. With force, its documents and subfolders move to the parent folder (the root for a top-level folder) and then the folder is removed. Documents are never deleted by this tool. The root cannot be deleted.
| Parameter | Type | Required | Default |
|---|---|---|---|
path |
string | yes | |
force |
boolean | no | false |
Returns {"path": "...", "deleted": true}. Prompt: "Delete the Scratch folder but keep what is in it."
Roots and folder features
Background: Roots and folders. Every document lives in one root: the user's My documents ("~", the default everywhere), the workspace's General root ("general"), or a shared root addressed by its slug. A document in General or a shared root belongs to the root, not to its author.
The root parameter is accepted by publish_html, publish_markdown, publish_snippet, publish_file, move_doc, list_docs, list_folders, create_folder, list_tree, download_bundle, search_docs and list_changes. Omit it or pass "~" for My documents. An unknown root, or one the user cannot see, answers "root not found"; a root the user can see but not write to answers with the level they hold.
list_roots
No parameters. The roots this user can see: General always, other shared roots when the user holds any level inside them or manages the workspace.
{
"personal": { "slug": "~", "name": "My documents" },
"roots": [
{ "id": 1, "slug": "general", "name": "General", "kind": "shared",
"description": "Shared by everyone in the workspace.", "icon": null,
"is_general": true, "my_level": "edit", "doc_count": 42,
"can_manage": false, "published": false, "agent_write": false,
"created_at": "2026-09-17T08:00:00+00:00", "updated_at": "2026-09-17T08:00:00+00:00" },
{ "id": 4, "slug": "engineering", "name": "Engineering", "kind": "shared",
"description": "Designs, runbooks and incident reviews.", "icon": "lucide:wrench/blue",
"is_general": false, "my_level": "view", "doc_count": 17,
"can_manage": false, "published": true, "agent_write": true, "created_at": "…", "updated_at": "…" }
]
}
my_level is view, comment or edit at the top of the root (null when the user only reaches a folder deeper inside); writing needs edit. doc_count counts the documents the user may open. published says whether the top of the root is published as a site. agent_write says whether agents may publish or move documents into the root; when it is false, publishing there is refused with a 403 whose message says to publish into My documents instead, and an owner or admin turns it on in the root's settings. Prompt: "Which shared roots can I publish to?"
create_root
Workspace owners and admins only. Agents create a root only when the user asked for a new shared space.
| Parameter | Type | Required | Notes |
|---|---|---|---|
name |
string | yes | up to 120 characters |
slug |
string | no | defaults to the name; lowercase letters, digits and dashes, up to 64 characters. ~, personal, me, brain, brains, shared and r are reserved |
description |
string | no | up to 512 characters |
icon |
string | no | icon grammar |
Returns the root row shown under list_roots. The new root starts restricted to managers. Add its audience using share_root, Root settings → Access, or POST /api/roots/{root}/access. An empty list never opens the space to the workspace. Fails with 409 when the slug exists and with 400 past 200 shared roots. Prompt: "Create a shared root called Customer success."
Sharing and organizing shared spaces
| Tool | Parameters | Purpose |
|---|---|---|
list_root_access |
root, optional folder |
Inspect direct grants, inherited grants and whether the folder overrides its parent |
share_root |
root, subject, optional level="view", folder="" |
Managers grant user:<email>, group:<slug> or workspace access |
unshare_root |
root, grant_id, optional folder="" |
Remove an exact grant. Removing the last entry keeps the space/folder restricted |
inherit_folder_access |
root, folder |
Deliberately discard this folder's audience and restore its parent's permissions |
configure_root |
root, agent_write |
Managers explicitly enable or disable agent publishing and moves |
An omitted folder when publishing or moving into a shared root uses a stable Members/<member name> destination. An explicit project path wins; an explicit empty string targets the root itself. Member folders inherit access from the space; their names do not make them private. Renaming one preserves the member's default destination.
Prompt: “Create an Engineering space for the Developers group. Give them edit access and enable agent publishing. Put unfiled contributions in each member's default folder; use Projects/Launch when I name that project.”
get_folder_view
One folder the way the app's navigator shows it.
| Parameter | Type | Default | Notes |
|---|---|---|---|
root |
string | My documents | a shared root's slug, or "~" |
path |
string | "" |
"" is the top of the root |
{
"root": { "id": 1, "slug": "general", "name": "General", "kind": "shared", "is_general": true },
"path": "Handbook",
"crumbs": [ { "name": "General", "path": "" }, { "name": "Handbook", "path": "Handbook" } ],
"meta": { "icon": "lucide:book-open", "description": "How we work.", "index_doc_id": "01J7…",
"view_mode": "page", "child_order": ["d:01J7…", "f:Tools", "s:12"],
"published": true, "publish_visibility": "signed_in", "publish_group_id": null },
"index": { "doc_id": "01J7…", "title": "Welcome", "kind": "markdown", "url": "https://…/d/…/",
"html": "<article class=\"doc md\">…</article>", "toc": [ { "id": "welcome", "text": "Welcome", "level": 1 } ] },
"folders": [ { "name": "Tools", "path": "Handbook/Tools", "icon": null, "description": null, "doc_count": 6,
"shared": false, "has_index": true, "view_mode": null, "published": false, "my_level": "edit" } ],
"docs": [ { "doc_id": "01J7…", "title": "Welcome", "is_index": true, "mine": false, "owner_email": "ada@acme.com", "…": "…" } ],
"shortcuts": [ { "id": 12, "label": "Pricing FAQ", "folder": "Handbook", "target": { "doc_id": "01J9…", "…": "…" } } ],
"order": ["d:01J7…", "f:Tools", "s:12"],
"my_level": "edit",
"can_manage": false
}
For My documents root is {id: null, slug: "~", name: "My documents", kind: "personal"}. index is null when the folder has no index page; html is the rendered page for markdown and snippets, and null for an HTML page or a file share, which the app shows in a sandboxed frame from url. A folder's doc_count counts the documents under it that the user may open, and shared is true when the folder carries its own access list (in My documents: a folder grant). docs are document objects plus is_index, mine and owner_email. Shortcuts whose target is gone, or that the user may not open, are left out. order is the merged order of children as keys f:<folder name>, d:<doc_id>, s:<shortcut_id>. Use list_tree for several levels at once. Prompt: "What is in the Handbook folder of General?"
set_folder_meta
Folder settings. Only the arguments passed change; name keys in clear to reset them. Needs edit on the folder; the three publish keys need a workspace owner or admin (or the root's creator) in a shared root, and are always allowed in the user's own My documents.
| Parameter | Type | Notes |
|---|---|---|
path |
string | "" is the top of the root |
root |
string | a shared root's slug; omit for My documents |
icon |
string | icon grammar |
description |
string | up to 512 characters |
index_doc_id |
string | the folder's landing page: a document of the same root (in My documents, one of the user's own) |
view_mode |
grid, list, page |
the view the folder opens in |
child_order |
string list | keys f:<folder name>, d:<doc_id>, s:<shortcut_id>, at most 2,000; anything not listed follows |
published |
boolean | publish the folder and everything under it as a site |
publish_visibility |
public, signed_in, group |
who can read the site; inherited by subfolders unless they are published with their own setting |
publish_group_id |
integer | required with group; ids come from list_groups |
clear |
string list | keys to reset, e.g. ["index_doc_id"]; "published" unpublishes |
Returns the folder's settings: the meta fields above plus root, path, my_level and can_publish. A published folder is served at /w/@<workspace>/r/<root-slug>/<path>/ on the shared content host and at /w/r/<root-slug>/<path>/ on a custom domain; a tree in My documents uses ~<user id> as the root segment. Agents publish a folder only when the user asked: it can make documents publicly reachable. Prompts: "Make the welcome page the index of Handbook." "Publish the Handbook folder for signed-in readers."
create_shortcut
Show a document in a second folder without copying it.
| Parameter | Type | Required | Notes |
|---|---|---|---|
target_doc_id |
string | yes | any document of the same workspace that the user may open |
folder |
string | no | "" is the top of the root |
root |
string | no | a shared root's slug; omit for My documents |
label |
string | no | up to 160 characters; defaults to the document's title |
Returns {id, label, folder, root, target} where target is the document object. The key s:<id> places it in child_order. Needs edit on the folder. People who cannot open the target do not see the shortcut. Prompt: "Show the pricing FAQ in the Sales folder too."
Settings
Each returns the updated document object. Visibility, retention, listing and alias are owner-only; tags may also be changed by an editor.
set_visibility
| Parameter | Type | Required |
|---|---|---|
doc_id |
string | yes |
visibility |
restricted, unlisted, password, workspace |
yes |
password |
string | when password |
A workspace policy may restrict the allowed values; the error lists them. Prompt: "Password-protect it with harbor-42." or "Lock this to the company."
set_tags
Replaces the whole tag set; [] clears. Read the current tags from any response before adding one. Tags are topics that cut across folders; a tag view lists every document carrying the tag wherever it lives. Use move_doc to change where a document lives.
| Parameter | Type | Required |
|---|---|---|
doc_id |
string | yes |
tags |
string list | yes |
set_retention
| Parameter | Type | Required |
|---|---|---|
doc_id |
string | yes |
retention_days |
integer >= 0 | yes |
A workspace policy may set a minimum or maximum; out-of-range values are refused with the bound. Prompt: "Keep this one forever."
set_listing
| Parameter | Type | Required |
|---|---|---|
doc_id |
string | yes |
listing |
private, team |
yes |
Prompt: "List this for the whole team."
Sharing and comments
Background and the permission matrix: Members and roles. Individual and whole-workspace grants are available on every plan. Groups need Business or above; comments need any paid plan.
get_identity
No parameters. Returns {email, role, workspace: {id, name, slug}} for the connected account. Call this before working with files when the user has multiple accounts. It cannot switch accounts.
Access requests
| Tool | Parameters | Purpose |
|---|---|---|
request_access |
slug, optional message (up to 1,000 characters) |
Request view access to a known restricted link; existing workspace members only |
access_request_status |
slug |
Read this account's status and whether another request is allowed, without private document metadata |
list_access_requests |
doc_id |
Sharing managers list up to 100 requests, with pending requests first |
decide_access_request |
request_id, decision: "approve" | "deny" |
Sharing managers decide one request; approval grants this document's view permission only |
Requesting access sends notifications. Call request_access and decide_access_request only with the user's authorization. A repeated pending request reuses the existing request; decisions are single-use. Poll at most once every five seconds while pending, stop on a decision, and back off after errors. Requests expire after 30 days; denial has a 24-hour retry cooldown. An approved request does not restore a subsequently revoked grant.
Group and personal-folder tools
| Tool | Parameters | Purpose |
|---|---|---|
create_group |
name |
Workspace managers create a group (Business+) |
set_group_member |
group, email, optional present=true |
Managers add or remove an existing member; no invitation is sent |
share_folder |
folder, subject, optional level="view" |
Share a folder in My documents, including descendants |
list_folder_access |
folder |
Inspect direct grants on your personal folder |
unshare_folder |
folder, grant_id |
Remove an exact personal-folder grant |
Use share_root with folder for folders in shared spaces. All tools run as the connected account. An email mentioned in a prompt never switches the authenticated account.
list_access
Who can do what on a document the user can see, and what the user may do with it.
| Parameter | Type | Required |
|---|---|---|
doc_id |
string | yes |
{
"doc_id": "01J7…",
"owner": { "id": 3, "email": "ada@acme.com" },
"my_level": "edit",
"can_manage": true,
"narrowed": true,
"grants": [
{ "id": 12, "doc_id": "01J7…", "folder": null, "folder_owner_id": null, "subject_type": "group", "subject_id": 2, "subject": { "id": 2, "slug": "design", "name": "Design" }, "subject_label": "group:design", "level": "comment", "created_by": "ada@acme.com", "created_at": "2026-09-15T10:00:00+00:00", "inherited": false },
{ "id": 9, "doc_id": null, "folder": "Customers/Acme", "folder_owner_id": 3, "subject_type": "user", "subject_id": 7, "subject": { "id": 7, "email": "bo@acme.com" }, "subject_label": "user:bo@acme.com", "level": "edit", "created_by": "ada@acme.com", "created_at": "2026-09-14T09:12:00+00:00", "inherited": true }
]
}
my_level is null, view, comment or edit. Inherited grants come from the document's folder and can only be removed from the folder. Prompt: "Can I edit this, or should I ask Ada?"
share_doc
Grant a subject a level on a document. Document owner or a workspace admin.
| Parameter | Type | Required | Default |
|---|---|---|---|
doc_id |
string | yes | |
subject |
string | yes | user:<email>, group:<slug>, or workspace |
level |
view, comment, edit |
no | view |
Returns the grant object. Sharing an existing subject again changes its level. Grants authorize restricted links, app access and MCP access. They do not close an existing public/password link; use set_visibility with restricted for that. Prompt: "Share the proposal with the design group so they can comment."
unshare_doc
| Parameter | Type | Required |
|---|---|---|
doc_id |
string | yes |
grant_id |
integer | yes |
Returns {doc_id, grant_id, removed: true}. Folder grants cannot be removed here.
list_groups
No parameters. Returns {groups: [{id, name, slug, source, member_count, members: [{id, email, role}], created_at}]}. source is manual or scim. Any member may read; creating and editing groups is done in the REST API by owners and admins.
list_comments
| Parameter | Type | Required | Default |
|---|---|---|---|
doc_id |
string | yes | |
include_resolved |
boolean | no | false |
{
"doc_id": "01J7…", "title": "Q3 proposal", "open_count": 1, "can_comment": true,
"comments": [
{ "id": 31, "doc_id": "01J7…", "author": { "id": 7, "email": "bo@acme.com" }, "body": "The pricing table is a quarter old.", "path": "source.md", "anchor": "pricing", "resolved": false, "resolved_at": null, "created_at": "2026-09-15T11:20:00+00:00", "mine": false }
]
}
Prompt: "Any feedback on the proposal since I published it?"
A request posted with Ask an agent in the app arrives here as an ordinary open comment whose body is the instruction and whose path and anchor say where. Apply it with edit_file, apply_patch or write_file, then resolve_comment. See Editing in the browser.
add_comment
| Parameter | Type | Required |
|---|---|---|
doc_id |
string | yes |
body |
string | yes, up to 8,000 characters |
path |
string | no |
anchor |
string | no, a heading id or a line number |
Returns the comment object. Needs at least comment access, or membership on a team-visible document no grant has narrowed.
resolve_comment
| Parameter | Type | Required | Default |
|---|---|---|---|
comment_id |
integer | yes | |
resolved |
boolean | no | true; false reopens |
Returns the comment object. Prompt: "Fix what Bo pointed out, publish, then mark the comment resolved."
Policy, legal hold and audit
get_policy
No parameters. The workspace's policy and what the plan allows:
{
"allowed_visibilities": ["workspace", "password"],
"min_retention_days": null,
"max_retention_days": 365,
"secret_scan": "block",
"ip_allowlist": null,
"enabled": true,
"plan": "business",
"ip_allowlist_available": false,
"legal_hold_available": false,
"updated_at": "2026-09-15T09:00:00+00:00"
}
Agents call this when a publish is refused for policy reasons. enabled is false below Business, where a stored policy is not enforced. See Workspace policy.
set_legal_hold
Enterprise. Freeze a document against expiry, deletion and purge, or release it. The document's owner, or a workspace owner or admin.
| Parameter | Type | Required | Default |
|---|---|---|---|
doc_id |
string | yes | |
on |
boolean | no | true |
Returns {doc_id, title, legal_hold}. Prompt: "Put the Q2 contract under legal hold."
list_audit
Business and above. Recent audit events for the workspace, newest first; owners and admins see everything, members see events about their own documents.
| Parameter | Type | Required | Default |
|---|---|---|---|
action |
string | no | one action name such as doc.publish |
limit |
integer | no | 50, at most 200 |
Returns {events: [{id, action, actor, actor_user_id, target_type, target_id, workspace_id, meta, ip, created_at}]}. Action names are listed in Audit log. Prompt: "Who changed the visibility of the Acme proposal?"
Deleting
delete_doc
Soft-delete. The link stops working at once; content is removed after 7 days. Owner only; a document under legal hold is refused.
| Parameter | Type | Required |
|---|---|---|
doc_id |
string | yes |
Returns {"doc_id": "...", "deleted": true, "purge_at": "..."}.
Errors
Tool errors come back as MCP tool errors with a plain message, for example publish rate limit exceeded (120/hour); slow down and retry later, storage quota exceeded: 1 GiB on the free plan; ..., visibility 'workspace' (sign-in gate) needs a Team plan or above; upgrade at https://fermium.app/pricing, old_string matches 3 times in 'source.md'; add surrounding lines to make it unique, or pass replace_all=true to change all of them, workspace policy does not allow visibility 'unlisted'; allowed: workspace, locked by ada@acme.com until 2026-09-16T09:22:40+00:00, unknown lucide icon 'rockets'; call list_icons for the curated names, or only workspace owners and admins can edit navigations. Agents are expected to relay these. See Errors.