Tool reference¶
The server registers 31 tools. Names are snake_case. Every tool except auth_status,
list_projects, create_project, and import_project_zip takes a projectId from
list_projects or from one of the tools that create a project. An id never contains /, \,
., ?, #, or %; one that does is refused with INVALID_ARGUMENT before anything is sent.
Results are JSON; auth_status, list_projects, the project lifecycle tools, upload_file,
batch_upload, download_project_zip, the folder sync tools, and the compilation tools other
than stop_compile also declare an outputSchema and return the same object as
structuredContent. Failures are JSON with code, message,
retryable, and optional details; the codes are listed in the
safety model. The Overleaf routes behind each tool are catalogued in the
private API page.
Each tool declares MCP annotations: read-only tools change nothing on Overleaf and write
nothing locally; destructive tools can replace or remove existing content, on Overleaf or on
the server's disk. Clients may use these to decide when to ask the user. When the
access policy is configured, any tool can be refused with
POLICY_DENIED or PATH_OUTSIDE_ROOT before anything is sent.
Replacements must be asked for. Since 0.6.0 an upload that would replace something, a folder
sync without a planToken, and a batch_upload onto taken paths are refused with nothing sent
unless the call says so: overwrite or expectedHash for a binary, uncheckedDocumentReplace
for a text document, onConflict for a batch, and planToken or unplanned for a sync. 0.5.x
announced these with a deprecations list in the result; the
safety model lists what changed.
Account and projects¶
auth_status read-only¶
Verify the saved web session without exposing cookies.
No parameters.
Returns authenticated: true, baseUrl, the account's userId when known, projectCount,
sessionExpiresAt (ISO 8601, when the session lapses unless a request refreshes it first;
absent when no cookie carries a deadline), permissionsUnchecked (true on filesystems without
POSIX modes), an optional warning, and socketPresenceNotice explaining that an open project
connection can show the account as online. The result is also returned as structuredContent.
A missing or expired session fails with AUTH_EXPIRED.
list_projects read-only¶
List the projects the account can access, newest first by default.
| Parameter | Required | Meaning |
|---|---|---|
query |
no | Case-insensitive substring of the project name |
includeArchived |
no | Include archived projects; default false |
includeTrashed |
no | Include trashed projects; default false |
limit |
no | Maximum projects returned; default 50, at most 200 |
sort |
no | lastUpdated (newest first, the default) or name |
Returns projects, an array of { id, name, accessLevel, lastUpdated, archived, trashed },
plus totalMatched (how many passed the filters before limit) and totalProjects (everything
the account can access, archived and trashed included). Overleaf returns the whole list in one
response, so filtering and limit happen in the server; there is no server-side pagination to
expose. The result is also returned as structuredContent.
Project lifecycle¶
create_project¶
Create a new project and return its id.
| Parameter | Required | Meaning |
|---|---|---|
name |
yes | Project name, 1 to 150 characters without slashes |
template |
no | blank (default) or example, Overleaf's example paper |
Returns projectId, name, url, and rootDocPath. A blank project still contains Overleaf's
stub main.tex as its root document. After adding the real manuscript, point the project at it
with update_project_settings or delete the stub with manage_entity; otherwise Recompile builds
the stub. If the project was created but its tree could not be read, the error carries the new
projectId in details so the assistant does not create a duplicate.
clone_project¶
Copy an existing project, files and settings included, into a new one.
| Parameter | Required | Meaning |
|---|---|---|
sourceProjectId |
yes | Project to copy |
name |
yes | Name of the copy |
Returns projectId, name, and url.
import_project_zip¶
Create a new project from a local .zip archive of LaTeX sources.
| Parameter | Required | Meaning |
|---|---|---|
localZipPath |
yes | Local path of a .zip archive |
name |
no | Project name; defaults to the archive's file name without .zip |
Returns projectId, name, and url. Overleaf caps archives at about 50 MB
(UPDATE_TOO_LARGE) and rate-limits this route: RATE_LIMITED means nothing was created and
details.retryAfterMs, when present, says how long to wait. Overleaf's rejections
(invalid_zip_file, empty_zip_file, zip_contents_too_large, invalid_filename) surface as
INVALID_ARGUMENT with the code in details.overleafError. If the archive has several top-level
.tex files, set the root with update_project_settings afterwards.
manage_project destructive¶
Rename, trash, restore, archive, unarchive, or permanently delete a project.
| Parameter | Required | Meaning |
|---|---|---|
projectId |
yes | Project id |
action |
yes | rename, trash, restore, archive, unarchive, or delete |
newName |
for rename |
New name, 1 to 150 characters without slashes |
confirmName |
for trash, archive, delete |
Must equal the current project name exactly, else CONFIRMATION_MISMATCH |
Returns action, projectId, and the project's name after the action. trash is the normal
way to remove a project: it is reversible with restore or from the web UI's Trashed view.
delete is permanent and only succeeds on a project that is already trashed; on a live project it
fails with INVALID_ARGUMENT and changes nothing. The name check happens before any request is
sent, so a wrong confirmName never reaches Overleaf. Trashed and archived projects disappear
from list_projects unless includeTrashed or includeArchived is set.
update_project_settings¶
Persist compile and editor settings in the project itself, so the web UI follows them too.
| Parameter | Required | Meaning |
|---|---|---|
projectId |
yes | Project id |
rootFilePath |
one of | Document Overleaf should compile by default; must be an existing text document |
compiler |
one of | pdflatex, latex, xelatex, or lualatex |
imageName |
one of | TeX Live image name, as shown in Overleaf's menu |
spellCheckLanguage |
one of | Overleaf language code such as en or de; "" turns spell checking off |
At least one setting is required. Returns projectId, rootDocPath, compiler, imageName,
and spellCheckLanguage as re-read from a fresh project join, so the result reflects what
Overleaf actually stored. A rootFilePath that does not exist fails with NOT_FOUND; one that
names a folder or binary file fails with INVALID_ARGUMENT.
Projects and files¶
get_project_tree read-only¶
Return the file and folder tree together with the project's compile settings.
| Parameter | Required | Meaning |
|---|---|---|
projectId |
yes | Project id |
Returns entities, an array of { id, name, path, type, parentFolderId, hash? } where type is
doc (text), file (binary), or folder, plus rootDocPath (the document Overleaf compiles by
default), compiler, imageName (the TeX Live image), spellCheckLanguage when spell checking
is on, trackChangesActive, and hashNote.
hash is present only on binary file entities and is a git blob hash:
sha1("blob " + byteLength + "\0" + content), exactly what git hash-object <file> prints. Plain
sha1sum never matches. Text documents have no hash and must be compared by reading them.
read_file read-only¶
Read a text document.
| Parameter | Required | Meaning |
|---|---|---|
projectId |
yes | Project id |
filePath |
yes | Project-relative path with forward slashes |
Returns content with line endings normalized to LF, the opaque revision needed for any write,
newline: "LF", the document's OT protocol, and trackChangesActive.
write_file destructive¶
Replace a text document with a revision-checked, minimal, verified edit.
| Parameter | Required | Meaning |
|---|---|---|
projectId |
yes | Project id |
filePath |
yes | Document path |
revision |
yes | The revision from a prior read_file or write of this document |
content |
one of | Complete replacement text |
localPath |
one of | Local UTF-8 file holding the complete replacement. Non-UTF-8 content is rejected; a leading byte order mark is stripped |
writeMode |
no | untracked (default) or tracked to record the edit as Overleaf tracked changes |
Exactly one of content and localPath must be given. Returns the new revision, protocol,
trackChangesActive, writeMode, and recoveredAfterTimeout: true when the write was confirmed
during the recovery window after a timeout. A no-op write succeeds and records nothing. Fails with
REVISION_CONFLICT if the document changed since the revision was read.
create_file¶
Create a text document, optionally with initial content.
| Parameter | Required | Meaning |
|---|---|---|
projectId |
yes | Project id |
filePath |
yes | Path of the new document; parent folders must exist |
content |
no | Initial text |
writeMode |
no | untracked (default) or tracked; tracked requires non-empty content |
Returns the document's revision, protocol, trackChangesActive, and writeMode. Creating the
entity itself is always an ordinary project-tree operation; only the initial content can be
tracked.
manage_entity destructive¶
Create a folder, or rename, move, or delete an existing document, file, or folder.
| Parameter | Required | Meaning |
|---|---|---|
projectId |
yes | Project id |
action |
yes | create_folder, rename, move, or delete |
path |
yes | For create_folder, the folder to create; otherwise the entity to act on |
newName |
for rename |
New name without slashes |
destinationFolderPath |
for move |
Target folder; "" is the project root |
confirmPath |
for delete |
Must equal path exactly, else CONFIRMATION_MISMATCH |
Returns the action, the affected entity id (or the created folder as created: { _id, name }),
and trackChangesActive. Deleting a folder removes everything inside it.
upload_file destructive¶
Upload a local file into a project folder.
| Parameter | Required | Meaning |
|---|---|---|
projectId |
yes | Project id |
localPath |
yes | Local file to upload |
destinationFolderPath |
no | Target folder; default "", the project root |
destinationName |
no | Name to store the file under; defaults to the local file name |
overwrite |
no | true allows replacing a binary file at the path; false refuses to replace anything. Omitted, replacing a binary needs expectedHash; a document is governed by uncheckedDocumentReplace |
expectedHash |
no | The hash get_project_tree reported for the binary file being replaced, 40 lowercase hex characters |
uncheckedDocumentReplace |
no | Allow replacing a text document, which has no revision check |
If an entity already exists at the destination path and the call asks for it to be replaced,
Overleaf replaces it; otherwise a new entity is created. A text document replaced by a text document keeps its entity id. A replaced binary
file may get a new one, as may a document replaced by a binary or the reverse (Overleaf's source
creates a new entity in those cases), so look the id up again in get_project_tree rather than
reusing the old one. Overleaf, not the caller, decides whether the result is a text doc or a
binary file, by extension and UTF-8 validity, so this is a valid way to replace .tex, .bib,
and .bst documents from disk.
What is at the path decides what the call needs, and every check runs inside the upload's queue job, after a fresh look at the tree and before anything is sent:
| At the path | Result |
|---|---|
| nothing | uploaded |
a binary file, with overwrite: true or a matching expectedHash |
replaced |
| a binary file, with neither | CONFIRMATION_MISMATCH (details.missing: "overwrite"), nothing sent |
a text document, with uncheckedDocumentReplace: true |
replaced |
| a text document, without it | INVALID_ARGUMENT (details.missing: "uncheckedDocumentReplace"), naming write_file with localPath, nothing sent |
a text document or binary file, with overwrite: false |
CONFIRMATION_MISMATCH, nothing sent |
In 0.5.x the two refused rows still replaced the file and listed a deprecations entry.
An expectedHash that does not match what is at the path, nothing and a text document included,
fails with REMOTE_DRIFT and nothing is sent. It is a preflight, not a compare-and-swap: the
upload route accepts no expected state, so a replacement that lands between the check and the
upload is lost. Replacing a text document also needs the unchecked-replace effect when the
access policy limits effects.
Replacing a document this way is a blind write with no revision check. Overleaf compares the new
text with the old and applies the difference, and records it as tracked changes when track
changes is already on for this account; otherwise it is untracked. The caller cannot choose
either way. A new file created by upload is never tracked content. A folder of the same name in
the destination folder fails the upload with INVALID_ARGUMENT (details.overleafError:
"duplicate_file_name").
Returns entityId, entityType, path, replaced (whether something existed at that path),
for binary files hash, computed locally so it can be checked against get_project_tree later,
trackChangesActive, and writeMode. writeMode is tracked when a text document replaced a text document while track
changes is on for this account, because Overleaf then records the difference as tracked changes,
and untracked otherwise: for a binary, a new file, or with track changes off.
batch_upload destructive¶
Upload a list of local files, each to its own project path, in one call.
| Parameter | Required | Meaning |
|---|---|---|
projectId |
yes | Project id |
files |
yes | 1 to 500 entries { localPath, destinationPath }. destinationPath is the full project path, file name included, such as figures/fig1.pdf |
onConflict |
no | What to do when a document or binary file is already at a destination path. fail, the default, refuses the whole call before anything is uploaded; skip leaves what is there alone and lists it under skipped; overwrite replaces a binary file, as upload_file with overwrite: true does |
uncheckedDocumentReplace |
no | Allow replacing text documents, which has no revision check |
stopOnError |
no | Stop at the first failure instead of continuing; default false |
It is a composition of upload_file and manage_entity's create_folder, like the
folder sync tools, and saves tool calls, not time. What happens,
in order:
- Every entry is checked before anything is sent. The project root, a path ending in
/or., one with a..segment, adestinationPathlisted twice, and one that is the parent folder of another listed path areINVALID_ARGUMENT; alocalPaththat does not exist isNOT_FOUND, and one that is a folder isINVALID_ARGUMENT. Any of these fails the whole call with nothing changed. - The project tree is read once. With
onConflict: "fail", the default, any destination path that holds a document or binary file fails the whole call withCONFIRMATION_MISMATCH(details.missing: "onConflict",details.count, and up to 25details.paths) and nothing is uploaded. A file whose path runs through a document or binary file, or whose path is a folder, fails withINVALID_ARGUMENTin any mode. A file whose path holds a document or binary file is skipped withonConflict: "skip", and replaced with"overwrite", as byupload_file: a replaced document keeps its entity id, a replaced binary may get a new one. - Files are handled in the order given, one at a time through the project's queue. Just before a
file is uploaded, its missing parent folders are created, parents first, each once, and listed
in
completedascreate_folder. A folder that could not be created is not tried again, and every file under it fails with the folder's error code. WithonConflict: "skip"the path is checked again just before each upload, so a file someone added in the meantime is skipped rather than replaced. Failures are per file; the rest continue unlessstopOnError, after which every file not attempted is inremaining. The call also stops at the firstRATE_LIMITED, with or withoutstopOnError, since Overleaf would refuse the rest too: that file is infailed, the rest inremaining, and nothing more is sent. Overleaf allows about 500 uploads per project in 15 minutes, as many as one call can list, so a second large batch soon after the first can hit it. ARATE_LIMITEDfolder creation stops the call the same way. - The tree is read back once. An upload that is not there, or a binary whose hash differs from
the local file, moves to
failedwithREMOTE_ERROR. An upload that timed out is classified, never sent again: a binary now in the tree with the local file's hash moves tocompletedwithrecoveredAfterTimeout: true, since the path holds what was asked for whether or not this upload put it there. A path that was empty before and after, or holds the same binary with the same hash as before, stays failed withTIMEOUT: it had not landed when the tree was read, though it could still land late. Anything else, a document for example, since documents have no hash, stays failed withOUTCOME_UNKNOWN. Checkget_project_treebefore uploading that file again.
Overleaf decides whether each file becomes a text doc or a binary file, as for upload_file.
Replacing a document this way is a blind write with no revision check, so a collaborator's
concurrent edit is lost; Overleaf records the replacement as tracked changes only when track
changes is already on for this account. For documents someone may be editing, use write_file
with localPath, or plan_sync and sync_directory. Each file goes through upload_file's
checks: with onConflict: "overwrite", a document is replaced only with
uncheckedDocumentReplace: true, and otherwise that file fails with INVALID_ARGUMENT and
nothing is sent for it. In 0.5.x an omitted onConflict overwrote, and the result listed
deprecations.
Returns:
status:complete, orpartialwhen anything failed or was not attempted. Skipped files do not make it partial.onConflict, as applied.completed:{ destinationPath, action, entityId?, entityType?, replaced?, recoveredAfterTimeout? }, whereactioniscreate_folderorupload, andreplacedsays whether something was at the path before an upload.skipped:{ destinationPath, localPath, entityType }, withonConflict: "skip"only.failed:{ destinationPath, action, errorCode, message }.remaining:{ destinationPath, action }, what was not attempted afterstopOnErrororRATE_LIMITEDstopped the call.verified:truewhen the tree was read back and every completed upload was confirmed in it, or when nothing was attempted;falsewhen the read-back failed, in which case the entries are as the uploads reported them.
Nothing is retried. A client that sends a progress token receives notifications/progress as
each file is uploaded.
download_file destructive¶
Save one document or binary file to a local path. It changes nothing on Overleaf; it is annotated destructive, not read-only, because it writes a local file and can replace one.
| Parameter | Required | Meaning |
|---|---|---|
projectId |
yes | Project id |
filePath |
yes | Entity to download; folders are refused |
localPath |
yes | Where to write it |
overwrite |
no | Replace localPath if it exists; default false |
Returns bytes and localPath. Fails with INVALID_ARGUMENT when the local file exists and
overwrite is not true. A localPath that is the saved session's cookie jar or browser
profile, or lies outside OVERLEAF_LOCAL_WRITE_ROOTS when that is set, fails with
PATH_OUTSIDE_ROOT before anything is downloaded. The same goes for a localPath that leads
there through a symbolic link, or through a .. after one.
download_project_zip destructive¶
Save the whole project, sources and binaries, as one zip archive on the server's disk: the same archive as Overleaf's "Download as zip".
| Parameter | Required | Meaning |
|---|---|---|
projectId |
yes | Project id |
localPath |
yes | Where to write the archive; its folder must exist |
overwrite |
no | Replace localPath if it exists; default false |
timeoutMs |
no | How long the whole transfer may take, 1 second to 15 minutes; default 5 minutes |
The local checks run before anything is requested: the saved session's files, and a path outside
OVERLEAF_LOCAL_WRITE_ROOTS when that is set, are PATH_OUTSIDE_ROOT, a missing parent folder is
NOT_FOUND, a folder at localPath is INVALID_ARGUMENT, and an existing file without overwrite: true is
CONFIRMATION_MISMATCH (where download_file answers INVALID_ARGUMENT). The temporary file the
archive is streamed to is created in the same folder at this point too, so a folder that cannot be
written fails with INVALID_ARGUMENT (details.errno, such as EACCES) before the request uses
up any of Overleaf's allowance. The archive must start with a zip signature, else the call fails
with PROTOCOL_UNSUPPORTED, and end with a complete zip end record. Overleaf builds the archive
while it sends it, so a transfer cut short still arrives with HTTP 200; one without that end
record, or one that stops before its first four bytes, fails with REMOTE_ERROR,
retryable: true. Either way nothing is written. Only
a complete archive is moved into place. With overwrite: true the existing file is replaced in
one step, so a failed download leaves it intact; without it, a file that appeared at localPath
in the meantime is not replaced (CONFIRMATION_MISMATCH). The temporary file is removed on every
failure.
The end check catches a truncated transfer, not a missing file: if Overleaf fails to read one
file while building the archive, it leaves that file out of an otherwise well-formed archive and
reports no error. For a backup that must be complete, compare the archive's contents with
get_project_tree.
Overleaf allows about 10 downloads per project a minute. It sends no Retry-After, so
RATE_LIMITED normally has no details.retryAfterMs: wait a minute before trying again. Nothing
is written. Overleaf checks this route's project access rather than the login, so an expired
session shows up as PERMISSION_DENIED rather than AUTH_EXPIRED; check auth_status.
It changes nothing on Overleaf; it is annotated destructive because it can replace a local file.
Take one as a backup before a mirror sync_directory or a large batch_upload with
onConflict: "overwrite".
Returns projectId, localPath resolved to an absolute path, bytes, and replaced (whether a
file existed at localPath).
Choosing between write_file and upload_file¶
| Need | Tool | Revision check | Tracked changes | Content source |
|---|---|---|---|---|
| Small edit, or collaborators may be editing | write_file with content |
yes | optional | inline |
| Replace a large text file safely | write_file with localPath |
yes | optional | disk |
| Replace a binary, or push text when nobody else is editing | upload_file with overwrite or uncheckedDocumentReplace |
no | account setting | disk |
| Upload a list of files to paths you choose | batch_upload |
no | account setting | disk |
| Bring a whole folder up to date | plan_sync, then sync_directory |
yes, for documents | optional | disk |
"Account setting" means the caller does not choose: replacing an existing document by upload is recorded as tracked changes when track changes is already on for this account, and untracked otherwise. A binary, or a new file, is never tracked.
Neither is limited by file size in practice: DOC_TOO_LARGE applies at the advertised
ol-maxDocLength (2,097,152 UTF-16 code units by default) and UPDATE_TOO_LARGE at 7,340,032
serialized characters. A 115 KB document uses about 5% of the document limit. The practical
ceiling on inline content is the MCP client's tool-argument budget, which localPath avoids.
Folder sync and bulk delete¶
plan_sync and sync_directory compare a local folder with a project folder and make the
project match it; delete_entities removes several entities at once. All three are compositions
of the tools above, so they inherit their checks: changed text documents are replaced through
the same revision-checked, verified edit as write_file, binaries through the same upload as
upload_file, and deletes through manage_entity. They save tool calls and context,
not time: every step still runs through the project's one queue, one after another. A client that
sends a progress token receives notifications/progress as documents are read and files are
applied.
The usual sequence is plan_sync, a look at the plan with the user, then sync_directory with
the plan's planToken. The reference cleanup the roadmap was written from, four changed files
uploaded, twenty stale entities deleted, and nine identical figures left alone, is those two
calls.
How the two sides are compared¶
| Local | Project | Compared by | Result |
|---|---|---|---|
| file | nothing | toUpload, reason: "new" |
|
| file | binary file |
git blob hash against the tree's hash, at no cost |
identical, or toUpload with comparedBy: "hash" |
| UTF-8 file | text doc |
reading the document and comparing LF-normalized text; a leading byte order mark is ignored | identical, or toUpload with comparedBy: "content" |
| non-UTF-8 file | text doc |
conflicts, not_utf8_text |
|
| file | folder | conflicts, local_file_remote_folder |
|
| folder | doc or file |
conflicts, local_folder_remote_file; nothing inside the folder is compared |
|
| nothing | anything | remoteOnly, collapsed to the highest folder |
Documents carry no hash, so each document with a local counterpart, and each one only the
project has, is read once, one document join through the project's queue. That suits tens of
documents, not thousands. A binary stored without a hash, which only very old projects have,
is listed as changed without comparedBy.
Ignore rules are gitignore-style and apply to both sides. The defaults are .git/,
.DS_Store, hidden files and folders (.*), __MACOSX/, and LaTeX build output: *.aux,
*.log, *.bbl, *.blg, *.out, *.toc, *.synctex.gz, *.fdb_latexmk, and *.fls. A
.olignore file at the top of the local folder, the one overleaf-sync uses, is applied next,
and the ignore parameter last, so "!.latexmkrc" re-includes a file a default excludes.
Patterns match case-insensitively, and a folder pattern such as build/ covers everything
inside. An ignored path is never uploaded, and a project entity an ignore rule matches is
never compared or deleted, so mirror mode cannot remove a file the sync was told to leave
alone. A folder that holds such a protected entity is not deleted as a whole; its other contents
are listed one by one.
Local paths. localFolderPath is resolved on the disk of the machine the server runs on, so
pass an absolute path: a relative one resolves against the server's working directory, which the
MCP client chooses. plan_sync reports the path it resolved.
Symbolic links are followed only when they resolve inside it; one that leads outside fails the
call with PATH_OUTSIDE_ROOT before anything is compared, unless an ignore pattern excludes
it. A linked folder that leads back into one of its own parents is refused as a cycle. The
walk stops with INVALID_ARGUMENT above 2,000 files that are not ignored, Overleaf's own limit
for one project, or 20,000 entries in all. Files are hashed as they are read, streaming those
larger than 8 MB.
plan_sync read-only¶
Compare a local folder with a project folder and report what sync_directory would do. Nothing
is changed.
| Parameter | Required | Meaning |
|---|---|---|
projectId |
yes | Project id |
localFolderPath |
yes | Local folder on the server's disk |
destinationFolderPath |
no | Project folder the local folder corresponds to; default "", the project root |
ignore |
no | Extra gitignore-style patterns, applied after the defaults and .olignore |
verbose |
no | List every identical path and ignored entry instead of the first 25; default false |
Returns:
planToken, to pass tosync_directory.localFolderPath, resolved to an absolute path, anddestinationFolderPath.toUpload:{ localPath, destinationPath, reason, comparedBy?, remoteType? }for every new or changed file.localPathis relative to the local folder;remoteTypesays what the project holds today for a changed entry,doc(replaced with a revision-checked write) orfile(replaced by upload).identical:{ count, paths }, the first 25 paths unlessverbose.remoteOnly:{ destinationPath, entityId, type, contains? }for what mirror mode would delete. A folder the local side does not have is one entry, andcontainssays how many entities inside it go with it.conflicts:{ localPath, destinationPath, reason, message }for what a sync cannot apply on its own. Resolve these by hand, for example withdelete_entities, then plan again.ignored:{ count, entries }, each{ localPath, matchedPattern }, or{ localPath, reason }for a broken symbolic link or something that is neither a file nor a folder. An ignored folder is one entry ending in/; nothing inside it is read.
sync_directory destructive¶
Make a project folder match a local folder.
| Parameter | Required | Meaning |
|---|---|---|
projectId |
yes | Project id |
localFolderPath |
yes | Local folder on the server's disk |
mode |
yes | additive uploads and writes only; mirror also deletes what exists only in the project |
destinationFolderPath |
no | As for plan_sync; default "" |
planToken |
unless unplanned |
From plan_sync, or from a partial sync_directory to resume it |
unplanned |
no | true runs an additive sync without a planToken on purpose; refused with a planToken or in mirror mode |
confirmDeleteCount |
for mirror |
The number of entries in plan_sync's remoteOnly, each folder counting once |
ignore |
no | As for plan_sync; must match the plan's when a planToken is given |
writeMode |
no | untracked (default) or tracked |
stopOnError |
no | Stop at the first failure instead of continuing; default false |
What happens, in order:
- Before anything is read:
unplanned: truewith aplanToken, or in mirror mode, isINVALID_ARGUMENT, and a call with neither aplanTokennorunplanned: trueisCONFIRMATION_MISMATCHwithdetails.missing: "planToken". The folder and the project are then compared again, exactly asplan_syncdoes. Withunplanned: truethere is no plan to compare them with, so the additive sync applies what it finds now. With aplanToken, a sync whose project side or local side differs from the plan fails withREMOTE_DRIFTand changes nothing;details.changedisremote,local, orboth. The token covers every entity in scope, identical ones included, so a collaborator's edit to a file the plan called identical stops the sync instead of being overwritten. A token issued for a different project, folder, destination, or ignore list isINVALID_ARGUMENT. - In mirror mode,
confirmDeleteCountmust equal the number of remote-only entries, elseCONFIRMATION_MISMATCHand nothing changes. - Uploads and writes, in path order. Missing folders are created first, each once; a folder
that could not be created is not tried again for the next file inside it. Only folders that
hold an uploaded file are created, so an empty local folder is not reproduced. A changed document
is replaced with
write_filesemantics against the revision just compared, so a concurrent edit fails that one file withREVISION_CONFLICTand is never overwritten. A changed binary and every new file are uploaded, and Overleaf decides whether a new file is a document or a binary. The upload checks, just before it is sent, that a new path is still empty and a changed binary still has the hash the plan compared; anything else fails that file withREMOTE_DRIFT, which also withholds every delete. WithwriteMode: "tracked", changed documents are written as tracked changes, and new files Overleaf treats as text (.tex,.bib,.sty,.cls,.bst,.txt, and similar) are created as documents with tracked content, the waycreate_filedoes, since a new file created by upload is never tracked content; binaries are uploaded as usual. Conflicts from the plan are reported as failures. - The tree is read back, and an upload that is not there, or whose hash does not match the
local file, is moved to
failedwithREMOTE_ERROR. - Deletes, in mirror mode only, and only if nothing so far failed. Just before each delete, the
entity's id, and for a folder everything inside it, is checked against the plan; anything
that changed is left in place and reported as
REMOTE_DRIFT.
Nothing is retried automatically. Returns status (complete, or partial when anything failed
or was not attempted), mode, completed ({ destinationPath, action, entityId? }), failed
({ destinationPath, action, errorCode, message }), remaining ({ destinationPath, action },
what was not attempted), identicalCount, planToken, planned (whether the call was checked
against a planToken; false only with unplanned: true).
action is create_folder, upload, write, create, or delete.
The returned planToken describes the state this sync left: what it changed as the project now
shows it, and everything else as it was planned. Re-running with it resumes a partial sync, and
still stops with REMOTE_DRIFT if someone changed something in between, including a document
whose write failed with REVISION_CONFLICT: that one needs a fresh plan and a look at what the
collaborator changed. The token is absent when the project could not be read back after the
changes; plan again before resuming.
delete_entities destructive¶
Delete several documents, files, or folders in one call.
| Parameter | Required | Meaning |
|---|---|---|
projectId |
yes | Project id |
paths |
yes | Project paths, 1 to 500 |
confirmCount |
yes | Must equal the number of paths, else CONFIRMATION_MISMATCH |
stopOnError |
no | Stop at the first failure instead of continuing; default false |
Every path is resolved before anything is deleted, so a missing path fails the whole call with
NOT_FOUND and changes nothing. A path listed twice, or one inside a folder that is also listed,
is INVALID_ARGUMENT: deleting a folder removes everything inside it, so list the folder alone.
Returns status, completed ({ path, type, entityId }), failed
({ path, errorCode, message }), and remaining ({ path }).
LaTeX sections¶
Section tools work on one file at a time. They recognize starred headings and optional titles,
ignore % comments and common verbatim-like environments, and never follow \input or
\include.
get_sections read-only¶
Parse the section headings of one file.
| Parameter | Required | Meaning |
|---|---|---|
projectId |
yes | Project id |
filePath |
yes | Document to parse |
Returns sections, revision, and singleFileOnly: true. Each section has an id to pass to
the other two section tools, its command (for example section or subsection), level,
starred, title, optional shortTitle, and character offsets start, headingEnd,
bodyStart, and end into the LF-normalized content.
get_section_content read-only¶
Read one section's body.
| Parameter | Required | Meaning |
|---|---|---|
projectId |
yes | Project id |
filePath |
yes | Document |
sectionId |
yes | From get_sections |
Returns content (the body text), the matching section record, and the document's current
revision.
write_section destructive¶
Replace one section's body with a revision-checked write.
| Parameter | Required | Meaning |
|---|---|---|
projectId |
yes | Project id |
filePath |
yes | Document |
revision |
yes | From get_sections, get_section_content, or read_file |
sectionId |
yes | Section to replace |
content |
yes | New body |
writeMode |
no | untracked (default) or tracked |
Returns the same fields as write_file. Fails with REVISION_CONFLICT if the document changed.
Compilation¶
compile_project¶
Compile the project and report on the build from its logs.
| Parameter | Required | Meaning |
|---|---|---|
projectId |
yes | Project id |
rootFilePath |
no | Document to compile for this call only. Omitted, the root document configured in Overleaf is used, the same one the web editor's Recompile button builds |
timeoutMs |
no | How long to wait, 1 second to 15 minutes; default 120 seconds |
stopOnFirstError |
no | Stop at the first LaTeX error, for a short log holding the one error that matters |
draft |
no | Draft mode: images become boxes, for faster text-only builds |
Check summary.builtCleanly, not status. Overleaf compiles in nonstopmode and reports success
whenever a PDF came out, even when LaTeX hit errors on the way. builtCleanly is true only when
a PDF was produced, output.log was read, and it reports no error. After the build the server
reads the log, at most 1 MB of it, and any .blg BibTeX logs, and returns:
status: Overleaf's own,success,failure(LaTeX stopped without a PDF), orstopped-on-first-error.outputFiles:{ path, type, build? }for each output; the URLs Overleaf sends are dropped, since every output is fetched from the project's own route.stats: Overleaf's counters, numbers only.buildIdandclsiServerIdwhen Overleaf sent them.rootFilePath: the document actually compiled.buildRef: an opaque token naming this build toget_compile_loganddownload_compile_output, in this process or a later one.summary:{ status, builtCleanly, pdfProduced, logRead, errorCount, warningCount, errors, warnings, undefinedReferences, undefinedCitations, missingFiles, pageCount?, pdfSizeBytes? }.errorsandwarningshold the first 20 as{ file?, line?, message }; the counts cover all of them. A message is the error or warning line alone, cut to 240 characters, and never the source excerpt TeX prints afterl.<n>.logReadisfalsewhen the build left no log or it could not be read, andbuiltCleanlyis thenfalsetoo.
A build that ran, failure included, is a result, not an error. A compile that produced no build
to report on fails, with details exactly { status, rootFilePath, buildId?, buildRef? }
(plus retryAfterMs for COMPILE_RATE_LIMITED when known); a status that is not a short
lowercase identifier is reported as unrecognized:
| Overleaf status | Code | Retryable |
|---|---|---|
too-recently-compiled, rate-limited, autocompile-backoff, compile-in-progress, or HTTP 429 |
COMPILE_RATE_LIMITED |
yes |
clsi-maintenance, unavailable |
COMPILE_UNAVAILABLE |
yes |
timedout |
COMPILE_TIMEOUT |
no |
anything else, terminated, exited, project-too-large, and validation-problems included |
COMPILE_FAILED |
no |
A build that timed out or was terminated can still have left a log; its details.buildRef then
reaches it through get_compile_log. A TIMEOUT on the compile request itself is not retryable,
because Overleaf may still be compiling; wait, or call stop_compile, before compiling again. A
response without a status, or with malformed outputFiles, is PROTOCOL_UNSUPPORTED with
details { endpoint: "compile", expectedKeys, receivedKeys }. A project with no configured root
and no rootFilePath fails with INVALID_ARGUMENT. Compiles use the account's compile
allowance; timeoutMs bounds only the wait.
stop_compile destructive¶
Stop the active compile for a project.
| Parameter | Required | Meaning |
|---|---|---|
projectId |
yes | Project id |
Returns stopped: true.
get_compile_log read-only¶
Read a build's LaTeX or BibTeX log without compiling again.
| Parameter | Required | Meaning |
|---|---|---|
projectId |
yes | Project id |
buildRef |
no | From compile_project; default, the latest build compile_project made in this server process |
kind |
no | latex (default), output.log; or bibtex, the build's .blg file |
format |
no | raw (default), or errors-only for only the lines that start an error |
tail |
no | How many lines to return, from the end; default 200, at most 2,000 |
At most 1 MB of the log is read, from its start, as Overleaf's own editor does, and at most
100,000 characters are returned. Returns buildId, file, log, truncated (whether anything
of the log was left out), and errorLines, the first 100 lines that start an error as
{ line, text } with 1-based line numbers, each cut to 240 characters. The log quotes source
lines near each error; prefer compile_project's summary when it is enough.
Without a buildRef and with no compile in this process the call fails with NO_BUILD. A
buildRef for another project is INVALID_ARGUMENT. A log the build did not produce is
NOT_FOUND, and a build Overleaf no longer keeps is BUILD_NOT_FOUND: compile again.
download_compile_output destructive¶
Save a build's PDF, or another output file, to a local path on the server's disk.
| Parameter | Required | Meaning |
|---|---|---|
projectId |
yes | Project id |
localPath |
yes | Local file to write; its folder must exist |
file |
no | Output path in the build, such as output.log, output.blg, or output.synctex.gz; default output.pdf |
buildRef |
no | From compile_project; default, the latest build in this server process |
overwrite |
no | Replace localPath if it already exists; default false |
file must be a relative path without .., \, or control characters, else INVALID_ARGUMENT
before anything is requested. It is requested only from the project's own route,
/project/<projectId>/build/<buildId>/output/<file>, never from a URL in Overleaf's response. An
existing localPath without overwrite: true is CONFIRMATION_MISMATCH and nothing is
downloaded; a replacement is atomic, so a failed download leaves the old file. A .pdf that does
not start with %PDF- is PROTOCOL_UNSUPPORTED and nothing is written. NO_BUILD, NOT_FOUND,
and BUILD_NOT_FOUND are as for get_compile_log. Returns the absolute localPath, file,
sizeBytes, buildId, and replaced.
Compile output resources¶
The server also offers two read-only MCP resources for the latest build of a project in this
server process: overleaf://project/{projectId}/output/output.log (text, the first 1 MB) and
overleaf://project/{projectId}/output/output.pdf (a PDF of at most 16 MB; use
download_compile_output for a larger one). Reading one before the project was compiled fails
with NO_BUILD.
Review¶
Review comments and tracked changes need an Overleaf deployment and plan that support them. Positions use 1-based lines and UTF-16 columns.
list_comments read-only¶
List comment threads, resolving their document positions lazily.
| Parameter | Required | Meaning |
|---|---|---|
projectId |
yes | Project id |
filePath |
no | Only threads anchored in this document |
status |
no | open (default), resolved, or all |
author |
no | Only threads with messages by this author name |
Returns threads, each with id, status, messages (author, content, timestamp), and when
located filePath, start, end, and quotedText. A thread without a document range has
unlocated: true. If the deployment offers no project-wide range index and no filePath was
given, the result has positionsUnavailable: true rather than scanning every document.
reply_to_comment¶
Reply in an existing thread.
| Parameter | Required | Meaning |
|---|---|---|
projectId |
yes | Project id |
threadId |
yes | From list_comments |
content |
yes | Message text |
Returns the created message. A reply that timed out is accepted only when the refreshed thread shows a matching message from the current author within the request window, so no reply is posted twice.
add_comment¶
Create a thread anchored to exact text.
| Parameter | Required | Meaning |
|---|---|---|
projectId |
yes | Project id |
filePath |
yes | Document |
revision |
yes | From a fresh read_file |
start, end |
yes | { line, column }, 1-based, UTF-16 columns |
expectedText |
yes | Must equal the normalized text in that range exactly |
content |
yes | Comment text |
Returns the new thread's identifier and verification details. The thread is created and then attached to the range through OT; if attachment cannot be confirmed, the orphaned thread is cleaned up only after the unchanged document proves it never applied.
set_comment_status destructive¶
Resolve or reopen a thread.
| Parameter | Required | Meaning |
|---|---|---|
projectId |
yes | Project id |
filePath |
yes | Document the thread is anchored in |
revision |
yes | Latest revision of that document |
threadId |
yes | Thread to change |
status |
yes | open or resolved |
Returns the verified status and resulting revision.
History¶
monitor_project_history read-only¶
Poll one recent window of project history.
| Parameter | Required | Meaning |
|---|---|---|
projectId |
yes | Project id |
sinceVersion |
no | Return only update groups newer than this version |
Returns currentVersion, nextSinceVersion to pass on the next poll, hasEarlierHistory,
gapDetected (true when the cursor predates the single window returned), and updates, each with
fromVersion, toVersion, startedAt, endedAt, authors (id and display name; emails are
stripped), paths, projectOperations, labels, and origin. This is stateless polling, not a
background watcher, and it never pages backward or computes diffs.