315 lines
11 KiB
TypeScript
315 lines
11 KiB
TypeScript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
import { z } from "zod";
|
|
import { novuRequest, toolResult, toolError } from "../services/api-client.js";
|
|
import { PaginationV1Schema } from "../schemas/common.js";
|
|
|
|
export function registerWorkflowsTools(server: McpServer): void {
|
|
// ── List Workflows ──────────────────────────────────────────────────
|
|
server.registerTool(
|
|
"novu_list_workflows",
|
|
{
|
|
title: "List Novu Workflows",
|
|
description:
|
|
"List all notification workflows in the current Novu environment. " +
|
|
"Returns workflow names, IDs, statuses, and step summaries. " +
|
|
"Supports offset-based pagination.",
|
|
inputSchema: {
|
|
...PaginationV1Schema,
|
|
},
|
|
annotations: {
|
|
readOnlyHint: true,
|
|
destructiveHint: false,
|
|
idempotentHint: true,
|
|
openWorldHint: true,
|
|
},
|
|
},
|
|
async (params) => {
|
|
try {
|
|
const result = await novuRequest("/v1/workflows", "GET", undefined, {
|
|
limit: params.limit,
|
|
offset: params.offset,
|
|
});
|
|
return toolResult(JSON.stringify(result, null, 2));
|
|
} catch (error) {
|
|
return toolError(error);
|
|
}
|
|
}
|
|
);
|
|
|
|
// ── Get Workflow ────────────────────────────────────────────────────
|
|
server.registerTool(
|
|
"novu_get_workflow",
|
|
{
|
|
title: "Get Novu Workflow",
|
|
description:
|
|
"Retrieve a single workflow by its ID. " +
|
|
"Returns the full workflow definition including steps, triggers, tags, and status.",
|
|
inputSchema: {
|
|
workflow_id: z.string().describe("The unique ID of the workflow to retrieve"),
|
|
},
|
|
annotations: {
|
|
readOnlyHint: true,
|
|
destructiveHint: false,
|
|
idempotentHint: true,
|
|
openWorldHint: true,
|
|
},
|
|
},
|
|
async (params) => {
|
|
try {
|
|
const result = await novuRequest(
|
|
`/v1/workflows/${params.workflow_id}`,
|
|
"GET"
|
|
);
|
|
return toolResult(JSON.stringify(result, null, 2));
|
|
} catch (error) {
|
|
return toolError(error);
|
|
}
|
|
}
|
|
);
|
|
|
|
// ── Create Workflow ─────────────────────────────────────────────────
|
|
server.registerTool(
|
|
"novu_create_workflow",
|
|
{
|
|
title: "Create Novu Workflow",
|
|
description:
|
|
"Create a new notification workflow. " +
|
|
"At minimum, provide a name. Optionally configure description, tags, " +
|
|
"active status, steps, notification group, and critical flag. " +
|
|
"Steps and triggers are complex nested objects; pass them as arrays of objects.",
|
|
inputSchema: {
|
|
name: z.string().describe("Name of the workflow"),
|
|
description: z
|
|
.string()
|
|
.optional()
|
|
.describe("Description of the workflow"),
|
|
tags: z
|
|
.array(z.string())
|
|
.optional()
|
|
.describe("Tags to categorize the workflow"),
|
|
active: z
|
|
.boolean()
|
|
.optional()
|
|
.describe("Whether the workflow is active (default: true)"),
|
|
steps: z
|
|
.array(z.record(z.unknown()))
|
|
.optional()
|
|
.describe(
|
|
"Array of step definition objects. Each step typically includes " +
|
|
"a template (with type, content, subject, etc.) and optional filters."
|
|
),
|
|
notificationGroupId: z
|
|
.string()
|
|
.optional()
|
|
.describe("ID of the notification group this workflow belongs to"),
|
|
critical: z
|
|
.boolean()
|
|
.optional()
|
|
.describe(
|
|
"Whether this workflow is critical. Critical workflows are always sent, " +
|
|
"even if the subscriber has disabled notifications."
|
|
),
|
|
},
|
|
annotations: {
|
|
readOnlyHint: false,
|
|
destructiveHint: false,
|
|
idempotentHint: false,
|
|
openWorldHint: true,
|
|
},
|
|
},
|
|
async (params) => {
|
|
try {
|
|
const body: Record<string, unknown> = { name: params.name };
|
|
if (params.description !== undefined)
|
|
body.description = params.description;
|
|
if (params.tags !== undefined) body.tags = params.tags;
|
|
if (params.active !== undefined) body.active = params.active;
|
|
if (params.steps !== undefined) body.steps = params.steps;
|
|
if (params.notificationGroupId !== undefined)
|
|
body.notificationGroupId = params.notificationGroupId;
|
|
if (params.critical !== undefined) body.critical = params.critical;
|
|
|
|
const result = await novuRequest("/v1/workflows", "POST", body);
|
|
return toolResult(JSON.stringify(result, null, 2));
|
|
} catch (error) {
|
|
return toolError(error);
|
|
}
|
|
}
|
|
);
|
|
|
|
// ── Update Workflow ─────────────────────────────────────────────────
|
|
server.registerTool(
|
|
"novu_update_workflow",
|
|
{
|
|
title: "Update Novu Workflow",
|
|
description:
|
|
"Update an existing workflow by its ID. " +
|
|
"Provide only the fields you want to change. " +
|
|
"Steps and triggers are complex nested objects; pass them as arrays of objects.",
|
|
inputSchema: {
|
|
workflow_id: z.string().describe("The unique ID of the workflow to update"),
|
|
name: z.string().optional().describe("Updated name of the workflow"),
|
|
description: z
|
|
.string()
|
|
.optional()
|
|
.describe("Updated description of the workflow"),
|
|
tags: z
|
|
.array(z.string())
|
|
.optional()
|
|
.describe("Updated tags to categorize the workflow"),
|
|
active: z
|
|
.boolean()
|
|
.optional()
|
|
.describe("Whether the workflow is active"),
|
|
steps: z
|
|
.array(z.record(z.unknown()))
|
|
.optional()
|
|
.describe(
|
|
"Updated array of step definition objects. Each step typically includes " +
|
|
"a template (with type, content, subject, etc.) and optional filters."
|
|
),
|
|
notificationGroupId: z
|
|
.string()
|
|
.optional()
|
|
.describe("ID of the notification group this workflow belongs to"),
|
|
critical: z
|
|
.boolean()
|
|
.optional()
|
|
.describe(
|
|
"Whether this workflow is critical. Critical workflows are always sent, " +
|
|
"even if the subscriber has disabled notifications."
|
|
),
|
|
},
|
|
annotations: {
|
|
readOnlyHint: false,
|
|
destructiveHint: false,
|
|
idempotentHint: true,
|
|
openWorldHint: true,
|
|
},
|
|
},
|
|
async (params) => {
|
|
try {
|
|
const body: Record<string, unknown> = {};
|
|
if (params.name !== undefined) body.name = params.name;
|
|
if (params.description !== undefined)
|
|
body.description = params.description;
|
|
if (params.tags !== undefined) body.tags = params.tags;
|
|
if (params.active !== undefined) body.active = params.active;
|
|
if (params.steps !== undefined) body.steps = params.steps;
|
|
if (params.notificationGroupId !== undefined)
|
|
body.notificationGroupId = params.notificationGroupId;
|
|
if (params.critical !== undefined) body.critical = params.critical;
|
|
|
|
const result = await novuRequest(
|
|
`/v1/workflows/${params.workflow_id}`,
|
|
"PUT",
|
|
body
|
|
);
|
|
return toolResult(JSON.stringify(result, null, 2));
|
|
} catch (error) {
|
|
return toolError(error);
|
|
}
|
|
}
|
|
);
|
|
|
|
// ── Delete Workflow ─────────────────────────────────────────────────
|
|
server.registerTool(
|
|
"novu_delete_workflow",
|
|
{
|
|
title: "Delete Novu Workflow",
|
|
description:
|
|
"Permanently delete a workflow by its ID. " +
|
|
"This action cannot be undone. All associated triggers will stop working.",
|
|
inputSchema: {
|
|
workflow_id: z.string().describe("The unique ID of the workflow to delete"),
|
|
},
|
|
annotations: {
|
|
readOnlyHint: false,
|
|
destructiveHint: true,
|
|
idempotentHint: true,
|
|
openWorldHint: true,
|
|
},
|
|
},
|
|
async (params) => {
|
|
try {
|
|
const result = await novuRequest(
|
|
`/v1/workflows/${params.workflow_id}`,
|
|
"DELETE"
|
|
);
|
|
return toolResult(
|
|
JSON.stringify(
|
|
result ?? { success: true, message: "Workflow deleted successfully" },
|
|
null,
|
|
2
|
|
)
|
|
);
|
|
} catch (error) {
|
|
return toolError(error);
|
|
}
|
|
}
|
|
);
|
|
|
|
// ── Sync Workflow ───────────────────────────────────────────────────
|
|
server.registerTool(
|
|
"novu_sync_workflow",
|
|
{
|
|
title: "Sync Novu Workflow",
|
|
description:
|
|
"Sync a workflow to the target environment. " +
|
|
"Use this to promote workflow changes between environments (e.g., dev to production).",
|
|
inputSchema: {
|
|
workflow_id: z.string().describe("The unique ID of the workflow to sync"),
|
|
},
|
|
annotations: {
|
|
readOnlyHint: false,
|
|
destructiveHint: false,
|
|
idempotentHint: true,
|
|
openWorldHint: true,
|
|
},
|
|
},
|
|
async (params) => {
|
|
try {
|
|
const result = await novuRequest(
|
|
`/v1/workflows/${params.workflow_id}/sync`,
|
|
"PUT"
|
|
);
|
|
return toolResult(JSON.stringify(result, null, 2));
|
|
} catch (error) {
|
|
return toolError(error);
|
|
}
|
|
}
|
|
);
|
|
|
|
// ── Get Workflow Step ───────────────────────────────────────────────
|
|
server.registerTool(
|
|
"novu_get_workflow_step",
|
|
{
|
|
title: "Get Novu Workflow Step",
|
|
description:
|
|
"Retrieve a specific step from a workflow. " +
|
|
"Returns the full step definition including its template, filters, and metadata.",
|
|
inputSchema: {
|
|
workflow_id: z.string().describe("The unique ID of the workflow"),
|
|
step_id: z.string().describe("The unique ID of the step within the workflow"),
|
|
},
|
|
annotations: {
|
|
readOnlyHint: true,
|
|
destructiveHint: false,
|
|
idempotentHint: true,
|
|
openWorldHint: true,
|
|
},
|
|
},
|
|
async (params) => {
|
|
try {
|
|
const result = await novuRequest(
|
|
`/v1/workflows/${params.workflow_id}/steps/${params.step_id}`,
|
|
"GET"
|
|
);
|
|
return toolResult(JSON.stringify(result, null, 2));
|
|
} catch (error) {
|
|
return toolError(error);
|
|
}
|
|
}
|
|
);
|
|
}
|