Files
hermes-mcp/src/tools.ts
garfieldheron 356b6b9f55 Initial commit: Hermes MCP - Yahoo Mail server for Claude AI
- Multi-account email support (Yahoo + self-hosted IMAP/SMTP)
- MCP tools: get_profile, search_messages, read_message, list_folders, create_draft, send_email
- Streamable HTTP transport with session recovery
- Docker + Kubernetes deployment configuration
- Express server with health endpoint

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-03-05 13:14:30 -05:00

161 lines
4.8 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'],
description: 'Which mailbox to use: "yahoo" (gheron01@yahoo.com) or "fetcherpay" (garfield.heron@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}` }],
};
}
}