Files
stalwart_mcp/build/index.js

218 lines
8.7 KiB
JavaScript

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
import { JMAPClient } from "./jmap_client.js";
import * as dotenv from "dotenv";
dotenv.config();
const server = new Server({
name: "stalwart_mcp",
version: "1.0.0",
}, {
capabilities: {
tools: {},
},
});
const JMAP_URL = process.env.STALWART_JMAP_URL || "https://mail.infra.mintel.me";
const JMAP_USER = process.env.STALWART_USER || "antigravity";
const JMAP_PASSWORD = process.env.STALWART_APP_PASSWORD || "90e21uh0eondwqa9i0owsdn";
const client = new JMAPClient(JMAP_URL, JMAP_USER, JMAP_PASSWORD);
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "stalwart_list_mailboxes",
description: "List all mailboxes (folders) for the authenticated account.",
inputSchema: {
type: "object",
properties: {},
},
},
{
name: "stalwart_search_emails",
description: "Search for emails in a specific mailbox or across all mailboxes.",
inputSchema: {
type: "object",
properties: {
mailboxId: {
type: "string",
description: "Optional mailbox ID to filter the search.",
},
query: {
type: "string",
description: "Search query (e.g., 'from:someone', 'subject:hello').",
},
limit: {
type: "number",
description: "Maximum number of emails to return (default 10).",
}
},
},
},
{
name: "stalwart_read_email",
description: "Read the full contents of a specific email by its ID.",
inputSchema: {
type: "object",
properties: {
emailId: {
type: "string",
description: "The ID of the email to read.",
},
},
required: ["emailId"],
},
},
{
name: "stalwart_send_email",
description: "Send a new email.",
inputSchema: {
type: "object",
properties: {
to: {
type: "string",
description: "Recipient email address.",
},
subject: {
type: "string",
description: "Subject of the email.",
},
textBody: {
type: "string",
description: "Plain text body of the email.",
},
htmlBody: {
type: "string",
description: "Optional HTML body of the email.",
}
},
required: ["to", "subject", "textBody"],
},
}
],
};
});
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
if (name === "stalwart_list_mailboxes") {
const response = await client.request([
["Mailbox/get", { accountId: client.getAccountId(), ids: null }, "0"]
]);
const mailboxes = response.methodResponses[0][1].list;
return {
content: [{ type: "text", text: JSON.stringify(mailboxes, null, 2) }],
};
}
else if (name === "stalwart_search_emails") {
const { mailboxId, query, limit = 10 } = args;
// Note: A full search implementation in JMAP is slightly more complex,
// but we can query Email/query.
const filter = {};
if (mailboxId)
filter.inMailbox = mailboxId;
if (query)
filter.text = query; // Simple text search
const queryResponse = await client.request([
["Email/query", {
accountId: client.getAccountId(),
filter: Object.keys(filter).length > 0 ? filter : null,
limit
}, "0"],
["Email/get", {
accountId: client.getAccountId(),
"#ids": {
resultOf: "0",
name: "Email/query",
path: "/ids"
},
properties: ["id", "subject", "from", "to", "receivedAt", "preview"]
}, "1"]
]);
const emails = queryResponse.methodResponses[1][1].list;
return {
content: [{ type: "text", text: JSON.stringify(emails, null, 2) }],
};
}
else if (name === "stalwart_read_email") {
const { emailId } = args;
const response = await client.request([
["Email/get", {
accountId: client.getAccountId(),
ids: [emailId],
properties: ["id", "subject", "from", "to", "receivedAt", "bodyValues", "textBody", "htmlBody"]
}, "0"]
]);
const email = response.methodResponses[0][1].list[0];
if (!email) {
return { content: [{ type: "text", text: `Email with ID ${emailId} not found.` }] };
}
return {
content: [{ type: "text", text: JSON.stringify(email, null, 2) }],
};
}
else if (name === "stalwart_send_email") {
const { to, subject, textBody, htmlBody } = args;
const accountId = client.getAccountId();
const creationId = "draft-" + Date.now();
const bodyParts = [
{ partId: "part_text", type: "text/plain" }
];
const bodyValues = {
"part_text": { value: textBody, isEncodingProblem: false, isTruncated: false }
};
if (htmlBody) {
bodyParts.push({ partId: "part_html", type: "text/html" });
bodyValues["part_html"] = { value: htmlBody, isEncodingProblem: false, isTruncated: false };
}
const response = await client.request([
["Email/set", {
accountId,
create: {
[creationId]: {
from: [{ email: JMAP_USER.includes("@") ? JMAP_USER : `${JMAP_USER}@mintel.me` }],
to: [{ email: to }],
subject: subject,
bodyValues: bodyValues,
textBody: [{ partId: "part_text" }],
...(htmlBody ? { htmlBody: [{ partId: "part_html" }] } : {})
}
}
}, "0"],
["EmailSubmission/set", {
accountId,
create: {
"send-1": {
emailId: `#${creationId}`
}
}
}, "1"]
]);
return {
content: [{ type: "text", text: JSON.stringify(response, null, 2) }],
};
}
throw new Error(`Unknown tool: ${name}`);
}
catch (error) {
return {
isError: true,
content: [{ type: "text", text: `Error executing tool: ${error.message}\n${JSON.stringify(error.response?.data || {})}` }],
};
}
});
async function run() {
// Initial connection test
try {
await client.connect();
console.error("Successfully connected to Stalwart JMAP!");
}
catch (e) {
console.error("Failed to connect to Stalwart on startup. Ensure credentials are correct.");
process.exit(1);
}
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Stalwart MCP Server running on stdio");
}
run().catch(console.error);
//# sourceMappingURL=index.js.map