md-log-mcp
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, 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 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 theAuthorizationheader. See 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-infetch/httpare 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).
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.
The server fails fast at startup with a clear message if either var is missing or the base URL is malformed.
Build
npm install
npm run build # tsup → dist/server.js (ESM, Node 22)
npm run typecheck # tsc --noEmit (optional)
Smoke test
scripts/smoke.mjs spawns the built server over stdio (MCP SDK Client +
StdioClientTransport), then runs initialize → tools/list → save_markdown (a small report
embedding a tiny data: PNG) → get_markdown (reads it back, checks the marker) →
search_markdown — printing PASS/FAIL per step and exiting non-zero on any failure. Run it against
a live backend with a real PAT:
npm run build
MDLOG_API_BASE_URL="http://localhost:8080/api/v1" \
MDLOG_PAT="mdlog_pat_xxxx" \
node scripts/smoke.mjs # or: npm run smoke
Connect a client
Mint the PAT in the web app's Settings → Tokens, store it securely, then add md-log to your MCP client. Never commit a PAT.
📄 연결 가이드 (HTML) — md-log.com/guides/customer-guide.html: 웹 앱에서 발급받은 MCP 키(PAT) 로 URL 연결(권장) 또는
npx로컬 연결 (Claude Code · Desktop · Codex · Cursor).
Recommended — remote (URL), no install
Point your client at the hosted endpoint and pass the PAT as a Bearer header. Nothing to
install — no Node.js, no npx. (You don't even need this package for the hosted connection.)
# Claude Code
claude mcp add --transport http md-log https://mcp.md-log.com/mcp \
--header "Authorization: Bearer mdlog_pat_xxxxxxxxxxxxxxxxxxxxxxxx"
// Cursor / Claude Desktop / any client that takes JSON — the `type` field MUST be "http"
{
"mcpServers": {
"md-log": {
"type": "http",
"url": "https://mcp.md-log.com/mcp",
"headers": { "Authorization": "Bearer mdlog_pat_xxxxxxxxxxxxxxxxxxxxxxxx" }
}
}
}
Over the remote endpoint, embed images inline (base64); uploading a local image by path
(file_path) works only with the local method below.
Alternative — local (stdio via npx)
Runs this connector as a local subprocess (needs Node 22+; npx fetches the published package, nothing
to build). Use it if you prefer a local process, need local-file (file_path) image uploads, or
self-host md-log without a hosted MCP endpoint.
{
"mcpServers": {
"md-log": {
"command": "npx",
"args": ["-y", "md-log-mcp"],
"env": {
"MDLOG_API_BASE_URL": "https://app.md-log.com/api/v1",
"MDLOG_PAT": "mdlog_pat_xxxxxxxxxxxxxxxxxxxxxxxx"
}
}
}
}
Mint & secure the PAT. Create it in the web Settings → Tokens (it is shown only once) and keep it out of version control — prefer the OS keychain. On macOS, for example:
security add-generic-password -a "$USER" -s md-log-pat -w "mdlog_pat_xxxx" export MDLOG_PAT="$(security find-generic-password -a "$USER" -s md-log-pat -w)"If a PAT leaks, revoke it in the web app and mint a new one.
Remote (Streamable HTTP) mode
The second bin, md-log-mcp-http, serves the same 17 tools over MCP's
Streamable HTTP transport — a single
POST /mcp endpoint — so agents connect by URL with no local install. Use it when you want to
host the connector centrally (a container / small VM behind a TLS reverse proxy) instead of every
user running npx.
How it differs from stdio:
- PAT per request. The token is not read from env; each request carries its own
Authorization: Bearer <mdlog_pat_…>header, so one endpoint serves many users — each with their own md-log token. (MDLOG_PATis ignored in this mode.) - Stateless. A fresh client + server per request; no session store (replica / autoscale friendly).
- No local files. The
file_pathimage source is refused (it would read the server's disk); send images inline asdata_base64. Everything else is identical.
Run it
npm run build
MDLOG_API_BASE_URL="https://app.md-log.com/api/v1" \
node dist/http.js # or: npm run start:http
# → md-log-mcp-http ready — POST http://127.0.0.1:8787/mcp
Configuration (env)
Security posture (per the MCP spec): binds to 127.0.0.1 by default, requires a Bearer token on
every MCP request, validates Origin against the allowlist to defeat DNS-rebinding, and caps the body
size. A GET /health liveness probe (no auth) returns {"status":"ok"}. GET/DELETE on the MCP
endpoint return 405 (stateless: no standalone SSE stream, no session to terminate).
Connect an agent by URL
{
"mcpServers": {
"md-log": {
"type": "http",
"url": "https://your-host.example/mcp",
"headers": { "Authorization": "Bearer mdlog_pat_xxxxxxxxxxxxxxxxxxxxxxxx" }
}
}
}
Client config shape varies (Claude Code / Cursor / etc.) — the essentials are the endpoint URL and an
Authorization: Bearer <PAT>header. Always terminate TLS in front of a public deployment; the PAT rides on every request.
Scripts
npm run build— bundle todist/server.js(stdio) +dist/http.js(Streamable HTTP) (tsup, ESM, Node 22).npm run dev— rebuild on change (tsup --watch).npm run typecheck—tsc --noEmit.npm run smoke— stdio smoke test against a live backend (needs env + a build).npm run smoke:http— backend-free HTTP-transport smoke test (handshake + auth/origin/file_path guards).npm start— run the built stdio server (node dist/server.js).npm run start:http— run the built HTTP server (node dist/http.js).