search_by_links
Find notes in your vault by analyzing link relationships. Search for notes that link to specific content, are linked from other notes, contain external domain links, or have broken internal connections.
Instructions
Search for notes based on their link relationships
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| has_links_to | No | Find notes that link to any of these notes | |
| linked_from | No | Find notes that are linked from any of these notes | |
| external_domains | No | Find notes with external links to these domains | |
| broken_links | No | Find notes with broken internal links |
Implementation Reference
- src/server/link-handlers.ts:187-283 (handler)The primary handler function for the 'search_by_links' tool. It validates arguments, resolves the vault context, executes SQL queries based on link criteria (has_links_to, linked_from, external_domains, broken_links), and returns JSON results or error.handleSearchByLinks = async (args: { has_links_to?: string[]; linked_from?: string[]; external_domains?: string[]; broken_links?: boolean; vault_id?: string; }) => { try { // Validate arguments validateToolArgs('search_by_links', args); const { hybridSearchManager } = await this.resolveVaultContext(args.vault_id); const db = await hybridSearchManager.getDatabaseConnection(); let notes: NoteRow[] = []; // Handle different search criteria if (args.has_links_to && args.has_links_to.length > 0) { // Find notes that link to any of the specified notes const targetIds = args.has_links_to.map(id => this.generateNoteIdFromIdentifier(id) ); const placeholders = targetIds.map(() => '?').join(','); notes = await db.all( `SELECT DISTINCT n.* FROM notes n INNER JOIN note_links nl ON n.id = nl.source_note_id WHERE nl.target_note_id IN (${placeholders})`, targetIds ); } else if (args.linked_from && args.linked_from.length > 0) { // Find notes that are linked from any of the specified notes const sourceIds = args.linked_from.map(id => this.generateNoteIdFromIdentifier(id) ); const placeholders = sourceIds.map(() => '?').join(','); notes = await db.all( `SELECT DISTINCT n.* FROM notes n INNER JOIN note_links nl ON n.id = nl.target_note_id WHERE nl.source_note_id IN (${placeholders})`, sourceIds ); } else if (args.external_domains && args.external_domains.length > 0) { // Find notes with external links to specified domains const domainConditions = args.external_domains .map(() => 'el.url LIKE ?') .join(' OR '); const domainParams = args.external_domains.map(domain => `%${domain}%`); notes = await db.all( `SELECT DISTINCT n.* FROM notes n INNER JOIN external_links el ON n.id = el.note_id WHERE ${domainConditions}`, domainParams ); } else if (args.broken_links) { // Find notes with broken internal links notes = await db.all( `SELECT DISTINCT n.* FROM notes n INNER JOIN note_links nl ON n.id = nl.source_note_id WHERE nl.target_note_id IS NULL` ); } return { content: [ { type: 'text', text: JSON.stringify( { success: true, notes: notes, count: notes.length }, null, 2 ) } ] }; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; return { content: [ { type: 'text', text: JSON.stringify( { success: false, error: errorMessage }, null, 2 ) } ], isError: true }; } };
- src/server.ts:1341-1351 (registration)Registration of the 'search_by_links' tool handler in the MCP server's CallToolRequestSchema switch statement, mapping tool calls to LinkHandlers.handleSearchByLinks.case 'search_by_links': return await this.linkHandlers.handleSearchByLinks( args as unknown as { has_links_to?: string[]; linked_from?: string[]; external_domains?: string[]; broken_links?: boolean; vault_id?: string; } );
- src/server.ts:1179-1206 (schema)Input schema definition for the 'search_by_links' tool, returned by ListToolsRequestSchema handler, defining parameters and descriptions.{ name: 'search_by_links', description: 'Search for notes based on their link relationships', inputSchema: { type: 'object', properties: { has_links_to: { type: 'array', items: { type: 'string' }, description: 'Find notes that link to any of these notes' }, linked_from: { type: 'array', items: { type: 'string' }, description: 'Find notes that are linked from any of these notes' }, external_domains: { type: 'array', items: { type: 'string' }, description: 'Find notes with external links to these domains' }, broken_links: { type: 'boolean', description: 'Find notes with broken internal links' } } } },
- src/server/validation.ts:856-915 (helper)Validation rules used by validateToolArgs('search_by_links', args) in the handler, including custom validators for identifier formats in arrays.search_by_links: [ { field: 'has_links_to', required: false, type: 'array', arrayItemType: 'string', allowEmpty: true, customValidator: (value: any) => { if (!Array.isArray(value)) return null; for (const identifier of value) { if (!identifier.includes('/')) { return `identifier "${identifier}" must be in format "type/filename"`; } const parts = identifier.split('/'); if (parts.length !== 2 || !parts[0] || !parts[1]) { return `identifier "${identifier}" must be in format "type/filename" with both parts non-empty`; } } return null; } }, { field: 'linked_from', required: false, type: 'array', arrayItemType: 'string', allowEmpty: true, customValidator: (value: any) => { if (!Array.isArray(value)) return null; for (const identifier of value) { if (!identifier.includes('/')) { return `identifier "${identifier}" must be in format "type/filename"`; } const parts = identifier.split('/'); if (parts.length !== 2 || !parts[0] || !parts[1]) { return `identifier "${identifier}" must be in format "type/filename" with both parts non-empty`; } } return null; } }, { field: 'external_domains', required: false, type: 'array', arrayItemType: 'string', allowEmpty: true }, { field: 'broken_links', required: false, type: 'boolean' }, { field: 'vault_id', required: false, type: 'string', allowEmpty: false } ],