Source
Documentation

REST API

usesource://usesource/docs/api.md

Every capability of Source is one operation — defined once, exposed identically as an MCP tool and a REST call. The reference below is generated from that registry, so it is always the live surface.

Conventions#

Authentication. Send an API key as a Bearer token on every request:

curl -X POST https://usesource.dev/api/v1/ops/write_file \
  -H "Authorization: Bearer src_live_..." \
  -H "Content-Type: application/json" \
  -d '{"libraryId": "LIB_ID", "path": "/decisions/firestore.md", "content": "# Why Firestore\n..."}'

Calling operations. POST https://usesource.dev/api/v1/ops/{name} with a JSON body matching the operation's parameters. GET https://usesource.dev/api/v1/ops lists every operation with its JSON Schema; GET https://usesource.dev/api/v1/me returns the authenticated principal.

Envelope. Success is {"ok": true, "result": ...}. Every failure is {"error": {"code": ..., ...}} with a matching HTTP status:

Status Code Meaning
400 invalid_input The body failed schema validation; issues lists why.
400 invalid_pattern A regex or glob was rejected (including ReDoS guards).
401 unauthenticated Missing or invalid Bearer token.
402 limit_exceeded Your tier says no — either a quota is full or the tier never held the capability; limit names which. The request was well-formed. Nothing was deleted; reads keep working.
403 permission_denied You can see the target but lack the verb; action and path say which.
404 not_found The target does not exist — or you may not read it.
404 section_not_found The file exists but the section ref resolved to nothing.
404 unknown_op No operation by that name.
500 internal Server fault; nothing about the cause is leaked.

No existence leak. Reads you are not permitted return 404 not_found, never 403 — an unauthorized caller cannot distinguish "hidden" from "absent". 403 permission_denied appears only where existence is already known, such as writing to a path you can read.

Validation. Every input is zod-validated on every surface. Paths are normalized (leading /, NFC, no dot segments); files end in .md; content is capped at 512 KiB per file.

Reference#

accept_invitation#

Accept an invitation to an org with the token from the invitation email's link (the last segment of /invitations/). You become a member of the org with the role the invitation offers; org-subject grants and the org's base permission apply to you from your next request. Returns the orgId and the role granted. An org has two roles: an owner manages the org — its members, their roles and its base permission — and may do everything on every library the org owns without a grant; a member holds the org's base permission on those libraries. Invitations are by email address only — never by principal id — and are the only way a person joins an org other than creating it. An invitation expires seven days after it is sent; inviting an address that already has a pending invitation to the org replaces it (the earlier one is revoked and a new link is mailed), which is how an invitation is resent. It is accepted only by an account whose email is the invited address — compared trimmed and case-insensitively — and verified. Any authenticated caller may call it, and it succeeds only for the account the invitation is for. Refused: a token naming no invitation (not_found); an invitation that is accepted, revoked or expired (invalid_input naming the state); an account whose email Firebase has not verified (invalid_input — verify it first); an account with another email, or none (invalid_input — the invitation is for another address); an account already a member of the org (invalid_input). Every refusal leaves the invitation pending, unless it had expired.

Permission: manage_org

Parameter Type Required Description
token string yes —

add_comment#

Add a comment anchored to a section of a markdown file (sectionRef: slug path, bare slug, ordinal, or '' for the top of the document), optionally pinned to a quoted text range. The anchor is stored in canonical form — the slug path of the section it named, or '' — and follows the section from then on: a write that renames or re-ranks the section re-points the comment, and one that removes it re-attaches the comment to the nearest surviving section and marks it 'displacedFrom'. A sectionRef that does not resolve is invalid_input. Takes no version: a comment is added beside the others and replaces none. Returns the comment, with its 'version' and 'writtenAgainst', the current document version. Each comment carries 'id', 'sectionRef' — the canonical slug path of the section it is anchored to, or '' for the top of the document — 'body', 'authorId', 'createdAt', 'replies' (oldest first, each with 'id', 'authorId', 'body' and 'createdAt'), 'writtenAgainst' — the document version the comment was written against, a record and never a precondition: compare it with the file's current version to see whether the text has changed since — 'displacedFrom' where set — the address it had when its section stopped existing and it was re-attached to the nearest surviving section — and 'version', the opaque string archive_comment requires. A comment's version covers its archived state and its replies, not its anchor. To add a comment in the same call as a write, use that write's 'comments'. Requires comment permission on the path. 'libraryId' takes the library's address, 'usesource:///' — the two segments list_libraries returns as 'address', and the first two segments of any 'address' a read hands back. A bare library id still resolves as a legacy pointer, and the response then carries 'libraryNotice' naming the canonical address to use instead. Three forms are refused as invalid_input: a 'source://' string (the scheme was renamed and is never rewritten for you), a one-segment 'usesource://' (a library slug is unique only within its owner, so the owner segment is never inferred from the caller), and a full document address (segments 1 and 2 are the library; everything after them belongs in the separate 'path' argument). An address naming no library you may read answers not_found, whether it does not exist or you hold no grant on it.

Permission: comment

Parameter Type Required Description
libraryId string yes —
path string yes —
sectionRef string yes —
body string yes —
textRange object no —

add_reply#

Reply to a comment on a markdown file: the reply is appended after the comment's other replies, which are kept in the order they were added. A reply has no anchor, no replies and no lifecycle of its own. Takes no version: a reply replaces nothing. Returns the whole comment as it now stands, with its new 'version', so you can archive it next without reading it again. Refused with invalid_input: a reply to an archived comment (the message says so), an empty body, a body over 4000 characters, a reply to a comment already holding 100 replies, and a reply that would make the stored comment exceed 524288 bytes — each message names the bound. A comment that does not exist on the file — including one left behind by a file deleted from this path — is not_found. Each comment carries 'id', 'sectionRef' — the canonical slug path of the section it is anchored to, or '' for the top of the document — 'body', 'authorId', 'createdAt', 'replies' (oldest first, each with 'id', 'authorId', 'body' and 'createdAt'), 'writtenAgainst' — the document version the comment was written against, a record and never a precondition: compare it with the file's current version to see whether the text has changed since — 'displacedFrom' where set — the address it had when its section stopped existing and it was re-attached to the nearest surviving section — and 'version', the opaque string archive_comment requires. A comment's version covers its archived state and its replies, not its anchor. Requires comment permission on the path. 'libraryId' takes the library's address, 'usesource:///' — the two segments list_libraries returns as 'address', and the first two segments of any 'address' a read hands back. A bare library id still resolves as a legacy pointer, and the response then carries 'libraryNotice' naming the canonical address to use instead. Three forms are refused as invalid_input: a 'source://' string (the scheme was renamed and is never rewritten for you), a one-segment 'usesource://' (a library slug is unique only within its owner, so the owner segment is never inferred from the caller), and a full document address (segments 1 and 2 are the library; everything after them belongs in the separate 'path' argument). An address naming no library you may read answers not_found, whether it does not exist or you hold no grant on it.

Permission: comment

Parameter Type Required Description
libraryId string yes —
path string yes —
commentId string yes —
body string yes —

archive_comment#

Archive (soft-remove) a comment by id. Archiving is a comment's one lifecycle: an archived comment leaves every default view and stays readable through list_comments with includeArchived. 'version' is required: send the 'version' that list_comments, read_file, read_section or add_reply returned for that comment. The version covers the comment's archived state AND its replies, so archiving one that gained a reply since you read it returns version_conflict saying it has changed — read its replies first — and archiving one another caller already archived returns version_conflict saying so, rather than not_found, which would be untrue of a comment you have just read and which still exists. The version does not cover the comment's anchor: a write that re-pointed or re-attached the comment leaves your version valid. Requires comment permission on the path, plus one of three grounds: being the comment's author, holding write permission on the path — so the agent that fixed the text can close the comment about it — or holding manage_grants there. A caller meeting none is refused before the version is compared. To fix the text and archive the comment in one call, use the write's 'comments'. 'libraryId' takes the library's address, 'usesource:///' — the two segments list_libraries returns as 'address', and the first two segments of any 'address' a read hands back. A bare library id still resolves as a legacy pointer, and the response then carries 'libraryNotice' naming the canonical address to use instead. Three forms are refused as invalid_input: a 'source://' string (the scheme was renamed and is never rewritten for you), a one-segment 'usesource://' (a library slug is unique only within its owner, so the owner segment is never inferred from the caller), and a full document address (segments 1 and 2 are the library; everything after them belongs in the separate 'path' argument). An address naming no library you may read answers not_found, whether it does not exist or you hold no grant on it.

Permission: comment

Parameter Type Required Description
libraryId string yes —
path string yes —
commentId string yes —
version string yes —

create_agent#

Create an agent identity attached to an owner; API keys can then be minted for it from the account page. owner defaults to the caller. Naming an org creates the org's agent, which only the org's owners may do; the org's agent becomes a member of the org with role member and holds the org's base permission. The new agent counts against its owner's agent limit, and its plan is its owner's. Requires no library permission.

Permission: manage_principals

Parameter Type Required Description
displayName string yes —
owner object no What the new agent is attached to: a principal or an org. Defaults to the caller.

create_folder#

Create a folder at path (must not end in .md). Missing parent folders are created. Takes no version: a create destroys nothing, and this one commits through a transactional create-if-absent on a deterministic id. Creating the folder makes any markdown link already written to that path resolve; the response carries no 'notices' field, because a folder holds no content to report on. Requires create_folder permission on the path. A library whose 'mode' is 'repository' reflects one GitHub repository and refuses this call for every caller, its owner included, with repository_bound — 409 on REST, the same code on MCP. It is a state of the library, not a permission, and it is decided before permissions and 'version' are checked. The error names where to make the change instead: 'repository' (owner/name), 'branch', 'path' (the library path, which is also the repository path) and 'url' (that path on GitHub). The three origin fields are omitted when the origin cannot be resolved; the call is refused all the same. A caller who may not read the path gets permission_denied instead. 'libraryId' takes the library's address, 'usesource:///' — the two segments list_libraries returns as 'address', and the first two segments of any 'address' a read hands back. A bare library id still resolves as a legacy pointer, and the response then carries 'libraryNotice' naming the canonical address to use instead. Three forms are refused as invalid_input: a 'source://' string (the scheme was renamed and is never rewritten for you), a one-segment 'usesource://' (a library slug is unique only within its owner, so the owner segment is never inferred from the caller), and a full document address (segments 1 and 2 are the library; everything after them belongs in the separate 'path' argument). An address naming no library you may read answers not_found, whether it does not exist or you hold no grant on it. Everything returned carries its canonical 'address' beside its 'path': a document's is 'usesource:///', a section's is that plus '#', and a library's is 'usesource:///'. The address is derived from the path returned beside it, so the two cannot disagree, and it can be pasted into a link or sent straight back as 'libraryId' (its first two segments).

Permission: create_folder

Parameter Type Required Description
libraryId string yes —
path string yes —

create_grant#

Grant a principal, org, or the public access to a library path prefix ('/' = whole library, '' = unscoped writer rule), via either a role (reader, writer, admin) or an explicit action list — provide exactly one of role or actions. Requires manage_grants permission on the path prefix. 'libraryId' takes the library's address, 'usesource:///' — the two segments list_libraries returns as 'address', and the first two segments of any 'address' a read hands back. A bare library id still resolves as a legacy pointer, and the response then carries 'libraryNotice' naming the canonical address to use instead. Three forms are refused as invalid_input: a 'source://' string (the scheme was renamed and is never rewritten for you), a one-segment 'usesource://' (a library slug is unique only within its owner, so the owner segment is never inferred from the caller), and a full document address (segments 1 and 2 are the library; everything after them belongs in the separate 'path' argument). An address naming no library you may read answers not_found, whether it does not exist or you hold no grant on it.

Permission: manage_grants

Parameter Type Required Description
libraryId string yes —
subject object \ object \ object
pathPrefix string yes —
role "reader" \ "writer" \ "admin"
actions array no —

create_library#

Create a new library, in one of two modes. Without 'repository', the library keeps its documents in Source: it is owned by 'owner' when given and by the calling principal when not, 'name' is required, and the returned library carries 'mode': 'source'. 'owner' is optional and takes the form {kind, id} — {kind: 'org', id} for an org, {kind: 'principal', id} for a person, the same shape a library summary's 'owner' carries; omitted, it is the caller. Only a holder of an org's owner role may create a library for that org: an org you are a member of but do not own, or do not belong to, is refused with permission_denied naming create_library, whatever the org's base permission, and so is a principal 'owner' other than yourself; an org that does not exist is refused with not_found. Nothing is created by a refused call. A library created for an org carries no grant — not even for you: the org's members read it through the org's base permission, its owners may do everything on it, and its address is under the org's owner slug. 'owner' together with 'repository' is refused with invalid_input before any call to GitHub. With 'repository' — a GitHub repository given as 'owner/name', e.g. 'acme/handbook', matched case-insensitively — the library reflects that repository: it is owned by the Source owner of the GitHub App installation that grants the repository (the person who claimed it, or the Source org it was claimed into — the caller must be that person, or hold that org's owner role), never by an owner you choose; it is bound to the repository's default branch, which is the only branch a library reflects; 'name' is optional and defaults to the repository's name without its owner; the returned library carries 'mode': 'repository'; and its first import from GitHub starts after this call returns. No operation binds or unbinds an existing library, and this one never modifies one. Refusals specific to 'repository': repository_not_granted (409) when no installation you may create a library for grants the repository on GitHub now — someone who may install the Source GitHub App must give it the repository there, which no call here can do; repository_already_bound (409) when the repository already has a library in Source, attached or detached — a detached one is re-attached by adding its repository back to the installation on GitHub, not by this call; and github_unavailable (503) when this deployment has no GitHub integration configured. Both 409 refusals carry 'repository' as you sent it and name no library. Everyone may create libraries they own, in either mode, and no count of libraries refuses a creation: what an owner's plan bounds is what its libraries cost — the storage charged to the owner as documents are written — never how many there are. Each library carries 'mode': 'source' when its documents live in Source, or 'repository' when it reflects one GitHub repository and refuses content writes with repository_bound. The mode is fixed when the library is created. A library in 'repository' mode also carries 'origin', where its documents live: 'repository' (the GitHub repository as 'owner/name', as currently named), 'branch' (the default branch it reflects), 'url' (the repository's web URL), 'commit' (the commit the library reflects, or null before its first import), 'state' ('attached' or 'detached'), and 'detachedBecause' (present only when detached: 'removed', 'uninstalled', 'deleted' or 'transferred'). 'origin' is null when the library's repository binding could not be read, and absent on a library in 'source' mode. A library created with 'repository' returns its origin attached, with 'commit': null, because its first import runs after this call returns. Everything returned carries its canonical 'address' beside its 'path': a document's is 'usesource:///', a section's is that plus '#', and a library's is 'usesource:///'. The address is derived from the path returned beside it, so the two cannot disagree, and it can be pasted into a link or sent straight back as 'libraryId' (its first two segments).

Permission: create_library

Parameter Type Required Description
name string no —
repository string no —
owner object \ object no

create_org#

Create an org; you become its first and only owner. The org is created on an org's Free plan with base permission read, and nothing of your own plan is copied onto it. Orgs can own libraries and be grant subjects. Any authenticated caller may call it, on any plan: no plan gates creating an org. What bounds it is how many orgs you already hold the owner role in — your plan's orgsOwned limit — and a call past it is refused with limit_exceeded naming 'orgsOwned' and your plan in force; no org is created.

Permission: manage_org

Parameter Type Required Description
name string yes —

delete_library#

Soft-delete an entire library; its contents become invisible to all ops. 'version' is required and is the subtree version list_tree returns when called with NO prefix — what a library delete destroys is the library's content, so that is the version it takes. A stale version returns version_conflict carrying the current one and the library stays live. The same bound folder operations carry applies: the subtree commits in chunks of 400 writes that are not atomic across chunk boundaries. 'force' is optional and defaults to false. Without it, the delete (links between this library's own documents die with it and never count; a link from another library always does) is REFUSED with the error code 'link_conflict' (409) when something in what it would delete is still the current target of a markdown link from a document that SURVIVES the delete — a link from inside the deleted set does not count, and a link from another library always does. The refusal names the linking documents you may read ('links', each with 'source' and 'destination'), counts every one in 'inboundLinks' whether or not you may read it, and states in 'disclosure' that there may be others you cannot see. It is decided AFTER the version precondition, so a stale version answers version_conflict first, and it commits nothing. With 'force': true the delete completes and 'notices' carries one 'inbound_link_broken' entry per link it broke, named under the same bound, with 'inboundLinks' and 'disclosure' beside it. Repairing the links first is the ordinary path; forcing is the deliberate one. At most 50 notices of each capped family — the link codes, the validity codes and the borrowed 'MD###' codes — are returned per response, each family counted separately, in the order they were found; consequence codes are never capped. When more conditions held than are listed, the response carries 'noticesTruncated': true and 'noticesBound', one sentence naming the cap and the families it cut; otherwise neither field is present. The cap changes nothing about what the operation did or stored — every condition still took effect — and it is distinct from 'bound', which folder operations use for their 400-write commit chunks. 'notices' is always present and is a list of objects, empty when there is nothing to report — never a list of strings. Each entry has 'code' (what happened, for branching), 'message' (the same thing in prose), and, where the code calls for them, 'ref', 'previousRef', 'line', 'info', 'destination' and 'source'. 'ref' is the address the notice is about IN THE DOCUMENT AS IT NOW STANDS ('' is the preamble's address); 'previousRef' is the address it had before this write; 'line' is 1-based in the written document; 'destination' is a link exactly as its author typed it; 'source' is the address of the document a link is written in, on the inbound link codes only. Branch on 'code' and those fields — never by parsing 'message'. A code's SHAPE says who minted it: a code of the form 'MD###' is markdownlint's own and means exactly what markdownlint's documentation for that rule says, and every other code is this system's, snake_case and never hyphenated. Only the 'MD###' codes carry 'info'. Requires delete permission on the library root, AND read permission covering the whole library: you may not delete a library part of which you cannot read. 'libraryId' takes the library's address, 'usesource:///' — the two segments list_libraries returns as 'address', and the first two segments of any 'address' a read hands back. A bare library id still resolves as a legacy pointer, and the response then carries 'libraryNotice' naming the canonical address to use instead. Three forms are refused as invalid_input: a 'source://' string (the scheme was renamed and is never rewritten for you), a one-segment 'usesource://' (a library slug is unique only within its owner, so the owner segment is never inferred from the caller), and a full document address (segments 1 and 2 are the library; everything after them belongs in the separate 'path' argument). An address naming no library you may read answers not_found, whether it does not exist or you hold no grant on it.

Permission: delete

Parameter Type Required Description
libraryId string yes —
version string yes —
force boolean no —

delete_node#

Soft-delete the file or folder at path; folders delete their entire subtree. Returns the number of nodes deleted. 'version' is required: for a file, the document version read_file returned; for a folder, the subtree version list_tree returned for that prefix. A token of the other kind, or a folder version observed over a different prefix, is invalid_input; a stale one is version_conflict carrying the current version. A folder delete commits in chunks of 400 writes that are not atomic across chunk boundaries, so above 400 nodes the version narrows the race without closing it — the response says so too. 'force' is optional and defaults to false. Without it, the delete is REFUSED with the error code 'link_conflict' (409) when something in what it would delete is still the current target of a markdown link from a document that SURVIVES the delete — a link from inside the deleted set does not count, and a link from another library always does. The refusal names the linking documents you may read ('links', each with 'source' and 'destination'), counts every one in 'inboundLinks' whether or not you may read it, and states in 'disclosure' that there may be others you cannot see. It is decided AFTER the version precondition, so a stale version answers version_conflict first, and it commits nothing. With 'force': true the delete completes and 'notices' carries one 'inbound_link_broken' entry per link it broke, named under the same bound, with 'inboundLinks' and 'disclosure' beside it. Repairing the links first is the ordinary path; forcing is the deliberate one. At most 50 notices of each capped family — the link codes, the validity codes and the borrowed 'MD###' codes — are returned per response, each family counted separately, in the order they were found; consequence codes are never capped. When more conditions held than are listed, the response carries 'noticesTruncated': true and 'noticesBound', one sentence naming the cap and the families it cut; otherwise neither field is present. The cap changes nothing about what the operation did or stored — every condition still took effect — and it is distinct from 'bound', which folder operations use for their 400-write commit chunks. 'notices' is always present and is a list of objects, empty when there is nothing to report — never a list of strings. Each entry has 'code' (what happened, for branching), 'message' (the same thing in prose), and, where the code calls for them, 'ref', 'previousRef', 'line', 'info', 'destination' and 'source'. 'ref' is the address the notice is about IN THE DOCUMENT AS IT NOW STANDS ('' is the preamble's address); 'previousRef' is the address it had before this write; 'line' is 1-based in the written document; 'destination' is a link exactly as its author typed it; 'source' is the address of the document a link is written in, on the inbound link codes only. Branch on 'code' and those fields — never by parsing 'message'. A code's SHAPE says who minted it: a code of the form 'MD###' is markdownlint's own and means exactly what markdownlint's documentation for that rule says, and every other code is this system's, snake_case and never hyphenated. Only the 'MD###' codes carry 'info'. Requires delete permission on the path, AND read permission covering the whole subtree there: you may not delete a subtree part of which you cannot read. A library whose 'mode' is 'repository' reflects one GitHub repository and refuses this call for every caller, its owner included, with repository_bound — 409 on REST, the same code on MCP. It is a state of the library, not a permission, and it is decided before permissions and 'version' are checked. The error names where to make the change instead: 'repository' (owner/name), 'branch', 'path' (the library path, which is also the repository path) and 'url' (that path on GitHub). The three origin fields are omitted when the origin cannot be resolved; the call is refused all the same. A caller who may not read the path gets permission_denied instead. 'libraryId' takes the library's address, 'usesource:///' — the two segments list_libraries returns as 'address', and the first two segments of any 'address' a read hands back. A bare library id still resolves as a legacy pointer, and the response then carries 'libraryNotice' naming the canonical address to use instead. Three forms are refused as invalid_input: a 'source://' string (the scheme was renamed and is never rewritten for you), a one-segment 'usesource://' (a library slug is unique only within its owner, so the owner segment is never inferred from the caller), and a full document address (segments 1 and 2 are the library; everything after them belongs in the separate 'path' argument). An address naming no library you may read answers not_found, whether it does not exist or you hold no grant on it.

Permission: delete

Parameter Type Required Description
libraryId string yes —
path string yes —
version string yes —
force boolean no —

get_audit_log#

Read one audit log, newest first — who acted, when, what they did, and the subject it was done to (a document path, an org, a principal or an agent) — up to limit entries (default 50, max 200). Every library keeps a log, and so does every owner (a principal or an org). Pass exactly one selector, which picks exactly one log: libraryId reads that library's log and needs view_audit on the library; orgId reads that org's log and is allowed to holders of the org's owner role only, never to a member (membership and role changes, invitations, and the org's billing: its plan changes and invoices, which are never in a principal's log); principalId reads that principal's log and must be your own id (your billing, and an agent's self-signup and its claim). Anyone else gets not_found, whether or not the org or principal exists. Each log stands alone: no call merges several logs, a read of an owner's log never includes the entries of the libraries it owns, and a principal who may read several logs reads each with its own call. Who created a principal, and when, is not a log entry: it is recorded on the principal as createdAt and createdBy (returned by create_agent). Entries written before that change may still appear under the actions provision_principal and create_agent. Optional filters: actorId (one principal), action (exact, e.g. write_file), pathPrefix (entries at or under a path — libraryId only), before (only entries older than this epoch-millis timestamp — use for paging). Entries are returned exactly as they were written, and 'details' is returned verbatim: the log is append-only history and nothing rewrites it. One consequence you have to code against: a section write's 'details.notices' is a list of CODED OBJECTS ({code, message, ref, previousRef, line}) on records written since notices gained codes, and a list of PLAIN STRINGS on records written before that. Both shapes live under the same key, you will meet both, and no migration will make them uniform. 'libraryId' takes the library's address, 'usesource:///' — the two segments list_libraries returns as 'address', and the first two segments of any 'address' a read hands back. A bare library id still resolves as a legacy pointer, and the response then carries 'libraryNotice' naming the canonical address to use instead. Three forms are refused as invalid_input: a 'source://' string (the scheme was renamed and is never rewritten for you), a one-segment 'usesource://' (a library slug is unique only within its owner, so the owner segment is never inferred from the caller), and a full document address (segments 1 and 2 are the library; everything after them belongs in the separate 'path' argument). An address naming no library you may read answers not_found, whether it does not exist or you hold no grant on it.

Permission: view_audit

Parameter Type Required Description
libraryId string no —
orgId string no —
principalId string no —
actorId string no —
action string no —
pathPrefix string no —
limit integer no —
before integer no —

get_binding_status#

Report what a repository-bound library reflects of its GitHub repository: the commit it reflects, whether a reconciliation is running, how the last one ended, and which files were skipped or left links broken. Read-only; answers from what Source has recorded and makes no call to GitHub. Requires read permission somewhere in the library. The status carries 'address' (the library's canonical address, the value to send back as 'libraryId'); 'repository' (owner/name), 'branch' and 'url', the repository the library reflects; 'state', 'attached' or 'detached', and only when detached 'detachedBecause' ('removed', 'uninstalled', 'deleted' or 'transferred'); 'commit', the commit the library reflects, null until the first import completes, and 'commitAt', when it was recorded, or null; 'headSeen', the branch head most recently read from GitHub by a reconciliation or by resync_binding — never a push's commit — or null; 'running', true exactly while a reconciliation is in progress; 'lastRun', null or the last finished run's 'startedAt', 'finishedAt', 'outcome' ('completed', or 'incomplete' when it stopped on its time budget or an error) with 'applied' and 'skipped' counts and 'error' when an error ended it; 'skipped', the repository files not ingested, and 'broken', the deleted documents still linked to. 'commit' changes only once every change up to a head has been applied or skipped, so a write accepted and a commit ingested are two different facts. Each entry of 'skipped' has 'path' (the repository path), 'reason' — 'not_markdown', 'too_large' (over 512 KB), 'invalid_path', 'not_text' (not UTF-8), 'path_collision' (another file maps to the same library path; 'winner' names the one ingested) or 'limit_exceeded' (the owner's document or storage limit; retried on every reconciliation). Each entry of 'broken' has 'path', 'commit' (the commit that deleted it) and 'inboundLinks', the documents still linking to it. 'skipped' and 'broken' are each {entries, truncated}: at most 100 entries in path order, and a read stops after 1,000 stored entries; 'truncated' is true exactly when the list stopped at either bound before its last stored entry. Entries, and inbound links, under paths you cannot read are omitted, never counted. Refusals: not_found when you may read no path of the library; invalid_input on a library whose 'mode' is 'source', which is not repository-bound and has no binding. 'libraryId' takes the library's address, 'usesource:///' — the two segments list_libraries returns as 'address', and the first two segments of any 'address' a read hands back. A bare library id still resolves as a legacy pointer, and the response then carries 'libraryNotice' naming the canonical address to use instead. Three forms are refused as invalid_input: a 'source://' string (the scheme was renamed and is never rewritten for you), a one-segment 'usesource://' (a library slug is unique only within its owner, so the owner segment is never inferred from the caller), and a full document address (segments 1 and 2 are the library; everything after them belongs in the separate 'path' argument). An address naming no library you may read answers not_found, whether it does not exist or you hold no grant on it.

Permission: read

Parameter Type Required Description
libraryId string yes —

get_org#

Get one org's summary. Each org is a summary: id, name, slug (only when the org holds an owner slug), role (your role in it, owner or member), basePermission (none, read or write — what every member may do on the org's libraries without a grant) and tier, the org's own plan in force — the plan its library gates are judged against, whatever your own plan is. No billing, creator or member field is returned; list_org_members lists the members. Any member or owner of the org may call it. A missing org and an org you are not a member of both answer not_found, with the same message, so it cannot be used to learn which org ids exist. Read-only: it writes nothing and appends no audit entry.

Permission: manage_org

Parameter Type Required Description
orgId string yes —

get_usage#

Read a library's usage: live file, folder, and byte counts, the library owner's plan in force as 'tier', and that plan's storage limit as 'limits.storageBytes' (and the owner's agent count for personal libraries). Libraries and documents are not limits and are not reported as one. Requires view_stats permission on the library. 'libraryId' takes the library's address, 'usesource:///' — the two segments list_libraries returns as 'address', and the first two segments of any 'address' a read hands back. A bare library id still resolves as a legacy pointer, and the response then carries 'libraryNotice' naming the canonical address to use instead. Three forms are refused as invalid_input: a 'source://' string (the scheme was renamed and is never rewritten for you), a one-segment 'usesource://' (a library slug is unique only within its owner, so the owner segment is never inferred from the caller), and a full document address (segments 1 and 2 are the library; everything after them belongs in the separate 'path' argument). An address naming no library you may read answers not_found, whether it does not exist or you hold no grant on it.

Permission: view_stats

Parameter Type Required Description
libraryId string yes —

invite_org_member#

Invite an email address to an org with role owner or member, and email it a link to accept. Nothing is added to the member list until the invitation is accepted; the token in the link reaches only the invited inbox, and neither this result nor any other carries it. Invitations are by email address only — never by principal id — and are the only way a person joins an org other than creating it. An invitation expires seven days after it is sent; inviting an address that already has a pending invitation to the org replaces it (the earlier one is revoked and a new link is mailed), which is how an invitation is resent. It is accepted only by an account whose email is the invited address — compared trimmed and case-insensitively — and verified. An org has two roles: an owner manages the org — its members, their roles and its base permission — and may do everything on every library the org owns without a grant; a member holds the org's base permission on those libraries. Returns the invitation (id, email, role, state pending, invitedBy, createdAt, expiresAt) and 'delivery', what became of the email: sent, refused (the provider would not take it), failed (not sent, or not known to be), or unavailable. The invitation stays pending whatever 'delivery' says; inviting the address again is the retry. Refused: an address belonging to a current member of the org (invalid_input); a deployment that cannot send email (email_unavailable — 503 on REST, the same code on MCP), with nothing written. Only an owner of the org may call it (manage_org).

Permission: manage_org

Parameter Type Required Description
orgId string yes —
email string yes —
role "owner" \ "member" yes

leave_org#

Leave an org: end your own membership, in either role. Org-subject grants and the org's base permission stop applying to you from your next request. The sole owner cannot leave until another member is made an owner. Any member or owner of the org may call it; anyone else gets not_found.

Permission: manage_org

Parameter Type Required Description
orgId string yes —

list_comments#

List the comments on a markdown file, oldest first; archived comments are excluded unless includeArchived is true. Every open comment is returned — read_file and read_section carry at most 20, and this is where the rest are. Each comment carries 'id', 'sectionRef' — the canonical slug path of the section it is anchored to, or '' for the top of the document — 'body', 'authorId', 'createdAt', 'replies' (oldest first, each with 'id', 'authorId', 'body' and 'createdAt'), 'writtenAgainst' — the document version the comment was written against, a record and never a precondition: compare it with the file's current version to see whether the text has changed since — 'displacedFrom' where set — the address it had when its section stopped existing and it was re-attached to the nearest surviving section — and 'version', the opaque string archive_comment requires. A comment's version covers its archived state and its replies, not its anchor. Archiving needs no second read. A comment is archived or it is not; there is no other state. Requires read permission on the path. 'libraryId' takes the library's address, 'usesource:///' — the two segments list_libraries returns as 'address', and the first two segments of any 'address' a read hands back. A bare library id still resolves as a legacy pointer, and the response then carries 'libraryNotice' naming the canonical address to use instead. Three forms are refused as invalid_input: a 'source://' string (the scheme was renamed and is never rewritten for you), a one-segment 'usesource://' (a library slug is unique only within its owner, so the owner segment is never inferred from the caller), and a full document address (segments 1 and 2 are the library; everything after them belongs in the separate 'path' argument). An address naming no library you may read answers not_found, whether it does not exist or you hold no grant on it.

Permission: read

Parameter Type Required Description
libraryId string yes —
path string yes —
includeArchived boolean no —

list_grants#

List the active (non-revoked) grants of a library. Requires manage_grants permission on the library root. 'libraryId' takes the library's address, 'usesource:///' — the two segments list_libraries returns as 'address', and the first two segments of any 'address' a read hands back. A bare library id still resolves as a legacy pointer, and the response then carries 'libraryNotice' naming the canonical address to use instead. Three forms are refused as invalid_input: a 'source://' string (the scheme was renamed and is never rewritten for you), a one-segment 'usesource://' (a library slug is unique only within its owner, so the owner segment is never inferred from the caller), and a full document address (segments 1 and 2 are the library; everything after them belongs in the separate 'path' argument). An address naming no library you may read answers not_found, whether it does not exist or you hold no grant on it.

Permission: manage_grants

Parameter Type Required Description
libraryId string yes —

list_installations#

List the GitHub App installations the caller may act on, with the repository-bound libraries each one created. An installation is listed by its owner, never by who connected it: a person sees the installations they own and those of every Source org in which they hold the 'owner' role; a 'member' of an org does not see the org's, even one they connected. An agent sees the installations owned by the person who owns it, and no org's — not even those of an org that person owns. Read-only; answers from what Source has recorded and makes no call to GitHub. Takes no input. Returns {installations, count}: 'count' is the number of installations returned. Installations are ordered by owner — the caller's own first, then each org's in ascending org id — and by 'id' within each owner. Each installation carries 'id' (GitHub's installation id), 'owner' ({kind, id}: kind 'principal' with the owning person's principal id, or kind 'org' with the owning Source org's id — the shape a library's owner has), 'account' ({login, type}: the GitHub account it is installed on, 'User' or 'Organization'), 'state' ('active', or 'deleted' once uninstalled on GitHub), 'libraries', 'notCreated', 'notCreatedCount' and 'truncated'. Each entry of 'libraries' carries 'libraryId', its canonical 'address' (the value to send back as 'libraryId'), 'repository' (owner/name), 'state' ('attached', or 'detached' when the repository no longer reaches the library) and, only when detached, 'detachedBecause' ('removed', 'uninstalled', 'deleted' or 'transferred'). A library you cannot read is omitted from 'libraries', never named. Each entry of 'notCreated' is a repository the installation grants that has no library: 'repository' (owner/name) and 'reason' — 'library_limit' (an entry recorded while libraries were limited; libraries are no longer a limit, so no new one is recorded, and calling create_library with that repository binds it and removes the entry) or 'bound_elsewhere' (the repository is already bound to a library another owner holds). 'notCreated' lists at most 100 repositories per installation; 'notCreatedCount' is the true number recorded, and 'truncated' is true exactly when 'notCreatedCount' exceeds the number listed. No permission is needed beyond authentication. Everything returned carries its canonical 'address' beside its 'path': a document's is 'usesource:///', a section's is that plus '#', and a library's is 'usesource:///'. The address is derived from the path returned beside it, so the two cannot disagree, and it can be pasted into a link or sent straight back as 'libraryId' (its first two segments).

Permission: read

No parameters — send {}.

list_invitations#

List an org's invitations, newest first, in every state — pending, accepted, expired or revoked — each with its id, email, role, state, invitedBy, createdAt and expiresAt, and who ended it and when (acceptedBy/acceptedAt, revokedBy/revokedAt and replacedBy for a replacement, expiredAt). No entry carries a token. A pending invitation past its expiry is reported, and stored, as expired. Invitations are by email address only — never by principal id — and are the only way a person joins an org other than creating it. An invitation expires seven days after it is sent; inviting an address that already has a pending invitation to the org replaces it (the earlier one is revoked and a new link is mailed), which is how an invitation is resent. It is accepted only by an account whose email is the invited address — compared trimmed and case-insensitively — and verified. Only an owner of the org may call it; anyone else, a member included, gets not_found.

Permission: manage_org

Parameter Type Required Description
orgId string yes —

list_libraries#

List the libraries visible to the caller: libraries they own; libraries owned by an org they belong to, when they hold the org's owner role or the org's base permission is read or write (at none, a member sees only the org's libraries a grant names them on); plus libraries where a grant names them. No permission needed beyond authentication. Each library carries 'mode': 'source' when its documents live in Source, or 'repository' when it reflects one GitHub repository and refuses content writes with repository_bound. The mode is fixed when the library is created. A library in 'repository' mode also carries 'origin', where its documents live: 'repository' (the GitHub repository as 'owner/name', as currently named), 'branch' (the default branch it reflects), 'url' (the repository's web URL), 'commit' (the commit the library reflects, or null before its first import), 'state' ('attached' or 'detached'), and 'detachedBecause' (present only when detached: 'removed', 'uninstalled', 'deleted' or 'transferred'). 'origin' is null when the library's repository binding could not be read, and absent on a library in 'source' mode. Everything returned carries its canonical 'address' beside its 'path': a document's is 'usesource:///', a section's is that plus '#', and a library's is 'usesource:///'. The address is derived from the path returned beside it, so the two cannot disagree, and it can be pasted into a link or sent straight back as 'libraryId' (its first two segments). An entry's 'address' is what to send back as 'libraryId', so listing once is enough to address every library it named.

Permission: read

No parameters — send {}.

list_org_members#

List an org's members with their roles, owner or member. Any member or owner of the org may call it; anyone else gets not_found.

Permission: manage_org

Parameter Type Required Description
orgId string yes —

list_orgs#

List every org you are a member of, in either role, with your role in each. Takes no input. Each org is a summary: id, name, slug (only when the org holds an owner slug), role (your role in it, owner or member), basePermission (none, read or write — what every member may do on the org's libraries without a grant) and tier, the org's own plan in force — the plan its library gates are judged against, whatever your own plan is. No billing, creator or member field is returned; list_org_members lists the members. The list is ordered by org name, compared case-insensitively, then by org id; it is the whole membership, not capped or paginated. Any authenticated caller may call it; a caller in no org gets an empty list, never an error. Read-only: it writes nothing and appends no audit entry.

Permission: manage_org

No parameters — send {}.

list_sections#

List the heading sections of a markdown file. Returns {sections, documentVersion}: for each section its slug path, ordinal, rank, title and 'version' — the section version replace_section and replace_in_section require — plus 'documentVersion', the whole file's version, which write_file requires. Both are opaque strings to carry back unchanged: never parse, compare or order them. Listing once is enough to write to any section of the file without reading each one first. Each entry also carries its own 'address', and 'commentCount': the open comments anchored to that section itself, its subsections' excluded. The response carries 'commentsTotal', the open comments on the whole document; the per-section counts, plus the comments anchored to the top of the document ('') when no preamble entry is listed, sum to it. No comment bodies are carried — read_section and read_file carry those. Requires read permission on the path. 'libraryId' takes the library's address, 'usesource:///' — the two segments list_libraries returns as 'address', and the first two segments of any 'address' a read hands back. A bare library id still resolves as a legacy pointer, and the response then carries 'libraryNotice' naming the canonical address to use instead. Three forms are refused as invalid_input: a 'source://' string (the scheme was renamed and is never rewritten for you), a one-segment 'usesource://' (a library slug is unique only within its owner, so the owner segment is never inferred from the caller), and a full document address (segments 1 and 2 are the library; everything after them belongs in the separate 'path' argument). An address naming no library you may read answers not_found, whether it does not exist or you hold no grant on it. Everything returned carries its canonical 'address' beside its 'path': a document's is 'usesource:///', a section's is that plus '#', and a library's is 'usesource:///'. The address is derived from the path returned beside it, so the two cannot disagree, and it can be pasted into a link or sent straight back as 'libraryId' (its first two segments).

Permission: read

Parameter Type Required Description
libraryId string yes —
path string yes —

list_shares#

List the active shares (non-revoked grants) scoped at or under a path ('/' when omitted). Requires share permission on that path. 'libraryId' takes the library's address, 'usesource:///' — the two segments list_libraries returns as 'address', and the first two segments of any 'address' a read hands back. A bare library id still resolves as a legacy pointer, and the response then carries 'libraryNotice' naming the canonical address to use instead. Three forms are refused as invalid_input: a 'source://' string (the scheme was renamed and is never rewritten for you), a one-segment 'usesource://' (a library slug is unique only within its owner, so the owner segment is never inferred from the caller), and a full document address (segments 1 and 2 are the library; everything after them belongs in the separate 'path' argument). An address naming no library you may read answers not_found, whether it does not exist or you hold no grant on it.

Permission: share

Parameter Type Required Description
libraryId string yes —
path string no —

list_tree#

List the files and folders of a library, optionally under a path prefix and limited to a depth below it. Returns {nodes, version}: node metadata without file content, sorted by path, plus 'version' — the version of the subtree under the prefix, which move_node, delete_node and delete_library require for a folder. 'version' is an opaque string to carry back unchanged: never parse, compare or order it. It is returned whether or not depth was supplied and is the same value either way, because the depth limit narrows what is returned and not what was observed. Requires read permission; nodes you cannot read are omitted, and the version is observed over only the nodes you may read. 'libraryId' takes the library's address, 'usesource:///' — the two segments list_libraries returns as 'address', and the first two segments of any 'address' a read hands back. A bare library id still resolves as a legacy pointer, and the response then carries 'libraryNotice' naming the canonical address to use instead. Three forms are refused as invalid_input: a 'source://' string (the scheme was renamed and is never rewritten for you), a one-segment 'usesource://' (a library slug is unique only within its owner, so the owner segment is never inferred from the caller), and a full document address (segments 1 and 2 are the library; everything after them belongs in the separate 'path' argument). An address naming no library you may read answers not_found, whether it does not exist or you hold no grant on it. Everything returned carries its canonical 'address' beside its 'path': a document's is 'usesource:///', a section's is that plus '#', and a library's is 'usesource:///'. The address is derived from the path returned beside it, so the two cannot disagree, and it can be pasted into a link or sent straight back as 'libraryId' (its first two segments).

Permission: read

Parameter Type Required Description
libraryId string yes —
prefix string no —
depth integer no —

move_node#

Move or rename a file or folder from fromPath to toPath; folders move with their entire subtree. 'version' is required: for a file, the document version read_file returned for fromPath; for a folder, the subtree version list_tree returned for that prefix. A token of the other kind, or a folder version observed over a different prefix, is invalid_input; a stale one is version_conflict carrying the current version. A file move commits the check, the copy and the tombstone together. A folder move does not: its subtree is committed in chunks of 400 writes that are not atomic across chunk boundaries, so above 400 nodes the version narrows the race without closing it — the response says so too. Returns 'notices' with one 'inbound_link_forwarded' entry per markdown link from ANOTHER document that pointed at something this move relocated — carrying 'source' (that document's address) and 'destination' (the link as written), and no 'line', since the linking document is never opened. Nothing is broken and no document is rewritten: the old path keeps a forwarding address, the index is re-pointed, and reads of the linking document report the redirect until its author updates the link. A move is never refused on account of inbound links and takes no 'force'. Only linking documents you may read are named; 'inboundLinks' counts every one, readable or not, and 'disclosure' states that bound. At most 50 notices of each capped family — the link codes, the validity codes and the borrowed 'MD###' codes — are returned per response, each family counted separately, in the order they were found; consequence codes are never capped. When more conditions held than are listed, the response carries 'noticesTruncated': true and 'noticesBound', one sentence naming the cap and the families it cut; otherwise neither field is present. The cap changes nothing about what the operation did or stored — every condition still took effect — and it is distinct from 'bound', which folder operations use for their 400-write commit chunks. 'notices' is always present and is a list of objects, empty when there is nothing to report — never a list of strings. Each entry has 'code' (what happened, for branching), 'message' (the same thing in prose), and, where the code calls for them, 'ref', 'previousRef', 'line', 'info', 'destination' and 'source'. 'ref' is the address the notice is about IN THE DOCUMENT AS IT NOW STANDS ('' is the preamble's address); 'previousRef' is the address it had before this write; 'line' is 1-based in the written document; 'destination' is a link exactly as its author typed it; 'source' is the address of the document a link is written in, on the inbound link codes only. Branch on 'code' and those fields — never by parsing 'message'. A code's SHAPE says who minted it: a code of the form 'MD###' is markdownlint's own and means exactly what markdownlint's documentation for that rule says, and every other code is this system's, snake_case and never hyphenated. Only the 'MD###' codes carry 'info'. Comments move with the document: every comment on a moved file, archived ones included, is at its new path with the same id, body, replies, anchor and version, so a comment version read before the move still archives after it. A file later created at the old path starts with no comments. Requires write and delete permission on fromPath and write permission on toPath, AND read permission covering the whole subtree at fromPath: you may not move a subtree part of which you cannot read. A library whose 'mode' is 'repository' reflects one GitHub repository and refuses this call for every caller, its owner included, with repository_bound — 409 on REST, the same code on MCP. It is a state of the library, not a permission, and it is decided before permissions and 'version' are checked. The error names where to make the change instead: 'repository' (owner/name), 'branch', 'path' (the library path, which is also the repository path) and 'url' (that path on GitHub). The three origin fields are omitted when the origin cannot be resolved; the call is refused all the same. A caller who may not read the path gets permission_denied instead. The refusal names fromPath. 'libraryId' takes the library's address, 'usesource:///' — the two segments list_libraries returns as 'address', and the first two segments of any 'address' a read hands back. A bare library id still resolves as a legacy pointer, and the response then carries 'libraryNotice' naming the canonical address to use instead. Three forms are refused as invalid_input: a 'source://' string (the scheme was renamed and is never rewritten for you), a one-segment 'usesource://' (a library slug is unique only within its owner, so the owner segment is never inferred from the caller), and a full document address (segments 1 and 2 are the library; everything after them belongs in the separate 'path' argument). An address naming no library you may read answers not_found, whether it does not exist or you hold no grant on it. Everything returned carries its canonical 'address' beside its 'path': a document's is 'usesource:///', a section's is that plus '#', and a library's is 'usesource:///'. The address is derived from the path returned beside it, so the two cannot disagree, and it can be pasted into a link or sent straight back as 'libraryId' (its first two segments). The returned node's 'address' names where it now is, not where it was.

Permission: write

Parameter Type Required Description
libraryId string yes —
fromPath string yes —
toPath string yes —
version string yes —

read_file#

Read the markdown file at path, returning its full content plus metadata (title, size, who last wrote it and when) and 'version' — the document version write_file, move_node and delete_node require for this file. 'version' is an opaque string to carry back unchanged: never parse, compare or order it. Also returns 'notices', reporting the decay of the markdown links in the document returned: 'link_redirected' (the link's target has moved since it was written, so the link resolves only through a forwarding address — 'message' names where it stands now) and 'link_broken' (the link's target does not presently exist; whether it was deleted or never existed is not distinguished). Each carries 'destination' (the link exactly as written), 'line' and 'ref'. The verdict comes from the index the writes maintain, not from re-checking every target on read, and the notices cover only the links that begin inside the document returned: an empty list is NOT a claim that every link in the document or the library is sound, and links in a document nobody has written since the index was introduced are covered only once the backfill has run. Nothing is audited for a read. At most 50 notices of each capped family — the link codes, the validity codes and the borrowed 'MD###' codes — are returned per response, each family counted separately, in the order they were found; consequence codes are never capped. When more conditions held than are listed, the response carries 'noticesTruncated': true and 'noticesBound', one sentence naming the cap and the families it cut; otherwise neither field is present. The cap changes nothing about what the operation did or stored — every condition still took effect — and it is distinct from 'bound', which folder operations use for their 400-write commit chunks. 'notices' is always present and is a list of objects, empty when there is nothing to report — never a list of strings. Each entry has 'code' (what happened, for branching), 'message' (the same thing in prose), and, where the code calls for them, 'ref', 'previousRef', 'line', 'info', 'destination' and 'source'. 'ref' is the address the notice is about IN THE DOCUMENT AS IT NOW STANDS ('' is the preamble's address); 'previousRef' is the address it had before this write; 'line' is 1-based in the written document; 'destination' is a link exactly as its author typed it; 'source' is the address of the document a link is written in, on the inbound link codes only. Branch on 'code' and those fields — never by parsing 'message'. A code's SHAPE says who minted it: a code of the form 'MD###' is markdownlint's own and means exactly what markdownlint's documentation for that rule says, and every other code is this system's, snake_case and never hyphenated. Only the 'MD###' codes carry 'info'. A path the document has MOVED AWAY FROM answers the error code 'moved' rather than 'not_found', carrying 'movedTo' — the path it stands at now — so retry there; this is answered only when you may read the document at both paths, and is a plain 'not_found' otherwise. The response carries the comments on the document: 'comments', the open comments oldest first, at most 20; 'commentsTotal', how many there are; and 'commentsTruncated', true exactly when commentsTotal exceeds the comments returned — call list_comments for the rest. All three are present when there are none (an empty list, 0 and false). Archived comments are not carried. Each comment carries 'id', 'sectionRef' — the canonical slug path of the section it is anchored to, or '' for the top of the document — 'body', 'authorId', 'createdAt', 'replies' (oldest first, each with 'id', 'authorId', 'body' and 'createdAt'), 'writtenAgainst' — the document version the comment was written against, a record and never a precondition: compare it with the file's current version to see whether the text has changed since — 'displacedFrom' where set — the address it had when its section stopped existing and it was re-attached to the nearest surviving section — and 'version', the opaque string archive_comment requires. A comment's version covers its archived state and its replies, not its anchor. To fix the text a comment is about and close it in one call, send the comment's 'version' as an archive in the write's 'comments'. Requires read permission on the path. 'libraryId' takes the library's address, 'usesource:///' — the two segments list_libraries returns as 'address', and the first two segments of any 'address' a read hands back. A bare library id still resolves as a legacy pointer, and the response then carries 'libraryNotice' naming the canonical address to use instead. Three forms are refused as invalid_input: a 'source://' string (the scheme was renamed and is never rewritten for you), a one-segment 'usesource://' (a library slug is unique only within its owner, so the owner segment is never inferred from the caller), and a full document address (segments 1 and 2 are the library; everything after them belongs in the separate 'path' argument). An address naming no library you may read answers not_found, whether it does not exist or you hold no grant on it. Everything returned carries its canonical 'address' beside its 'path': a document's is 'usesource:///', a section's is that plus '#', and a library's is 'usesource:///'. The address is derived from the path returned beside it, so the two cannot disagree, and it can be pasted into a link or sent straight back as 'libraryId' (its first two segments).

Permission: read

Parameter Type Required Description
libraryId string yes —
path string yes —

read_section#

Read one section of a markdown file by ref — a slug path like 'getting-started/install', an ordinal like '4.2', or '' for the preamble — as two separate fields: 'heading' is the section's own heading ('## Install'; '' for the preamble; two lines for an underlined setext heading, which is two lines in the file) and 'body' is everything beneath it, subsections included, byte-exact. Also returns the document's full section listing, this section's 'version' — required by replace_section and replace_in_section — and 'documentVersion', the whole file's version, required by write_file. Both are opaque strings to carry back unchanged: never parse, compare or order them, and note the section 'version' covers the heading AND the body, so a concurrent rename invalidates it. To edit a section, change 'body' and send it back to replace_section as 'body': the two fields are the same shape on both ops, so either can be echoed back unchanged and the round trip never duplicates a heading. The section read carries its own 'address', and so does every entry of the listing. Also returns 'notices', reporting the decay of the markdown links in the section returned (a link elsewhere in the same document is not reported here): 'link_redirected' (the link's target has moved since it was written, so the link resolves only through a forwarding address — 'message' names where it stands now) and 'link_broken' (the link's target does not presently exist; whether it was deleted or never existed is not distinguished). Each carries 'destination' (the link exactly as written), 'line' and 'ref'. The verdict comes from the index the writes maintain, not from re-checking every target on read, and the notices cover only the links that begin inside the section returned (a link elsewhere in the same document is not reported here): an empty list is NOT a claim that every link in the document or the library is sound, and links in a document nobody has written since the index was introduced are covered only once the backfill has run. Nothing is audited for a read. At most 50 notices of each capped family — the link codes, the validity codes and the borrowed 'MD###' codes — are returned per response, each family counted separately, in the order they were found; consequence codes are never capped. When more conditions held than are listed, the response carries 'noticesTruncated': true and 'noticesBound', one sentence naming the cap and the families it cut; otherwise neither field is present. The cap changes nothing about what the operation did or stored — every condition still took effect — and it is distinct from 'bound', which folder operations use for their 400-write commit chunks. 'notices' is always present and is a list of objects, empty when there is nothing to report — never a list of strings. Each entry has 'code' (what happened, for branching), 'message' (the same thing in prose), and, where the code calls for them, 'ref', 'previousRef', 'line', 'info', 'destination' and 'source'. 'ref' is the address the notice is about IN THE DOCUMENT AS IT NOW STANDS ('' is the preamble's address); 'previousRef' is the address it had before this write; 'line' is 1-based in the written document; 'destination' is a link exactly as its author typed it; 'source' is the address of the document a link is written in, on the inbound link codes only. Branch on 'code' and those fields — never by parsing 'message'. A code's SHAPE says who minted it: a code of the form 'MD###' is markdownlint's own and means exactly what markdownlint's documentation for that rule says, and every other code is this system's, snake_case and never hyphenated. Only the 'MD###' codes carry 'info'. A path the document has MOVED AWAY FROM answers the error code 'moved' rather than 'not_found', carrying 'movedTo' — the path it stands at now — so retry there; this is answered only when you may read the document at both paths, and is a plain 'not_found' otherwise. The response carries the comments on this section and its subsections — not a sibling's, and for the preamble (ref '') only those anchored to the top of the document: 'comments', the open comments oldest first, at most 20; 'commentsTotal', how many there are; and 'commentsTruncated', true exactly when commentsTotal exceeds the comments returned — call list_comments for the rest. All three are present when there are none (an empty list, 0 and false). Archived comments are not carried. Each comment carries 'id', 'sectionRef' — the canonical slug path of the section it is anchored to, or '' for the top of the document — 'body', 'authorId', 'createdAt', 'replies' (oldest first, each with 'id', 'authorId', 'body' and 'createdAt'), 'writtenAgainst' — the document version the comment was written against, a record and never a precondition: compare it with the file's current version to see whether the text has changed since — 'displacedFrom' where set — the address it had when its section stopped existing and it was re-attached to the nearest surviving section — and 'version', the opaque string archive_comment requires. A comment's version covers its archived state and its replies, not its anchor. To fix the text a comment is about and close it in one call, send the comment's 'version' as an archive in the write's 'comments'. Requires read permission on the path. 'libraryId' takes the library's address, 'usesource:///' — the two segments list_libraries returns as 'address', and the first two segments of any 'address' a read hands back. A bare library id still resolves as a legacy pointer, and the response then carries 'libraryNotice' naming the canonical address to use instead. Three forms are refused as invalid_input: a 'source://' string (the scheme was renamed and is never rewritten for you), a one-segment 'usesource://' (a library slug is unique only within its owner, so the owner segment is never inferred from the caller), and a full document address (segments 1 and 2 are the library; everything after them belongs in the separate 'path' argument). An address naming no library you may read answers not_found, whether it does not exist or you hold no grant on it. Everything returned carries its canonical 'address' beside its 'path': a document's is 'usesource:///', a section's is that plus '#', and a library's is 'usesource:///'. The address is derived from the path returned beside it, so the two cannot disagree, and it can be pasted into a link or sent straight back as 'libraryId' (its first two segments).

Permission: read

Parameter Type Required Description
libraryId string yes —
path string yes —
ref string yes —

remove_org_member#

Remove another member from an org; org-subject grants and the org's base permission stop applying to them from their next request. The last owner cannot be removed, and you cannot remove yourself — call leave_org to leave. Only an owner of the org may call it (manage_org).

Permission: manage_org

Parameter Type Required Description
orgId string yes —
principalId string yes —

replace_in_section#

Apply a regex find-and-replace inside one section of a markdown file (ref: slug path, ordinal, or '' for the preamble). Matching covers the section's whole body, including the heading lines of its subsections; the section's own heading line is never matched or replaced — use replace_section with 'heading' to change that. Returns the updated node, 'count' — the number of replacements made — and 'notices'. Zero matches leaves the file untouched, bumps no version and returns an empty 'notices'. 'pattern' is a regular expression, not literal text — '|' is alternation, so a table row sent unescaped matches far more than the row. 'flags' defaults to 'g': EVERY match is replaced; send flags '' to replace only the first. Every call is bounded by a replacement ceiling — 'maxReplacements' when you send it, otherwise 10. The replacements the call would make are counted after the version check and before anything is written; a call that would make more than the ceiling is REFUSED whole with the error code 'too_many_replacements' (409 on REST): nothing is stored, no version is bumped and no replacement is applied, and the refusal carries 'replacements' (how many the call would have made) and 'maxReplacements' (the ceiling applied). If every one of them is meant, re-send the same call with 'maxReplacements' set to the reported 'replacements'; otherwise narrow the pattern. Replacements are bounded, not matches: a call without 'g' makes at most one and is never refused by the ceiling, and a call matching nothing never is. 'pattern': The pattern is the SOURCE of a JavaScript regular expression, not literal text: the characters \ ^ $ . | ? * + ( ) [ ] { } carry meaning, and a literal one is written with a backslash before it ('.' for a dot, '|' for a pipe). A literal backslash is therefore '\' in the pattern, and in a JSON request body each of those backslashes is escaped again, so the pattern '\' is the four characters '\\' inside the JSON string. An over- or under-escaped pattern is usually still a valid regular expression, so it is NOT rejected: it matches something other than what you meant. 'flags' is a JavaScript RegExp flag string of at most 10 characters, read exactly as the JavaScript engine reads it — any flag it accepts, such as 'g', 'i', 'm', 's', 'u', 'v' or 'y'; a flag string or pattern the engine cannot compile is refused with invalid_pattern. 'replacement' is a replacement TEMPLATE, not plain text. The engine interprets these tokens: '$$' inserts a literal '$' — the way to write a dollar sign; '$&' the matched text; '$`' the text of the section's body before the match; "$'" the text of the section's body after the match; '$n' and '$nn' the numbered capture group; '$' the named capture group. A two-digit '$nn' is read as one group only when the pattern has that many groups, and is otherwise '$n' followed by a literal digit — so '$29' against a pattern with two groups is group 2 followed by the literal digit '9'. A '$n' naming no group the pattern has, '$0' and '$00' included, is written literally, as is '$<' when the pattern has no named group; a '$' naming none of the pattern's named groups inserts nothing. Every interpreted token in an applied replacement is reported as a 'replacement_token_expanded' notice. Every other content field — write_file's 'content', replace_section's 'heading' and 'body', and any field whose contract names no syntax — carries no template or regular-expression syntax: no '$' token and no backslash in it is interpreted, and it is stored exactly as sent. 'notices' is always present and is a list of objects, empty when there is nothing to report — never a list of strings. Each entry has 'code' (what happened, for branching), 'message' (the same thing in prose), and, where the code calls for them, 'ref', 'previousRef', 'line', 'info', 'destination' and 'source'. 'ref' is the address the notice is about IN THE DOCUMENT AS IT NOW STANDS ('' is the preamble's address); 'previousRef' is the address it had before this write; 'line' is 1-based in the written document; 'destination' is a link exactly as its author typed it; 'source' is the address of the document a link is written in, on the inbound link codes only. Branch on 'code' and those fields — never by parsing 'message'. A code's SHAPE says who minted it: a code of the form 'MD###' is markdownlint's own and means exactly what markdownlint's documentation for that rule says, and every other code is this system's, snake_case and never hyphenated. Only the 'MD###' codes carry 'info'. An interpretation code, which reports how THIS call's own input was read rather than what the edit did to the document: 'replacement_token_expanded' — a template token in 'replacement' that was interpreted rather than written as its own characters, '$$' included. ONE ENTRY PER DISTINCT TOKEN as written, however many matches it was expanded in and however often the template repeats it; 'message' names the token, quotes what it expanded to on the first match — cut to the first 80 characters, with the full length stated, when longer — and states how many matches it was expanded in; 'ref' is the written section's address, and no 'previousRef', 'line' or 'info' is carried. The entries come in the order the tokens first appear in 'replacement', ahead of every other notice, and are never capped. A call that matches nothing, a refused call, and a replacement with no interpreted token carry none. Consequence codes, which report what this edit did to the rest of the document: 'heading_rank_changed' (the written section's rank changed; 'message' names what it absorbed or reparented), 'section_renamed' ('ref' the new address, 'previousRef' the old one), 'section_address_moved' (some OTHER section's address moved — renaming one of two same-titled siblings shifts the other's '-1' suffix; 'ref' and 'previousRef' both present), 'section_destroyed' (some other section stopped existing, e.g. an unclosed code fence in 'body' swallowed it — it carries 'previousRef' ONLY and no 'ref', because there is no address left that names it), 'heading_escaped' (a heading inside the written content created a new section after the one written; 'ref' is that created section's address), 'body_emptied', 'heading_unfolded' (a soft-wrapped heading was joined into one line) and 'heading_anchored' (a blank line was inserted so a setext heading would stand on its own). ONE ENTRY PER FACT: a write that moves three addresses returns three 'section_address_moved' entries, never one naming all three. Validity codes, which report whether the document produced is sound markdown rather than what the edit did to it, and whether THIS viewer will render it as written. 'unclosed_code_fence' — a fenced code block that is never closed, so everything after it becomes part of the code block. It carries 'line', the opening fence's line, and 'ref', the section enclosing it. It is a WARNING, never a rejection: the write applies and the document holds exactly the bytes you sent; nothing is repaired and no closing fence is inserted, because where you meant to close it is a guess. 'literal_escape_sequence' — a line whose PROSE holds a backslash the markdown parse left literal, immediately followed by a lowercase 'n', 't' or 'r', so the reader sees the two characters '\n', '\t' or '\r' rather than a line break, a tab or a carriage return. Only prose is examined — paragraphs, headings, list items, blockquotes, table cells, emphasis and a link's bracketed text; code blocks, inline code, raw HTML, link and image destinations and titles, image alt text, autolinks and link reference definitions are NEVER examined. A backslash escaped as '\' is not reported, which is how to keep a backslash you meant: write '\n' for the characters '\n'; an unescaped one is reported whatever was meant, so 'C:\new' is. One entry per line however many sequences it holds, carrying 'line' and 'ref', with 'message' naming which of the three the line holds, how many of each, and both repairs; NEVER 'info'. It is a WARNING like 'unclosed_code_fence': the write applies and the document holds exactly the bytes you sent — nothing is repaired or unescaped. Five RENDER-FIDELITY codes report the conditions under which well-formed markdown will not render as you meant, because of what this viewer's render path does to it — facts no general markdown linter can name: 'raw_html_block_dropped' (a block of raw HTML, such as a '' on its own line: the viewer drops the block and everything inside it) and 'raw_html_inline_dropped' (a raw HTML tag inside text, such as '': the tag is dropped and the surrounding text is kept; an opening and a closing tag are two entries, each naming its tag in 'message'; a tag inside a code block is code and is never reported, and an HTML comment is not reported either); 'url_scheme_stripped' (a link, image or link reference definition whose URL scheme the viewer does not keep in that position, such as 'javascript:' on a link or 'data:' on an image, so the URL is stripped and the text is shown unlinked or the image empty; 'message' names the schemes kept in that position; a reference definition is reported once, at the definition); 'mermaid_unknown_diagram_type' (a mermaid fence whose first line names no diagram type the renderer knows — a typo, or another tool's syntax) and 'mermaid_syntax_error' (a mermaid fence of a known type whose source does not parse; 'line' is the source line the renderer named, as a line of the written document, or the fence's opening line when it named none). Each of the five carries 'line', the construct's line in the written document, and 'ref', the section enclosing it, and NEVER 'info'; 'message' carries what will be lost and, for the two mermaid codes, the renderer's own words. All five are WARNINGS that leave the stored bytes exactly as sent, the same as 'unclosed_code_fence'. A mermaid fence is checked by PARSING it with the same renderer the viewer draws with, and never by laying it out: a diagram that parses but fails to draw in a particular browser is reported nowhere but beside the fence, to the reader. A fence the parse could not be run against — the check did not start, timed out or failed — yields NOTHING rather than a guess, so the absence of both mermaid codes does not by itself mean the diagram will draw. Beside them, BORROWED codes, which are markdownlint's own and not this system's: an entry whose 'code' has the form 'MD###' was produced by markdownlint itself running over the document this write stored, and means exactly what markdownlint's documentation for that rule says — look it up there, and 'info.url' links that page for the version that ran. Exactly four rules run, all of them conditions that make the document RENDER WRONG rather than style opinions: 'MD011' (a reversed link, '(text)[url]', which renders as plain text), 'MD042' (an empty link, 'x', which renders as nothing), 'MD052' (a reference link whose '[label]:' definition is missing anywhere in the document, which renders as literal brackets) and 'MD056' (a table row whose cell count differs from its header's, silently dropping or adding data). Nothing else in markdownlint's catalogue runs — no line-length, whitespace or heading-structure opinions, and not its raw-HTML rule, whose condition the two raw_html codes report under this system's own name. Borrowed entries carry 'line' and 'ref' like the validity codes, plus 'info': 'info.url' is the rule's documentation and 'info.fix' is the repair markdownlint described, relayed and NEVER applied. They are warnings too — nothing is repaired, and they never reject a write. Both families are scoped to the content THIS write supplied: for replace_in_section that is the body it edited — the whole addressed section's body as it now stands, NOT only the spans your pattern matched, since a fence's opening line and its missing closer can fall on either side of a replacement. A defect already present in that content is reported even though this write did not introduce it, and a defect elsewhere in the document is not reported even though the document still contains it. Link codes, which report what the markdown links in the content resolve to — an inline link, a reference link or an autolink, never a bare path in prose or backticks, never an image, raw HTML or another site's URL. A link beginning with a scheme ('usesource:////', or this deployment's own https URL) is absolute and may name another library; one beginning '/' is rooted at THIS library's root and never consults the owner namespace, so '/acme/handbook/x.md' is a path here even if an owner 'acme' exists; anything else is relative to the linking document's folder, and one that climbs above the library root resolves nowhere. Percent-encoding is decoded and the path is normalized exactly as a stored path is; the '.md' extension is required of a file and never guessed. 'link_unresolved' — the destination resolves to no live document or folder you may read (a missing target and one you may not read are reported identically), climbs above the root, or is malformed; the 'message' says which where saying so discloses nothing. 'link_fragment_unresolved' — the target resolves but its '#fragment' names no section of it, or is a bare slug naming more than one; a bare '#fragment' with no path is checked against the document written. 'link_redirected' — the target has moved, so the link resolves only through a forwarding address; point it at the new path. Each carries 'destination' (the link exactly as written), 'line' and 'ref'. THESE ARE WARNINGS: an unresolved link never fails a write, nothing is rewritten, no extension is appended and no redirect is followed into the stored bytes — a forward reference to a document you are about to create is ordinary, and it starts resolving the moment its target exists. They are scoped like the validity codes: the links that begin inside the addressed section's body as it now stands, whether or not your pattern touched them; a zero-match call stores nothing and reports none. One entry per occurrence, even where two occurrences share one destination. At most 50 notices of each capped family — the link codes, the validity codes and the borrowed 'MD###' codes — are returned per response, each family counted separately, in the order they were found; consequence codes are never capped. When more conditions held than are listed, the response carries 'noticesTruncated': true and 'noticesBound', one sentence naming the cap and the families it cut; otherwise neither field is present. The cap changes nothing about what the operation did or stored — every condition still took effect — and it is distinct from 'bound', which folder operations use for their 400-write commit chunks. The set of codes may grow: treat a code you do not recognize as informational and keep processing the rest of the response. For the same reason an empty 'notices' does NOT mean the document is sound markdown — it means nothing from the REPORTED SET was found in the content this write supplied, and the reported set is this system’s own codes above plus those four markdownlint rules and no others — and a mermaid fence the parse could not be run against contributes nothing to it. markdownlint’s remaining rules never run, so a condition outside that set is neither checked nor reported, and no other markdown checker runs at all. 'version' is required and is checked BEFORE the pattern is applied, always: a stale version returns version_conflict carrying the section's current 'version', 'heading' and 'body' rather than a count of zero, because a count computed against a section you no longer hold says nothing true about the live document. Send the section 'version' that read_section or list_sections returned; a document version is invalid_input. A successful write returns the section's new 'version' and the file's new 'documentVersion'. 'comments' is optional: up to 10 comment operations committed together with this write — {op: 'add', sectionRef, body, textRange?} to add a comment, {op: 'reply', commentId, body} to reply to one, {op: 'archive', commentId, version} to archive one with the 'version' a read carried for it. The write and its comment operations apply together or not at all: every precondition is checked before anything changes — the write's own version first, then for each operation in order that its comment exists (not_found), that a reply's comment is not archived and has room (invalid_input), and that an archive's version is current (version_conflict naming the comment in 'commentId') — then that each add's sectionRef resolves in the content this write PRODUCES (invalid_input). An add is anchored and recorded against the content this write leaves behind. Any entry requires comment permission on the path as well as the write's own; the write permission then meets an archive's grounds, so fixing a section and archiving the comment about it is one call. A call carrying 'comments' returns 'commentResults': one entry per operation, in order, each with 'op', 'applied', 'commentId' (for an add, the new comment's id) and, for an add or a reply, the comment's new 'version'. A write that commits nothing applies none of them and reports each with applied: false. Whatever the call carries, this write keeps every comment on the document pointing at its text: a comment on a section that moved follows it, and one whose section is gone is re-attached to the nearest surviving ancestor, else a sibling, else the top of the document, and marked 'displacedFrom' with the address it had. A zero-match call commits nothing, so it applies none of its comment operations — an archive closing a fix that did not happen is reported with applied: false. Comments are re-pointed exactly as replace_section re-points them. Requires write permission on the path. A library whose 'mode' is 'repository' reflects one GitHub repository and refuses this call for every caller, its owner included, with repository_bound — 409 on REST, the same code on MCP. It is a state of the library, not a permission, and it is decided before permissions and 'version' are checked. The error names where to make the change instead: 'repository' (owner/name), 'branch', 'path' (the library path, which is also the repository path) and 'url' (that path on GitHub). The three origin fields are omitted when the origin cannot be resolved; the call is refused all the same. A caller who may not read the path gets permission_denied instead. 'libraryId' takes the library's address, 'usesource:///' — the two segments list_libraries returns as 'address', and the first two segments of any 'address' a read hands back. A bare library id still resolves as a legacy pointer, and the response then carries 'libraryNotice' naming the canonical address to use instead. Three forms are refused as invalid_input: a 'source://' string (the scheme was renamed and is never rewritten for you), a one-segment 'usesource://' (a library slug is unique only within its owner, so the owner segment is never inferred from the caller), and a full document address (segments 1 and 2 are the library; everything after them belongs in the separate 'path' argument). An address naming no library you may read answers not_found, whether it does not exist or you hold no grant on it. Everything returned carries its canonical 'address' beside its 'path': a document's is 'usesource:///', a section's is that plus '#', and a library's is 'usesource:///'. The address is derived from the path returned beside it, so the two cannot disagree, and it can be pasted into a link or sent straight back as 'libraryId' (its first two segments).

Permission: write

Parameter Type Required Description
libraryId string yes —
path string yes —
ref string yes —
version string yes —
pattern string yes The pattern is the SOURCE of a JavaScript regular expression, not literal text: the characters \ ^ $ . | ? * + ( ) [ ] { } carry meaning, and a literal one is written with a backslash before it ('.' for a dot, '\
flags string no Regular-expression flags. Defaults to 'g': every match is replaced. Send '' to replace only the first match; add 'i' to ignore case ('gi' replaces every match, ignoring case). 'flags' is a JavaScript RegExp flag string of at most 10 characters, read exactly as the JavaScript engine reads it — any flag it accepts, such as 'g', 'i', 'm', 's', 'u', 'v' or 'y'; a flag string or pattern the engine cannot compile is refused with invalid_pattern.
maxReplacements integer no The replacement ceiling for this call: the most replacements it may make. A call that would make more is refused with too_many_replacements and stores nothing. Defaults to 10; an integer, at least 1, with no upper bound.
replacement string yes 'replacement' is a replacement TEMPLATE, not plain text. The engine interprets these tokens: '$$' inserts a literal '$' — the way to write a dollar sign; '$&' the matched text; '$`' the text of the section's body before the match; "$'" the text of the section's body after the match; '$n' and '$nn' the numbered capture group; '$' the named capture group. A two-digit '$nn' is read as one group only when the pattern has that many groups, and is otherwise '$n' followed by a literal digit — so '$29' against a pattern with two groups is group 2 followed by the literal digit '9'. A '$n' naming no group the pattern has, '$0' and '$00' included, is written literally, as is '$<' when the pattern has no named group; a '$' naming none of the pattern's named groups inserts nothing. Every interpreted token in an applied replacement is reported as a 'replacement_token_expanded' notice.
comments array \ null no

replace_section#

Replace a section's heading line, its body, or both (ref: slug path, ordinal, or '' for the preamble). 'heading' replaces the section's own heading line — that renames it, and a different number of '#' re-ranks it. 'body' replaces everything beneath the heading line, subsections included, and is exactly the 'body' read_section returned. 'body' may also write SEVERAL sections in one call, which is a supported edit (appending a section, splitting one in two): a heading in 'body' of the same rank as the written section closes it and opens a sibling after it, and a heading of higher rank (fewer '#') opens a new section that also takes in the sections that follow it as its subsections. Either way the response's 'slugPath' still addresses the section originally written, and each created section is reported as a 'heading_escaped' notice. Whatever you omit is kept unchanged; sending neither is invalid_input. Example: {"libraryId": "LIB", "path": "/runbooks/deploy.md", "ref": "deploy/ship", "heading": "## Ship it", "body": "\nRun the release script with --canary first.\n"} renames the section and rewrites its body atomically; sending only 'body' keeps the heading, sending only 'heading' keeps the body. Every other byte of the document stays identical. Returns the node, the section's 'slugPath' after the write — always the address of the section just written — and 'notices'. 'notices' is always present and is a list of objects, empty when there is nothing to report — never a list of strings. Each entry has 'code' (what happened, for branching), 'message' (the same thing in prose), and, where the code calls for them, 'ref', 'previousRef', 'line', 'info', 'destination' and 'source'. 'ref' is the address the notice is about IN THE DOCUMENT AS IT NOW STANDS ('' is the preamble's address); 'previousRef' is the address it had before this write; 'line' is 1-based in the written document; 'destination' is a link exactly as its author typed it; 'source' is the address of the document a link is written in, on the inbound link codes only. Branch on 'code' and those fields — never by parsing 'message'. A code's SHAPE says who minted it: a code of the form 'MD###' is markdownlint's own and means exactly what markdownlint's documentation for that rule says, and every other code is this system's, snake_case and never hyphenated. Only the 'MD###' codes carry 'info'. Consequence codes, which report what this edit did to the rest of the document: 'heading_rank_changed' (the written section's rank changed; 'message' names what it absorbed or reparented), 'section_renamed' ('ref' the new address, 'previousRef' the old one), 'section_address_moved' (some OTHER section's address moved — renaming one of two same-titled siblings shifts the other's '-1' suffix; 'ref' and 'previousRef' both present), 'section_destroyed' (some other section stopped existing, e.g. an unclosed code fence in 'body' swallowed it — it carries 'previousRef' ONLY and no 'ref', because there is no address left that names it), 'heading_escaped' (a heading inside the written content created a new section after the one written; 'ref' is that created section's address), 'body_emptied', 'heading_unfolded' (a soft-wrapped heading was joined into one line) and 'heading_anchored' (a blank line was inserted so a setext heading would stand on its own). ONE ENTRY PER FACT: a write that moves three addresses returns three 'section_address_moved' entries, never one naming all three. Validity codes, which report whether the document produced is sound markdown rather than what the edit did to it, and whether THIS viewer will render it as written. 'unclosed_code_fence' — a fenced code block that is never closed, so everything after it becomes part of the code block. It carries 'line', the opening fence's line, and 'ref', the section enclosing it. It is a WARNING, never a rejection: the write applies and the document holds exactly the bytes you sent; nothing is repaired and no closing fence is inserted, because where you meant to close it is a guess. 'literal_escape_sequence' — a line whose PROSE holds a backslash the markdown parse left literal, immediately followed by a lowercase 'n', 't' or 'r', so the reader sees the two characters '\n', '\t' or '\r' rather than a line break, a tab or a carriage return. Only prose is examined — paragraphs, headings, list items, blockquotes, table cells, emphasis and a link's bracketed text; code blocks, inline code, raw HTML, link and image destinations and titles, image alt text, autolinks and link reference definitions are NEVER examined. A backslash escaped as '\' is not reported, which is how to keep a backslash you meant: write '\n' for the characters '\n'; an unescaped one is reported whatever was meant, so 'C:\new' is. One entry per line however many sequences it holds, carrying 'line' and 'ref', with 'message' naming which of the three the line holds, how many of each, and both repairs; NEVER 'info'. It is a WARNING like 'unclosed_code_fence': the write applies and the document holds exactly the bytes you sent — nothing is repaired or unescaped. Five RENDER-FIDELITY codes report the conditions under which well-formed markdown will not render as you meant, because of what this viewer's render path does to it — facts no general markdown linter can name: 'raw_html_block_dropped' (a block of raw HTML, such as a '' on its own line: the viewer drops the block and everything inside it) and 'raw_html_inline_dropped' (a raw HTML tag inside text, such as '': the tag is dropped and the surrounding text is kept; an opening and a closing tag are two entries, each naming its tag in 'message'; a tag inside a code block is code and is never reported, and an HTML comment is not reported either); 'url_scheme_stripped' (a link, image or link reference definition whose URL scheme the viewer does not keep in that position, such as 'javascript:' on a link or 'data:' on an image, so the URL is stripped and the text is shown unlinked or the image empty; 'message' names the schemes kept in that position; a reference definition is reported once, at the definition); 'mermaid_unknown_diagram_type' (a mermaid fence whose first line names no diagram type the renderer knows — a typo, or another tool's syntax) and 'mermaid_syntax_error' (a mermaid fence of a known type whose source does not parse; 'line' is the source line the renderer named, as a line of the written document, or the fence's opening line when it named none). Each of the five carries 'line', the construct's line in the written document, and 'ref', the section enclosing it, and NEVER 'info'; 'message' carries what will be lost and, for the two mermaid codes, the renderer's own words. All five are WARNINGS that leave the stored bytes exactly as sent, the same as 'unclosed_code_fence'. A mermaid fence is checked by PARSING it with the same renderer the viewer draws with, and never by laying it out: a diagram that parses but fails to draw in a particular browser is reported nowhere but beside the fence, to the reader. A fence the parse could not be run against — the check did not start, timed out or failed — yields NOTHING rather than a guess, so the absence of both mermaid codes does not by itself mean the diagram will draw. Beside them, BORROWED codes, which are markdownlint's own and not this system's: an entry whose 'code' has the form 'MD###' was produced by markdownlint itself running over the document this write stored, and means exactly what markdownlint's documentation for that rule says — look it up there, and 'info.url' links that page for the version that ran. Exactly four rules run, all of them conditions that make the document RENDER WRONG rather than style opinions: 'MD011' (a reversed link, '(text)[url]', which renders as plain text), 'MD042' (an empty link, 'x', which renders as nothing), 'MD052' (a reference link whose '[label]:' definition is missing anywhere in the document, which renders as literal brackets) and 'MD056' (a table row whose cell count differs from its header's, silently dropping or adding data). Nothing else in markdownlint's catalogue runs — no line-length, whitespace or heading-structure opinions, and not its raw-HTML rule, whose condition the two raw_html codes report under this system's own name. Borrowed entries carry 'line' and 'ref' like the validity codes, plus 'info': 'info.url' is the rule's documentation and 'info.fix' is the repair markdownlint described, relayed and NEVER applied. They are warnings too — nothing is repaired, and they never reject a write. Both families are scoped to the content THIS write supplied: for replace_section that is the section it wrote — the heading line and body as they now stand, an inserted anchoring blank line included — so a fence left open in another section of the same document is not reported here. A defect already present in that content is reported even though this write did not introduce it, and a defect elsewhere in the document is not reported even though the document still contains it. Link codes, which report what the markdown links in the content resolve to — an inline link, a reference link or an autolink, never a bare path in prose or backticks, never an image, raw HTML or another site's URL. A link beginning with a scheme ('usesource:////', or this deployment's own https URL) is absolute and may name another library; one beginning '/' is rooted at THIS library's root and never consults the owner namespace, so '/acme/handbook/x.md' is a path here even if an owner 'acme' exists; anything else is relative to the linking document's folder, and one that climbs above the library root resolves nowhere. Percent-encoding is decoded and the path is normalized exactly as a stored path is; the '.md' extension is required of a file and never guessed. 'link_unresolved' — the destination resolves to no live document or folder you may read (a missing target and one you may not read are reported identically), climbs above the root, or is malformed; the 'message' says which where saying so discloses nothing. 'link_fragment_unresolved' — the target resolves but its '#fragment' names no section of it, or is a bare slug naming more than one; a bare '#fragment' with no path is checked against the document written. 'link_redirected' — the target has moved, so the link resolves only through a forwarding address; point it at the new path. Each carries 'destination' (the link exactly as written), 'line' and 'ref'. THESE ARE WARNINGS: an unresolved link never fails a write, nothing is rewritten, no extension is appended and no redirect is followed into the stored bytes — a forward reference to a document you are about to create is ordinary, and it starts resolving the moment its target exists. They are scoped like the validity codes: the links that begin inside the section written, whether or not they were already there; a link in a section this call did not supply is not reported, though the document still carries it. One entry per occurrence, even where two occurrences share one destination. At most 50 notices of each capped family — the link codes, the validity codes and the borrowed 'MD###' codes — are returned per response, each family counted separately, in the order they were found; consequence codes are never capped. When more conditions held than are listed, the response carries 'noticesTruncated': true and 'noticesBound', one sentence naming the cap and the families it cut; otherwise neither field is present. The cap changes nothing about what the operation did or stored — every condition still took effect — and it is distinct from 'bound', which folder operations use for their 400-write commit chunks. The set of codes may grow: treat a code you do not recognize as informational and keep processing the rest of the response. For the same reason an empty 'notices' does NOT mean the document is sound markdown — it means nothing from the REPORTED SET was found in the content this write supplied, and the reported set is this system’s own codes above plus those four markdownlint rules and no others — and a mermaid fence the parse could not be run against contributes nothing to it. markdownlint’s remaining rules never run, so a condition outside that set is neither checked nor reported, and no other markdown checker runs at all. A setext heading ('Install' over '=======') is accepted for 'heading' and gets a blank line inserted before it when the line above is prose, since its underline would otherwise bind to that line. 'version' is required: send the section 'version' that read_section or list_sections returned for this ref. A document version is invalid_input, not version_conflict — it is the wrong kind of token, not a stale one. A stale section version returns version_conflict carrying the section's current 'version', 'heading' and 'body', so you can rebase and retry in one round trip. Two callers editing DIFFERENT sections of one file both succeed: the edit is re-applied to the bytes it overwrites. A successful write returns the section's new 'version' and the file's new 'documentVersion'. 'comments' is optional: up to 10 comment operations committed together with this write — {op: 'add', sectionRef, body, textRange?} to add a comment, {op: 'reply', commentId, body} to reply to one, {op: 'archive', commentId, version} to archive one with the 'version' a read carried for it. The write and its comment operations apply together or not at all: every precondition is checked before anything changes — the write's own version first, then for each operation in order that its comment exists (not_found), that a reply's comment is not archived and has room (invalid_input), and that an archive's version is current (version_conflict naming the comment in 'commentId') — then that each add's sectionRef resolves in the content this write PRODUCES (invalid_input). An add is anchored and recorded against the content this write leaves behind. Any entry requires comment permission on the path as well as the write's own; the write permission then meets an archive's grounds, so fixing a section and archiving the comment about it is one call. A call carrying 'comments' returns 'commentResults': one entry per operation, in order, each with 'op', 'applied', 'commentId' (for an add, the new comment's id) and, for an add or a reply, the comment's new 'version'. A write that commits nothing applies none of them and reports each with applied: false. Whatever the call carries, this write keeps every comment on the document pointing at its text: a comment on a section that moved follows it, and one whose section is gone is re-attached to the nearest surviving ancestor, else a sibling, else the top of the document, and marked 'displacedFrom' with the address it had. A section write re-points comments exactly from the same pairing its consequence notices come from: a comment on a section this write renamed or re-ranked follows it to its new slug path, and one on a section it destroyed is re-attached and marked 'displacedFrom'. Requires write permission on the path. A library whose 'mode' is 'repository' reflects one GitHub repository and refuses this call for every caller, its owner included, with repository_bound — 409 on REST, the same code on MCP. It is a state of the library, not a permission, and it is decided before permissions and 'version' are checked. The error names where to make the change instead: 'repository' (owner/name), 'branch', 'path' (the library path, which is also the repository path) and 'url' (that path on GitHub). The three origin fields are omitted when the origin cannot be resolved; the call is refused all the same. A caller who may not read the path gets permission_denied instead. 'libraryId' takes the library's address, 'usesource:///' — the two segments list_libraries returns as 'address', and the first two segments of any 'address' a read hands back. A bare library id still resolves as a legacy pointer, and the response then carries 'libraryNotice' naming the canonical address to use instead. Three forms are refused as invalid_input: a 'source://' string (the scheme was renamed and is never rewritten for you), a one-segment 'usesource://' (a library slug is unique only within its owner, so the owner segment is never inferred from the caller), and a full document address (segments 1 and 2 are the library; everything after them belongs in the separate 'path' argument). An address naming no library you may read answers not_found, whether it does not exist or you hold no grant on it. Everything returned carries its canonical 'address' beside its 'path': a document's is 'usesource:///', a section's is that plus '#', and a library's is 'usesource:///'. The address is derived from the path returned beside it, so the two cannot disagree, and it can be pasted into a link or sent straight back as 'libraryId' (its first two segments).

Permission: write

Parameter Type Required Description
libraryId string yes —
path string yes —
ref string yes —
version string yes —
heading string \ null no
body string \ null no
comments array \ null no

resync_binding#

Ask a repository-bound library to reflect its repository's branch head now — for a push that never arrived, or a run that stopped short. It returns before ingestion: it reads the branch head, records the request and answers at once, and the reconciliation runs after the response. The answer is the binding status taken after the request was recorded, so 'commit' is still the commit reflected before the run and 'headSeen' is the head it will reconcile to or past; call get_binding_status to see when 'commit' reaches it and 'running' is false. A resync while a reconciliation is running starts no second one: the running one reconciles it. Requires write permission on '/'; takes no 'version', because the repository, not the caller, decides the bytes. The status carries 'address' (the library's canonical address, the value to send back as 'libraryId'); 'repository' (owner/name), 'branch' and 'url', the repository the library reflects; 'state', 'attached' or 'detached', and only when detached 'detachedBecause' ('removed', 'uninstalled', 'deleted' or 'transferred'); 'commit', the commit the library reflects, null until the first import completes, and 'commitAt', when it was recorded, or null; 'headSeen', the branch head most recently read from GitHub by a reconciliation or by resync_binding — never a push's commit — or null; 'running', true exactly while a reconciliation is in progress; 'lastRun', null or the last finished run's 'startedAt', 'finishedAt', 'outcome' ('completed', or 'incomplete' when it stopped on its time budget or an error) with 'applied' and 'skipped' counts and 'error' when an error ended it; 'skipped', the repository files not ingested, and 'broken', the deleted documents still linked to. 'commit' changes only once every change up to a head has been applied or skipped, so a write accepted and a commit ingested are two different facts. Each entry of 'skipped' has 'path' (the repository path), 'reason' — 'not_markdown', 'too_large' (over 512 KB), 'invalid_path', 'not_text' (not UTF-8), 'path_collision' (another file maps to the same library path; 'winner' names the one ingested) or 'limit_exceeded' (the owner's document or storage limit; retried on every reconciliation). Each entry of 'broken' has 'path', 'commit' (the commit that deleted it) and 'inboundLinks', the documents still linking to it. 'skipped' and 'broken' are each {entries, truncated}: at most 100 entries in path order, and a read stops after 1,000 stored entries; 'truncated' is true exactly when the list stopped at either bound before its last stored entry. Entries, and inbound links, under paths you cannot read are omitted, never counted. Refusals, each before anything is recorded: not_found when you may read no path of the library; permission_denied when you may read but lack write on '/'; invalid_input on a library whose 'mode' is 'source', which is not repository-bound; binding_detached — 409 on REST, the same code on MCP — when the repository no longer reaches the library, with 'detachedBecause' naming why; github_unavailable (503) when this deployment has no GitHub App configured. 'libraryId' takes the library's address, 'usesource:///' — the two segments list_libraries returns as 'address', and the first two segments of any 'address' a read hands back. A bare library id still resolves as a legacy pointer, and the response then carries 'libraryNotice' naming the canonical address to use instead. Three forms are refused as invalid_input: a 'source://' string (the scheme was renamed and is never rewritten for you), a one-segment 'usesource://' (a library slug is unique only within its owner, so the owner segment is never inferred from the caller), and a full document address (segments 1 and 2 are the library; everything after them belongs in the separate 'path' argument). An address naming no library you may read answers not_found, whether it does not exist or you hold no grant on it.

Permission: write

Parameter Type Required Description
libraryId string yes —

revoke_grant#

Revoke a grant by id; it stops applying immediately. Requires manage_grants permission on the grant's path prefix.

Permission: manage_grants

Parameter Type Required Description
grantId string yes —

revoke_invitation#

Revoke a pending invitation: its link stops working, and the invitation is returned with state revoked. An invitation that is not pending — accepted, expired or already revoked — is refused with invalid_input naming its state; an id naming no invitation of this org is not_found. Invitations are by email address only — never by principal id — and are the only way a person joins an org other than creating it. An invitation expires seven days after it is sent; inviting an address that already has a pending invitation to the org replaces it (the earlier one is revoked and a new link is mailed), which is how an invitation is resent. It is accepted only by an account whose email is the invited address — compared trimmed and case-insensitively — and verified. Only an owner of the org may call it (manage_org).

Permission: manage_org

Parameter Type Required Description
orgId string yes —
invitationId string yes —

revoke_share#

Revoke a share by its grant id; access stops immediately. Requires share (or manage_grants) permission on the share's path.

Permission: share

Parameter Type Required Description
grantId string yes —

search#

Search a library's markdown files. mode=glob matches file paths, mode=grep scans file content with a regular expression, mode=boolean matches literal terms and phrases in file content, and mode=semantic ranks sections by meaning using vector embeddings, available when the library owner's plan in force includes semantic search (a person's Pro, an org's Team) — judged on the library's owner, never on the caller, and refused otherwise with limit_exceeded naming 'semantic search' and the owner's plan: each hit names the section it matched, one document may supply more than one hit, and the score a hit carries is its similarity to the matched section. The syntax of 'query' depends on 'mode'. mode=glob: a micromatch glob over library paths (query "**/.md"). A leading '/' anchors the pattern at the library root; a pattern without one matches at any depth, and a slash-free one also against the file's basename. '', '?', '[…]', '{…}' and a leading '!' (negation, applied to a slash-free pattern's basename match) are special, and a backslash escapes a special character. mode=grep: a regular expression run over each file's WHOLE content (query "^## Install"). ^ and $ match at line boundaries and . does not cross a newline, but \n in the pattern matches a newline, so a match may span lines; a hit reports the line on which its match begins. The pattern is the SOURCE of a JavaScript regular expression, not literal text: the characters \ ^ $ . | ? * + ( ) [ ] { } carry meaning, and a literal one is written with a backslash before it ('.' for a dot, '|' for a pipe). A literal backslash is therefore '\' in the pattern, and in a JSON request body each of those backslashes is escaped again, so the pattern '\' is the four characters '\\' inside the JSON string. An over- or under-escaped pattern is usually still a valid regular expression, so it is NOT rejected: it matches something other than what you meant. Before it is compiled the pattern passes a safety guard: at most 256 characters, and no backreference, no lookbehind and no quantified group that itself contains a quantifier; a pattern the guard refuses, or one that does not compile, is rejected as invalid_input. For text containing characters the dialect gives meaning to, backslashes among them, use mode=boolean with a quoted phrase: that is the LITERAL search. mode=boolean: whitespace-separated terms, AND-matched by case-insensitive substring containment (query "incident OR postmortem -draft"). Only three things are special: a '-' beginning a term negates it, a standalone uppercase OR splits the query into alternative groups, and a '"' beginning a term opens a phrase that the next '"' closes, whitespace included; a '-' inside a quoted phrase is literal. Every other character, a backslash included, is matched as itself, so a quoted phrase is the literal search and needs no escaping. A phrase cannot contain a '"' and a term cannot begin with one, so a double quote is searchable only inside an unquoted term (say"hi), and a quote left unclosed runs to the end of the query. mode=semantic: natural language, with no syntax. Optional pathPrefix restricts the search to that subtree. Returns hits with path, enclosing section reference, snippet, line number, and 'documentVersion' — the opaque document version of the file the hit came from, which write_file, move_node and delete_node accept for that file; carry it back unchanged and never parse, compare or order it. A hit carries no section version, because a snippet is not a section you have seen in full and writing from one is not supported: call read_section for that. Bounds, all reported in the response: limit defaults to 50 and caps at 200, and truncated=true means that request limit cut the result — raise limit to see more. grep returns at most 5 matches per file; a file with more is named in cappedFiles with how many were shown, how many were found, and whether that total is exact (it is counted only to a ceiling). Per-file capping never sets truncated; cappedFiles is empty in glob and boolean, which return at most one hit per file, and empty in semantic, to which no per-file cap applies. scannedFiles counts the files actually examined and candidateFiles the files eligible; they differ when the limit stopped the scan early, and candidateFiles=0 with no hits means the query reached no readable file rather than that nothing matched. Requires read permission; files you cannot read are never scanned or revealed, and both counts exclude them. Every hit also carries its 'address': the document's address plus '#' where the hit names a section, and the document's address alone where it does not. 'libraryId' takes the library's address, 'usesource:///' — the two segments list_libraries returns as 'address', and the first two segments of any 'address' a read hands back. A bare library id still resolves as a legacy pointer, and the response then carries 'libraryNotice' naming the canonical address to use instead. Three forms are refused as invalid_input: a 'source://' string (the scheme was renamed and is never rewritten for you), a one-segment 'usesource://' (a library slug is unique only within its owner, so the owner segment is never inferred from the caller), and a full document address (segments 1 and 2 are the library; everything after them belongs in the separate 'path' argument). An address naming no library you may read answers not_found, whether it does not exist or you hold no grant on it. Everything returned carries its canonical 'address' beside its 'path': a document's is 'usesource:///', a section's is that plus '#', and a library's is 'usesource:///'. The address is derived from the path returned beside it, so the two cannot disagree, and it can be pasted into a link or sent straight back as 'libraryId' (its first two segments).

Permission: read

Parameter Type Required Description
libraryId string yes —
query string yes The syntax of 'query' depends on 'mode'. mode=glob: a micromatch glob over library paths (query "**/.md"). A leading '/' anchors the pattern at the library root; a pattern without one matches at any depth, and a slash-free one also against the file's basename. '', '?', '[…]', '{…}' and a leading '!' (negation, applied to a slash-free pattern's basename match) are special, and a backslash escapes a special character. mode=grep: a regular expression run over each file's WHOLE content (query "^## Install"). ^ and $ match at line boundaries and . does not cross a newline, but \n in the pattern matches a newline, so a match may span lines; a hit reports the line on which its match begins. The pattern is the SOURCE of a JavaScript regular expression, not literal text: the characters \ ^ $ . | ? * + ( ) [ ] { } carry meaning, and a literal one is written with a backslash before it ('.' for a dot, '\
mode "glob" \ "grep" \ "boolean" \
pathPrefix string no —
limit integer no —

set_org_base_permission#

Set an org's base permission — what every member may do on every library the org owns without a grant: none (nothing; a member reaches only what a grant gives), read (read and comment), or write (read, comment, write files and create folders; not delete). The default is read. It applies from the next request, writes no library or grant, and an explicit grant still reaches beyond it. Returns the org. Only an owner of the org may call it (manage_org).

Permission: manage_org

Parameter Type Required Description
orgId string yes —
basePermission "none" \ "read" \ "write"

set_org_member_role#

Change another org member's role to owner or member. An org has two roles: an owner manages the org — its members, their roles and its base permission — and may do everything on every library the org owns without a grant; a member holds the org's base permission on those libraries. The last owner cannot be demoted, and you cannot change your own role — another owner must. Only an owner of the org may call it (manage_org).

Permission: manage_org

Parameter Type Required Description
orgId string yes —
principalId string yes —
role "owner" \ "member" yes

share_node#

Share a file or folder subtree with a principal, an org, an email (resolved to an existing account), or the public, at an access level: view (read), comment (read + comment), or edit (read + comment + write). Public shares appear at /{owner}/{library}{path}. Requires share permission on the path. 'libraryId' takes the library's address, 'usesource:///' — the two segments list_libraries returns as 'address', and the first two segments of any 'address' a read hands back. A bare library id still resolves as a legacy pointer, and the response then carries 'libraryNotice' naming the canonical address to use instead. Three forms are refused as invalid_input: a 'source://' string (the scheme was renamed and is never rewritten for you), a one-segment 'usesource://' (a library slug is unique only within its owner, so the owner segment is never inferred from the caller), and a full document address (segments 1 and 2 are the library; everything after them belongs in the separate 'path' argument). An address naming no library you may read answers not_found, whether it does not exist or you hold no grant on it.

Permission: share

Parameter Type Required Description
libraryId string yes —
path string yes —
subject object \ object \ object \
access "view" \ "comment" \ "edit"

write_file#

Create or overwrite a markdown file at path (must end in .md). Missing parent folders are created. 'version' is REQUIRED to overwrite and OMITTED to create: send the document 'version' that read_file, read_section, list_sections or search returned for this path, and a call that omits it for a path that already holds a file returns invalid_input. Creating needs no version because a create destroys nothing — but if another caller creates the path first, the create returns version_conflict carrying that file's current version rather than silently destroying their file. A stale version returns version_conflict with the current version and no content; call read_file for the bytes. Returns the node and 'notices', on a create and on an overwrite alike. 'notices' is always present and is a list of objects, empty when there is nothing to report — never a list of strings. Each entry has 'code' (what happened, for branching), 'message' (the same thing in prose), and, where the code calls for them, 'ref', 'previousRef', 'line', 'info', 'destination' and 'source'. 'ref' is the address the notice is about IN THE DOCUMENT AS IT NOW STANDS ('' is the preamble's address); 'previousRef' is the address it had before this write; 'line' is 1-based in the written document; 'destination' is a link exactly as its author typed it; 'source' is the address of the document a link is written in, on the inbound link codes only. Branch on 'code' and those fields — never by parsing 'message'. A code's SHAPE says who minted it: a code of the form 'MD###' is markdownlint's own and means exactly what markdownlint's documentation for that rule says, and every other code is this system's, snake_case and never hyphenated. Only the 'MD###' codes carry 'info'. Validity codes, which report whether the document produced is sound markdown rather than what the edit did to it, and whether THIS viewer will render it as written. 'unclosed_code_fence' — a fenced code block that is never closed, so everything after it becomes part of the code block. It carries 'line', the opening fence's line, and 'ref', the section enclosing it. It is a WARNING, never a rejection: the write applies and the document holds exactly the bytes you sent; nothing is repaired and no closing fence is inserted, because where you meant to close it is a guess. 'literal_escape_sequence' — a line whose PROSE holds a backslash the markdown parse left literal, immediately followed by a lowercase 'n', 't' or 'r', so the reader sees the two characters '\n', '\t' or '\r' rather than a line break, a tab or a carriage return. Only prose is examined — paragraphs, headings, list items, blockquotes, table cells, emphasis and a link's bracketed text; code blocks, inline code, raw HTML, link and image destinations and titles, image alt text, autolinks and link reference definitions are NEVER examined. A backslash escaped as '\' is not reported, which is how to keep a backslash you meant: write '\n' for the characters '\n'; an unescaped one is reported whatever was meant, so 'C:\new' is. One entry per line however many sequences it holds, carrying 'line' and 'ref', with 'message' naming which of the three the line holds, how many of each, and both repairs; NEVER 'info'. It is a WARNING like 'unclosed_code_fence': the write applies and the document holds exactly the bytes you sent — nothing is repaired or unescaped. Five RENDER-FIDELITY codes report the conditions under which well-formed markdown will not render as you meant, because of what this viewer's render path does to it — facts no general markdown linter can name: 'raw_html_block_dropped' (a block of raw HTML, such as a '' on its own line: the viewer drops the block and everything inside it) and 'raw_html_inline_dropped' (a raw HTML tag inside text, such as '': the tag is dropped and the surrounding text is kept; an opening and a closing tag are two entries, each naming its tag in 'message'; a tag inside a code block is code and is never reported, and an HTML comment is not reported either); 'url_scheme_stripped' (a link, image or link reference definition whose URL scheme the viewer does not keep in that position, such as 'javascript:' on a link or 'data:' on an image, so the URL is stripped and the text is shown unlinked or the image empty; 'message' names the schemes kept in that position; a reference definition is reported once, at the definition); 'mermaid_unknown_diagram_type' (a mermaid fence whose first line names no diagram type the renderer knows — a typo, or another tool's syntax) and 'mermaid_syntax_error' (a mermaid fence of a known type whose source does not parse; 'line' is the source line the renderer named, as a line of the written document, or the fence's opening line when it named none). Each of the five carries 'line', the construct's line in the written document, and 'ref', the section enclosing it, and NEVER 'info'; 'message' carries what will be lost and, for the two mermaid codes, the renderer's own words. All five are WARNINGS that leave the stored bytes exactly as sent, the same as 'unclosed_code_fence'. A mermaid fence is checked by PARSING it with the same renderer the viewer draws with, and never by laying it out: a diagram that parses but fails to draw in a particular browser is reported nowhere but beside the fence, to the reader. A fence the parse could not be run against — the check did not start, timed out or failed — yields NOTHING rather than a guess, so the absence of both mermaid codes does not by itself mean the diagram will draw. Beside them, BORROWED codes, which are markdownlint's own and not this system's: an entry whose 'code' has the form 'MD###' was produced by markdownlint itself running over the document this write stored, and means exactly what markdownlint's documentation for that rule says — look it up there, and 'info.url' links that page for the version that ran. Exactly four rules run, all of them conditions that make the document RENDER WRONG rather than style opinions: 'MD011' (a reversed link, '(text)[url]', which renders as plain text), 'MD042' (an empty link, 'x', which renders as nothing), 'MD052' (a reference link whose '[label]:' definition is missing anywhere in the document, which renders as literal brackets) and 'MD056' (a table row whose cell count differs from its header's, silently dropping or adding data). Nothing else in markdownlint's catalogue runs — no line-length, whitespace or heading-structure opinions, and not its raw-HTML rule, whose condition the two raw_html codes report under this system's own name. Borrowed entries carry 'line' and 'ref' like the validity codes, plus 'info': 'info.url' is the rule's documentation and 'info.fix' is the repair markdownlint described, relayed and NEVER applied. They are warnings too — nothing is repaired, and they never reject a write. Both families are scoped to the content THIS write supplied: write_file supplies the WHOLE document, so a finding anywhere in what it stored is in scope, on a create and an overwrite alike. A defect already present in that content is reported even though this write did not introduce it, and a defect elsewhere in the document is not reported even though the document still contains it. Link codes, which report what the markdown links in the content resolve to — an inline link, a reference link or an autolink, never a bare path in prose or backticks, never an image, raw HTML or another site's URL. A link beginning with a scheme ('usesource:////', or this deployment's own https URL) is absolute and may name another library; one beginning '/' is rooted at THIS library's root and never consults the owner namespace, so '/acme/handbook/x.md' is a path here even if an owner 'acme' exists; anything else is relative to the linking document's folder, and one that climbs above the library root resolves nowhere. Percent-encoding is decoded and the path is normalized exactly as a stored path is; the '.md' extension is required of a file and never guessed. 'link_unresolved' — the destination resolves to no live document or folder you may read (a missing target and one you may not read are reported identically), climbs above the root, or is malformed; the 'message' says which where saying so discloses nothing. 'link_fragment_unresolved' — the target resolves but its '#fragment' names no section of it, or is a bare slug naming more than one; a bare '#fragment' with no path is checked against the document written. 'link_redirected' — the target has moved, so the link resolves only through a forwarding address; point it at the new path. Each carries 'destination' (the link exactly as written), 'line' and 'ref'. THESE ARE WARNINGS: an unresolved link never fails a write, nothing is rewritten, no extension is appended and no redirect is followed into the stored bytes — a forward reference to a document you are about to create is ordinary, and it starts resolving the moment its target exists. They are scoped like the validity codes: every link in the document, on a create and an overwrite alike. One entry per occurrence, even where two occurrences share one destination. At most 50 notices of each capped family — the link codes, the validity codes and the borrowed 'MD###' codes — are returned per response, each family counted separately, in the order they were found; consequence codes are never capped. When more conditions held than are listed, the response carries 'noticesTruncated': true and 'noticesBound', one sentence naming the cap and the families it cut; otherwise neither field is present. The cap changes nothing about what the operation did or stored — every condition still took effect — and it is distinct from 'bound', which folder operations use for their 400-write commit chunks. Note the asymmetry against replace_section and replace_in_section: write_file's 'notices' carries findings about the DOCUMENT PRODUCED — the validity codes, the link codes and markdownlint's borrowed 'MD###' codes alike — and never the consequence codes those two return ('section_address_moved', 'section_destroyed' and the rest). A whole-document write moved every section deliberately, so 'which section moved' has no meaning for it — use replace_section when you want that reported. The set of codes may grow: treat a code you do not recognize as informational and keep processing the rest of the response. For the same reason an empty 'notices' does NOT mean the document is sound markdown — it means nothing from the REPORTED SET was found in the content this write supplied, and the reported set is this system’s own codes above plus those four markdownlint rules and no others — and a mermaid fence the parse could not be run against contributes nothing to it. markdownlint’s remaining rules never run, so a condition outside that set is neither checked nor reported, and no other markdown checker runs at all. 'comments' is optional: up to 10 comment operations committed together with this write — {op: 'add', sectionRef, body, textRange?} to add a comment, {op: 'reply', commentId, body} to reply to one, {op: 'archive', commentId, version} to archive one with the 'version' a read carried for it. The write and its comment operations apply together or not at all: every precondition is checked before anything changes — the write's own version first, then for each operation in order that its comment exists (not_found), that a reply's comment is not archived and has room (invalid_input), and that an archive's version is current (version_conflict naming the comment in 'commentId') — then that each add's sectionRef resolves in the content this write PRODUCES (invalid_input). An add is anchored and recorded against the content this write leaves behind. Any entry requires comment permission on the path as well as the write's own; the write permission then meets an archive's grounds, so fixing a section and archiving the comment about it is one call. A call carrying 'comments' returns 'commentResults': one entry per operation, in order, each with 'op', 'applied', 'commentId' (for an add, the new comment's id) and, for an add or a reply, the comment's new 'version'. A write that commits nothing applies none of them and reports each with applied: false. Whatever the call carries, this write keeps every comment on the document pointing at its text: a comment on a section that moved follows it, and one whose section is gone is re-attached to the nearest surviving ancestor, else a sibling, else the top of the document, and marked 'displacedFrom' with the address it had. A whole-document write re-points nothing exactly: a comment keeps its anchor when the new content has a section at the same slug path, and is otherwise re-attached and marked 'displacedFrom' — reported on the comment, where the next read shows it, never in this response's 'notices'. Requires write permission on the path. A library whose 'mode' is 'repository' reflects one GitHub repository and refuses this call for every caller, its owner included, with repository_bound — 409 on REST, the same code on MCP. It is a state of the library, not a permission, and it is decided before permissions and 'version' are checked. The error names where to make the change instead: 'repository' (owner/name), 'branch', 'path' (the library path, which is also the repository path) and 'url' (that path on GitHub). The three origin fields are omitted when the origin cannot be resolved; the call is refused all the same. A caller who may not read the path gets permission_denied instead. 'libraryId' takes the library's address, 'usesource:///' — the two segments list_libraries returns as 'address', and the first two segments of any 'address' a read hands back. A bare library id still resolves as a legacy pointer, and the response then carries 'libraryNotice' naming the canonical address to use instead. Three forms are refused as invalid_input: a 'source://' string (the scheme was renamed and is never rewritten for you), a one-segment 'usesource://' (a library slug is unique only within its owner, so the owner segment is never inferred from the caller), and a full document address (segments 1 and 2 are the library; everything after them belongs in the separate 'path' argument). An address naming no library you may read answers not_found, whether it does not exist or you hold no grant on it. Everything returned carries its canonical 'address' beside its 'path': a document's is 'usesource:///', a section's is that plus '#', and a library's is 'usesource:///'. The address is derived from the path returned beside it, so the two cannot disagree, and it can be pasted into a link or sent straight back as 'libraryId' (its first two segments).

Permission: write

Parameter Type Required Description
libraryId string yes —
path string yes —
content string yes —
version string \ null no
comments array \ null no
usesource://usesource/docs/api.md