Skip to main content
Glama
frstlvl

Obsidian MCP Server

by frstlvl
README.md
# Obsidian MCP Server

> Provides real-time Claude AI access to Obsidian vaults via Model Context Protocol (MCP)

## Overview

This MCP server enables Claude to query, search, and read notes from Obsidian vaults **without token limitations**. Unlike Claude Projects which load all documents into context, this server provides dynamic, on-demand access to your knowledge base.

## Features

- **Multi-Vault Support**: Serve multiple Obsidian vaults from a single MCP server instance (v2.0)
- **Real-time Vault Access**: Query and read notes dynamically without pre-uploading
- **Automatic Index Updates**: File system watcher automatically updates vector index when notes change (v1.3.0)
- **Vector Search**: Semantic search using local embeddings (Transformers.js) or Anthropic API
- **Hybrid Search**: Combines keyword and semantic search for optimal results
- **Write Operations**: Create, update, and delete notes programmatically
- **Cross-Vault Search**: Search across all configured vaults with `vault: "*"`
- **Search Tools**: Keyword, tag, and folder-based filtering
- **Multiple Formats**: JSON and Markdown response formats
- **Secure**: Path validation and security checks prevent unauthorized access
- **Token Efficient**: No vault size limitations or token constraints

## Quick Start

### Prerequisites

- **Node.js** 18+ (recommended: 20+)
- **TypeScript** 5.7+
- **Obsidian vault** with markdown notes
- **Claude Desktop** or MCP-compatible client

### Installation

**Windows (PowerShell):**

```powershell
# 1. Clone or navigate to repository
cd /path/to/obsidian-mcp-server

# 2. Install dependencies
npm install

# 3. Build TypeScript
npm run build

# 4. Verify build succeeded
Test-Path dist\index.js  # Should return True
```

**macOS/Linux (Bash):**

```bash
# 1. Clone or navigate to repository
cd ~/obsidian-mcp-server

# 2. Install dependencies
npm install

# 3. Build TypeScript
npm run build

# 4. Verify build succeeded
ls dist/index.js  # Should exist
```

### Configuration

Create `config.json` in the project root with your vault(s):

```json
{
  "vaults": [
    {
      "name": "work",
      "path": "C:\\Users\\YourName\\Documents\\WorkVault",
      "enableWrite": true
    },
    {
      "name": "personal",
      "path": "C:\\Users\\YourName\\Documents\\PersonalVault",
      "enableWrite": false
    }
  ],
  "vectorSearch": {
    "enabled": true,
    "provider": "transformers",
    "model": "Xenova/bge-small-en-v1.5",
    "indexOnStartup": "auto"
  },
  "searchOptions": {
    "maxResults": 20,
    "excerptLength": 200,
    "caseSensitive": false,
    "includeMetadata": true
  },
  "logging": {
    "level": "info",
    "file": "logs/mcp-server.log"
  }
}
```

**Choosing the Right Embedding Model:**

The default model (`Xenova/bge-small-en-v1.5`) offers excellent quality at 384 dimensions. Consider alternatives based on your needs:

- **Default**: `Xenova/bge-small-en-v1.5` — best quality-to-resource ratio, recommended for multi-vault setups
- **High-end CPU** (Ryzen 9+, i9+, M3 Max+): Use `Xenova/bge-base-en-v1.5` for best quality (768 dims, ~2x RAM)
- **Multilingual vault**: Use `Xenova/paraphrase-multilingual-MiniLM-L12-v2`

See [Semantic Search Guide](docs/semantic-search.md#hardware-specific-recommendations) for detailed model comparison.

### Initial Indexing (Required for Large Vaults)

**Important:** For vaults with 1,000+ notes or when switching embedding models, run initial indexing **standalone** before using Claude Desktop.

**Quick Start:**

```powershell
# Windows
$env:OBSIDIAN_VAULT_PATH = "X:\Path\To\Your\Vault"
$env:OBSIDIAN_CONFIG_PATH = "D:\repos\obsidian-mcp-server\config.json"
node --expose-gc --max-old-space-size=16384 dist\index.js
```

```bash
# macOS/Linux
export OBSIDIAN_VAULT_PATH="/path/to/vault"
export OBSIDIAN_CONFIG_PATH="$HOME/obsidian-mcp-server/config.json"
node --expose-gc --max-old-space-size=16384 dist/index.js
```

After indexing completes, configure Claude Desktop (see below) for daily usage.

> **📚 See [Indexing Workflow Guide](docs/indexing-workflow.md)** for detailed instructions, troubleshooting, and model switching procedures.

### Claude Desktop Integration

#### Windows

Edit Claude Desktop configuration:

```powershell
# Config location: %APPDATA%\Claude\claude_desktop_config.json
notepad "$env:APPDATA\Claude\claude_desktop_config.json"
```

Add this configuration:

```json
{
  "mcpServers": {
    "obsidian": {
      "command": "node",
      "args": [
        "--expose-gc",
        "--max-old-space-size=16384",
        "/path/to/obsidian-mcp-server/dist/index.js"
      ],
      "env": {
        "OBSIDIAN_CONFIG_PATH": "/path/to/obsidian-mcp-server/config.json"
      }
    }
  }
}
```

#### macOS

Edit Claude Desktop configuration:

```bash
# Config location: ~/Library/Application Support/Claude/claude_desktop_config.json
vi ~/Library/Application\ Support/Claude/claude_desktop_config.json
```

Add this configuration:

```json
{
  "mcpServers": {
    "obsidian": {
      "command": "node",
      "args": [
        "--expose-gc",
        "--max-old-space-size=16384",
        "/path/to/obsidian-mcp/dist/index.js"
      ],
      "env": {
        "OBSIDIAN_CONFIG_PATH": "/path/to/obsidian-mcp-server/config.json"
      }
    }
  }
}
```

#### Multi-Vault Setup

The server natively supports multiple vaults via the `vaults` array in `config.json`. All vaults are served by a single MCP server process — no need for separate server entries per vault.

Each vault gets its own independent vector index (stored in each vault's `.mcp-vector-store/` folder) and file watcher. Use the `vault` parameter on all tools to target a specific vault, or `"*"` on read tools to search across all vaults.

```json
{
  "vaults": [
    {
      "name": "work",
      "path": "C:\\Users\\YourName\\Documents\\WorkVault",
      "enableWrite": true
    },
    {
      "name": "personal",
      "path": "C:\\Users\\YourName\\Documents\\PersonalVault",
      "enableWrite": false
    }
  ],
  "vectorSearch": {
    "enabled": true,
    "provider": "transformers",
    "model": "Xenova/bge-small-en-v1.5",
    "indexOnStartup": "auto"
  }
}
```

**Memory planning**: Each vault loads the embedding model independently (~150-300MB RAM per vault depending on model). Plan memory accordingly.

> **Legacy Note:** If you previously used `OBSIDIAN_VAULT_PATH` with one server process per vault, the server auto-migrates this to a single-vault `vaults` array at startup. The old multi-process approach still works but is no longer recommended.

#### Restart Claude Desktop

After configuration, **completely quit and restart Claude Desktop** for changes to take effect.

## Documentation

- **[Indexing Workflow Guide](docs/indexing-workflow.md)** - Initial setup, model switching, and troubleshooting
- **[Configuration Guide](docs/configuration.md)** - Complete configuration reference
- **[Semantic Search Guide](docs/semantic-search.md)** - Semantic search setup and usage

## Usage

### In Claude Conversations

Once configured, Claude can automatically access your vault:

```text
User: "Search my vault for notes about Python debugging"

Claude will use: obsidian_search_vault(query="Python debugging")
Returns: Matching notes with excerpts and URIs

User: "Show me the full Getting-Started note"

Claude will read: obsidian://vault/Guides/Getting-Started.md
Returns: Complete note content
```

### Available Tools

All tools require a `vault` parameter specifying which vault to operate on. Use the vault name from your `config.json`. Read tools also accept `"*"` for cross-vault search.

#### obsidian_list_vaults

List all configured vaults with their status and metadata.

**Parameters**: None required.

**Returns**: Vault names, paths, write status, and index health (note count, model, indexed date).

#### obsidian_search_vault

Search vault by keywords, tags, or folders.

**Parameters:**

- `vault` (required): Vault name or `"*"` for all vaults
- `query` (required): Search keywords (space-separated)
- `tags` (optional): Filter by tags (must have ALL)
- `folders` (optional): Limit to specific folders
- `limit` (optional): Max results (1-100, default: 20)
- `offset` (optional): Pagination offset (default: 0)
- `response_format` (optional): "markdown" or "json" (default: "markdown")

**Examples:**

```typescript
// Search a specific vault
obsidian_search_vault((vault = "work"), (query = "JavaScript testing"));

// Search across all vaults
obsidian_search_vault((vault = "*"), (query = "project"), (tags = ["active"]));
```

#### obsidian_semantic_search

Search vault using semantic similarity (meaning-based) instead of keyword matching.

**Parameters:**

- `vault` (required): Vault name or `"*"` for all vaults
- `query` (required): Natural language query (1-500 chars)
- `limit` (optional): Max results (1-50, default: 10)
- `min_score` (optional): Similarity threshold (0-1, default: 0.5)
- `hybrid` (optional): Combine with keyword search (default: false)
- `response_format` (optional): "markdown" or "json" (default: "markdown")

**Examples:**

```typescript
// Semantic search in one vault
obsidian_semantic_search((vault = "work"), (query = "machine learning ethics"));

// Cross-vault hybrid search
obsidian_semantic_search(
  (vault = "*"),
  (query = "web development best practices"),
  (hybrid = true)
);
```

#### obsidian_read_note

Read the full content of a specific note with parsed frontmatter. Read-only operation.

**Parameters:**

- `vault` (required): Vault name (no `"*"`)
- `path` (required): Relative path to note (e.g., "Projects/MyNote.md")
- `response_format` (optional): "markdown" or "json" (default: "markdown")

#### obsidian_create_note

Create a new note in a specific vault. Write must be enabled for the target vault.

**Parameters:**

- `vault` (required): Vault name (no `"*"`)
- `path` (required): Relative path for new note (e.g., "Projects/NewNote.md")
- `content` (required): Note content (markdown)
- `frontmatter` (optional): YAML frontmatter object

#### obsidian_update_note

Update an existing note's content or frontmatter.

**Parameters:**

- `vault` (required): Vault name (no `"*"`)
- `path` (required): Relative path to note
- `content` (optional): New content (replaces existing)
- `frontmatter` (optional): New frontmatter (merges with existing)
- `append` (optional): Append content instead of replace (default: false)

#### obsidian_delete_note

Delete a note from a specific vault. Write must be enabled for the target vault.

**Parameters:**

- `vault` (required): Vault name (no `"*"`)
- `path` (required): Relative path to note
- `confirm` (required): Must be `true` to confirm deletion

### Available Resources

Every note in your vault is exposed as a resource with URI:

```text
obsidian://vault/{vaultName}/[relative-path]
```

Claude can list all available notes and read specific notes by URI.

## Architecture

### Design Philosophy

This server uses a **search-tool-only** approach rather than pre-registering thousands of individual resources:

- **Efficient**: Claude uses `obsidian_search_vault` to find notes dynamically
- **Scalable**: Works with vaults of any size (tested with 5,000+ notes)
- **Fast**: No startup delay from resource registration
- **MCP-compliant**: Follows best practices for large datasets

Claude discovers notes through search, receives `obsidian://vault/` URIs, and can then read specific notes on demand.

### Component Diagram

```mermaid
graph LR
    A[Claude AI] -->|MCP Protocol| B[MCP Server]
    B -->|stdio| A
    B --> C[Vault Registry]
    C --> D1[Vault: work]
    C --> D2[Vault: personal]
    D1 --> E1[Search Engine]
    D1 --> F1[Vector Store]
    D1 --> G1[File Watcher]
    D2 --> E2[Search Engine]
    D2 --> F2[Vector Store]
    D2 --> G2[File Watcher]
```

### Components

- **index.ts** - Server initialization, multi-vault setup, and transport
- **obsidian-server.ts** - MCP request handlers (resources, tools) with vault routing
- **vault-registry.ts** - Multi-vault context management and resolution
- **search.ts** - Search engine with scoring and filtering
- **embeddings.ts** - Vector store and embedding generation
- **utils.ts** - Configuration, file operations, security

## Security

### Path Validation

All file paths are validated to prevent directory traversal attacks:

```typescript
// Checks that requested path is within vault boundaries
if (!isPathSafe(notePath, vaultPath)) {
  throw new Error("Access denied: path outside vault");
}
```

### Read-Only Mode

By default, server is **read-only**. To enable write operations (future feature):

```json
{
  "enableWrite": true
}
```

### Input Sanitization

- **Zod schemas** validate all tool inputs
- **Path normalization** prevents Windows/Unix path issues
- **Query limits** prevent resource exhaustion (max 500 chars)

## Development

### Build

```bash
npm run build
```

### Development Mode (Hot Reload)

```bash
npm run dev
```

### Linting

```bash
npm run lint
npm run format
```

### Testing

```bash
npm test
```

## Troubleshooting

### Server Not Starting

**Check vault path:**

```powershell
# Windows
Test-Path "C:\Users\YourName\Documents\ObsidianVault"  # Should return True
```

```bash
# macOS/Linux
ls -la ~/Documents/ObsidianVault  # Should show folder contents
```

**Check build output:**

```powershell
# Windows
Test-Path .\dist\index.js  # Should return True
```

```bash
# macOS/Linux
ls dist/index.js  # Should exist
```

**View error logs:**

```bash
# All platforms
node dist/index.js
```

### Claude Not Finding Server

1. **Verify config file location:**
   - Windows: `%APPDATA%\Claude\claude_desktop_config.json`
   - macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`

2. **Check JSON syntax:** Use a JSON validator

3. **Restart Claude Desktop completely** (don't just close window)

4. **Check Claude logs:**
   - Windows: `%APPDATA%\Claude\logs\`
   - macOS: `~/Library/Logs/Claude/`

### Search Returns No Results

- **Check exclude patterns** - Your notes might be excluded
- **Verify file extension** - Only `.md` files are indexed
- **Check query terms** - Try broader terms

### Permission Errors

Ensure your user has read access to:

- Vault directory
- All subdirectories
- All `.md` files

## Configuration Options

Full configuration schema:

```typescript
{
  vaults: Array<{                   // Vault definitions (required)
    name: string;                   // Unique vault identifier
    path: string;                   // Absolute path to vault
    enableWrite?: boolean;          // Per-vault write override
    includePatterns?: string[];     // Per-vault include override
    excludePatterns?: string[];     // Per-vault exclude override
    vectorSearch?: { ... };         // Per-vault vector config override
  }>;
  includePatterns: string[];        // Default glob patterns to include
  excludePatterns: string[];        // Default glob patterns to exclude
  enableWrite: boolean;             // Default write permission (default: false)
  vectorSearch?: {                  // Default vector search config
    enabled: boolean;               // Enable semantic search
    provider: "transformers";       // Embedding provider
    model?: string;                 // Model name (default: Xenova/bge-small-en-v1.5)
    indexOnStartup: "auto" | "always" | "never" | boolean;
  };
  searchOptions: {
    maxResults: number;             // Max search results (default: 20)
    excerptLength: number;          // Excerpt length (default: 200)
    caseSensitive: boolean;         // Case-sensitive search (default: false)
    includeMetadata: boolean;       // Include frontmatter (default: true)
  };
  logging: {
    level: string;                  // Log level (default: "info")
    file: string;                   // Log file path
  };
}
```

## Performance

### Optimization Strategies

- **Lazy loading** - Only reads files when requested
- **Pagination** - Limits search results
- **Character limits** - Truncates large responses
- **Exclude patterns** - Skips unnecessary files

### Recommended Limits

- **Vault size**: < 10,000 notes
- **Search results**: < 100 per query
- **File size**: < 10MB per note

### Future Enhancements

- **Indexing** - Pre-build search index for faster queries
- **Caching** - Cache frontmatter and metadata
- **Vector search** - Semantic similarity search
- **Watch mode** - Real-time file system monitoring

## MCP Protocol Compliance

This server follows [MCP best practices](https://modelcontextprotocol.io/):

- ✅ Zod input validation
- ✅ Tool annotations (readOnlyHint, destructiveHint, etc.)
- ✅ Multiple response formats (JSON/Markdown)
- ✅ Character limits (25k) with truncation
- ✅ Proper error handling
- ✅ Pagination support
- ✅ Security validation
- ✅ Descriptive tool documentation
- ✅ Search-tool pattern for large datasets

## Changelog

### v2.0.0 (January 2025) - Multi-Vault Support

**New Features**:

- ✅ **Multi-vault support**: Serve multiple Obsidian vaults from a single MCP server instance
- ✅ **`obsidian_list_vaults` tool**: List all configured vaults with index health and metadata
- ✅ **`vault` parameter**: All tools require a `vault` parameter for explicit targeting
- ✅ **Cross-vault search**: Use `vault: "*"` on read tools to search across all vaults
- ✅ **Per-vault config**: Override write permissions, include/exclude patterns, and vector search settings per vault
- ✅ **Per-vault file watchers**: Independent file watching and auto-indexing per vault
- ✅ **Updated URI scheme**: `obsidian://vault/{vaultName}/{path}` includes vault context

**Configuration**:

- ✅ **`vaults` array**: Define multiple vaults in `config.json`
- ✅ **Legacy migration**: `OBSIDIAN_VAULT_PATH` env var auto-migrates to single-vault `vaults` array
- ✅ **Per-vault overrides**: enableWrite, includePatterns, excludePatterns, vectorSearch inherit from server defaults

**Breaking Changes**:

- ⚠️ **`vault` parameter required**: All tools now require a `vault` parameter
- ⚠️ **URI scheme changed**: URIs now include vault name: `obsidian://vault/{vaultName}/{path}`
- ⚠️ **Config format**: `vaults` array is the recommended config format (legacy auto-migration preserves backward compatibility)

**Migration**:

- Existing configs with `OBSIDIAN_VAULT_PATH` continue to work via auto-migration
- Update tool calls to include `vault` parameter
- Update any URI parsing to handle the new `{vaultName}` segment

### v1.4.0 (December 2025) - Parallel Batch Processing & Smart Auto-Indexing

**Performance Improvements**:

- 🚀 **10x faster indexing**: Parallel batch processing with Promise.all (10 concurrent embeddings)
- ⚡ **65x speedup**: 5,681 notes indexed in 5.5 minutes (down from 6+ hours)
- 💾 **Robust checkpoints**: Proper Vectra transaction management (beginUpdate/endUpdate)
- 🔧 **Memory optimized**: Explicit GC at checkpoints, pipeline refresh every 500 notes
- 📊 **Production tested**: Successfully indexed 5,681/6,056 notes (93.8% coverage)

**New Features**:

- ✅ **Smart `indexOnStartup` modes**: `"auto"` (smart detection), `"always"`, `"never"`
- ✅ **Automatic model change detection**: No manual config toggling when switching models
- ✅ **Index validation**: Detects missing, corrupted, or incompatible indexes
- ✅ **Model metadata storage**: Stores model info in index for validation
- ✅ **Seamless model switching**: Just change model in config and restart - auto re-indexes

**Bug Fixes**:

- ✅ **Fixed checkpoint persistence**: Vectra index now properly flushed to disk at checkpoints
- ✅ **Eliminated index loss**: Transaction management prevents progress loss on crashes
- ✅ **Type safety**: Fixed batch processing parameter types for NoteMetadata

**Breaking Changes**:

- ⚠️ **`indexOnStartup` enhanced**: Now accepts string values (`"auto"`, `"always"`, `"never"`) in addition to boolean (backwards compatible)
- ⚠️ **Default changed**: `indexOnStartup` now defaults to `"auto"` instead of `false`

**Migration**:

- Old config with `true`/`false` still works (mapped to `"always"`/`"never"`)
- Recommended: Update to `"auto"` for best experience
- **Delete old indexes**: If you have incomplete indexes from v1.3, delete `.mcp-vector-store/` and let v1.4 rebuild with parallel processing

### v1.3.0 (October 2025) - Automatic Index Updates

**New Features**:

- ✅ **Automatic file watching**: Real-time vector index updates when notes change (chokidar)
- ✅ **Debounced re-indexing**: Smart 2-second delay prevents excessive rebuilds
- ✅ **Seamless integration**: No manual re-indexing required

**Breaking Changes**:

- ⚠️ **Config property renamed**: `autoIndex` → `indexOnStartup` (better reflects that it only controls initial indexing on server startup, not the automatic file watching)

### v1.2.0 (October 2025) - Vector Search & Write Operations

**New Features**:

- ✅ **Semantic search** with vector embeddings (Transformers.js)
- ✅ **Write operations**: Create, update, and delete notes
- ✅ **Hybrid search**: Combine semantic and keyword search (60/40 weighting)
- ✅ **Incremental indexing**: Track file modifications for efficient updates
- ✅ **Local embeddings**: Privacy-first with local Transformers.js models

**Bug Fixes**:

- ✅ Fixed config.json loading to use script directory instead of CWD
- ✅ Fixed tags handling for non-array frontmatter tags
- ✅ Improved error handling for malformed YAML frontmatter

**Performance**:

- ✅ Tested with 5,457 note vault (5,456 indexed successfully)
- ✅ Fast startup with optional auto-indexing
- ✅ Vectra-based local vector store (no external server required)

### v1.0.0 (January 2025) - Production Release

**Architecture**:

- ✅ Search-tool-only design (no resource pre-registration)
- ✅ Fast startup (< 1 second)
- ✅ Scalable to vaults of any size

**Security**:

- ✅ Updated all dependencies (0 vulnerabilities)
- ✅ ESLint 9.17.0, Rimraf 6.0.1, TypeScript-ESLint 8.18.2
- ✅ Eliminated deprecated packages with memory leak risks

**MCP SDK**:

- ✅ Migrated to MCP SDK 1.20+ API
- ✅ Updated from old `registerResourceList`/`registerResource` methods
- ✅ Clean TypeScript compilation (0 errors)

**Testing**:

- ✅ Verified with 5,453 note vault
- ✅ Tested on Windows 11 with Node.js 25.0.0
- ✅ Confirmed Claude Desktop integration

## License

MIT License - See LICENSE file for details

## Contributing

Contributions welcome! Please:

1. Follow existing code style
2. Add tests for new features
3. Update documentation
4. Follow MCP best practices

## References

- [MCP Specification](https://modelcontextprotocol.io/)
- [MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk)
- [Claude MCP Documentation](https://docs.anthropic.com/en/docs/agents-and-tools/mcp)
- [Obsidian API](https://docs.obsidian.md/)

---

**Built with ❤️ for the Obsidian + Claude community**