
Security News
Happy Birthday, Shai-Hulud
It has been one year since Shai-Hulud made its first appearance on npm.
mcp-server-overleaf
Advanced tools
Read and edit Overleaf papers from any AI client. Edits can arrive as tracked-change suggestions.
Your papers live on Overleaf, and the model you want to write with cannot reach them. Copy-pasting sections into a chat window works until you paste something back and quietly flatten a change your co-author made ten minutes ago. This is an MCP server that gives an AI client real access to your Overleaf projects over the official git bridge: it reads the actual files, edits them in a local clone, and publishes only when you say so. Before every read and before every push it checks what your collaborators have done, and if your edit and theirs touch the same lines it refuses to push and shows you both versions rather than picking a winner. Losing someone else's work is the one outcome it is built to prevent.
It also writes suggestions rather than only text. With an Overleaf session cookie, an edit arrives in the review panel as a tracked change for a co-author to accept or reject, and the comments already sitting there become readable. A Git push is plain text and can express neither.
Independent community project; not affiliated with or endorsed by Overleaf.
Several Overleaf MCP servers exist. Of the ones built on Overleaf's official Git bridge, this is the most complete, and it is the only one that stops instead of overwriting when a co-author has already changed the lines you are editing.
push_changes
refuses and shows you both versions.Related projects compares the alternatives tool by tool, and is equally direct about the things this server cannot do.
Every route below reaches the same server and the same 16 tools, and every one of them keeps edits local until an explicit push. Pick the row for your client. The three tracked-changes tools are extra, and appear only once a session cookie is configured.
| Client | How to install it |
|---|---|
| Claude Desktop | Download the bundle and double-click it. One click |
| Claude Code | Add this repository as a plugin marketplace. Two commands |
| Codex CLI, desktop, and IDE extension | The same, with the Codex commands. Two commands |
| Cursor | npx --yes mcp-server-overleaf setup. One command |
| Visual Studio Code | A .vscode/mcp.json entry. Guide |
| ChatGPT on the web and desktop | An outbound-only Secure MCP Tunnel, no public endpoint and no open port. Guide |
| A catalogue that reads the MCP registry | Already listed as io.github.yangzichao/mcp-server-overleaf. Details |
| Any other MCP client | npx --yes mcp-server-overleaf --stdio, or Streamable HTTP. By hand |
| A machine you are developing on | A source checkout. From source |
Claude Desktop takes a packaged bundle; Claude Code and Codex each take a plugin.
Everywhere else setup does the equivalent. All four routes end the same way: the token is
verified against Overleaf, written to one file at mode 600, and no client configuration
holds the secret.
.mcpb extension: what is inside it, how to build it, and how to remove it.An MCP server exposing 16 tools over stdio or Streamable HTTP, plus 3 more when a session
cookie turns on tracked changes. One core, two transports;
the tool implementations are identical and only the framing differs. setup connects it
to the AI clients on this computer and is the only command most people need.
Requires Node 22.14+ and git on PATH; Node 24 LTS is recommended. compile_project additionally needs latexmk and a TeX
distribution; every other tool works without them.
The release test matrix covers macOS and Linux. Windows Git and TeX workflows are not yet validated. This server is a command-line application, not an importable JavaScript library.
Git integration has to be available for the project. Overleaf documents it as a premium feature gated on the project owner's subscription, so a free account invited to a project owned by someone with a subscription can still use it, and that subscription can be individual, group, or institutional. Do not tell a user their own plan disqualifies them. Verify it directly in step 3 instead.
Download mcp-server-overleaf.mcpb and double-click it. Claude Desktop opens an install panel, asks for the two values below, and starts the server itself. No terminal, no Node installation, no build.
The bundle carries the same server npm publishes, with its locked runtime dependencies and nothing else. It is macOS and Windows only, because that is where Claude Desktop runs. For any other client, use the command below. Details in Install the Claude Desktop bundle.
Store the token once:
npx --yes mcp-server-overleaf@0.3.4 setup --clients none
Then, in Claude Code:
claude plugin marketplace add yangzichao/mcp-server-overleaf
claude plugin install overleaf@mcp-server-overleaf
Or in Codex:
codex plugin marketplace add yangzichao/mcp-server-overleaf
codex plugin add overleaf@mcp-server-overleaf
Restart the client, then ask it to list the files in your project.
Neither client has a central plugin directory. In both, a marketplace is just a Git
repository, so this one is its own: Claude Code reads .claude-plugin/marketplace.json out
of it and Codex reads .agents/plugins/marketplace.json, and both point at the same
plugins/overleaf directory. That plugin holds no token and sets no environment variable,
because the first command writes the token to ~/.config/overleaf-mcp/projects.json and the
server finds it there. Details in
Install the Claude Code and Codex plugins.
npx --yes mcp-server-overleaf@0.3.4 setup
It checks Node and git, asks for the project and a token, proves the token reaches Overleaf before writing anything, and registers the server with the clients it finds on this computer: Claude Code, Codex, Claude Desktop and Cursor. A failed run leaves no half-configuration behind, because nothing is written until Overleaf accepts the token.
Two things are needed, both from the browser:
The token is written to ~/.config/overleaf-mcp/projects.json at mode 600 and nowhere
else. The server finds that file on its own, so no client configuration holds the secret,
a path to it, or any environment variable. Clients are registered with an absolute Node
path and an absolute entry point rather than npx, because a desktop application starts
with a much smaller PATH than a terminal.
Restart Claude Desktop, Cursor, or Codex afterwards; Claude Code picks it up on the next run. Then ask the assistant to list the files in the project.
Setup can also run unattended:
printf '%s' "$OVERLEAF_TOKEN" | npx --yes mcp-server-overleaf@0.3.4 setup \
--project paper=https://www.overleaf.com/project/64a1b2c3d4e5f6a7b8c9d0e1 \
--token-stdin --yes
--clients claude-code,codex limits which clients are touched, and --clients none
writes the configuration without registering anything. Run it again to add another
project or to replace an expired token; the previous file is kept alongside it.
The server is listed in the official Model Context Protocol registry as
io.github.yangzichao/mcp-server-overleaf. The listing names the npm package and the Claude
Desktop bundle, and clients and catalogues that read the registry pick it up from there. The
release workflow publishes the listing itself, so it can never point at a version npm does
not have.
Claude Code and Codex do not read that registry. They have their own plugin marketplaces, which is the section above, or the server can be added by hand:
claude mcp add --transport stdio --scope user overleaf -- npx --yes mcp-server-overleaf@0.3.4 --stdio
codex mcp add overleaf -- npx --yes mcp-server-overleaf@0.3.4 --stdio
Both forms need OVERLEAF_GIT_TOKEN and OVERLEAF_PROJECT_ID in the environment, which is
why setup writes a projects.json instead and registers an absolute path.
Use this when you want to see every step, or to configure a client setup does not cover.
The commands below select version 0.3.4 explicitly so a client restart does not silently
upgrade the server. If that version has not been published yet, use the source installation
below. npm installs compiled JavaScript and locked runtime dependencies; no local build is needed.
npx --yes mcp-server-overleaf@0.3.4 --version
Keep configuration outside the npm installation and npx cache. Create a private file:
mkdir -p "$HOME/.config/overleaf-mcp"
touch "$HOME/.config/overleaf-mcp/env"
chmod 600 "$HOME/.config/overleaf-mcp/env"
Edit that file locally to contain your token and project ID:
OVERLEAF_GIT_TOKEN=olp_your_token_here
OVERLEAF_PROJECTS=paper=64a1b2c3d4e5f6a7b8c9d0e1
For clients using the mcpServers JSON format:
{
"mcpServers": {
"overleaf": {
"command": "npx",
"args": ["--yes", "mcp-server-overleaf@0.3.4", "--stdio"],
"env": {
"OVERLEAF_MCP_ENV_FILE": "/absolute/path/to/.config/overleaf-mcp/env"
}
}
}
}
Replace the file path with its actual absolute path; JSON does not expand ~ or $HOME.
Clients with another configuration format should launch the same command and arguments with
the same environment variable. If a desktop app cannot find npx, use its absolute path
from command -v npx. See the client guide for source
installation examples and client-specific configuration locations.
Alternatively, set both OVERLEAF_GIT_TOKEN and OVERLEAF_PROJECTS in the client's env.
With OVERLEAF_MCP_ENV_FILE, environment variables override individual file values; an
unreadable or relative explicit file path is an error. Without it, the install's .env is
used only when the client supplies no project or credential settings (including file paths).
A JSON projects file can provide independent credentials; see the configuration guide.
The current working directory is never searched for configuration.
After restarting the client, call list_projects to check configuration and list_files
to verify access to the real project. Tool discovery alone does not prove Overleaf access.
npm ci && npm run build
Confirm dist/index.js exists before continuing. Record the absolute path of the
repository root; every client configuration below needs it, and relative paths will not
work because clients spawn the server from an arbitrary working directory.
The git authentication token. Generated at https://www.overleaf.com/user/settings,
under Git integration. One token covers all their projects and expires after a year. It
starts with olp_.
Ask the user to paste it into .env themselves rather than into the chat, using the
command in step 3. A token pasted into a conversation is in the transcript permanently and
has to be rotated. If they paste it anyway, tell them plainly to rotate it, and continue.
One or more project ids. The last path segment of a project URL:
https://www.overleaf.com/project/<projectId>. A project id is exactly 24 hexadecimal
characters. Anything else is not a project id, and you should ask again rather than guess.
cp .env.example .env && chmod 600 .env
Then have the user fill in:
OVERLEAF_GIT_TOKEN=olp_...
OVERLEAF_PROJECTS=paper=64a1b2c3d4e5f6a7b8c9d0e1
OVERLEAF_PROJECTS is a comma-separated list of name=projectId pairs. The names are
arbitrary labels the user picks. A bare project id with no name is also accepted and
registers under its own id. With exactly one project registered it becomes the default; with
several, set OVERLEAF_DEFAULT_PROJECT, use an alias named default, or name the project in each call.
Verify the credentials reach Overleaf before configuring any client:
git ls-remote https://git@git.overleaf.com/<projectId>
Git will prompt for a password. That is the token. Refs printed means it works. 403 or
Repository not found means either the token is wrong or that account's plan does not
include Git integration; resolve this before continuing, because every tool depends on it.
The server reads .env itself, because MCP clients spawn it without a shell. This keeps the
token in one file whose permissions the user controls, instead of copied into every client's
config. Anything the client sets in the environment explicitly still wins.
Use the absolute path from step 1. Configure only the client the user actually asked for. For client-specific configuration locations, verification, and troubleshooting, see the local MCP client guide.
Claude Code
claude mcp add overleaf -s user -- node /absolute/path/to/mcp-server-overleaf/dist/index.js --stdio
Then run claude mcp list and confirm it reports ✔ Connected. Do not report success
until you have seen that.
Claude Desktop or Cursor — add to the client's MCP config file. The file location and restart steps differ by client; the local MCP client guide lists both.
{
"mcpServers": {
"mcp-server-overleaf": {
"command": "node",
"args": ["/absolute/path/to/mcp-server-overleaf/dist/index.js", "--stdio"]
}
}
}
Codex desktop, CLI, or IDE extension — the clients on one Codex host share
~/.codex/config.toml:
[mcp_servers.mcp-server-overleaf]
command = "node"
args = ["/absolute/path/to/mcp-server-overleaf/dist/index.js", "--stdio"]
Add default_tools_approval_mode = "writes" inside the server table if the user wants
reads to run freely while every write asks first.
ChatGPT desktop local MCP settings can start the same stdio command directly on the computer that holds this repository. This local configuration is shared with Codex clients on the same host; the local MCP client guide includes the UI and CLI paths.
ChatGPT web or a workspace-shared ChatGPT app — use OpenAI Secure MCP Tunnel without
exposing the server to the public internet. ChatGPT connects to an OpenAI-hosted tunnel
endpoint while tunnel-client forwards requests to dist/index.js --stdio. Follow the
step-by-step Secure MCP Tunnel guide; it
includes the required Platform organization and ChatGPT workspace association, runtime key,
developer-mode app setup, and end-to-end verification.
Remote clients over HTTP
node dist/index.js --http --port 3017
--http refuses to start without OVERLEAF_MCP_HTTP_AUTH_TOKEN, since anyone who reaches
the port could otherwise read and rewrite the user's papers. --allow-anonymous overrides
this and should only be used when the port is genuinely unreachable from outside the
machine. Do not pass it to work around a missing token.
Call list_files. A file listing means the whole path works: configuration, token, clone,
and the client connection. If it fails, the error text says which.
Every tool takes an optional project: a project's Overleaf address, its raw 24-character
id, or a name from OVERLEAF_PROJECTS. Omit it to use the default. The address is accepted
because it is the form you already have, so a paper that was never configured can be reached
by pasting it. An unregistered project requires a default Git token; registered ones use
their configured project credentials.
| Tool | What it does |
|---|---|
list_projects | The projects this server can reach |
list_files | Files grouped by kind; extension filter and optional new local files |
read_file | Text or line ranges; optional revision-aware full/unchanged/delta reads |
list_sections | Sectioning commands in a .tex file, with line ranges |
read_section | The body of one section, found by title |
search_project | Search tracked text files |
replace_text | Replace an exact snippet, refusing ambiguous matches |
edit_section | Replace one section wholesale |
write_file | Overwrite or create a file |
show_diff | The diff of everything not yet pushed |
discard_local_changes | Throw away unpushed edits and commits, back to Overleaf's version |
project_status | Sync state, pending edits, recent history |
project_summary | JSON file counts, main document, sections and pending changes |
sync_project | Pull from Overleaf |
compile_project | Compile with latexmk and report errors |
push_changes | Commit and publish, with the collaborator check |
Edits are local until push_changes. This is the whole design, not an implementation
detail, and it is what lets a bad edit be thrown away instead of published.
Read show_diff before calling push_changes. Push once, with a real commit message; the
user sees it in Overleaf's history next to their co-authors' entries.
push_changes can return conflict-with-collaborator. This means someone edited the same
lines while you were working. Nothing was published and both versions are intact. Do not
retry, do not force, and do not attempt to merge the two yourself. Report the conflict and
let the user decide.
A refused push leaves its commit in the clone, so the clone is then both ahead of and
behind Overleaf and every later sync conflicts on the same lines. discard_local_changes
is the way out: it drops unpushed commits as well as uncommitted edits and returns the
clone to what Overleaf holds. Tell the user what is being thrown away before calling it,
because their edit is what goes.
replace_text refuses a snippet that appears more than once rather than silently changing
the first. Add surrounding context to make it unique, or pass replaceAll deliberately.
Prefer replace_text and edit_section over write_file. write_file replaces an entire
file, so a partial reconstruction of a document silently deletes the rest of it.
For a read–edit cycle, request read_file with mode: "full" or "smart" and pass its
revision as expectedRevision to any edit tool. The edit then refuses to overwrite a
file changed by a collaborator or another client since that read. See the
revision protocol.
Compilation is optional and is not a gate. push_changes never checks that the document
builds, so a broken document can be published if you do not check first.
| Variable | Meaning |
|---|---|
OVERLEAF_GIT_TOKEN | Default credential; required unless supplied by a token file or per project. |
OVERLEAF_GIT_TOKEN_FILE | Absolute path to a default token file. |
OVERLEAF_PROJECTS_CONFIG | Absolute path to a JSON projects file with independent credentials. |
OVERLEAF_PROJECT_ID / OVERLEAF_PROJECT_NAME | Single-project alternative to OVERLEAF_PROJECTS. |
OVERLEAF_MCP_ENV_FILE | Absolute path to an external config file; recommended for npm/npx. |
OVERLEAF_PROJECTS | name=projectId pairs; optional with JSON or raw-id access. |
OVERLEAF_DEFAULT_PROJECT | Which registered name to use when a call omits project. |
OVERLEAF_MCP_CHECKOUT_MODE | full (default) or text-only; see expansion behavior in the configuration guide. |
OVERLEAF_MCP_WORKSPACE_DIR | Where clones live. Default ~/.overleaf-mcp/projects. |
OVERLEAF_GIT_BASE_URL | Default https://git.overleaf.com. |
OVERLEAF_MCP_COMPILE_TIMEOUT_MS | Compile timeout. Must be a positive integer. |
OVERLEAF_MCP_HTTP_AUTH_TOKEN | Bearer token required by --http. |
OVERLEAF_MCP_HTTP_PORT | Port for --http. --port overrides it. |
OVERLEAF_SESSION_COOKIE | Overleaf session cookie. Turns on the three tracked-changes tools. |
OVERLEAF_SESSION_COOKIE_FILE | Absolute path to a file holding the same value. Preferred over the variable. |
OVERLEAF_WEB_BASE_URL | Overleaf's web address for those tools. Default https://www.overleaf.com. |
Overleaf's git bridge is not a general git remote. Each project has exactly one branch with one linear history, force pushes are refused, and there are no tags, submodules or LFS. Everything here stays inside that envelope.
The real risk is not a failed push, it is silently overwriting a co-author, so writing and publishing are separate:
show_diff,
then publish it or explicitly discard it before continuing; stale reads and edits
are refused.push_changes.push_changes re-checks Overleaf immediately before pushing and rebases onto anything
that landed in the meantime. If that cannot be applied cleanly the push is refused and
the conflict reported, rather than resolved by guessing.Git credentials are handed to git through a credential helper, so authentication tokens
are absent from argv and .git/config. Git diagnostics redact the selected credential;
tool errors redact every configured project credential. Keep secrets out of project files,
which are intentionally readable by the connected client.
Compilation writes outside the clone, so .aux, .log and .pdf files are never staged
and pushed back. Clones also carry a local exclude list, so a .DS_Store never reaches a
co-author's project.
Tool calls are serialized per project, first by an in-process queue and then by a lock directory under the workspace, so a second server process, such as the same server registered in two clients, waits instead of racing.
Client-supplied paths are checked twice: once as a path, and once against the filesystem
after symbolic links are resolved, so a link inside a clone cannot reach outside it or into
.git.
src/
config/ environment parsing, secret redaction
overleaf/ git command runner, repository operations, project registry, path safety
latex/ section parsing, file categorization
workflow/ sync-before-edit, publish-with-conflict-check, compile
tools/ MCP tool definitions
transport/ stdio and Streamable HTTP
server/ server factory shared by both transports
tests/
config/ latex/ overleaf/ unit tests, run in-process
integration/ spawn the built server and speak MCP to it over stdio or HTTP
npm run check
Runs the type checker, Biome, the build, and the tests in one pass.
The type checker includes test code. Every Vitest invocation builds the server first,
including focused runs and coverage, so the subprocess tests cannot use an old dist.
| Command | What it does |
|---|---|
npm run typecheck | Type-check source, tests and Vitest configuration |
npm run lint | Biome lint and format check |
npm run format | Biome, applying fixes |
npm test | Build, then Vitest, whole suite |
npm run package:check | Build twice, compare tarballs, install as a user would, and test the installed server |
npm run check:release | Source checks, package checks, and dependency audit |
npm run test:coverage | Build, then Vitest with v8 coverage |
The suites cover the same tool contract over stdio and HTTP, including schemas, error
flags, reading, editing, reviewing and publishing. Recovery tests exercise dirty-tree
sync, conflict recovery, remote rejection and reconnection, process restarts, a killed
lock holder, and simultaneous clients. New files and unpushed commits must remain
visible in show_diff. Deterministic subprocess fixtures test Git and compiler timeouts
without needing a network connection or TeX installation.
TeX-dependent tests share one availability check. Set OVERLEAF_TEST_TEX=skip to run
without TeX, or OVERLEAF_TEST_TEX=required to fail if latexmk or pdflatex is missing.
The default, auto, runs those tests when both executables are available. GitHub Actions
runs core checks on Linux and macOS with Node 22.14, 24, and 26, plus a Linux job that installs
TeX and requires the compilation tests to run.
npm-shrinkwrap.json is the canonical dependency lock. Update it with npm and commit it with
package.json. The copy inside the published package is the runtime half of that file:
prepack removes the development entries and postpack restores the full lock, because npm
builds a dependency's tree from the shipped lock and would otherwise install the compiler,
the linter and the test runner on every user's machine. The package check fails if the
shrinkwrap inside the tarball still describes development dependencies.
Release package tests reuse the integration suites against an installed tarball from an
unrelated working directory, covering stdio, HTTP, edits, conflicts, and recovery.
Tests never touch the real Overleaf. tests/integration/fakeOverleafRemote.ts stands up a
bare git repository plus a second clone acting as a co-author, which reproduces everything
the safety contract depends on: fetch, rebase, and a rejected push.
Coverage under-reports. The integration tests spawn node dist/index.js as a separate
process, and v8 cannot instrument a child, so src/tools/ and src/server/ report 0% while
being driven end to end over the real MCP wire protocol.
The server runs entirely on your computer. It has no analytics, no telemetry, and no
service behind it. Its only outbound traffic is Git over HTTPS to Overleaf. Your token is
stored either in the operating system keychain, when Claude Desktop installs the bundle, or
in a mode 600 file written by setup, and it is redacted from error output.
Whatever a tool returns becomes part of the conversation with the MCP client you connected, and is handled under that client's own policy.
Full text: docs/privacy-policy.md.
MIT
FAQs
Read and edit Overleaf papers from any AI client. Edits can arrive as tracked-change suggestions.
We found that mcp-server-overleaf demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Security News
It has been one year since Shai-Hulud made its first appearance on npm.

Research
/Security News
Operators behind PolinRider used a compromised GitHub account to plant malware in four development versions of a Packagist package with 700,000+ downloads.

Security News
GitHub Actions now supports cache-mode, a least-privilege control on the Actions cache aimed at the cache poisoning technique behind recent compromises.