From 72fbb8cc3970434b0ba650c50969c7588257c525 Mon Sep 17 00:00:00 2001 From: Sweets Sweetman Date: Wed, 7 Jan 2026 13:26:21 -0500 Subject: [PATCH 1/3] feat: store sent emails as memories when room_id provided MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Call insertMemories to store assistant email content - Call insertMemoryEmail to link email with memory - Only insert memory when room_id is provided - Add tests for memory insertion behavior 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../__tests__/registerSendEmailTool.test.ts | 55 +++++++++++++++++++ lib/mcp/tools/registerSendEmailTool.ts | 24 ++++++++ 2 files changed, 79 insertions(+) diff --git a/lib/mcp/tools/__tests__/registerSendEmailTool.test.ts b/lib/mcp/tools/__tests__/registerSendEmailTool.test.ts index 509e1aba..d093934e 100644 --- a/lib/mcp/tools/__tests__/registerSendEmailTool.test.ts +++ b/lib/mcp/tools/__tests__/registerSendEmailTool.test.ts @@ -4,11 +4,25 @@ import { registerSendEmailTool } from "../registerSendEmailTool"; import { NextResponse } from "next/server"; const mockSendEmailWithResend = vi.fn(); +const mockInsertMemories = vi.fn(); +const mockInsertMemoryEmail = vi.fn(); vi.mock("@/lib/emails/sendEmail", () => ({ sendEmailWithResend: (...args: unknown[]) => mockSendEmailWithResend(...args), })); +vi.mock("@/lib/supabase/memories/insertMemories", () => ({ + default: (...args: unknown[]) => mockInsertMemories(...args), +})); + +vi.mock("@/lib/supabase/memory_emails/insertMemoryEmail", () => ({ + default: (...args: unknown[]) => mockInsertMemoryEmail(...args), +})); + +vi.mock("@/lib/uuid/generateUUID", () => ({ + generateUUID: () => "mock-uuid-123", +})); + describe("registerSendEmailTool", () => { let mockServer: McpServer; let registeredHandler: (args: unknown) => Promise; @@ -97,4 +111,45 @@ describe("registerSendEmailTool", () => { ], }); }); + + it("inserts memory and memory_email when room_id is provided", async () => { + mockSendEmailWithResend.mockResolvedValue({ id: "email-456" }); + mockInsertMemories.mockResolvedValue({ id: "mock-uuid-123" }); + mockInsertMemoryEmail.mockResolvedValue({}); + + await registeredHandler({ + to: ["test@example.com"], + subject: "Test Subject", + text: "Email content for memory", + room_id: "room-789", + }); + + expect(mockInsertMemories).toHaveBeenCalledWith({ + id: "mock-uuid-123", + room_id: "room-789", + content: { + role: "assistant", + content: "Email content for memory", + }, + }); + + expect(mockInsertMemoryEmail).toHaveBeenCalledWith({ + email_id: "email-456", + memory: "mock-uuid-123", + message_id: "email-456", + }); + }); + + it("does not insert memory when room_id is not provided", async () => { + mockSendEmailWithResend.mockResolvedValue({ id: "email-456" }); + + await registeredHandler({ + to: ["test@example.com"], + subject: "Test Subject", + text: "Email content", + }); + + expect(mockInsertMemories).not.toHaveBeenCalled(); + expect(mockInsertMemoryEmail).not.toHaveBeenCalled(); + }); }); diff --git a/lib/mcp/tools/registerSendEmailTool.ts b/lib/mcp/tools/registerSendEmailTool.ts index dd4fa3ef..92168588 100644 --- a/lib/mcp/tools/registerSendEmailTool.ts +++ b/lib/mcp/tools/registerSendEmailTool.ts @@ -6,6 +6,9 @@ import { getToolResultError } from "@/lib/mcp/getToolResultError"; import { RECOUP_FROM_EMAIL } from "@/lib/const"; import { getEmailFooter } from "@/lib/emails/getEmailFooter"; import { NextResponse } from "next/server"; +import insertMemories from "@/lib/supabase/memories/insertMemories"; +import insertMemoryEmail from "@/lib/supabase/memory_emails/insertMemoryEmail"; +import { generateUUID } from "@/lib/uuid/generateUUID"; /** * Registers the "send_email" tool on the MCP server. @@ -43,6 +46,27 @@ export function registerSendEmailTool(server: McpServer): void { ); } + // If room_id is provided, store the sent email as a memory + if (room_id && result.id) { + const memoryId = generateUUID(); + const emailContent = text || html || ""; + + await insertMemories({ + id: memoryId, + room_id, + content: { + role: "assistant", + content: emailContent, + }, + }); + + await insertMemoryEmail({ + email_id: result.id, + memory: memoryId, + message_id: result.id, + }); + } + return getToolResultSuccess({ success: true, message: `Email sent successfully from ${RECOUP_FROM_EMAIL} to ${to}. CC: ${cc.length > 0 ? JSON.stringify(cc) : "none"}.`, From 2fd4eeee64480ebd0ee81bfe6a823f89993cd2bc Mon Sep 17 00:00:00 2001 From: Sweets Sweetman Date: Wed, 7 Jan 2026 13:58:16 -0500 Subject: [PATCH 2/3] refactor: use getMessages and filterMessageContentForMemories for memory content MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Consistent with validateNewEmailMemory pattern for storing email memories 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../__tests__/registerSendEmailTool.test.ts | 22 +++++++++++++++++++ lib/mcp/tools/registerSendEmailTool.ts | 8 +++---- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/lib/mcp/tools/__tests__/registerSendEmailTool.test.ts b/lib/mcp/tools/__tests__/registerSendEmailTool.test.ts index 8be73776..9c396502 100644 --- a/lib/mcp/tools/__tests__/registerSendEmailTool.test.ts +++ b/lib/mcp/tools/__tests__/registerSendEmailTool.test.ts @@ -19,6 +19,27 @@ vi.mock("@/lib/supabase/memory_emails/insertMemoryEmail", () => ({ default: (...args: unknown[]) => mockInsertMemoryEmail(...args), })); +vi.mock("@/lib/messages/getMessages", () => ({ + getMessages: (content: string, role: string) => [ + { + id: "mock-message-id", + role, + parts: [{ type: "text", text: content }], + }, + ], +})); + +vi.mock("@/lib/messages/filterMessageContentForMemories", () => ({ + default: (message: { role: string; parts: { type: string; text: string }[] }) => ({ + role: message.role, + parts: message.parts, + content: message.parts + .filter((part: { type: string }) => part.type === "text") + .map((part: { type: string; text?: string }) => part.text || "") + .join(""), + }), +})); + describe("registerSendEmailTool", () => { let mockServer: McpServer; let registeredHandler: (args: unknown) => Promise; @@ -125,6 +146,7 @@ describe("registerSendEmailTool", () => { room_id: "room-789", content: { role: "assistant", + parts: [{ type: "text", text: "Email content for memory" }], content: "Email content for memory", }, }); diff --git a/lib/mcp/tools/registerSendEmailTool.ts b/lib/mcp/tools/registerSendEmailTool.ts index cf1342c1..27a5a1ab 100644 --- a/lib/mcp/tools/registerSendEmailTool.ts +++ b/lib/mcp/tools/registerSendEmailTool.ts @@ -9,6 +9,8 @@ import { NextResponse } from "next/server"; import { marked } from "marked"; import insertMemories from "@/lib/supabase/memories/insertMemories"; import insertMemoryEmail from "@/lib/supabase/memory_emails/insertMemoryEmail"; +import { getMessages } from "@/lib/messages/getMessages"; +import filterMessageContentForMemories from "@/lib/messages/filterMessageContentForMemories"; /** * Registers the "send_email" tool on the MCP server. @@ -49,14 +51,12 @@ export function registerSendEmailTool(server: McpServer): void { // If room_id is provided, store the sent email as a memory if (room_id && result.id) { const emailContent = text || html || ""; + const assistantMessage = getMessages(emailContent, "assistant")[0]; await insertMemories({ id: result.id, room_id, - content: { - role: "assistant", - content: emailContent, - }, + content: filterMessageContentForMemories(assistantMessage), }); await insertMemoryEmail({ From c3798c41437d682e491b010f216e849ccb754006 Mon Sep 17 00:00:00 2001 From: Sweets Sweetman Date: Wed, 7 Jan 2026 14:09:46 -0500 Subject: [PATCH 3/3] revert: remove memory insertion from send_email MCP tool MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Memory linking should be handled in Chat repo where message_id is available after the assistant message is saved to the memories table. The MCP tool now just: - Sends the email via Resend - Returns email_id in result for Chat to use with insertMemoryEmail 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../__tests__/registerSendEmailTool.test.ts | 73 ------------------- lib/mcp/tools/registerSendEmailTool.ts | 22 ------ 2 files changed, 95 deletions(-) diff --git a/lib/mcp/tools/__tests__/registerSendEmailTool.test.ts b/lib/mcp/tools/__tests__/registerSendEmailTool.test.ts index 9c396502..509e1aba 100644 --- a/lib/mcp/tools/__tests__/registerSendEmailTool.test.ts +++ b/lib/mcp/tools/__tests__/registerSendEmailTool.test.ts @@ -4,42 +4,11 @@ import { registerSendEmailTool } from "../registerSendEmailTool"; import { NextResponse } from "next/server"; const mockSendEmailWithResend = vi.fn(); -const mockInsertMemories = vi.fn(); -const mockInsertMemoryEmail = vi.fn(); vi.mock("@/lib/emails/sendEmail", () => ({ sendEmailWithResend: (...args: unknown[]) => mockSendEmailWithResend(...args), })); -vi.mock("@/lib/supabase/memories/insertMemories", () => ({ - default: (...args: unknown[]) => mockInsertMemories(...args), -})); - -vi.mock("@/lib/supabase/memory_emails/insertMemoryEmail", () => ({ - default: (...args: unknown[]) => mockInsertMemoryEmail(...args), -})); - -vi.mock("@/lib/messages/getMessages", () => ({ - getMessages: (content: string, role: string) => [ - { - id: "mock-message-id", - role, - parts: [{ type: "text", text: content }], - }, - ], -})); - -vi.mock("@/lib/messages/filterMessageContentForMemories", () => ({ - default: (message: { role: string; parts: { type: string; text: string }[] }) => ({ - role: message.role, - parts: message.parts, - content: message.parts - .filter((part: { type: string }) => part.type === "text") - .map((part: { type: string; text?: string }) => part.text || "") - .join(""), - }), -})); - describe("registerSendEmailTool", () => { let mockServer: McpServer; let registeredHandler: (args: unknown) => Promise; @@ -128,46 +97,4 @@ describe("registerSendEmailTool", () => { ], }); }); - - it("inserts memory and memory_email when room_id is provided", async () => { - mockSendEmailWithResend.mockResolvedValue({ id: "email-456" }); - mockInsertMemories.mockResolvedValue({ id: "email-456" }); - mockInsertMemoryEmail.mockResolvedValue({}); - - await registeredHandler({ - to: ["test@example.com"], - subject: "Test Subject", - text: "Email content for memory", - room_id: "room-789", - }); - - expect(mockInsertMemories).toHaveBeenCalledWith({ - id: "email-456", - room_id: "room-789", - content: { - role: "assistant", - parts: [{ type: "text", text: "Email content for memory" }], - content: "Email content for memory", - }, - }); - - expect(mockInsertMemoryEmail).toHaveBeenCalledWith({ - email_id: "email-456", - memory: "email-456", - message_id: "email-456", - }); - }); - - it("does not insert memory when room_id is not provided", async () => { - mockSendEmailWithResend.mockResolvedValue({ id: "email-456" }); - - await registeredHandler({ - to: ["test@example.com"], - subject: "Test Subject", - text: "Email content", - }); - - expect(mockInsertMemories).not.toHaveBeenCalled(); - expect(mockInsertMemoryEmail).not.toHaveBeenCalled(); - }); }); diff --git a/lib/mcp/tools/registerSendEmailTool.ts b/lib/mcp/tools/registerSendEmailTool.ts index 27a5a1ab..3f29eb5d 100644 --- a/lib/mcp/tools/registerSendEmailTool.ts +++ b/lib/mcp/tools/registerSendEmailTool.ts @@ -7,10 +7,6 @@ import { RECOUP_FROM_EMAIL } from "@/lib/const"; import { getEmailFooter } from "@/lib/emails/getEmailFooter"; import { NextResponse } from "next/server"; import { marked } from "marked"; -import insertMemories from "@/lib/supabase/memories/insertMemories"; -import insertMemoryEmail from "@/lib/supabase/memory_emails/insertMemoryEmail"; -import { getMessages } from "@/lib/messages/getMessages"; -import filterMessageContentForMemories from "@/lib/messages/filterMessageContentForMemories"; /** * Registers the "send_email" tool on the MCP server. @@ -48,24 +44,6 @@ export function registerSendEmailTool(server: McpServer): void { ); } - // If room_id is provided, store the sent email as a memory - if (room_id && result.id) { - const emailContent = text || html || ""; - const assistantMessage = getMessages(emailContent, "assistant")[0]; - - await insertMemories({ - id: result.id, - room_id, - content: filterMessageContentForMemories(assistantMessage), - }); - - await insertMemoryEmail({ - email_id: result.id, - memory: result.id, - message_id: result.id, - }); - } - return getToolResultSuccess({ success: true, message: `Email sent successfully from ${RECOUP_FROM_EMAIL} to ${to}. CC: ${cc.length > 0 ? JSON.stringify(cc) : "none"}.`,