find_broken_links
Identifies broken wikilinks in markdown notes that reference non-existent files. Optionally narrows search to a specific directory scope.
Instructions
Finds wikilinks pointing to non-existent notes. Optional { scope } path prefix. Returns { root, scope, brokenLinks[] } with sourcePath, link, line.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
No arguments | |||
Implementation Reference
- src/tools/link-tools.ts:104-138 (handler)The tool handler function that defines the 'find_broken_links' MCP tool. It parses args (optional scope), delegates to services.links.findBrokenLinks(), and returns the result as JSON.
function makeFindBrokenLinksTool(container: ServiceContainer): ToolHandler { return { name: "find_broken_links", description: "Finds wikilinks pointing to non-existent notes. Optional `{ scope }` path prefix. Returns `{ root, scope, brokenLinks[] }` with `sourcePath`, `link`, `line`.", inputSchema: FindBrokenLinksSchema, async handler(args): Promise<ToolResponse> { try { const services = requireServices(container); const { scope } = FindBrokenLinksSchema.parse(args); log.info({ scope }, "find_broken_links called"); const brokenLinks = await services.links.findBrokenLinks(scope); log.info({ scope, count: brokenLinks.length }, "find_broken_links complete"); return { content: [ { type: "text", text: JSON.stringify({ root: getRoot(container), scope: scope ?? null, brokenLinks }), }, ], }; } catch (err) { log.error({ err }, "find_broken_links failed"); return { content: [{ type: "text", text: JSON.stringify({ root: getRoot(container), error: err instanceof Error ? err.message : String(err), possibleSolutions: ["Check the scope path is root-relative", "Omit scope to scan the entire directory"], }) }], isError: true, }; } }, }; } - src/tools/link-tools.ts:100-102 (schema)Zod schema for find_broken_links input validation. Takes an optional 'scope' string to filter which files to scan.
const FindBrokenLinksSchema = z.object({ scope: z.string().optional(), }); - src/tools/link-tools.ts:257-272 (registration)Registration function that adds the find_broken_links tool (via makeFindBrokenLinksTool) to the tool registry map.
export function registerLinkTools( registry: Map<string, ToolHandler>, container: ServiceContainer, ): void { const tools = [ makeGetBacklinksTool(container), makeFindUnlinkedMentionsTool(container), makeFindBrokenLinksTool(container), makeFindOrphansTool(container), makeFindBidirectionalMentionsTool(container), ]; for (const tool of tools) { registry.set(tool.name, tool); } } - src/tools/index.ts:38-54 (registration)Allowlist of lite-mode tools; 'find_broken_links' is listed so it remains available in --lite mode.
export const LITE_TOOL_NAMES: ReadonlySet<string> = new Set([ // Schema / lint "lint_note", "validate_folder", "validate_area", "validate_all", "list_schemas", // Link graph "find_broken_links", "find_orphans", "find_unlinked_mentions", "find_bidirectional_mentions", "get_backlinks", // Meta "switch_directory", "get_stats", ]); - src/services/link-engine.ts:159-184 (helper)Core service implementation of findBrokenLinks. Collects files (optionally scoped), builds a set of existing stems, then scans all wikilinks and reports any whose target stem doesn't exist in the set.
async findBrokenLinks(scope?: string): Promise<BrokenLink[]> { log.info({ scope }, "findBrokenLinks"); const brokenLinks: BrokenLink[] = []; const files = await this.collectFiles(scope); const existingStems = this.buildStemSet(files); for (const filePath of files) { const content = await this.readFileContent(filePath); if (!content) continue; for (const scanned of scanWikilinks(content)) { const stem = this.stemFromTarget(scanned.target); if (!existingStems.has(stem)) { brokenLinks.push({ sourcePath: filePath, link: { raw: scanned.raw, target: scanned.target, display: scanned.display, section: scanned.section }, line: scanned.line, }); } } } log.info({ scope, brokenCount: brokenLinks.length }, "findBrokenLinks complete"); return brokenLinks; }