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,
          };
        }
      }
    );

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