README.md•10.5 kB
# Custom Obsidian MCP Server
A comprehensive Model Context Protocol (MCP) server for Obsidian vault operations, optimized for **Zettelkasten note creation workflows**. Works with any LLM that supports MCP (Claude, Perplexity, etc.).
## Overview
This MCP server connects to the [Obsidian Local REST API plugin](https://github.com/coddingtonbear/obsidian-local-rest-api) and provides 12 powerful tools for:
- **File Operations**: List, read, and manage files in your vault
- **Content Operations**: Create, append, and precisely edit notes
- **Search**: Find related notes across your vault
- **Metadata Management**: Tags, frontmatter, and note properties
Designed specifically for **Zettelkasten practitioners** who want AI assistance in creating atomic notes, finding connections, and maintaining a knowledge graph.
## Features
✅ **Zettelkasten-Optimized**: Tools designed for atomic note creation and linking
✅ **LLM-Agnostic**: Works with any MCP-compatible LLM
✅ **Safe by Default**: CREATE mode won't overwrite existing notes
✅ **Powerful Search**: Find existing notes before creating duplicates
✅ **Precise Editing**: Insert content at specific headings or blocks
✅ **Metadata Rich**: Full frontmatter and tag management
## Prerequisites
1. **Obsidian** with [Local REST API plugin](https://github.com/coddingtonbear/obsidian-local-rest-api) installed and configured
2. **Python 3.10+**
3. **uv** (optional but recommended for managing Python environments)
## Installation
### 1. Install the MCP Server
Using `uv` (recommended):
```bash
cd custom-obsidian-mcp
uv sync
```
Or using pip:
```bash
cd custom-obsidian-mcp
pip install -e .
```
### 2. Set Up Obsidian Local REST API
1. Install the "Local REST API" plugin in Obsidian
2. Enable the plugin in Settings → Community plugins
3. Go to plugin settings and copy your API key
4. Note the port (default: 27124)
### 3. Configure Environment Variables
Create a `.env` file or set these variables:
```bash
export OBSIDIAN_API_KEY="your-api-key-here"
export OBSIDIAN_HOST="127.0.0.1" # Usually localhost
export OBSIDIAN_PORT="27124" # Default port
```
## Configuration for LLM Clients
### Claude Desktop (Desktop App)
Add to your `claude_desktop_config.json`:
```json
{
"mcpServers": {
"obsidian": {
"command": "/path/to/uvx",
"args": ["custom-obsidian-mcp"],
"env": {
"OBSIDIAN_API_KEY": "your-api-key-here",
"OBSIDIAN_HOST": "127.0.0.1",
"OBSIDIAN_PORT": "27124"
}
}
}
}
```
Or if using pip installation:
```json
{
"mcpServers": {
"obsidian": {
"command": "python",
"args": ["-m", "custom_obsidian_mcp.server"],
"env": {
"OBSIDIAN_API_KEY": "your-api-key-here",
"OBSIDIAN_HOST": "127.0.0.1",
"OBSIDIAN_PORT": "27124"
}
}
}
}
```
### Perplexity or Other MCP Clients
Use similar configuration structure adapted to your client's format.
## Available Tools
### File Operations
#### `obsidian_list_files_in_vault`
List all files and directories in vault root.
```python
# No parameters needed
```
#### `obsidian_list_files_in_dir`
List contents of a specific directory.
```python
{
"dirpath": "Zettelkasten" # Empty string for root
}
```
#### `obsidian_get_file_contents`
Read a single file's complete contents.
```python
{
"filepath": "Zettelkasten/202411061234.md"
}
```
#### `obsidian_batch_get_file_contents`
Read multiple files at once (max 20).
```python
{
"filepaths": [
"Zettelkasten/note1.md",
"Zettelkasten/note2.md"
]
}
```
### Search Operations
#### `obsidian_simple_search`
Text search across all vault files with context.
```python
{
"query": "systems thinking",
"context_length": 100 # Optional, default 100
}
```
#### `obsidian_complex_search`
Advanced JsonLogic-based search.
```python
{
"query": {"glob": ["*.md", {"var": "path"}]}
}
```
### Content Operations
#### `obsidian_write_note`
Create or modify notes with optional frontmatter.
```python
{
"filepath": "Zettelkasten/202411061234 Systems Thinking.md",
"content": "# Systems Thinking\n\nContent here...",
"mode": "create", # create|overwrite|append|prepend
"frontmatter": {
"tags": ["zettelkasten", "systems"],
"created": "2024-11-06"
}
}
```
**Modes:**
- `create`: Only create new files (won't overwrite)
- `overwrite`: Replace entire file
- `append`: Add to end
- `prepend`: Add to beginning
#### `obsidian_append_content`
Quick append to file (creates if doesn't exist).
```python
{
"filepath": "Zettelkasten/note.md",
"content": "\n## New Section\n\nNew content..."
}
```
#### `obsidian_patch_content`
Insert content at specific locations within notes.
```python
{
"filepath": "Zettelkasten/note.md",
"target_type": "heading", # heading|block|frontmatter
"target": "Related Concepts/Subsection",
"operation": "append", # append|prepend|replace
"content": "New related concept..."
}
```
**Target Types:**
- `heading`: Navigate to heading path (e.g., "Section/Subsection")
- `block`: Use block reference (e.g., "^block-id")
- `frontmatter`: Update specific frontmatter field
#### `obsidian_delete_file`
Delete file or directory (requires confirmation).
```python
{
"filepath": "Zettelkasten/old-note.md",
"confirm": true # Must be true to proceed
}
```
### Metadata Operations
#### `obsidian_get_frontmatter`
Extract YAML frontmatter from a note.
```python
{
"filepath": "Zettelkasten/note.md"
}
```
#### `obsidian_update_frontmatter`
Update frontmatter without modifying content.
```python
{
"filepath": "Zettelkasten/note.md",
"updates": {
"tags": ["zettelkasten", "new-tag"],
"status": "published"
}
}
```
#### `obsidian_manage_tags`
Add, remove, or list tags.
```python
{
"filepath": "Zettelkasten/note.md",
"action": "add", # add|remove|list
"tags": ["systems-thinking", "mental-models"]
}
```
#### `obsidian_get_notes_info`
Get metadata for multiple notes (max 50).
```python
{
"filepaths": [
"Zettelkasten/note1.md",
"Zettelkasten/note2.md"
]
}
```
Returns: tags, creation date, size, and other metadata.
## Zettelkasten Workflow Examples
### Creating a New Atomic Note
1. **Search for existing related notes:**
```
Use obsidian_simple_search with query "systems thinking"
```
2. **Read related notes:**
```
Use obsidian_batch_get_file_contents with discovered notes
```
3. **Create new atomic note:**
```
Use obsidian_write_note:
- filepath: "Zettelkasten/202411061234 Systems Thinking Core Concept.md"
- content: Your atomic note content
- mode: "create" (safe - won't overwrite)
- frontmatter: {tags: ["zettelkasten", "systems"], created: "2024-11-06"}
```
### Linking Related Notes
1. **Find notes in topic cluster:**
```
Use obsidian_simple_search or obsidian_get_notes_info
```
2. **Add connection to existing note:**
```
Use obsidian_patch_content:
- target_type: "heading"
- target: "Related Concepts"
- operation: "append"
- content: "- [[202411061234 Systems Thinking Core Concept]]"
```
### Organizing with Tags
1. **Check current tags:**
```
Use obsidian_manage_tags with action: "list"
```
2. **Add topic tags:**
```
Use obsidian_manage_tags with action: "add"
```
## Best Practices
### For Zettelkasten
- **Always search first**: Use `obsidian_simple_search` before creating new notes to avoid duplicates
- **Atomic notes**: One idea per note, clearly titled
- **Link liberally**: Use `obsidian_patch_content` to add connections
- **Tag consistently**: Use `obsidian_manage_tags` for topic organization
- **Use CREATE mode**: Default to `mode="create"` to prevent accidental overwrites
### For Safety
- **CREATE mode is default**: Won't overwrite existing notes
- **Confirmation required**: Destructive operations need explicit confirmation
- **Test in dev vault**: Try tools in a test vault before using on your main Zettelkasten
## Troubleshooting
### Connection Errors
**Error:** "Connection error for GET /vault/"
**Solutions:**
1. Verify Obsidian is running
2. Check Local REST API plugin is enabled
3. Verify API key is correct
4. Check port matches plugin settings (default: 27124)
### Authentication Errors
**Error:** "Authentication failed"
**Solutions:**
1. Check `OBSIDIAN_API_KEY` environment variable
2. Regenerate API key in plugin settings
3. Ensure no extra spaces in API key
### File Not Found
**Error:** "Resource not found"
**Solutions:**
1. Verify file path is relative to vault root
2. Check file exists: use `obsidian_list_files_in_dir`
3. Ensure proper file extension (e.g., `.md`)
## Architecture
```
custom-obsidian-mcp/
├── src/
│ └── custom_obsidian_mcp/
│ ├── __init__.py
│ ├── server.py # FastMCP server with all tools
│ └── obsidian_client.py # REST API client
├── pyproject.toml # Project configuration
└── README.md
```
### Key Components
- **FastMCP**: Modern Python MCP framework with Pydantic validation
- **ObsidianClient**: Async HTTP client for REST API communication
- **Pydantic Models**: Type-safe input validation for all tools
- **Error Handling**: Actionable error messages guide correct usage
## Development
### Running Tests
```bash
# Verify Python syntax
python -m py_compile src/custom_obsidian_mcp/server.py
# Test basic import
python -c "from custom_obsidian_mcp.server import mcp; print('OK')"
```
### Adding New Tools
1. Define Pydantic input model
2. Add tool function with `@mcp.tool` decorator
3. Include proper annotations (readOnlyHint, etc.)
4. Add comprehensive docstring
5. Implement error handling
## Contributing
Contributions welcome! Please:
1. Follow existing code style
2. Add Pydantic models for validation
3. Include docstrings with examples
4. Test with Obsidian Local REST API
## License
MIT License - See LICENSE file for details
## Acknowledgments
- Built on [MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk)
- Inspired by [MarkusPfundstein's mcp-obsidian](https://github.com/MarkusPfundstein/mcp-obsidian)
- Uses [Obsidian Local REST API](https://github.com/coddingtonbear/obsidian-local-rest-api)
## Support
For issues or questions:
1. Check Troubleshooting section
2. Verify Obsidian Local REST API is working
3. Test with simple tools first (list_files_in_vault)
4. Open an issue with error messages and configuration
---
**Happy note-taking! 📝✨**