Skip to content

Internals

How the server talks to Overleaf, and the guarantees that follow from it. Read this if you are debugging, contributing, or deciding whether to trust a particular behaviour.

Connection model

Two channels are used, both through the same saved web session:

  • Private REST endpoints, the ones the Overleaf editor and dashboard call, for the project list, project creation, cloning, zip import, renaming, trash and archive state, project settings, the file tree operations, uploads, downloads, compiles, comment threads, and history. Every route is listed in the private API catalogue.
  • Socket.IO 0.9 with operational transformation (OT) for reading and writing document text. The server implements the Socket.IO 0.9 wire format used by the targeted Overleaf client family and rejects unsupported protocol versions at project bootstrap.

No Overleaf Git integration is involved.

Layers

Every operation is defined once and reached through one engine, whichever interface calls it.

  • Contracts (src/contracts/): for each operation, its input and output schemas, its description, its MCP annotations, and its effects, plus the error codes and the OperationContext an interface passes in (progress, cancellation, diagnostics). Tests check that the annotations agree with the effects and that every destructive operation takes a confirm value or an expected state.
  • Service (src/service/operations.ts): createOverleafService(runtime) returns one function per operation. Each one shapes its arguments for the domain engine and nothing else; the MCP handlers in src/mcp/tools.ts make one service call each.
  • Domain engine (src/overleaf/, src/protocol/, src/http/): every public method checks the access policy for the project and each effect it performs before any request, so a direct call through the exported runtime is held to the same rules as a tool call. The per-project queue, revision checks, write verification, and timeout recovery live here.

The engine is published without MCP as overleaf-web-mcp/core, which exports OverleafRuntime, readConfig, createOverleafService, McpError, ERROR_CODES, and the contract types, and never loads the MCP SDK. It enforces the same defaults as the MCP server; v0.7.0 documents its stable surface.

import { createOverleafService, OverleafRuntime, readConfig } from 'overleaf-web-mcp/core'

const runtime = await OverleafRuntime.create(readConfig())
try {
  const service = createOverleafService(runtime)
  const { content, revision } = await service.read_file({ projectId, filePath: 'main.tex' })
} finally {
  await runtime.close()
}

Protocol and reliability notes

  • ShareJS text OT and history-OT are normalized behind one document interface. Tracked ShareJS writes carry the authenticated author in update metadata; tracked history-OT writes carry author and timestamp metadata on inserted and retained-deletion components.
  • Visible history-OT offsets account for tracked deletions retained in the raw snapshot.
  • At most two project sockets are cached by default. Active sockets are never evicted, and idle sockets disconnect after 90 seconds.
  • All document sessions and tree mutations in a project share one FIFO queue, because Overleaf's join/leave epoch is socket-wide. A document is joined for one queued operation and then left.
  • A write succeeds only after acknowledgement, a matching otUpdateApplied, a leave and rejoin, and content-hash verification of the live document.
  • If a write times out, the intended hash proves success, the unchanged original revision proves the write was not applied, and any third observable state is reported as a conflict. The write is never submitted again automatically.
  • A comment is created as a REST thread and then attached through OT. Timed-out attachment recovery checks the new thread id and exact range; orphan cleanup happens only after the unchanged document proves attachment did not apply.
  • A timed-out reply is accepted only when current author, exact normalized content, and the request-time window identify the refreshed message.
  • ShareJS comment status uses the dedicated REST action. History-OT comment status is part of the document operation and snapshot.
  • Folder sync is a composition, not a second implementation: sync_directory calls the same document write, upload, folder creation, and delete that the single-file tools use, one at a time through the project queue, and each upload or delete re-opens the project so the next step sees a fresh tree. A plan token is a small base64url JSON object holding three truncated SHA-256 digests: of the arguments that decide the scope, of every project entity in scope (path, type, id, and binary hash or document version and content hash), and of every local file's blob hash and folder. Nothing is stored on the server between calls, so a token survives a restart; after a sync, the digest covers what it changed as re-read from the tree and everything else as planned.
  • batch_upload is the same kind of composition: one tree read, the folder creation and upload that sync_directory uses, and one read-back that confirms each upload and classifies a timed-out one without sending it again. It stops at the first RATE_LIMITED, since Overleaf counts uploads per project and would refuse the rest. download_project_zip streams the archive to a temporary file beside the target and moves it into place, never holding the whole archive in memory. It keeps the last 64 KB as it goes, the most a zip end record and its comment can span, and checks for that record there, because Overleaf builds the archive while sending it and a transfer cut short still ends with HTTP 200.
  • History monitoring reads one 25-group update window, strips email fields, and keeps no cursor or background state on the server.

Protocol fixtures under test/fixtures/protocol are sanitized: cookies, user data, project and document ids, and document content are removed.

How comment locations are resolved

Thread messages, authors, and resolution state come from /project/:id/threads. When the deployment exposes /project/:id/ranges, that project-wide index identifies which documents contain the filtered threads, and only those documents are joined to compute line and column positions and quoted context.

If a usable project-wide range index is unavailable, a project-wide list_comments call returns threads with positionsUnavailable: true; it never scans every document silently. Supplying filePath joins only that document and resolves its ShareJS ranges or history-OT comment state. Threads without a document range are returned as unlocated.

The discussion record and the source range are separate Overleaf objects. The thread endpoint provides messages; live document state provides attachment and status.

Representative rather than exhaustive; capabilities change over time. The README's comparison table checks each capability against the source of the most widely used servers.

Implementation Connection model Focus
This project Browser-assisted saved session plus private REST and Socket.IO/OT Project and file management, tracked writing, compilation, review, recent history
@netique/overleaf-mcp Browser session plus private REST and Socket.IO/OT A close web/OT peer with review comments and tracked-change workflows
overleaf-mcp-rt Session authentication plus native OT Real-time file, compile, and comment tooling for www.overleaf.com and Community Edition
olcli Session cookie plus private REST A command-line client with an MCP mode: whole-file uploads, compilation, comments, and local-folder diffs
OverleafMCP, overleaf-mcp-server, vibeTeX Overleaf Git bridge Git-backed synchronization, editing, and history

The Git-bridge servers need Overleaf's Git integration, a paid feature on www.overleaf.com, and work on the repository rather than the live document, so their edits bypass tracked changes and revision checks entirely. Web-session peers such as @netique/overleaf-mcp share this project's connection model and also offer tracked changes; what sets this project apart is the project lifecycle tools, the verified-write guarantees described above, and folder sync that, given the planToken from plan_sync, stops when either side drifted from the plan. The roadmap plans a command line and a TypeScript client over the same engine overleaf-web-mcp/core already exposes, tracked-change review, and multi-file documents.

Review-range investigation was informed by Overleaf Comment Exporter. Real-time protocol behaviour was informed by Overleaf Workshop.