import { initStderrTee } from "../../../../lib/mcp-stderr-tee/dist/index.js";
initStderrTee("aeo");
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { eagerTool } from "../../../../lib/mcp-eager/dist/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { emitJsonLd } from "./tools/aeo-emit-jsonld.js";
import { writeLlmsTxt } from "./tools/aeo-write-llms-txt.js";
import { auditPage } from "./tools/aeo-audit-page.js";
import { closeDriver } from "./lib/neo4j.js";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const server = new McpServer({
    name: "maxy-aeo",
    version: "0.1.0",
});
// Plugin-system boot tolerance — see Task 202. Module-init must never
// throw; plugin-system spawn enumerates tools/list before ACCOUNT_ID
// is set. Per-call refusal preserves account isolation.
const accountId = process.env.ACCOUNT_ID ?? null;
process.stderr.write(`[aeo] boot accountId=${accountId ?? "null"}\n`);
function refuseNoAccount(toolName) {
    process.stderr.write(`[aeo] tool=${toolName} refuse reason=no-account-context\n`);
    return {
        content: [
            {
                type: "text",
                text: `${toolName} cannot execute: this MCP instance was launched without ACCOUNT_ID (plugin-system metadata-only mode). Per-account AEO operations require ACCOUNT_ID at spawn.`,
            },
        ],
        isError: true,
    };
}
const agentSlug = process.env.AGENT_SLUG || undefined;
const sessionId = process.env.SESSION_ID || undefined;
// Task 476 — boot signal so a stale spawn-env contract is visible at the
// first byte of stderr.
process.stderr.write(`[aeo] boot accountId=${accountId ?? "-"} sessionId=${sessionId ?? "-"} agentSlug=${agentSlug ?? "-"}\n`);
// ---------------------------------------------------------------------------
// aeo-emit-jsonld
// ---------------------------------------------------------------------------
eagerTool(server, "aeo-emit-jsonld", "Emit a schema.org JSON-LD block for a typed graph entity. Pass entityId for a Neo4j-sourced render, or label + properties for an inline render. Returns the parsed JSON-LD object and a ready-to-inline <script> block string.", {
    entityId: z
        .string()
        .optional()
        .describe("Neo4j elementId of a typed node (Organization, Person, Service, …)"),
    label: z
        .string()
        .optional()
        .describe("Source label when rendering inline (no entityId)"),
    properties: z
        .record(z.unknown())
        .optional()
        .describe("Property bag for inline render"),
}, async (args) => {
    if (!accountId)
        return refuseNoAccount("aeo-emit-jsonld");
    try {
        const result = await emitJsonLd({ accountId, ...args });
        return {
            content: [
                {
                    type: "text",
                    text: JSON.stringify(result, null, 2),
                },
            ],
        };
    }
    catch (err) {
        const message = err instanceof Error ? err.message : String(err);
        process.stderr.write(`[aeo-emit-jsonld] error: ${message}\n`);
        return {
            content: [{ type: "text", text: `Error: ${message}` }],
            isError: true,
        };
    }
});
// ---------------------------------------------------------------------------
// aeo-write-llms-txt
// ---------------------------------------------------------------------------
eagerTool(server, "aeo-write-llms-txt", "Generate /llms.txt and /llms-full.txt for the account's published KnowledgeDocument pages. Index lists every page with title, url, summary. Full file concatenates each page's body, demoting any in-body H1s to H2 to keep one heading hierarchy. Pass siteDir (absolute path to an existing site root) to also write the two files to disk; in that mode the response carries writtenPaths and omits the inline file bodies. Used by the publish-site skill to refresh both files on every publish.", {
    siteName: z.string().describe("Display name of the site (top H1 of llms.txt)"),
    siteDescription: z
        .string()
        .optional()
        .describe("One-line site description (emitted as a blockquote)"),
    siteDir: z
        .string()
        .optional()
        .describe("Absolute path to an existing site-root directory. When set, writes llms.txt and llms-full.txt into that directory. Tool fails (no silent mkdir) if the directory is missing or not a directory."),
}, async (args) => {
    if (!accountId)
        return refuseNoAccount("aeo-write-llms-txt");
    try {
        const result = await writeLlmsTxt({ accountId, ...args });
        // When the caller asked us to write to disk, the inline file bodies
        // are redundant (and can run to tens of KB). Omit them from the
        // response so the MCP transport stays small for the publish hook.
        const payload = result.writtenPaths
            ? {
                pageCount: result.pageCount,
                skippedNoUrl: result.skippedNoUrl,
                sizeBytes: result.sizeBytes,
                writtenPaths: result.writtenPaths,
            }
            : {
                pageCount: result.pageCount,
                skippedNoUrl: result.skippedNoUrl,
                sizeBytes: result.sizeBytes,
                llmsTxt: result.llmsTxt,
                llmsFullTxt: result.llmsFullTxt,
            };
        return {
            content: [
                {
                    type: "text",
                    text: JSON.stringify(payload, null, 2),
                },
            ],
        };
    }
    catch (err) {
        const message = err instanceof Error ? err.message : String(err);
        process.stderr.write(`[aeo-llms-txt] error: ${message}\n`);
        return {
            content: [{ type: "text", text: `Error: ${message}` }],
            isError: true,
        };
    }
});
// ---------------------------------------------------------------------------
// aeo-audit-page
// ---------------------------------------------------------------------------
eagerTool(server, "aeo-audit-page", "Run the eight-heuristic AEO audit against a URL or inline HTML. Returns a 0–100 score, per-heuristic pass/warn/fail, and one rewrite suggestion per fail. Pass persist=true with targetKnowledgeDocumentId to record an :AEOAudit node linked from the document.", {
    url: z.string().url().optional().describe("Page URL to fetch and audit"),
    html: z.string().optional().describe("Raw HTML to audit (alternative to url)"),
    persist: z
        .boolean()
        .optional()
        .describe("If true, write an :AEOAudit node and HAS_AEO_AUDIT edge"),
    targetKnowledgeDocumentId: z
        .string()
        .optional()
        .describe("Neo4j elementId of the KnowledgeDocument to link the audit to"),
}, async (args) => {
    if (!accountId)
        return refuseNoAccount("aeo-audit-page");
    try {
        const result = await auditPage({
            accountId,
            agentSlug,
            sessionId,
            ...args,
        });
        return {
            content: [
                { type: "text", text: JSON.stringify(result, null, 2) },
            ],
        };
    }
    catch (err) {
        const message = err instanceof Error ? err.message : String(err);
        process.stderr.write(`[aeo-audit] error: ${message}\n`);
        return {
            content: [{ type: "text", text: `Error: ${message}` }],
            isError: true,
        };
    }
});
// ---------------------------------------------------------------------------
// Boot
// ---------------------------------------------------------------------------
async function shutdown() {
    try {
        await closeDriver();
    }
    catch {
        // already-closed driver during shutdown is benign
    }
}
process.on("SIGTERM", () => {
    void shutdown().finally(() => process.exit(0));
});
process.on("SIGINT", () => {
    void shutdown().finally(() => process.exit(0));
});
const transport = new StdioServerTransport();
await server.connect(transport);
process.stderr.write("[aeo] ready\n");
//# sourceMappingURL=index.js.map