# Aldine (remote · demo.aldine.dev)

Self-hosted collaborative LaTeX editor: read and edit files, typeset, get the errors and the PDF.

- Trust score: 66/100 (medium)
- Registry status: active
- Liveness: live
- Owner verified: no
- Last scored: 2026-11-04

## Components

- remote · `demo.aldine.dev`: 66/100 (this document), [markdown](https://verifymcp.io/servers/trahloff-aldine/demo.md), [page](https://verifymcp.io/servers/trahloff-aldine/demo)
- remote · `{aldine_host}`: 0/100, [markdown](https://verifymcp.io/servers/trahloff-aldine/https-aldine-host-mcp.md), [page](https://verifymcp.io/servers/trahloff-aldine/https-aldine-host-mcp)

## Channel facts

- Endpoint: `https://demo.aldine.dev/mcp`
- Transports: `streamable-http`
- Auth: `none`
- Version: `1.8.0`

## Trust breakdown

How this component scores in each security and reliability category. Every signal is checked automatically against the live server, and we only credit what we can confirm. Scores are 0–100 per category. Scoring method: https://verifymcp.io/docs/scoring (what has changed: https://verifymcp.io/docs/scoring/changelog)

Scored 2026-11-04.

- **Endpoint Security**: 57/100
  - The endpoint's TLS certificate is valid, in date, and uses a strong key.
  - Authorisation not yet verified: we couldn't confirm whether this endpoint requires it.
  - HTTPS is enforced; there's no plaintext access path.
  - HSTS check failed: the Strict-Transport-Security header is absent.
  - DNSSEC check failed: this domain isn't protected by DNSSEC.
- **Transport & Reachability**: 100/100
  - Verified streamable-http transport via a live MCP handshake.
- **Schema Quality & AI Usability**: 77/100
  - 100% of prompts and resources have a non-trivial description (not blank, and not just the item's name).
  - AI-judged instruction clarity (excellent).
  - Context-footprint check failed: tool/resource definitions use about 3915 tokens (~230/item across 17 items; 16 tools + 1 resources), over budget; trim descriptions and params.
  - Usage-examples check failed: none of the tools include examples.
- **Stability & Change Management**: 13/100
  - Stability observed for 4 of 30 days with no destabilising changes; credit accrues until the full window elapses.
- **Tool Coverage**: 96/100
  - 100% of tools have a non-trivial description (not blank, and not just the tool's name).
  - 88% of tool parameters carry a description.
- **Tool Safety**: 100/100
  - No prompt-injection markers were found in the server instructions, tool names or descriptions we captured.
  - We read all 16 captured tool definition(s), and no name or description among them implies an irreversible operation.
  - An AI judge read all 17 captured unit(s) of tool text and found none that tries to manipulate the model reading it.
- **Capabilities**: 100/100
  - Implements a supported MCP spec version (2025-11-25); the latest is 2026-07-28.
  - Supports UI / widget rendering.

## Install

### How do I install the Aldine MCP server?

Aldine is a hosted endpoint at https://demo.aldine.dev/mcp, so there is nothing to install locally. Ready-made configuration for Claude, Cursor, VS Code, Codex and 5 more is on this page, copied from each client's own documentation.

### Claude

```bash
claude mcp add --transport http trahloff-aldine 'https://demo.aldine.dev/mcp'
```

### Cursor

```json
{
  "mcpServers": {
    "trahloff-aldine": {
      "url": "https://demo.aldine.dev/mcp"
    }
  }
}
```

### VS Code

```json
{
  "servers": {
    "trahloff-aldine": {
      "type": "http",
      "url": "https://demo.aldine.dev/mcp"
    }
  }
}
```

### Codex

```toml
[mcp_servers.trahloff-aldine]
url = "https://demo.aldine.dev/mcp"
```

### opencode

```json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "trahloff-aldine": {
      "type": "remote",
      "url": "https://demo.aldine.dev/mcp",
      "enabled": true
    }
  }
}
```

### OpenClaw

```bash
openclaw mcp add trahloff-aldine --url 'https://demo.aldine.dev/mcp' --transport streamable-http
```

### Hermes

```yaml
mcp_servers:
  trahloff-aldine:
    url: "https://demo.aldine.dev/mcp"
```

### Netclaw

```json
{
  "McpServers": {
    "trahloff-aldine": {
      "Transport": "http",
      "Url": "https://demo.aldine.dev/mcp"
    }
  }
}
```

### Vellum

```bash
assistant mcp add trahloff-aldine -t streamable-http -u 'https://demo.aldine.dev/mcp'
```

### Other

```json
{
  "mcpServers": {
    "trahloff-aldine": {
      "type": "http",
      "url": "https://demo.aldine.dev/mcp"
    }
  }
}
```

The mcpServers block is a cross-client convention. Remote transports vary, so check your client's docs.

## Changelog

Every change recorded for this component, newest first. Days that predate change tracking, or that we cannot explain, say so: "we were watching and nothing happened" and "we were not watching" are different claims.

### 2026-11-03 (score 66, +1)

- [functional] Server version: 1.6.0 → 1.7.0

### 2026-11-02 (score 65, 0)

- [functional] Server version: 1.5.0 → 1.6.0

### 2026-11-01 (score 65, +1)

- [security] Tool “trash_project” rewrote its description, which is the text the model reads
- [security] Tool “batch_write” is now declared destructive
- [security] Tool “edit_file” is now declared destructive
- [security] Tool “write_file” is now declared destructive
- [functional improvement] Stability: unverified → 0.03
- [functional] Server version: 1.4.0 → 1.5.0
- [cosmetic] Tool “batch_write” changed its title: Change several files in one commit
- [cosmetic] Tool “commit” changed its title: Commit pending changes
- [cosmetic] Tool “compile” changed its title: Typeset the project
- [cosmetic] Tool “create_project” changed its title: Create a project
- [cosmetic] Tool “edit_file” changed its title: Edit a file
- [cosmetic] Tool “get_pdf_url” changed its title: Get a link to the PDF
- [cosmetic] Tool “list_citations” changed its title: List citation keys
- [cosmetic] Tool “list_labels” changed its title: List labels
- [cosmetic] Tool “list_projects” changed its title: List projects
- [cosmetic] Tool “ping” changed its title: Check the connection
- [cosmetic] Tool “project_structure” changed its title: Show a project's files
- [cosmetic] Tool “read_file” changed its title: Read a file
- [cosmetic] Tool “references_add” changed its title: Add a reference by DOI or arXiv id
- [cosmetic] Tool “trash_project” changed its title: Move a project to the trash
- [cosmetic] Tool “wordcount” changed its title: Count words
- [cosmetic] Tool “write_file” changed its title: Write a whole file

### 2026-10-31 (score 64)

First indexed and scored.

## MCP tools (16)

### `ping` (~47 tokens)

Check the connection

Check that the Aldine connector is reachable and authenticated. If it fails, tell the user their Aldine instance is not responding or the connector needs reconnecting — do not go on to other tools.

### `list_projects` (~72 tokens)

List projects

List the LaTeX projects this token can reach: id, name, branches, rootFile, engine, agentCreated (true for projects made with create_project — the only ones trash_project accepts). Call it when you do not know the project id. A token scoped to one project makes the project argument optional everywhere.

### `project_structure` (~146 tokens)

Show a project's files

File tree of a project branch plus rootFile, engine, and contentVersion (pass it as base_version to edit_file, write_file or a batch_write entry — a write is refused only when its own file changed after that version). Call it before working in a project you have not read yet.

Input parameters:

- `branch` (string): Branch name (defaults to 'main'). Every result, error results included, echoes {branch, head} — head is the branch's current commit (after a write, the commit that write made). Pass the branch explic…
- `project` (string): Project id. Optional when the access token is scoped to exactly one project.

### `read_file` (~275 tokens)

Read a file

Read a text file as the editor shows it right now (open documents are flushed first). Reads cap at ~100 KB — window larger files with from_line/to_line (1-based, inclusive; the result echoes the window it served, to_line clamped to the last line). Read before you edit: edit_file quotes must match this content verbatim. The result carries contentVersion (the branch version — pass it as base_version when you write this file) and fileVersion (the version at which this file itself last changed). Text is decided by content, not extension: a file with binary bytes is refused.

Input parameters:

- `branch` (string): Branch name (defaults to 'main'). Every result, error results included, echoes {branch, head} — head is the branch's current commit (after a write, the commit that write made). Pass the branch explic…
- `from_line` (integer)
- `path` (string, required): File path relative to the project root, e.g. "main.tex" or "sections/intro.tex" — no leading "/", no "..", and hidden folders (.git, .aldine-out) are off limits.
- `project` (string): Project id. Optional when the access token is scoped to exactly one project.
- `to_line` (integer)

### `edit_file` (~565 tokens)

Edit a file

Replace exact quoted text inside an existing file — the default way to change a document, and the only safe way to change one a person has open (edits merge with their live typing; write_file would discard it). Each quote: ≥8 characters, copied verbatim from a read, matching exactly one place (or set occurrence). Nothing is applied on any error result. {error:"stale_anchor"}: the quote is not in the file — re-read it, re-anchor from the candidates, retry; at most 2 retries, then tell the user what you tried and ask. {error:"ambiguous_anchor"}: the quote matches several places — the candidates carry the occurrence that picks each; resend with it, no re-read needed. {error:"version_conflict"}: this file changed after the read that base_version came from (writes to other files never conflict, so parallel tools on different files are safe) — re-read this file, then re-apply once. Committed as author Claude before the tool answers, under message (the intent, imperative — "Tighten the abstract"); without one the commit is titled "Edit <path>". The result's commit and head are that commit; an edit that leaves the file as it was makes no commit and answers commit:null with unchanged:true (nothing is pending).

Input parameters:

- `base_version` (integer): contentVersion (or fileVersion) from your read of this file, or contentVersion from project_structure for a file you are creating. Refused with {error:"version_conflict", reason, currentVersion, file…
- `branch` (string): Branch name (defaults to 'main'). Every result, error results included, echoes {branch, head} — head is the branch's current commit (after a write, the commit that write made). Pass the branch explic…
- `edits` (array, required)
- `message` (string): Commit message stating the intent of this change, imperative ("Tighten the abstract"), at most 200 characters. It titles the commit in the History panel; every call is one commit.
- `path` (string, required): File path relative to the project root, e.g. "main.tex" or "sections/intro.tex" — no leading "/", no "..", and hidden folders (.git, .aldine-out) are off limits.
- `project` (string): Project id. Optional when the access token is scoped to exactly one project.

### `write_file` (~488 tokens)

Write a whole file

Create a new file, or replace a whole file. For an existing file prefer edit_file — a whole-file write on an open document teleports collaborators' viewports and can drop their in-flight typing. Pass base_version (contentVersion from a prior read of this file) to get {error:"version_conflict"} instead of overwriting newer content — only a change to this file conflicts, never one to another file; on conflict re-read and re-write once, then ask. Folders come into being with the first file written inside them. The first .tex written into a project with no main document becomes the main document (newRoot in the result). Committed as author Claude before the tool answers, under message (the intent, imperative); without one the commit is titled "Update <path>". The result's commit and head are that commit; content identical to the file makes no commit and answers commit:null with unchanged:true (nothing is pending).

Input parameters:

- `base_version` (integer): contentVersion (or fileVersion) from your read of this file, or contentVersion from project_structure for a file you are creating. Refused with {error:"version_conflict", reason, currentVersion, file…
- `branch` (string): Branch name (defaults to 'main'). Every result, error results included, echoes {branch, head} — head is the branch's current commit (after a write, the commit that write made). Pass the branch explic…
- `content` (string, required)
- `message` (string): Commit message stating the intent of this change, imperative ("Tighten the abstract"), at most 200 characters. It titles the commit in the History panel; every call is one commit.
- `path` (string, required): File path relative to the project root, e.g. "main.tex" or "sections/intro.tex" — no leading "/", no "..", and hidden folders (.git, .aldine-out) are off limits.
- `project` (string): Project id. Optional when the access token is scoped to exactly one project.

### `batch_write` (~267 tokens)

Change several files in one commit

Apply one multi-file change as ONE named commit (author Claude). Each entry carries full content (new files) or quote-anchored edits (existing files, same rules as edit_file). All edits resolve before anything is written — a stale_anchor or ambiguous_anchor writes nothing (the error names the entry as path); fix that entry and resend the whole batch. A version_conflict on any entry also writes nothing: re-read that file and resend. Use it for a coherent change across files; use edit_file for a single file. message = the intent, imperative ("Add related-work section"). The result's commit and head are that commit; a batch that leaves every file as it was makes no commit and answers commit:null with unchanged:true (nothing is pending).

Input parameters:

- `branch` (string): Branch name (defaults to 'main'). Every result, error results included, echoes {branch, head} — head is the branch's current commit (after a write, the commit that write made). Pass the branch explic…
- `files` (array, required)
- `message` (string, required): Commit message stating the intent of the change, at most 200 characters.
- `project` (string): Project id. Optional when the access token is scoped to exactly one project.

### `compile` (~443 tokens)

Typeset the project

Typeset the project with latexmk: parsed errors [{type,file,line,message,context?,source?}] (type "error" rows before "warning" rows, first 50; errorsTotal counts the errors and warningsTotal the warnings, whether or not the list was capped; box warnings are omitted; context is the source line the engine was reading, source the tool that raised a warning — latex, a package name, biber — and a biber row names the .bib and its line), on failure ≤4 KB of the log around the first error, pages, and a deep link into Aldine. With errors present the PDF is complete but the bibliography and cross-references are not rebuilt — say so. pdfUrl is a signed link to this run's PDF that anyone can open for 15 minutes — hand it to the user as the way to see the result, and call get_pdf_url for a fresh link later instead of recompiling. pdfStale:true means this run wrote no PDF: pdfUrl shows the previous one, or is null when that no longer exists. Takes up to ~2 minutes; progress notifications arrive while it runs. Compile after a coherent set of edits, not after each one, and never just to check syntax. On errors: fix and recompile at most 3 times, narrating each attempt ("attempt 2 of 3: added natbib"), then stop and ask the user, quoting the failing file:line. A project with no .tex file cannot be typeset: write the main document first. A compiler-not-responding, compiler-cannot-see-the-project, missing-package (hint), quota, or typeset-already-running error is for the user to act on — relay it, do not retry.

Input parameters:

- `branch` (string): Branch name (defaults to 'main'). Every result, error results included, echoes {branch, head} — head is the branch's current commit (after a write, the commit that write made). Pass the branch explic…
- `project` (string): Project id. Optional when the access token is scoped to exactly one project.

### `get_pdf_url` (~209 tokens)

Get a link to the PDF

A fresh signed link to the PDF on disk for the branch, without recompiling — use it when a compile result's pdfUrl has expired (15 minutes) or the user asks to see the PDF again. Returns {pdfUrl, pdfFile, pages, typesetAt, deepLink}; pdfStale is always false here because the link is to that file, whichever typeset wrote it (by you or by a person). It does not contain edits made since that typeset: if you have written on this branch since your last compile result, call compile instead. No output yet → compile first.

Input parameters:

- `branch` (string): Branch name (defaults to 'main'). Every result, error results included, echoes {branch, head} — head is the branch's current commit (after a write, the commit that write made). Pass the branch explic…
- `project` (string): Project id. Optional when the access token is scoped to exactly one project.

### `commit` (~258 tokens)

Commit pending changes

Commit the files you have written on this branch that are not yet committed, as one commit under message (author Claude). Only your own paths land: a collaborator's unsaved typing stays out of it and reaches history as their own autosave. Call it when the user asks for a named checkpoint — every write already commits on its own as it lands, so {committed:false} is the normal answer, not a failure: it means they landed already, and the result names the current head and your latest commits on the branch. For a multi-file change you are making right now, use batch_write instead — it writes and commits it in one step under its own message. message titles the commit and replaces the per-file titles those writes would have had.

Input parameters:

- `branch` (string): Branch name (defaults to 'main'). Every result, error results included, echoes {branch, head} — head is the branch's current commit (after a write, the commit that write made). Pass the branch explic…
- `message` (string, required): Commit message stating the intent of the checkpoint, at most 200 characters.
- `project` (string): Project id. Optional when the access token is scoped to exactly one project.

### `references_add` (~341 tokens)

Add a reference by DOI or arXiv id

Resolve a DOI, doi.org URL, arXiv id, or OpenAlex id to a BibTeX entry and append it to a .bib file on the branch (default: references.bib next to the root file). A bibFile that does not exist is created, folders included (created:true in the result). Returns {key, bibFile, duplicate, created} — use \cite{key} right away; a note says when no .tex on the branch loads that .bib (\addbibresource or \bibliography), in which case the citation stays undefined until one does. Titles are not lookups: for a paper you only know by name, ask the user for its DOI or arXiv id. If the lookup budget is reached or the upstream service fails, tell the user — do not retry in a loop.

Input parameters:

- `bibFile` (string): Target .bib path relative to the project root. Defaults to references.bib beside the root file; a missing file (and its folders) is created.
- `branch` (string): Branch name (defaults to 'main'). Every result, error results included, echoes {branch, head} — head is the branch's current commit (after a write, the commit that write made). Pass the branch explic…
- `project` (string): Project id. Optional when the access token is scoped to exactly one project.
- `query` (string, required): A DOI (10.xxxx/…), doi.org URL, arXiv id (2301.12345 or arXiv:2301.12345), or OpenAlex id (W…).

### `list_citations` (~142 tokens)

List citation keys

Citation keys defined in the project's .bib files: [{key, title, author, year, file}]. Always call it before writing a \cite — never invent a key. A key you need that is missing: add it with references_add, or ask the user.

Input parameters:

- `branch` (string): Branch name (defaults to 'main'). Every result, error results included, echoes {branch, head} — head is the branch's current commit (after a write, the commit that write made). Pass the branch explic…
- `project` (string): Project id. Optional when the access token is scoped to exactly one project.

### `list_labels` (~124 tokens)

List labels

\label targets defined in the project's .tex files: [{label, file}]. Call it before writing \ref, \eqref, or \cref — never invent a label.

Input parameters:

- `branch` (string): Branch name (defaults to 'main'). Every result, error results included, echoes {branch, head} — head is the branch's current commit (after a write, the commit that write made). Pass the branch explic…
- `project` (string): Project id. Optional when the access token is scoped to exactly one project.

### `wordcount` (~149 tokens)

Count words

Word count of the document as it compiles — the root file plus its \input/\include graph, commands and comments excluded: {rootFile, total, files}. Use it for length questions instead of estimating from source. A project with no main document yet is an error, not a count of 0.

Input parameters:

- `branch` (string): Branch name (defaults to 'main'). Every result, error results included, echoes {branch, head} — head is the branch's current commit (after a write, the commit that write made). Pass the branch explic…
- `project` (string): Project id. Optional when the access token is scoped to exactly one project.

### `create_project` (~207 tokens)

Create a project

Create a new project, blank or from a template, and return its id for the write tools, with contentVersion (pass it as base_version to the first write) and the files the template seeded. Needs a token with access to all projects — a project-scoped token cannot create; if refused, relay that to the user (they can mint an unscoped token in Settings → Agent access).

Input parameters:

- `name` (string, required): Project name shown in the workspace.
- `template` (string): Template id from the workspace gallery: a folder template ("article", "beamer", "report", "iac-paper") or a venue kit ("venue:neurips", "venue:elsarticle", …; the publisher's class files are download…

### `trash_project` (~156 tokens)

Move a project to the trash

Move a project that was created with create_project to the trash, where its owner can restore it from the workspace for 30 days; nothing in Aldine is deleted for good by this tool, but a GitLab repository Aldine provisioned for the project is deleted (a restore provisions a new one). Refused for any project a person created (agentCreated:false in list_projects) — ask them to delete it in Aldine themselves — and for projects the token's owner does not own. Use it for scratch projects the user asked you to throw away; never on your own initiative.

Input parameters:

- `project` (string, required): Project id (from list_projects or create_project). Required: this tool never falls back to a scoped token's project.

## Diagnostics

Captured diagnostic sections: TLS, DNSSEC, Authorisation, Transports. The full working is on the page: https://verifymcp.io/servers/trahloff-aldine/demo#diagnostics

## Score history

- 2026-11-04: 66
- 2026-11-03: 66
- 2026-11-02: 65
- 2026-11-01: 65
- 2026-10-31: 64

## Common questions

### What is the Aldine MCP server?

Aldine is an MCP server listed in the public MCP registry as io.github.trahloff/aldine. Self-hosted collaborative LaTeX editor: read and edit files, typeset, get the errors and the PDF. This page covers its hosted endpoint (https://demo.aldine.dev/mcp).

### Is the Aldine MCP server safe to use?

Aldine scores 66 out of 100 on VerifyMCP. That is a record of what we were able to check automatically, not an endorsement. The category breakdown on this page shows every signal behind the number, including the ones we could not confirm.

### What tools does the Aldine MCP server expose?

Aldine exposes 16 tools: ping, list_projects, project_structure, read_file, edit_file, and 11 more. Their descriptions and schemas cost roughly 3,889 tokens of context every time the server is loaded.

### Does the Aldine MCP server require authentication?

Its publisher declares no required credentials for Aldine. We have not been able to confirm that against the live endpoint, and a server can require authorisation without declaring it here.

### Is the Aldine MCP server still maintained?

Aldine is still listed as active in the MCP registry. We last reached this channel on 4 November 2026. Those dates come from our own scans of the registry and the channel itself, not from anything the publisher announced.

## Links

- Remote endpoint: https://demo.aldine.dev/mcp
- Repository: https://github.com/trahloff/Aldine
- Website: https://aldine.dev/latex-mcp/
- Changelog RSS feed: https://verifymcp.io/servers/trahloff-aldine/demo.xml
- Changelog JSON feed: https://verifymcp.io/servers/trahloff-aldine/demo.json
- HTML version of this page: https://verifymcp.io/servers/trahloff-aldine/demo
