Docs Reference
MCP tools
What an agent connected to CoDNA is offered: 39 tools, each as tools/list describes it to the
model. How an agent connects, and what it is told when it does, are in MCP server.
Every tool acts as the person the agent signed in as, and sees only what they can.
whoami
Who am I acting for. Reads only.
Identify the person this session acts for. Everything you can see and do is bounded by their permissions, so this tells you whose knowledge base you are in.
No inputs.
Answers with email, name, groups, kind.
search
Search the knowledge base. Reads only.
Full-text search across every document this user can see, with typo tolerance. Start here before writing anything: extending an existing wiki page is almost always better than adding a second one on the same subject. Results never include documents private to other people. When more is true, call again with page set to next_page for the rest.
Inputs:
query(string, required): What to look for. Natural language works; typos are tolerated. Use * to list everything.space(string): Restrict to one space: raw, wiki, or output. Omit to search all.tags(string): Comma-separated tags. A document must carry all of them.path(string): Only documents in this folder or beneath it, for example engineering or wiki:engineeringlimit(integer): Results per page, default 10, at most 50.page(integer): Which page, from 1: pass next_page from the previous call
Answers with hits, found, more, next_page.
get_document
Read a document. Reads only.
Read a document by address or id. Returns a short header -- address, title, version, owner, whether you can edit, tags, its images and files -- then a blank line, then the markdown exactly as stored, so text you quote from it matches the page. For a long page, ask for the outline first, then read only the section you need. Keep the version: pass it as expected_version when replacing the whole page with update_document.
Inputs:
document(string, required): Address like wiki:engineering/onboarding, or a document idsection(string): Read only this section: a heading, with or without its #s. It runs to the next heading of the same or a higher level.outline(true or false): Read only the headings, with the line each starts on and how big its section is, instead of the page
get_file
Look at an image in a page. Reads only.
Look at an image a page shows: a screenshot, a diagram, a scanned form. A page keeps its images and PDFs in a folder beside it and shows them with ordinary markdown, such as ; get_document lists a page's files in its header. Pass the page and the link as the page writes it, or just the file's name, and an image comes back as an image you can see. An image larger than 1568 pixels on a side is scaled to fit first, which is all a model sees of it anyway. A PDF is described, not sent: a person opens it from the page. You see a file only if you can read its page, and a page that shows another page's image shows it only to people who can read that one.
Inputs:
document(string, required): The page the file is in, or the page whose link to it you are following: an address like wiki:engineering/onboarding, or a document idfile(string, required): The file's name, such as diagram.png, or its link exactly as the page writes it, such as onboarding.files/diagram.png or ../ops/deploy.files/flow.png
list_documents
Browse documents. Reads only.
List documents by space, folder, or tag, newest first. Use this to see how knowledge is organised before adding to it; use search when you know what you are looking for. A page holds at most 200: when more is true, call again with offset set to next_offset to get the rest.
Inputs:
space(string): raw, wiki, or outputpath(string): Folder, for example engineering or wiki:engineering. Includes its subfolderstags(string): Comma-separated tags; a document must carry all of themlimit(integer): Results per page, default 50, at most 200offset(integer): Where this page starts: pass next_offset from the previous page
Answers with documents, more, next_offset.
create_document
Create a document. Adds things, and removes nothing.
Create a new document. Search first: adding a second page on a subject that already has one makes the knowledge base worse, and extending the existing page is usually right. The new document is visible only to this person until it is shared.
The returned address is what other documents must link to. It comes from the title unless you pass an explicit slug, and a title rarely produces the address you assumed: 'CoDNA: Overview' becomes codna-overview, so a link written as ./overview.md will never resolve. When writing a set of documents that reference each other, either set slug on each, or create them first and write the links afterwards using the addresses you got back.
Inputs:
space(string, required): wiki for knowledge, output for task artifacts. raw is written only by ingestion and cannot be created here.title(string, required)content(string, required): Markdown body. Link to other documents with relative paths such as ./laptop-setup.md or ../raw/2026/09/handbook.md. A diagram goes in as a fenced code block marked mermaid; CoDNA draws it.path(string): Folder, for example engineering/onboardingslug(string): The last part of the address, controlling where links must point. Derived from the title when omitted -- a page titled 'CoDNA: Overview' lands at codna-overview, not overview. Set it explicitly when other documents link here.tags(string): Comma-separated tags
Answers with id, address, title, space, tags, version, content, owner, can_edit, created_at, updated_at.
update_document
Edit a document. Changes things, and may overwrite or remove them.
Replace a document's whole content, or its title or tags. To change part of a page -- a few words, a line, a section -- use edit_document instead, which sends only the change. When replacing content, read it first and pass expected_version, so an edit that raced with somebody else's is rejected instead of silently discarding their work. Give a change_summary: the history is what makes a knowledge base auditable. Documents in raw cannot be edited.
Inputs:
document(string, required): Address or idcontent(string): Replacement markdown body: the whole page. Omit to leave unchanged. To change part of a page, use edit_document. A diagram goes in as a fenced code block marked mermaid; CoDNA draws it.title(string)tags(string): Comma-separated; replaces the existing tagschange_summary(string): Why this edit was made. Shown in the document's history.expected_version(integer): The version you read. The edit is rejected if it changed since.append(string): Deprecated: use edit_document. Text to add at the end of the page, or of section.section(string): Deprecated: use edit_document. The heading append or section_content acts on.section_content(string): Deprecated: use edit_document. New content for everything under section.find(string): Deprecated: use edit_document. Exact text, occurring once, to replace with replace_with.replace_with(string): Deprecated: use edit_document. What replaces find; empty removes it.
Answers with id, address, title, space, tags, version, content, owner, can_edit, created_at, updated_at.
edit_document
Change part of a document. Changes things, and may overwrite or remove them.
Change a document without sending it back: a list of edits, each naming the exact text it changes or goes next to, applied in order and saved as one revision -- or, if any cannot apply, none of them. Put every change to a page in one call.
{op: replace, old, new} exact text, once (all: true for every one); new "" removes it
{op: insert_after, anchor, text} new lines after the line the anchor ends on
{op: insert_before, anchor, text} new lines before the line the anchor starts on
{op: append, text, section?} at the end of the page, or of a section
{op: replace_section, section, text} everything under a heading, keeping it
{op: delete_section, section} a heading and everything under it
Copy old and anchor from get_document's text exactly; give section to look only within one. If text is not found, the answer names the closest line; if it occurs more than once, the lines it is on. The answer is the diff of what changed, so there is no need to read the page again to check. dry_run shows the diff and saves nothing. A title or tags can change in the same revision. Raw cannot be edited.
Inputs:
document(string, required): Address or idedits(list of objects): Applied in order, each to the page as the ones before left it, and saved as one revision -- or, if any cannot apply, none of themop(string, required): replace, insert_after, insert_before, append, replace_section, or delete_sectionold(string): replace: the exact text to change, copied from the page. It must occur once, unless all is set.new(string): replace: what old becomes; empty removes it. A diagram goes in as a fenced code block marked mermaid; CoDNA draws it.all(true or false): replace: change every occurrence of oldanchor(string): insert_after and insert_before: exact text occurring once. The text goes on new lines after the line the anchor ends on, or before the line it starts on.text(string): insert_after, insert_before, append, replace_section: the markdown to put in, as written; end it with a blank line to leave one. A diagram goes in as a fenced code block marked mermaid; CoDNA draws it.section(string): A heading, with or without its #s. append adds to the end of this section (else of the page); replace_section and delete_section act on it; replace and insert_* look only within it.
title(string): A new title, in the same revisiontags(string): Comma-separated; replaces the tags, in the same revisionchange_summary(string): Why: shown in the document's historyexpected_version(integer): Refuse if the page is no longer this version. Usually leave it out: edits name the text they change, so they apply to the page as it is or fail loudly.dry_run(true or false): Show the diff these edits would make, and save nothing
revert_document
Undo changes to a document. Changes things, and may overwrite or remove them.
Bring back an earlier version's title, tags and content, as a new revision: the history keeps both what was undone and the undoing. Use it to undo an edit that went wrong; document_history lists the versions. The document stays where it is.
Inputs:
document(string, required): Address or idversion(integer, required): The earlier version to bring back, from document_historychange_summary(string): Why it is being undone
move_document
Move or rename a document. Changes things, and may overwrite or remove them.
Change a document's address. Its own relative links are rewritten in the same revision so they reach the same documents from the new address (except in raw, which is never rewritten). Links pointing at it keep working, but their written text becomes out of date, and the people who wrote those links are asked whether to update them. Move things sparingly for that reason.
Inputs:
document(string, required): Address or idto(string, required): New address, for example wiki:ops/deploy
Answers with id, address, title, space, tags, version, content, owner, can_edit, created_at, updated_at.
delete_document
Delete a document. Changes things, and may overwrite or remove them.
Delete a document. It is hidden everywhere and removed from search, but its full history is kept and anyone who could read it before can restore it. People whose documents link to it are asked what to do. Ask before deleting anything you did not just create.
Inputs:
document(string, required): Address or id
Answers with ok, message.
document_history
Document history. Reads only.
List how a document has changed: what kind of change, when, and why. Useful for answering whether knowledge is current and who last touched it.
Inputs:
document(string, required): Address or idlimit(integer): Maximum revisions, default 20
Answers with revisions.
backlinks
What links here. Reads only.
List the documents that link to this one, limited to those you can see. Tells you what depends on a page before you change or move it, and for a raw document, what has been distilled out of it.
Inputs:
document(string, required): Address like wiki:engineering/onboarding, or a document id
Answers with backlinks.
preview_share
Preview what sharing would include. Reads only.
Show what else a recipient is likely to need alongside what is being shared, found by following its links. Give one document, several (documents), or folders -- one preview covers the whole set, with counts for all of it and the pages it would add and the most linked-to suggestions listed; a folder's preview is what share_folder would share now. Grants nothing. Always run this before share_document or share_folder, show the person the suggestions and why each was suggested, and let them choose -- approving a list nobody explained is how people over-share.
Inputs:
document(string): Address or id of one documentdocuments(string): Comma-separated addresses or ids, to preview sharing them togetherfolder(string): Comma-separated folders as space:path, for example wiki:se/proposals: everything of yours in them and beneath them, previewed as one setrecipient(string, required): An email address, or group:slug for a teamlimit(integer): For several documents or folders: how many of the pages to be shared, and of the suggestions, to list. Default 25, at most 200; the counts always cover all of them.
Answers with document, recipient, already_has_access, through, documents, folder, skipped, truncated, suggestions, documents_total, documents_already_open, documents_more, suggestions_total.
share_document
Share documents. Adds things, and removes nothing.
Grant a person or team access to one or more documents. Run preview_share first and only pass what the person actually agreed to. Never share on your own initiative: access is the one thing here that cannot be quietly undone, because whoever gained it may already have read the document.
Inputs:
documents(string, required): Comma-separated addresses or ids: the document plus any suggestions the person acceptedrecipient(string, required): An email address, or group:slug for a teampermission(string): read (default) or writedeclined(string): Suggestions the person rejected. Recorded so they are not suggested again.
Answers with granted, skipped.
list_access
Who can see a document. Reads only.
List everyone a document is shared with -- people and groups, read or write, how each share was made and by whom -- besides its owner, who always can. Its owner and its editors may ask: anyone who can edit a document can share it. To go the other way, from a person to everything they were given, use shared_with.
Inputs:
document(string, required): Address or id
Answers with document, owner, grants.
shared_with
What a person was given. Reads only.
List what this person has given someone else -- a person or a group -- and through which shares: given to them directly, or to a group they are in, Everyone included. Their own documents, and other people's they shared as an editor. Answers "what does Theodor see of mine now?" in one call. Shares somebody else made on other people's documents are theirs to report.
Inputs:
person(string, required): An email address, or group:slugfolder(string): Only documents in this folder or beneath it, as space:path (wiki:se); a folder without a space is in wiki
Answers with person, documents, folders, truncated.
share_folder
Share a folder, now and from now on. Adds things, and removes nothing.
Share a folder of this person's with people or teams, standing: every document of theirs in it and beneath it is shared now, and every one they put there later -- by writing it there or moving it there -- is shared as it arrives. Only their own documents: a page someone else keeps in the same folder is theirs to share. Several folders and several recipients can go in one call. Run preview_share with the folder first and show the person what it covers and what it links to; never share on your own initiative. Sharing again changes the permission; unshare_folder ends it.
Inputs:
folder(string, required): Comma-separated folders as space:path, for example wiki:se/proposals. Each covers every folder beneath it; wiki: alone is the whole space.recipient(string, required): Comma-separated email addresses, or group:slug for a teampermission(string): read (default) or write
Answers with folder_shares, granted, skipped.
unshare_folder
Stop sharing a folder. Changes things, and may overwrite or remove them.
End a standing folder share: nothing put in the folder from now on is shared by it. What it already shared stays shared unless revoke_existing is set. Ask the person which they mean.
Inputs:
folder(string, required): The folder as space:path, as it was sharedrecipient(string, required): An email address, or group:slugrevoke_existing(true or false): Also take back what the folder share already shared. A document shared with them directly stays shared.
Answers with ok, message.
list_folder_shares
Which folders are shared, and with whom. Reads only.
List this person's standing folder shares: which folders, with whom, read or write. Each goes on sharing what they put in that folder.
No inputs.
Answers with folder_shares.
recent_access
What was shared with me recently. Reads only.
List documents this user was recently given access to. Worth checking at the start of a conversation: newly shared material is often exactly what they are about to ask about, and they may not know it arrived.
Inputs:
days(integer): How far back to look, default 14
Answers with granted.
pending_decisions
Links in my documents that need a decision. Reads only.
List what is waiting on this person's decision about links. Two kinds: links in their documents that point at something which moved or was deleted (resolve with update_link, keep_link to leave it as written -- a stale link still resolves to the right document -- or drop_link), and links someone proposed adding to a document they can edit (resolve with decide_link_suggestion). CoDNA never rewrites a document on its own, so these wait for a person. Worth checking when asked what needs attention.
No inputs.
Answers with decisions, link_suggestions.
update_link
Point a stale link at its target's new address. Changes things, and may overwrite or remove them.
Rewrite a link whose target moved, keeping the link text. This edits the document containing the link and produces a normal revision attributed to this user -- which is why CoDNA asks rather than doing it automatically on every rename.
Inputs:
link_id(string, required): From pending_decisions
Answers with ok, message.
drop_link
Remove a link, keeping its words. Changes things, and may overwrite or remove them.
Remove the link markup around a reference, leaving the text that was linked so the sentence still reads. Use when the target is gone for good and the mention still makes sense without a link.
Inputs:
link_id(string, required): From pending_decisions
Answers with ok, message.
keep_link
Leave a moved link as written. Adds things, and removes nothing. Repeating it changes nothing more.
Answer a link whose target moved by leaving it as it is. It still reaches the right document; only the address written in the text is out of date. Nothing in the document changes, and the link stops being listed by pending_decisions until its target moves again or is deleted. Not for links to deleted documents: those can only be dropped, or the document restored.
Inputs:
link_id(string, required): From pending_decisions
Answers with ok, message.
ingest_text
File source material into raw. Changes things, and may overwrite or remove them.
Store source material in the raw space, where it becomes citable evidence. Use this for anything given to you rather than written by you: a pasted email or document, a fetched page, a meeting transcript. Raw is immutable, so file it as it is -- do not summarise or tidy it first. Write the summary into wiki afterwards and link back to the address this returns. Always record where it came from in source.
Inputs:
title(string, required): What this is. Becomes the document title and its address.content(string, required): The material itself.source(string): Where it came from: a URL, an email subject, a person. Recorded with the document.format(string): markdown (default), html, or text.
Answers with address, title, id, warning.
list_ingestions
What this user has uploaded. Reads only.
List what this user has fed into raw, most recent first, including uploads that failed to convert. A failed ingestion produces no document, so this is the only way to answer "where did the file I uploaded go?". Recordings (audio and video) are listed from the moment they start arriving, with where their transcript stands: a recording's raw page is filed at once and gains the transcript as its next version.
Inputs:
limit(integer): Maximum rows, default 20.
Answers with ingestions.
create_automation
Register a script that keeps running. Changes things, and may overwrite or remove them.
Register JavaScript that runs on an event or a schedule, as the person you act for, for something continuing ("whenever a page is tagged runbook, share it with ops"). For something once, just do it. To share a folder, use share_folder. It can never do more than they could by hand. Register it disabled, show them the script and a run_automation dry_run, and enable it once they agree.
Script: one default-exported synchronous function:
export default function ({ event, doc, actor, recipient, codna, actions }) { }
event { type, at, id }; type is 'schedule.tick' on a schedule
doc the subject, frozen, absent on a schedule: { id, address, space, path,
slug, title, tags, status, version, content, createdBy, createdAt,
updatedAt }; address 'wiki:se/x', path the folder 'se'
actor who caused it; recipient who gained or lost access (access.* only):
{ id, email, name }
Reads, as the owner, seeing only what they can:
codna.find({ space, pathPrefix, tags, status, updatedBefore, updatedAfter, limit,
includeContent }) -> docs as above
codna.get(ref) -> doc or null codna.backlinks(ref) -> [{ id, address, title, space }]
Actions are PROPOSED, then checked against the owner's permissions:
actions.share(principal, 'read'|'write') email or 'group:ops'
actions.unshare(principal) actions.addTag(tag) actions.removeTag(tag)
actions.notify(principal, message) 'me' is the owner
actions.suggestLink(targetRef, rationale) actions.log(message)
Each takes a last { document: ref } to act on another document.
No fetch, require, process, Date, Math.random, setTimeout, eval or async: use event.at for the time. Per run: 100ms, 16MB, 50 actions, 20 reads.
Inputs:
name(string, required): Short name, shown to the owner in their list of automations.description(string): What this does and why, in a sentence, for whoever reads it in six months.script(string, required): The JavaScript. One default-exported synchronous function.triggers(string): Comma-separated event types for an event automation, for example document.created,document.updatedschedule(string): Five-field cron for a scheduled automation, for example '0 9 * * 1'. Give this or triggers, not both.timezone(string): IANA timezone for the schedule, for example Asia/Tbilisi. Defaults to UTC.scope_space(string): Only fire for documents in this space.scope_path(string): Only fire for documents in this folder or any folder beneath it: 'se' covers se/proposals, never se-old. Every scope given must match.scope_tags(string): Comma-separated; only fire for documents carrying all of them. Much cheaper than checking inside the script.enabled(true or false): Start it running. Default false: register it, show the person what it does, then enable it.
Answers with id, name, description, kind, enabled, triggers, schedule, timezone, scope, next_run_at, owner, disabled_reason, disabled_at.
list_automations
What is running for this person. Reads only.
List the automations this person owns, whether each is enabled, and what fires it. Check here before writing a new one: an automation that duplicates an existing one is two scripts fighting over the same documents.
Inputs:
limit(integer): Maximum results, default 50.
Answers with automations, available_triggers.
automation_runs
Why an automation is not doing what was expected. Reads only.
Show an automation's recent runs: what it proposed, what was applied, and what was refused and why. Start here whenever someone says a script "is not working" -- the usual answer is that its actions are being refused because the owner cannot share or edit the documents it is reaching for, which the run record states outright.
Inputs:
automation(string, required): The automation's id.limit(integer): Maximum runs, default 10.
Answers with runs.
get_automation
Read an automation. Reads only.
Read one of this person's automations in full: its script, what fires it, and its scope. Read it before changing it with update_automation.
Inputs:
automation(string, required): The automation's id, from list_automations.
Answers with id, name, description, kind, enabled, triggers, schedule, timezone, scope, next_run_at, owner, disabled_reason, disabled_at, script.
update_automation
Change an automation. Changes things, and may overwrite or remove them. Repeating it changes nothing more.
Change an automation in place: its script, triggers or schedule, scope, name, or whether it is on. Only what you give changes; it keeps its id and its run history. A change to an enabled automation takes effect at once, so show the person the change first -- or pass enabled: false, try it with run_automation dry_run, and enable it once they agree.
Inputs:
automation(string, required): The automation's id.name(string)description(string)script(string): The whole new script; the contract is in create_automation's description.triggers(string): Comma-separated event types. Makes it an event automation.schedule(string): Five-field cron. Makes it a scheduled automation.timezone(string)scope_space(string): Empty clears it.scope_path(string): A folder and every folder beneath it. Empty clears it.scope_tags(string): Comma-separated. Empty clears them.enabled(true or false): Left as it is unless given.
Answers with id, name, description, kind, enabled, triggers, schedule, timezone, scope, next_run_at, owner, disabled_reason, disabled_at, script.
run_automation
Run an automation now. Changes things, and may overwrite or remove them.
Run an automation by hand, disabled or not: on one document, or on every document already in its scope (automations otherwise only act on what happens after they are enabled), or -- for a scheduled one -- as a tick. With dry_run it runs the script and checks every action it proposes, and applies nothing: use that to show the person what a new or changed automation will do before it is enabled. A real run is recorded like any other and acts as its owner.
Inputs:
automation(string, required): The automation's id.document(string): Run it on this one document, as if its trigger had fired for it.all_in_scope(true or false): Run it on every existing document its scope matches -- what it would have done to them had it been running when they were written. A page at a time; see more.dry_run(true or false): Report what it would do, change nothing and record nothing.event(string): Which of its triggers the script is told fired; its first by default.limit(integer): Documents per page with all_in_scope, at most 200.offset(integer): Where the page starts: next_offset from the previous call.
Answers with dry_run, event, runs, more, next_offset.
set_automation_enabled
Turn an automation on or off. Changes things, and may overwrite or remove them. Repeating it changes nothing more.
Enable or disable an automation without losing it or its history. Enable only when the person has seen what the script does and agreed to it.
Inputs:
automation(string, required): The automation's id.enabled(true or false, required)
Answers with id, name, description, kind, enabled, triggers, schedule, timezone, scope, next_run_at, owner, disabled_reason, disabled_at.
delete_automation
Remove an automation. Changes things, and may overwrite or remove them.
Delete an automation and its run history. Prefer disabling it: a disabled automation can be read, explained, and turned back on, and its history still says what it did.
Inputs:
automation(string, required): The automation's id.
Answers with deleted.
list_groups
Teams that can be shared with. Reads only.
List the groups in this organisation and which of them this person belongs to. Use it to resolve a name someone says out loud -- "share this with the ops team" -- into the 'group:<slug>' that share_document takes, and to check how many people that actually is before proposing it. 'everyone' is the whole organisation. Creating groups and changing who is in them is done by the organisation's managers, not here.
No inputs.
Answers with groups, my_groups.
related_documents
Documents worth linking from this one. Reads only.
Suggest documents that probably belong linked from this one, by shared tags and by co-citation -- documents other pages already cite alongside it. Read the candidates before proposing any: shared tags alone are weak evidence, and a link is only worth proposing if a reader of this document would actually want to follow it. Documents already linked, and pairs already proposed or rejected, are left out.
Inputs:
document(string, required): Address or id of the document to find links forlimit(integer): Maximum candidates, default 10
Answers with candidates.
propose_link
Suggest a link, for a person to decide. Changes things, and may overwrite or remove them.
Propose adding a link from one document to another. Nothing changes yet: the proposal waits for whoever can edit the source to accept or reject it, and accepting adds an ordinary markdown link to the document. Use this rather than editing a link in yourself when the document is not one you were asked to change. Give a rationale a person can judge in one read. A rejected link is never proposed again, so do not retry one that comes back as rejected.
Inputs:
from(string, required): The document the link would be added toto(string, required): The document it would point atrationale(string, required): Why a reader of 'from' would want to follow it. Shown to the person deciding.
Answers with suggestion_id.
decide_link_suggestion
Accept or reject a proposed link. Changes things, and may overwrite or remove them.
Record the person's decision on a proposed link. Only call this when they have said yes or no -- proposing is your job, deciding is theirs. Accepting adds the link under a Related heading in the source document, as an ordinary edit with its own revision. Rejecting is permanent: the same link will not be proposed again.
Inputs:
suggestion_id(string, required): From pending_decisionsaccept(true or false, required): true adds the link to the document; false rejects it for good
Answers with status.