{
  "markdown": "# md-log-mcp\n\n[![npm version](https://img.shields.io/npm/v/md-log-mcp)](https://www.npmjs.com/package/md-log-mcp)\n[![MCP Registry](https://img.shields.io/badge/MCP%20Registry-com.md--log%2Fmd--log--mcp-1f6feb)](https://registry.modelcontextprotocol.io/v0/servers?search=md-log)\n[![License: MIT](https://img.shields.io/npm/l/md-log-mcp)](./LICENSE)\n[![Node](https://img.shields.io/node/v/md-log-mcp)](https://nodejs.org)\n\n> **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.\n\nA [Model Context Protocol](https://modelcontextprotocol.io) server — **two transports, one tool set** —\nthat lets **Claude Code** (and other agents) save `.md` files — text **and** embedded screenshots\ntogether — straight into **md-log**, a human-in-the-loop review & archive layer for vibe coding. The\n**recommended** way to connect is the **hosted remote endpoint** (`https://mcp.md-log.com/mcp`, a URL +\nyour key — no install); a local **stdio** (`npx -y md-log-mcp`) transport is the alternative. The agent writes a report\n**by path** (`my-project/2026-07-07-error-report.md`); missing folders are auto-created, images are\nuploaded and their references rewritten to `asset://` links, and every save becomes an immutable new\nversion. The same report is then readable, editable, and stylus-annotatable (S-Pen / Apple Pencil\nwhere supported) on a phone or tablet, and on the web.\n\nmd-log is a **hosted service** at **https://app.md-log.com** — you don't run any server yourself.\nThis package is just the connector: a **thin authenticated HTTP client** that validates POSIX paths,\norchestrates asset uploads, maps errors to stable agent codes, and forwards everything to the hosted\nmd-log service — the single authority for auth, storage, versioning and quota. All you need is a\nPersonal Access Token from the web app.\n\n## Stack\n\n- **@modelcontextprotocol/sdk** (TypeScript) — one `McpServer` (15 tools), two transports.\n- **stdio transport** (`md-log-mcp`) — JSON-RPC over stdin/stdout; the default local mode (so stdout\n  is reserved for the protocol; logs go to stderr). PAT from env.\n- **Streamable HTTP transport** (`md-log-mcp-http`) — the remote mode: agents connect by URL with no\n  local install; the PAT is taken **per request** from the `Authorization` header. See\n  [Remote (Streamable HTTP) mode](#remote-streamable-http-mode).\n- **TypeScript**, bundled with **tsup** to ESM `dist/server.js` (stdio) + `dist/http.js` (HTTP).\n  Runtime deps: the MCP SDK and **zod** (input schemas). Node's built-in `fetch`/`http` are the only\n  network layers — no web framework.\n- **PAT auth** — a md-log Personal Access Token sent to the backend as `Authorization: Bearer`.\n\n## Requirements\n\n- Node **22+**\n- A **Personal Access Token** (PAT) minted in the md-log web app (**Settings → Tokens**; shown once)\n\nThat's it — the md-log service itself is hosted at `https://app.md-log.com`; there is nothing to\ninstall or self-host.\n\n## Tools (15)\n\nEvery tool returns dual output — a human-readable `content[].text` and a machine-readable\n`structuredContent` — and validates the POSIX path (NFC-normalize; reject `..`/`.`, control chars,\nempty/whitespace segments, backslashes, reserved names; enforce 255-byte name / 1024-byte path\nlimits; require `.md` for files) **before** any backend call. All requests hit the base URL in\n`MDLOG_API_BASE_URL` (which already includes `/api/v1`).\n\n| Tool | What it does |\n| ---- | ------------ |\n| **`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. |\n| `upload_asset` | Upload one image (reserve → presigned PUT → complete) and return an `asset://<key>` reference to embed as `![alt](asset://<key>)`. 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). |\n| `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. |\n| `update_markdown` | Replace a file's content. Pass `expected_version` for optimistic concurrency (mismatch → `CONFLICT`); omit it to force LWW. Accepts `commit_message` — a recommended 1-2 line change summary shown in the version history. |\n| `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. |\n| `list_versions` | List a file's immutable version history, newest first (`version_no`, `commit_message`, author, `registered_at`, size). |\n| `delete_markdown` | Soft-delete a file. Requires `confirm:true` (otherwise `VALIDATION`); resolves the path to a document key first. |\n| `create_folder` | `mkdir -p` — create every missing segment; already-existing folders count as success. |\n| `list_folders` | Return the full folder tree. |\n| `list_files` | List the documents and immediate subfolders inside a folder path. |\n| `search_markdown` | Search by TITLE (substring) + BODY full-text (current versions; whole-word match, ranked, body hits include a snippet). |\n| `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. |\n| `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. |\n| `rename_folder` | Rename a folder in place (descendant paths rewritten server-side). |\n| `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`). |\n\n### Error codes surfaced to the agent\n\nBackend failures return `{ isError: true, content:[{type:\"text\", ...}] }` with a mapped code in\n`structuredContent.error.code`:\n\n`NOT_FOUND` · `CONFLICT` (carries the server head `{server_version_no, server_checksum, …}` in\n`detail`) · `UNAUTHORIZED` · `RATE_LIMITED` · `QUOTA_EXCEEDED` · `BACKEND_UNAVAILABLE` ·\n`VALIDATION` · `FOLDER_EXISTS` (swallowed as success by `create_folder`) · `ERROR`.\n\n## Authentication\n\nThe MCP/PC lane authenticates with a **Personal Access Token** (`mdlog_pat_…`) — minted once in the\nweb app's **Settings** and supplied via env. The client attaches it as `Authorization: Bearer <PAT>`\n(plus `X-API-Token` for compatibility) on every request. The backend is the single source of truth\nfor auth and quota.\n\n| Variable | Required | Example | Notes |\n| -------- | -------- | ------- | ----- |\n| `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. |\n| `MDLOG_PAT` | yes | `mdlog_pat_xxxxxxxxxxxxxxxxxxxxxxxx` | Bearer PAT. **Store it securely (OS keychain) — never commit it.** |\n\nThe server fails fast at startup with a clear message if either var is missing or the base URL is\nmalformed.\n\n## Build\n\n```bash\nnpm install\nnpm run build      # tsup → dist/server.js (ESM, Node 22)\nnpm run typecheck  # tsc --noEmit (optional)\n```\n\n## Smoke test\n\n`scripts/smoke.mjs` spawns the **built** server over stdio (MCP SDK `Client` +\n`StdioClientTransport`), then runs `initialize` → `tools/list` → `save_markdown` (a small report\nembedding a tiny `data:` PNG) → `get_markdown` (reads it back, checks the marker) →\n`search_markdown` — printing PASS/FAIL per step and exiting non-zero on any failure. Run it against\na **live** backend with a real PAT:\n\n```bash\nnpm run build\nMDLOG_API_BASE_URL=\"http://localhost:8080/api/v1\" \\\nMDLOG_PAT=\"mdlog_pat_xxxx\" \\\nnode scripts/smoke.mjs        # or: npm run smoke\n```\n\n## Connect a client\n\nMint the PAT in the web app's **Settings → Tokens**, store it securely, then add md-log to your MCP\nclient. **Never commit a PAT.**\n\n> 📄 **연결 가이드 (HTML)** — [md-log.com/guides/customer-guide.html](https://www.md-log.com/guides/customer-guide.html): 웹 앱에서 발급받은 **MCP 키(PAT)** 로 URL 연결(권장) 또는 `npx` 로컬 연결 (Claude Code · Desktop · Codex · Cursor).\n\n### Recommended — remote (URL), no install\n\nPoint your client at the **hosted endpoint** and pass the PAT as a Bearer header. **Nothing to\ninstall — no Node.js, no `npx`.** (You don't even need this package for the hosted connection.)\n\n```bash\n# Claude Code\nclaude mcp add --transport http md-log https://mcp.md-log.com/mcp \\\n  --header \"Authorization: Bearer mdlog_pat_xxxxxxxxxxxxxxxxxxxxxxxx\"\n```\n\n```jsonc\n// Cursor / Claude Desktop / any client that takes JSON — the `type` field MUST be \"http\"\n{\n  \"mcpServers\": {\n    \"md-log\": {\n      \"type\": \"http\",\n      \"url\": \"https://mcp.md-log.com/mcp\",\n      \"headers\": { \"Authorization\": \"Bearer mdlog_pat_xxxxxxxxxxxxxxxxxxxxxxxx\" }\n    }\n  }\n}\n```\n\nOver the remote endpoint, embed images inline (base64); uploading a local image **by path**\n(`file_path`) works only with the local method below.\n\n### Alternative — local (stdio via `npx`)\n\nRuns this connector as a local subprocess (needs Node 22+; `npx` fetches the published package, nothing\nto build). Use it if you prefer a local process, need local-file (`file_path`) image uploads, or\nself-host md-log without a hosted MCP endpoint.\n\n```jsonc\n{\n  \"mcpServers\": {\n    \"md-log\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"md-log-mcp\"],\n      \"env\": {\n        \"MDLOG_API_BASE_URL\": \"https://app.md-log.com/api/v1\",\n        \"MDLOG_PAT\": \"mdlog_pat_xxxxxxxxxxxxxxxxxxxxxxxx\"\n      }\n    }\n  }\n}\n```\n\n> **Mint & secure the PAT.** Create it in the web **Settings → Tokens** (it is shown only once) and\n> keep it out of version control — prefer the OS keychain. On macOS, for example:\n>\n> ```bash\n> security add-generic-password -a \"$USER\" -s md-log-pat -w \"mdlog_pat_xxxx\"\n> export MDLOG_PAT=\"$(security find-generic-password -a \"$USER\" -s md-log-pat -w)\"\n> ```\n>\n> If a PAT leaks, revoke it in the web app and mint a new one.\n\n## Remote (Streamable HTTP) mode\n\nThe second bin, **`md-log-mcp-http`**, serves the **same 15 tools** over MCP's\n[Streamable HTTP transport](https://modelcontextprotocol.io/docs/concepts/transports) — a single\n`POST /mcp` endpoint — so agents connect by **URL with no local install**. Use it when you want to\nhost the connector centrally (a container / small VM behind a TLS reverse proxy) instead of every\nuser running `npx`.\n\nHow it differs from stdio:\n\n- **PAT per request.** The token is **not** read from env; each request carries its own\n  `Authorization: Bearer <mdlog_pat_…>` header, so **one endpoint serves many users** — each with\n  their own md-log token. (`MDLOG_PAT` is ignored in this mode.)\n- **Stateless.** A fresh client + server per request; no session store (replica / autoscale friendly).\n- **No local files.** The `file_path` image source is **refused** (it would read the *server's* disk);\n  send images inline as `data_base64`. Everything else is identical.\n\n### Run it\n\n```bash\nnpm run build\nMDLOG_API_BASE_URL=\"https://app.md-log.com/api/v1\" \\\nnode dist/http.js            # or: npm run start:http\n# → md-log-mcp-http ready — POST http://127.0.0.1:8787/mcp\n```\n\n### Configuration (env)\n\n| Variable | Required | Default | Notes |\n| -------- | -------- | ------- | ----- |\n| `MDLOG_API_BASE_URL` | yes | — | Hosted md-log base, **including** `/api/v1`. |\n| `MDLOG_HTTP_HOST` | no | `127.0.0.1` | Bind interface. Localhost-only by default; set `0.0.0.0` **only** behind a TLS reverse proxy. |\n| `MDLOG_HTTP_PORT` | no | `8787` | TCP port. |\n| `MDLOG_HTTP_PATH` | no | `/mcp` | The MCP endpoint path. |\n| `MDLOG_HTTP_ALLOWED_ORIGINS` | no | *(none)* | Comma-separated browser `Origin` allowlist (DNS-rebinding defense). A request that **carries** an `Origin` not on the list is `403`d; non-browser clients (no `Origin`) are always allowed. |\n| `MDLOG_HTTP_ALLOWED_HOSTS` | no | *(none)* | Optional comma-separated `Host` allowlist (extra DNS-rebinding defense). |\n| `MDLOG_HTTP_MAX_BODY_BYTES` | no | `33554432` (32 MiB) | Max request body (base64 images inflate ~33%). |\n\n**Security posture** (per the MCP spec): binds to `127.0.0.1` by default, requires a Bearer token on\nevery MCP request, validates `Origin` against the allowlist to defeat DNS-rebinding, and caps the body\nsize. A `GET /health` liveness probe (no auth) returns `{\"status\":\"ok\"}`. `GET`/`DELETE` on the MCP\nendpoint return `405` (stateless: no standalone SSE stream, no session to terminate).\n\n### Connect an agent by URL\n\n```jsonc\n{\n  \"mcpServers\": {\n    \"md-log\": {\n      \"type\": \"http\",\n      \"url\": \"https://your-host.example/mcp\",\n      \"headers\": { \"Authorization\": \"Bearer mdlog_pat_xxxxxxxxxxxxxxxxxxxxxxxx\" }\n    }\n  }\n}\n```\n\n> Client config shape varies (Claude Code / Cursor / etc.) — the essentials are the endpoint **URL**\n> and an `Authorization: Bearer <PAT>` header. Always terminate TLS in front of a public deployment;\n> the PAT rides on every request.\n\n## Scripts\n\n- `npm run build` — bundle to `dist/server.js` (stdio) + `dist/http.js` (Streamable HTTP) (tsup, ESM, Node 22).\n- `npm run dev` — rebuild on change (`tsup --watch`).\n- `npm run typecheck` — `tsc --noEmit`.\n- `npm run smoke` — stdio smoke test against a live backend (needs env + a build).\n- `npm run smoke:http` — **backend-free** HTTP-transport smoke test (handshake + auth/origin/file_path guards).\n- `npm start` — run the built stdio server (`node dist/server.js`).\n- `npm run start:http` — run the built HTTP server (`node dist/http.js`).\n",
  "bytes": 14377,
  "sha": "958c7252cc67d15d9d515b9501fd7e0386b5f3c794051c9fa9233f289df82222",
  "repo_slug": "md-log/md-log-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_com_md_log_md_log_mcp_c5623d8e/readme"
}