Files
listmonk_mcp/index.js
2026-06-02 23:11:13 +02:00

250 lines
9.2 KiB
JavaScript

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import axios from "axios";
import { z } from "zod";
import * as dotenv from "dotenv";
dotenv.config();
const LISTMONK_URL = process.env.LISTMONK_URL || "https://listmonk.infra.mintel.me";
const LISTMONK_USERNAME = process.env.LISTMONK_USERNAME || "mmintel";
const LISTMONK_PASSWORD = process.env.LISTMONK_PASSWORD || "Tim300493.";
const apiClient = axios.create({
baseURL: LISTMONK_URL,
auth: {
username: LISTMONK_USERNAME,
password: LISTMONK_PASSWORD,
},
headers: {
"Content-Type": "application/json",
},
});
const server = new McpServer({
name: "listmonk-mcp",
version: "1.0.0",
});
// GET /api/subscribers
server.tool("listmonk_list_subscribers", {
query: z.string().optional().describe("Search query (email or name)"),
page: z.number().optional().describe("Page number (default 1)"),
per_page: z.number().optional().describe("Results per page (default 20)"),
}, async ({ query, page, per_page }) => {
try {
const response = await apiClient.get("/api/subscribers", {
params: { query, page, per_page },
});
return {
content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }],
};
}
catch (error) {
return {
content: [{ type: "text", text: `Error: ${error.response?.data?.message || error.message}` }],
isError: true,
};
}
});
// GET /api/subscribers/{id}
server.tool("listmonk_get_subscriber", { id: z.number().describe("Subscriber ID") }, async ({ id }) => {
try {
const response = await apiClient.get(`/api/subscribers/${id}`);
return {
content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }],
};
}
catch (error) {
return {
content: [{ type: "text", text: `Error: ${error.response?.data?.message || error.message}` }],
isError: true,
};
}
});
// POST /api/subscribers
server.tool("listmonk_create_subscriber", {
email: z.string().email().describe("Subscriber email address"),
name: z.string().describe("Subscriber name"),
status: z.enum(["enabled", "disabled", "blocklisted"]).describe("Subscriber status"),
lists: z.array(z.number()).describe("Array of list IDs to subscribe to"),
attribs: z.record(z.string(), z.any()).optional().describe("Optional JSON attributes"),
preconfirm_subscriptions: z.boolean().optional().describe("Mark subscriptions as confirmed (opt-in bypassed)"),
}, async ({ email, name, status, lists, attribs, preconfirm_subscriptions }) => {
try {
const response = await apiClient.post("/api/subscribers", {
email,
name,
status,
lists,
attribs,
preconfirm_subscriptions,
});
return {
content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }],
};
}
catch (error) {
return {
content: [{ type: "text", text: `Error: ${error.response?.data?.message || error.message}` }],
isError: true,
};
}
});
// PUT /api/subscribers/{id}
server.tool("listmonk_update_subscriber", {
id: z.number().describe("Subscriber ID"),
email: z.string().email().optional().describe("Subscriber email address"),
name: z.string().optional().describe("Subscriber name"),
status: z.enum(["enabled", "disabled", "blocklisted"]).optional().describe("Subscriber status"),
lists: z.array(z.number()).optional().describe("Array of list IDs to subscribe to"),
attribs: z.record(z.string(), z.any()).optional().describe("Optional JSON attributes"),
}, async ({ id, email, name, status, lists, attribs }) => {
try {
const data = {};
if (email)
data.email = email;
if (name)
data.name = name;
if (status)
data.status = status;
if (lists)
data.lists = lists;
if (attribs)
data.attribs = attribs;
const response = await apiClient.put(`/api/subscribers/${id}`, data);
return {
content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }],
};
}
catch (error) {
return {
content: [{ type: "text", text: `Error: ${error.response?.data?.message || error.message}` }],
isError: true,
};
}
});
// GET /api/lists
server.tool("listmonk_list_lists", {}, async () => {
try {
const response = await apiClient.get("/api/lists");
return {
content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }],
};
}
catch (error) {
return {
content: [{ type: "text", text: `Error: ${error.response?.data?.message || error.message}` }],
isError: true,
};
}
});
// POST /api/lists
server.tool("listmonk_create_list", {
name: z.string().describe("List name"),
type: z.enum(["public", "private"]).describe("List type"),
optin: z.enum(["single", "double"]).describe("Opt-in type"),
description: z.string().optional().describe("Optional description"),
tags: z.array(z.string()).optional().describe("Optional tags"),
}, async ({ name, type, optin, description, tags }) => {
try {
const response = await apiClient.post("/api/lists", {
name,
type,
optin,
description,
tags,
});
return {
content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }],
};
}
catch (error) {
return {
content: [{ type: "text", text: `Error: ${error.response?.data?.message || error.message}` }],
isError: true,
};
}
});
// GET /api/campaigns
server.tool("listmonk_list_campaigns", {
page: z.number().optional().describe("Page number"),
per_page: z.number().optional().describe("Results per page"),
}, async ({ page, per_page }) => {
try {
const response = await apiClient.get("/api/campaigns", {
params: { page, per_page },
});
return {
content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }],
};
}
catch (error) {
return {
content: [{ type: "text", text: `Error: ${error.response?.data?.message || error.message}` }],
isError: true,
};
}
});
// POST /api/campaigns
server.tool("listmonk_create_campaign", {
name: z.string().describe("Internal campaign name"),
subject: z.string().describe("Email subject line"),
lists: z.array(z.number()).describe("Array of list IDs to send to"),
type: z.enum(["regular", "optin"]).describe("Campaign type"),
content_type: z.enum(["richtext", "html", "markdown", "plain"]).describe("Content type"),
body: z.string().describe("Email body content"),
altbody: z.string().optional().describe("Optional plain text alternative body"),
from_email: z.string().optional().describe("From email address"),
messenger: z.string().describe("Messenger to use (e.g. 'email')"),
template_id: z.number().optional().describe("Template ID to wrap the content"),
tags: z.array(z.string()).optional().describe("Optional tags"),
}, async (params) => {
try {
const response = await apiClient.post("/api/campaigns", params);
return {
content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }],
};
}
catch (error) {
return {
content: [{ type: "text", text: `Error: ${error.response?.data?.message || error.message}` }],
isError: true,
};
}
});
// POST /api/tx
server.tool("listmonk_send_tx", {
subscriber_email: z.string().email().describe("Subscriber email to send to"),
subscriber_id: z.number().optional().describe("Subscriber ID (optional, alternative to email)"),
template_id: z.number().describe("Template ID to use"),
data: z.record(z.string(), z.any()).describe("JSON data for template variables"),
messenger: z.string().optional().describe("Messenger type (default 'email')"),
}, async ({ subscriber_email, subscriber_id, template_id, data, messenger }) => {
try {
const payload = {
template_id,
data,
messenger: messenger || "email",
};
if (subscriber_email)
payload.subscriber_email = subscriber_email;
if (subscriber_id)
payload.subscriber_id = subscriber_id;
const response = await apiClient.post("/api/tx", payload);
return {
content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }],
};
}
catch (error) {
return {
content: [{ type: "text", text: `Error: ${error.response?.data?.message || error.message}` }],
isError: true,
};
}
});
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Listmonk MCP Server running on stdio");
}
main().catch((error) => {
console.error("Fatal error in main():", error);
process.exit(1);
});
//# sourceMappingURL=index.js.map