rewrite_for_geo
Rewrites content to optimize for generative engine summaries. Produces entity-rich, comparison-ready text suited for synthesis across sources like Perplexity or Google AI Mode.
Instructions
Rewrite a content block for Generative Engine Optimization: entity-rich, comparison-ready, synthesis-friendly. Tuned for surfaces that summarize across sources (Perplexity, Google AI Mode, Claude search).
Read-only on input. Does NOT write back to the source URL - returns the rewritten content as a string.
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. Output may vary across runs (model-dependent).
When to use: optimizing for synthesis-style answers across multiple sources. For direct-answer (BLUF + FAQ) optimization on a single page, use rewrite_for_aeo 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 to rewrite directly. Either this OR `url` is required. | |
| target_query | Yes | The user query the rewrite should answer. Required - drives entity selection and comparison framing. | |
| add_comparison_table | No | If true, inject an explicit X-vs-Y comparison table into the rewrite (useful for `X vs Y` queries). Default false. | |
| max_words | No | Soft word budget. Default 1500. Range 100-5000. | |
| respect_robots | No | If true (default), respect robots.txt when fetching `url`. Ignored when `text` is used. |
Implementation Reference
- src/tools/rewrite-for-geo.ts:50-157 (handler)Main handler function `rewriteForGeo` that fetches content (URL or raw text), scores citation worthiness before/after, and uses MCP sampling or a fallback prompt template to rewrite content for Generative Engine Optimization.
export async function rewriteForGeo( input: RewriteForGeoInput, hostDelays?: HostDelayMap, robotsCache?: Map<string, string>, server?: McpServer ): Promise<RewriteGeoResult> { 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); } 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}" Add comparison table: ${input.add_comparison_table} Max words: ${input.max_words} Original content: --- ${originalText.substring(0, 4000)} --- Rewrite this content for GEO. Return JSON with: - rewritten_text: Markdown content - schema_additions: JSON-LD for key entities - changes_made: array of changes applied - entities_added: array of entity names you defined or linked`; if (server) { try { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - sampling API availability varies by client const samplingResult = await server.server.request( { method: "sampling/createMessage", params: { messages: [{ role: "user", content: { type: "text", text: userMessage } }], systemPrompt: GEO_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 { 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[]; entities_added: 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, entities_added: parsed.entities_added ?? [], mode: "sampling", }; } catch { // fall through to prompt template } } } catch { // sampling unavailable } } const promptTemplate = `${GEO_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, entities_added: [], mode: "prompt_template", }; } - src/tools/rewrite-for-geo.ts:10-21 (schema)Zod input validation schema `rewriteForGeoInputSchema` with fields: url, text, target_query, add_comparison_table, max_words, respect_robots. Requires url or text.
export const rewriteForGeoInputSchema = z .object({ url: z.string().url().optional(), text: z.string().optional(), target_query: z.string(), add_comparison_table: z.boolean().optional().default(false), 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-geo.ts:25-33 (schema)Type `RewriteGeoResult` defining the output shape: rewritten_text, schema_additions, changes_made, before_score, after_score, entities_added, mode.
export interface RewriteGeoResult { rewritten_text: string; schema_additions: string; changes_made: string[]; before_score: number; after_score: number; entities_added: string[]; mode: "sampling" | "prompt_template"; } - src/index.ts:304-354 (registration)Registration of the 'rewrite_for_geo' tool on the MCP server using `server.tool()`, with description, zod schema for params, and handler callback that calls `rewriteForGeo`.
// --- Tool 12: rewrite_for_geo --- server.tool( "rewrite_for_geo", [ "Rewrite a content block for Generative Engine Optimization: entity-rich, comparison-ready, synthesis-friendly. Tuned for surfaces that summarize across sources (Perplexity, Google AI Mode, Claude search).", "Read-only on input. Does NOT write back to the source URL - returns the rewritten content as a string.", "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. Output may vary across runs (model-dependent).", "When to use: optimizing for synthesis-style answers across multiple sources. For direct-answer (BLUF + FAQ) optimization on a single page, use `rewrite_for_aeo` 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 to rewrite directly. Either this OR `url` is required."), target_query: z .string() .describe("The user query the rewrite should answer. Required - drives entity selection and comparison framing."), add_comparison_table: z .boolean() .optional() .default(false) .describe("If true, inject an explicit X-vs-Y comparison table into the rewrite (useful for `X vs Y` queries). Default false."), max_words: z .number() .int() .min(100) .max(5000) .optional() .default(1500) .describe("Soft word budget. Default 1500. Range 100-5000."), 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(() => rewriteForGeo(input as Parameters<typeof rewriteForGeo>[0], undefined, undefined, server) ); } ); - src/tools/rewrite-for-geo.ts:35-48 (helper)GEO_SYSTEM_PROMPT constant: the system prompt for Generative Engine Optimization, defining rules for entity definition, citations, comparison tables, etc.
const GEO_SYSTEM_PROMPT = `You are a Generative Engine Optimization (GEO) specialist focused on Google AI Overviews and multi-source synthesis engines. Rules: 1. Define every key entity inline on first mention: "X (also known as Y) is a type of Z that...". 2. Include authoritative external citations with hyperlinks (Wikipedia, official docs, research papers). 3. Add a comparison table if requested or if the query implies comparison. 4. Use numbered feature lists for technical capabilities. 5. Structure for multi-source synthesis: each section should be independently citable without reading the full article. 6. Cite statistics with source, year, and context. 7. Add a "Related entities" section at the end listing key entities with sameAs links. 8. No em-dashes. No filler phrases. 9. Keep within the max_words limit. Generate JSON-LD schema for all key entities mentioned (Organization, Person, Product, SoftwareApplication as appropriate) with sameAs links.`;