Skip to main content
Glama

Obsidian MCP Server

by quinny1187
task_list.md18.5 kB
# Obsidian MCP Server - Development Task List ## Project Overview Build a Model Context Protocol (MCP) server for Obsidian that follows the established patterns from other MCP servers in the repos directory (claude-speak, claude-listen, claude-look, godot-mcp). The server will enable Claude Code to interact directly with Obsidian vaults through direct file system access - no third-party plugins required. ## Architecture Approach - **TypeScript-based** implementation using `@modelcontextprotocol/sdk` - **Direct file system access** to Obsidian vaults - full control, no external dependencies - **Stdio transport** for Claude Code communication - **Modular tool structure** with separate files for each tool category - **Production-grade setup** with proper error handling, logging, and validation - **Windows-optimized** with proper path handling and vault discovery ## Development Tasks ### 1. Project Setup & Configuration **Files to create:** - `package.json` - Node.js project configuration - `tsconfig.json` - TypeScript configuration - `.gitignore` - Git ignore patterns - `README.md` - Documentation **Implementation details:** ```json // package.json structure (similar to claude-speak) { "name": "obsidian-mcp", "version": "1.0.0", "type": "module", "main": "dist/index.js", "dependencies": { "@modelcontextprotocol/sdk": "^1.0.4", "winston": "^3.11.0", "zod": "^3.22.4", "gray-matter": "^4.0.3", "markdown-it": "^14.0.0" } } ``` ### 2. Core MCP Server Implementation **File: `src/index.ts`** - Initialize MCP server with stdio transport - Register tool handlers - Set up error handling and logging **Key components:** ```typescript // Server initialization pattern from existing servers const server = new Server( { name: "obsidian-mcp", version: "1.0.0" }, { capabilities: { tools: {} } } ); ``` ### 3. Vault Management Module **File: `src/lib/vaultManager.ts`** - Vault discovery and validation - Path resolution and normalization - File system operations wrapper - Obsidian-specific file handling (.md, attachments) **Implementation needs:** - Auto-discover vaults from common Windows locations: ```typescript // Default Obsidian vault locations on Windows const vaultPaths = [ `${process.env.USERPROFILE}\\Documents\\Obsidian`, `${process.env.USERPROFILE}\\OneDrive\\Documents\\Obsidian`, `${process.env.APPDATA}\\obsidian\\vaults.json` // Obsidian's vault registry ]; ``` - Validate vault structure (presence of .obsidian folder) - Safe path operations to prevent access outside vault - Handle Windows path separators correctly - Read Obsidian's config to understand vault settings ### 4. Tool Implementations #### 4.1 Core Vault Operations **File: `src/tools/vault-operations.ts`** **Tools to implement:** - `get_server_info` - Get MCP server status and capabilities - Input: none - Output: version, available vaults, current config - `fetch` - General purpose file fetcher - Input: vault_path, file_path, options - Output: file content with metadata #### 4.2 Active File Management **File: `src/tools/active-file.ts`** **Tools to implement:** - `get_active_file` - Get the currently "active" file (last accessed) - Input: vault_path - Output: file path, content, metadata - `update_active_file` - Replace entire content of active file - Input: vault_path, content - Output: success status - `append_to_active_file` - Append content to active file - Input: vault_path, content, position (end/beginning/after_frontmatter) - Output: success status - `patch_active_file` - Partially update active file - Input: vault_path, patches[] (line-based or pattern-based) - Output: success status, changes applied - `delete_active_file` - Delete the active file - Input: vault_path - Output: success status #### 4.3 Note Operations Tools **File: `src/tools/notes.ts`** **Tools to implement:** - `get_vault_file` - Read any file from vault (alias: read_note) - Input: vault_path, file_path - Output: content, frontmatter, stats - `create_vault_file` - Create new file (alias: write_note) - Input: vault_path, file_path, content - Output: success status, file path - `append_to_vault_file` - Append to existing file - Input: vault_path, file_path, content, position - Output: success status - `patch_vault_file` - Partial file updates with precise targeting - Input: vault_path, file_path, patches[] - Patch types: ```typescript type Patch = | { type: 'line', lineNumber: number, content: string } | { type: 'heading', heading: string, content: string, mode: 'replace'|'append'|'prepend' } | { type: 'block', blockId: string, content: string } | { type: 'pattern', pattern: string, replacement: string, all?: boolean } ``` - `delete_vault_file` - Delete a file (alias: delete_note) - Input: vault_path, file_path - Output: success status - `list_vault_files` - List all files in vault - Input: vault_path, options (extensions, folder, recursive) - Output: array of file paths with metadata - `show_file_in_obsidian` - Open file in Obsidian (if running) - Input: vault_path, file_path - Output: success status (uses obsidian:// URI scheme) #### 4.4 Search Tools **File: `src/tools/search.ts`** **Tools to implement:** - `search_vault` - Full-text search with advanced options - Input: vault_path, query, options (regex, case_sensitive, file_types, folders) - Output: matching files with context, line numbers, match count - `search_vault_simple` - Simplified search for quick queries - Input: vault_path, query - Output: list of matching file paths - `search_vault_smart` - AI-enhanced semantic search - Input: vault_path, query, options (semantic_matching, fuzzy, synonyms) - Output: ranked results with relevance scores - Features: - Fuzzy matching for typos - Synonym expansion - Semantic similarity (if configured) - Smart ranking based on: - Title matches (higher weight) - Tag matches - Recent modification - Link connectivity - `find_by_tag` - Find notes with specific tags - Input: vault_path, tags[] - Output: note paths with tag matches - `find_backlinks` - Find notes linking to a specific note - Input: vault_path, note_path - Output: array of linking notes #### 4.5 Template Operations **File: `src/tools/templates.ts`** **Tools to implement:** - `execute_template` - Process and execute Obsidian templates - Input: vault_path, template_path, variables, target_path - Output: processed content, file created/updated - Features: - Variable substitution: `{{title}}`, `{{date}}`, `{{time}}` - Date formatting: `{{date:YYYY-MM-DD}}` - Dynamic fields from variables object - Templater plugin syntax support (optional) - Example: ```markdown # {{title}} Created: {{date:YYYY-MM-DD HH:mm}} Tags: {{tags}} ## Notes {{content}} ``` - `list_templates` - List available templates - Input: vault_path, template_folder (default: "Templates") - Output: array of template files with descriptions - `create_from_template` - Create new note from template - Input: vault_path, template_name, title, folder - Output: new file path, processed content #### 4.6 Graph Navigation Tools **File: `src/tools/graph.ts`** **Tools to implement:** - `trace_links` - Follow the graph view down N levels from a starting note - Input: vault_path, note_path, depth (how many levels to follow), direction (forward/backward/both) - Output: complete graph structure with all discovered nodes and connections - Example: "Follow all connections from 'Project Overview' down 3 levels" ```json // trace_links("vault", "Project Overview", 3, "forward") { "nodes": [ {"id": "Project Overview", "level": 0, "type": "start"}, {"id": "Task List", "level": 1, "linkedFrom": ["Project Overview"]}, {"id": "Team Notes", "level": 1, "linkedFrom": ["Project Overview"]}, {"id": "Sprint 1", "level": 2, "linkedFrom": ["Task List"]}, {"id": "John's Tasks", "level": 2, "linkedFrom": ["Task List", "Team Notes"]}, {"id": "Bug #123", "level": 3, "linkedFrom": ["Sprint 1"]}, {"id": "Feature X", "level": 3, "linkedFrom": ["John's Tasks"]} ], "edges": [ {"from": "Project Overview", "to": "Task List", "type": "link"}, {"from": "Project Overview", "to": "Team Notes", "type": "embed"}, {"from": "Task List", "to": "Sprint 1", "type": "link"}, {"from": "Task List", "to": "John's Tasks", "type": "link"}, {"from": "Team Notes", "to": "John's Tasks", "type": "link"}, {"from": "Sprint 1", "to": "Bug #123", "type": "link"}, {"from": "John's Tasks", "to": "Feature X", "type": "link"} ], "stats": { "totalNodes": 7, "maxDepthReached": 3, "totalEdges": 7, "nodesPerLevel": [1, 2, 2, 2] } } ``` - `get_note_connections` - Get all connections for a specific note - Input: vault_path, note_path - Output: incoming links, outgoing links, tags, aliases - `find_orphaned_notes` - Find notes with no incoming or outgoing links - Input: vault_path - Output: list of isolated notes - `find_shortest_path` - Find shortest path between two notes - Input: vault_path, from_note, to_note - Output: array of notes forming the path #### 4.7 Metadata Tools **File: `src/tools/metadata.ts`** **Tools to implement:** - `get_frontmatter` - Extract frontmatter from note - Input: vault_path, note_path - Output: parsed frontmatter object - `update_frontmatter` - Update note frontmatter - Input: vault_path, note_path, frontmatter - Output: success status - `list_tags` - List all tags in vault - Input: vault_path - Output: tag list with counts #### 4.8 Vault Information Tools **File: `src/tools/vault.ts`** **Tools to implement:** - `get_vault_info` - Get vault statistics and configuration - Input: vault_path - Output: stats (note count, size, etc.) - `list_vaults` - List available Obsidian vaults - Input: none - Output: array of vault paths - `get_recent_notes` - Get recently modified notes - Input: vault_path, limit - Output: sorted list of recent notes ### 5. Utility Modules #### 5.1 Frontmatter Parser **File: `src/lib/frontmatter.ts`** - Parse YAML frontmatter using gray-matter - Merge and update frontmatter - Validate frontmatter structure - Preserve formatting and comments #### 5.2 Markdown Processor **File: `src/lib/markdown.ts`** - Extract links and backlinks - Parse tags (#tag and frontmatter tags) - Handle Obsidian-specific syntax: ```typescript // Wiki links: [[Note Name]] or [[Note Name|Display Text]] // Embeds: ![[Image.png]] or ![[Other Note]] // Block references: [[Note Name#^block-id]] // Heading links: [[Note Name#Heading]] ``` #### 5.3 File Watcher (Optional) **File: `src/lib/watcher.ts`** - Monitor vault for changes - Cache invalidation on file updates - Efficient indexing for large vaults #### 5.4 Logger **File: `src/lib/logger.ts`** - Winston-based logging (following claude-speak pattern) - Structured logging for debugging - Error tracking and reporting ### 6. Input Validation **File: `src/lib/validation.ts`** - Zod schemas for all tool inputs - Path validation and sanitization - Prevent directory traversal attacks ### 7. Testing Suite **Files in `src/__tests__/`** - Unit tests for each tool - Integration tests for vault operations - Mock file system for testing **Test structure:** ```typescript // Following jest pattern from claude-speak describe('Note Operations', () => { test('should read note with frontmatter', async () => { // Test implementation }); }); ``` ### 8. Build & Development Scripts **Scripts to add in package.json:** ```json { "scripts": { "build": "tsc", "dev": "tsx watch src/index.ts", "start": "node dist/index.js", "test": "jest", "lint": "eslint src --ext .ts", "format": "prettier --write src/**/*.ts" } } ``` ## Implementation Order 1. **Phase 1: Foundation** (Core setup) - Project initialization - Basic MCP server setup - Vault manager implementation - Logger setup 2. **Phase 2: Basic Operations** (MVP) - read_note tool - write_note tool - list_notes tool - Basic error handling 3. **Phase 3: Search & Discovery** - search_notes tool - find_by_tag tool - Markdown parser for links/tags 4. **Phase 4: Advanced Features** - Frontmatter operations - Backlink discovery - Vault statistics 5. **Phase 5: Polish & Testing** - Comprehensive error handling - Unit and integration tests - Documentation - Performance optimization ## Configuration Integration ### Claude Desktop Configuration ```json { "mcpServers": { "obsidian-mcp": { "command": "node", "args": ["C:\\repos\\obsidian-mcp\\dist\\index.js"], "env": { "DEFAULT_VAULT": "C:\\Users\\[username]\\Documents\\Obsidian\\[vault-name]" } } } } ``` ## Key Design Decisions 1. **Direct File Access**: We access vault files directly through the file system, giving us: - No dependencies on third-party plugins - Works whether Obsidian is running or not - Full control over operations - Better performance (no HTTP overhead) - Simpler deployment and maintenance 2. **TypeScript First**: Following the pattern of other MCP servers in the repo, using TypeScript for type safety and better development experience. 3. **Modular Architecture**: Each tool category in its own file for maintainability, similar to claude-speak's structure. 4. **Smart Vault Discovery**: The server can: - Auto-discover vaults from Obsidian's registry - Accept explicit vault paths via environment variables - Remember recently used vaults - Default to the most recently modified vault 5. **Safe by Default**: All operations validate paths, prevent directory traversal, and handle errors gracefully. ## Security Considerations - Path validation to prevent access outside vault - Input sanitization using Zod schemas - Read-only operations by default - Explicit confirmation for destructive operations - No execution of embedded scripts or code ## Performance Optimizations - Cache vault structure for list operations - Lazy loading of note content - Streaming for large vaults - Debounced file watching for real-time updates - Efficient search using indexed metadata ## Advantages of Direct File Access 1. **No Dependencies**: Works out of the box, no plugins to install or maintain 2. **Always Available**: Works whether Obsidian is open or closed 3. **Full Control**: Direct access to all vault files and metadata 4. **Performance**: No HTTP overhead, direct file I/O is fastest 5. **Reliability**: No middleware that can fail or need updates 6. **Scriptability**: Can be used in automated workflows without Obsidian running 7. **Version Control Friendly**: Can read/write files in git-managed vaults safely ## Implementation Notes ### Reading Obsidian's Vault Registry ```typescript // Obsidian stores vault info in %APPDATA%/obsidian/obsidian.json interface ObsidianConfig { vaults: { [vaultId: string]: { path: string; ts: number; // last opened timestamp open?: boolean; } } } ``` ### Handling Obsidian File References ```typescript // Convert wiki link to file path function wikiLinkToPath(link: string, currentFile: string): string { // [[Note]] -> Note.md // [[Folder/Note]] -> Folder/Note.md // [[../Note]] -> resolve relative to current file } ``` ### Active File Tracking ```typescript // Track the "active" file across sessions class ActiveFileTracker { private activeFilePath: string | null = null; private lastAccessTime: Map<string, Date> = new Map(); setActive(filePath: string) { this.activeFilePath = filePath; this.lastAccessTime.set(filePath, new Date()); // Persist to .obsidian-mcp/state.json } getActive(): string | null { // Return current active or most recently accessed return this.activeFilePath || this.getMostRecent(); } } ``` ### Graph Traversal Implementation ```typescript // Breadth-first traversal with configurable depth async function traceLinks( startNote: string, depth: number = 2, // How many levels deep to follow direction: 'forward' | 'backward' | 'both' = 'both' ): Promise<GraphStructure> { const visited = new Set<string>(); const queue: Array<{note: string, level: number}> = [{note: startNote, level: 0}]; const nodes: GraphNode[] = []; const edges: GraphEdge[] = []; while (queue.length > 0) { const {note, level} = queue.shift()!; // Stop at specified depth if (level > depth || visited.has(note)) continue; visited.add(note); nodes.push({id: note, level}); // Follow based on direction if (direction === 'forward' || direction === 'both') { // Get all outgoing links from this note const outLinks = await extractOutgoingLinks(note); for (const link of outLinks) { edges.push({from: note, to: link.target, type: link.type}); if (level < depth) { queue.push({note: link.target, level: level + 1}); } } } if (direction === 'backward' || direction === 'both') { // Get all incoming links to this note (backlinks) const inLinks = await findBacklinks(note); for (const link of inLinks) { edges.push({from: link.source, to: note, type: 'backlink'}); if (level < depth) { queue.push({note: link.source, level: level + 1}); } } } } return {nodes, edges}; } // Example usage: // trace_links("My Project", 1) -> Direct connections only // trace_links("My Project", 2) -> Connections and their connections // trace_links("My Project", 5) -> Follow 5 levels deep // trace_links("My Project", 10, 'forward') -> Deep forward traversal // trace_links("My Project", 3, 'backward') -> Find what references this note up to 3 levels ``` ## Future Enhancements (Post-MVP) - Canvas file support (.canvas JSON files) - Plugin configuration reading from .obsidian/plugins/ - Template operations with variable substitution - Daily notes integration (auto-create, naming patterns) - Graph view data export (node relationships) - Attachment management (images, PDFs, etc.) - Vault backup operations - Smart conflict resolution for concurrent edits - Obsidian URI scheme support (obsidian://open?vault=...)

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/quinny1187/obsidian-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server