Skip to main content
Glama
apaulineoliveira

obsidian-mcp

README.md
# Obsidian MCP Server 

A [Model Context Protocol (MCP)](https://modelcontextprotocol.io) server that connects your Obsidian vault directly to Claude and Cursor, allowing you to manage your to-dos without retyping tasks.

**Portuguese version:** [README.pt.md](README.pt.md)

## What is MCP?

**Model Context Protocol** is an open protocol that allows integrating external tools (like your Obsidian) with AIs (Claude, Cursor, etc). Think of it as a "universal adapter" that tells Claude: "Hey, you can use these tools to access my to-dos in Obsidian".

Without MCP, you'd have to copy and paste your tasks every time. With MCP, Claude accesses directly.

## Features

- ✅ **List all to-dos** from your Obsidian vault automatically
- ✅ **Mark tasks as complete** directly via Claude/Cursor
- ✅ **Read entire notes** for context
- ✅ **Works with Claude Desktop** and **Cursor** simultaneously
- ✅ Recursive search across all folders
- ✅ Support for standard Obsidian checkboxes (`- [ ]` and `- [x]`)


## Project Structure

```
obsidian-mcp-server/
├── src/
│   ├── __init__.py              # Marks folder as Python package
│   └── obsidian_mcp.py          # Main MCP server (critical file)
│
├── venv/                        # Python virtual environment (ignored in git)
│
├── requirements.txt             # Project dependencies
├── .env.example                 # Configuration template
├── .gitignore                   # Files ignored by git
├── README.md                    # Portuguese documentation
├── README.en.md                 # English documentation
└── setup.py                     # Package metadata (optional)
```

### What each file does:

| File | Function |
|------|----------|
| `src/obsidian_mcp.py` | **Heart of the project**. Defines 3 tools: `get_todos`, `get_note_content`, `update_todo`. Uses `MCPServer` to communicate with Claude/Cursor via MCP protocol |
| `venv/bin/python3` | Isolated Python with MCP packages installed. When Claude calls the server, it runs this Python |
| `requirements.txt` | Lists packages: `mcp>=0.2.0` and `python-dotenv>=1.0.0`. Installed with `pip install -r requirements.txt` |
| `.env` | Your local file (not committed) with `OBSIDIAN_VAULT_PATH=/Users/you/Documents` |
| `claude_desktop_config.json` | Config that Claude Desktop reads from `~/Library/Application Support/Claude/` to know about the server |
| `mcp.json` | Config that Cursor reads from `~/.cursor/` to know about the server |

## Installation

### Prerequisites

- macOS (tested on 11.0+)
- Python 3.11 or higher
- Homebrew (to install Python)
- Claude Desktop or Cursor installed
- An existing Obsidian vault

### Step 1: Clone or Create the Project

```bash
# If cloning from GitHub:
git clone git@github.com:YOUR-USER/obsidian-mcp.git
cd obsidian-mcp

# Or create from scratch:
mkdir obsidian-mcp
cd obsidian-mcp
```

### Step 2: Create Python Virtual Environment

```bash
python3.11 -m venv venv
source venv/bin/activate
```

Your prompt should appear like this:
```
(venv) obsidian-mcp %
```

### Step 3: Install Dependencies

```bash
pip install --upgrade pip
pip install -r requirements.txt
```

This installs:
- `mcp` (2.2.0+) — the MCP protocol
- `python-dotenv` — to read environment variables

### Step 4: Create `.env` File

```bash
# Copy the template:
cp .env.example .env

# Open and replace the path:
nano .env
```

Find your vault path:
1. Open Obsidian
2. Go to **Settings** → **About**
3. Look for **Vault location:**
4. Copy the full path and paste in `.env`

Example:
```env
OBSIDIAN_VAULT_PATH=/Users/pauline/Documents/MyVault
```

Save (Ctrl+X, then `y` and Enter).

### Step 5: Test the Server Locally

```bash
python3 src/obsidian_mcp.py
```

If it works, the terminal waits (no error should appear). Press **Ctrl+C** to exit.

## Configuration (Claude Desktop + Cursor)

### Claude Desktop

1. Create the configuration folder:
```bash
mkdir -p ~/Library/Application\ Support/Claude
```

2. Create the `claude_desktop_config.json` file:
```bash
cat > ~/Library/Application\ Support/Claude/claude_desktop_config.json << 'EOF'
{
  "mcpServers": {
    "obsidian-mcp": {
      "command": "/full/path/to/obsidian-mcp/venv/bin/python3",
      "args": [
        "/full/path/to/obsidian-mcp/src/obsidian_mcp.py"
      ],
      "env": {
        "OBSIDIAN_VAULT_PATH": "/Users/your-username/Documents/your-vault"
      }
    }
  }
}
EOF
```

**Replace:**
- `/full/path/to/obsidian-mcp` → the real path (ex: `/Users/pauline/Documents/obsidian-mcp`)
- `/Users/your-username/Documents/your-vault` → your vault

3. Restart Claude Desktop (Cmd+Q and open again)

### Cursor

1. Create the folder:
```bash
mkdir -p ~/.cursor
```

2. Create the `mcp.json` file:
```bash
cat > ~/.cursor/mcp.json << 'EOF'
{
  "mcpServers": {
    "obsidian-mcp": {
      "command": "/full/path/to/obsidian-mcp/venv/bin/python3",
      "args": [
        "/full/path/to/obsidian-mcp/src/obsidian_mcp.py"
      ],
      "env": {
        "OBSIDIAN_VAULT_PATH": "/Users/your-username/Documents/your-vault"
      }
    }
  }
}
EOF
```

3. Restart Cursor (Cmd+Q and open again)

## How to Use

### In Claude Desktop

Open a conversation and ask anything related to tasks:

```
What are my to-dos in Obsidian?
```

Claude will:
1. Call the `get_todos` tool
2. Receive JSON with all tasks (167 in your case!)
3. Format and display readably

You can also do:
```
Mark the task "Deploy the app" in Projects/project-x.md line 5 as complete
```

Claude will automatically call `update_todo`.

### In Cursor

Access the Claude tab (left side) and ask the same questions. Behavior is identical.

## How It Works Under the Hood

```
Claude/Cursor (AIs)
       ↓
claude_desktop_config.json / mcp.json (configs)
       ↓
OS Executor (reads config and runs Python command)
       ↓
/venv/bin/python3 src/obsidian_mcp.py (server running)
       ↓
MCPServer ("listens" for MCP requests)
       ↓
Functions decorated with @mcp.tool():
  - get_todos()         → Sweeps .rglob("*.md"), extracts checkboxes
  - get_note_content()  → Opens file and returns content
  - update_todo()       → Writes [ ] or [x] and saves file
       ↓
JSON responses
       ↓
Claude/Cursor displays to you
```

## Code Structure

### `src/obsidian_mcp.py` - The Jewel

```python
from mcp.server.mcpserver import MCPServer

mcp = MCPServer("obsidian-mcp")

@mcp.tool()
def get_todos(folder: Optional[str] = None) -> str:
    """Tool 1: Lists all to-dos"""
    # Sweeps vault_path recursively with .rglob("*.md")
    # Searches for lines with "- [ ]" or "- [x]"
    # Returns JSON with {file, line, completed, text}

@mcp.tool()
def get_note_content(note_path: str) -> str:
    """Tool 2: Reads entire note"""
    # Opens file and returns raw content

@mcp.tool()
def update_todo(note_path: str, line_number: int, completed: bool) -> str:
    """Tool 3: Marks task as done/not done"""
    # Reads file, replaces [ ] with [x] (or vice versa), saves

if __name__ == "__main__":
    mcp.run()  # Starts MCP server via stdio
```

### Task Flow

1. **User**: "Show me my to-dos"
2. **Claude**: Calls `get_todos()` via MCP
3. **Server** (your code):
   - Reads `OBSIDIAN_VAULT_PATH` variable
   - Traverses all folders with `.rglob("*.md")`
   - For each file, searches for lines with `"- [ ]"` or `"- [x]"`
   - Returns JSON: `{"todos": [...], "count": 167}`
4. **Claude**: Receives JSON, formats nicely and displays
5. **You**: See your 167 to-dos listed

## Troubleshooting

### "I don't have access to your Obsidian"

Means Claude couldn't connect to the server. Check:

1. **Config file exists?**
   ```bash
   cat ~/Library/Application\ Support/Claude/claude_desktop_config.json
   ```

2. **Python path is correct?**
   ```bash
   ls /path/you/put/venv/bin/python3
   ```
   If not found, use the real path (run `which python3` with venv activated)

3. **Vault path is correct?**
   ```bash
   ls /Users/your-username/your-vault
   ```

4. **Restarted Claude Desktop after editing config?**

### "ModuleNotFoundError: No module named 'mcp'"

Means it's running the wrong Python (not from venv). Check the path in `claude_desktop_config.json`:

```bash
# Must be THIS:
/Users/pauline/Documents/obsidian-mcp/venv/bin/python3

# Not this:
python3.11
```

## Resources

- [Model Context Protocol - Official Docs](https://modelcontextprotocol.io)
- [Python MCP SDK](https://py.sdk.modelcontextprotocol.io)
- [Claude Desktop Setup](https://support.anthropic.com/en/articles/8784710-claude-desktop)
- [Obsidian API](https://docs.obsidian.md/)

# Usage Examples - Advanced Filters

This document shows how to use the 3 filtering methods in Obsidian MCP Server.

---

## 1️⃣ Inline Tags (`#tag`)

### File: `Projects/oloroke-notes.md`

```markdown
# Oloroke Improvements

- [ ] Implement OAuth authentication #urgent #backend
- [ ] Improve carousel performance #performance #frontend
- [ ] Add unit tests #testing #backend
- [x] Review UI/UX #design #completed
- [ ] Document API #documentation #backend
```

### How to call in Claude:

```
Show me all tasks with tag #urgent from oloroke
```

Claude automatically calls:
```python
get_todos(folder="Projects/oloroke-notes", tag="urgent")
```

Returns:
```json
{
  "todos": [
    {
      "file": "Projects/oloroke-notes.md",
      "line": 2,
      "completed": false,
      "text": "Implement OAuth authentication",
      "tags": ["urgent", "backend"],
      "priority": null,
      "project": null
    }
  ],
  "count": 1
}
```

### Example questions:

```
Which tasks have #backend?
Show me tasks with #testing
Execute all #urgent tasks from oloroke
```

---

## 2️⃣ Priority with Emoji (`🔴🟡🟢⚫`)

### File: `Projects/oloroke-notes.md`

```markdown
# Oloroke Improvements

- [ ] 🔴 Implement OAuth authentication
- [ ] 🟡 Improve carousel performance
- [ ] 🟢 Add unit tests
- [x] 🔵 Review UI/UX
- [ ] 🟡 Document API
- [ ] ⚫ Database migration (blocked)
```

### Emoji Legend:

| Emoji | Meaning | Flag |
|-------|---------|------|
| 🔴 | High priority | `priority="high"` |
| 🟡 | Medium priority | `priority="medium"` |
| 🟢 | Low priority | `priority="low"` |
| ⚫ | Blocked | `priority="blocked"` |

### How to call in Claude:

```
Show me high priority tasks from oloroke
```

Claude automatically calls:
```python
get_todos(folder="Projects/oloroke-notes", priority="high")
```

Returns:
```json
{
  "todos": [
    {
      "file": "Projects/oloroke-notes.md",
      "line": 2,
      "completed": false,
      "text": "Implement OAuth authentication",
      "tags": [],
      "priority": "high",
      "project": null
    }
  ],
  "count": 1
}
```

### Example questions:

```
Which are the red tasks (🔴)?
Show me everything that's blocked (⚫)
Execute medium priority tasks
```

---

## 3️⃣ YAML Frontmatter (More Structured)

### File: `Projects/oloroke-notes.md`

```markdown
---
projeto: oloroke
prioridade: alta
tags: [urgent, backend]
responsavel: Pauline
deadline: 2024-12-31
---

# Oloroke Improvements

- [ ] Implement OAuth authentication
- [ ] Improve carousel performance
- [x] Review UI/UX

---
projeto: oloroke
prioridade: média
tags: [testing, refactor]
---

## Tests and Refactor

- [ ] Add unit tests
- [ ] Clean up legacy code
```

### Frontmatter Structure:

```yaml
---
projeto: project-name              # Identifies the project
prioridade: high|medium|low        # General priority for the note
tags: [tag1, tag2, tag3]           # Tags applied to ALL to-dos
responsavel: Person's Name          # Who is responsible
deadline: YYYY-MM-DD               # Deadline (you can use for filtering)
---
```

### How to call in Claude:

```
Show me all tasks from oloroke project with high priority
```

Claude calls:
```python
get_todos(folder="Projects/oloroke-notes", priority="high")
```

Returns:
```json
{
  "todos": [
    {
      "file": "Projects/oloroke-notes.md",
      "line": 0,
      "completed": false,
      "text": "Implement OAuth authentication",
      "tags": ["urgent", "backend"],
      "priority": "high",
      "project": "oloroke"
    },
    {
      "file": "Projects/oloroke-notes.md",
      "line": 1,
      "completed": false,
      "text": "Improve carousel performance",
      "tags": ["urgent", "backend"],
      "priority": "high",
      "project": "oloroke"
    }
  ],
  "count": 2
}
```

### Example questions:

```
What tasks are in oloroke project?
Show me everything about terreiro-app project
Execute high priority tasks from petlove
```

---

## Combining All Methods

### File: `Projects/oloroke-notes.md`

```markdown
---
projeto: oloroke
tags: [oloroke, app]
---

# Oloroke - Priority Tasks

## Backend

- [ ] 🔴 Implement OAuth authentication #urgent #backend
- [ ] 🟡 Improve carousel performance #performance #backend
- [ ] 🟢 Add unit tests #testing

## Frontend

- [ ] 🔴 Review interface design #urgent #design
- [ ] 🟡 Implement dark mode #ui #frontend
- [ ] ⚫ TypeScript migration #blocked #refactor
```

Here you have:
1. **Frontmatter**: Identifies project `oloroke` and general tags
2. **Inline tags**: Specifies `#urgent`, `#backend`, `#testing`, etc
3. **Emoji**: Shows visual priority `🔴🟡🟢⚫`

### Powerful example questions:

```
"Execute all urgent (#urgent) tasks from oloroke project"
→ get_todos(folder="Projects/oloroke-notes", tag="urgent", project="oloroke")

"Which tasks are blocked that I need to unblock?"
→ get_todos(priority="blocked")

"What backend (#backend) work needs high priority?"
→ get_todos(tag="backend", priority="high")

"Which incomplete tasks from oloroke have #urgent?"
→ get_todos(folder="Projects/oloroke-notes", tag="urgent", completed=False)
```

---

## Comparison: Which to Use?

| Method | Pros | Cons | Best For |
|--------|------|------|----------|
| **Inline Tags** | Flexible, easy to add | Can pollute the line | Quick categorizations |
| **Emoji** | Visual, intuitive | Limited to 4 priorities | Quick priority view |
| **Frontmatter** | Structured, rich metadata | More work to maintain | Complex projects |
| **All 3** | Maximum flexibility | None! | Recommended! |

---

## Installing Advanced Version

1. Replace `src/obsidian_mcp.py` with the advanced version

2. Update `requirements.txt`:
```bash
cat > requirements.txt << 'EOF'
mcp>=0.2.0
python-dotenv>=1.0.0
pyyaml>=6.0
EOF
```

3. Reinstall dependencies:
```bash
pip install -r requirements.txt
```

4. Restart Claude Desktop and Cursor

---

## Pro Tips

### 1. Organize by project
```markdown
---
projeto: oloroke
---
```

### 2. Use tags for categories
```markdown
- [ ] Task #backend #urgent #oauth
```

### 3. Combine with emoji for quick visualization
```markdown
- [ ] 🔴 #urgent Critical thing
```

### 4. Add useful metadata in frontmatter
```yaml
---
projeto: oloroke
deadline: 2024-12-31
responsavel: Pauline
reviewed_on: 2024-09-25
---
```

### 5. Aggregate related notes
Create a folder per project:
```
Projects/
├── oloroke-notes.md
├── terreiro-app-notes.md
└── petlove-improvements.md
```

Then ask:
```
"What are all the tasks in the Projects folder?"
```

---

## Real Conversation Examples

### Conversation 1: Explore a project

```
You: What projects do I have?
Claude: [lists via list_projects()]

You: Show me high priority tasks from oloroke
Claude: [calls get_todos(project="oloroke", priority="high")]
         Shows 3 tasks 🔴

You: Execute the first one (OAuth)
Claude: [calls update_todo()]
        Marks as complete and confirms
```

### Conversation 2: Filter by context

```
You: I'm working on tests now
Claude: Understood, show me only tasks with #testing tag
Claude: [calls get_todos(tag="testing")]
         Shows 2 test tasks

You: Mark the first one as complete
Claude: [calls update_todo()]
```

### Conversation 3: Combine filters

```
You: What's my most urgent backend work in oloroke?
Claude: [calls get_todos(
  folder="Projects/oloroke-notes",
  tag="backend",
  priority="high",
  completed=False
)]
Shows: "Implement OAuth authentication"
```

---

Done! Now you have **3 powerful ways** to organize your tasks in Obsidian and Claude can filter exactly what you need. 

## License

MIT - Use freely! If you make improvements, consider submitting a pull request 

## Contributing

Found a bug or have an idea? Open an [issue](https://github.com/your-user/obsidian-mcp/issues) or submit a [pull request](https://github.com/your-user/obsidian-mcp/pulls)!

---

**Created by Pauline Oliveira**

Questions? Open an issue on GitHub or reach out!