index.ts•6.28 kB
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import minimist from 'minimist';
import { Resend } from 'resend';
import { z } from 'zod';
// Parse command line arguments
const argv = minimist(process.argv.slice(2));
// Get API key from command line argument or fall back to environment variable
const apiKey = argv.key || process.env.RESEND_API_KEY;
// Get sender email address from command line argument or fall back to environment variable
// Optional.
const senderEmailAddress = argv.sender || process.env.SENDER_EMAIL_ADDRESS;
// Get reply to email addresses from command line argument or fall back to environment variable
let replierEmailAddresses: string[] = [];
if (Array.isArray(argv['reply-to'])) {
replierEmailAddresses = argv['reply-to'];
} else if (typeof argv['reply-to'] === 'string') {
replierEmailAddresses = [argv['reply-to']];
} else if (process.env.REPLY_TO_EMAIL_ADDRESSES) {
replierEmailAddresses = process.env.REPLY_TO_EMAIL_ADDRESSES.split(',');
}
if (!apiKey) {
console.error(
'No API key provided. Please set RESEND_API_KEY environment variable or use --key argument',
);
process.exit(1);
}
const resend = new Resend(apiKey);
// Create server instance
const server = new McpServer({
name: 'email-sending-service',
version: '1.0.0',
});
server.tool(
'send-email',
'Send an email using Resend',
{
to: z.string().email().describe('Recipient email address'),
subject: z.string().describe('Email subject line'),
text: z.string().describe('Plain text email content'),
html: z
.string()
.optional()
.describe(
'HTML email content. When provided, the plain text argument MUST be provided as well.',
),
cc: z
.string()
.email()
.array()
.optional()
.describe(
'Optional array of CC email addresses. You MUST ask the user for this parameter. Under no circumstance provide it yourself',
),
bcc: z
.string()
.email()
.array()
.optional()
.describe(
'Optional array of BCC email addresses. You MUST ask the user for this parameter. Under no circumstance provide it yourself',
),
scheduledAt: z
.string()
.optional()
.describe(
"Optional parameter to schedule the email. This uses natural language. Examples would be 'tomorrow at 10am' or 'in 2 hours' or 'next day at 9am PST' or 'Friday at 3pm ET'.",
),
// If sender email address is not provided, the tool requires it as an argument
...(!senderEmailAddress
? {
from: z
.string()
.email()
.nonempty()
.describe(
'Sender email address. You MUST ask the user for this parameter. Under no circumstance provide it yourself',
),
}
: {}),
...(replierEmailAddresses.length === 0
? {
replyTo: z
.string()
.email()
.array()
.optional()
.describe(
'Optional email addresses for the email readers to reply to. You MUST ask the user for this parameter. Under no circumstance provide it yourself',
),
}
: {}),
},
async ({ from, to, subject, text, html, replyTo, scheduledAt, cc, bcc }) => {
const fromEmailAddress = from ?? senderEmailAddress;
const replyToEmailAddresses = replyTo ?? replierEmailAddresses;
// Type check on from, since "from" is optionally included in the arguments schema
// This should never happen.
if (typeof fromEmailAddress !== 'string') {
throw new Error('from argument must be provided.');
}
// Similar type check for "reply-to" email addresses.
if (
typeof replyToEmailAddresses !== 'string' &&
!Array.isArray(replyToEmailAddresses)
) {
throw new Error('replyTo argument must be provided.');
}
console.error(`Debug - Sending email with from: ${fromEmailAddress}`);
// Explicitly structure the request with all parameters to ensure they're passed correctly
const emailRequest: {
to: string;
subject: string;
text: string;
from: string;
replyTo: string | string[];
html?: string;
scheduledAt?: string;
cc?: string[];
bcc?: string[];
} = {
to,
subject,
text,
from: fromEmailAddress,
replyTo: replyToEmailAddresses,
};
// Add optional parameters conditionally
if (html) {
emailRequest.html = html;
}
if (scheduledAt) {
emailRequest.scheduledAt = scheduledAt;
}
if (cc) {
emailRequest.cc = cc;
}
if (bcc) {
emailRequest.bcc = bcc;
}
console.error(`Email request: ${JSON.stringify(emailRequest)}`);
const response = await resend.emails.send(emailRequest);
if (response.error) {
throw new Error(
`Email failed to send: ${JSON.stringify(response.error)}`,
);
}
return {
content: [
{
type: 'text',
text: `Email sent successfully! ${JSON.stringify(response.data)}`,
},
],
};
},
);
server.tool(
'list-audiences',
'List all audiences from Resend. This tool is useful for getting the audience ID to help the user find the audience they want to use for other tools. If you need an audience ID, you MUST use this tool to get all available audiences and then ask the user to select the audience they want to use.',
{},
async () => {
console.error('Debug - Listing audiences');
const response = await resend.audiences.list();
if (response.error) {
throw new Error(
`Failed to list audiences: ${JSON.stringify(response.error)}`,
);
}
return {
content: [
{
type: 'text',
text: `Audiences found: ${JSON.stringify(response.data)}`,
},
],
};
},
);
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('Email sending service MCP Server running on stdio');
}
main().catch((error) => {
console.error('Fatal error in main():', error);
process.exit(1);
});