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;
    }
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