MCP-Logic

import { GmailService } from "../services/gmail-service.js"; import { CallToolRequest, CallToolResult, ListToolsRequest, ListToolsResult, } from "@modelcontextprotocol/sdk/types.js"; import { TOOLS } from "../constants/tools.js"; import { ListEmailsArgs, GetEmailArgs, GetDraftArgs, SearchEmailsArgs, SendEmailAIArgs, SendEmailManualArgs, CreateDraftAIArgs, EditDraftAIArgs, ListDraftsArgs, DeleteDraftArgs, TrashMessageArgs, } from "../types/tool-schemas.js"; import { TOOL_ERROR_MESSAGES } from "../constants/tools.js"; import { sendSamplingRequest } from "./sampling.js"; import { handleGetPrompt } from "./prompt-handlers.js"; import { injectVariables } from "../utils/message-handlers.js"; import { gmail_v1 } from "googleapis"; const EMAIL_REGEX = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/; function validateEmail(email: string): boolean { return EMAIL_REGEX.test(email.trim()); } function validateEmailList(emails: string | string[] | undefined): void { if (!emails) return; const emailList = Array.isArray(emails) ? emails : emails.split(",").map((e) => e.trim()); for (const email of emailList) { if (!validateEmail(email)) { throw new Error(`Invalid email address: ${email}`); } } } export async function handleListTools(request: ListToolsRequest): Promise<ListToolsResult> { return { tools: TOOLS }; } export async function handleToolCall(request: CallToolRequest): Promise<CallToolResult> { try { switch (request.params.name) { case "gmail_list_emails": { const gmail = new GmailService(); const args = request.params.arguments as unknown as ListEmailsArgs; const messages = await gmail.listMessages(args.maxResults ?? 5); // Format messages into a more concise structure const formattedMessages = messages.map((msg: gmail_v1.Schema$Message) => ({ id: msg.id, threadId: msg.threadId, snippet: msg.snippet, // Extract key headers from: msg.payload?.headers?.find((h) => h?.name?.toLowerCase() === "from")?.value, to: msg.payload?.headers?.find((h) => h?.name?.toLowerCase() === "to")?.value, subject: msg.payload?.headers?.find((h) => h?.name?.toLowerCase() === "subject")?.value, date: msg.payload?.headers?.find((h) => h?.name?.toLowerCase() === "date")?.value, })); return { content: [ { type: "text", text: JSON.stringify( { count: formattedMessages.length, messages: formattedMessages, }, null, 2, ), }, ], }; } case "gmail_get_email": { const gmail = new GmailService(); const args = request.params.arguments as unknown as GetEmailArgs; const message = await gmail.getMessage(args.messageId); return { content: [ { type: "text", text: JSON.stringify(message, null, 2), }, ], }; } case "gmail_get_draft": { const gmail = new GmailService(); const args = request.params.arguments as unknown as GetDraftArgs; const draft = await gmail.getDraft(args.draftId); return { content: [ { type: "text", text: JSON.stringify(draft, null, 2), }, ], }; } case "gmail_search_emails": { const gmail = new GmailService(); const args = request.params.arguments as unknown as SearchEmailsArgs; const messages = await gmail.searchMessages(args.query, args.maxResults); return { content: [ { type: "text", text: JSON.stringify(messages, null, 2), }, ], }; } case "gmail_send_email_ai": { const args = request.params.arguments as unknown as SendEmailAIArgs; const { userInstructions, to, replyTo } = args; if (!userInstructions) { throw new Error( "Tool call failed: Missing required parameters - userInstructions is required", ); } if (!to) { throw new Error("Tool call failed: Missing required parameters - to is required"); } validateEmailList(to); let threadContent: string | undefined; if (replyTo) { const gmail = new GmailService(); const message = await gmail.getMessage(replyTo); threadContent = JSON.stringify(message); } const prompt = await handleGetPrompt({ method: "prompts/get", params: { name: replyTo ? "gmail_reply_email" : "gmail_send_email", arguments: { userInstructions, to, ...(replyTo && threadContent ? { messageId: replyTo, threadContent, } : {}), }, }, }); if (!prompt._meta?.responseSchema) { throw new Error("Invalid prompt configuration: missing response schema"); } await sendSamplingRequest({ method: "sampling/createMessage", params: { messages: prompt.messages.map((msg) => injectVariables(msg, { userInstructions, to, ...(replyTo && threadContent ? { messageId: replyTo, threadContent, } : {}), }), ) as Array<{ role: "user" | "assistant"; content: { type: "text"; text: string }; }>, maxTokens: 100000, temperature: 0.7, _meta: { callback: replyTo ? "reply_email" : "send_email", responseSchema: prompt._meta.responseSchema, }, arguments: { userInstructions, to, ...(replyTo ? { messageId: replyTo } : {}) }, }, }); return { content: [ { type: "text", text: `Your ${replyTo ? "reply" : "email"} request has been received and is being processed, we will notify you when it is complete.`, }, ], }; } case "gmail_send_email_manual": { const gmail = new GmailService(); const args = request.params.arguments as unknown as SendEmailManualArgs; const { to, subject, body, cc, bcc, isHtml, replyTo } = args; validateEmailList(to); if (cc) validateEmailList(cc); if (bcc) validateEmailList(bcc); if (replyTo) { await gmail.replyEmail(replyTo, body, isHtml); } else { if (!subject) { throw new Error( "Tool call failed: Missing required parameters - subject is required for new emails", ); } await gmail.sendEmail({ to, subject, body, cc, bcc, isHtml, }); } return { content: [ { type: "text", text: JSON.stringify({ status: `${replyTo ? "Reply" : "Email"} sent successfully`, to, }), }, ], }; } case "gmail_create_draft_ai": { const args = request.params.arguments as unknown as CreateDraftAIArgs; const { userInstructions, to, replyTo } = args; if (!userInstructions) { throw new Error( "Tool call failed: Missing required parameters - userInstructions is required", ); } if (!to) { throw new Error("Tool call failed: Missing required parameters - to is required"); } validateEmailList(to); if (replyTo) { const gmail = new GmailService(); await gmail.getMessage(replyTo); } const prompt = await handleGetPrompt({ method: "prompts/get", params: { name: replyTo ? "gmail_reply_draft" : "gmail_create_draft", arguments: { userInstructions, to, ...(replyTo ? { messageId: replyTo } : {}) }, }, }); if (!prompt._meta?.responseSchema) { throw new Error("Invalid prompt configuration: missing response schema"); } await sendSamplingRequest({ method: "sampling/createMessage", params: { messages: prompt.messages.map((msg) => injectVariables(msg, { userInstructions, to, ...(replyTo ? { messageId: replyTo } : {}), }), ) as Array<{ role: "user" | "assistant"; content: { type: "text"; text: string }; }>, maxTokens: 100000, temperature: 0.7, _meta: { callback: replyTo ? "reply_draft" : "create_draft", responseSchema: prompt._meta.responseSchema, }, arguments: { userInstructions, to, ...(replyTo ? { messageId: replyTo } : {}) }, }, }); return { content: [ { type: "text", text: `Your draft ${replyTo ? "reply" : "email"} request has been received and is being processed, we will notify you when it is complete.`, }, ], }; } case "gmail_edit_draft_ai": { const args = request.params.arguments as unknown as EditDraftAIArgs; const { draftId, userInstructions } = args; if (!userInstructions) { throw new Error( "Tool call failed: Missing required parameters - userInstructions is required", ); } if (!draftId) { throw new Error("Tool call failed: Missing required parameters - draftId is required"); } const gmail = new GmailService(); const draft = await gmail.getDraft(draftId); const prompt = await handleGetPrompt({ method: "prompts/get", params: { name: "gmail_edit_draft", arguments: { userInstructions, draftId, draft: JSON.stringify(draft) }, }, }); if (!prompt._meta?.responseSchema) { throw new Error("Invalid prompt configuration: missing response schema"); } await sendSamplingRequest({ method: "sampling/createMessage", params: { messages: prompt.messages.map((msg) => injectVariables(msg, { userInstructions, draftId, draft: JSON.stringify(draft), }), ) as Array<{ role: "user" | "assistant"; content: { type: "text"; text: string }; }>, maxTokens: 100000, temperature: 0.7, _meta: { callback: "edit_draft", responseSchema: prompt._meta.responseSchema, }, arguments: { userInstructions, draftId, draft: JSON.stringify(draft) }, }, }); return { content: [ { type: "text", text: `Your draft edit request has been received and is being processed, we will notify you when it is complete.`, }, ], }; } case "gmail_list_drafts": { const gmail = new GmailService(); const args = request.params.arguments as unknown as ListDraftsArgs; const { maxResults } = args; const drafts = await gmail.listDrafts(maxResults); return { content: [ { type: "text", text: JSON.stringify(drafts, null, 2), }, ], }; } case "gmail_delete_draft": { const gmail = new GmailService(); const args = request.params.arguments as unknown as DeleteDraftArgs; const { draftId } = args; await gmail.deleteDraft(draftId); return { content: [ { type: "text", text: JSON.stringify({ status: "Draft deleted successfully", }), }, ], }; } case "gmail_delete_email": { const gmail = new GmailService(); const args = request.params.arguments as unknown as TrashMessageArgs; const { messageId } = args; await gmail.trashMessage(messageId); return { content: [ { type: "text", text: JSON.stringify({ status: "Message moved to trash successfully", }), }, ], }; } default: throw new Error(`${TOOL_ERROR_MESSAGES.UNKNOWN_TOOL} ${request.params.name}`); } } catch (error) { console.error(`${TOOL_ERROR_MESSAGES.TOOL_CALL_FAILED} ${error}`); throw error; } }