feat: add all tool domain files (events, subscribers, topics, workflows, notifications, messages, integrations, environments, translations, contexts, channels)
This commit is contained in:
@@ -0,0 +1,652 @@
|
||||
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";
|
||||
|
||||
// Reusable schema fragments for subscriber fields
|
||||
const SubscriberOptionalFields = {
|
||||
first_name: z.string().optional().describe("Subscriber's first name"),
|
||||
last_name: z.string().optional().describe("Subscriber's last name"),
|
||||
email: z.string().optional().describe("Subscriber's email address"),
|
||||
phone: z.string().optional().describe("Subscriber's phone number"),
|
||||
avatar: z.string().optional().describe("URL to subscriber's avatar image"),
|
||||
locale: z.string().optional().describe("Subscriber's locale (e.g., 'en-US')"),
|
||||
timezone: z.string().optional().describe("Subscriber's timezone (e.g., 'America/New_York')"),
|
||||
data: z
|
||||
.record(z.unknown())
|
||||
.optional()
|
||||
.describe("Custom data object for the subscriber"),
|
||||
};
|
||||
|
||||
/** Map snake_case optional subscriber params to camelCase API body. */
|
||||
function mapSubscriberFields(params: {
|
||||
first_name?: string;
|
||||
last_name?: string;
|
||||
email?: string;
|
||||
phone?: string;
|
||||
avatar?: string;
|
||||
locale?: string;
|
||||
timezone?: string;
|
||||
data?: Record<string, unknown>;
|
||||
}): Record<string, unknown> {
|
||||
const body: Record<string, unknown> = {};
|
||||
if (params.first_name !== undefined) body.firstName = params.first_name;
|
||||
if (params.last_name !== undefined) body.lastName = params.last_name;
|
||||
if (params.email !== undefined) body.email = params.email;
|
||||
if (params.phone !== undefined) body.phone = params.phone;
|
||||
if (params.avatar !== undefined) body.avatar = params.avatar;
|
||||
if (params.locale !== undefined) body.locale = params.locale;
|
||||
if (params.timezone !== undefined) body.timezone = params.timezone;
|
||||
if (params.data !== undefined) body.data = params.data;
|
||||
return body;
|
||||
}
|
||||
|
||||
export function registerSubscribersTools(server: McpServer): void {
|
||||
// ── 1. Create Subscriber ──────────────────────────────────────────────
|
||||
server.registerTool(
|
||||
"novu_create_subscriber",
|
||||
{
|
||||
title: "Create Novu Subscriber",
|
||||
description:
|
||||
"Create a new subscriber in Novu with an identifier and optional profile fields.",
|
||||
inputSchema: {
|
||||
subscriber_id: z.string().describe("Unique subscriber identifier"),
|
||||
...SubscriberOptionalFields,
|
||||
},
|
||||
annotations: {
|
||||
readOnlyHint: false,
|
||||
destructiveHint: false,
|
||||
idempotentHint: false,
|
||||
openWorldHint: true,
|
||||
},
|
||||
},
|
||||
async (params) => {
|
||||
try {
|
||||
const result = await novuRequest("/v1/subscribers", "POST", {
|
||||
subscriberId: params.subscriber_id,
|
||||
...mapSubscriberFields(params),
|
||||
});
|
||||
return toolResult(JSON.stringify(result, null, 2));
|
||||
} catch (error) {
|
||||
return toolError(error);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// ── 2. Get Subscriber ─────────────────────────────────────────────────
|
||||
server.registerTool(
|
||||
"novu_get_subscriber",
|
||||
{
|
||||
title: "Get Novu Subscriber",
|
||||
description: "Retrieve a subscriber by their unique identifier.",
|
||||
inputSchema: {
|
||||
subscriber_id: z.string().describe("Unique subscriber identifier"),
|
||||
},
|
||||
annotations: {
|
||||
readOnlyHint: true,
|
||||
destructiveHint: false,
|
||||
idempotentHint: true,
|
||||
openWorldHint: true,
|
||||
},
|
||||
},
|
||||
async (params) => {
|
||||
try {
|
||||
const result = await novuRequest(
|
||||
`/v1/subscribers/${encodeURIComponent(params.subscriber_id)}`,
|
||||
"GET"
|
||||
);
|
||||
return toolResult(JSON.stringify(result, null, 2));
|
||||
} catch (error) {
|
||||
return toolError(error);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// ── 3. Update Subscriber ──────────────────────────────────────────────
|
||||
server.registerTool(
|
||||
"novu_update_subscriber",
|
||||
{
|
||||
title: "Update Novu Subscriber",
|
||||
description:
|
||||
"Update an existing subscriber's profile fields. Only provided fields are changed.",
|
||||
inputSchema: {
|
||||
subscriber_id: z.string().describe("Unique subscriber identifier"),
|
||||
...SubscriberOptionalFields,
|
||||
},
|
||||
annotations: {
|
||||
readOnlyHint: false,
|
||||
destructiveHint: false,
|
||||
idempotentHint: true,
|
||||
openWorldHint: true,
|
||||
},
|
||||
},
|
||||
async (params) => {
|
||||
try {
|
||||
const result = await novuRequest(
|
||||
`/v1/subscribers/${encodeURIComponent(params.subscriber_id)}`,
|
||||
"PATCH",
|
||||
mapSubscriberFields(params)
|
||||
);
|
||||
return toolResult(JSON.stringify(result, null, 2));
|
||||
} catch (error) {
|
||||
return toolError(error);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// ── 4. Delete Subscriber ──────────────────────────────────────────────
|
||||
server.registerTool(
|
||||
"novu_delete_subscriber",
|
||||
{
|
||||
title: "Delete Novu Subscriber",
|
||||
description:
|
||||
"Permanently delete a subscriber and all associated data. This action cannot be undone.",
|
||||
inputSchema: {
|
||||
subscriber_id: z.string().describe("Unique subscriber identifier"),
|
||||
},
|
||||
annotations: {
|
||||
readOnlyHint: false,
|
||||
destructiveHint: true,
|
||||
idempotentHint: true,
|
||||
openWorldHint: true,
|
||||
},
|
||||
},
|
||||
async (params) => {
|
||||
try {
|
||||
const result = await novuRequest(
|
||||
`/v1/subscribers/${encodeURIComponent(params.subscriber_id)}`,
|
||||
"DELETE"
|
||||
);
|
||||
return toolResult(JSON.stringify(result, null, 2));
|
||||
} catch (error) {
|
||||
return toolError(error);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// ── 5. Search Subscribers ─────────────────────────────────────────────
|
||||
server.registerTool(
|
||||
"novu_search_subscribers",
|
||||
{
|
||||
title: "Search Novu Subscribers",
|
||||
description:
|
||||
"List subscribers with optional pagination. Returns a paginated list.",
|
||||
inputSchema: {
|
||||
...PaginationV1Schema,
|
||||
},
|
||||
annotations: {
|
||||
readOnlyHint: true,
|
||||
destructiveHint: false,
|
||||
idempotentHint: true,
|
||||
openWorldHint: true,
|
||||
},
|
||||
},
|
||||
async (params) => {
|
||||
try {
|
||||
const result = await novuRequest(
|
||||
"/v1/subscribers",
|
||||
"GET",
|
||||
undefined,
|
||||
{ limit: params.limit, offset: params.offset }
|
||||
);
|
||||
return toolResult(JSON.stringify(result, null, 2));
|
||||
} catch (error) {
|
||||
return toolError(error);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// ── 6. Bulk Create Subscribers ────────────────────────────────────────
|
||||
server.registerTool(
|
||||
"novu_bulk_create_subscribers",
|
||||
{
|
||||
title: "Bulk Create Novu Subscribers",
|
||||
description:
|
||||
"Create multiple subscribers in a single request. Each subscriber object must include a subscriberId.",
|
||||
inputSchema: {
|
||||
subscribers: z
|
||||
.array(
|
||||
z.object({
|
||||
subscriberId: z.string().describe("Unique subscriber identifier"),
|
||||
firstName: z.string().optional().describe("First name"),
|
||||
lastName: z.string().optional().describe("Last name"),
|
||||
email: z.string().optional().describe("Email address"),
|
||||
phone: z.string().optional().describe("Phone number"),
|
||||
avatar: z.string().optional().describe("Avatar URL"),
|
||||
locale: z.string().optional().describe("Locale"),
|
||||
timezone: z.string().optional().describe("Timezone"),
|
||||
data: z
|
||||
.record(z.unknown())
|
||||
.optional()
|
||||
.describe("Custom data"),
|
||||
})
|
||||
)
|
||||
.describe("Array of subscriber objects to create"),
|
||||
},
|
||||
annotations: {
|
||||
readOnlyHint: false,
|
||||
destructiveHint: false,
|
||||
idempotentHint: false,
|
||||
openWorldHint: true,
|
||||
},
|
||||
},
|
||||
async (params) => {
|
||||
try {
|
||||
const result = await novuRequest(
|
||||
"/v1/subscribers/bulk",
|
||||
"POST",
|
||||
{ subscribers: params.subscribers }
|
||||
);
|
||||
return toolResult(JSON.stringify(result, null, 2));
|
||||
} catch (error) {
|
||||
return toolError(error);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// ── 7. Get Subscriber Preferences ─────────────────────────────────────
|
||||
server.registerTool(
|
||||
"novu_get_subscriber_preferences",
|
||||
{
|
||||
title: "Get Subscriber Preferences",
|
||||
description:
|
||||
"Retrieve notification preferences for a subscriber.",
|
||||
inputSchema: {
|
||||
subscriber_id: z.string().describe("Unique subscriber identifier"),
|
||||
},
|
||||
annotations: {
|
||||
readOnlyHint: true,
|
||||
destructiveHint: false,
|
||||
idempotentHint: true,
|
||||
openWorldHint: true,
|
||||
},
|
||||
},
|
||||
async (params) => {
|
||||
try {
|
||||
const result = await novuRequest(
|
||||
`/v1/subscribers/${encodeURIComponent(params.subscriber_id)}/preferences`,
|
||||
"GET"
|
||||
);
|
||||
return toolResult(JSON.stringify(result, null, 2));
|
||||
} catch (error) {
|
||||
return toolError(error);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// ── 8. Update Subscriber Preferences ──────────────────────────────────
|
||||
server.registerTool(
|
||||
"novu_update_subscriber_preferences",
|
||||
{
|
||||
title: "Update Subscriber Preferences",
|
||||
description:
|
||||
"Update notification preferences for a subscriber.",
|
||||
inputSchema: {
|
||||
subscriber_id: z.string().describe("Unique subscriber identifier"),
|
||||
channel: z
|
||||
.object({
|
||||
type: z
|
||||
.enum(["in_app", "email", "sms", "chat", "push"])
|
||||
.describe("Channel type"),
|
||||
enabled: z.boolean().describe("Whether the channel is enabled"),
|
||||
})
|
||||
.optional()
|
||||
.describe("Channel preference to update"),
|
||||
enabled: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe("Global preference enabled state"),
|
||||
},
|
||||
annotations: {
|
||||
readOnlyHint: false,
|
||||
destructiveHint: false,
|
||||
idempotentHint: true,
|
||||
openWorldHint: true,
|
||||
},
|
||||
},
|
||||
async (params) => {
|
||||
try {
|
||||
const body: Record<string, unknown> = {};
|
||||
if (params.channel !== undefined) body.channel = params.channel;
|
||||
if (params.enabled !== undefined) body.enabled = params.enabled;
|
||||
|
||||
const result = await novuRequest(
|
||||
`/v1/subscribers/${encodeURIComponent(params.subscriber_id)}/preferences`,
|
||||
"PATCH",
|
||||
body
|
||||
);
|
||||
return toolResult(JSON.stringify(result, null, 2));
|
||||
} catch (error) {
|
||||
return toolError(error);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// ── 9. Bulk Update Subscriber Preferences ─────────────────────────────
|
||||
server.registerTool(
|
||||
"novu_bulk_update_subscriber_preferences",
|
||||
{
|
||||
title: "Bulk Update Subscriber Preferences",
|
||||
description:
|
||||
"Update multiple notification preferences for a subscriber at once.",
|
||||
inputSchema: {
|
||||
subscriber_id: z.string().describe("Unique subscriber identifier"),
|
||||
preferences: z
|
||||
.array(
|
||||
z.object({
|
||||
workflowId: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe("Workflow identifier to update preferences for"),
|
||||
channel: z
|
||||
.object({
|
||||
type: z
|
||||
.enum(["in_app", "email", "sms", "chat", "push"])
|
||||
.describe("Channel type"),
|
||||
enabled: z
|
||||
.boolean()
|
||||
.describe("Whether the channel is enabled"),
|
||||
})
|
||||
.optional()
|
||||
.describe("Channel preference"),
|
||||
enabled: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe("Global preference enabled state"),
|
||||
})
|
||||
)
|
||||
.describe("Array of preference objects to update"),
|
||||
},
|
||||
annotations: {
|
||||
readOnlyHint: false,
|
||||
destructiveHint: false,
|
||||
idempotentHint: true,
|
||||
openWorldHint: true,
|
||||
},
|
||||
},
|
||||
async (params) => {
|
||||
try {
|
||||
const result = await novuRequest(
|
||||
`/v1/subscribers/${encodeURIComponent(params.subscriber_id)}/preferences/bulk`,
|
||||
"PATCH",
|
||||
{ preferences: params.preferences }
|
||||
);
|
||||
return toolResult(JSON.stringify(result, null, 2));
|
||||
} catch (error) {
|
||||
return toolError(error);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// ── 10. Get Subscriber Subscriptions ──────────────────────────────────
|
||||
server.registerTool(
|
||||
"novu_get_subscriber_subscriptions",
|
||||
{
|
||||
title: "Get Subscriber Subscriptions",
|
||||
description:
|
||||
"Retrieve topic subscriptions for a subscriber.",
|
||||
inputSchema: {
|
||||
subscriber_id: z.string().describe("Unique subscriber identifier"),
|
||||
},
|
||||
annotations: {
|
||||
readOnlyHint: true,
|
||||
destructiveHint: false,
|
||||
idempotentHint: true,
|
||||
openWorldHint: true,
|
||||
},
|
||||
},
|
||||
async (params) => {
|
||||
try {
|
||||
const result = await novuRequest(
|
||||
`/v1/subscribers/${encodeURIComponent(params.subscriber_id)}/subscriptions`,
|
||||
"GET"
|
||||
);
|
||||
return toolResult(JSON.stringify(result, null, 2));
|
||||
} catch (error) {
|
||||
return toolError(error);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// ── 11. Get Subscriber Notifications ──────────────────────────────────
|
||||
server.registerTool(
|
||||
"novu_get_subscriber_notifications",
|
||||
{
|
||||
title: "Get Subscriber Notifications",
|
||||
description:
|
||||
"Retrieve notifications for a subscriber with pagination.",
|
||||
inputSchema: {
|
||||
subscriber_id: z.string().describe("Unique subscriber identifier"),
|
||||
...PaginationV1Schema,
|
||||
},
|
||||
annotations: {
|
||||
readOnlyHint: true,
|
||||
destructiveHint: false,
|
||||
idempotentHint: true,
|
||||
openWorldHint: true,
|
||||
},
|
||||
},
|
||||
async (params) => {
|
||||
try {
|
||||
const result = await novuRequest(
|
||||
`/v1/subscribers/${encodeURIComponent(params.subscriber_id)}/notifications`,
|
||||
"GET",
|
||||
undefined,
|
||||
{ limit: params.limit, offset: params.offset }
|
||||
);
|
||||
return toolResult(JSON.stringify(result, null, 2));
|
||||
} catch (error) {
|
||||
return toolError(error);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// ── 12. Update Notification State ─────────────────────────────────────
|
||||
server.registerTool(
|
||||
"novu_update_notification_state",
|
||||
{
|
||||
title: "Update Notification State",
|
||||
description:
|
||||
"Update the state (read/seen/unseen/unread) of specific notifications for a subscriber.",
|
||||
inputSchema: {
|
||||
subscriber_id: z.string().describe("Unique subscriber identifier"),
|
||||
message_ids: z
|
||||
.array(z.string())
|
||||
.describe("Array of notification message IDs to update"),
|
||||
state: z
|
||||
.enum(["read", "seen", "unseen", "unread"])
|
||||
.describe("Target notification state"),
|
||||
},
|
||||
annotations: {
|
||||
readOnlyHint: false,
|
||||
destructiveHint: false,
|
||||
idempotentHint: true,
|
||||
openWorldHint: true,
|
||||
},
|
||||
},
|
||||
async (params) => {
|
||||
try {
|
||||
const result = await novuRequest(
|
||||
`/v1/subscribers/${encodeURIComponent(params.subscriber_id)}/notifications/state`,
|
||||
"POST",
|
||||
{ messageIds: params.message_ids, state: params.state }
|
||||
);
|
||||
return toolResult(JSON.stringify(result, null, 2));
|
||||
} catch (error) {
|
||||
return toolError(error);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// ── 13. Update All Notification States ────────────────────────────────
|
||||
server.registerTool(
|
||||
"novu_update_all_notification_states",
|
||||
{
|
||||
title: "Update All Notification States",
|
||||
description:
|
||||
"Update the state of all notifications for a subscriber at once.",
|
||||
inputSchema: {
|
||||
subscriber_id: z.string().describe("Unique subscriber identifier"),
|
||||
state: z
|
||||
.enum(["read", "seen", "unseen", "unread"])
|
||||
.describe("Target notification state to apply to all notifications"),
|
||||
},
|
||||
annotations: {
|
||||
readOnlyHint: false,
|
||||
destructiveHint: false,
|
||||
idempotentHint: true,
|
||||
openWorldHint: true,
|
||||
},
|
||||
},
|
||||
async (params) => {
|
||||
try {
|
||||
const result = await novuRequest(
|
||||
`/v1/subscribers/${encodeURIComponent(params.subscriber_id)}/notifications/state/all`,
|
||||
"POST",
|
||||
{ state: params.state }
|
||||
);
|
||||
return toolResult(JSON.stringify(result, null, 2));
|
||||
} catch (error) {
|
||||
return toolError(error);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// ── 14. Update Subscriber Online Status ───────────────────────────────
|
||||
server.registerTool(
|
||||
"novu_update_subscriber_online_status",
|
||||
{
|
||||
title: "Update Subscriber Online Status",
|
||||
description: "Set a subscriber's online or offline status.",
|
||||
inputSchema: {
|
||||
subscriber_id: z.string().describe("Unique subscriber identifier"),
|
||||
is_online: z.boolean().describe("Whether the subscriber is online"),
|
||||
},
|
||||
annotations: {
|
||||
readOnlyHint: false,
|
||||
destructiveHint: false,
|
||||
idempotentHint: true,
|
||||
openWorldHint: true,
|
||||
},
|
||||
},
|
||||
async (params) => {
|
||||
try {
|
||||
const result = await novuRequest(
|
||||
`/v1/subscribers/${encodeURIComponent(params.subscriber_id)}/online-status`,
|
||||
"PATCH",
|
||||
{ isOnline: params.is_online }
|
||||
);
|
||||
return toolResult(JSON.stringify(result, null, 2));
|
||||
} catch (error) {
|
||||
return toolError(error);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// ── 15. Update Provider Credentials ───────────────────────────────────
|
||||
server.registerTool(
|
||||
"novu_update_provider_credentials",
|
||||
{
|
||||
title: "Update Provider Credentials",
|
||||
description:
|
||||
"Set (replace) integration provider credentials for a subscriber. Use PUT semantics — all credential fields must be provided.",
|
||||
inputSchema: {
|
||||
subscriber_id: z.string().describe("Unique subscriber identifier"),
|
||||
provider_id: z.string().describe("Integration provider identifier"),
|
||||
credentials: z
|
||||
.record(z.unknown())
|
||||
.describe("Provider credentials object (e.g., deviceTokens, webhookUrl)"),
|
||||
},
|
||||
annotations: {
|
||||
readOnlyHint: false,
|
||||
destructiveHint: false,
|
||||
idempotentHint: true,
|
||||
openWorldHint: true,
|
||||
},
|
||||
},
|
||||
async (params) => {
|
||||
try {
|
||||
const result = await novuRequest(
|
||||
`/v1/subscribers/${encodeURIComponent(params.subscriber_id)}/credentials`,
|
||||
"PUT",
|
||||
{
|
||||
providerId: params.provider_id,
|
||||
credentials: params.credentials,
|
||||
}
|
||||
);
|
||||
return toolResult(JSON.stringify(result, null, 2));
|
||||
} catch (error) {
|
||||
return toolError(error);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// ── 16. Upsert Provider Credentials ───────────────────────────────────
|
||||
server.registerTool(
|
||||
"novu_upsert_provider_credentials",
|
||||
{
|
||||
title: "Upsert Provider Credentials",
|
||||
description:
|
||||
"Create or partially update integration provider credentials for a subscriber. Uses PATCH semantics — only provided fields are changed.",
|
||||
inputSchema: {
|
||||
subscriber_id: z.string().describe("Unique subscriber identifier"),
|
||||
provider_id: z.string().describe("Integration provider identifier"),
|
||||
credentials: z
|
||||
.record(z.unknown())
|
||||
.describe("Provider credentials object to upsert"),
|
||||
},
|
||||
annotations: {
|
||||
readOnlyHint: false,
|
||||
destructiveHint: false,
|
||||
idempotentHint: true,
|
||||
openWorldHint: true,
|
||||
},
|
||||
},
|
||||
async (params) => {
|
||||
try {
|
||||
const result = await novuRequest(
|
||||
`/v1/subscribers/${encodeURIComponent(params.subscriber_id)}/credentials`,
|
||||
"PATCH",
|
||||
{
|
||||
providerId: params.provider_id,
|
||||
credentials: params.credentials,
|
||||
}
|
||||
);
|
||||
return toolResult(JSON.stringify(result, null, 2));
|
||||
} catch (error) {
|
||||
return toolError(error);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// ── 17. Delete Provider Credentials ───────────────────────────────────
|
||||
server.registerTool(
|
||||
"novu_delete_provider_credentials",
|
||||
{
|
||||
title: "Delete Provider Credentials",
|
||||
description:
|
||||
"Remove integration provider credentials from a subscriber. This action cannot be undone.",
|
||||
inputSchema: {
|
||||
subscriber_id: z.string().describe("Unique subscriber identifier"),
|
||||
provider_id: z.string().describe("Integration provider identifier to remove"),
|
||||
},
|
||||
annotations: {
|
||||
readOnlyHint: false,
|
||||
destructiveHint: true,
|
||||
idempotentHint: true,
|
||||
openWorldHint: true,
|
||||
},
|
||||
},
|
||||
async (params) => {
|
||||
try {
|
||||
const result = await novuRequest(
|
||||
`/v1/subscribers/${encodeURIComponent(params.subscriber_id)}/credentials/${encodeURIComponent(params.provider_id)}`,
|
||||
"DELETE"
|
||||
);
|
||||
return toolResult(JSON.stringify(result, null, 2));
|
||||
} catch (error) {
|
||||
return toolError(error);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user