import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import {
listAccountsResultSchema,
listAccountsSchema,
sendMessageResultSchema,
sendMessageSchema,
verifyAccountResultSchema,
verifyAccountSchema,
} from "./schemas.js";
import { handleListAccounts } from "./tools/list_accounts.js";
import { handleSendMessage } from "./tools/send_message.js";
import { handleVerifyAccount } from "./tools/verify_account.js";
import type { Logger } from "./logger.js";
import type { ToolCallResult } from "./tools/response.js";
export interface ServerOptions {
readonly logger: Logger;
readonly env: NodeJS.ProcessEnv;
}
interface ToolHandler<Input> {
(input: Input): ToolCallResult | Promise<ToolCallResult>;
}
function nowNs(): bigint {
return process.hrtime.bigint();
}
function durationMs(startedAtNs: bigint): number {
return Number(process.hrtime.bigint() - startedAtNs) / 1_000_000;
}
function wrapToolHandler<Input>(
toolName: string,
handler: ToolHandler<Input>,
logger: Logger,
): ToolHandler<Input> {
return async (input: Input) => {
const startedAt = nowNs();
let errorMessage: string | undefined;
try {
const result = await handler(input);
if (result.isError) {
const first = result.content[0];
if (first) {
try {
const parsed = JSON.parse(first.text) as { error?: { message?: string } };
errorMessage = parsed.error?.message;
} catch {
errorMessage = "Tool failed.";
}
}
}
return result;
} catch (error) {
errorMessage = error instanceof Error ? error.message : "Tool failed.";
throw error;
} finally {
logger.audit(toolName, {
duration_ms: Math.round(durationMs(startedAt)),
arguments: input,
error: errorMessage ? { message: errorMessage } : undefined,
});
}
};
}
/**
* Create an MCP server instance wired with mail-smtp tools.
*/
export function createServer(options: ServerOptions): McpServer {
const server = new McpServer({
name: "mail-smtp-mcp",
version: "0.1.0",
});
server.registerTool(
"mail_smtp_list_accounts",
{
description: "List configured SMTP accounts (metadata only).",
inputSchema: listAccountsSchema,
outputSchema: listAccountsResultSchema,
},
wrapToolHandler(
"mail_smtp_list_accounts",
(input) => handleListAccounts(input, options.env, options.logger),
options.logger,
),
);
server.registerTool(
"mail_smtp_verify_account",
{
description: "Verify SMTP account connectivity without sending an email.",
inputSchema: verifyAccountSchema,
outputSchema: verifyAccountResultSchema,
},
wrapToolHandler(
"mail_smtp_verify_account",
(input) => handleVerifyAccount(input, options.env, options.logger),
options.logger,
),
);
server.registerTool(
"mail_smtp_send_message",
{
description: "Send an outbound email via SMTP with optional attachments.",
inputSchema: sendMessageSchema,
outputSchema: sendMessageResultSchema,
},
wrapToolHandler(
"mail_smtp_send_message",
(input) => handleSendMessage(input, options.env, options.logger),
options.logger,
),
);
return server;
}