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

@@ -72,11 +72,7 @@
}, },
{ {
"name": "obsidian_search", "name": "obsidian_search",
"description": "Search for notes in the vault by content. Returns matching files with optional context snippets. Supports case-sensitive search and folder filtering." "description": "Search vault for text. Returns matching files and optionally match counts. Supports path filtering, result limits, case sensitivity, and multiple output formats (text/json)."
},
{
"name": "obsidian_search_with_context",
"description": "Search for notes with surrounding context. Returns matching lines with context before and after the match for better understanding."
}, },
{ {
"name": "obsidian_get_backlinks", "name": "obsidian_get_backlinks",

View File

@@ -108,8 +108,8 @@
### Implementation for User Story 2 ### Implementation for User Story 2
- [X] T046 [P] [US2] Create obsidian_search tool in src/tools/search.ts - [X] T046 [P] [US2] Create obsidian_search tool in src/tools/search.ts
- [X] T047 [P] [US2] Define Zod schema for search parameters (query, folder, limit, caseSensitive) - [X] T047 [P] [US2] Define Zod schema for search parameters (query, path, limit, case, total)
- [X] T048 [P] [US2] Create obsidian_search_with_context tool in src/tools/search.ts - [X] T048 [P] [US2] REMOVED - Search with context (not in Obsidian CLI spec)
- [X] T049 [P] [US2] Create obsidian_get_backlinks tool in src/tools/links.ts - [X] T049 [P] [US2] Create obsidian_get_backlinks tool in src/tools/links.ts
- [X] T050 [P] [US2] Define Zod schema for backlinks parameters (file/path, counts) - [X] T050 [P] [US2] Define Zod schema for backlinks parameters (file/path, counts)
- [X] T051 [P] [US2] Create obsidian_get_outgoing_links tool in src/tools/links.ts - [X] T051 [P] [US2] Create obsidian_get_outgoing_links tool in src/tools/links.ts

View File

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

View File

@@ -20,20 +20,26 @@ export async function registerSearchTools(server: ObsidianMCPServer): Promise<vo
// T046: Search tool // T046: Search tool
server.registerTool( server.registerTool(
'obsidian_search', '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: {} }, { type: 'object', properties: {} },
createToolHandler( createToolHandler(
'Search for notes by content', 'Search vault for text',
{ type: 'object', properties: {} }, { type: 'object', properties: {} },
async (args) => { async (args) => {
const validated = searchSchema.parse(args) as any; const validated = searchSchema.parse(args) as any;
const sanitized = sanitizeParameters(validated) as any; const sanitized = sanitizeParameters(validated) as any;
const cmdArgs: string[] = ['search', sanitized.query as string]; const cmdArgs: string[] = ['search'];
if (sanitized.folder) cmdArgs.push('--folder', sanitized.folder as string);
if (sanitized.limit) cmdArgs.push('--limit', String(sanitized.limit)); // Add query parameter
if (sanitized.caseSensitive) cmdArgs.push('--case-sensitive'); cmdArgs.push(`query=${sanitized.query as string}`);
if (sanitized.format) cmdArgs.push('--format', sanitized.format 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); const result = await executeObsidianCommand('search', cmdArgs);
handleCLIResult(result, { operation: 'search', query: sanitized.query }); handleCLIResult(result, { operation: 'search', query: sanitized.query });
@@ -53,37 +59,5 @@ export async function registerSearchTools(server: ObsidianMCPServer): Promise<vo
) )
); );
// T048: Search with context tool logger.info('Search tools registered', { count: 1 });
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 });
} }

View File

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