search_by_tag
Search for notes containing a specific tag in Obsidian vaults, with options to preview content and limit results.
Instructions
Find all notes that contain a specific tag
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| tag | Yes | Tag to search for (with or without # prefix) | |
| includeContent | No | If true, include first 200 characters of note content as a preview | |
| maxResults | No | Maximum number of results |
Implementation Reference
- src/tools/tags.ts:97-156 (handler)The handler function that implements the logic for 'search_by_tag', including searching for tags in note content and returning the matching results.
async ({ tag, includeContent, maxResults }) => { try { const searchTag = tag.replace(/^#/, "").toLowerCase(); const notes = await listNotes(vaultPath); const matchingNotes: { path: string; preview?: string }[] = []; for (const notePath of notes) { if (matchingNotes.length >= maxResults) break; let content: string; try { content = await readNote(vaultPath, notePath); } catch (err) { console.error(`Failed to read note for tag search: ${notePath}`, err); continue; } const tags = extractTags(content); const hasMatch = tags.some((t) => { const normalized = t.toLowerCase(); return normalized === searchTag || normalized.startsWith(`${searchTag}/`); }); if (hasMatch) { const entry: { path: string; preview?: string } = { path: notePath }; if (includeContent) { entry.preview = content.slice(0, 200).trim(); } matchingNotes.push(entry); } } if (matchingNotes.length === 0) { return { content: [ { type: "text" as const, text: `No notes found with tag #${searchTag}` }, ], }; } const lines: string[] = []; lines.push(`Found ${matchingNotes.length} ${matchingNotes.length === 1 ? "note" : "notes"} with tag #${searchTag}`); lines.push(""); for (const note of matchingNotes) { lines.push(`- ${note.path}`); if (note.preview) { lines.push(` ${note.preview}`); lines.push(""); } } return { content: [{ type: "text" as const, text: lines.join("\n") }], }; } catch (err) { console.error("Error in search_by_tag:", err); return errorResult(`Error searching by tag: ${err instanceof Error ? err.message : String(err)}`); } }, - src/tools/tags.ts:85-96 (schema)The schema definition (inputSchema) for the 'search_by_tag' tool.
{ description: "Find all notes that contain a specific tag", inputSchema: { tag: z.string().describe("Tag to search for (with or without # prefix)"), includeContent: z .boolean() .optional() .default(false) .describe("If true, include first 200 characters of note content as a preview"), maxResults: z.number().optional().default(100).describe("Maximum number of results"), }, }, - src/tools/tags.ts:83-157 (registration)The registration call for the 'search_by_tag' tool within the McpServer instance.
server.registerTool( "search_by_tag", { description: "Find all notes that contain a specific tag", inputSchema: { tag: z.string().describe("Tag to search for (with or without # prefix)"), includeContent: z .boolean() .optional() .default(false) .describe("If true, include first 200 characters of note content as a preview"), maxResults: z.number().optional().default(100).describe("Maximum number of results"), }, }, async ({ tag, includeContent, maxResults }) => { try { const searchTag = tag.replace(/^#/, "").toLowerCase(); const notes = await listNotes(vaultPath); const matchingNotes: { path: string; preview?: string }[] = []; for (const notePath of notes) { if (matchingNotes.length >= maxResults) break; let content: string; try { content = await readNote(vaultPath, notePath); } catch (err) { console.error(`Failed to read note for tag search: ${notePath}`, err); continue; } const tags = extractTags(content); const hasMatch = tags.some((t) => { const normalized = t.toLowerCase(); return normalized === searchTag || normalized.startsWith(`${searchTag}/`); }); if (hasMatch) { const entry: { path: string; preview?: string } = { path: notePath }; if (includeContent) { entry.preview = content.slice(0, 200).trim(); } matchingNotes.push(entry); } } if (matchingNotes.length === 0) { return { content: [ { type: "text" as const, text: `No notes found with tag #${searchTag}` }, ], }; } const lines: string[] = []; lines.push(`Found ${matchingNotes.length} ${matchingNotes.length === 1 ? "note" : "notes"} with tag #${searchTag}`); lines.push(""); for (const note of matchingNotes) { lines.push(`- ${note.path}`); if (note.preview) { lines.push(` ${note.preview}`); lines.push(""); } } return { content: [{ type: "text" as const, text: lines.join("\n") }], }; } catch (err) { console.error("Error in search_by_tag:", err); return errorResult(`Error searching by tag: ${err instanceof Error ? err.message : String(err)}`); } }, );