MCP server by start-trek
Obsidian Spec MCP
A spec-centric Model Context Protocol (MCP) server that gives AI assistants authoritative knowledge of Obsidian markdown syntax, community plugin conventions, and vault-specific configuration — so they generate correct notes on the first try.
Read-only by design. This server validates and generates markdown but never writes to your vault. Pair it with a vault-write MCP like
mcp-obsidianfor the complete workflow.
Features
| Capability | What it does |
|---|---|
| Syntax lookup | Fetch authoritative rules and examples for any bundled pack |
| Validation | Check markdown against one or more packs before writing |
| Snippet generation | Produce starter snippets tailored to your vault's profile |
| Runtime config | Ingest your actual plugin config files so validation matches your vault |
| Alias resolution | Normalize common misspellings (doxcer → docxer, metabind → meta_bind) |
| Multi-pack | Validate against multiple packs at once (e.g. Tasks + Linter) |
Bundled Packs
| Pack | Syntax Domains | Description |
|---|---|---|
| core | wikilinks, embeds, callouts, properties | Core Obsidian markdown rules |
| tasks | task-line, tasks-query | Tasks plugin: checklist items, dates, priorities, query blocks |
| templater | templater-inline, templater-exec | Templater: <% %> commands, tp.* functions |
| quickadd | quickadd-format, quickadd-choice | QuickAdd: {{VALUE:x}} placeholders, capture templates |
| meta_bind | meta-bind-input, meta-bind-block, meta-bind-button | Meta Bind: interactive fields bound to frontmatter |
| js_engine | js-engine-block | JS Engine: executable JavaScript code blocks |
| docxer | docxer-workflow | Docxer: .docx to markdown conversion workflow |
| linter | linter-profile, lint-style | Linter: heading, spacing, and frontmatter hygiene rules |
| dataview | dataview-query, dataviewjs, inline-dataview | Dataview: dynamic queries and live data views |
| datacore | datacore-component, datacore-block | Datacore: reactive data components (experimental) |
Quick Start
Prerequisites
- Python ≥ 3.11
uv(recommended) orpip
Install
git clone https://github.com/YourOrg/Obsidian-Spec-MCP.git
cd Obsidian-Spec-MCP
uv sync
Run
stdio (for MCP clients like Claude Desktop, Windsurf, Cursor):
uv run obsidian-spec-mcp
Streamable HTTP (for web-based or multi-client setups):
uv run python -c "from obsidian_spec_mcp.server import mcp; mcp.run(transport='streamable-http')"
# Runs on http://127.0.0.1:8000
Verify
uv run pytest tests/ -v
Client Configuration
Windsurf / Cascade
Add to ~/.codeium/windsurf/mcp_config.json:
{
"mcpServers": {
"obsidian-spec": {
"command": "uv",
"args": ["run", "--directory", "/path/to/Obsidian-Spec-MCP", "obsidian-spec-mcp"]
}
}
}
Claude Desktop
Add to claude_desktop_config.json:
{
"mcpServers": {
"obsidian-spec": {
"command": "uv",
"args": ["run", "--directory", "/path/to/Obsidian-Spec-MCP", "obsidian-spec-mcp"]
}
}
}
With Runtime Config (environment variables)
{
"mcpServers": {
"obsidian-spec": {
"command": "uv",
"args": ["run", "--directory", "/path/to/Obsidian-Spec-MCP", "obsidian-spec-mcp"],
"env": {
"OBS_SPEC_TASKS_CONFIG_PATH": "/path/to/vault/.obsidian/plugins/obsidian-tasks-plugin/data.json",
"OBS_SPEC_LINTER_CONFIG_PATH": "/path/to/vault/.obsidian/plugins/obsidian-linter/data.json"
}
}
}
}
Tools
| Tool | Description |
|---|---|
| list_packs | List all registered spec packs with metadata |
| get_pack_info | Get detailed metadata and source links for a pack |
| search_spec | Search bundled docs by keyword across packs |
| get_doc | Get the full spec document for a pack |
| get_effective_profile | Load the merged profile with runtime config overlays |
| validate_obsidian_markdown | Validate markdown against one or more packs |
| generate_obsidian_snippet | Generate a starter snippet for a pack and intent |
| normalized_pack_name | Resolve aliases to canonical pack names |
Common Intents for generate_obsidian_snippet
| Pack | Intent | Output |
|---|---|---|
| core | note | Frontmatter + heading + callout + wikilinks |
| tasks | task-line | Single task with priority, date, status |
| tasks | query | Fenced tasks query block |
| templater | project-template | Full template with tp.* commands |
| quickadd | capture | Capture template with {{VALUE:x}} placeholders |
| meta_bind | form | Inline INPUT/VIEW fields |
| js_engine | script | Fenced js-engine block |
| docxer | convert | .docx conversion workflow note |
| linter | hygiene | Clean note skeleton |
| dataview | query | Fenced dataview query block |
| dataview | inline | Inline Dataview expression |
| dataview | dataviewjs | Fenced dataviewjs JavaScript block |
| datacore | view | Fenced datacore reactive component |
Resources
| URI | Returns |
|---|---|
| obsidian://packs/index | JSON array of all pack metadata |
| obsidian://packs/{name} | JSON object for a single pack |
| obsidian://docs/{name} | Full markdown spec document for a pack |
| obsidian://profiles/{name} | Profile configuration as JSON |
Prompts
| Prompt | Parameters | Use case |
|---|---|---|
| Create Obsidian Markdown | goal, packs_csv, strict | General Obsidian authoring |
| Create Tasks Snippet | goal, mode | Tasks-specific generation |
| Create Templater Template | goal | Templater template authoring |
Recommended Workflow
The ideal workflow pairs this server with a vault-write MCP like mcp-obsidian:
┌─────────────────────────────────────────────────────┐
│ AI Assistant │
│ │
│ 1. get_doc("tasks") ← Lookup syntax │
│ 2. generate_obsidian_snippet ← Generate draft │
│ 3. validate_obsidian_markdown ← Validate draft │
│ 4. mcp-obsidian: append_content ← Write to vault │
│ │
│ obsidian-spec-mcp ──────────── mcp-obsidian │
│ (read-only, spec) (read-write, vault) │
└─────────────────────────────────────────────────────┘
Why Two Servers?
| Concern | obsidian-spec-mcp | mcp-obsidian | |---|---|---| | Reads vault files | No | Yes | | Writes vault files | No | Yes | | Knows plugin syntax | Yes | No | | Validates markdown | Yes | No | | Generates snippets | Yes | No | | Requires Obsidian running | No | Yes |
This separation follows the MCP best practice of single-responsibility servers — each server does one thing well.
Runtime Configuration
The server can ingest your actual plugin config files to tailor validation and generation to your vault's real settings.
Supported Inputs
| Config | Tool Parameter | Environment Variable |
|---|---|---|
| Profile overlay | profile_path | OBS_SPEC_PROFILE_PATH |
| Tasks plugin | tasks_path | OBS_SPEC_TASKS_CONFIG_PATH |
| Linter plugin | linter_path | OBS_SPEC_LINTER_CONFIG_PATH |
| QuickAdd plugin | quickadd_path | OBS_SPEC_QUICKADD_CONFIG_PATH |
| Templater plugin | templater_path | OBS_SPEC_TEMPLATER_CONFIG_PATH |
| Meta Bind plugin | meta_bind_path | OBS_SPEC_META_BIND_CONFIG_PATH |
| JS Engine plugin | js_engine_path | OBS_SPEC_JS_ENGINE_CONFIG_PATH |
| Docxer plugin | docxer_path | OBS_SPEC_DOCXER_CONFIG_PATH |
You can pass config paths per-call (via tool parameters) or set them as environment variables in your MCP client config. Call get_effective_profile to verify what was loaded.
Example: Validate with Runtime Config
{
"markdown": "- [/] Draft proposal #task ⏫ 📅 2026-04-05",
"packs": ["tasks", "linter"],
"tasks_path": "/path/to/vault/.obsidian/plugins/obsidian-tasks-plugin/data.json",
"linter_path": "/path/to/vault/.obsidian/plugins/obsidian-linter/data.json"
}
Example: Generate with Runtime Config
{
"pack": "templater",
"intent": "project-template",
"title": "Project Alpha",
"templater_path": "/path/to/vault/.obsidian/plugins/templater-obsidian/data.json"
}
Starter config examples are included in examples/runtime_configs/.
Pack Details
Core
Validates wikilinks, embeds, callouts, and YAML frontmatter properties. Respects the profile's use_wikilinks and prefer_properties settings.
Tasks
Validates checklist task lines (status symbols, priority emojis, date markers, recurrence) and fenced tasks query blocks. Honors custom statuses, global filters, and date priority from runtime config.
Templater
Validates <% %> interpolation and <%* %> execution tags for balanced delimiters. Checks tp.user.* references against configured user scripts.
QuickAdd
Validates {{VALUE:x}} and {{DATE}} format placeholders for balanced braces. Checks variable names against configured known variables.
Meta Bind
Validates INPUT[type:property], VIEW[property], and BUTTON[id] syntax. Checks field types against configured meta_bind_field_types.
JS Engine
Validates fenced js-engine code blocks for proper opening/closing. Surfaces configured helper script names in generated snippets.
Docxer
Validates that notes referencing .docx files also mention a .md output. Uses configured default source/output paths in generated snippets.
Linter
Validates heading structure (single H1), blank line normalization, and frontmatter placement. Driven by linter_expectations from runtime config.
Development
Project Structure
obsidian_spec_mcp/
├── __init__.py
├── server.py # MCP server — tools, resources, prompts
├── registry.py # Pack registration, metadata, doc loading
├── validators.py # Per-pack markdown validation logic
├── renderers.py # Per-pack snippet generation
├── config.py # Runtime config loading and profile merging
├── models.py # Pydantic models (PackInfo, Profile, etc.)
├── docs/ # Bundled spec documents (one .md per pack)
│ ├── core.md
│ ├── tasks.md
│ ├── templater.md
│ ├── quickadd.md
│ ├── meta_bind.md
│ ├── js_engine.md
│ ├── docxer.md
│ └── linter.md
└── profiles/
└── default_profile.json
tests/
├── fixtures/
│ └── vault/ # Seed notes for gauntlet tests (git-tracked)
│ ├── core/
│ ├── tasks/
│ ├── templater/
│ ├── quickadd/
│ ├── meta_bind/
│ ├── js_engine/
│ ├── docxer/
│ ├── linter/
│ └── cross_pack/
├── test_gauntlet.py # Per-pack gauntlet tests using seed notes
├── test_integration.py # End-to-end tests via Obsidian REST API
├── test_validators.py
├── test_renderers.py
├── test_config.py
├── test_registry.py
└── test_server.py
Running Tests
# All tests (unit + gauntlet, excludes integration)
uv run pytest tests/ -v --ignore=tests/test_integration.py
# Just the gauntlet (comprehensive per-pack validation)
uv run pytest tests/test_gauntlet.py -v
# Integration tests (requires Obsidian + Local REST API plugin)
OBSIDIAN_API_KEY=your_key uv run pytest tests/test_integration.py -v -m integration
Integration Tests with mcp-obsidian
The integration tests in tests/test_integration.py exercise the full recommended workflow:
- Generate a snippet via
obsidian-spec-mcp - Validate it against the relevant pack(s)
- Write it to your vault via the Obsidian Local REST API
- Read it back
- Verify the content survived the round-trip
Prerequisites:
- Obsidian running with the Local REST API community plugin enabled
OBSIDIAN_API_KEYenvironment variable set (find the key in the plugin settings)
Tests write to a _spec_mcp_integration_test/ folder inside your vault and clean up after themselves.
Adding a New Pack
- Register — Add a
PackInfoentry inregistry.pywith metadata, syntax kinds, docs, and examples. - Document — Create
docs/your_pack.mdwith rules, examples, and edge cases. - Validate — Add a
_validate_your_pack()function invalidators.py. - Render — Add a
_your_pack()function inrenderers.py. - Wire up — Add the pack to the dispatcher dicts in
validators.pyandrenderers.py. - Test — Add test cases in
tests/test_gauntlet.pyand seed notes intests/fixtures/vault/your_pack/. - Alias — Add common aliases in
registry.py's_ALIASESdict.
Extending Validators
Each pack's validator receives the markdown string and the effective Profile. It returns a list of ValidationIssue objects with severity (error, warning, info), a message, and an optional suggestion. Keep validators fast and deterministic — they run on every call.
Extending Renderers
Each pack's renderer receives the intent, title, details dict, and Profile. It returns a GeneratedSnippet with the markdown string, notes, and profile-specific hints. Generated snippets should always pass their own pack's validator.
Extending It
Ideas for contributors and power users:
- Fuller docs — Replace bundled docs with comprehensive local mirrors of official plugin documentation.
- Direct vault config — Read plugin
data.jsonfiles directly from a vault path instead of requiring explicit paths. - Stricter validators — Add more granular validation rules for each pack.
- Custom prompts — Add prompt variants for your most common note patterns.
- Orchestration — Build a meta-tool that validates here and writes through
mcp-obsidianin one flow. - New packs — Add support for Dataview, Canvas, Database Folder, or any other Obsidian plugin.
- Integration tests — Expand
tests/test_integration.pywith more round-trip scenarios.
License
See pyproject.toml for project metadata.