Skip to main content
Glama

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

TableJSON Schema
NameRequiredDescriptionDefault

No arguments

Implementation Reference

  • 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,
            };
          }
        },
      };
    }
  • 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(),
    });
  • 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);
      }
    }
  • 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",
    ]);
  • 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;
    }
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description discloses the return structure (root, scope, brokenLinks) and that it accepts an optional scope path prefix. No annotations exist, so the description carries the burden. It does not mention side effects or scope of search (e.g., entire vault vs. directory), but the return includes root and scope, hinting at the search scope.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two concise sentences covering purpose, optional parameter, and return structure. No extraneous words.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's simplicity (one optional parameter, no output schema), the description adequately covers purpose, parameter, and output format. It could be slightly more explicit about the scope of the search, but it remains sufficient.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The only parameter, scope, is fully described in the schema as an optional string. The description adds value by clarifying it is a 'path prefix', which explains its purpose beyond the schema definition.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states it finds wikilinks pointing to non-existent notes, distinguishing it from siblings like find_orphans and find_unlinked_mentions. The verb 'finds' and resource 'broken links' are specific.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage for finding broken links but does not explicitly state when to use this tool versus alternatives like find_orphans or find_unlinked_mentions. No exclusions or prerequisites are provided.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Erodenn/markscribe'

If you have feedback or need assistance with the MCP directory API, please join our Discord server