Skip to main content
Glama

generate_aeo_content_suite

Generate HTML, JSON-LD, and structured content files from completed brand visibility audits to improve AI search ranking and citation rates.

Instructions

Start Content Suite generation (HTML + JSON-LD + llms.txt) for a completed audit — async (returns in seconds with orderId). Poll check_aeo_content_suite_status every 15–30s until completed (often 5–25+ min). Uses AGENTAEO_API_KEY — no shell/curl. Admin QA without Cashfree: adminContentBypass=true + allowlisted key. Otherwise pass orderId after payment.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
auditIdYesCompleted audit id (e.g. aud_xxx_timestamp)
packageTypeNoContent bundle typefull
orderIdNoUUID from aeo_content_orders after $499 payment. Required when adminContentBypass is false.
adminContentBypassNoIf true: omit orderId; server creates aeo_content_orders (admin/allowlisted key + X-AgentAEO-Admin-Content). For internal QA only.

Implementation Reference

  • The registration and handler implementation for the generate_aeo_content_suite tool.
    server.tool(
      "generate_aeo_content_suite",
      "Start Content Suite generation (HTML + JSON-LD + llms.txt) for a completed audit — **async** (returns in seconds with orderId). Poll check_aeo_content_suite_status every 15–30s until completed (often 5–25+ min). Uses AGENTAEO_API_KEY — no shell/curl. Admin QA without Cashfree: adminContentBypass=true + allowlisted key. Otherwise pass orderId after payment.",
      {
        auditId: z.string().describe("Completed audit id (e.g. aud_xxx_timestamp)"),
        packageType: z.enum(["full", "faq"]).optional().default("full").describe("Content bundle type"),
        orderId: z
          .string()
          .optional()
          .describe("UUID from aeo_content_orders after $499 payment. Required when adminContentBypass is false."),
        adminContentBypass: z
          .boolean()
          .optional()
          .default(false)
          .describe(
            "If true: omit orderId; server creates aeo_content_orders (admin/allowlisted key + X-AgentAEO-Admin-Content). For internal QA only."
          ),
      },
      async ({ auditId, packageType, orderId, adminContentBypass }) => {
        try {
          const adminBypass = adminContentBypass === true;
          if (!adminBypass && (!orderId || !orderId.trim())) {
            return {
              content: [
                {
                  type: "text" as const,
                  text:
                    "Error: either pass orderId (after Cashfree content purchase) or set adminContentBypass=true for admin testing.",
                },
              ],
              isError: true,
            };
          }
          const pkg = packageType || "full";
          const body: Record<string, unknown> = {
            auditid: auditId.trim(),
            packagetype: pkg,
            async: true,
          };
          if (orderId && orderId.trim() && !adminBypass) {
            body.orderid = orderId.trim();
          }
          if (adminBypass) {
            body.admin_content_bypass = true;
          }
    
          const res = await fetch(`${API_BASE}/api/aeo-generate-content`, {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
              "X-API-Key": apiKey,
              ...(adminBypass ? { "X-AgentAEO-Admin-Content": "1" } : {}),
            },
            body: JSON.stringify(body),
          });
          const data = (await res.json()) as Record<string, unknown>;
          if (!res.ok) {
            const err = (data?.error as string) || (data?.message as string) || `HTTP ${res.status}`;
            return {
              content: [{ type: "text" as const, text: `Error: ${err}\n\n${JSON.stringify(data, null, 2)}` }],
              isError: true,
            };
          }
    
          const resolvedOrderId = String(data?.orderid ?? data?.order_id ?? "").trim();
          const syncStatus = String(data?.status ?? "").toLowerCase();
    
          // Sync path: already complete or idempotent "already generating" (HTTP 200)
          if (res.status === 200) {
            if (syncStatus === "completed" && data?.downloadurl) {
              const text =
                `✅ Content Suite already complete.\n\n` +
                `orderid: ${resolvedOrderId}\n` +
                `auditid: ${data?.auditid ?? auditId}\n` +
                `download (GET with same X-API-Key): ${API_BASE}${data?.downloadurl}\n\n` +
                `Full JSON:\n${JSON.stringify(data, null, 2)}`;
              return { content: [{ type: "text" as const, text }] };
            }
            if (syncStatus === "generating" && resolvedOrderId) {
              if (!inlineContentPoll) {
                const text =
                  `✅ Content generation already in progress (or accepted).\n\n` +
                  `orderid: ${resolvedOrderId}\n\n` +
                  `Next: call **check_aeo_content_suite_status** every 15–30s until status is **completed**.\n\n` +
                  `Server response:\n${JSON.stringify(data, null, 2)}`;
                return { content: [{ type: "text" as const, text }] };
              }
              // fall through to inline poll using resolvedOrderId
            } else if (!resolvedOrderId) {
              const text = `Unexpected 200 response:\n${JSON.stringify(data, null, 2)}`;
              return { content: [{ type: "text" as const, text }] };
            }
          }
    
          // HTTP 202 async accepted — or inline poll from "generating" 200
          if (res.status === 202 || (inlineContentPoll && resolvedOrderId && (res.status === 202 || syncStatus === "generating"))) {
            const oid = resolvedOrderId;
            if (!oid) {
              return {
                content: [
                  {
                    type: "text" as const,
                    text: `Async response missing orderid:\n${JSON.stringify(data, null, 2)}`,
                  },
                ],
                isError: true,
              };
            }
    
            if (!inlineContentPoll) {
              const text =
                `✅ Content Suite job accepted (**async**).\n\n` +
                `orderid: ${oid}\n` +
                `auditid: ${data?.auditid ?? auditId}\n\n` +
                `Next: call **check_aeo_content_suite_status** every 15–30s until status is **completed** or **failed** (often 5–25+ minutes).\n` +
                (data?.pollUrl ? `Poll URL: ${data.pollUrl}\n` : "") +
                `\nServer response:\n${JSON.stringify(data, null, 2)}`;
              return { content: [{ type: "text" as const, text }] };
            }
    
            const POLL_INTERVAL_MS = 20000;
            const MAX_POLLS = 150;
            let last: Record<string, unknown> = {};
    
            for (let i = 0; i < MAX_POLLS; i++) {
              if (i > 0) await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
    
              const pollRes = await fetch(`${API_BASE}/api/aeo-content-status/${encodeURIComponent(oid)}`, {
                method: "GET",
                headers: { "X-API-Key": apiKey },
              });
              last = (await pollRes.json()) as Record<string, unknown>;
              if (!pollRes.ok) break;
    
              const st = String(last?.status ?? "").toLowerCase();
              if (st === "failed") {
                return {
                  content: [
                    {
                      type: "text" as const,
                      text: `Content generation failed for order ${oid}.\n\n${JSON.stringify(last, null, 2)}`,
                    },
                  ],
                  isError: true,
                };
              }
              if (st === "completed") {
                const du = (last?.download_url as string) || `${API_BASE}/api/aeo-content-download/${oid}`;
                const text =
                  `✅ Content Suite generation finished.\n\n` +
                  `orderid: ${oid}\n` +
                  `download (GET with same X-API-Key): ${du}\n\n` +
                  `Last poll:\n${JSON.stringify(last, null, 2)}`;
                return { content: [{ type: "text" as const, text }] };
              }
            }
    
            return {
              content: [
                {
                  type: "text" as const,
                  text:
                    `Content job started (orderid: ${oid}) but did not complete within ~50 minutes of polling.\n` +
                    `Last status:\n${JSON.stringify(last, null, 2)}\n\n` +
                    `Use **check_aeo_content_suite_status** with orderId "${oid}" to continue.`,
                },
              ],
            };
          }
    
          // Synchronous completion (HTTP 200 full result — e.g. server without async or legacy)
          const text =
            `✅ Content Suite generation finished.\n\n` +
            `orderid: ${data?.orderid ?? "?"}\n` +
            `auditid: ${data?.auditid ?? auditId}\n` +
            `pages: ${data?.pagesgenerated ?? "?"}\n` +
            `download (use same X-API-Key as GET): ${API_BASE}${data?.downloadurl ?? ""}\n\n` +
            `Full JSON:\n${JSON.stringify(data, null, 2)}`;
          return { content: [{ type: "text" as const, text }] };
        } catch (err) {
          const msg = err instanceof Error ? err.message : String(err);
          return {
            content: [{ type: "text" as const, text: `Error: ${msg}` }],
            isError: true,
          };
        }
      }
    );
Behavior4/5

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

With no annotations provided, the description carries full burden and does well: it discloses the async behavior (returns in seconds with orderId), typical completion time (5-25+ min), polling interval (15-30s), authentication requirement (AGENTAEO_API_KEY), and admin bypass conditions. It doesn't mention error handling or rate limits, but covers key operational traits.

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

Conciseness4/5

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

The description is appropriately sized and front-loaded with the core purpose. Every sentence adds value: async behavior, polling instructions, authentication, and admin bypass details. It could be slightly more structured but avoids redundancy.

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

Completeness4/5

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

Given the complexity (async operation with polling) and no annotations/output schema, the description is mostly complete: it covers purpose, usage, behavior, and parameter context. It lacks details on error responses or output format, but provides enough for an agent to use the tool effectively with the schema.

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

Parameters3/5

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

Schema description coverage is 100%, so the baseline is 3. The description adds some context: it clarifies that orderId comes from 'aeo_content_orders after $499 payment' and that adminContentBypass requires 'allowlisted key + X-AgentAEO-Admin-Content', but doesn't provide additional meaning beyond what's in the schema for auditId or packageType.

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?

The description clearly states the tool's purpose: 'Start Content Suite generation (HTML + JSON-LD + llms.txt) for a completed audit.' It specifies the exact output format and the required input condition (completed audit), and distinguishes it from siblings by mentioning the async nature and the need to poll check_aeo_content_suite_status.

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?

The description provides explicit guidance on when to use this tool vs alternatives: it mentions using adminContentBypass for admin QA without Cashfree, otherwise requiring orderId after payment. It also specifies the polling mechanism with check_aeo_content_suite_status and distinguishes from run_aeo_audit by focusing on content generation post-audit.

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/agentaeo/agentaeo-mcp-server'

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