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
| Name | Required | Description | Default |
|---|---|---|---|
| auditId | Yes | Completed audit id (e.g. aud_xxx_timestamp) | |
| packageType | No | Content bundle type | full |
| orderId | No | UUID from aeo_content_orders after $499 payment. Required when adminContentBypass is false. | |
| adminContentBypass | No | If true: omit orderId; server creates aeo_content_orders (admin/allowlisted key + X-AgentAEO-Admin-Content). For internal QA only. |
Implementation Reference
- src/index.ts:330-517 (handler)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, }; } } );