MCP server that saves your Claude Code, Cursor, and Codex sessions as versioned Markdown reports — with human-in-the-loop review and Apple Pencil / S Pen annotation.
- ✓Open-source license (MIT)
- ✓Actively maintained (<30d)
- ✓Clear description
- ✓Topics declared
- ✓Documented (README)
claude mcp add md-log-mcp -- npx -y md-log-mcp{
"mcpServers": {
"md-log-mcp": {
"command": "npx",
"args": ["-y", "md-log-mcp"],
"env": {
"MDLOG_API_BASE_URL": "<mdlog_api_base_url>"
}
}
}
}MDLOG_API_BASE_URLMCP Servers overview
# md-log-mcp
[](https://www.npmjs.com/package/md-log-mcp)
[](https://registry.modelcontextprotocol.io/v0/servers?search=md-log)
[](./LICENSE)
[](https://nodejs.org)
> **Review the report, not the diff.** An MCP server that lets your AI coding agent — **Claude Code, Claude Desktop, Codex, Cursor** — save its work and analysis as immutable, versioned **Markdown reports** into [**md-log**](https://md-log.com), a human-in-the-loop review & archive layer for **"vibe coding."** You then read and **stylus-annotate** (S-Pen / Apple Pencil) those reports on web, phone, and tablet — every save a new immutable version.
A [Model Context Protocol](https://modelcontextprotocol.io) server — **two transports, one tool set** —
that lets **Claude Code** (and other agents) save `.md` files — text **and** embedded screenshots
together — straight into **md-log**, a human-in-the-loop review & archive layer for vibe coding. The
**recommended** way to connect is the **hosted remote endpoint** (`https://mcp.md-log.com/mcp`, a URL +
your key — no install); a local **stdio** (`npx -y md-log-mcp`) transport is the alternative. The agent writes a report
**by path** (`my-project/2026-07-07-error-report.md`); missing folders are auto-created, images are
uploaded and their references rewritten to `asset://` links, and every save becomes an immutable new
version. The same report is then readable, editable, and stylus-annotatable (S-Pen / Apple Pencil
where supported) on a phone or tablet, and on the web.
md-log is a **hosted service** at **https://app.md-log.com** — you don't run any server yourself.
This package is just the connector: a **thin authenticated HTTP client** that validates POSIX paths,
orchestrates asset uploads, maps errors to stable agent codes, and forwards everything to the hosted
md-log service — the single authority for auth, storage, versioning and quota. All you need is a
Personal Access Token from the web app.
## Stack
- **@modelcontextprotocol/sdk** (TypeScript) — one `McpServer` (17 tools), two transports.
- **stdio transport** (`md-log-mcp`) — JSON-RPC over stdin/stdout; the default local mode (so stdout
is reserved for the protocol; logs go to stderr). PAT from env.
- **Streamable HTTP transport** (`md-log-mcp-http`) — the remote mode: agents connect by URL with no
local install; the PAT is taken **per request** from the `Authorization` header. See
[Remote (Streamable HTTP) mode](#remote-streamable-http-mode).
- **TypeScript**, bundled with **tsup** to ESM `dist/server.js` (stdio) + `dist/http.js` (HTTP).
Runtime deps: the MCP SDK and **zod** (input schemas). Node's built-in `fetch`/`http` are the only
network layers — no web framework.
- **PAT auth** — a md-log Personal Access Token sent to the backend as `Authorization: Bearer`.
## Requirements
- Node **22+**
- A **Personal Access Token** (PAT) minted in the md-log web app (**Settings → Tokens**; shown once)
That's it — the md-log service itself is hosted at `https://app.md-log.com`; there is nothing to
install or self-host.
## Tools (17)
Every tool returns dual output — a human-readable `content[].text` and a machine-readable
`structuredContent` — and validates the POSIX path (NFC-normalize; reject `..`/`.`, control chars,
empty/whitespace segments, backslashes, reserved names; enforce 255-byte name / 1024-byte path
limits; require `.md` for files) **before** any backend call. All requests hit the base URL in
`MDLOG_API_BASE_URL` (which already includes `/api/v1`).
| Tool | What it does |
| ---- | ------------ |
| **`save_markdown`** ⭐ | The headline tool. Create or overwrite a `.md` by path (force last-writer-wins); missing folders auto-created. Optionally uploads embedded images first (each given as `data_base64` **or** a local `file_path`) and rewrites each `placeholder` in the content to an `asset://<key>` link. Accepts `commit_message` — a recommended 1-2 line change summary shown in the version history. |
| `upload_asset` | Upload one image (reserve → presigned PUT → complete) and return an `asset://<key>` reference to embed as ``. Provide the image as **either** `data_base64` (inline base64) **or** `file_path` (a local file the server reads) — exactly one; with `file_path`, `filename` defaults to the basename and `content_type` is inferred from the extension (png/jpg/jpeg/gif/webp/avif). |
| `append_to_markdown` | Append to an existing file with optimistic concurrency (GET current → concat → conditional PUT with `base_version_no`). Auto-retries once on conflict, then surfaces `CONFLICT`. Accepts `commit_message` — a recommended 1-2 line change summary shown in the version history. |
| **`edit_markdown`** | Change PART of a file by exact literal replacement (read → apply → conditional PUT with `base_version_no`; auto-retries once on conflict, re-applying every edit). Pass several changes at once as **`edits: [{old_string, new_string, replace_all?}, …]`** (max 50, applied in order) — they land as **one write = one version = one notification**; for a single change the `old_string`/`new_string` shorthand still works (never both). Each `old_string` must match **exactly once**, counting overlapping positions too (`"\n\n"` inside `"\n\n\n"` is ambiguous → `detail.overlapping: true`) — 0 matches or an ambiguous >1 rejects the **whole call** with `VALIDATION` (`detail.reason` = `NO_MATCH` / `AMBIGUOUS_MATCH`, `detail.edit_index` = the failing edit) and **nothing is written**; set `replace_all:true` to change every occurrence on purpose. Other `detail.reason`s: input shape — `MIXED_INPUT`, `MISSING_EDIT`, `EMPTY_EDITS`, `TOO_MANY_EDITS`, `EMPTY_OLD_STRING`, `IDENTICAL_STRINGS` (rejected before any read); result — `NO_CHANGE` (edits cancel out), `DOCUMENT_TOO_LARGE` (a step would pass the 25 MiB cap), `TOO_MANY_REPLACEMENTS` (more occurrences replaced in one call than the document cap allows), `BATCH_TOO_SLOW` (edits after the first stopped once the call has run 2 s — split them across calls). A match failure on the conflict retry carries `detail.after_concurrent_write: true`. Never creates a file (missing path → `NOT_FOUND`) and has no force mode — prefer it over `update_markdown` for any partial change. |
| `update_markdown` | Replace a file's ENTIRE content. Pass `expected_version` for optimistic concurrency (mismatch → `CONFLICT`); omit it to force LWW. Passing `expected_version` for a path that does **not** exist now returns `CONFLICT` (`detail.server_version_no` = `0`), not a silent create. For a partial change use `edit_markdown` instead. Accepts `commit_message` — a recommended 1-2 line change summary shown in the version history. |
| `restore_version` | Roll a file back to an earlier `version_no` (from `list_versions`): that version's content becomes a NEW current version — history is immutable, so the rollback is itself undoable. Restoring the version the file is already on is an accepted no-op. Accepts `commit_message`. |
| `get_markdown` | Read a file's content by path (materializes inline content or a presigned content URL for large docs). Pass `version` (a `version_no` from `list_versions`) to read an old immutable version. |
| `list_versions` | List a file's immutable version history, newest first (`version_no`, `commit_message`, author, `registered_at`, size). |
| `delete_markdown` | Soft-delete a file. Requires `confirm:true` (otherwise `VALIDATION`); resolves the path to a document key first. |
| `create_folder` | `mkdir -p` — create every missing segment; already-existing folders count as success. |
| `list_folders` | Return the full folder tree. |
| `list_files` | List the documents and immediate subfolders inside a folder path. |
| `search_markdown` | Search by TITLE (substring) + BODY full-text (current versions; whole-word match, ranked, body hits include a snippet). |
| `move_markdown` | Move and/or rename a `.md` by path (`from_path` → `to_path`); destination folders auto-created; the document KEEPS its key, so version history and reviewers' annotations survive. |
| `move_folder` | Move a folder (whole subtree) under a new parent (`new_parent_path` empty/omitted = root); parent auto-created; cyclic moves rejected server-side. |
| `rename_folder` | Rename a folder in place (descendant paths rewritten server-side). |
| `delete_folder` | Delete a folder. Requires `confirm:true`; by default only an EMPTY folder is deleted — pass `cascade:true` to soft-delete the whole subtree (`rm -r`). |
### Error codes surfaced to the agent
Backend failures return `{ isError: true, content:[{type:"text", ...}] }` with a mapped code in
`structuredContent.error.code`:
`NOT_FOUND` · `CONFLICT` (carries the server head `{server_version_no, server_checksum, …}` in
`detail`) · `UNAUTHORIZED` · `RATE_LIMITED` · `QUOTA_EXCEEDED` · `BACKEND_UNAVAILABLE` ·
`VALIDATION` · `FOLDER_EXISTS` (swallowed as success by `create_folder`) · `ERROR`.
## Authentication
The MCP/PC lane authenticates with a **Personal Access Token** (`mdlog_pat_…`) — minted once in the
web app's **Settings** and supplied via env. The client attaches it as `Authorization: Bearer <PAT>`
(plus `X-API-Token` for compatibility) on every request. The backend is the single source of truth
for auth and quota.
| Variable | Required | Example | Notes |
| -------- | -------- | ------- | ----- |
| `MDLOG_API_BASE_URL` | yes | `https://app.md-log.com/api/v1` | The hosted service base, **including** `/api/v1`. No version suffix is appended; a trailing slash is stripped. |
| `MDLOG_PAT` | yes | `mdlog_pat_xxxxxxxxxxxxxxxxxxxxxxxx` | Bearer PAT. **Store it securely (OS keychain) — never commit it.** |
| `MDLOG_MAX_DOCUMENT_BYTES` | no | `26214400` (25 MiB) | Client-side pre-flight for the backend's per-document body capWhat people ask about md-log-mcp
What is md-log/md-log-mcp?
+
md-log/md-log-mcp is mcp servers for the Claude AI ecosystem. MCP server that saves your Claude Code, Cursor, and Codex sessions as versioned Markdown reports — with human-in-the-loop review and Apple Pencil / S Pen annotation. It has 3 GitHub stars and its last recorded update is dated 2026-10-04.
How do I install md-log-mcp?
+
You can install md-log-mcp by cloning the repository (https://github.com/md-log/md-log-mcp) or following the README instructions on GitHub. ClaudeWave also provides quick install blocks on this page.
Is md-log/md-log-mcp safe to use?
+
Our security agent has analyzed md-log/md-log-mcp and assigned a Trust Score of 95/100 (tier: Verified). See the full breakdown of passed checks and flags on this page.
Who maintains md-log/md-log-mcp?
+
md-log/md-log-mcp is maintained by md-log. The last recorded GitHub activity is dated 2026-10-04, with 0 open issues.
Are there alternatives to md-log-mcp?
+
Yes. On ClaudeWave you can browse similar mcp servers at /categories/mcp, sorted by popularity or recent activity.
Deploy md-log-mcp to your cloud
Ship this repo to production in minutes. Each platform spins up its own environment with editable env vars.
Maintain this repo? Add a badge to your README
Drop the badge into your GitHub README to show it's tracked on ClaudeWave. Each badge links back to this page and reflects the live Trust Score.
[](https://claudewave.com/repo/md-log-md-log-mcp)<a href="https://claudewave.com/repo/md-log-md-log-mcp"><img src="https://claudewave.com/api/badge/md-log-md-log-mcp" alt="Featured on ClaudeWave: md-log/md-log-mcp" width="320" height="64" /></a>More MCP Servers
Fair-code workflow automation platform with native AI capabilities. Combine visual building with custom code, self-host or cloud, 400+ integrations.
User-friendly AI Interface (Supports Ollama, OpenAI API, ...)
An open-source AI agent that brings the power of Gemini directly into your terminal.
Real-time global intelligence dashboard. AI-powered news aggregation, geopolitical monitoring, and infrastructure tracking in a unified situational awareness interface
🕷️ An adaptive Web Scraping framework that handles everything from a single request to a full-scale crawl! Don't be shy, join here: https://discord.gg/EMgGbDceNQ and follow here for daily tips and tricks: https://x.com/Scrapling_dev
The fastest path to AI-powered full stack observability, even for lean teams.