find_broken_links
Identify wikilinks pointing to missing notes in your Obsidian vault to maintain content integrity.
Instructions
Find all wikilinks that point to non-existent notes
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| folder | No | Limit scan to a specific folder | |
| maxResults | No | Maximum results to return |
Implementation Reference
- src/tools/links.ts:393-479 (handler)The handler for the 'find_broken_links' tool, which scans notes for wikilinks that do not resolve to an existing note.
async ({ folder, maxResults }) => { try { // Get all notes in vault for resolution, but only scan the folder const allNotes = await listNotes(vaultPath); const scanNotes = folder ? await listNotes(vaultPath, folder) : allNotes; const brokenBySource = new Map<string, BrokenLink[]>(); for (const notePath of scanNotes) { let content: string; try { content = await readNote(vaultPath, notePath); } catch { console.error(`Failed to read note for broken link scan: ${notePath}`); continue; } const lines = content.split("\n"); const links = extractWikilinks(content); for (const link of links) { const targetBase = link.target.split("#")[0].trim(); if (!targetBase) continue; const resolved = resolveWikilink(targetBase, notePath, allNotes); if (!resolved) { const lineInfo = findLineWithLink(lines, link.target); const broken: BrokenLink = { sourcePath: notePath, targetLink: link.target, line: lineInfo.line, }; if (!brokenBySource.has(notePath)) { brokenBySource.set(notePath, []); } brokenBySource.get(notePath)!.push(broken); } } } if (brokenBySource.size === 0) { const scopeStr = folder ? ` in folder: ${folder}` : ""; return { content: [ { type: "text" as const, text: `No broken links found${scopeStr}`, }, ], }; } let totalBroken = 0; for (const brokenLinks of brokenBySource.values()) { totalBroken += brokenLinks.length; } const lines: string[] = []; const scopeStr = folder ? ` (folder: ${folder})` : ""; lines.push(`Broken links report${scopeStr}\n`); let shown = 0; for (const [sourcePath, brokenLinks] of brokenBySource) { if (shown >= maxResults) break; lines.push(`${sourcePath}:`); for (const bl of brokenLinks) { if (shown >= maxResults) break; const lineStr = bl.line > 0 ? ` (line ${bl.line})` : ""; lines.push(` - [[${bl.targetLink}]]${lineStr}`); shown++; } lines.push(""); } if (shown < totalBroken) { lines.push(`... and ${totalBroken - shown} more broken link(s) not shown`); } lines.push(`Total: ${totalBroken} broken link(s) across ${brokenBySource.size} file(s)`); return { content: [{ type: "text" as const, text: lines.join("\n") }] }; } catch (err) { console.error("find_broken_links error:", err); return errorResult(`Error finding broken links: ${err instanceof Error ? err.message : String(err)}`); } }, ); - src/tools/links.ts:376-392 (registration)Registration of the 'find_broken_links' tool in the MCP server, defining its description and input schema.
// ── find_broken_links ────────────────────────────────────────── server.registerTool( "find_broken_links", { description: "Find all wikilinks that point to non-existent notes", inputSchema: { folder: z .string() .optional() .describe("Limit scan to a specific folder"), maxResults: z .number() .optional() .default(200) .describe("Maximum results to return"), }, },