MCP Servers

A collection of Model Context Protocol servers, templates, tools and more.

O
Obsidian Spec MCP

MCP server by start-trek

Created 4/1/2026
Updated about 4 hours ago
Repository documentation and setup instructions

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-obsidian for 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 (doxcerdocxer, metabindmeta_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) or pip

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:

  1. Generate a snippet via obsidian-spec-mcp
  2. Validate it against the relevant pack(s)
  3. Write it to your vault via the Obsidian Local REST API
  4. Read it back
  5. Verify the content survived the round-trip

Prerequisites:

  • Obsidian running with the Local REST API community plugin enabled
  • OBSIDIAN_API_KEY environment 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

  1. Register — Add a PackInfo entry in registry.py with metadata, syntax kinds, docs, and examples.
  2. Document — Create docs/your_pack.md with rules, examples, and edge cases.
  3. Validate — Add a _validate_your_pack() function in validators.py.
  4. Render — Add a _your_pack() function in renderers.py.
  5. Wire up — Add the pack to the dispatcher dicts in validators.py and renderers.py.
  6. Test — Add test cases in tests/test_gauntlet.py and seed notes in tests/fixtures/vault/your_pack/.
  7. Alias — Add common aliases in registry.py's _ALIASES dict.

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.json files 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-obsidian in one flow.
  • New packs — Add support for Dataview, Canvas, Database Folder, or any other Obsidian plugin.
  • Integration tests — Expand tests/test_integration.py with more round-trip scenarios.

License

See pyproject.toml for project metadata.

Quick Setup
Installation guide for this server

Install Package (if required)

uvx obsidian-spec-mcp

Cursor configuration (mcp.json)

{ "mcpServers": { "start-trek-obsidian-spec-mcp": { "command": "uvx", "args": [ "obsidian-spec-mcp" ] } } }