import { initStderrTee } from "../../../../lib/mcp-stderr-tee/dist/index.js";
initStderrTee("cloudflare");
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import * as cloudflared from "./lib/cloudflared.js";
const server = new McpServer({
    name: "cloudflare",
    version: "0.2.0",
});
// ===================================================================
// Token management
// ===================================================================
server.tool("cf-set-token", "Set the Cloudflare API token. Validates the token against the Cloudflare API, discovers the account ID, and persists both to ~/.cloudflare/api-token. Required scopes: Zone:DNS:Edit, Account:Cloudflare Tunnel:Edit, Zone:Zone:Edit.", {
    token: z
        .string()
        .describe("The Cloudflare API token to store"),
}, async ({ token }) => {
    const trimmed = token.trim();
    if (!trimmed) {
        return {
            content: [
                {
                    type: "text",
                    text: "No token provided. Paste an existing Cloudflare API token as the token argument. If the user does not have a token, use tunnel-login instead — it handles authentication automatically.",
                },
            ],
            isError: true,
        };
    }
    try {
        const result = await cloudflared.validateToken(trimmed);
        if (!result.valid) {
            return {
                content: [
                    {
                        type: "text",
                        text: `Token validation failed: ${result.error}`,
                    },
                ],
                isError: true,
            };
        }
        if (!result.accountId) {
            return {
                content: [
                    {
                        type: "text",
                        text: `${result.error}`,
                    },
                ],
                isError: true,
            };
        }
        cloudflared.writeToken(trimmed, result.accountId);
        return {
            content: [
                {
                    type: "text",
                    text: `API token validated and saved.\n\n  Account: ${result.accountName ?? result.accountId}\n  Token stored: ~/.cloudflare/api-token (mode 0600)\n\nReady for tunnel operations.`,
                },
            ],
        };
    }
    catch (err) {
        return {
            content: [
                {
                    type: "text",
                    text: `Token validation failed: ${err instanceof Error ? err.message : String(err)}`,
                },
            ],
            isError: true,
        };
    }
});
server.tool("tunnel-login", "Authenticate with Cloudflare via `cloudflared tunnel login`. Three phases: (1) if already authenticated, reports status; (2) if cert.pem exists, derives API credentials; (3) if no cert.pem, spawns login process and returns auth URL. Detects existing login processes — safe to call repeatedly without spawning duplicates.", {}, async () => {
    try {
        // Phase: Already authenticated
        const auth = await cloudflared.validateAuth();
        if (auth.hasToken && auth.tokenValid) {
            return {
                content: [
                    {
                        type: "text",
                        text: `Already authenticated. API token is valid.\n\nTo re-authenticate, delete ~/.cloudflare/api-token and ~/.cloudflared/cert.pem, then call tunnel-login again.`,
                    },
                ],
            };
        }
        // Phase: cert.pem exists — derive credentials
        if (cloudflared.hasCert()) {
            const creds = cloudflared.parseCertPem();
            if (!creds) {
                return {
                    content: [
                        {
                            type: "text",
                            text: `cert.pem exists at ~/.cloudflared/cert.pem but could not extract API credentials from it. The file format may have changed.\n\nFallback: ask the user to paste an existing connection key and use cf-set-token to store it.`,
                        },
                    ],
                    isError: true,
                };
            }
            // Validate the cert-derived token
            const validation = await cloudflared.validateToken(creds.apiToken);
            if (!validation.valid) {
                return {
                    content: [
                        {
                            type: "text",
                            text: `cert.pem credentials were extracted but the token was rejected by Cloudflare: ${validation.error}\n\nFallback: ask the user to paste an existing connection key and use cf-set-token to store it.`,
                        },
                    ],
                    isError: true,
                };
            }
            // Use the account ID from validation (more reliable) or fall back to cert
            const accountId = validation.accountId ?? creds.accountId;
            cloudflared.writeToken(creds.apiToken, accountId);
            return {
                content: [
                    {
                        type: "text",
                        text: `Credentials derived from cert.pem and validated.\n\n  Account: ${validation.accountName ?? accountId}\n  Token stored: ~/.cloudflare/api-token (mode 0600)\n\nReady for tunnel operations.`,
                    },
                ],
            };
        }
        // Phase: Login process already running — wait for authorization
        const activeLogin = cloudflared.getActiveLogin();
        if (activeLogin) {
            return {
                content: [
                    {
                        type: "text",
                        text: `Authorization in progress — a login process is already running (PID ${activeLogin.pid}, started ${new Date(activeLogin.startedAt).toISOString()}).\n\nAuth URL: ${activeLogin.authUrl}\n\nAsk the user to complete authorization in the browser. Call tunnel-login again after the user has authorized — it will detect cert.pem and derive API credentials automatically.`,
                    },
                ],
            };
        }
        // Phase: No cert.pem, no active login — spawn cloudflared tunnel login
        if (!cloudflared.isInstalled()) {
            return {
                content: [
                    {
                        type: "text",
                        text: "cloudflared is not installed. Run tunnel-install first.",
                    },
                ],
                isError: true,
            };
        }
        const result = await cloudflared.tunnelLogin();
        const alreadyNote = result.alreadyRunning
            ? " (reusing existing login process)"
            : "";
        return {
            content: [
                {
                    type: "text",
                    text: `Cloudflare login started${alreadyNote}. Open this URL to authorize:\n\n${result.authUrl}\n\nThe user must open this URL and click Authorize. After authorization, call tunnel-login again — it will detect the authorization and derive API credentials automatically.`,
                },
            ],
        };
    }
    catch (err) {
        return {
            content: [
                {
                    type: "text",
                    text: `Login failed: ${err instanceof Error ? err.message : String(err)}`,
                },
            ],
            isError: true,
        };
    }
});
server.tool("cf-zone-status", "List all zones (domains) on the Cloudflare account with their activation status and assigned nameservers.", {}, async () => {
    try {
        const zones = await cloudflared.listZones();
        if (zones.length === 0) {
            return {
                content: [
                    {
                        type: "text",
                        text: "No zones found on this account. Add a domain to Cloudflare first.",
                    },
                ],
            };
        }
        const lines = zones.map((z) => `  ${z.name} — ${z.status}${z.nameservers.length > 0 ? `\n    Nameservers: ${z.nameservers.join(", ")}` : ""}`);
        return {
            content: [
                {
                    type: "text",
                    text: `Zones on this account:\n\n${lines.join("\n\n")}`,
                },
            ],
        };
    }
    catch (err) {
        return {
            content: [
                {
                    type: "text",
                    text: `Failed: ${err instanceof Error ? err.message : String(err)}`,
                },
            ],
            isError: true,
        };
    }
});
server.tool("cf-add-zone", "Add a domain to the Cloudflare account via the official API. Returns the zone status and assigned nameservers. Idempotent — returns existing zone info if the domain is already on the account. Requires a valid API token with Zone:Zone:Edit scope (set via cf-set-token).", {
    domain: z
        .string()
        .describe("The bare domain to add (e.g. 'mybusiness.com'). Must be a registrable domain, not a subdomain."),
}, async ({ domain }) => {
    if (!cloudflared.hasToken()) {
        return {
            content: [
                {
                    type: "text",
                    text: "No API token configured. Run cf-set-token first.",
                },
            ],
            isError: true,
        };
    }
    try {
        const start = Date.now();
        const result = await cloudflared.createZone(domain);
        const elapsed = Date.now() - start;
        const action = result.existing ? "already on this account" : "added";
        const nsLines = result.nameservers.length > 0
            ? `\n  Nameservers: ${result.nameservers.join(", ")}`
            : "";
        console.error(`[cf-add-zone] ${domain} ${action} (${result.status}) in ${elapsed}ms`);
        return {
            content: [
                {
                    type: "text",
                    text: `Domain ${action}.\n\n  Domain: ${result.name}\n  Status: ${result.status}${nsLines}\n\n${result.status === "pending"
                        ? "The domain is pending activation — update the nameservers at your registrar to the Cloudflare-assigned nameservers shown above. Once nameservers propagate (minutes to 24 hours), the status changes to Active."
                        : result.status === "active"
                            ? "The domain is active on Cloudflare."
                            : `Current status: ${result.status}.`}`,
                },
            ],
        };
    }
    catch (err) {
        const msg = err instanceof Error ? err.message : String(err);
        console.error(`[cf-add-zone] failed for ${domain}: ${msg}`);
        // Detect scope/permission errors and guide the user
        if (msg.includes("403") || msg.includes("permission") || msg.includes("forbidden")) {
            return {
                content: [
                    {
                        type: "text",
                        text: `Zone creation failed — insufficient permissions.\n\nThe API token needs the Zone:Zone:Edit scope to add domains. The current token may only have Zone:Zone:Read.\n\nTo fix: re-run tunnel-login to get a fresh token with full permissions, or ask the user to paste a token with the required scopes and use cf-set-token.`,
                    },
                ],
                isError: true,
            };
        }
        return {
            content: [
                {
                    type: "text",
                    text: `Zone creation failed: ${msg}`,
                },
            ],
            isError: true,
        };
    }
});
// ===================================================================
// Cloudflare Tunnel tools
// ===================================================================
server.tool("tunnel-status", "Full Cloudflare Tunnel status: installed, version, token valid, running, domain, URLs.", {
    domain: z
        .string()
        .optional()
        .describe("The bare domain (e.g. 'maxy.bot'). If omitted, domain info is excluded."),
}, async ({ domain }) => {
    try {
        const status = await cloudflared.getStatus(domain);
        // If a domain is known, check if the subdomains actually resolve
        const effectiveDomain = domain ?? status.domain;
        let dnsWarning = "";
        if (effectiveDomain) {
            const dns = await import("node:dns/promises");
            const resolver = new dns.Resolver();
            resolver.setServers(["8.8.8.8", "1.1.1.1"]);
            const checks = [];
            for (const sub of [
                `admin.${effectiveDomain}`,
                `public.${effectiveDomain}`,
            ]) {
                try {
                    await resolver.resolve4(sub);
                }
                catch {
                    checks.push(sub);
                }
            }
            if (checks.length > 0) {
                dnsWarning = `\n\nDNS NOT RESOLVING: ${checks.join(", ")} do not resolve (checked via Google DNS 8.8.8.8). Possible causes: (1) CNAME records not created — run tunnel-create to create them; (2) nameservers not pointing to Cloudflare — check cf-zone-status; (3) DNS propagation in progress — wait a few minutes and re-check.`;
            }
        }
        return {
            content: [
                {
                    type: "text",
                    text: JSON.stringify(status, null, 2) + dnsWarning,
                },
            ],
        };
    }
    catch (err) {
        return {
            content: [
                {
                    type: "text",
                    text: `Failed: ${err instanceof Error ? err.message : String(err)}`,
                },
            ],
            isError: true,
        };
    }
});
server.tool("tunnel-install", "Install the cloudflared binary if not already present.", {}, async () => {
    const { execFileSync } = await import("node:child_process");
    if (cloudflared.isInstalled()) {
        const v = cloudflared.version();
        return {
            content: [
                {
                    type: "text",
                    text: `cloudflared is already installed (v${v}).`,
                },
            ],
        };
    }
    try {
        const arch = process.arch === "arm64" ? "arm64" : "amd64";
        const debUrl = `https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-${arch}.deb`;
        execFileSync("curl", ["-fsSL", debUrl, "-o", "/tmp/cloudflared.deb"], {
            timeout: 60000,
        });
        execFileSync("sudo", ["dpkg", "-i", "/tmp/cloudflared.deb"], {
            timeout: 30000,
        });
        execFileSync("rm", ["-f", "/tmp/cloudflared.deb"]);
        return {
            content: [
                {
                    type: "text",
                    text: `cloudflared installed (v${cloudflared.version()}).`,
                },
            ],
        };
    }
    catch (err) {
        return {
            content: [
                {
                    type: "text",
                    text: `Installation failed: ${err instanceof Error ? err.message : String(err)}`,
                },
            ],
            isError: true,
        };
    }
});
server.tool("tunnel-create", "Create a Cloudflare Tunnel and route DNS for admin.{domain} and public.{domain}. Uses the Cloudflare SDK — requires a valid API token (set via cf-set-token).", {
    domain: z
        .string()
        .describe("The bare domain (e.g. 'maxy.bot'). Subdomains admin. and public. are created automatically."),
    tunnelName: z
        .string()
        .optional()
        .describe("Human-readable tunnel name (default: derived from domain)"),
}, async ({ domain, tunnelName }) => {
    if (!cloudflared.hasToken()) {
        return {
            content: [
                {
                    type: "text",
                    text: "No API token configured. Run cf-set-token first.",
                },
            ],
            isError: true,
        };
    }
    try {
        const name = tunnelName ?? domain.replace(/\./g, "-");
        // Create or reuse tunnel
        const result = await cloudflared.createTunnel(name);
        // Configure ingress rules on Cloudflare's edge
        const platformPort = parseInt(process.env.PLATFORM_PORT ?? "19200", 10);
        await cloudflared.configureTunnel(result.tunnelId, domain, platformPort);
        // Create DNS CNAME records
        const zoneId = await cloudflared.getZoneId(domain);
        const adminDns = await cloudflared.createDnsRecord(zoneId, `admin.${domain}`, result.tunnelId);
        const publicDns = await cloudflared.createDnsRecord(zoneId, `public.${domain}`, result.tunnelId);
        // Verify subdomains resolve via Google DNS
        const dns = await import("node:dns/promises");
        const resolver = new dns.Resolver();
        resolver.setServers(["8.8.8.8", "1.1.1.1"]);
        const dnsWarnings = [];
        for (const sub of [`admin.${domain}`, `public.${domain}`]) {
            try {
                await resolver.resolve4(sub);
            }
            catch {
                dnsWarnings.push(sub);
            }
        }
        let dnsNote = "";
        if (dnsWarnings.length > 0) {
            dnsNote = `\n\nDNS WARNING: ${dnsWarnings.join(", ")} did not resolve via Google DNS (8.8.8.8). CNAME records were created via the Cloudflare API. This may be propagation delay (wait a few minutes and re-check) or the domain may not be Active on Cloudflare — run cf-zone-status to check.`;
        }
        const adminNote = adminDns.updated
            ? "updated — was pointing to wrong tunnel"
            : adminDns.existing
                ? "already existed"
                : "created";
        const publicNote = publicDns.updated
            ? "updated — was pointing to wrong tunnel"
            : publicDns.existing
                ? "already existed"
                : "created";
        return {
            content: [
                {
                    type: "text",
                    text: `Tunnel created.\n\n  ID: ${result.tunnelId}\n  Name: ${result.tunnelName}\n  DNS: admin.${domain} → tunnel (${adminNote})\n  DNS: public.${domain} → tunnel (${publicNote})\n  Config: remotely managed (Cloudflare edge)\n\nUse tunnel-enable to start the tunnel.${dnsNote}`,
                },
            ],
        };
    }
    catch (err) {
        return {
            content: [
                {
                    type: "text",
                    text: `Create failed: ${err instanceof Error ? err.message : String(err)}`,
                },
            ],
            isError: true,
        };
    }
});
server.tool("tunnel-enable", "Start the Cloudflare Tunnel process. The tunnel must be created first via tunnel-create. Fetches a connector token from the Cloudflare API and starts cloudflared with it. If tunnelId and domain are omitted, reads them from persisted tunnel state.", {
    tunnelId: z.string().optional().describe("Tunnel UUID from tunnel-create. If omitted, reads from persisted tunnel state."),
    domain: z.string().optional().describe("The bare domain (e.g. 'maxy.bot'). If omitted, reads from persisted tunnel state."),
}, async ({ tunnelId: tunnelIdParam, domain: domainParam }) => {
    try {
        // Resolve tunnelId and domain from params or persisted state
        const tunnelId = tunnelIdParam ?? cloudflared.getPersistedTunnelId();
        const domain = domainParam ?? cloudflared.getPersistedDomain();
        if (!tunnelId) {
            return {
                content: [
                    {
                        type: "text",
                        text: "No tunnel ID provided and none found in persisted state. Run tunnel-create first to create a tunnel.",
                    },
                ],
                isError: true,
            };
        }
        if (!domain) {
            return {
                content: [
                    {
                        type: "text",
                        text: "No domain provided and none found in persisted state. Run tunnel-create first to create a tunnel with a domain.",
                    },
                ],
                isError: true,
            };
        }
        // GATE 1: Check if subdomains resolve (via Google DNS)
        const dns = await import("node:dns/promises");
        const resolver = new dns.Resolver();
        resolver.setServers(["8.8.8.8", "1.1.1.1"]);
        const unresolvedSubs = [];
        for (const sub of [`admin.${domain}`, `public.${domain}`]) {
            try {
                await resolver.resolve4(sub);
            }
            catch {
                unresolvedSubs.push(sub);
            }
        }
        if (unresolvedSubs.length > 0) {
            return {
                content: [
                    {
                        type: "text",
                        text: `REFUSED: ${unresolvedSubs.join(", ")} do not resolve (checked via Google DNS). The tunnel URLs will not work.\n\nThis usually means the CNAME records for admin.${domain} and public.${domain} have not been created. Run tunnel-create with domain "${domain}" first — it creates the CNAME records in Cloudflare.\n\nIf tunnel-create has already been run, this may be DNS propagation (wait a few minutes) or the domain may not be Active on Cloudflare. Run cf-zone-status to check.\n\nDo not call tunnel-enable until the subdomains resolve.`,
                    },
                ],
                isError: true,
            };
        }
        const brand = cloudflared.loadBrand();
        const platformPort = parseInt(process.env.PLATFORM_PORT ?? "19200", 10);
        console.error(`[tunnel-enable] using PLATFORM_PORT=${platformPort}`);
        // GATE 2: Remote auth must be configured (verified via live API, not file check)
        try {
            const res = await fetch(`http://127.0.0.1:${platformPort}/api/remote-auth/status`, {
                signal: AbortSignal.timeout(5000),
            });
            const body = await res.json();
            if (!body.configured) {
                return {
                    content: [
                        {
                            type: "text",
                            text: `REFUSED: Remote access password is not configured. The admin interface would be exposed to the internet without authentication.\n\nDirect the user to set a remote password in the browser at /__remote-auth/setup. Do NOT collect the password in chat.`,
                        },
                    ],
                    isError: true,
                };
            }
        }
        catch {
            return {
                content: [
                    {
                        type: "text",
                        text: `REFUSED: Cannot verify remote authentication — the web server at http://127.0.0.1:${platformPort} is not reachable. The admin interface cannot be exposed without a running auth gate.\n\nEnsure the ${brand.productName} platform is running before enabling the tunnel.`,
                    },
                ],
                isError: true,
            };
        }
        // GATE 3: API token must be valid
        if (!cloudflared.hasToken()) {
            return {
                content: [
                    {
                        type: "text",
                        text: `REFUSED: No API token configured. Run cf-set-token first.`,
                    },
                ],
                isError: true,
            };
        }
        // GATE 4: Ingress port verification — self-heal if rules point to wrong port
        const ingressFix = await cloudflared.fixIngressPort(tunnelId, platformPort);
        if (ingressFix.corrected) {
            console.error(`[tunnel-enable] ingress port corrected from ${ingressFix.fromPort} to ${platformPort}`);
        }
        // Fetch connector token from Cloudflare API
        const connectorToken = await cloudflared.getConnectorToken(tunnelId);
        // Start the tunnel daemon
        cloudflared.startTunnel(connectorToken, tunnelId, domain);
        // Wait up to 5 seconds to verify the process didn't immediately crash
        const logHint = `~/${brand.configDir}/logs/cloudflared.log`;
        for (let i = 0; i < 10; i++) {
            await new Promise((r) => setTimeout(r, 500));
            const status = await cloudflared.getStatus(domain);
            if (!status.running) {
                return {
                    content: [
                        {
                            type: "text",
                            text: `Tunnel process exited immediately after starting. Check ${logHint} for details.`,
                        },
                    ],
                    isError: true,
                };
            }
        }
        const status = await cloudflared.getStatus(domain);
        // Programmatic verification — HTTP-check the public URL via Cloudflare's edge
        let verified = false;
        const publicUrl = `https://public.${domain}`;
        for (let attempt = 0; attempt < 6; attempt++) {
            if (attempt > 0)
                await new Promise((r) => setTimeout(r, 5000));
            try {
                const res = await fetch(publicUrl, {
                    signal: AbortSignal.timeout(10000),
                    redirect: "manual",
                });
                if (res.status !== 530) {
                    verified = true;
                    break;
                }
            }
            catch {
                // network error — retry
            }
        }
        if (!verified) {
            return {
                content: [
                    {
                        type: "text",
                        text: `Tunnel process is running (PID ${status.pid}) but https://public.${domain} returned HTTP 530 after 30s of retries. Cloudflare's edge cannot route traffic to this tunnel.\n\nCommon causes:\n  1. Stale DNS CNAME records pointing to a previous tunnel — run tunnel-create again to fix\n  2. UDP buffer too small for QUIC — check ${logHint}\n  3. Firewall blocking outbound QUIC to Cloudflare edge\n\nThe tunnel is NOT working. Do not report success.`,
                    },
                ],
                isError: true,
            };
        }
        const ingressNote = ingressFix.corrected
            ? `\n  Ingress: port corrected from ${ingressFix.fromPort} to ${platformPort}`
            : "";
        return {
            content: [
                {
                    type: "text",
                    text: `Tunnel started and verified.\n\n  PID: ${status.pid}\n  Admin: https://admin.${domain}\n  Public: https://public.${domain}${ingressNote}\n\nBoth URLs are reachable through Cloudflare's edge. The tunnel auto-starts on device reboot.`,
                },
            ],
        };
    }
    catch (err) {
        return {
            content: [
                {
                    type: "text",
                    text: `Enable failed: ${err instanceof Error ? err.message : String(err)}`,
                },
            ],
            isError: true,
        };
    }
});
server.tool("tunnel-disable", "Stop the Cloudflare Tunnel process. Config is preserved for re-enabling.", {}, async () => {
    try {
        cloudflared.stopTunnel();
        return {
            content: [
                {
                    type: "text",
                    text: "Tunnel stopped. Config preserved — use tunnel-enable to restart.",
                },
            ],
        };
    }
    catch (err) {
        return {
            content: [
                {
                    type: "text",
                    text: `Disable failed: ${err instanceof Error ? err.message : String(err)}`,
                },
            ],
            isError: true,
        };
    }
});
server.tool("tunnel-add-hostname", "Add an alias hostname to an existing Cloudflare Tunnel for direct serving. Reads the current tunnel ingress config, appends the new hostname, creates a CNAME in the alias domain's zone pointing to the tunnel, and registers the alias so the web server treats it as a public hostname. The web server hot-reloads alias domains automatically — the new domain is active within seconds, no restart needed. Idempotent — safe to call again with the same hostname.", {
    hostname: z
        .string()
        .describe("The alias domain to add (e.g. 'maxy.chat'). Must be an Active zone on the Cloudflare account."),
    tunnelId: z
        .string()
        .describe("The tunnel UUID. Get this from tunnel-status."),
}, async ({ hostname, tunnelId }) => {
    if (!cloudflared.hasToken()) {
        return {
            content: [
                {
                    type: "text",
                    text: "No API token configured. Run cf-set-token first.",
                },
            ],
            isError: true,
        };
    }
    try {
        // Step 1: Verify the alias domain's zone exists and is accessible
        const zoneId = await cloudflared.getZoneId(hostname);
        // Step 2: Add hostname to tunnel ingress (read-then-append)
        const ingress = await cloudflared.addHostnameToIngress(tunnelId, hostname);
        // Step 3: Create CNAME in the alias domain's zone
        const dns = await cloudflared.createDnsRecord(zoneId, hostname, tunnelId);
        // Step 4: Persist alias domain for web server recognition
        cloudflared.saveAliasDomain(hostname);
        // Build response
        const ingressNote = ingress.alreadyPresent
            ? "already in tunnel ingress"
            : "added to tunnel ingress";
        const dnsNote = dns.updated
            ? "updated — was pointing to wrong tunnel"
            : dns.existing
                ? "already existed"
                : "created";
        return {
            content: [
                {
                    type: "text",
                    text: `Alias domain configured for direct serving.\n\n  Hostname: ${hostname}\n  Tunnel ingress: ${ingressNote}\n  DNS CNAME: ${dnsNote} (→ ${tunnelId}.cfargotunnel.com)\n  Web server: registered in alias-domains.json (hot-reloaded automatically)\n\nhttps://${hostname} is now active — the web server picks up the new alias domain within seconds. It serves the public chat directly — no redirect, URL stays as ${hostname}. Verify the URL to confirm.`,
                },
            ],
        };
    }
    catch (err) {
        return {
            content: [
                {
                    type: "text",
                    text: `Failed to add alias hostname: ${err instanceof Error ? err.message : String(err)}`,
                },
            ],
            isError: true,
        };
    }
});
server.tool("dns-lookup", "Resolve DNS records for a hostname. Replaces dig/nslookup (not installed on Pi). Supports A, AAAA, CNAME, MX, TXT, NS, and SOA record types.", {
    hostname: z
        .string()
        .describe("The hostname to resolve (e.g. 'admin.maxy.bot')"),
    type: z
        .enum(["A", "AAAA", "CNAME", "MX", "TXT", "NS", "SOA"])
        .optional()
        .describe("Record type (default: A)"),
    nameserver: z
        .string()
        .optional()
        .describe("Custom nameserver IP (default: Google 8.8.8.8 + Cloudflare 1.1.1.1)"),
}, async ({ hostname, type, nameserver }) => {
    try {
        const dns = await import("node:dns/promises");
        const resolver = new dns.Resolver();
        resolver.setServers(nameserver ? [nameserver] : ["8.8.8.8", "1.1.1.1"]);
        const recordType = type ?? "A";
        const results = {
            hostname,
            type: recordType,
        };
        switch (recordType) {
            case "A": {
                results.addresses = await resolver.resolve4(hostname);
                break;
            }
            case "AAAA": {
                results.addresses = await resolver.resolve6(hostname);
                break;
            }
            case "CNAME": {
                results.cnames = await resolver.resolveCname(hostname);
                break;
            }
            case "MX": {
                results.exchanges = await resolver.resolveMx(hostname);
                break;
            }
            case "TXT": {
                results.records = await resolver.resolveTxt(hostname);
                break;
            }
            case "NS": {
                results.nameservers = await resolver.resolveNs(hostname);
                break;
            }
            case "SOA": {
                results.soa = await resolver.resolveSoa(hostname);
                break;
            }
        }
        return {
            content: [
                { type: "text", text: JSON.stringify(results, null, 2) },
            ],
        };
    }
    catch (err) {
        const msg = err instanceof Error ? err.message : String(err);
        const code = err.code;
        return {
            content: [
                {
                    type: "text",
                    text: `DNS lookup failed: ${msg}${code ? ` (${code})` : ""}\n\nCommon causes:\n- ENOTFOUND: hostname does not exist\n- ENODATA: no records of this type for hostname\n- ETIMEOUT: nameserver unreachable`,
                },
            ],
            isError: true,
        };
    }
});
const transport = new StdioServerTransport();
await server.connect(transport);
//# sourceMappingURL=index.js.map