Skip to main content
Glama

get_feature_hub

Navigate and manage feature hubs in codebases using Obsidian-style wikilinks. List all hubs, display hub contents with linked file skeletons, or identify orphaned files not connected to any hub for better codebase organization.

Instructions

Obsidian-style feature hub navigator. Hub files are .md files containing [[path/to/file]] wikilinks that act as a Map of Content. Modes: (1) No args = list all hubs, (2) hub_path or feature_name = show hub with bundled skeletons of all linked files, (3) show_orphans = find files not linked to any hub. Prevents orphaned code and enables graph-based codebase navigation.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
hub_pathNoPath to a specific hub .md file (relative to root).
feature_nameNoFeature name to search for. Finds matching hub file automatically.
show_orphansNoIf true, lists all source files not linked to any feature hub.

Implementation Reference

  • The getFeatureHub function is the primary handler that retrieves, resolves, and displays documentation/skeleton code for a feature hub.
    export async function getFeatureHub(options: FeatureHubOptions): Promise<string> {
      const { rootDir, showOrphans } = options;
      const out: string[] = [];
    
      if (!options.hubPath && !options.featureName && !showOrphans) {
        const hubs = await discoverHubs(rootDir);
        if (hubs.length === 0) {
          return "No hub files found. Create a .md file with [[path/to/file]] links to establish a feature hub.";
        }
        out.push(`Feature Hubs (${hubs.length}):`);
        out.push("");
        for (const h of hubs) {
          const info = await parseHubFile(resolve(rootDir, h));
          out.push(`  ${h} | ${info.title} | ${info.links.length} links`);
        }
        return out.join("\n");
      }
    
      if (showOrphans) {
        const entries = await walkDirectory({ rootDir, depthLimit: 10 });
        const filePaths = entries.filter((e) => !e.isDirectory).map((e) => e.relativePath);
        const orphans = await findOrphanedFiles(rootDir, filePaths);
        if (orphans.length === 0) return "No orphaned files. All source files are linked to a hub.";
    
        out.push(`Orphaned Files (${orphans.length}):`);
        out.push("These files are not linked to any feature hub:");
        out.push("");
        for (const o of orphans) out.push(`  ⚠ ${o}`);
        out.push("");
        out.push("Fix: Add [[" + orphans[0] + "]] to the appropriate hub .md file.");
        return out.join("\n");
      }
    
      let hubRelPath = options.hubPath;
      if (!hubRelPath && options.featureName) {
        hubRelPath = (await findHubByName(rootDir, options.featureName)) ?? undefined;
        if (!hubRelPath) {
          return `No hub found for feature "${options.featureName}". Available hubs:\n` +
            (await discoverHubs(rootDir)).map((h) => `  - ${h}`).join("\n") || "  (none)";
        }
      }
    
      if (!hubRelPath) return "Provide hub_path, feature_name, or set show_orphans=true.";
    
      const hubFull = resolve(rootDir, hubRelPath);
      if (!(await fileExists(hubFull))) {
        return `Hub file not found: ${hubRelPath}`;
      }
    
      const hub = await parseHubFile(hubFull);
    
      out.push(`Hub: ${hub.title}`);
      out.push(`Path: ${hubRelPath}`);
      out.push(`Links: ${hub.links.length}`);
      if (hub.crossLinks.length > 0) {
        out.push(`Cross-links: ${hub.crossLinks.map((c) => c.hubName).join(", ")}`);
      }
      out.push("");
      out.push("---");
      out.push("");
    
      const resolved: string[] = [];
      const missing: string[] = [];
    
      for (const link of hub.links) {
        const linkFull = resolve(rootDir, link.target);
        if (await fileExists(linkFull)) {
          resolved.push(link.target);
        } else {
          missing.push(link.target);
        }
      }
    
      for (const filePath of resolved) {
        const ext = extname(filePath);
        const desc = hub.links.find((l) => l.target === filePath)?.description;
    
        if (desc) out.push(`## ${filePath} - ${desc}`);
        else out.push(`## ${filePath}`);
    
        try {
          const skeleton = await getFileSkeleton({ rootDir, filePath });
          out.push(skeleton);
        } catch {
          const content = await readFile(resolve(rootDir, filePath), "utf-8");
          out.push(content.split("\n").slice(0, 20).join("\n"));
        }
        out.push("");
      }
    
      if (missing.length > 0) {
        out.push("---");
        out.push(`Missing Links (${missing.length}):`);
        for (const m of missing) out.push(`  ✗ ${m}`);
      }
    
      return out.join("\n");
    }
  • Input parameters for the getFeatureHub tool.
    export interface FeatureHubOptions {
      rootDir: string;
      hubPath?: string;
      featureName?: string;
      showOrphans?: boolean;
    }
Behavior3/5

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

With no annotations provided, the description must carry the full burden of behavioral disclosure. It adds valuable context that the tool returns 'bundled skeletons of all linked files' and identifies orphaned files, but omits critical safety information (read-only vs. destructive), error handling for invalid hub paths, or performance characteristics for large codebases.

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 efficiently structured and front-loaded: it opens with the navigation concept, defines the resource (hub files), enumerates the three modes with clear numbering, and closes with value propositions. Every sentence serves a distinct purpose without redundancy.

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 three well-documented parameters (100% schema coverage) but no output schema, the description adequately explains the tool's scope and mentions 'bundled skeletons' to hint at return format. It covers the three operational modes comprehensively, though it could explicitly describe the return structure or read-only nature given the lack of output schema and annotations.

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?

With 100% schema description coverage, the baseline is 3. The description adds significant value by explaining how the three parameters create distinct modes of operation (listing vs. specific retrieval vs. orphan detection), and clarifies that hub_path and feature_name are alternative ways to target a specific hub.

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 defines the tool as an 'Obsidian-style feature hub navigator' and specifies it operates on '.md files containing [[path/to/file]] wikilinks.' It distinguishes from siblings like get_context_tree or semantic_navigate by emphasizing the specific 'Map of Content' pattern and graph-based navigation approach.

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

Usage Guidelines4/5

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

The description explicitly outlines three distinct usage modes: (1) no arguments to list all hubs, (2) hub_path or feature_name to show a specific hub with bundled skeletons, and (3) show_orphans to find unlinked files. While it maps parameters to behaviors clearly, it lacks explicit guidance on when to use this versus alternatives like get_context_tree or retrieve_with_traversal.

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/ForLoopCodes/contextplus'

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