rewrite_for_aeo
Optimize content for answer engines like ChatGPT and Google AI Overviews by restructuring with BLUF, FAQ schema, and query-driven headings.
Instructions
Rewrite a content block for Answer Engine Optimization. Adds a BLUF opening, FAQ structure, schema additions, and concise question-shaped headings tuned for ChatGPT / Perplexity / Google AI Overviews.
Read-only when given url (one HTTP GET). Zero network when given text. The tool does NOT write back to the URL - it only returns the rewritten content as a string. No side effects on the source.
This tool delegates the actual rewrite to the calling LLM via MCP sampling - it does not call any external API itself. The MCP host's model produces the rewrite. Same input may produce different output across runs (model-dependent).
When to use: optimizing content for direct-answer surfaces (definitions, how-tos, FAQs). For Generative Engine Optimization (entity-rich, comparison-ready synthesis), use rewrite_for_geo instead.
Either url or text must be provided. target_query is required.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| url | No | Public URL whose content should be fetched and rewritten. Either this OR `text` is required. | |
| text | No | Raw content (markdown or HTML) to rewrite directly. Either this OR `url` is required. | |
| target_query | Yes | The user query the rewrite should answer (e.g. `what is RAG`, `how to deploy Ghost to Docker`). Required - drives heading shape and BLUF wording. | |
| format | No | Output shape. `article` for prose-with-headings. `faq` for Q&A list. `howto` for numbered-step procedural content with HowTo schema hints. `comparison` for X-vs-Y tables. Default `article`. | article |
| max_words | No | Soft word budget for the rewrite. Default 1500. Range 100-5000. The rewrite tries to stay under this; very small budgets may force truncation. | |
| respect_robots | No | If true (default), respect robots.txt when fetching `url`. Ignored when `text` is used. |
Implementation Reference
- src/tools/rewrite-for-aeo.ts:51-159 (handler)The core handler function `rewriteForAeo` that executes the tool logic: fetches URL content (or uses provided text), scores citation worthiness before rewrite, attempts MCP sampling to rewrite content via LLM, and falls back to returning a prompt template.
export async function rewriteForAeo( input: RewriteForAeoInput, hostDelays?: HostDelayMap, robotsCache?: Map<string, string>, server?: McpServer ): Promise<RewriteAeoResult> { // Fetch URL if provided let originalText = input.text ?? ""; if (input.url) { const result = await politeFetch(input.url, { respectRobots: input.respect_robots, hostDelays, robotsCache, }); const body = parseBody(result.body, input.url); originalText = body.bodyText.substring(0, 8000); } // Compute before score const beforeResult = await scoreCitationWorthiness( { text: originalText, target_query: input.target_query, respect_robots: false }, hostDelays, robotsCache ); const before_score = beforeResult.overall_score; const userMessage = `Target query: "${input.target_query}" Format: ${input.format} Max words: ${input.max_words} Original content: --- ${originalText.substring(0, 4000)} --- Rewrite this content for AEO. Return your response as JSON with these fields: - rewritten_text: the rewritten content (Markdown) - schema_additions: JSON-LD string to add to the page <head> - changes_made: array of strings describing each change applied`; // Attempt MCP sampling if (server) { try { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - sampling API availability varies by client; not typed in all SDK versions const samplingResult = await server.server.request( { method: "sampling/createMessage", params: { messages: [{ role: "user", content: { type: "text", text: userMessage } }], systemPrompt: AEO_SYSTEM_PROMPT, maxTokens: 4096, }, }, // eslint-disable-next-line @typescript-eslint/no-explicit-any {} as any ); const text = samplingResult?.content?.text ?? samplingResult?.content?.[0]?.text ?? ""; if (text) { try { // Extract JSON from response (may be wrapped in markdown code blocks) const jsonMatch = text.match(/```json\n([\s\S]+?)\n```/) ?? text.match(/\{[\s\S]+\}/); const jsonStr = jsonMatch ? jsonMatch[1] ?? jsonMatch[0] : text; const parsed = JSON.parse(jsonStr) as { rewritten_text: string; schema_additions: string; changes_made: string[]; }; const afterResult = await scoreCitationWorthiness( { text: parsed.rewritten_text, target_query: input.target_query, respect_robots: false }, hostDelays, robotsCache ); return { rewritten_text: parsed.rewritten_text, schema_additions: parsed.schema_additions, changes_made: parsed.changes_made ?? [], before_score, after_score: afterResult.overall_score, mode: "sampling", }; } catch { // JSON parse failed - fall through to prompt template } } } catch { // Sampling unavailable or failed - fall through to prompt template } } // Prompt template fallback const promptTemplate = `${AEO_SYSTEM_PROMPT} ${userMessage}`; return { rewritten_text: promptTemplate, schema_additions: "", changes_made: [ "sampling/createMessage unavailable - returned prompt template for manual use", ], before_score, after_score: before_score, // unchanged since we did not rewrite mode: "prompt_template", }; } - src/tools/rewrite-for-aeo.ts:11-22 (schema)Zod input schema for the tool: url, text, target_query, format, max_words, respect_robots.
export const rewriteForAeoInputSchema = z .object({ url: z.string().url().optional(), text: z.string().optional(), target_query: z.string(), format: z.enum(["article", "faq", "howto", "comparison"]).default("article"), max_words: z.number().int().min(100).max(5000).optional().default(1500), respect_robots: z.boolean().optional().default(true), }) .refine((d) => d.url !== undefined || d.text !== undefined, { message: "One of url or text is required", }); - src/tools/rewrite-for-aeo.ts:26-33 (schema)Result interface `RewriteAeoResult` defining the output shape: rewritten_text, schema_additions, changes_made, before_score, after_score, mode.
export interface RewriteAeoResult { rewritten_text: string; schema_additions: string; changes_made: string[]; before_score: number; after_score: number; mode: "sampling" | "prompt_template"; } - src/index.ts:253-302 (registration)Tool registration via `server.tool('rewrite_for_aeo', ...)` with description, Zod input schema, and handler that calls `rewriteForAeo`.
// --- Tool 11: rewrite_for_aeo --- server.tool( "rewrite_for_aeo", [ "Rewrite a content block for Answer Engine Optimization. Adds a BLUF opening, FAQ structure, schema additions, and concise question-shaped headings tuned for ChatGPT / Perplexity / Google AI Overviews.", "Read-only when given `url` (one HTTP GET). Zero network when given `text`. The tool does NOT write back to the URL - it only returns the rewritten content as a string. No side effects on the source.", "This tool delegates the actual rewrite to the calling LLM via MCP sampling - it does not call any external API itself. The MCP host's model produces the rewrite. Same input may produce different output across runs (model-dependent).", "When to use: optimizing content for direct-answer surfaces (definitions, how-tos, FAQs). For Generative Engine Optimization (entity-rich, comparison-ready synthesis), use `rewrite_for_geo` instead.", "Either `url` or `text` must be provided. `target_query` is required.", ].join("\n\n"), { url: z .string() .url() .optional() .describe("Public URL whose content should be fetched and rewritten. Either this OR `text` is required."), text: z .string() .optional() .describe("Raw content (markdown or HTML) to rewrite directly. Either this OR `url` is required."), target_query: z .string() .describe("The user query the rewrite should answer (e.g. `what is RAG`, `how to deploy Ghost to Docker`). Required - drives heading shape and BLUF wording."), format: z .enum(["article", "faq", "howto", "comparison"]) .default("article") .describe("Output shape. `article` for prose-with-headings. `faq` for Q&A list. `howto` for numbered-step procedural content with HowTo schema hints. `comparison` for X-vs-Y tables. Default `article`."), max_words: z .number() .int() .min(100) .max(5000) .optional() .default(1500) .describe("Soft word budget for the rewrite. Default 1500. Range 100-5000. The rewrite tries to stay under this; very small budgets may force truncation."), respect_robots: z .boolean() .optional() .default(true) .describe("If true (default), respect robots.txt when fetching `url`. Ignored when `text` is used."), }, async (input) => { if (!input.url && !input.text) { return toolError({ type: "invalid_url", message: "One of url or text is required" }); } return wrapHandler(() => rewriteForAeo(input as Parameters<typeof rewriteForAeo>[0], undefined, undefined, server) ); } ); - src/tools/rewrite-for-aeo.ts:35-49 (helper)AEO_SYSTEM_PROMPT constant used as the system prompt for the LLM rewrite.
const AEO_SYSTEM_PROMPT = `You are an Answer Engine Optimization (AEO) specialist. Rewrite the provided content to maximize AI engine citation probability. Rules: 1. Open with a direct 40-60 word answer to the target query (BLUF - Bottom Line Up Front). 2. Structure body content into FAQ format: H3 questions ending in "?" followed by 40-60 word answers. 3. Include at least one ordered list for procedural content. 4. Define key technical terms inline (e.g., "X is a type of Y that..."). 5. Cite statistics with year and source where present. 6. End with a "Key Takeaways" or "Summary" section. 7. No em-dashes. No filler phrases ("In conclusion", "It is important to note"). 8. Keep within the max_words limit. 9. For howto format: use numbered HowToStep structure. 10. For comparison format: include a comparison table. Also generate a JSON-LD schema block appropriate for the content type and format.`;