Skip to main content
Glama
apaulineoliveira

obsidian-mcp

README.md
# Obsidian MCP Server
Article on Medium: https://paulineoliveira.medium.com/por-que-eu-fiz-o-claude-e-o-cursor-lerem-meus-to-dos-no-obsidian-4f0ab55eed84?postPublishedType=initial

An [MCP](https://modelcontextprotocol.io) server that connects your Obsidian vault to Claude Desktop and Cursor, so the AI can search your notes, read and update their metadata, and manage your to-dos without you copying and pasting anything.

🇧🇷 [Versão em português](README.pt.md)

## What it can do

| Tool | Description |
|------|-------------|
| `search_notes` | Searches notes by text or regex, by tags and by frontmatter fields (all combinable), returning matching lines with their line numbers |
| `list_tags` | Lists every tag in the vault (inline and frontmatter) with how many notes use it |
| `get_frontmatter` | Returns a note's YAML frontmatter as JSON |
| `update_frontmatter` | Creates, changes or removes frontmatter keys, keeping comments, key order and the note body intact |
| `get_todos` | Lists to-dos (`- [ ]` / `- [x]`), filterable by `folder`, `tag`, `priority`, `completed` and `language` |
| `update_todo` | Marks a to-do as done/undone (by note path and line number) |
| `get_note_content` | Returns the full content of a note |
| `create_note` | Creates a new note (and any missing folders), never overwriting an existing note |
| `list_projects` | Lists projects found in notes' frontmatter |
| `get_supported_languages` | Lists languages accepted for priority keywords |

Example prompts:

- *"Find notes mentioning OAuth in Projects"* · *"Search for `TODO\(\w+\)` as a regex"*
- *"Which notes are tagged #project and have `status: draft`?"*
- *"Set `status: done` in the frontmatter of Projects/oloroke.md"*
- *"Show my high-priority #backend tasks"* · *"Mark line 5 of Projects/x.md as done"*
- *"Save this summary as a note in Study/mcp.md"*

### Organizing your notes (all optional, can be combined)

- **Inline tags:** `- [ ] Fix login #urgent #backend`. Nested tags work like in Obsidian: filtering by `project` also matches `#project/api`. Tags inside code blocks, URLs (`page#anchor`) and links (`[[Note#Heading]]`) are ignored.
- **Priority emoji:** 🔴 high · 🟡 medium · 🟢 low · ⚫ blocked
- **YAML frontmatter:** `projeto`/`project`, `prioridade`/`priority`, `tags` (list or `"a, b"`) and any custom field you want to search on

```markdown
---
project: oloroke
priority: high
status: draft
tags: [backend]
---
- [ ] 🔴 Implement OAuth #urgent
```

Priority keywords work in **pt, en, es, fr, de, it**. To add another language, add its keywords to `PRIORITY_KEYWORDS` in [src/core/priorities.py](src/core/priorities.py); [src/examples/add_custom_language.py](src/examples/add_custom_language.py) shows ready-made keywords for ja, zh, ru, el and ko.

### Safety

The server only touches files inside `OBSIDIAN_VAULT_PATH`:

- Absolute paths, `..` that leave the vault, and symlinks pointing outside it are rejected.
- `.obsidian/`, `.trash/` and `.git/` are never read or written; only `.md` notes can be opened or edited.
- Edits are atomic (temp file + rename), keep the file's line endings, and are refused if the existing frontmatter is invalid YAML.
- A new note is only created when nothing exists at that path yet: `create_note` never overwrites.

## Requirements

- Python 3.11+
- Claude Desktop or Cursor
- An Obsidian vault

## Setup

### 1. Install

```bash
git clone git@github.com:apaulineoliveira/obsidian-mcp.git
cd obsidian-mcp
python3.11 -m venv venv
source venv/bin/activate          # Windows: venv\Scripts\activate
pip install -e .
```

Get the absolute paths you will need in the next step:

```bash
echo "$PWD/venv/bin/python3"      # command
echo "$PWD/src/obsidian_mcp.py"   # script
```

### 2a. Claude Desktop

Open the config file (or go to *Settings → Developer → Edit Config*):

| OS | Path |
|----|------|
| macOS | `~/Library/Application Support/Claude/claude_desktop_config.json` |
| Windows | `%APPDATA%\Claude\claude_desktop_config.json` |

Add the server under `mcpServers` (keep any servers already there):

```json
{
  "mcpServers": {
    "obsidian-mcp": {
      "command": "/abs/path/to/obsidian-mcp/venv/bin/python3",
      "args": ["/abs/path/to/obsidian-mcp/src/obsidian_mcp.py"],
      "env": { "OBSIDIAN_VAULT_PATH": "/abs/path/to/your/vault" }
    }
  }
}
```

On Windows, use `venv\\Scripts\\python.exe` and escape backslashes (`C:\\Users\\me\\vault`).

Fully quit Claude Desktop (Cmd+Q / exit from the tray) and reopen it. The tools appear under the 🔨 / connectors menu in the chat box.

### 2b. Cursor

Use the same `mcpServers` block in one of these files:

- `~/.cursor/mcp.json` — available in every project
- `.cursor/mcp.json` in a project — only for that project

Then open *Cursor Settings → MCP*: `obsidian-mcp` should show a green dot and its list of tools. Tools are used by the **Agent** mode in chat.

### Configuring the vault path

`OBSIDIAN_VAULT_PATH` must be the absolute path of the vault folder (the one that contains `.obsidian/`). It is read, in order of priority, from:

1. The `env` block of the client config (recommended)
2. A `.env` file at the repository root (`cp .env.example .env`)
3. Default: `~/Obsidian`

## Troubleshooting

- **Server doesn't show up:** validate the config JSON, confirm both paths exist, and fully restart the client.
- **`ModuleNotFoundError: mcp`:** `command` must point to the venv's Python, not the system one.
- **`Vault não encontrado` / `Vault not found`:** `OBSIDIAN_VAULT_PATH` is wrong or not absolute.
- **`Caminho fora do vault` / path outside vault:** use paths relative to the vault, e.g. `Projects/x.md`.
- **Logs:** Claude Desktop writes them to `~/Library/Logs/Claude/mcp-server-obsidian-mcp.log` (macOS) or `%APPDATA%\Claude\logs` (Windows); Cursor shows them in *Output → MCP Logs*.
- **Inspect the server by hand:** `npx @modelcontextprotocol/inspector venv/bin/python3 src/obsidian_mcp.py`

## Architecture

The code follows a layered (clean / hexagonal) architecture. Dependencies only point inward: `adapters → application → core`.

```
src/
├── obsidian_mcp.py      Composition root: wires adapters and services, starts the server
├── core/                Domain rules. Standard library only; no MCP, disk or YAML
│   ├── paths.py         Vault path rules (traversal, internal folders, .md only)
│   ├── frontmatter.py   Frontmatter block split/assemble, via the FrontmatterCodec port
│   ├── tags.py          Obsidian tag rules (nested, case-insensitive, code blocks)
│   ├── search.py        Query, matching, snippets and ranking
│   ├── todos.py         Checkbox parsing, filters and toggling
│   ├── priorities.py    Priority keywords per language and emoji
│   ├── ports.py         NoteRepository and FrontmatterCodec interfaces
│   └── errors.py        Domain errors
├── application/         Use cases that orchestrate the core through the ports
│   ├── vault_notes.py   Reads every note once, already split into metadata and body
│   ├── notes.py         Content, creation, frontmatter, tags and projects
│   ├── todos.py         List and complete to-dos
│   ├── search.py        Note search
│   └── languages.py     Supported languages
└── adapters/            The outside world
    ├── filesystem.py    NoteRepository on disk (symlinks, atomic writes)
    ├── yaml_codec.py    FrontmatterCodec with ruamel.yaml (round-trip)
    └── mcp_server.py    MCP tools: arguments → use cases → JSON
```

Where to put a change:

- **New rule about notes** (e.g. parsing wikilinks): a function in `core/`, tested with plain strings.
- **New capability**: a method in an `application/` service, tested with an in-memory repository.
- **New tool**: a method in `VaultTools` plus its name in `TOOL_NAMES` (`adapters/mcp_server.py`).
- **New storage** (e.g. Obsidian REST API): a class implementing `NoteRepository`, wired in `obsidian_mcp.py`.

## Development

```bash
pip install -e ".[dev]"
pytest
```

Tests mirror the layers: `tests/core` (pure functions), `tests/application` (in-memory repository) and `tests/adapters` (real disk, YAML and MCP).

## Found a bug?

Open an [issue](https://github.com/apaulineoliveira/obsidian-mcp/issues) with: what you did, what you expected, what happened, your OS/Python version, and any error output.

## Contributing

1. Fork the repo and create a branch (`git checkout -b feature/my-change`)
2. Make your change, add tests and run `pytest`
3. Commit using [Conventional Commits](https://www.conventionalcommits.org) (e.g. `feat(search): ...`)
4. Open a pull request describing what and why

## License

MIT — created by Pauline Oliveira.