A Claude Code plugin providing Markdown language server support via Marksman, plus validation hooks for code blocks, links, frontmatter, and Claude Code syntax.
| Operation | Description |
|---|---|
documentSymbol |
Navigate all headings with proper nesting |
goToDefinition |
Jump from [link](#heading) to the heading |
findReferences |
Find all links pointing to a heading |
hover |
Preview section content |
workspaceSymbol |
Search headings across all markdown files |
| Hook | Trigger | Validates |
|---|---|---|
validate-code-blocks |
onSave | Code blocks have language identifiers |
validate-links |
onSave | Internal anchors and file links exist |
validate-frontmatter |
onSave | YAML frontmatter structure |
validate-claude-code |
onSave | LSP operations, slash commands |
- Marksman - Markdown language server
- Node.js 18+ - For validation hooks
- Claude Code - With
ENABLE_LSP_TOOL=1
# macOS
brew install marksman
# Linux (x86_64)
curl -L https://github.com/artempyanykh/marksman/releases/latest/download/marksman-linux-x64 -o marksman
chmod +x marksman && sudo mv marksman /usr/local/bin/
# Windows
scoop install marksman
# or
winget install artempyanykh.marksmanVerify installation:
marksman --versionOption A: From release tarball
tar -xzf marksman-lsp.tar.gz
/plugin install ./marksman-lsp --scope userOption B: From repository
git clone https://github.com/zircote/markdown-lsp.git
/plugin install ./markdown-lsp --scope userOption C: Local development (plugin-dir mode)
cd /path/to/markdown-lsp
# Claude Code auto-detects .claude-plugin/plugin.jsonexport ENABLE_LSP_TOOL=1Or add to your shell profile (~/.zshrc, ~/.bashrc):
echo 'export ENABLE_LSP_TOOL=1' >> ~/.zshrcUse the LSP tool in Claude Code on any .md file:
# Get document outline
LSP documentSymbol README.md line=1 char=1
# Jump to heading from link
LSP goToDefinition README.md line=15 char=10
# Find all references to a heading
LSP findReferences README.md line=20 char=4
# Preview section content
LSP hover README.md line=15 char=10
# Search across workspace
LSP workspaceSymbol README.md line=1 char=1Hooks run automatically on save. Diagnostics appear in Claude Code's system reminders.
Example output:
lsp-test.md:
[Line 186:1] Code block missing language identifier (warning)
[Line 198:26] Broken anchor link: #does-not-exist (warning)
marksman-lsp/
├── .claude-plugin/
│ └── plugin.json # Plugin metadata
├── .lsp.json # LSP server configuration
├── hooks/
│ ├── hooks.json # Hook definitions
│ ├── validate-code-blocks.js
│ ├── validate-links.js
│ ├── validate-frontmatter.js
│ └── validate-claude-code.js
├── tests/
│ └── lsp-test.md # Test file for all features
├── src/
│ └── index.ts # MCP server (optional)
├── CLAUDE.md # Project context for Claude
└── README.md
{
"markdown": {
"command": "marksman",
"args": ["server"],
"extensionToLanguage": {
".md": "markdown"
},
"transport": "stdio"
}
}{
"markdownDiagnostics": [
{
"name": "validateCodeBlocks",
"trigger": "onSave",
"filePattern": "**/*.md",
"command": "$CLAUDE_PROJECT_DIR/hooks/validate-code-blocks.js"
}
]
}# Test individual hooks
node hooks/validate-code-blocks.js tests/lsp-test.md
node hooks/validate-links.js tests/lsp-test.md
node hooks/validate-frontmatter.js tests/lsp-test.md
node hooks/validate-claude-code.js tests/lsp-test.md- Create a new
.jsfile inhooks/ - Output JSON:
{ "diagnostics": [...] } - Register in
hooks/hooks.json
Diagnostic format:
{
"line": 10,
"column": 1,
"severity": "warning",
"message": "Description of issue",
"source": "hook-name"
}- Check marksman is installed:
which marksman - Verify LSP enabled:
echo $ENABLE_LSP_TOOL(should be1) - Restart Claude Code after installing plugin
- Check
.lsp.jsonlocation: Must be in project root for plugin-dir mode
- Check hooks are executable:
ls -la hooks/*.js - Verify Node.js available:
which node - Test hook manually:
node hooks/validate-links.js README.md
- Never commit tokens or API keys
- Hooks run with user permissions
- All hook output is JSON (no shell injection)
MIT