/**
* π Welcome to your Smithery project!
* To run your server, run "npm run dev"
*
* You might find these resources useful:
*
* π§βπ» MCP's TypeScript SDK (helps you define your server)
* https://github.com/modelcontextprotocol/typescript-sdk
*
* π smithery.yaml (defines user-level config, like settings or API keys)
* https://smithery.ai/docs/build/project-config/smithery-yaml
*
* π» smithery CLI (run "npx @smithery/cli dev" or explore other commands below)
* https://smithery.ai/docs/concepts/cli
*/
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import { AgentMailClient } from "agentmail";
import dotenv from "dotenv";
dotenv.config();
// Optional: If you have user-level config, define it here
// This should map to the config in your smithery.yaml file
export const configSchema = z.object({
agentmailApiKey: z
.string()
.describe("AgentMail API key for email operations"),
});
export default function createStatelessServer({
config,
sessionId,
}: {
config: z.infer<typeof configSchema>; // Define your config in smithery.yaml
sessionId: string; // Use the sessionId field for mapping requests to stateful processes
}) {
const server = new McpServer({
name: "AgentMail",
version: "1.0.0",
});
// Initialize AgentMail client
const agentMailClient = new AgentMailClient({
apiKey: config.agentmailApiKey,
});
// Add tool for creating inboxes
server.tool(
"create_inbox",
"Create a new email inbox",
{
domain: z.string().optional().describe("Optional domain for the inbox"),
},
async ({ domain }) => {
try {
const inbox = await agentMailClient.inboxes.create({ domain: domain });
return {
content: [
{
type: "text",
text: `Inbox created successfully! Inbox ID: ${
inbox.inboxId
}, Details: ${JSON.stringify(inbox)}`,
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Failed to create inbox: ${
error instanceof Error ? error.message : "Unknown error"
}`,
},
],
};
}
}
);
// Add tool for sending emails
server.tool(
"send_email",
"Send an email from an inbox",
{
inboxId: z.string().describe("ID of the inbox to send from"),
to: z.string().describe("Recipient email address"),
subject: z.string().describe("Email subject"),
text: z.string().describe("Email body text"),
html: z
.string()
.optional()
.describe("Optional HTML version of the email"),
},
async ({ inboxId, to, subject, text, html }) => {
try {
await agentMailClient.inboxes.messages.send(inboxId, {
to,
subject,
text,
...(html && { html }),
});
return {
content: [
{
type: "text",
text: `Email sent successfully to ${to}!`,
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Failed to send email: ${
error instanceof Error ? error.message : "Unknown error"
}`,
},
],
};
}
}
);
// Add tool for getting thread details and messages
server.tool(
"get_thread",
"Get detailed information about a thread including all messages",
{
inboxId: z.string().describe("ID of the inbox containing the thread"),
threadId: z.string().describe("ID of the thread to retrieve"),
},
async ({ inboxId, threadId }) => {
try {
const thread = await agentMailClient.inboxes.threads.get(
inboxId,
threadId
);
const messageCount = thread.messages?.length || 0;
const lastMessage =
thread.messages && thread.messages.length > 0
? thread.messages[thread.messages.length - 1]
: null;
return {
content: [
{
type: "text",
text: `Thread retrieved successfully! Thread ID: ${
thread.threadId
}, Message count: ${messageCount}${
lastMessage ? `, Last message ID: ${lastMessage.messageId}` : ""
}`,
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Failed to get thread: ${
error instanceof Error ? error.message : "Unknown error"
}`,
},
],
};
}
}
);
// Add tool for replying to messages
server.tool(
"reply_to_message",
"Reply to a specific message in a thread",
{
inboxId: z.string().describe("ID of the inbox containing the message"),
messageId: z.string().describe("ID of the message to reply to"),
text: z.string().describe("Reply text content"),
html: z
.string()
.optional()
.describe("Optional HTML version of the reply"),
},
async ({ inboxId, messageId, text, html }) => {
try {
await agentMailClient.inboxes.messages.reply(inboxId, messageId, {
text,
...(html && { html }),
});
return {
content: [
{
type: "text",
text: `Reply sent successfully to message ${messageId}!`,
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Failed to send reply: ${
error instanceof Error ? error.message : "Unknown error"
}`,
},
],
};
}
}
);
// Add tool for listing threads with specific labels
server.tool(
"list_threads",
"List threads in an inbox, optionally filtered by labels",
{
inboxId: z.string().describe("ID of the inbox to list threads from"),
labels: z
.array(z.string())
.optional()
.describe("Optional labels to filter threads by"),
},
async ({ inboxId, labels }) => {
try {
const response = await agentMailClient.inboxes.threads.list(inboxId, {
...(labels && { labels }),
});
const threads = response.threads || [];
return {
content: [
{
type: "text",
text: `Found ${threads.length} threads${
labels ? ` with labels: ${labels.join(", ")}` : ""
}. Thread IDs: ${threads.map((t) => t.threadId).join(", ")}`,
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Failed to list threads: ${
error instanceof Error ? error.message : "Unknown error"
}`,
},
],
};
}
}
);
// Add tool for updating message labels
server.tool(
"update_message_labels",
"Update labels on a message (add new labels and/or remove existing ones)",
{
inboxId: z.string().describe("ID of the inbox containing the message"),
messageId: z.string().describe("ID of the message to update"),
addLabels: z
.array(z.string())
.optional()
.describe("Labels to add to the message"),
removeLabels: z
.array(z.string())
.optional()
.describe("Labels to remove from the message"),
},
async ({ inboxId, messageId, addLabels, removeLabels }) => {
try {
await agentMailClient.inboxes.messages.update(inboxId, messageId, {
...(addLabels && { addLabels }),
...(removeLabels && { removeLabels }),
} as any);
const changes: string[] = [];
if (addLabels && addLabels.length > 0)
changes.push(`added: ${addLabels.join(", ")}`);
if (removeLabels && removeLabels.length > 0)
changes.push(`removed: ${removeLabels.join(", ")}`);
return {
content: [
{
type: "text",
text: `Message labels updated successfully! Changes: ${changes.join(
", "
)}`,
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Failed to update message labels: ${
error instanceof Error ? error.message : "Unknown error"
}`,
},
],
};
}
}
);
// Add tool for finding threads that need replies (conversational loop helper)
server.tool(
"find_unreplied_threads",
"Find threads that need replies by looking for specific labels",
{
inboxId: z.string().describe("ID of the inbox to search"),
unrepliedLabel: z
.string()
.default("unreplied")
.describe("Label that marks threads needing replies"),
},
async ({ inboxId, unrepliedLabel }) => {
try {
const response = await agentMailClient.inboxes.threads.list(inboxId, {
labels: [unrepliedLabel],
});
const threads = response.threads || [];
if (threads.length === 0) {
return {
content: [
{
type: "text",
text: `No threads found with label "${unrepliedLabel}". All caught up!`,
},
],
};
}
return {
content: [
{
type: "text",
text: `Found ${
threads.length
} thread(s) needing replies with label "${unrepliedLabel}". Thread IDs: ${threads
.map((t) => t.threadId)
.join(", ")}`,
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Failed to find unreplied threads: ${
error instanceof Error ? error.message : "Unknown error"
}`,
},
],
};
}
}
);
// Add tool for the complete conversational loop workflow
server.tool(
"process_conversational_loop",
"Complete workflow: find unreplied thread, get details, send reply, and update labels",
{
inboxId: z.string().describe("ID of the inbox to process"),
replyText: z.string().describe("Text content for the reply"),
replyHtml: z
.string()
.optional()
.describe("Optional HTML version of the reply"),
unrepliedLabel: z
.string()
.default("unreplied")
.describe("Label marking threads needing replies"),
repliedLabel: z
.string()
.default("replied")
.describe("Label to mark threads as replied"),
},
async ({ inboxId, replyText, replyHtml, unrepliedLabel, repliedLabel }) => {
try {
// Step 1: Find unreplied threads
const response = await agentMailClient.inboxes.threads.list(inboxId, {
labels: [unrepliedLabel],
});
const threads = response.threads || [];
if (threads.length === 0) {
return {
content: [
{
type: "text",
text: `No threads found with label "${unrepliedLabel}". All caught up!`,
},
],
};
}
// Step 2: Get the first unreplied thread
const threadToReplyTo = threads[0];
const threadDetails = await agentMailClient.inboxes.threads.get(
inboxId,
threadToReplyTo.threadId
);
if (!threadDetails.messages || threadDetails.messages.length === 0) {
return {
content: [
{
type: "text",
text: `Thread ${threadToReplyTo.threadId} has no messages to reply to.`,
},
],
};
}
// Step 3: Get the last message to reply to
const lastMessage =
threadDetails.messages[threadDetails.messages.length - 1];
const messageIdToReplyTo = lastMessage.messageId;
// Step 4: Send the reply
await agentMailClient.inboxes.messages.reply(
inboxId,
messageIdToReplyTo,
{
text: replyText,
...(replyHtml && { html: replyHtml }),
}
);
// Step 5: Update labels
await agentMailClient.inboxes.messages.update(
inboxId,
messageIdToReplyTo,
{
addLabels: [repliedLabel],
removeLabels: [unrepliedLabel],
}
);
return {
content: [
{
type: "text",
text: `Conversational loop completed successfully! Replied to thread ${threadToReplyTo.threadId}, message ${messageIdToReplyTo}. Labels updated: removed "${unrepliedLabel}", added "${repliedLabel}".`,
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Failed to process conversational loop: ${
error instanceof Error ? error.message : "Unknown error"
}`,
},
],
};
}
}
);
// Add tool for listing messages in an inbox
server.tool(
"list_messages",
"List messages in an inbox, optionally filtered by labels",
{
inboxId: z.string().describe("ID of the inbox to list messages from"),
labels: z
.array(z.string())
.optional()
.describe("Optional labels to filter messages by"),
},
async ({ inboxId, labels }) => {
try {
const response = await agentMailClient.inboxes.messages.list(inboxId, {
...(labels && { labels }),
});
const messages = response.messages || [];
return {
content: [
{
type: "text",
text: `Found ${messages.length} messages${
labels ? ` with labels: ${labels.join(", ")}` : ""
}. Message IDs: ${messages.map((m) => m.messageId).join(", ")}`,
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Failed to list messages: ${
error instanceof Error ? error.message : "Unknown error"
}`,
},
],
};
}
}
);
// Add tool for getting message details
server.tool(
"get_message",
"Get detailed information about a specific message",
{
inboxId: z.string().describe("ID of the inbox containing the message"),
messageId: z.string().describe("ID of the message to retrieve"),
},
async ({ inboxId, messageId }) => {
try {
const message = await agentMailClient.inboxes.messages.get(
inboxId,
messageId
);
return {
content: [
{
type: "text",
text: `Message retrieved successfully! Message ID: ${
message.messageId
}, From: ${message.from}, Subject: ${message.subject}, Labels: ${
message.labels?.join(", ") || "none"
}`,
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Failed to get message: ${
error instanceof Error ? error.message : "Unknown error"
}`,
},
],
};
}
}
);
return server.server;
}