import { initStderrTee } from "../../../../lib/mcp-stderr-tee/dist/index.js";
initStderrTee("workflows");
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { workflowCreate } from "./tools/workflow-create.js";
import { workflowList } from "./tools/workflow-list.js";
import { workflowGet } from "./tools/workflow-get.js";
import { workflowUpdate } from "./tools/workflow-update.js";
import { workflowDelete } from "./tools/workflow-delete.js";
import { workflowValidate } from "./tools/workflow-validate.js";
import { workflowExecute } from "./tools/workflow-execute.js";
import { workflowRuns } from "./tools/workflow-runs.js";
import { closeDriver } from "./lib/neo4j.js";
// Cast to `any` to avoid TS2589. TypeScript hits its 50-level instantiation
// limit when accumulating typed tool signatures on a generic McpServer.
// Individual tool handlers retain full type safety in their own files.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const server = new McpServer({
    name: "maxy-workflows",
    version: "0.2.0",
});
const accountId = process.env.ACCOUNT_ID;
if (!accountId) {
    throw new Error("ACCOUNT_ID environment variable is required");
}
// ---------------------------------------------------------------------------
// Shared schema fragments
// ---------------------------------------------------------------------------
const workflowIdSchema = z.string().describe("Workflow ID (UUID)");
// Shared fields for both step types
const stepBase = {
    dependsOn: z.array(z.string()).optional().describe("Step IDs or position indexes (as strings) that must complete before this step runs"),
    onFailure: z.enum(["abort", "skip", "retry"]).optional().describe("What to do if this step fails (default: abort)"),
    retryLimit: z.number().int().min(1).max(3).optional().describe("Max retry attempts when onFailure is 'retry' (max 3)"),
    outputKey: z.string().optional().describe("Key name to store this step's output under, for use in subsequent step templates"),
    label: z.string().min(1).describe("Human-readable description of what this step does"),
};
const toolStepSchema = z.object({
    ...stepBase,
    type: z.literal("tool").describe("Step type: direct MCP tool call"),
    plugin: z.string().min(1).describe("Plugin name (e.g. 'memory', 'scheduling')"),
    tool: z.string().min(1).describe("Tool name within the plugin (e.g. 'contact-create')"),
    params: z.record(z.unknown()).optional().describe("Parameters for the tool call. Use {{outputKey.field}} for template variables that resolve from prior step outputs."),
    timeout: z.number().int().min(1).max(120).optional().describe("Step execution timeout in seconds (default: 30, max: 120). Increase for steps calling slow tools."),
}).strict(); // Reject unknown fields (e.g. fallbackMode accidentally set on a tool step)
const llmStepSchema = z.object({
    ...stepBase,
    type: z.literal("llm").describe("Step type: LLM reasoning via Claude Code OAuth"),
    prompt: z.string().min(1).describe("Prompt template sent to the model. Use {{outputKey.field}} for template variables from prior step outputs. " +
        "Instruct the model to return JSON for structured output that downstream steps can reference."),
    model: z.string().optional().describe("Anthropic model ID (e.g. 'claude-sonnet-4-5-20250514'). Defaults to the account's adminModel."),
    maxTokens: z.number().int().min(1).max(16384).optional().describe("Maximum output tokens (default: 4096)"),
    timeout: z.number().int().min(1).max(300).optional().describe("Step execution timeout in seconds (default: 60, max: 300). LLM calls are slower than tool calls."),
    fallbackMode: z.enum(["fail", "raw"]).optional().describe("Behaviour on LLM unavailability. 'fail' (default) applies the step's onFailure policy. " +
        "'raw' emits a degraded output (banner + prior step data) via the fallback template and " +
        "marks the step as degraded. Pre-flight probe failure or runtime spawn failure both trigger the fallback."),
    fallbackTemplate: z.string().min(1).max(4096).optional().describe("Custom fallback text template used when fallbackMode='raw'. Supports {{_error}}, {{_priorOutputs}}, " +
        "and any {{stepKey.field}} reference. Unresolved refs become '<unresolved: name>' rather than throwing. " +
        "Defaults to a standard banner + prior-output dump if omitted."),
});
// Cross-field validation lives on the discriminated union because Zod v3
// does not accept ZodEffects inside discriminatedUnion members.
const stepSchema = z.discriminatedUnion("type", [toolStepSchema, llmStepSchema])
    .superRefine((data, ctx) => {
    if (data.type !== "llm")
        return;
    // fallbackTemplate only makes sense alongside fallbackMode='raw'.
    // Reject silently-ignored config — it hides authoring errors until an outage.
    if (data.fallbackTemplate !== undefined && data.fallbackMode !== "raw") {
        ctx.addIssue({
            code: z.ZodIssueCode.custom,
            message: "fallbackTemplate requires fallbackMode='raw' — it has no effect otherwise.",
            path: ["fallbackTemplate"],
        });
    }
    // fallbackMode='raw' needs outputKey so downstream steps can consume the fallback.
    // Without outputKey the degraded output has nowhere to go.
    if (data.fallbackMode === "raw" && !data.outputKey) {
        ctx.addIssue({
            code: z.ZodIssueCode.custom,
            message: "fallbackMode='raw' requires outputKey — the degraded output must be bound to a key so downstream delivery steps can consume it.",
            path: ["fallbackMode"],
        });
    }
});
// ---------------------------------------------------------------------------
// workflow-create
// ---------------------------------------------------------------------------
server.tool("workflow-create", "Create a persistent workflow with structured executable steps. Steps can be type 'tool' (MCP tool call) or type 'llm' (LLM reasoning via prompt template). Validates that referenced plugins exist for tool steps — sets status to 'draft' if any are missing, 'active' if all are valid.", {
    name: z.string().min(1).describe("Short workflow name (e.g. 'Viewing follow-up')"),
    description: z
        .string()
        .min(1)
        .describe("What this workflow does — used for discovery via semantic search"),
    steps: z.array(stepSchema).min(1).max(50).describe("Ordered list of executable steps. Each step is type 'tool' (plugin tool call) or type 'llm' (LLM reasoning)."),
}, async (params) => {
    try {
        const result = await workflowCreate({ ...params, accountId });
        const parts = [`Workflow created: "${params.name}" (ID: ${result.workflowId}, status: ${result.status})`];
        if (result.validationErrors.length > 0) {
            parts.push("");
            parts.push("Validation issues (workflow set to draft):");
            for (const err of result.validationErrors) {
                parts.push(`  - ${err}`);
            }
        }
        return {
            content: [{ type: "text", text: parts.join("\n") }],
        };
    }
    catch (err) {
        return {
            content: [
                {
                    type: "text",
                    text: `Failed to create workflow: ${err instanceof Error ? err.message : String(err)}`,
                },
            ],
            isError: true,
        };
    }
});
// ---------------------------------------------------------------------------
// workflow-list
// ---------------------------------------------------------------------------
server.tool("workflow-list", "List workflows for this account, optionally filtered by status. Returns name, description, status, and step count for each.", {
    status: z
        .enum(["active", "paused", "draft"])
        .optional()
        .describe("Filter by workflow status"),
    limit: z.number().int().min(1).max(100).optional().describe("Max results (default 50)"),
}, async (params) => {
    try {
        const workflows = await workflowList({ ...params, accountId });
        if (workflows.length === 0) {
            return {
                content: [{ type: "text", text: "No workflows found." }],
            };
        }
        const lines = workflows.map((w) => `[${w.status.toUpperCase()}] ${w.name} — ${w.description} (${w.stepCount} steps, ${w.workflowId})`);
        return {
            content: [
                {
                    type: "text",
                    text: `${workflows.length} workflow(s):\n\n${lines.join("\n")}`,
                },
            ],
        };
    }
    catch (err) {
        return {
            content: [
                {
                    type: "text",
                    text: `List failed: ${err instanceof Error ? err.message : String(err)}`,
                },
            ],
            isError: true,
        };
    }
});
// ---------------------------------------------------------------------------
// workflow-get
// ---------------------------------------------------------------------------
server.tool("workflow-get", "Fetch a single workflow by ID. Returns all fields including structured steps with their tool references and parameters.", {
    workflowId: workflowIdSchema,
}, async ({ workflowId: wfId }) => {
    try {
        const workflow = await workflowGet(wfId, accountId);
        if (!workflow) {
            return {
                content: [
                    { type: "text", text: `Workflow not found: ${wfId}` },
                ],
                isError: true,
            };
        }
        const stepLines = workflow.steps.map((s, i) => {
            const header = s.type === "llm"
                ? `  ${i + 1}. [llm] ${s.label}${s.model ? ` (${s.model})` : ""}`
                : `  ${i + 1}. [tool] ${s.label} → ${s.plugin}/${s.tool}`;
            const parts = [header];
            if (s.type === "llm" && s.prompt) {
                const promptPreview = s.prompt.length > 120 ? s.prompt.slice(0, 120) + "…" : s.prompt;
                parts.push(`     Prompt: ${promptPreview}`);
                if (s.maxTokens)
                    parts.push(`     Max tokens: ${s.maxTokens}`);
            }
            if (s.params)
                parts.push(`     Params: ${JSON.stringify(s.params)}`);
            if (s.outputKey)
                parts.push(`     Output key: ${s.outputKey}`);
            if (s.dependsOn && s.dependsOn.length > 0)
                parts.push(`     Depends on: ${s.dependsOn.join(", ")}`);
            parts.push(`     On failure: ${s.onFailure}${s.retryLimit ? ` (max ${s.retryLimit} retries)` : ""}`);
            return parts.join("\n");
        });
        const output = [
            `Workflow: ${workflow.name}`,
            `ID: ${workflow.workflowId}`,
            `Status: ${workflow.status}`,
            `Steps: ${workflow.steps.length}`,
            ``,
            `Description:\n${workflow.description}`,
            ``,
            `Steps:`,
            ...stepLines,
            ``,
            `Created: ${workflow.createdAt} | Updated: ${workflow.updatedAt}`,
        ];
        return {
            content: [{ type: "text", text: output.join("\n") }],
        };
    }
    catch (err) {
        return {
            content: [
                {
                    type: "text",
                    text: `Get failed: ${err instanceof Error ? err.message : String(err)}`,
                },
            ],
            isError: true,
        };
    }
});
// ---------------------------------------------------------------------------
// workflow-update
// ---------------------------------------------------------------------------
server.tool("workflow-update", "Update a workflow's name, description, steps, or status. When steps are provided, replaces all existing steps and re-validates capabilities. Re-embeds automatically when name or description changes.", {
    workflowId: workflowIdSchema,
    name: z.string().min(1).optional().describe("New workflow name"),
    description: z.string().min(1).optional().describe("New description"),
    steps: z.array(stepSchema).min(1).max(50).optional().describe("New step list — replaces all existing steps. Each step is type 'tool' or 'llm'. Triggers re-validation."),
    status: z
        .enum(["active", "paused"])
        .optional()
        .describe("New status (use 'paused' to deactivate without deleting)"),
}, async (params) => {
    const { workflowId: wfId, ...updates } = params;
    const hasUpdate = Object.values(updates).some((v) => v !== undefined);
    if (!hasUpdate) {
        return {
            content: [
                {
                    type: "text",
                    text: "No update fields provided. Specify at least one of: name, description, steps, status.",
                },
            ],
            isError: true,
        };
    }
    try {
        await workflowUpdate({ ...params, accountId });
        return {
            content: [
                { type: "text", text: `Workflow updated: ${wfId}` },
            ],
        };
    }
    catch (err) {
        return {
            content: [
                {
                    type: "text",
                    text: `Update failed: ${err instanceof Error ? err.message : String(err)}`,
                },
            ],
            isError: true,
        };
    }
});
// ---------------------------------------------------------------------------
// workflow-delete
// ---------------------------------------------------------------------------
server.tool("workflow-delete", "Permanently remove a workflow and all associated steps, run history, and step results. Confirm with the user before deleting.", {
    workflowId: workflowIdSchema,
}, async ({ workflowId: wfId }) => {
    try {
        await workflowDelete(wfId, accountId);
        return {
            content: [
                { type: "text", text: `Workflow deleted: ${wfId}` },
            ],
        };
    }
    catch (err) {
        return {
            content: [
                {
                    type: "text",
                    text: `Delete failed: ${err instanceof Error ? err.message : String(err)}`,
                },
            ],
            isError: true,
        };
    }
});
// ---------------------------------------------------------------------------
// workflow-validate
// ---------------------------------------------------------------------------
server.tool("workflow-validate", "Validate a workflow's step capabilities. Checks that every referenced plugin directory and MCP server exist. Updates status to 'active' if all valid, 'draft' if any are missing.", {
    workflowId: workflowIdSchema,
}, async ({ workflowId: wfId }) => {
    try {
        const result = await workflowValidate(wfId, accountId);
        if (result.valid) {
            return {
                content: [
                    {
                        type: "text",
                        text: `Workflow "${wfId}" validated successfully — status: active. All step capabilities are available.`,
                    },
                ],
            };
        }
        const parts = [
            `Workflow "${wfId}" has validation issues — status: draft.`,
            "",
            "Missing capabilities:",
        ];
        for (const err of result.errors) {
            parts.push(`  - ${err}`);
        }
        return {
            content: [{ type: "text", text: parts.join("\n") }],
        };
    }
    catch (err) {
        return {
            content: [
                {
                    type: "text",
                    text: `Validation failed: ${err instanceof Error ? err.message : String(err)}`,
                },
            ],
            isError: true,
        };
    }
});
// ---------------------------------------------------------------------------
// workflow-execute
// ---------------------------------------------------------------------------
server.tool("workflow-execute", "Execute a workflow. Runs each step in dependency order, dispatching MCP tool calls to the referenced plugins. Creates a WorkflowRun record with per-step results. Only active workflows can be executed.", {
    workflowId: workflowIdSchema,
    context: z.record(z.unknown()).optional().describe("Runtime variables available to step templates (e.g. { email: 'user@example.com' })"),
    trigger: z
        .enum(["conversational", "schedule", "manual"])
        .optional()
        .describe("What triggered this execution (default: conversational)"),
}, async (params) => {
    try {
        const result = await workflowExecute({ ...params, accountId });
        const parts = [
            `Workflow run ${result.status}: ${result.runId}`,
            `Steps: ${result.stepsCompleted} completed, ${result.stepsDegraded} degraded, ${result.stepsSkipped} skipped, ${result.stepsFailed} failed (${result.stepsTotal} total)`,
        ];
        if (result.error) {
            parts.push(`Error: ${result.error}`);
        }
        return {
            content: [{ type: "text", text: parts.join("\n") }],
        };
    }
    catch (err) {
        return {
            content: [
                {
                    type: "text",
                    text: `Execution failed: ${err instanceof Error ? err.message : String(err)}`,
                },
            ],
            isError: true,
        };
    }
});
// ---------------------------------------------------------------------------
// workflow-runs
// ---------------------------------------------------------------------------
server.tool("workflow-runs", "Query workflow execution history. Returns recent runs with status, timing, and per-step outcome counts. Optionally filter by workflow ID or run status.", {
    workflowId: z.string().optional().describe("Filter to a specific workflow's runs"),
    status: z
        .enum(["running", "completed", "partial", "failed"])
        .optional()
        .describe("Filter by run status"),
    limit: z.number().int().min(1).max(100).optional().describe("Max results (default 20)"),
}, async (params) => {
    try {
        const runs = await workflowRuns({ ...params, accountId });
        if (runs.length === 0) {
            return {
                content: [{ type: "text", text: "No workflow runs found." }],
            };
        }
        const lines = runs.map((r) => {
            const duration = r.completedAt
                ? `${new Date(r.completedAt).getTime() - new Date(r.startedAt).getTime()}ms`
                : "running";
            return [
                `[${r.status.toUpperCase()}] ${r.workflowName} — ${r.trigger} trigger`,
                `  Run: ${r.runId}`,
                `  Started: ${r.startedAt} | Duration: ${duration}`,
                `  Steps: ${r.stepsCompleted}/${r.stepsTotal} completed, ${r.stepsDegraded} degraded, ${r.stepsSkipped} skipped, ${r.stepsFailed} failed`,
                r.error ? `  Error: ${r.error}` : null,
            ]
                .filter(Boolean)
                .join("\n");
        });
        return {
            content: [
                {
                    type: "text",
                    text: `${runs.length} run(s):\n\n${lines.join("\n\n")}`,
                },
            ],
        };
    }
    catch (err) {
        return {
            content: [
                {
                    type: "text",
                    text: `Query failed: ${err instanceof Error ? err.message : String(err)}`,
                },
            ],
            isError: true,
        };
    }
});
// ---------------------------------------------------------------------------
// Shutdown + start
// ---------------------------------------------------------------------------
process.on("SIGINT", async () => {
    await closeDriver();
    process.exit(0);
});
const transport = new StdioServerTransport();
await server.connect(transport);
//# sourceMappingURL=index.js.map