search_notes
Find specific notes in your Obsidian vault using search queries, tag filters, and folder navigation to locate relevant information quickly.
Instructions
Search for notes in a vault
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| folder | No | Filter by folder | |
| limit | No | Maximum number of results | |
| query | Yes | Search query | |
| tags | No | Filter by tags | |
| vault | Yes | Vault name |
Implementation Reference
- src/types/index.ts:45-51 (schema)Defines the SearchOptions interface for input parameters to the search_notes tool.export interface SearchOptions { query: string; tags?: string[]; folder?: string; limit?: number; includeContent?: boolean; }
- src/index.ts:78-92 (registration)Registers the search_notes tool in the MCP server's list tools handler with name, description, and input schema.{ name: 'search_notes', description: 'Search for notes in a vault', inputSchema: { type: 'object', properties: { vault: { type: 'string', description: 'Vault name' }, query: { type: 'string', description: 'Search query' }, tags: { type: 'array', items: { type: 'string' }, description: 'Filter by tags' }, folder: { type: 'string', description: 'Filter by folder' }, limit: { type: 'number', description: 'Maximum number of results' }, }, required: ['vault', 'query'], }, },
- src/index.ts:493-508 (handler)Top-level MCP tool handler for 'search_notes': validates vault, calls connector.searchNotes with parameters, returns JSON result.case 'search_notes': { const connector = this.connectors.get(args?.vault as string); if (!connector) { throw new Error(`Vault "${args?.vault}" not found`); } const result = await connector.searchNotes({ query: args?.query as string, tags: args?.tags as string[] | undefined, folder: args?.folder as string | undefined, limit: args?.limit as number | undefined, includeContent: true, }); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }], }; }
- src/connectors/LocalConnector.ts:95-139 (handler)Core search logic for local vaults: retrieves all notes from cache, applies filters for folder/tags/query/content, limits results.async searchNotes(options: SearchOptions): Promise<VaultOperationResult<Note[]>> { try { const allNotes = await this.getAllNotes(); if (!allNotes.success || !allNotes.data) { return allNotes; } let results = allNotes.data; // Filter by folder if (options.folder) { results = results.filter(note => note.path.startsWith(options.folder!)); } // Filter by tags if (options.tags && options.tags.length > 0) { results = results.filter(note => { if (!note.tags) return false; return options.tags!.some(tag => note.tags!.includes(tag)); }); } // Search in title and content if (options.query) { const query = options.query.toLowerCase(); results = results.filter(note => { const inTitle = note.title.toLowerCase().includes(query); const inContent = options.includeContent && note.content.toLowerCase().includes(query); return inTitle || inContent; }); } // Apply limit if (options.limit && options.limit > 0) { results = results.slice(0, options.limit); } return { success: true, data: results }; } catch (error) { return { success: false, error: `Failed to search notes: ${error instanceof Error ? error.message : String(error)}` }; } }
- src/connectors/RemoteConnector.ts:95-146 (handler)Core search logic for remote vaults: attempts server-side search via POST /search, falls back to identical client-side filtering.async searchNotes(options: SearchOptions): Promise<VaultOperationResult<Note[]>> { try { // Try server-side search first try { const response = await this.client.post('/search', options); if (response.data.notes) { return { success: true, data: response.data.notes }; } } catch { // Fall back to client-side search } // Client-side search fallback const allNotes = await this.getAllNotes(); if (!allNotes.success || !allNotes.data) { return allNotes; } let results = allNotes.data; if (options.folder) { results = results.filter(note => note.path.startsWith(options.folder!)); } if (options.tags && options.tags.length > 0) { results = results.filter(note => { if (!note.tags) return false; return options.tags!.some(tag => note.tags!.includes(tag)); }); } if (options.query) { const query = options.query.toLowerCase(); results = results.filter(note => { const inTitle = note.title.toLowerCase().includes(query); const inContent = options.includeContent && note.content.toLowerCase().includes(query); return inTitle || inContent; }); } if (options.limit && options.limit > 0) { results = results.slice(0, options.limit); } return { success: true, data: results }; } catch (error) { return { success: false, error: `Failed to search notes: ${error instanceof Error ? error.message : String(error)}` }; } }