Skip to main content
Glama
AutomateLab-tech

automatelab-ai-seo

Official

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

TableJSON Schema
NameRequiredDescriptionDefault
urlNoPublic URL whose content should be fetched and rewritten. Either this OR `text` is required.
textNoRaw content to rewrite directly. Either this OR `url` is required.
target_queryYesThe user query the rewrite should answer. Required - drives entity selection and comparison framing.
add_comparison_tableNoIf true, inject an explicit X-vs-Y comparison table into the rewrite (useful for `X vs Y` queries). Default false.
max_wordsNoSoft word budget. Default 1500. Range 100-5000.
respect_robotsNoIf true (default), respect robots.txt when fetching `url`. Ignored when `text` is used.

Implementation Reference

  • 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",
      };
    }
  • 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",
      });
  • 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)
        );
      }
    );
  • 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.`;
Behavior5/5

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

Discloses tool is read-only, does not write back to source URL, returns rewritten string, delegates to LLM via sampling, and output may vary. No annotations provided, so description carries full burden and meets it.

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

Conciseness5/5

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

Well-structured with clear purpose, behavioral notes, and usage guidelines. Every sentence adds value, no fluff.

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

Completeness5/5

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

Complete for a tool with 6 parameters and no output schema. Explains behavior, parameter requirements, and output nature. Agent can correctly select and invoke.

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

Parameters4/5

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

Schema coverage is 100%, baseline 3. Description adds value by clarifying relationship between url/text, target_query drives entity selection, and explains add_comparison_table and respect_robots behavior. Slight extra context justifies 4.

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

Purpose5/5

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

Clearly states rewrites content for Generative Engine Optimization with specific traits (entity-rich, comparison-ready, synthesis-friendly). Distinguishes from sibling rewrite_for_aeo.

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

Usage Guidelines5/5

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

Explicitly states when to use (optimizing for synthesis-style answers across multiple sources) and when not (use rewrite_for_aeo for direct-answer optimization on single page).

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/AutomateLab-tech/ai-seo'

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