search_notes_advanced
Search notes in Flint Note using structured filters for metadata, dates, content, and sorting to find specific information quickly.
Instructions
Advanced search with structured filters for metadata, dates, and content
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| type | No | Filter by note type | |
| metadata_filters | No | Array of metadata filters | |
| updated_within | No | Find notes updated within time period (e.g., "7d", "1w", "2m") | |
| updated_before | No | Find notes updated before time period (e.g., "7d", "1w", "2m") | |
| created_within | No | Find notes created within time period | |
| created_before | No | Find notes created before time period | |
| content_contains | No | Search within note content | |
| sort | No | Sort order for results | |
| limit | No | Maximum number of results | |
| offset | No | Number of results to skip | |
| vault_id | No | Optional vault ID to operate on. If not provided, uses the current active vault. | |
| fields | No | Optional array of field names to include in response. Supports dot notation for nested fields (e.g. "metadata.tags") and wildcard patterns (e.g. "metadata.*"). If not specified, all fields are returned. |
Implementation Reference
- src/server/search-handlers.ts:53-73 (handler)The primary MCP tool handler for 'search_notes_advanced'. Validates arguments, resolves vault context, executes advanced search via HybridSearchManager, applies field filtering, and returns formatted JSON results.*/ handleSearchNotesAdvanced = async (args: SearchNotesAdvancedArgs) => { // Validate arguments validateToolArgs('search_notes_advanced', args); const { hybridSearchManager } = await this.resolveVaultContext(args.vault_id); const results = await hybridSearchManager.searchNotesAdvanced(args); // Apply field filtering if specified const filteredResults = filterSearchResults(results, args.fields); return { content: [ { type: 'text', text: JSON.stringify(filteredResults, null, 2) } ] }; };
- src/server/tool-schemas.ts:314-392 (registration)Tool registration and schema definition in the TOOL_SCHEMAS array used for MCP tool registration. Defines name, description, and complete input schema.{ name: 'search_notes_advanced', description: 'Advanced search with structured filters for metadata, dates, and content', inputSchema: { type: 'object', properties: { type: { type: 'string', description: 'Filter by note type' }, metadata_filters: { type: 'array', items: { type: 'object', properties: { key: { type: 'string', description: 'Metadata key to filter on' }, value: { type: 'string', description: 'Value to match' }, operator: { type: 'string', enum: ['=', '!=', '>', '<', '>=', '<=', 'LIKE', 'IN'], description: 'Comparison operator', default: '=' } }, required: ['key', 'value'] }, description: 'Filters for metadata fields' }, updated_within: { type: 'string', description: 'Filter notes updated within this period (e.g., "7d", "1h")' }, updated_before: { type: 'string', description: 'Filter notes updated before this date (ISO format)' }, created_within: { type: 'string', description: 'Filter notes created within this period (e.g., "7d", "1h")' }, created_before: { type: 'string', description: 'Filter notes created before this date (ISO format)' }, content_query: { type: 'string', description: 'Search within note content' }, title_query: { type: 'string', description: 'Search within note titles' }, limit: { type: 'number', description: 'Maximum number of results to return' }, vault_id: { type: 'string', description: 'Optional vault ID to search in. If not provided, uses the current active vault.' }, fields: { type: 'array', items: { type: 'string' }, description: 'Optional list of fields to include in response (id, title, content, type, filename, path, created, updated, size, metadata)' } } } },
- src/server/validation.ts:723-795 (schema)Validation rules and custom validators for search_notes_advanced tool arguments in TOOL_VALIDATION_RULES, plus dedicated validateSearchNotesAdvancedArgs function.search_notes_advanced: [ { field: 'type', required: false, type: 'string', allowEmpty: false }, { field: 'metadata_filters', required: false, type: 'array', allowEmpty: true }, { field: 'updated_within', required: false, type: 'string', allowEmpty: false }, { field: 'updated_before', required: false, type: 'string', allowEmpty: false }, { field: 'created_within', required: false, type: 'string', allowEmpty: false }, { field: 'created_before', required: false, type: 'string', allowEmpty: false }, { field: 'content_contains', required: false, type: 'string', allowEmpty: false }, { field: 'sort', required: false, type: 'array', allowEmpty: true }, { field: 'limit', required: false, type: 'number' }, { field: 'offset', required: false, type: 'number' }, { field: 'vault_id', required: false, type: 'string', allowEmpty: false }, { field: 'fields', required: false, type: 'array', arrayItemType: 'string', allowEmpty: true } ],
- Core implementation of advanced note search in HybridSearchManager. Constructs SQL queries dynamically based on filters (type, metadata, dates, content), handles pagination, FTS integration, and result formatting.async searchNotesAdvanced(options: AdvancedSearchOptions): Promise<SearchResponse> { const startTime = Date.now(); const connection = await this.getReadOnlyConnection(); try { const limit = options.limit ?? 50; const offset = options.offset ?? 0; const sql = 'SELECT DISTINCT n.*'; const countSql = 'SELECT COUNT(DISTINCT n.id) as total'; let fromClause = ' FROM notes n'; const whereConditions: string[] = []; const params: (string | number)[] = []; const joins: string[] = []; // Type filter if (options.type) { whereConditions.push('n.type = ?'); params.push(options.type); } // Metadata filters if (options.metadata_filters && options.metadata_filters.length > 0) { options.metadata_filters.forEach((filter, index) => { const alias = `m${index}`; joins.push(`JOIN note_metadata ${alias} ON n.id = ${alias}.note_id`); whereConditions.push(`${alias}.key = ?`); params.push(filter.key); const operator = filter.operator || '='; if (operator === 'IN') { const values = filter.value.split(',').map(v => v.trim()); const placeholders = values.map(() => '?').join(','); whereConditions.push(`${alias}.value IN (${placeholders})`); params.push(...values); } else { whereConditions.push(`${alias}.value ${operator} ?`); params.push(filter.value); } }); } // Date filters if (options.updated_within) { const date = this.parseDateFilter(options.updated_within); whereConditions.push('n.updated >= ?'); params.push(date); } if (options.updated_before) { const date = this.parseDateFilter(options.updated_before); whereConditions.push('n.updated <= ?'); params.push(date); } if (options.created_within) { const date = this.parseDateFilter(options.created_within); whereConditions.push('n.created >= ?'); params.push(date); } if (options.created_before) { const date = this.parseDateFilter(options.created_before); whereConditions.push('n.created <= ?'); params.push(date); } // Content search if (options.content_contains) { joins.push('JOIN notes_fts fts ON n.id = fts.id'); whereConditions.push('notes_fts MATCH ?'); params.push(options.content_contains); } // Build complete query fromClause += ' ' + joins.join(' '); if (whereConditions.length > 0) { fromClause += ' WHERE ' + whereConditions.join(' AND '); } // Add sorting let orderClause = ''; if (options.sort && options.sort.length > 0) { const sortTerms = options.sort.map( sort => `n.${sort.field} ${sort.order.toUpperCase()}` ); orderClause = ' ORDER BY ' + sortTerms.join(', '); } else { orderClause = ' ORDER BY n.updated DESC'; } // Execute count query const countResult = await connection.get<{ total: number }>( countSql + fromClause, params ); const total = countResult?.total || 0; // Execute main query const mainSql = sql + fromClause + orderClause + ' LIMIT ? OFFSET ?'; const rows = await connection.all<SearchRow>(mainSql, [...params, limit, offset]); const results = await this.convertRowsToResults(rows, connection); const queryTime = Date.now() - startTime; return { results, total, has_more: offset + results.length < total, query_time_ms: queryTime }; } catch (error) { throw new Error( `Advanced search failed: ${error instanceof Error ? error.message : 'Unknown error'}` ); } }