Obsidian MCP Server
by RasAlGhul96
README.md
# Obsidian MCP Server (Zero Trust)
Servidor MCP en TypeScript que conecta Claude Desktop con una boveda local de Obsidian
bajo un modelo de **Confianza Cero**. Es **solo lectura por defecto**; la escritura es
**opt-in** mediante `OBSIDIAN_ENABLE_WRITE=true` y pasa por el mismo sandbox.
## Herramientas expuestas
| Herramienta | Tipo | Descripcion |
|----------------|--------------|------------------------------------------------------|
| `read_note` | lectura | Lee el contenido de una nota `.md` de la boveda. |
| `list_notes` | lectura | Lista las notas de la boveda (o de una subcarpeta). |
| `search_vault` | lectura | Busca texto dentro de las notas de la boveda. |
| `create_note` | escritura * | Crea una nota `.md` nueva. Falla si ya existe. |
| `update_note` | escritura * | Sobrescribe una nota existente (escritura atomica). |
| `append_note` | escritura * | Anade texto al final de una nota existente. |
| `delete_note` | escritura * | Mueve una nota a la papelera `.trash` (reversible). |
\* Las herramientas de escritura solo se registran si `OBSIDIAN_ENABLE_WRITE=true`.
Por defecto el servidor es **solo lectura**.
## Modelo de seguridad (Zero Trust)
El servidor asume que **toda ruta recibida es hostil** hasta ser demostrada segura.
El unico limite de confianza es `OBSIDIAN_VAULT_PATH` (ruta absoluta).
Garantias del sandbox:
1. **Anti path traversal** — se resuelve la ruta a su forma canonica absoluta y se
verifica que siga estando *dentro* de la boveda. Se bloquea `../../etc/passwd`,
`..\\..\\.ssh\\id_rsa`, rutas absolutas externas, null bytes, etc.
2. **Ocultos ignorados** — cualquier segmento que empiece por `.` (p. ej. `.obsidian`,
`.git`, `.ssh`) queda vetado. La configuracion interna de Obsidian nunca es accesible.
Ademas se rechaza `:` (unidad relativa `C:foo` y flujos de datos alternativos NTFS).
3. **Enlaces simbolicos confinados** — se resuelve el destino real del symlink
(`fs.realpath`) y se rechaza si escapa de la boveda, evitando fugas por enlaces.
4. **Allowlist de extensiones** — solo se leen archivos `.md` (y `.markdown`).
5. **Un unico portero** — cada herramienta MUST pasar por el middleware `resolveSafePath`
antes de tocar el disco. Ninguna herramienta accede al filesystem por su cuenta.
## Estructura del proyecto
```
MCP/
├── src/
│ ├── index.ts # Entrypoint: crea el server MCP + transporte stdio (Fase 2)
│ ├── config/
│ │ └── env.ts # Carga y valida OBSIDIAN_VAULT_PATH (Fase 2)
│ ├── security/
│ │ └── pathGuard.ts # Middleware Zero Trust de validacion de rutas (Fase 2)
│ └── tools/
│ ├── readNote.ts # Herramienta read_note (Fase 2)
│ ├── listNotes.ts # Herramienta list_notes (Fase 2)
│ └── searchVault.ts # Herramienta search_vault (Fase 2)
├── tests/
│ └── security.test.ts # Red Team: intentos de path traversal, ocultos, symlinks (Fase 3)
├── package.json
├── tsconfig.json
├── .env.example
└── .gitignore
```
## Diseno del middleware de seguridad: `resolveSafePath`
Contrato de la funcion central que blindara cada herramienta en la Fase 2:
```
resolveSafePath(relativeInput: string): string // devuelve ruta absoluta segura o LANZA error
```
Pipeline de validacion (falla-cerrado, se rechaza ante cualquier duda):
1. **Normalizar entrada** — rechazar si contiene `\0` (null byte) o esta vacia.
2. **Prohibir rutas absolutas del cliente** — el input siempre es *relativo a la boveda*.
Se rechaza `path.isAbsolute(input)` y esquemas tipo `C:\`, `/`, `\\servidor`.
3. **Resolver contra la boveda** — `path.resolve(VAULT_ROOT, input)`.
4. **Verificar contencion** — la ruta resuelta debe empezar por `VAULT_ROOT + path.sep`
(comparacion normalizada, case-insensitive en Windows). Si no, `PATH_ESCAPE`.
5. **Vetar segmentos ocultos** — dividir la ruta relativa por separador y rechazar si
algun segmento empieza por `.`.
6. **Resolver symlinks reales** — `fs.realpathSync` del destino y repetir el paso 4
sobre la ruta real. Si el enlace apunta fuera, `SYMLINK_ESCAPE`.
7. **Validar extension** — para lecturas de archivo, exigir `.md` / `.markdown`.
Errores estructurados (nunca stack traces crudos al modelo):
`PATH_ESCAPE`, `HIDDEN_SEGMENT`, `SYMLINK_ESCAPE`, `INVALID_EXTENSION`, `NOT_FOUND`.
## Requisitos
- Node.js >= 20
- Una boveda de Obsidian local
## Estado
- [x] Fase 1 — Arquitectura y entorno
- [x] Fase 2 — Core y herramientas
- [x] Fase 3 — Red Team y tests de seguridad (18/18)
- [x] Fase 4 — Despliegue e integracion
TDQS
A4.4/5.0
Scored across 3 tools
Disambiguation5/5
Each tool serves a clearly distinct purpose: reading a single note, listing notes recursively, and searching for content across notes. There is no overlap or ambiguity between them.
Naming Consistency5/5
All tool names follow a consistent verb_noun pattern: read_note, list_notes, search_vault. The naming convention is uniform and predictable.
Tool Count5/5
Three tools is well-scoped for a read-only Obsidian vault server. Each tool earns its place and covers the essential read/query operations without redundancy.
Completeness5/5
For a read-only server, the surface is complete: you can read, list, and search notes. There are no obvious gaps for the stated purpose of accessing and searching vault content.
Maintenance
ActivitySlowing
ResponsivenessNo issues