Skip to main content
Glama
houtini-ai

Fan Out Query MCP

by houtini-ai

analyze_content_gap

Identify content coverage gaps by analyzing URLs against user queries using Query Decomposition and Self-RAG techniques to reveal missing information.

Instructions

Perform advanced content gap analysis using Query Decomposition and Self-RAG techniques. Analyzes a URL to identify what user queries the content covers and what gaps exist.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
urlYesURL of the content to analyze
depthNoAnalysis depth: quick (5 queries), standard (15 queries), comprehensive (30 queries)standard
focus_areaNoOptional focus area for query generation (e.g., 'pricing', 'installation')
target_keywordNoEnable keyword fan-out mode: generates query variants based on Google's methodology
fan_out_typesNoWhich variant types to generate (default: equivalent, specification, followUp, comparison, clarification)
fan_out_onlyNoSkip content inference, only generate keyword variants
contextNoOptional context signals for variant generation

Implementation Reference

  • Main handler function that executes the content gap analysis tool logic. It fetches content from a URL, decomposes queries, assesses coverage, generates keyword variants if requested, and formats the final report.
    export async function analyzeContentGap(
      args: z.infer<typeof AnalyzeContentGapSchema>
    ): Promise<string> {
      const apiKey = process.env.ANTHROPIC_API_KEY;
      if (!apiKey) {
        throw new Error("ANTHROPIC_API_KEY environment variable is required");
      }
    
      const {
        url,
        depth,
        focus_area,
        target_keyword,
        fan_out_types,
        fan_out_only,
        context,
      } = args;
    
      const fetcher = new ContentFetcher();
      const decomposer = new QueryDecomposer(apiKey);
      const assessor = new CoverageAssessor(apiKey);
      const formatter = new ReportFormatter();
      const keywordFanOut = target_keyword ? new KeywordFanOut(apiKey) : null;
    
      try {
        const startTime = Date.now();
    
        const fetchStart = Date.now();
        const content = await fetcher.fetchContent(url);
        const fetchTime = Date.now() - fetchStart;
    
        let contentQueries: QueryGraph = {
          prerequisite: [],
          core: [],
          followup: [],
        };
        let fanOutQueries: FanOutQuery[] = [];
        let queryTime = 0;
        let fanOutTime = 0;
    
        if (!fan_out_only) {
          const queryStart = Date.now();
          contentQueries = await decomposer.decomposeQueries(
            content,
            depth as AnalysisDepth,
            focus_area
          );
          queryTime = Date.now() - queryStart;
        }
    
        if (target_keyword && keywordFanOut) {
          const fanOutStart = Date.now();
          const types =
            (fan_out_types as FanOutVariantType[]) ||
            ([
              "equivalent",
              "specification",
              "followUp",
              "comparison",
              "clarification",
            ] as FanOutVariantType[]);
    
          fanOutQueries = await keywordFanOut.generateVariants(
            target_keyword,
            content,
            types,
            context as AnalysisContext | undefined
          );
          fanOutTime = Date.now() - fanOutStart;
        }
    
        const combinedGraph = mergeQueryGraphs(
          contentQueries,
          fanOutQueries,
          target_keyword
        );
    
        const assessStart = Date.now();
        const assessments = await assessor.assessCoverage(content, combinedGraph);
        const assessTime = Date.now() - assessStart;
    
        const totalTime = Date.now() - startTime;
    
        const fanOutMetadata: FanOutMetadata | undefined = target_keyword
          ? {
              mode: fan_out_only
                ? "keyword-only"
                : target_keyword
                ? "hybrid"
                : "content-only",
              targetKeyword: target_keyword,
              variantCounts: countVariantsByType(fanOutQueries),
              totalVariants: fanOutQueries.length,
              generationTime: fanOutTime,
            }
          : undefined;
    
        const report = formatter.formatReport(content, combinedGraph, assessments, {
          fetchTime,
          queryTime,
          assessTime,
          totalTime,
        });
    
        return report;
      } catch (error) {
        if (error instanceof Error) {
          throw new Error(`Content gap analysis failed: ${error.message}`);
        }
        throw new Error("Content gap analysis failed with unknown error");
      }
    }
  • Zod schema definition for validating tool input parameters including url, depth, focus_area, target_keyword, fan_out_types, fan_out_only, and context options.
    const AnalyzeContentGapSchema = z.object({
      url: z.string().url("Must be a valid URL"),
      depth: z
        .enum(["quick", "standard", "comprehensive"])
        .optional()
        .default("standard"),
      focus_area: z.string().optional(),
    
      target_keyword: z
        .string()
        .optional()
        .describe(
          "Enable keyword fan-out mode: generates query variants based on Google's methodology"
        ),
      fan_out_types: z
        .array(
          z.enum([
            "equivalent",
            "specification",
            "generalization",
            "followUp",
            "comparison",
            "clarification",
            "relatedAspects",
            "temporal",
          ])
        )
        .optional()
        .describe(
          "Which variant types to generate (default: equivalent, specification, followUp, comparison, clarification)"
        ),
      fan_out_only: z
        .boolean()
        .optional()
        .default(false)
        .describe("Skip content inference, only generate keyword variants"),
    
      context: z
        .object({
          temporal: z
            .object({
              currentDate: z.string().optional(),
              season: z.string().optional(),
            })
            .optional(),
          intent: z
            .enum(["shopping", "research", "navigation", "entertainment"])
            .optional(),
          specificity_preference: z
            .enum(["broad", "specific", "balanced"])
            .optional(),
        })
        .optional(),
    });
  • src/index.ts:26-116 (registration)
    Tool registration with ListToolsRequestSchema handler defining the tool name 'analyze_content_gap', description, and JSON Schema inputSchema for the MCP protocol.
    server.setRequestHandler(ListToolsRequestSchema, async () => {
      return {
        tools: [
          {
            name: "analyze_content_gap",
            description:
              "Perform advanced content gap analysis using Query Decomposition and Self-RAG techniques. Analyzes a URL to identify what user queries the content covers and what gaps exist.",
            inputSchema: {
              type: "object",
              properties: {
                url: {
                  type: "string",
                  description: "URL of the content to analyze",
                },
                depth: {
                  type: "string",
                  enum: ["quick", "standard", "comprehensive"],
                  description:
                    "Analysis depth: quick (5 queries), standard (15 queries), comprehensive (30 queries)",
                  default: "standard",
                },
                focus_area: {
                  type: "string",
                  description:
                    "Optional focus area for query generation (e.g., 'pricing', 'installation')",
                },
                target_keyword: {
                  type: "string",
                  description:
                    "Enable keyword fan-out mode: generates query variants based on Google's methodology",
                },
                fan_out_types: {
                  type: "array",
                  items: {
                    type: "string",
                    enum: [
                      "equivalent",
                      "specification",
                      "generalization",
                      "followUp",
                      "comparison",
                      "clarification",
                      "relatedAspects",
                      "temporal",
                    ],
                  },
                  description:
                    "Which variant types to generate (default: equivalent, specification, followUp, comparison, clarification)",
                },
                fan_out_only: {
                  type: "boolean",
                  description:
                    "Skip content inference, only generate keyword variants",
                  default: false,
                },
                context: {
                  type: "object",
                  properties: {
                    temporal: {
                      type: "object",
                      properties: {
                        currentDate: {
                          type: "string",
                          description: "Current date in YYYY-MM-DD format",
                        },
                        season: {
                          type: "string",
                          description: "Current season (winter, spring, summer, fall)",
                        },
                      },
                    },
                    intent: {
                      type: "string",
                      enum: ["shopping", "research", "navigation", "entertainment"],
                      description: "User intent for contextual variant generation",
                    },
                    specificity_preference: {
                      type: "string",
                      enum: ["broad", "specific", "balanced"],
                      description: "Preferred level of query specificity",
                    },
                  },
                  description: "Optional context signals for variant generation",
                },
              },
              required: ["url"],
            },
          },
        ],
      };
    });
  • src/index.ts:118-167 (registration)
    CallToolRequestSchema handler that routes 'analyze_content_gap' tool calls to the analyzeContentGap function, validates arguments with the schema, and returns the formatted result.
    server.setRequestHandler(CallToolRequestSchema, async (request) => {
      const { name, arguments: args } = request.params;
    
      if (name === "analyze_content_gap") {
        try {
          const validatedArgs = AnalyzeContentGapSchema.parse(args);
          const result = await analyzeContentGap(validatedArgs);
    
          return {
            content: [
              {
                type: "text",
                text: result,
              },
            ],
          };
        } catch (error) {
          if (error instanceof Error) {
            return {
              content: [
                {
                  type: "text",
                  text: `Error: ${error.message}`,
                },
              ],
              isError: true,
            };
          }
          return {
            content: [
              {
                type: "text",
                text: "An unknown error occurred",
              },
            ],
            isError: true,
          };
        }
      }
    
      return {
        content: [
          {
            type: "text",
            text: `Unknown tool: ${name}`,
          },
        ],
        isError: true,
      };
    });
  • Helper functions mergeQueryGraphs and countVariantsByType that support the main handler by combining query graphs and counting variant types.
    function mergeQueryGraphs(
      contentQueries: QueryGraph,
      fanOutQueries: FanOutQuery[],
      keyword?: string
    ): EnhancedQueryGraph {
      const enhanced: EnhancedQueryGraph = {
        ...contentQueries,
        targetKeyword: keyword,
      };
    
      if (fanOutQueries.length > 0) {
        enhanced.fanOutVariants = {};
    
        for (const query of fanOutQueries) {
          const type = query.variantType;
          if (!enhanced.fanOutVariants[type]) {
            enhanced.fanOutVariants[type] = [];
          }
          enhanced.fanOutVariants[type]!.push(query);
        }
    
        const variantDistribution: Record<FanOutVariantType, number> = {
          equivalent: 0,
          specification: 0,
          generalization: 0,
          followUp: 0,
          comparison: 0,
          clarification: 0,
          relatedAspects: 0,
          temporal: 0,
        };
    
        for (const query of fanOutQueries) {
          variantDistribution[query.variantType]++;
        }
    
        enhanced.generationMetadata = {
          contentInferenceTime: 0,
          fanOutTime: 0,
          totalVariants: fanOutQueries.length,
          variantDistribution,
        };
      }
    
      return enhanced;
    }
    
    function countVariantsByType(
      queries: FanOutQuery[]
    ): Record<FanOutVariantType, number> {
      const counts: Record<FanOutVariantType, number> = {
        equivalent: 0,
        specification: 0,
        generalization: 0,
        followUp: 0,
        comparison: 0,
        clarification: 0,
        relatedAspects: 0,
        temporal: 0,
      };
    
      for (const query of queries) {
        counts[query.variantType]++;
      }
    
      return counts;
    }
Behavior2/5

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

With no annotations provided, the description carries full burden for behavioral disclosure. It mentions 'advanced content gap analysis' and techniques used, but doesn't describe what the analysis returns, potential side effects, performance characteristics, or error conditions. For a complex tool with 7 parameters and no output schema, this is a significant gap in transparency.

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

Conciseness4/5

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

The description is appropriately concise with two sentences that directly address the tool's function. It's front-loaded with the primary purpose and avoids unnecessary elaboration. Every sentence contributes meaning, though it could be slightly more structured for a complex tool.

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

Completeness2/5

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

Given the tool's complexity (7 parameters, nested objects, no output schema, no annotations), the description is insufficient. It doesn't explain what the analysis returns, how results are structured, or what 'gaps' means operationally. For a sophisticated analysis tool, users need more context about outputs and behavioral expectations.

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

Parameters3/5

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

Schema description coverage is 100%, providing comprehensive parameter documentation. The description adds minimal value beyond the schema, mentioning only 'URL' and 'analysis depth' implicitly through 'Analyzes a URL' and 'advanced content gap analysis.' It doesn't explain parameter relationships or usage patterns, so it meets the baseline for high schema coverage.

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

Purpose4/5

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

The description clearly states the tool's purpose: 'Perform advanced content gap analysis using Query Decomposition and Self-RAG techniques' with specific verbs ('analyze', 'identify') and resources ('URL', 'user queries', 'content gaps'). It distinguishes what the tool does (analyze content coverage and gaps) effectively. However, without sibling tools, differentiation from alternatives isn't demonstrated, preventing a perfect score.

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

Usage Guidelines2/5

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

The description provides no guidance on when to use this tool versus alternatives, prerequisites, or typical scenarios. It mentions what the tool does but offers no context about when it's appropriate or what problems it solves. Without sibling tools, this gap is still notable as it lacks any usage context.

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/houtini-ai/fan-out-query-mcp'

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