Skip to main content
Glama

Hiworks Mail MCP

index.ts11.8 kB
#!/usr/bin/env node import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { z } from 'zod'; import { ReadEmailParams, ReadEmailResponse, SearchEmailParams, SearchEmailResponse, Email, SendEmailParams, SendEmailResponse } from './types/mail.js'; import Pop3Command from 'node-pop3'; import { simpleParser } from 'mailparser'; import { Config } from './types/config.js'; import nodemailer from 'nodemailer'; const config: Config = { pop3: { host: "pop3s.hiworks.com", port: 995, ssl: true }, smtp: { host: "smtps.hiworks.com", port: 465, secure: true } }; // UTC를 KST로 변환하는 함수 function convertToKST(date: Date): Date { return new Date(date.getTime() + (9 * 60 * 60 * 1000)); } // 날짜를 ISO 문자열로 변환하는 함수 (KST 기준) function formatDate(date: Date): string { return convertToKST(date).toISOString(); } // 로깅 함수 function log(...args: any[]) { // 개발 모드에서만 로그 출력 if (process.env.NODE_ENV === 'development') { console.error(new Date().toISOString(), ...args); } } // POP3 클라이언트 생성 함수 async function connectPOP3(username: string, password: string): Promise<Pop3Command> { const pop3Config = { user: username, password: password, host: config.pop3.host, port: config.pop3.port, tls: config.pop3.ssl, timeout: 60000 }; const client = new Pop3Command(pop3Config); return client; } // SMTP 클라이언트 생성 함수 async function createSMTPTransporter(username: string, password: string) { return nodemailer.createTransport({ host: config.smtp.host, port: config.smtp.port, secure: config.smtp.secure, auth: { user: username, pass: password } }); } // MCP 서버 설정 const server = new McpServer({ name: 'hiworks-mail-mcp', version: '1.0.8', capabilities: { resources: {}, tools: { read_username: { description: '하이웍스 username을 읽어옵니다.', parameters: { type: 'object', properties: { username: { type: 'string' }, password: { type: 'string' } }, required: ['username', 'password'] } }, search_email: { description: '하이웍스 이메일을 검색합니다.', parameters: { type: 'object', properties: { username: { type: 'string' }, password: { type: 'string' }, query: { type: 'string' }, limit: { type: 'number' } }, required: ['username', 'password'] } }, read_email: { description: '하이웍스 이메일을 읽어옵니다.', parameters: { type: 'object', properties: { username: { type: 'string' }, password: { type: 'string' }, messageId: { type: 'string' } }, required: ['username', 'password', 'messageId'] } } } } }); // 이메일 스키마 const emailSchema = { username: z.string().default(process.env['HIWORKS_USERNAME'] || ''), password: z.string().default(process.env['HIWORKS_PASSWORD'] || '') }; const searchEmailSchema = { ...emailSchema, query: z.string().optional(), limit: z.number().optional() }; const readEmailSchema = { ...emailSchema, messageId: z.string() }; // 도구 등록 server.tool( 'read_username', '하이웍스 username을 읽어옵니다.', emailSchema, async ({ username, password }) => { return { content: [ { type: "text", text: JSON.stringify({ username: username, password: password }) } ] }; } ); server.tool( 'search_email', '하이웍스 이메일을 검색합니다.', searchEmailSchema, async ({ username, password, query, limit = 100 }) => { try { const client = await connectPOP3(username, password); // STAT으로 메일박스 상태 확인 const stat = await client.STAT(); // LIST로 각 메일의 크기 확인 (메시지 번호는 1부터 시작) const messageList = await client.LIST(); const totalMessages = messageList.length; // LIST 결과로 전체 메시지 수 계산 // UIDL로 메일의 고유 ID 확인 const uidList = await client.UIDL(); const emails = []; const messagesToFetch = []; // 최신 메일 선택 (가장 높은 번호부터) const startIndex = Math.min(totalMessages, messageList[messageList.length - 1][0]); for (let i = startIndex; i > Math.max(1, startIndex - limit); i--) { if (messageList.some(([num]) => Number(num) === i)) { messagesToFetch.push(i); } } // 선택된 메일들의 정보 가져오기 for (const msgNum of messagesToFetch) { try { // 먼저 TOP으로 헤더만 가져오기 const messageTop = await client.TOP(msgNum, 0); const parsed = await simpleParser(messageTop); // KST로 변환된 날짜 사용 const date = parsed.date ? formatDate(parsed.date) : formatDate(new Date()); emails.push({ id: parsed.messageId || String(msgNum), subject: parsed.subject || '(제목 없음)', from: Array.isArray(parsed.from) ? parsed.from[0]?.text || '' : parsed.from?.text || '', to: Array.isArray(parsed.to) ? parsed.to[0]?.text || '' : parsed.to?.text || '', date }); } catch (err) { if (process.env.NODE_ENV === 'development') { log(`Error processing message ${msgNum}:`, err); } } } // KST 기준으로 정렬 emails.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()); await client.QUIT(); return { content: [ { type: "text", text: JSON.stringify({ success: true, emails } as SearchEmailResponse) } ] }; } catch (error: any) { return { content: [ { type: "text", text: JSON.stringify({ success: false, emails: [], error: error.message } as SearchEmailResponse) } ] }; } } ); server.tool( 'read_email', '하이웍스 이메일을 읽어옵니다.', readEmailSchema, async ({ username, password, messageId }) => { try { const client = await connectPOP3(username, password); const stat = await client.STAT(); const totalMessages = stat[0]; let email: Email | undefined; for (let i = totalMessages; i >= 1; i--) { try { const rawEmail = await client.RETR(i); const parsed = await simpleParser(rawEmail); if (parsed.messageId === messageId || String(i) === messageId) { // KST로 변환된 날짜 사용 const date = parsed.date ? formatDate(parsed.date) : formatDate(new Date()); email = { id: parsed.messageId || String(i), subject: parsed.subject || '(제목 없음)', from: Array.isArray(parsed.from) ? parsed.from[0]?.text || '' : parsed.from?.text || '', to: Array.isArray(parsed.to) ? parsed.to[0]?.text || '' : parsed.to?.text || '', date, content: parsed.text || '', html: parsed.html || undefined }; break; } } catch (err) { log(`Error processing email ${i}:`, err); continue; } } await client.QUIT(); return { content: [ { type: "text", text: JSON.stringify({ success: true, email } as ReadEmailResponse) } ] }; } catch (error: any) { return { content: [ { type: "text", text: JSON.stringify({ success: false, error: error.message } as ReadEmailResponse) } ] }; } } ); server.tool( 'send_email', '하이웍스 이메일을 전송합니다.', { ...emailSchema, to: z.string(), subject: z.string(), text: z.string().optional(), html: z.string().optional(), cc: z.array(z.string()).optional(), bcc: z.array(z.string()).optional(), attachments: z.array(z.object({ filename: z.string(), content: z.union([z.string(), z.instanceof(Buffer)]) })).optional() }, async ({ username, password, to, subject, text, html, cc, bcc, attachments }) => { try { log('Creating SMTP transporter...'); const transporter = await createSMTPTransporter(username, password); const mailOptions = { from: username, to, subject, text, html, cc, bcc, attachments }; log('Sending email...'); const info = await transporter.sendMail(mailOptions); log('Email sent successfully:', info.messageId); return { content: [ { type: "text", text: JSON.stringify({ success: true, messageId: info.messageId } as SendEmailResponse) } ] }; } catch (error: any) { log('Error sending email:', error); return { content: [ { type: "text", text: JSON.stringify({ success: false, error: error.message } as SendEmailResponse) } ] }; } } ); // 메인 함수 async function main() { if (process.env.NODE_ENV === 'development') { log('Starting Hiworks Mail MCP Server...'); } const transport = new StdioServerTransport(); // 프로세스 종료 시그널 처리 process.on('SIGTERM', () => { if (process.env.NODE_ENV === 'development') { log('Received SIGTERM signal'); } process.exit(0); }); process.on('SIGINT', () => { if (process.env.NODE_ENV === 'development') { log('Received SIGINT signal'); } process.exit(0); }); // 예기치 않은 에러 처리 process.on('uncaughtException', (error) => { if (process.env.NODE_ENV === 'development') { log('Uncaught Exception:', error); } process.exit(1); }); process.on('unhandledRejection', (reason, promise) => { if (process.env.NODE_ENV === 'development') { log('Unhandled Rejection at:', promise, 'reason:', reason); } process.exit(1); }); // stdio 스트림 에러 처리 process.stdin.on('error', (error) => { if (process.env.NODE_ENV === 'development') { log('stdin error:', error); } }); process.stdout.on('error', (error) => { if (process.env.NODE_ENV === 'development') { log('stdout error:', error); } }); process.stderr.on('error', (error) => { if (process.env.NODE_ENV === 'development') { log('stderr error:', error); } }); try { await server.connect(transport); if (process.env.NODE_ENV === 'development') { log('Hiworks Mail MCP Server running on stdio'); } } catch (error) { if (process.env.NODE_ENV === 'development') { log('Failed to start MCP server:', error); } process.exit(1); } } log('Starting main function...'); main().catch((error) => { log('Fatal error in main():', error); process.exit(1); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/beylessai/hiworks-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server