refactor: update search tool to match Obsidian CLI spec

Removed obsidian_search_with_context tool (not in CLI spec)
Updated obsidian_search to use exact CLI parameter names:
- query (required) - Search query text
- path (optional) - Limit search to folder path
- limit (optional) - Max number of files to return
- total (optional) - Return match count instead of file list
- case (optional) - Case sensitive search
- format (optional) - Output format: text or json (default: text)

Changed parameter names to match CLI:
- folder → path
- caseSensitive → case
- Added: total flag for match counts
- Removed: contextLines (not in CLI)

Files updated:
- src/tools/search.ts: Simplified to single search tool
- src/validation/schemas.ts: Updated searchSchema parameters
- manifest.json: Removed search_with_context, updated description
- tasks.md: Marked T048 as REMOVED

Total tools: 20 (was 21)
- User Story 1: 9 tools
- User Story 2: 11 tools (was 12)

Build:  0 errors
Validation:  Manifest passes

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-03-22 12:26:17 -05:00
parent a978d70b3f
commit c577c07877
5 changed files with 22 additions and 52 deletions

View File

@@ -110,7 +110,7 @@ export async function executeObsidianCommand(
const fullArgs = [subcommand, '--vault', vaultName, ...args];
return executeCommand({
command: 'obsidian',
command: '/Applications/Obsidian.app/Contents/MacOS/obsidian',
args: fullArgs,
timeout: options?.timeout,
});

View File

@@ -20,20 +20,26 @@ export async function registerSearchTools(server: ObsidianMCPServer): Promise<vo
// T046: Search tool
server.registerTool(
'obsidian_search',
'Search for notes in the vault by content. Returns matching files with optional context snippets. Supports case-sensitive search and folder filtering.',
'Search vault for text. Returns matching files and optionally match counts. Supports path filtering, result limits, case sensitivity, and multiple output formats (text/json).',
{ type: 'object', properties: {} },
createToolHandler(
'Search for notes by content',
'Search vault for text',
{ type: 'object', properties: {} },
async (args) => {
const validated = searchSchema.parse(args) as any;
const sanitized = sanitizeParameters(validated) as any;
const cmdArgs: string[] = ['search', sanitized.query as string];
if (sanitized.folder) cmdArgs.push('--folder', sanitized.folder as string);
if (sanitized.limit) cmdArgs.push('--limit', String(sanitized.limit));
if (sanitized.caseSensitive) cmdArgs.push('--case-sensitive');
if (sanitized.format) cmdArgs.push('--format', sanitized.format as string);
const cmdArgs: string[] = ['search'];
// Add query parameter
cmdArgs.push(`query=${sanitized.query as string}`);
// Add optional parameters
if (sanitized.path) cmdArgs.push(`path=${sanitized.path as string}`);
if (sanitized.limit) cmdArgs.push(`limit=${sanitized.limit}`);
if (sanitized.total) cmdArgs.push('total');
if (sanitized.case) cmdArgs.push('case');
if (sanitized.format) cmdArgs.push(`format=${sanitized.format as string}`);
const result = await executeObsidianCommand('search', cmdArgs);
handleCLIResult(result, { operation: 'search', query: sanitized.query });
@@ -53,37 +59,5 @@ export async function registerSearchTools(server: ObsidianMCPServer): Promise<vo
)
);
// T048: Search with context tool
server.registerTool(
'obsidian_search_with_context',
'Search for notes with surrounding context. Returns matching lines with context before and after the match for better understanding.',
{ type: 'object', properties: {} },
createToolHandler(
'Search with context snippets',
{ type: 'object', properties: {} },
async (args) => {
const validated = searchSchema.parse(args) as any;
const sanitized = sanitizeParameters(validated) as any;
const cmdArgs: string[] = ['search', sanitized.query as string, '--context'];
if (sanitized.folder) cmdArgs.push('--folder', sanitized.folder as string);
if (sanitized.limit) cmdArgs.push('--limit', String(sanitized.limit));
if (sanitized.contextLines) cmdArgs.push('--context-lines', String(sanitized.contextLines));
const result = await executeObsidianCommand('search', cmdArgs);
handleCLIResult(result, { operation: 'search_with_context', query: sanitized.query });
return {
content: [
{
type: 'text',
text: formatForMCP(result.stdout, 'text'),
},
],
};
}
)
);
logger.info('Search tools registered', { count: 2 });
logger.info('Search tools registered', { count: 1 });
}

View File

@@ -144,11 +144,11 @@ export const moveRenameSchema = z.intersection(
// Search parameters
export const searchSchema = z.object({
query: z.string().min(1, 'Search query cannot be empty'),
folder: optionalStringSchema,
path: optionalStringSchema, // Folder path to limit search
limit: z.number().int().positive().max(1000).optional(),
caseSensitive: booleanFlagSchema.optional(),
contextLines: z.number().int().positive().max(10).optional(),
...formatSchema.shape,
total: booleanFlagSchema.optional(), // Return match count instead of files
case: booleanFlagSchema.optional(), // Case sensitive search
format: z.enum(['text', 'json']).optional().default('text'),
});
// Tag search parameters