/**
* Search Tools - Simple and advanced search in Obsidian vault
*/
import { z } from 'zod';
import { makeRequest } from '../utils/api-client.js';
import {
SimpleSearchResult,
AdvancedSearchResult,
DATAVIEW_DQL_CONTENT_TYPE,
JSONLOGIC_CONTENT_TYPE,
} from '../types/obsidian.js';
// Schema definitions for tool parameters
export const simpleSearchSchema = z.object({
query: z.string().describe('Text to search for in the vault'),
contextLength: z.number().optional()
.describe('Number of characters of context to return around each match (default: 100)'),
});
export const advancedSearchSchema = z.object({
queryType: z.enum(['dataview', 'jsonlogic']).describe('Type of search query: "dataview" for Dataview DQL, or "jsonlogic" for JsonLogic queries'),
query: z.union([
z.string().describe('Dataview DQL query string (e.g., "TABLE FROM #tag WHERE field = value")'),
z.object({}).passthrough().describe('JsonLogic query object'),
]).describe('The search query - either a Dataview DQL string or JsonLogic object'),
});
/**
* Perform a simple text search
*/
export async function simpleSearch(args: z.infer<typeof simpleSearchSchema>): Promise<string> {
const { query, contextLength = 100 } = args;
const params = new URLSearchParams({
query,
contextLength: contextLength.toString(),
});
const response = await makeRequest<SimpleSearchResult[]>({
method: 'POST',
path: `/search/simple/?${params.toString()}`,
});
if (!response.success) {
return `Error: ${response.error || `Search failed (HTTP ${response.status})`}`;
}
if (!response.data || response.data.length === 0) {
return 'No matches found.';
}
// Format the output
let output = `Found ${response.data.length} matching file(s):\n\n`;
for (const result of response.data) {
output += `π ${result.filename} (score: ${result.score.toFixed(2)})\n`;
if (result.matches && result.matches.length > 0) {
for (const match of result.matches) {
output += ` β ${match.context}\n`;
}
}
output += '\n';
}
return output;
}
/**
* Perform an advanced search using Dataview DQL or JsonLogic
*/
export async function advancedSearch(args: z.infer<typeof advancedSearchSchema>): Promise<string> {
const { queryType, query } = args;
let contentType: string;
let body: string;
if (queryType === 'dataview') {
contentType = DATAVIEW_DQL_CONTENT_TYPE;
body = query as string;
} else {
contentType = JSONLOGIC_CONTENT_TYPE;
body = JSON.stringify(query);
}
const response = await makeRequest<AdvancedSearchResult[]>({
method: 'POST',
path: '/search/',
body,
contentType,
});
if (!response.success) {
return `Error: ${response.error || `Search failed (HTTP ${response.status})`}`;
}
if (!response.data || response.data.length === 0) {
return 'No matches found.';
}
// Format the output
let output = `Found ${response.data.length} matching file(s):\n\n`;
for (const result of response.data) {
output += `π ${result.filename}\n`;
if (result.result !== undefined) {
output += ` Result: ${JSON.stringify(result.result, null, 2)}\n`;
}
output += '\n';
}
return output;
}
// Tool definitions for MCP server registration
export const searchTools = [
{
name: 'search_simple',
description: 'FIND or SEARCH for text across all files in the vault. Performs a simple text search and returns matching files with context around each match. Use this tool to FIND specific text, SEARCH for keywords, or LOCATE mentions of topics across the entire vault. Best for straightforward text searches when you know what you\'re looking for. NEXT STEPS: After finding files, use vault_get_file to read the full content of specific files. FOR FRONTMATTER/TAG SEARCHES: Use search_advanced instead for metadata queries. Workflow: search_simple β vault_get_file.',
inputSchema: simpleSearchSchema,
handler: simpleSearch,
},
{
name: 'search_advanced',
description: 'QUERY, FILTER, or SEARCH using advanced query languages. Supports Dataview DQL for querying metadata and frontmatter (e.g., "TABLE FROM #tag WHERE field = value") or JsonLogic for complex logical queries on note properties. Use this tool when you need to QUERY notes by tags, FILTER by frontmatter values, or perform complex searches that go beyond simple text matching. NEXT STEPS: After finding files, use vault_get_file to read the full content of specific files. FOR SIMPLE TEXT SEARCHES: Use search_simple instead for basic text matching. Workflow: search_advanced β vault_get_file.',
inputSchema: advancedSearchSchema,
handler: advancedSearch,
},
];