Skip to main content
Glama
marwansaab

obsidian-modified-mcp-server

by marwansaab

Obsidian Modified MCP Server

npm version License: MIT

This is a personal fork of @connorbritain/obsidian-mcp-server by Connor Britain. Its purpose is to mitigate wrapper-side limitations of the Local-REST-API-based MCP server. Concrete changes so far: re-enabled the patch_content tool under a structural-only path validator; added two surgical-read tools (get_heading_contents, get_frontmatter_field); wired the seven graph tools through the dispatcher (they previously advertised schemas but returned Unknown tool at runtime); made delete_file recursive on directory paths with timeout-coherent responses; switched the post-timeout verification query to a direct-path probe (so an upstream auto-prune of the parent directory no longer surfaces a successful delete as outcome undetermined); and exposed the upstream's authoritative tag index via a new list_tags tool that includes both inline and frontmatter tags and excludes tag-shaped strings inside fenced code blocks — more accurate than text or frontmatter search for tag enumeration. Subsequent specs will add similar wrapper-level mitigations as the fork evolves.

Status: Personal fork. External support not guaranteed; use at your own discretion.

TypeScript MCP server for Obsidian with core vault operations, graph analytics, and semantic search.

Features

  • Core Tools: Read, write, search, append, delete files in your Obsidian vault

  • Periodic Notes: Access daily, weekly, monthly notes and recent changes

  • Advanced Search: JsonLogic queries for complex filtering

  • Graph Tools: Orphan detection, centrality analysis, cluster detection, path finding

  • Semantic Search: Smart Connections integration for concept-based search

Differences from upstream

Change

Description

Rationale

patch_content re-enabled

Heading/block/frontmatter PATCH tool is enabled in this fork under a structural-only path validator.

Wraps the same upstream endpoint Connor's fork disabled. The empirically-observed 40080 invalid-target is a client-side path-mismatch (per coddingtonbear/obsidian-local-rest-api#146), addressable by enforcing fully-qualified heading paths at the wrapper boundary.

get_heading_contents + get_frontmatter_field added

Two new MCP read tools that fetch part of a vault note instead of the whole file. get_heading_contents returns the raw markdown body under a fully-pathed heading (reusing patch_content's structural path validator). get_frontmatter_field returns one frontmatter field's value with its original type preserved (string, number, boolean, array, object, or null).

Avoids round-tripping the entire file through the MCP transport just to read one section or one field; surfaces the upstream Local REST API's surgical-read endpoints (GET /vault/{path}/heading/..., GET /vault/{path}/frontmatter/{field}) directly.

Graph tools wired through dispatcher

The seven graph tools (get_vault_stats, get_vault_structure, find_orphan_notes, get_note_connections, find_path_between_notes, get_most_connected_notes, detect_note_clusters) are now actually dispatched at runtime. Aggregation tools tolerate malformed notes via skipped + skippedPaths; per-note tools return note not found: <path> for missing endpoints (distinct from "found but no connections" and "no path between endpoints").

Previously the seven tools advertised JSON schemas at the catalog layer but returned Error: Unknown tool: <name> at runtime — the catalog was a superset of what the runtime served. Honouring the contract eliminates the false-advertisement state. Full I/O contracts in specs/004-fix-graph-tools/contracts/.

delete_file recursive + timeout-coherent + direct-path verify

Directory paths are deleted recursively in a single tool call — the wrapper walks contents in upstream listing order, deletes each file and subdirectory, then deletes the outer directory and returns {ok, deletedPath, filesRemoved, subdirectoriesRemoved}. On a transport timeout the wrapper performs a single direct-path verification query against the deleted target itself (404 = success, 200 = delete did not take effect: <path> (filesRemoved=N, subdirectoriesRemoved=M), anything else = outcome undetermined) — so callers see definite success or definite failure regardless of whether the upstream auto-pruned the parent.

Upstream delete_file is non-recursive on directories, and even an empty-directory delete that succeeded on the vault was surfaced as a 10-second transport-timeout error. Spec 005 added recursive walking + a parent-listing verification on timeout, but parent-listing fails when the upstream auto-prunes the now-empty parent (404 on the parent listing was indistinguishable from "verification call broken"). Spec 007 switches the verification probe to the deleted target's own path, eliminating the false-undetermined failure mode. Live contract in specs/007-fix-delete-verify-direct/contracts/delete_file.md (supersedes spec 005's).

list_tags added

New MCP tool that exposes the upstream Local REST API plugin's GET /tags/ index — every tag in the vault paired with its usage count. The result includes both inline (#tag) and YAML frontmatter tags and excludes tag-shaped strings inside fenced code blocks; hierarchical tags (e.g., work/tasks) contribute counts to every parent prefix, mirroring Obsidian's own tag sidebar. The upstream success body is forwarded verbatim — no wrapper-side reshaping. Phase 0 verification confirmed the GET /tags/{tagname}/ and PATCH /tags/{tagname}/ endpoints originally in scope are not implemented in upstream v3.5.0, so list-by-tag and tag-mutation tools are out of scope for this feature.

The existing text and frontmatter search tools systematically over-count (they hit code-block mentions) and under-count (they miss inline tags when only frontmatter is searched, or vice versa). Sourcing tag enumeration directly from Obsidian's own index is the only way to give an LLM caller a trustworthy starting point for tag-driven navigation, audit, or cleanup. Live contract in specs/008-tag-management/contracts/list_tags.md.

In flight (design landed, implementation pending)

The following specs have their full design + spike-blocked scaffold landed in this repo, but their tools are intentionally not yet exposed via tools/list — they're awaiting a build-time prerequisite. They appear here so readers can find the design docs without being misled into thinking the tools are usable now.

Spec

Status

Why pending

specs/012-safe-rename/rename_file, a wrapper-side composition for safe vault renames preserving wikilink integrity. Multi-step: getFileContents ×2 (pre-flight source + collision check) → listFilesInDir (pre-flight parent) → putContent (write destination) → findAndReplace ×3-or-4 (vault-wide wikilink rewrites via four regex passes covering bare/aliased/heading-targeted/embed/full-path shapes) → deleteFile (delete source). Atomicity holds for pre-flight rejections; mid-flight failures are explicitly best-effort with git restore . as the documented rollback. Tool description discloses non-atomicity, the git-clean precondition, the wikilink shape coverage, and the irrelevance of Obsidian's "Automatically update internal links" setting under this implementation.

Design + scaffold landed in v0.5.1. The 2026-05-02 T002 feasibility spike confirmed the original Option-A design (dispatching Obsidian's "Rename file" command via POST /commands/{commandId}/) is infeasible against stock Obsidian + the current Local REST API plugin — both workspace:edit-file-title and file-explorer:move-file open UI inputs and silently no-op when dispatched headlessly. Pivoted to Option B (filesystem composition above).

Build-time dependency on a future find_and_replace tool (the wrapper imports rest.findAndReplace as a static module dependency; the tool isn't wired into ALL_TOOLS until that ships).

Heading-path discipline (patch_content, get_heading_contents)

To avoid the disambiguation issue tracked in upstream issue coddingtonbear/obsidian-local-rest-api#146, this fork applies a structural validator at the MCP wrapper boundary before any HTTP call is made. The same rule applies to patch_content's heading targets and to get_heading_contents's heading argument — there is exactly one definition of the predicate across the codebase.

  • Heading targets MUST be path-shaped. At least two non-empty ::-separated segments, full path from the document's H1 downward. Use "About This Vault::Frontmatter Conventions", not "Frontmatter Conventions". Bare names are rejected with an actionable error message that names the rule, quotes the offending value, and shows a corrected example.

  • Headings whose literal text contains :: are unreachable through these tools — the validator treats every :: as a path separator and there is no escape syntax. Fall back to get_file_contents + put_content (write side) or get_file_contents + client-side slicing (read side).

  • Top-level-only headings (i.e., files with no ::-separable nesting) are also unreachable through these tools. Same fallback.

  • patch_content's block and frontmatter target types pass through to the upstream unchanged.

  • get_heading_contents returns just the raw markdown body under the targeted heading — frontmatter, tags, and file metadata are not included. For frontmatter use get_frontmatter_field (single field) or get_file_contents (whole note).

  • Upstream errors propagate verbatim with status code and message preserved (no silent fallbacks). For get_frontmatter_field in particular, a present-but-null field value ({"value":null}) is distinct from a missing field (upstream 4xx surfaced as isError).

These limitations are also stated in each tool's MCP description field, so they are visible to any caller that lists the available tools.

Prerequisites

Installation

From npm

npm install -g @marwansaab/obsidian-modified-mcp-server

From source

git clone https://github.com/marwansaab/obsidian-modified-mcp-server.git
cd obsidian-modified-mcp-server
npm install
npm run build

Configuration

Set the following environment variables:

Variable

Required

Default

Description

OBSIDIAN_API_KEY

Yes*

-

API key from Local REST API plugin settings (used when multi-vault JSON is not supplied)

OBSIDIAN_HOST

No

127.0.0.1

Obsidian REST API host

OBSIDIAN_PORT

No

27124

Obsidian REST API port

OBSIDIAN_PROTOCOL

No

https

http or https

OBSIDIAN_VAULT_PATH

No

-

Path to vault (required for graph tools)

SMART_CONNECTIONS_PORT

No

-

Port for Smart Connections API

GRAPH_CACHE_TTL

No

300

Graph cache TTL in seconds

OBSIDIAN_VAULTS_JSON

No

-

JSON string describing one or more vaults. Overrides the single OBSIDIAN_API_KEY style config.

OBSIDIAN_VAULTS_FILE

No

-

Path to a JSON file describing one or more vaults (same shape as OBSIDIAN_VAULTS_JSON).

OBSIDIAN_DEFAULT_VAULT

No

first defined

Name/ID of the vault to use when a tool call omits vaultId.

Multi-vault note: If neither OBSIDIAN_VAULTS_JSON nor OBSIDIAN_VAULTS_FILE is provided, the legacy single-vault env vars (OBSIDIAN_API_KEY, OBSIDIAN_HOST, etc.) are used to create a default vault entry automatically.

Example OBSIDIAN_VAULTS_JSON

[
  {
    "id": "work",
    "apiKey": "work-api-key",
    "host": "127.0.0.1",
    "port": 27124,
    "protocol": "https",
    "vaultPath": "C:/Users/you/Obsidian/work",
    "smartConnectionsPort": 29327
  },
  {
    "id": "personal",
    "apiKey": "personal-api-key",
    "vaultPath": "C:/Users/you/Obsidian/personal"
  }
]

Each tool in the MCP server accepts an optional vaultId argument. When omitted, the server uses OBSIDIAN_DEFAULT_VAULT (or the first defined vault). This allows a single MCP session to read/write multiple vaults just by specifying which vault to target in the tool call.

Multi-Vault Port Configuration

Important: When running multiple Obsidian vaults simultaneously, each vault's Local REST API plugin must listen on a unique port. By default, all vaults use port 27124, which causes conflicts—only one vault can bind to a port at a time, and requests to other vaults will fail with authorization errors.

Step 1: Assign Unique Ports in Obsidian

For each vault, open Settings → Community Plugins → Local REST API and scroll to Advanced Settings:

  1. Set Encrypted (HTTPS) Server Port to a unique value (e.g., 27124, 27125, 27126, 27127)

  2. Toggle the plugin off and back on (or restart Obsidian) to apply the change

  3. Copy the API Key shown in the plugin settings

Step 2: Update Your Vaults JSON

In your obsidian-vaults.json file (or OBSIDIAN_VAULTS_JSON env var), specify the port for each vault to match what you configured in the plugin:

[
  {
    "id": "vault_one",
    "apiKey": "your-api-key-for-vault-one",
    "port": 27124,
    "vaultPath": "C:/Users/you/Obsidian/vault_one"
  },
  {
    "id": "vault_two",
    "apiKey": "your-api-key-for-vault-two",
    "port": 27125,
    "vaultPath": "C:/Users/you/Obsidian/vault_two"
  },
  {
    "id": "vault_three",
    "apiKey": "your-api-key-for-vault-three",
    "port": 27126,
    "vaultPath": "C:/Users/you/Obsidian/vault_three"
  }
]

Step 3: Restart Your MCP Client

After updating the JSON file, restart your MCP client (Windsurf, Claude Desktop, etc.) so it reloads the configuration with the new ports.

Verifying Connectivity

You can test each vault's API directly with curl:

# Replace PORT and API_KEY for each vault
curl -k -H "Authorization: Bearer YOUR_API_KEY" https://127.0.0.1:PORT/vault/

A successful response returns a JSON object with the vault's file listing. If you receive 40101 Authorization required, the API key doesn't match. If you receive 40400 Not Found, the plugin isn't fully initialized on that port—try toggling it off/on or restarting the vault.

MCP Client Configuration

Use npx for the simplest setup:

{
  "mcpServers": {
    "obsidian": {
      "command": "npx",
      "args": ["-y", "@marwansaab/obsidian-modified-mcp-server"],
      "env": {
        "OBSIDIAN_API_KEY": "your-api-key-here",
        "OBSIDIAN_VAULT_PATH": "/path/to/your/vault",
        "OBSIDIAN_VAULTS_FILE": "C:/path/to/vaults.json",
        "OBSIDIAN_DEFAULT_VAULT": "work"
      }
    }
  }
}

Using Local Build (Development)

If running from source:

{
  "mcpServers": {
    "obsidian": {
      "command": "node",
      "args": ["/absolute/path/to/obsidian-modified-mcp-server/dist/index.js"],
      "env": {
        "OBSIDIAN_API_KEY": "your-api-key-here",
        "OBSIDIAN_VAULT_PATH": "/path/to/your/vault",
        "OBSIDIAN_VAULTS_JSON": "[{\"id\":\"work\",\"apiKey\":\"...\",\"vaultPath\":\"/work\"}]"
      }
    }
  }
}

Config File Locations

Client

Config Path

Claude Desktop (Windows)

%APPDATA%\Claude\claude_desktop_config.json

Claude Desktop (Mac/Linux)

~/.config/claude/claude_desktop_config.json

Windsurf

~/.windsurf/mcp_config.json

Cursor

~/.cursor/mcp_config.json

Available Tools

All tools accept an optional vaultId argument. If omitted, the server uses the default vault from your configuration. This lets you read/write multiple Obsidian vaults within the same MCP session.

Path separators: every tool that takes a filepath (or source / target) argument accepts forward-slash, backslash, or mixed separators uniformly across platforms. Forward-slash is the canonical form, but Windows-style backslash paths work without modification. See specs/006-normalise-graph-paths/.

Vault Management

Tool

Description

list_vaults

List all configured vaults with their IDs, capabilities, and connection info

Core File Operations

Tool

Description

list_files_in_vault

List all files/directories in vault root

list_files_in_dir

List files in a specific directory

get_file_contents

Read a single file

batch_get_file_contents

Read multiple files concatenated with headers

delete_file

Delete a file or directory. Directory paths are deleted recursively — the wrapper removes every contained file and subdirectory before deleting the directory itself, in a single tool call. On a transport timeout the wrapper verifies post-condition via a single direct-path query against the deleted target (404 → success, 200 → delete did not take effect: <path> (filesRemoved=N, subdirectoriesRemoved=M), anything else → outcome undetermined).

Surgical Read Operations

Tool

Description

get_heading_contents

Read just the raw markdown body under a fully-pathed heading (H1::H2[::H3...]). Frontmatter, tags, and file metadata are not included — see Heading-path discipline above.

get_frontmatter_field

Read one frontmatter field's value with its original type preserved (string, number, boolean, array, object, or null). Missing fields surface as upstream 4xx errors, distinct from a present-but-null value.

Tag Operations

Tool

Description

list_tags

List every tag in the vault with its usage count, sourced from the upstream's authoritative GET /tags/ index. Includes inline (#tag) and YAML frontmatter tags; excludes tag-shaped strings inside fenced code blocks. Hierarchical tags (e.g., work/tasks) contribute counts to every parent prefix (e.g., work), matching Obsidian's own tag sidebar. The upstream success body is forwarded verbatim. Live contract in specs/008-tag-management/contracts/list_tags.md.

Write Operations

Tool

Description

append_content

Append to file (creates if missing)

put_content

Overwrite file content

patch_content

Insert content relative to a heading, block, or frontmatter target. — see Heading-path discipline above.

find_and_replace

Vault-wide string-replacement across every .md file. Literal or regex (with capture groups). Optional dryRun: true preview, skipCodeBlocks / skipHtmlComments to preserve audit-trail content, pathPrefix scoping, and per-vault routing. Per-file size cap 5 MB on input AND output.

Tool

Description

search

Keyword search across vault

complex_search

JsonLogic query search (glob, regexp support)

pattern_search

Regex pattern extraction with context (requires vault path)

Periodic Notes & Recent Changes

Tool

Description

get_periodic_note

Get current daily/weekly/monthly/quarterly/yearly note

get_recent_periodic_notes

Get recent periodic notes with optional content

get_recent_changes

Get recently modified files (requires Dataview)

Obsidian Integration

Tool

Description

get_active_file

Get the currently active file in Obsidian

open_file

Open a file in Obsidian

list_commands

List all available Obsidian commands

execute_command

Execute one or more Obsidian commands

Graph Tools (requires OBSIDIAN_VAULT_PATH)

Each graph tool requires OBSIDIAN_VAULT_PATH to be set for the targeted vault. The two per-note tools (get_note_connections, find_path_between_notes) return note not found: <path> when the target note is not present in the vault — distinct from "found but no connections" (success with empty arrays) and "no path between endpoints" (success with path: null). Aggregation tools wrap their primary result in an envelope with skipped and skippedPaths (up to 50 entries) describing files skipped during the build because of read or parse errors. Full I/O contracts live under specs/004-fix-graph-tools/contracts/.

Tool

Description

Contract

get_vault_stats

Overview stats (notes, links, orphans, clusters)

contract

get_vault_structure

Folder tree structure of vault

contract

find_orphan_notes

Notes with no incoming/outgoing links

contract

get_note_connections

Incoming/outgoing links + tags for a note. Returns note not found: <path> when missing.

contract

find_path_between_notes

Shortest link path between two notes. Returns note not found: <path> (or notes not found: <source>, <target>) when an endpoint is missing.

contract

get_most_connected_notes

Top notes by link count or PageRank

contract

detect_note_clusters

Community detection via graph analysis

contract

Semantic Tools (requires Smart Connections plugin)

Tool

Description

semantic_search

Conceptual search via Smart Connections

find_similar_notes

Find semantically similar notes

Development

# Watch mode
npm run dev

# Lint
npm run lint

# Type check
npm run typecheck

# Build
npm run build

# Run the test suite (vitest + nock-mocked HTTP, V8 coverage gate)
npm test

# Run tests in watch mode
npm run test:watch

See TESTING.md for the coverage gate's floor, the ratchet procedure, and the AS-IS-vs.-fork-authored test directory convention.

Project Constitution & Spec-Driven Workflow

This repo uses Spec Kit for non-trivial features. The project constitution (principles every contribution must honor — modular code, public-tool tests, zod boundary validation, explicit upstream error propagation) lives in .specify/memory/constitution.md. Per-feature specs, plans, contracts, and task lists live under specs/. Pull requests should confirm that constitution Principles I–IV were considered.

Attributions

find_and_replace (feature 013)

find_and_replace is composed of three layers, two of which carry attribution to upstream Obsidian-MCP projects:

The corresponding feature spec, plan, and contracts live in specs/013-find-and-replace/.

License

MIT — see LICENSE. Copyright is held by Connor England (upstream author); this fork's modifications are released under the same MIT terms.

A
license - permissive license
-
quality - not tested
C
maintenance

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/marwansaab/obsidian-modified-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server