#!/usr/bin/env node
/**
* Unosend MCP Server
*
* Allows LLMs to interact with Unosend API.
* Works with Claude Desktop, Cursor, and other MCP clients.
*/
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
// Parse command line arguments
function parseArgs() {
const args = process.argv.slice(2);
let apiKey = process.env.UNOSEND_API_KEY || '';
let sender = process.env.SENDER_EMAIL_ADDRESS;
let replyTo = process.env.REPLY_TO_EMAIL_ADDRESS;
for (const arg of args) {
if (arg.startsWith('--key=')) {
apiKey = arg.slice(6);
}
else if (arg.startsWith('--sender=')) {
sender = arg.slice(9);
}
else if (arg.startsWith('--reply-to=')) {
replyTo = arg.slice(11);
}
}
if (!apiKey) {
console.error('Error: API key is required.');
console.error('Usage: node index.js --key=YOUR_API_KEY [--sender=email] [--reply-to=email]');
console.error('Or set UNOSEND_API_KEY environment variable.');
process.exit(1);
}
return { apiKey, sender, replyTo };
}
const config = parseArgs();
const API_BASE_URL = 'https://www.unosend.co/api/v1';
// Generic API request helper
async function apiRequest(endpoint, method = 'GET', body) {
try {
const options = {
method,
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${config.apiKey}`,
},
};
if (body)
options.body = JSON.stringify(body);
const response = await fetch(`${API_BASE_URL}${endpoint}`, options);
const data = await response.json();
if (!response.ok) {
return {
success: false,
error: data.error ||
data.message ||
`HTTP ${response.status}`
};
}
return { success: true, data };
}
catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
}
// Create MCP server
const server = new Server({
name: 'unosend',
version: '1.0.0',
}, {
capabilities: {
tools: {},
},
});
// Define all tools
const tools = [
// ============= EMAIL TOOLS =============
{
name: 'send_email',
description: 'Send an email using Unosend API. Supports HTML/text content, CC/BCC, reply-to, and scheduling.',
inputSchema: {
type: 'object',
properties: {
to: { type: 'string', description: 'Recipient email address (or comma-separated list)' },
subject: { type: 'string', description: 'Email subject line' },
html: { type: 'string', description: 'HTML content of the email' },
text: { type: 'string', description: 'Plain text content (used if html not provided)' },
from: { type: 'string', description: 'Sender email (must be from verified domain)' },
cc: { type: 'string', description: 'CC recipient(s), comma-separated' },
bcc: { type: 'string', description: 'BCC recipient(s), comma-separated' },
reply_to: { type: 'string', description: 'Reply-to email address' },
scheduled_at: { type: 'string', description: 'ISO 8601 datetime to schedule (e.g., "2026-01-28T10:00:00Z")' },
},
required: ['to', 'subject'],
},
},
{
name: 'get_email',
description: 'Get details and status of a sent email by its ID.',
inputSchema: {
type: 'object',
properties: {
id: { type: 'string', description: 'The email ID (returned from send_email)' },
},
required: ['id'],
},
},
{
name: 'list_emails',
description: 'List recent emails sent from your account.',
inputSchema: {
type: 'object',
properties: {
limit: { type: 'number', description: 'Number of emails to return (default: 10, max: 100)' },
},
required: [],
},
},
{
name: 'cancel_email',
description: 'Cancel a scheduled email before it is sent.',
inputSchema: {
type: 'object',
properties: {
id: { type: 'string', description: 'The scheduled email ID to cancel' },
},
required: ['id'],
},
},
// ============= SMS TOOLS =============
{
name: 'send_sms',
description: 'Send an SMS message. Charges from wallet at $0.0075 per segment.',
inputSchema: {
type: 'object',
properties: {
to: { type: 'string', description: 'Recipient phone number in E.164 format (e.g., +1234567890)' },
body: { type: 'string', description: 'SMS message content (160 chars = 1 segment)' },
from: { type: 'string', description: 'Sender phone number (if you have one)' },
},
required: ['to', 'body'],
},
},
{
name: 'get_sms',
description: 'Get details of an SMS message by its ID.',
inputSchema: {
type: 'object',
properties: {
id: { type: 'string', description: 'The SMS message ID' },
},
required: ['id'],
},
},
// ============= VALIDATION TOOLS =============
{
name: 'validate_email',
description: 'Validate an email address to check if it exists and is deliverable. Costs $0.01 per validation.',
inputSchema: {
type: 'object',
properties: {
email: { type: 'string', description: 'Email address to validate' },
},
required: ['email'],
},
},
// ============= DOMAIN TOOLS =============
{
name: 'list_domains',
description: 'List all verified domains in your account.',
inputSchema: {
type: 'object',
properties: {},
required: [],
},
},
{
name: 'get_domain',
description: 'Get details and DNS records for a specific domain.',
inputSchema: {
type: 'object',
properties: {
id: { type: 'string', description: 'The domain ID' },
},
required: ['id'],
},
},
// ============= AUDIENCE/CONTACT TOOLS =============
{
name: 'list_audiences',
description: 'List all audiences (contact lists) in your account.',
inputSchema: {
type: 'object',
properties: {},
required: [],
},
},
{
name: 'create_contact',
description: 'Add a contact to an audience.',
inputSchema: {
type: 'object',
properties: {
audience_id: { type: 'string', description: 'The audience ID to add the contact to' },
email: { type: 'string', description: 'Contact email address' },
first_name: { type: 'string', description: 'Contact first name' },
last_name: { type: 'string', description: 'Contact last name' },
unsubscribed: { type: 'boolean', description: 'Whether the contact is unsubscribed' },
},
required: ['audience_id', 'email'],
},
},
{
name: 'list_contacts',
description: 'List contacts in an audience.',
inputSchema: {
type: 'object',
properties: {
audience_id: { type: 'string', description: 'The audience ID' },
},
required: ['audience_id'],
},
},
// ============= UTILITY TOOLS =============
{
name: 'check_api_status',
description: 'Check if the Unosend API is accessible and the API key is valid.',
inputSchema: {
type: 'object',
properties: {},
required: [],
},
},
];
// List available tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
return { tools };
});
// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
const params = args;
// ============= EMAIL HANDLERS =============
if (name === 'send_email') {
const from = params.from || config.sender;
if (!from) {
return {
content: [{ type: 'text', text: '❌ Sender email required. Provide --sender argument or specify "from" parameter.' }],
isError: true,
};
}
const toList = params.to.split(',').map(e => e.trim());
const body = {
from,
to: toList,
subject: params.subject,
};
if (params.html)
body.html = params.html;
if (params.text)
body.text = params.text;
if (params.cc)
body.cc = params.cc.split(',').map(e => e.trim());
if (params.bcc)
body.bcc = params.bcc.split(',').map(e => e.trim());
if (params.reply_to || config.replyTo)
body.reply_to = params.reply_to || config.replyTo;
if (params.scheduled_at)
body.scheduled_at = params.scheduled_at;
const result = await apiRequest('/emails', 'POST', body);
if (result.success) {
const data = result.data;
return {
content: [{
type: 'text',
text: `✅ Email sent!\n\nID: ${data.id}\nTo: ${params.to}\nSubject: ${params.subject}${params.scheduled_at ? `\nScheduled: ${params.scheduled_at}` : ''}`,
}],
};
}
return { content: [{ type: 'text', text: `❌ Failed: ${result.error}` }], isError: true };
}
if (name === 'get_email') {
const result = await apiRequest(`/emails/${params.id}`);
if (result.success) {
const data = result.data;
return {
content: [{
type: 'text',
text: `📧 Email Details\n\nID: ${data.id}\nStatus: ${data.status}\nTo: ${data.to?.join(', ')}\nSubject: ${data.subject}\nCreated: ${data.created_at}`,
}],
};
}
return { content: [{ type: 'text', text: `❌ Failed: ${result.error}` }], isError: true };
}
if (name === 'list_emails') {
const limit = params.limit || 10;
const result = await apiRequest(`/emails?limit=${limit}`);
if (result.success) {
const data = result.data;
const emails = data.data || [];
if (emails.length === 0) {
return { content: [{ type: 'text', text: '📭 No emails found.' }] };
}
const list = emails.map((e, i) => `${i + 1}. [${e.status}] ${e.subject} → ${e.to?.join(', ')}`).join('\n');
return { content: [{ type: 'text', text: `📧 Recent Emails (${emails.length})\n\n${list}` }] };
}
return { content: [{ type: 'text', text: `❌ Failed: ${result.error}` }], isError: true };
}
if (name === 'cancel_email') {
const result = await apiRequest(`/emails/${params.id}/cancel`, 'POST');
if (result.success) {
return { content: [{ type: 'text', text: `✅ Email ${params.id} cancelled.` }] };
}
return { content: [{ type: 'text', text: `❌ Failed: ${result.error}` }], isError: true };
}
// ============= SMS HANDLERS =============
if (name === 'send_sms') {
const body = {
to: params.to,
body: params.body,
};
if (params.from)
body.from = params.from;
const result = await apiRequest('/sms', 'POST', body);
if (result.success) {
const data = result.data;
return {
content: [{
type: 'text',
text: `✅ SMS sent!\n\nID: ${data.id}\nTo: ${params.to}\nSegments: ${data.segments}\nCost: $${data.cost?.toFixed(4) || '0.0075'}`,
}],
};
}
return { content: [{ type: 'text', text: `❌ Failed: ${result.error}` }], isError: true };
}
if (name === 'get_sms') {
const result = await apiRequest(`/sms/${params.id}`);
if (result.success) {
const data = result.data;
return {
content: [{
type: 'text',
text: `📱 SMS Details\n\nID: ${data.id}\nStatus: ${data.status}\nTo: ${data.to}\nMessage: ${data.body}\nSegments: ${data.segments}`,
}],
};
}
return { content: [{ type: 'text', text: `❌ Failed: ${result.error}` }], isError: true };
}
// ============= VALIDATION HANDLER =============
if (name === 'validate_email') {
const result = await apiRequest('/validate/email', 'POST', { email: params.email });
if (result.success) {
const data = result.data;
const status = data.valid ? '✅ Valid' : '❌ Invalid';
return {
content: [{
type: 'text',
text: `${status}\n\nEmail: ${data.email}${data.reason ? `\nReason: ${data.reason}` : ''}${data.risk ? `\nRisk: ${data.risk}` : ''}`,
}],
};
}
return { content: [{ type: 'text', text: `❌ Failed: ${result.error}` }], isError: true };
}
// ============= DOMAIN HANDLERS =============
if (name === 'list_domains') {
const result = await apiRequest('/domains');
if (result.success) {
const data = result.data;
const domains = data.data || [];
if (domains.length === 0) {
return { content: [{ type: 'text', text: '🌐 No domains found.' }] };
}
const list = domains.map((d, i) => `${i + 1}. ${d.name} [${d.status}]`).join('\n');
return { content: [{ type: 'text', text: `🌐 Domains (${domains.length})\n\n${list}` }] };
}
return { content: [{ type: 'text', text: `❌ Failed: ${result.error}` }], isError: true };
}
if (name === 'get_domain') {
const result = await apiRequest(`/domains/${params.id}`);
if (result.success) {
const data = result.data;
return {
content: [{
type: 'text',
text: `🌐 Domain: ${data.name}\n\nStatus: ${data.status}\nDNS Records: ${data.dns_records?.length || 0} records`,
}],
};
}
return { content: [{ type: 'text', text: `❌ Failed: ${result.error}` }], isError: true };
}
// ============= AUDIENCE/CONTACT HANDLERS =============
if (name === 'list_audiences') {
const result = await apiRequest('/audiences');
if (result.success) {
const data = result.data;
const audiences = data.data || [];
if (audiences.length === 0) {
return { content: [{ type: 'text', text: '👥 No audiences found.' }] };
}
const list = audiences.map((a, i) => `${i + 1}. ${a.name} (ID: ${a.id})`).join('\n');
return { content: [{ type: 'text', text: `👥 Audiences (${audiences.length})\n\n${list}` }] };
}
return { content: [{ type: 'text', text: `❌ Failed: ${result.error}` }], isError: true };
}
if (name === 'create_contact') {
const body = { email: params.email };
if (params.first_name)
body.first_name = params.first_name;
if (params.last_name)
body.last_name = params.last_name;
if (params.unsubscribed !== undefined)
body.unsubscribed = params.unsubscribed;
const result = await apiRequest(`/audiences/${params.audience_id}/contacts`, 'POST', body);
if (result.success) {
const data = result.data;
return { content: [{ type: 'text', text: `✅ Contact added!\n\nID: ${data.id}\nEmail: ${params.email}` }] };
}
return { content: [{ type: 'text', text: `❌ Failed: ${result.error}` }], isError: true };
}
if (name === 'list_contacts') {
const result = await apiRequest(`/audiences/${params.audience_id}/contacts`);
if (result.success) {
const data = result.data;
const contacts = data.data || [];
if (contacts.length === 0) {
return { content: [{ type: 'text', text: '👤 No contacts in this audience.' }] };
}
const list = contacts.map((c, i) => {
const name = [c.first_name, c.last_name].filter(Boolean).join(' ');
return `${i + 1}. ${c.email}${name ? ` (${name})` : ''}`;
}).join('\n');
return { content: [{ type: 'text', text: `👤 Contacts (${contacts.length})\n\n${list}` }] };
}
return { content: [{ type: 'text', text: `❌ Failed: ${result.error}` }], isError: true };
}
// ============= UTILITY HANDLERS =============
if (name === 'check_api_status') {
const result = await apiRequest('/domains');
if (result.success) {
return { content: [{ type: 'text', text: '✅ Unosend API is accessible and API key is valid.' }] };
}
if (result.error?.includes('401') || result.error?.includes('Unauthorized')) {
return { content: [{ type: 'text', text: '❌ Invalid API key.' }], isError: true };
}
return { content: [{ type: 'text', text: `⚠️ API issue: ${result.error}` }], isError: true };
}
return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true };
});
// Start server
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('Unosend MCP Server running...');
}
main().catch(console.error);