overleaf-forge
v2.12.0
Published
MCP server to read, edit, compile, and verify Overleaf/LaTeX projects over git: conflict-safe edits, figure upload, clean-build verification, citation and voice linting.
Maintainers
Readme
overleaf-forge
A Model Context Protocol server that lets an AI assistant (Claude Code, Claude Desktop, or any MCP client) read, edit, compile, and verify an Overleaf project over Overleaf's built-in git integration. The model edits a local clone with surgical, conflict-safe operations and publishes verified batches to Overleaf. Nothing depends on scraping the web UI.
Acknowledgement. Forked from mjyoo2/OverleafMCP, the original Overleaf MCP server. This fork adds conflict-safe editing, binary/figure upload, a clean-build PASS/FAIL gate, citation and voice linting, snapshots, a bootstrap for recurring structured documents, per-project contexts, and a hardened git layer (no-shell
execFile, credential-helper auth, error redaction).
Why
Editing a LaTeX project through an AI normally means one of two bad options: paste files back and forth by hand, or let the model overwrite whole files and hope it didn't clobber an edit you made in the browser. This server removes both. It treats Overleaf's git remote as the source of truth and gives the model anchored edits (replace an exact string, refuse if it moved), a conflict gate (a stale or concurrent change is detected, never silently overwritten), and a build verdict (compile from scratch, PASS only on zero errors and zero undefined references), so an editing session is safe to run unattended and provable when it's done.
Token economy
Safety is one motivation. Keeping a large document out of the model's context window is the other, and it drove most of the tool design. A 50 KB chapter is about 12K to 13K tokens, so the cost of a naive workflow is dominated by moving that whole file in and out.
Measured token cost on a real 48 KB chapter (a 30-page report with a bibliography and minted listings). The whole-file column is an estimate of the read-and-rewrite alternative. The overleaf-forge column is measured over MCP, at about four characters per token.
| Operation (on a ~50 KB chapter) | Whole-file workflow (estimate) | overleaf-forge (measured) | Reduction |
| --- | --- | --- | --- |
| One surgical edit | ~25K (read + write the file) | ~25 tokens (edit_file) | >99% |
| A dozen edits (one revision pass) | ~170K | ~0.3K | >99% |
| One passing build check | ~1.5K to 2K (raw latexmk log) | ~4 tokens (verify_build PASS) | >99% |
| Locating a passage | ~13K (read the whole file) | ~2K (get_section_content / search_text) | ~85% |
| Re-reading unchanged guidance | full guidance again | ~25 tokens (get_context with previousVersion) | >99% |
The absolute numbers scale with file size. The percentages roughly hold.
- Anchored edits instead of whole-file rewrites. Changing one phrase by reading the whole file and writing it back costs roughly 25K tokens per edit: the file enters the context, then leaves it again as the write payload.
edit_filesends only the old and new strings and returns one line naming the new local commit. - A verdict instead of a log. A passing
verify_buildreturnsPASS: 30 pages.A failing one returns the counts, the first offending lines and the log path. A raw multi-pass log costs hundreds to thousands of tokens and buries the true final state under transient undefined-reference warnings from early passes, whichverify_buildavoids by classifying the final log only. - Section and grep reads instead of the whole file.
get_section_contentreturns one section andsearch_textreturns the matching lines, so locating something costs 1K to 3K tokens rather than 13K. - Fewer network round trips. Reads, builds and edits work on the local clone. In the measured session below, the previous release made 29 git network operations and this one makes 1, the final push.
A measured session. Context read, section read, twelve anchored edits, an intermediate build, the final gate, a context re-read and a publish, on the chapter above:
| | 2.9.1 | 2.12 | | --- | --- | --- | | Tool responses | ~13.9K tokens | ~5.5K tokens | | Tool definitions (paid once per session) | ~3.9K tokens | ~5.0K tokens | | Git network operations | 29 | 1 | | Wall time against a local stand-in remote | 53 s | 53 s |
The remaining wall time is LaTeX. Against real Overleaf, every one of the 28 avoided network operations is an HTTPS round trip. Most of the saving in tool responses comes from the guidance: a shorter default guide, and a compact reply when it is re-read unchanged. A revision session that also loads a long personal style reference narrows the gap.
Features
- Local-first, publish when verified: edits commit to the local clone and stay off the network until
publish_changesverifies the build and pushes the whole batch.settings.autoPushrestores push-on-every-write. - Surgical, conflict-safe edits:
edit_filereplaces an exact anchor and refuses if the region changed. When pushing, non-overlapping concurrent edits auto-merge via git. - No silent clobbering: full-file
write_filerequires a freshness token (baseSha) or an explicitoverwriteto replace an existing file. - Divergence recovery:
sync_projectreports what differs between the clone and Overleaf without changing anything, then resolves it by rebase (aborts cleanly on conflict) or by a confirmed reset that tags a backup first. - Binary / figure upload: add PNG/PDF figures from disk (single or a whole set in one commit), byte-exact, path-confined to the project.
- Build verification:
verify_buildcompiles from scratch withlatexmkand returns PASS/FAIL on the real "done" bar (a PDF, zero errors, zero undefined references/citations) with the page count. An unchanged successful build is reused instead of rebuilt.clean: falsegives a quicker incremental rebuild for intermediate checks, andlintmakes voice-linter findings fail the gate. - Citation tooling: append BibTeX entries with duplicate-key protection, and lint for undefined and unused citations.
- Section-aware reading: list sections and pull a single section's body by title, optionally bundled with the equations, figures and bibliography entries it references.
- Dependency tracking:
dependency_indexandchange_reportname the sections affected by a changed label, value, citation or included file. - Project grep:
search_textover tracked files. - Snapshots:
checkpointa rollback point before a risky edit, thenrestoreit as a forward commit (no force-push, no history rewrite). - Recurring-document bootstrap: one call to clone, register, and scaffold a new instance of a structured document (see Bootstrap).
- Per-project context: durable notes and writing guidelines surfaced to the model at the start of a session.
- Hardened git layer: every subprocess runs through
execFile(no shell), the token is supplied via an environment-backed credential helper and never appears in a command or an error, and tokenized URLs are redacted from any error returned.
Requirements
- Node.js ≥ 18 (ESM).
gitonPATH.- A LaTeX distribution with
latexmk(only forverify_build; the rest works without it). The default engine is LuaLaTeX.latexmkis expected at/Library/TeX/texbin(MacTeX) or otherwise onPATH. - An Overleaf account with Git integration enabled (a paid feature at time of writing).
Install
Recommended: npx. Nothing to clone, nothing to keep updated by hand. The whole install is a few lines of JSON in your MCP client, plus two values from Overleaf.
- Get your two values: an Overleaf git token (Account Settings → Git Integration → create token) and your project id (the
<ID>inhttps://www.overleaf.com/project/<ID>). - Add this block to your client's config file (locations in the table below):
{
"mcpServers": {
"overleaf": {
"command": "npx",
"args": ["-y", "overleaf-forge@latest"],
"env": {
"OVERLEAF_GIT_TOKEN": "olp_xxxxxxxxxxxxxxxxxxxxxxxx",
"OVERLEAF_PROJECT_ID": "0123456789abcdef01234567"
}
}
}
}- Restart the client (or reload its MCP servers). That's it.
npx fetches and runs the published package on demand. The token and project id are the entire setup for a single project, with no config file (this is env-only mode). @latest means each client restart picks up the newest published version automatically. Pin [email protected] instead to freeze a version. For multiple projects, per-project contexts, or the SSA bootstrap, see Configuration.
An MCP server is not an app you launch yourself: the client starts it as a subprocess, so "installing" it just means making its command available to the client. The npx form above needs no install step. If you would rather have a real command on your PATH, install it globally:
npm install -g overleaf-forgeand set "command": "overleaf-forge" with no args (keep the same env block). A global install does not auto-update: refresh it yourself with npm update -g overleaf-forge. npx is recommended precisely because it skips that step.
To hack on the server instead, run it from source:
git clone https://github.com/nomanfoundhere/overleaf-mcp.git
cd overleaf-mcp
npm installThen use "command": "node", "args": ["/absolute/path/to/overleaf-mcp-server.js"] in the client config in place of the npx form.
Wiring into specific clients
The mcpServers schema is identical across clients: only the file location differs. Put the npx block above inside each.
| Client | Config file |
| --- | --- |
| Claude Code | ~/.claude.json |
| Claude Desktop | claude_desktop_config.json (Settings → Developer → Edit Config) |
| LM Studio | ~/.lmstudio/mcp.json (or the app's Edit mcp.json) |
| Cursor and others on the Cursor mcp.json convention | their mcp.json |
Restart the client (or reload its MCP servers) after editing, so it spawns the server with the new config.
Updating
As a user. With overleaf-forge@latest in your config (the recommended form), restart the client or reload its MCP servers and it fetches the newest published version. If you pinned a version ([email protected]), change the number. If npx seems to keep running an old version, clear its cache with npx clear-npx-cache and restart. If you installed globally instead, update with npm update -g overleaf-forge.
Upgrading from 2.9 or earlier: edits no longer push by default. Since 2.11, edit_file, write_file, upload_file, add_citation and restore commit to the local clone and report the unpublished count: nothing reaches Overleaf until publish_changes runs. To keep the old push-on-every-write behaviour, set autoPush: true through configure (or pass push: true per call). Since 2.10, reads and builds no longer pull either, so start a session with sync_project when Overleaf may have moved. compile_file is now verify_build({clean: false}), and get_section_bundle is now get_section_content({bundle: true}).
As the maintainer (publishing a new release). From the repository:
npm version patch # or minor / major; bumps package.json and tags
npm publish # approve the npm 2FA prompt (browser or one-time code)
git push --follow-tags # push the commit and the version tagAnyone on @latest picks the new version up on their next client restart.
Configuration
Two modes, smallest first.
Env-only mode (one project, no files)
Set OVERLEAF_GIT_TOKEN and OVERLEAF_PROJECT_ID in the client's env block (as in Install). The server builds a single default project from them and lands clones under ~/.overleaf-mcp/repos/. This covers the common single-project case completely.
| Env var | Meaning |
| --- | --- |
| OVERLEAF_GIT_TOKEN | Overleaf git token. Required, here or in projects.json. |
| OVERLEAF_PROJECT_ID | The id from https://www.overleaf.com/project/<ID>. Its presence triggers env-only mode. |
| OVERLEAF_PROJECT_NAME | Optional display name for the synthesized project. |
| OVERLEAF_MCP_HOME | Optional. Override the data home (default ~/.overleaf-mcp). |
| OVERLEAF_MCP_TEMPLATES | Optional. Override the templates directory. |
| OVERLEAF_VOICE_LINTER | Optional. Prose-linter command for voice_lint (alternative to settings.voiceLinter). |
Config-file mode (multiple projects, contexts, bootstrap)
For more than one project, per-project contexts, or the SSA bootstrap, use a projects.json. Scaffold it:
npx overleaf-forge initThat creates ~/.overleaf-mcp/projects.json from the example and copies the editable templates into ~/.overleaf-mcp/templates/. You can fill in the settings conversationally instead of by hand: ask the assistant to set up overleaf-forge and it runs the configure tool (which asks where your templates, voice linter, and course folders live, then writes them). Or edit the file directly:
{
"settings": {
"gitToken": "olp_xxxxxxxxxxxxxxxxxxxxxxxx",
"repoDir": "/Users/you/Overleaf"
},
"projects": {
"default": {
"name": "My Paper",
"projectId": "0123456789abcdef01234567",
"cwd": "/Users/you/path/where/you/run/the/client"
}
}
}| Setting | Meaning |
| --- | --- |
| settings.gitToken | Overleaf git token, used by every project (or set OVERLEAF_GIT_TOKEN). A per-project gitToken overrides it. |
| settings.repoDir | Where project clones land by default (repoDir/<name>). A per-project localPath overrides it. |
| settings.templatesDir | Optional. Directory of scaffold templates, overriding the bundled defaults. |
| settings.voiceLinter | Optional. A prose-linter command (or set OVERLEAF_VOICE_LINTER) that voice_lint runs on a file. Defaults to the bundled examples/voice-lint.mjs; override with your own house style. |
| projects.<key>.projectId | The id from https://www.overleaf.com/project/<ID>. |
| projects.<key>.cwd | Directory you launch the client from for this project; used to auto-detect the active project. |
| projects.<key>.localPath | Explicit clone location (optional). |
projects.json is re-read on every call, so registering a project or rotating the token takes effect immediately, with no restart. (Code changes do need a restart: the server loads its .js once at startup.)
Where files live
User state (the projects.json, per-project contexts/, customised templates/, and the git clones) lives in the data home, resolved as: $OVERLEAF_MCP_HOME if set, else the package directory when it already holds a projects.json (so an existing local clone keeps working untouched), else ~/.overleaf-mcp. Bundled, read-only defaults (the templates and the stock writing-guidelines) ship inside the package, and a copy you place in the data home overrides the bundled one. For personal writing rules, use writing-guidelines.local.md (gitignored, read first on every get_context call). It stays distinct from the bundled file even when the data home is the package directory.
Getting Overleaf credentials
- Git token: Overleaf → Account Settings → Git Integration → create a token. One token covers every project on the account.
- Project ID: the id segment of the project URL:
https://www.overleaf.com/project/<ID>.
Per-project context (optional but recommended)
Assignment-specific notes (terminology, deadlines, structure constraints, pinned decisions) live in contexts/<key>.md under the data home, and shared writing rules in writing-guidelines.md. Both are re-read on every get_context call, so you can edit them mid-session. get_context is the intended first call of a writing session.
Customising the templates
The SSA bootstrap scaffolds from two templates: main.tex (the document skeleton) and context-scaffold.md (the project-questions checklist). Defaults ship with the package. To change them, run npx overleaf-forge init (which copies both into ~/.overleaf-mcp/templates/) and edit them in place, or point settings.templatesDir / OVERLEAF_MCP_TEMPLATES at your own directory. The tokens __SSA_NAME__, __SSA_TITLE__, __SSA_DATE__, and __OVERLEAF_READ_URL__ are substituted at scaffold time. A missing template falls back to the built-in default, so a partial templates directory never breaks the bootstrap.
Install via an AI agent
Hand this to an agent (Claude Code, Cursor, etc.) to set up the server in the current client:
Install the Overleaf MCP server (npm package: overleaf-forge).
1. Find this client's MCP config file (Claude Code ~/.claude.json, Claude Desktop's
claude_desktop_config.json, or LM Studio ~/.lmstudio/mcp.json).
2. Add an "overleaf" entry under mcpServers:
command: "npx", args: ["-y", "overleaf-forge"]
with an env block containing OVERLEAF_GIT_TOKEN and OVERLEAF_PROJECT_ID.
3. Ask me for my Overleaf git token (Account Settings → Git Integration) and the
project id from my project URL. Do not invent them.
4. For multiple projects or the SSA bootstrap, run `npx overleaf-forge init`
and edit ~/.overleaf-mcp/projects.json instead of the env block.
5. Tell me to restart or reload MCP servers in the client, then call status_summary
to confirm it connected.Quick start
A typical editing session, in the model's words:
- "Read the context for this project." →
get_context - "Show me the sections in
Chapters/ch2.tex." →get_sections - "In
Chapters/ch2.tex, change\section{Intro}to\section{Introduction}." →edit_file(anchored, conflict-safe) - "Add a figure: upload
~/plots/fig1.pngtofigures/fig1.png." →upload_file - "Verify the build." →
verify_build→PASS: 12 pages. - "Publish it." →
publish_changes(pushes every verified local commit to Overleaf at once)
Every write commits locally. verify_build is the gate before calling the work done, and publish_changes pushes the verified commits to Overleaf in one step. Set settings.autoPush: true (or pass push: true) to push on every write instead.
Conflict safety
Edits never silently overwrite a concurrent Overleaf change.
- Local by default. Write tools commit to the local clone and stay off the network unless
push: trueorsettings.autoPushis set.publish_changespushes the accumulated commits after verification. When any push is refused, only that operation's own commit is rolled back: earlier unpublished commits are never discarded. edit_filereplaces an exact anchor string. If the anchor is gone, the region changed since you read it, and the edit refuses rather than guessing. When pushing, it pulls first (so a non-overlapping browser edit is absorbed), and on the rare push race git performs a real 3-way merge and the edit refuses only on a true overlap.write_file(full-file create or overwrite) creates a new file freely. To overwrite an existing file it requires either thebaseShayou got fromread_file(a stale one is refused) or an explicitoverwrite: true. It never merges a wholesale replacement: a push race refuses and rolls back its own commit.upload_fileuses the same gate for binaries, never merges, and confines every destination path inside the project clone.sync_projectfetches and fast-forwards when the clone is only behind. On divergence it changes nothing and reports the local and remote commits.strategy: "rebase"replays local work onto Overleaf (aborting cleanly on conflict), andstrategy: "reset"discards local work only withconfirmequal to the reported head, after taggingmcp-backup/*copies of the old head and any uncommitted edits.- An explicit
projectNamethat doesn't resolve is an error, never a silent fall-through to a different project, so a write cannot land in the wrong repo.
Bootstrap for recurring structured documents
bootstrap_ssa automates the repetitive setup for documents you create on a recurring schedule, where each instance lives in a predictable folder and follows a naming convention. The built-in convention targets SSAs (the recurring coursework format this fork was built around), but the same shape fits lab notes, weekly reports, meeting minutes, or journal entries.
Bootstrap
https://www.overleaf.com/project/abc123asY1 ABC123 SSA 5.
By the built-in convention this parses the name, locates the parent course folder under settings.academicRoot/Year <year>/Q*/<COURSE>*, creates <parent>/<ssaSubdir>/<name>/, clones the Overleaf repo into an overleaf/ subfolder, registers the project, and scaffolds a context file with a question template. Pass cleanAfterClone: true when the project was duplicated from a previous instance to wipe the body (chapters, appendices, bib, figures) while keeping the preamble.
Adapting it. The parsing and folder rules are specific to the SSA scheme. To drive other recurring work, adjust parseSsaName / findCourseFolder and the bootstrap_ssa handler, or skip the bootstrap entirely and register_project each instance.
Tools
Project setup & context
| Tool | Purpose |
| --- | --- |
| configure | Set up the global settings conversationally: call with no arguments to see what's unset and the questions to ask, then call again with the answers. Writes projects.json. |
| get_context | Read the writing guidelines + the active project's context. Intended first call of a session. |
| list_projects | List configured projects; mark which one auto-detects from the current directory. |
| register_project | Add or overwrite a project entry without hand-editing JSON. |
| set_project_path | Update a project's localPath and/or cwd. |
| update_context | Replace or append contexts/<key>.md. Takes effect immediately. |
| bootstrap_ssa | One-shot onboarding for a recurring structured document (see above). |
| reset_ssa_content | Empty chapters/appendices/refs.bib/figures and optionally rewrite the title block; commit + push. Preamble untouched. |
Reading & search
| Tool | Purpose |
| --- | --- |
| read_file | Read a file. The first line carries the file's baseSha (its git blob hash) for conflict-safe writes. |
| list_files | List files in the project, filtered by extension. |
| get_sections | List \section / \subsection / \subsubsection entries in a .tex. |
| get_section_content | One section's body by exact, unique title, \section down to \paragraph (level-aware: a section keeps its subsections). bundle: true adds the referenced equation/figure blocks, bibliography entries and assets. |
| search_text | Grep tracked files. Regex by default; fixed for a literal, ignoreCase, extension to scope. Returns file:line:match. |
| status_summary | File count, main file, section count. |
Editing & figures
| Tool | Purpose |
| --- | --- |
| edit_file | Anchored oldString → newString edit + commit (push per push / settings.autoPush). Conflict-safe; auto-merges non-overlapping concurrent edits. Preferred for existing files. |
| write_file | Create a new file, or overwrite one wholesale. Existing-file overwrite needs baseSha or overwrite: true. |
| upload_file | Upload binary file(s) (figures) from a local path. Single or batch (one commit). Byte-exact, path-confined, same conflict gate. |
Building & verification
| Tool | Purpose |
| --- | --- |
| verify_build | Compile with latexmk from the repo root (project .latexmkrc, reruns and bibliography apply) + PASS/FAIL verdict: PASS only with a PDF and zero errors / undefined references / undefined citations. Reports page count. Default is the clean from-scratch done-bar gate; clean: false is a quick incremental rebuild; lint adds voice-linter findings to the gate. |
| sync_project | Fetch and reconcile with Overleaf: fast-forward, report divergence, or resolve it with rebase / confirmed reset (see Conflict safety). |
| publish_changes | Verify the clean local HEAD and push every unpublished commit once. |
| apply_changes | Verify a SHA-guarded multi-file batch in an isolated worktree, then commit it locally only if it passes. |
Analysis & measurement
| Tool | Purpose |
| --- | --- |
| dependency_index | Static map of includes, figures, labels, references, citations and declared values; names the sections a change affects. |
| change_report | Compact file and section changes against a retained baseline. |
| render_pages | Render chosen PDF pages to cached PNGs for visual checks. |
| usage_stats | In-process call counts, durations, response sizes and cache hits (no document text). |
Citations, snapshots, voice
| Tool | Purpose |
| --- | --- |
| add_citation | Append a BibTeX entry to refs.bib (refuses a duplicate key) + commit. |
| cite_lint | Report undefined (\cite with no entry) and unused (entry never cited) citations. Read-only. |
| checkpoint | Mark a local rollback point (a mcp-snap/<label> tag) before a risky edit. |
| restore | Roll back to a checkpoint via a forward commit (no force, no history rewrite). |
| voice_lint | Run a prose linter on a .tex (the bundled examples/voice-lint.mjs by default; override via settings.voiceLinter). Lints the local working copy as-is, never pulls. Read-only and advisory; verify_build with lint makes it gating. |
Local reads, builds and batches
get_context({projectName, previousVersion})returns a context version and omits unchanged content. The version covers project identity and the rendered guidance/context.get_section_content({projectName, filePath, sectionTitle, bundle: true, maxChars})reads locally without pulling and returns directly referenced equation/figure blocks, matching BibTeX entries and asset paths. It reports missing matches and truncation. Macro-generated references, recursive TeX expansion and parenthesized BibTeX entries require a focused follow-up read.- All read, search, lint, dependency, render and build tools are local-only. Use
sync_projectexplicitly before reading when needed. It fast-forwards on its own and resolves divergence only through an explicitly chosen strategy. - Build output is compact by default.
verbose: trueincludes a bounded log tail, and the full log stays at the returned path. verify_buildcan reuse a successful verification within the running server when project files, recorder inputs, tool binaries, environment and output artifacts are unchanged.force: truerebuilds. Missing recorder data, symlinks or executable build configuration (a project.latexmkrc, or the user's own~/.latexmkrc) conservatively disable reuse. The cache is in-memory and disappears on server restart. Custom build commands can have undeclared external dependencies, so projects with latexmkrc files or detected shell/Lua generation rebuild.publish_changesis the exception: it reuses a PASS from the same session when every project file is byte-identical since, so publishing does not repeat the final gate's full build.controlled: truerunslatexmk -norc -no-shell-escapeand permits caching in projects with a.latexmkrc, because the rc file cannot run. The mode still rejects shell escape,mintedand Lua file generation. Add required absolute regularexternalInputsso their content enters the cache key.dependency_indexrecords static TeX includes, figures, labels, references, citations and declared values.change_reportcompares a later index to a retained baseline and names affected sections. Dynamic macros are reported as unresolved rather than guessed.render_pagescaches a requested PDF page by PDF content hash, page, DPI and renderer version.usage_statsexposes aggregate in-process timing, response-size and cache-hit measurements without retaining request or document text.- Write tools commit locally unless
push: trueorsettings.autoPushis set, and report HEAD plus the unpublished count. apply_changesverifies an SHA-guarded UTF-8 multi-file candidate in an isolated worktree and fast-forwards one local commit only if verification passes.publish_changesseparately re-verifies its exact clean revision, then pushes it once, carrying every unpublished local commit. Neither operation pulls, resets, retries or silently merges.
After updating the server, reconnect the MCP client so it reloads the process and tool schemas.
Troubleshooting
Every build fails with undefined citations on macOS 27. TeX Live 2025 ships biber as a universal binary that unpacks itself with lipo -extract_family, which the lipo in macOS 27 rejects (biber: extracting arm64 binary with lipo failed). A user-level workaround: a small biber wrapper that extracts the native slice with lipo -thin and caches it, and a ~/.latexmkrc line pointing latexmk at it ($biber = "$ENV{HOME}/.local/bin/biber %O %S";). A user rc file counts as executable build configuration, so non-controlled builds stop using the full build cache while it exists.
How it works
Each project is a normal git clone of its Overleaf repo, kept under repoDir (or localPath). sync_project is the only synchronization command. Read, search, lint, dependency, render and build tools use the existing clone without pulling. This gives a task a stable source snapshot and prevents a read from replacing local edits. Git runs through execFile with argument arrays (no shell), so file paths, commit messages, and patterns can't inject commands. Authentication uses an inline git credential helper that reads the token from the process environment, so the token is never written into a remote URL, a command line, or an error message. The clone's origin stays token-free.
Testing & development
npm install
node --test # run the full suite
node --check overleaf-mcp-server.jsTests run the real client against a throwaway local bare repository that stands in for Overleaf, so the full edit/merge/conflict/upload/snapshot behaviour is exercised with no network and no real account. The verify_build log classifier is unit-tested on captured log strings. A single integration test compiles a trivial document and auto-skips when latexmk isn't installed, so the suite is green on any machine.
Security
projects.jsonis gitignored. Never commit a real token.- The token is supplied to git through an environment-backed credential helper and never appears in a command string, a remote URL, or an error. Any tokenized URL that could surface in an error is redacted before it is returned. Rotate by updating
settings.gitToken: it applies on the next call. - All subprocess calls use
execFile(no shell), so paths, commit messages, and section titles cannot inject shell commands. upload_filedestinations are resolved and confined inside the project clone (no..escape, no absolute paths, not the.gitdirectory).
Origin & credits
Forked from mjyoo2/OverleafMCP. The original established the git-integration approach and the base read/write/compile tools. This fork reworked the edit path for conflict safety, hardened the git layer, and added the verification, figure, citation, snapshot, voice, bootstrap, and context tooling.
License
MIT. See LICENSE if present, or treat this as MIT per the upstream project.
