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
| Name | Required | Description | Default |
|---|---|---|---|
| url | Yes | URL of the content to analyze | |
| depth | No | Analysis depth: quick (5 queries), standard (15 queries), comprehensive (30 queries) | standard |
| focus_area | No | Optional focus area for query generation (e.g., 'pricing', 'installation') | |
| target_keyword | No | Enable keyword fan-out mode: generates query variants based on Google's methodology | |
| fan_out_types | No | Which variant types to generate (default: equivalent, specification, followUp, comparison, clarification) | |
| fan_out_only | No | Skip content inference, only generate keyword variants | |
| context | No | Optional context signals for variant generation |
Implementation Reference
- src/tools/analyze-content-gap.ts:72-183 (handler)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; }