obsidian-markdown-lint-mcp-server

obsidian-markdown-lint-mcp-server

An MCP server that lints, validates, and renders your Obsidian vault markdown — all inside Docker.

Category
Visit Server

README

<div align="center">

obsidian-markdown-lint-mcp-server

An MCP server that lints, validates, and renders your Obsidian vault markdown — all inside Docker.

Node.js MCP SDK License: MIT

ToolsQuick StartConfigurationSchemasDevelopment

</div>


Run markdown linting, front matter validation, and Mermaid-to-SVG rendering as MCP tools callable from Claude Code. Docker isolates Chromium and Node.js from your host machine. The server is stateless — it processes content you send and returns results; Claude Code handles all disk I/O.

This is a stdio MCP server: Claude Code launches it as a subprocess (docker run -i --rm) and talks to it over stdin/stdout. There is no port and no long-running container — the container starts when a session opens and is removed when it ends.

Why Docker

Mermaid rendering requires a Chromium browser (via Puppeteer). Running that, plus dozens of Node.js dependencies, directly on your laptop is a valid security concern. This server packages everything inside a container. Your vault never mounts into it; content travels as strings.

Tools

Tool What it does
lint_markdown Runs markdownlint on content, returns errors and a corrected version
validate_front_matter Validates YAML front matter against a JSON Schema you pass in
render_mermaid_diagrams Renders all ```mermaid ``` blocks to SVG, replaces them with GitHub image links, embeds the original Mermaid source as base64 in each SVG's <metadata>
extract_mermaid_from_svg Reads a rendered SVG and returns the original Mermaid source as a code block, ready to edit

The render/extract pair supports a full edit cycle: render → view SVG in Obsidian → extract → edit source → re-render.

Quick start

Prerequisites: Docker Desktop, Node.js ≥ 20, Claude Code.

git clone <repo>
cd obsidian-markdown-lint-mcp-server
npm install
npm run build              # compile TypeScript → dist/
docker build -t obsidian-markdown-lint-mcp .   # or: docker compose build

Register the server with Claude Code. Claude Code runs docker run -i --rm itself, per session — you do not start a container yourself.

Per-project (recommended for a vault) — run this from your vault root; it writes a committable .mcp.json:

claude mcp add obsidian-markdown-lint -s project -- docker run -i --rm obsidian-markdown-lint-mcp

The resulting .mcp.json:

{
  "mcpServers": {
    "obsidian-markdown-lint": {
      "command": "docker",
      "args": ["run", "-i", "--rm", "obsidian-markdown-lint-mcp"]
    }
  }
}

Global — make it available in every project (-s user writes to ~/.claude.json):

claude mcp add obsidian-markdown-lint -s user -- docker run -i --rm obsidian-markdown-lint-mcp

Verify with claude mcp list (should show obsidian-markdown-lintconnected), then start a new Claude Code session — tools are discovered at session start. The four tools are now available.

The -i flag is required: it keeps the container's stdin open for the JSON-RPC stream. Without it the container gets EOF and exits immediately. Do not use docker compose up for this server — it is a stdio subprocess, not a long-running HTTP service.

Hardening (optional)

The server needs no network, no writable root filesystem, and little memory, so you can lock the container down. --network none and --security-opt no-new-privileges are always safe (the image skips the Puppeteer download and renders offline):

claude mcp add obsidian-markdown-lint -s project -- docker run -i --rm --network none --security-opt no-new-privileges --memory 2g obsidian-markdown-lint-mcp

For stricter isolation, add a read-only root with a writable temp dir and drop all Linux capabilities. Chromium renders into /tmp because the server launches it with --disable-dev-shm-usage, so the --tmpfs /tmp is required. Verify a Mermaid render still succeeds under these flags before relying on them, since Chromium's writable paths vary by version:

docker run -i --rm --network none --security-opt no-new-privileges --read-only --tmpfs /tmp --cap-drop ALL --memory 2g obsidian-markdown-lint-mcp

Configuration

Vault layout

The server reads nothing from disk directly. Claude Code reads your vault files and passes content as strings. Place these config files at your vault root:

vault/
  .markdownlint.json     ← linting rules (optional; defaults to markdownlint defaults)
  .schemas/
    _shared.json         ← shared field definitions (reference; not loaded at runtime)
    article.json
    how-to.json
    technical.json
    deep-research.json
    strategy.json
    meeting.json
    brainstorming.json

Pre-built schemas for all seven note types are included in this repo's .schemas/ directory. Copy them to your vault root. The seven type schemas are self-contained, so validate_front_matter only needs the one matching the document's type; _shared.json is the source the type schemas are built from and is kept for maintenance.

CLAUDE.md for your vault

Add a CLAUDE.md to your vault project that tells Claude how to use the server. Minimum viable example:

## MCP: obsidian-markdown-lint-mcp-server

Vault attachments directory: attachments
Schemas directory: .schemas/
Linting config: .markdownlint.json

When asked to "process a vault file":
1. Read the file
2. Read .markdownlint.json and call lint_markdown
3. Read the type field from front matter, read .schemas/{type}.json, call validate_front_matter
4. Call render_mermaid_diagrams with attachments_dir="attachments" and the document title
5. Write the modified_content back to the file
6. Write each SVG from the svgs array to its path field (decoded from base64)

SVG files contain the original Mermaid source base64-encoded in <metadata>.
To edit a diagram: call extract_mermaid_from_svg with the SVG content,
edit the returned mermaid_block, then re-run render_mermaid_diagrams.

Mermaid front matter options

Control rendering per-document:

mermaid_theme: dark          # default | dark | neutral | forest  (default: default)
mermaid_background: white    # any CSS color or "transparent"     (default: white)

After rendering, the server adds mermaid_svg_source: base64-embedded to the front matter so Claude knows SVGs in this document contain extractable source.

Front matter schemas

Seven note types are supported. Each schema lives at .schemas/{type}.json.

Type Extra required fields
article core only
how-to core only
technical system, component
deep-research sources (array, min 1)
strategy related, prepared_for, quarter
meeting meeting_date, attendees (array, min 1)
brainstorming core only

Core required fields on every type: type, title, author, category, tags, description, summary, status, version, date_created, date_updated.

The type field is the discriminator. Claude reads it from the front matter to select the right schema file before calling validate_front_matter.

How SVG embedding works

Each rendered SVG contains the original Mermaid source, base64-encoded, inside the SVG's <metadata> element:

<metadata>
  <mermaid:source xmlns:mermaid="http://mermaid.js.org/" encoding="base64">
    Zmxvd2NoYXJ0IExSCiAgICBBIC0tPiBCCg==
  </mermaid:source>
</metadata>

Base64 avoids all whitespace normalization issues — Mermaid is whitespace-sensitive and attribute-value normalization in XML would corrupt indentation. The extract_mermaid_from_svg tool decodes this and returns a ready-to-use ```mermaid ``` block.

SVG output paths

Diagrams render to {attachments_dir}/{document-title-slug}/{diagram-type}-{n}.svg.

For a document titled "System Architecture" with attachments_dir = "attachments" and two flowchart blocks, you get:

attachments/system-architecture/flowchart-1.svg
attachments/system-architecture/flowchart-2.svg

The markdown is updated to:

![flowchart diagram 1](attachments/system-architecture/flowchart-1.svg)

Standard GitHub-flavored markdown — renders in Obsidian, GitHub, and any standard markdown viewer.

Testing

Three layers, all runnable from the repo without building the Docker image:

npm test          # 52 Jest unit tests (ESM), enforced coverage thresholds
npm run eval      # tool-correctness evals against the compiled functions
npm run snapshot  # end-to-end fixture snapshots (requires a real Chromium)

Unit tests (tests/unit/) call the tool functions directly. Coverage is gated (90% lines/functions/statements, 85% branches); src/server.ts and src/create-server.ts are excluded as pure wiring and are covered separately through an in-memory MCP transport. Mermaid rendering is tested with an injected browser/renderer, so no Chromium is needed here. Use npm run test:coverage for the lcov/HTML report.

Evals (tests/evals/) check tool correctness against the compiled functions (no Docker). Output is JSON with summary.passed, summary.failed, and a results array; exit code is 1 if any eval fails. The suite covers all four tools: lint detection, schema validation (pass and fail cases), Mermaid rendering with a mock browser, and SVG round-trip extraction. In Claude Code, say "run evals" or "evaluate the tools" and it runs npm run eval and reports the results.

Snapshots (tests/snapshot/) prove that each input under test-obsidian-vault/original/ reproduces its committed test-obsidian-vault/snap-shot/ output through the real tool pipeline. Unlike the evals, test-1 launches Puppeteer for an actual render, so this needs a real Chromium and is kept out of npm test. SVG geometry is not byte-compared (it varies by Mermaid, Chromium, and font version); the deterministic projection is, namely the modified markdown, the file layout, and the embedded Mermaid source. Output is JSON; exits non-zero on any failure.

JSON Schema format

Each file in .schemas/ is a self-contained JSON Schema draft-07 object. validate_front_matter parses the document's YAML front matter and validates the resulting object against the schema you pass in. A trimmed view of .schemas/article.json:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "Article",
  "type": "object",
  "required": ["type","title","author","category","tags","description","summary","status","version","date_created","date_updated"],
  "properties": {
    "type":         { "const": "article" },
    "title":        { "type": "string", "minLength": 1 },
    "category":     { "type": "string", "enum": ["Software Development", "Security", "DevOps", "..."] },
    "tags":         { "type": "array", "items": { "type": "string", "pattern": "^[a-z0-9][a-z0-9_\\-/]*$" }, "minItems": 4, "maxItems": 12 },
    "status":       { "type": "string", "enum": ["published", "draft", "in-progress"] },
    "version":      { "type": "string", "pattern": "^\\d+\\.\\d+\\.\\d+$" },
    "date_created": { "type": "string", "format": "date" }
  },
  "additionalProperties": false
}

See .schemas/article.json for the complete schema, including the optional fields it also permits (aliases, subtitle, source, sources, reviewers, signoff, mermaid_theme, mermaid_background, mermaid_svg_source). Every schema sets additionalProperties: false, so any front matter key not declared in the schema is a validation error; add a key to the schema before using it in notes.

Important: Date fields must be quoted strings in YAML (date_created: "2026-01-01") because YAML auto-converts unquoted YYYY-MM-DD values to JavaScript Date objects, which fail the type: string check.

The per-type required fields are listed in Front matter schemas above.

Development

npm install
npm run build       # compile TypeScript → dist/

TypeScript source is in src/. After changing code, rebuild the image so the next Claude Code session picks it up:

npm run build && docker build -t obsidian-markdown-lint-mcp .   # or: docker compose build

Then start a new Claude Code session (stdio tools are loaded at session start).

To smoke-test the container by hand without Claude Code, drive it over stdio:

docker compose run --rm obsidian-markdown-lint-mcp
# then paste a JSON-RPC line, e.g. an initialize request, and read the reply

Project structure

src/
  server.ts             stdio entry point (StdioServerTransport bootstrap)
  create-server.ts      builds the McpServer and registers the 4 tools
  tools/
    lint.ts             lint_markdown implementation
    validate.ts         validate_front_matter implementation
    mermaid.ts          render_mermaid_diagrams + extract_mermaid_from_svg
  lib/
    frontmatter.ts      gray-matter parse/update helpers
    svg-metadata.ts     base64 embed/extract helpers
.schemas/               JSON Schema files for all 7 note types
Dockerfile              stdio server image (Chromium for Mermaid)
docker-compose.yml      build/tag helper (not `up` — see comments)

License

MIT © 2026 Philip Senger


<div align="center">

Lint, validate, and render your Obsidian vault markdown without touching your host system.

Report a bugRequest a feature

</div>

Recommended Servers

playwright-mcp

playwright-mcp

A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.

Official
Featured
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.

Official
Featured
Local
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

graphlit-mcp-server

The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.

Official
Featured
TypeScript
Kagi MCP Server

Kagi MCP Server

An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.

Official
Featured
Python
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured
Exa Search

Exa Search

A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.

Official
Featured