Skip to main content
Glama

Tether MD

You comment. The agent proposes. You decide.

CI npm license: MIT

Comment across the draft, as fact or interpretation. The agent works through every comment in one pass; you accept or reject each change. Export stays byte-identical.


Most AI writing surfaces either rewrite your document under you (chat canvases, "apply" buttons) or lock review into a platform (Google Docs, Word, Notion). Markdown in git has no standard suggest mode: a way to leave a comment on a phrase, have an AI act on it, and approve or reject each change yourself.

Tether MD is that layer: anchored comments for Markdown that AI agents act on, but never apply. It is a file format rather than a platform.

  • Comments live in the file. One invisible HTML-comment marker per comment, plus a machine-readable block at the end. No database, no sidecar, no service; the raw file renders clean on GitHub, in VS Code preview, and through pandoc.

  • Anchors survive editing. Quote selectors with fuzzy re-anchoring follow the text through edits, flag uncertain matches as needs-review, or orphan with an error.

  • Agents propose; humans apply. Your comments are anchored instructions. An agent attaches proposals; Accept applies one and clears the comment, Reject discards it. Paragraph moves follow the same rule: mark where text should go, and nothing moves until you press Accept Move.

  • Clean export is tested in CI. One projection function strips the comment layer; the result is byte-for-byte your prose.

Install

Three pieces. The CLI is the foundation; the other two are optional and independent.

  1. CLI + MCP server — npm i -g tether-md (Node 20+). Gives you the tether command and tether mcp.

  2. VS Code extension — download tether-md.vsix from Releases, then Extensions panel → → Install from VSIX → reload the window. (Or from a clone: npm install && npm run build && npm run package -w tether-md-vscode.)

  3. Agent compatibility, per project — run tether init in the project root. It writes .mcp.json (project-scoped MCP server) and the AGENTS.md contract note; tether init --skill also installs the Claude Code skill. Details in Hook up your agent.

Related MCP server: MD Vision

Quickstart

npm i -g tether-md
printf 'A draft with some exact phrase in it.\n' > draft.md
tether comment add draft.md --quote "some exact phrase" --body "tighten this" --write
tether status draft.md                       # comments, proposals, anchor health
tether comment suggest draft.md <id> --to "a tighter version" --write   # the agent's move (or yours)
tether comment diff draft.md <id>            # preview:  - current / + proposed
tether comment accept draft.md <id> --write  # apply it and clear the comment
tether export draft.md                       # the authored prose, byte-identical

Every mutating command prints its result by default, only edits in place with --write (atomically), and takes --json; comment list and error envelopes are JSON already. Exit codes are stable:

code

meaning

0

ok

1

usage error

2

malformed store block

3

IO error

4

check failed (status --check)

Try it on a real document in 30 seconds: examples/ ships a worked walkthrough, including the one-line proof of the export guarantee (tether export reviewed.md | diff - draft.md prints nothing: byte-identical).

Hook up your agent

One command, in your project:

tether init            # .mcp.json (project-scoped MCP server) + AGENTS.md contract note
tether init --skill    # also installs the Claude Code skill into .claude/skills

Claude Code discovers .mcp.json on its own the next time the project opens, and AGENTS.md carries the contract for agents that bring their own file tools. Both writes are idempotent and merge into existing files. To wire the MCP server by hand instead, any MCP-capable agent works (Claude Code, Cursor, Codex CLI, and others):

claude mcp add tether -- tether mcp

The MCP server exposes tether_list, tether_status, tether_diff, tether_suggest, tether_comment (flag-backs), and tether_export. It deliberately omits accept, reject, and edit, and its server instructions tell every connected agent to stay on that surface. Within it, the trust boundary is structural, not prompt etiquette.

One caveat that matters: most coding agents (Claude Code, Cursor, and friends) also carry their own file tools, and no file format can gate those. An agent that has never been told the contract will treat a Tether file like any other markdown and edit it in place. So tell it: run tether init, install the skill below, or add this to your project's AGENTS.md or CLAUDE.md:

Markdown files here may contain tether-md comments: invisible <!--tether:...-->
markers plus a store block at EOF. Never edit these files directly; never touch
the markers or the store block. Read the threads with `tether comment list`,
propose rewrites with `tether comment suggest <file> <id> --to "..." --write`,
flag concerns with `tether comment add <file> --quote "..." --body "..."
--author agent --write`. Accept, reject, and export belong to the human.

For Claude Code there is also a full skill, skills/tether-edit/SKILL.md. It teaches the contract end to end: read the document, read the comments, propose per instruction, flag back anything that can't be grounded, hand control back. The agent never applies, resolves, or exports.

The working loop becomes: comment on phrases, tell the agent to "address my comments", then review each suggestion in your editor and accept or reject it.

In VS Code

Install per Install above, open any markdown file, and everything below works on it. Comments render as native inline threads; nothing needs configuring.

keys / surface

what it does

select prose, ⌘⌥C (Ctrl+Alt+C)

comment on the selection: type the note, pick fact or interpretation

gutter +

same, mouse-first (one + per logical line; for a second comment on the same soft-wrapped paragraph, select and use the keybinding)

caret in a paragraph, ⌘⌥M (Ctrl+Alt+M)

pick the paragraph up to move (no selection needed); then click where it should go — the click snaps to the nearest paragraph boundary

⌘⌥M again while a move is armed

destination list with a live "move here" preview in the editor

Esc

cancel an armed move (any edit also cancels it)

thread buttons

Accept / Reject on an agent's suggestion; Accept Move / Reject on a move; Delete on a plain comment

right-click menu

Tether: Add Comment from Selection, Tether: Move Paragraph

command palette

Tether: Export Clean Document — writes <name>.clean.md beside the file

A marked move is just a comment with a destination: the excerpt gets a ① badge, the destination a ⇣① badge, and nothing actually moves until you press Accept Move on its thread. Moves show up in tether comment list (a moveTo field) and tether comment accept applies them, so the CLI and the editor stay interchangeable.

If anchoring ever degrades, you hear about it: fuzzy re-anchors get a dashed warning underline and a diagnostic, and Accept refuses until re-confirmed; orphans land in the Problems panel with a one-click fix.

The editor and the agent never talk to each other. Both only touch the file, so you can watch proposals arrive in your open editor.

What the file actually looks like

The pattern is always the same. The app starts as a text editor and becomes a
database. <!--tether:c=01KWQ2QFDFTHXWAF7RJS1ZNSSX-->Databases survive until the
next funding round.

<!--tether:store
{"id":"01KWQ2QFDFTHXWAF7RJS1ZNSSX","v":1,"trust":"fact","author":"human","body":"sharpen this",...}
tether:store-->

One caret marker per comment (a ULID, which can never form the -- sequence HTML comments forbid), and all data in one JSONL store block at the end of the file, escaped to stay grammar-legal. Renderers hide HTML comments, so the raw file previews clean on GitHub, in VS Code preview, and through pandoc. The grammar, the projection algorithm, the selector model, and the re-anchoring confidence bands are specified in docs/spec/wire-format-and-projection.md. The spec is the product; the TypeScript here is the reference implementation.

What happens when you edit the text

A comment points at a phrase, and you keep editing the document. Each time the file is read, every comment is re-matched to the current text and gets a confidence score between 0 and 1. The score decides one of three outcomes:

outcome

when

what you see

anchored

the phrase still matches (score ≥ 0.75)

the comment follows its text

needs-review

the phrase changed enough to blur the match (0.50–0.75)

a dashed warning underline and a diagnostic; Accept refuses until you re-confirm

orphaned

the phrase is effectively gone (below 0.50)

a loud error; tether status --check exits 4, so CI can catch it

A weak match downgrades to needs-review or orphans loudly; it does not silently attach to the wrong words.

Matching tries the exact phrase first, disambiguates duplicates by their surrounding context, and only then falls back to fuzzy matching, which is where the score comes from (details in the spec). One extra safeguard on top: accepting a proposal requires the anchored text to still read exactly as it did when the proposal was made, so a stale suggestion can never splice onto changed wording.

The three invariants

Everything above reduces to three properties, tested on every commit:

  1. Re-anchor or orphan. After any edit, every comment re-anchors above the confidence floor or orphans loudly. Weak matches are never silent.

  2. Zero perturbation. Adding or removing a comment never changes the exported prose or any other comment's anchor.

  3. Export identity. tether export is byte-for-byte the authored prose.

How it compares

comments in the file

survive edits

AI propose, human accept

works with any agent

clean export guarantee

Tether MD

yes

fuzzy, loud orphans

native threads / CLI

CLI + MCP + skill

byte-identical, in CI

Google Docs / Word / Notion

no (platform DB)

yes

their AI only

no

export differs from source

GitHub PR suggestions

no (PR thread)

no (stale on rebase)

in the PR UI

PR-bound bots

n/a (reviews a diff, not the doc)

CriticMarkup

yes (visible syntax)

inline, no drift detection

no native flow

format only

processor-dependent

Cursor / chat canvases

no (session diff)

until the session ends

no

Roughdraft

yes (CriticMarkup)

inline, no drift detection

in its local web UI

yes (CLI)

not spec'd or CI-tested

The bet: suggest mode should be a property of the document, not of an app. That is also why the wire format is specified independently of this implementation; ports are welcome.

Why not CriticMarkup? Its syntax is visible noise in any CriticMarkup-unaware renderer (GitHub included), and its comments bind only by adjacency: nothing detects when the prose they were about is rewritten. Tether's markers hide in rendered views and re-anchor by quoted text. Why not PR review? Suggestions there attach to diff lines in a forge's database, need a remote and a pull request, and go stale on rebase; here the document itself carries the review, offline, in any repo state.

Monorepo

Package

What

tether-md

The tether CLI and MCP server (tether mcp): the stable, file-only contract surface.

@tether-md/kernel

The pure projection kernel: P(raw) → clean, offset maps, anchoring, store codec. No IO.

tether-md-vscode

The VS Code extension (native Comments API).

skills/

The agent contract (Claude Code skill).

docs/

The wire-format spec, architecture decision records, milestone history.

git clone https://github.com/tether-md/tether-md && cd tether-md
npm install && npm run build && npm test

Limitations

  • Editor UI is VS Code-only today. The kernel is editor-agnostic; Obsidian and nvim ports are the most-wanted contributions.

  • The propose-not-apply contract is structural on the MCP surface, but an agent with its own file tools follows it only once it has the skill or the AGENTS.md note above. An untold agent edits the file like any other markdown.

  • Store blocks are LF-only by grammar. CRLF hard-fails loudly rather than mis-parsing; the repo's .gitattributes protects checkouts, and tolerant reads are tracked.

  • Paragraph moves are LF-only and paragraph-granular (paragraphs are blank-line separated). Seams that touch CRLF or whitespace-only blank lines, and destinations whose text repeats verbatim elsewhere, are refused loudly rather than guessed at.

  • A file with a pending move is written as a v: 2 record: tether-md 0.1.x refuses the whole file until the move is accepted or rejected (deliberate — an older version could otherwise half-apply it). Accepting or rejecting all moves returns the file to v: 1.

  • One store block per file; concurrent writers race at the file level. Treat the raw file like source code (git merges the store poorly).

  • A comment anchored inside a code region relocates its marker to just before that region. Two pathological shapes are rejected with clear errors.

Roadmap

  • Verification gate (the origin of this project): fact-grounding and claim-strength checks whose findings arrive as anchored comments.

  • CriticMarkup import/export, conformance test vectors for ports, an Obsidian plugin, a browser build of the kernel.

Contributing

The spec comes first, the three invariants are non-negotiable, and correctness bugs get priority over everything else. See CONTRIBUTING.md.

License

MIT

A
license - permissive license
-
quality - not tested
A
maintenance

Maintenance

Maintainers
Response time
0dRelease cycle
3Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/tether-md/tether-md'

If you have feedback or need assistance with the MCP directory API, please join our Discord server