소스 검색

feat: add attachment support for send-email and create-draft

Add optional `attachments` parameter (array of absolute file paths) to
both `send-email` and `create-draft` tools. This enables AI assistants
to attach files when composing emails, e.g. sending a generated PDF
or spreadsheet directly via Apple Mail.

Uses AppleScript's `make new attachment` to attach files to outgoing messages.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Michael Henze 3 달 전
부모
커밋
aba8d5b98d
2개의 변경된 파일42개의 추가작업 그리고 8개의 파일을 삭제
  1. 16 6
      src/index.ts
  2. 26 2
      src/services/appleMailManager.ts

+ 16 - 6
src/index.ts

@@ -191,15 +191,20 @@ server.tool(
     cc: z.array(z.string()).optional().describe("CC recipients"),
     bcc: z.array(z.string()).optional().describe("BCC recipients"),
     account: z.string().optional().describe("Account to send from"),
+    attachments: z
+      .array(z.string())
+      .optional()
+      .describe("Absolute file paths to attach (e.g., ['/Users/me/report.pdf'])"),
   },
-  withErrorHandling(({ to, subject, body, cc, bcc, account }) => {
-    const success = mailManager.sendEmail(to, subject, body, cc, bcc, account);
+  withErrorHandling(({ to, subject, body, cc, bcc, account, attachments }) => {
+    const success = mailManager.sendEmail(to, subject, body, cc, bcc, account, attachments);
 
     if (!success) {
       return errorResponse("Failed to send email. Check Mail.app configuration.");
     }
 
-    return successResponse(`Email sent to ${to.join(", ")}`);
+    const attachInfo = attachments?.length ? ` with ${attachments.length} attachment(s)` : "";
+    return successResponse(`Email sent to ${to.join(", ")}${attachInfo}`);
   }, "Error sending email")
 );
 
@@ -214,15 +219,20 @@ server.tool(
     cc: z.array(z.string()).optional().describe("CC recipients"),
     bcc: z.array(z.string()).optional().describe("BCC recipients"),
     account: z.string().optional().describe("Account to create draft in"),
+    attachments: z
+      .array(z.string())
+      .optional()
+      .describe("Absolute file paths to attach (e.g., ['/Users/me/report.pdf'])"),
   },
-  withErrorHandling(({ to, subject, body, cc, bcc, account }) => {
-    const success = mailManager.createDraft(to, subject, body, cc, bcc, account);
+  withErrorHandling(({ to, subject, body, cc, bcc, account, attachments }) => {
+    const success = mailManager.createDraft(to, subject, body, cc, bcc, account, attachments);
 
     if (!success) {
       return errorResponse("Failed to create draft. Check Mail.app configuration.");
     }
 
-    return successResponse(`Draft created for ${to.join(", ")}`);
+    const attachInfo = attachments?.length ? ` with ${attachments.length} attachment(s)` : "";
+    return successResponse(`Draft created for ${to.join(", ")}${attachInfo}`);
   }, "Error creating draft")
 );
 

+ 26 - 2
src/services/appleMailManager.ts

@@ -617,7 +617,8 @@ export class AppleMailManager {
     body: string,
     cc?: string[],
     bcc?: string[],
-    account?: string
+    account?: string,
+    attachments?: string[]
   ): boolean {
     const safeSubject = escapeForAppleScript(subject);
     const safeBody = escapeForAppleScript(body);
@@ -638,6 +639,15 @@ export class AppleMailManager {
       }
     }
 
+    // Build attachment additions
+    let attachmentCommands = "";
+    if (attachments) {
+      for (const filePath of attachments) {
+        const safePath = escapeForAppleScript(filePath);
+        attachmentCommands += `make new attachment with properties {file name:POSIX file "${safePath}"} at after the last paragraph\n`;
+      }
+    }
+
     let sendCommand: string;
     if (account) {
       const safeAccount = escapeForAppleScript(account);
@@ -646,6 +656,7 @@ export class AppleMailManager {
         tell newMessage
           ${recipientCommands}
           set sender to "${safeAccount}"
+          ${attachmentCommands}
         end tell
         send newMessage
         return "sent"
@@ -655,6 +666,7 @@ export class AppleMailManager {
         set newMessage to make new outgoing message with properties {subject:"${safeSubject}", content:"${safeBody}", visible:true}
         tell newMessage
           ${recipientCommands}
+          ${attachmentCommands}
         end tell
         send newMessage
         return "sent"
@@ -689,7 +701,8 @@ export class AppleMailManager {
     body: string,
     cc?: string[],
     bcc?: string[],
-    account?: string
+    account?: string,
+    attachments?: string[]
   ): boolean {
     const safeSubject = escapeForAppleScript(subject);
     const safeBody = escapeForAppleScript(body);
@@ -710,6 +723,15 @@ export class AppleMailManager {
       }
     }
 
+    // Build attachment additions
+    let attachmentCommands = "";
+    if (attachments) {
+      for (const filePath of attachments) {
+        const safePath = escapeForAppleScript(filePath);
+        attachmentCommands += `make new attachment with properties {file name:POSIX file "${safePath}"} at after the last paragraph\n`;
+      }
+    }
+
     let draftCommand: string;
     if (account) {
       const safeAccount = escapeForAppleScript(account);
@@ -718,6 +740,7 @@ export class AppleMailManager {
         tell newMessage
           ${recipientCommands}
           set sender to "${safeAccount}"
+          ${attachmentCommands}
         end tell
         return "draft created"
       `;
@@ -726,6 +749,7 @@ export class AppleMailManager {
         set newMessage to make new outgoing message with properties {subject:"${safeSubject}", content:"${safeBody}", visible:false}
         tell newMessage
           ${recipientCommands}
+          ${attachmentCommands}
         end tell
         return "draft created"
       `;