Files
hermes-mcp/src/tools.ts
garfieldheron 166f5d55a6 Add multi-account support and CORS/logging middleware
- Add garfield, sales, leads, founder accounts to IMAP and SMTP configs
- Refactor fetcherpay config into shared helper functions
- Add CORS middleware with wildcard origin
- Add request logging middleware and MCP session lifecycle logs
- Include package-lock.json and add @types/cors dependency

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 08:45:45 -04:00

161 lines
5.0 KiB
TypeScript

import { Tool } from '@modelcontextprotocol/sdk/types.js';
import { searchMessages, readMessage, getProfile, listFolders, type Account } from './imap.js';
import { sendEmail, createDraft } from './smtp.js';
const ACCOUNT_PARAM = {
account: {
type: 'string',
enum: ['yahoo', 'fetcherpay', 'garfield', 'sales', 'leads', 'founder'],
description: 'Which mailbox to use: "yahoo" (gheron01@yahoo.com), "fetcherpay" (garfield.heron@fetcherpay.com), "garfield" (garfield@fetcherpay.com), "sales" (sales@fetcherpay.com), "leads" (leads@fetcherpay.com), or "founder" (founder@fetcherpay.com). Defaults to "yahoo".',
},
};
export const tools: Tool[] = [
{
name: 'get_profile',
description: 'Get the email account profile (email address and name)',
inputSchema: {
type: 'object',
properties: { ...ACCOUNT_PARAM },
},
},
{
name: 'search_messages',
description: 'Search email messages by keyword, sender, or subject',
inputSchema: {
type: 'object',
properties: {
q: { type: 'string', description: 'Search query (keyword, from:email, subject:text)' },
maxResults: { type: 'number', description: 'Max messages to return (default 20)' },
...ACCOUNT_PARAM,
},
required: ['q'],
},
},
{
name: 'read_message',
description: 'Read a full email message by UID',
inputSchema: {
type: 'object',
properties: {
uid: { type: 'number', description: 'Message UID from search results' },
...ACCOUNT_PARAM,
},
required: ['uid'],
},
},
{
name: 'list_folders',
description: 'List all email folders/mailboxes',
inputSchema: {
type: 'object',
properties: { ...ACCOUNT_PARAM },
},
},
{
name: 'create_draft',
description: 'Create a draft email',
inputSchema: {
type: 'object',
properties: {
to: { type: 'string', description: 'Recipient email address' },
subject: { type: 'string', description: 'Email subject' },
body: { type: 'string', description: 'Email body (plain text)' },
...ACCOUNT_PARAM,
},
required: ['to', 'subject', 'body'],
},
},
{
name: 'send_email',
description: 'Send an email',
inputSchema: {
type: 'object',
properties: {
to: { type: 'string', description: 'Recipient email address' },
subject: { type: 'string', description: 'Email subject' },
body: { type: 'string', description: 'Email body (plain text)' },
...ACCOUNT_PARAM,
},
required: ['to', 'subject', 'body'],
},
},
];
function acct(args: Record<string, unknown>): Account {
return (args.account as Account) ?? 'yahoo';
}
export async function handleToolCall(
name: string,
args: Record<string, unknown>
): Promise<{ content: Array<{ type: string; text: string }> }> {
console.log(`[tool] ${name}`, JSON.stringify(args));
const t0 = Date.now();
try {
let result: unknown;
switch (name) {
case 'get_profile':
result = await getProfile(acct(args));
break;
case 'search_messages':
result = await searchMessages(args.q as string, (args.maxResults as number) ?? 20, acct(args));
break;
case 'read_message':
result = await readMessage(args.uid as number, acct(args));
break;
case 'list_folders':
result = await listFolders(acct(args));
break;
case 'create_draft':
result = await createDraft(args.to as string, args.subject as string, args.body as string, acct(args));
break;
case 'send_email':
result = await sendEmail(args.to as string, args.subject as string, args.body as string, acct(args));
break;
// Legacy Yahoo-prefixed names — keep working for any cached Claude sessions
case 'yahoo_get_profile':
result = await getProfile('yahoo');
break;
case 'yahoo_search_messages':
result = await searchMessages(args.q as string, (args.maxResults as number) ?? 20, 'yahoo');
break;
case 'yahoo_read_message':
result = await readMessage(args.uid as number, 'yahoo');
break;
case 'yahoo_list_folders':
result = await listFolders('yahoo');
break;
case 'yahoo_create_draft':
result = await createDraft(args.to as string, args.subject as string, args.body as string, 'yahoo');
break;
case 'yahoo_send_email':
result = await sendEmail(args.to as string, args.subject as string, args.body as string, 'yahoo');
break;
default:
throw new Error(`Unknown tool: ${name}`);
}
console.log(`[tool] ${name} OK (${Date.now() - t0}ms)`);
return {
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
};
} catch (error) {
const msg = (error as Error).message;
const stack = (error as Error).stack ?? '';
console.error(`[tool] ${name} ERROR (${Date.now() - t0}ms):`, msg);
console.error(stack);
return {
content: [{ type: 'text', text: `Error: ${msg}` }],
};
}
}