bluelamp 4.2.2 → 4.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/bluelamp-vscode-base +1 -1
- package/lib/auth/InteractiveLogin.js +1 -1
- package/lib/auth/PortalAuthClient.js +1 -1
- package/lib/auth/TokenManager.js +1 -1
- package/lib/mcp-gmail/index.js +141 -1
- package/lib/mcp-server/portal-api-client.cjs +1 -1
- package/package.json +1 -1
- package/scripts/playwright-launch.js +1 -1
package/lib/mcp-gmail/index.js
CHANGED
|
@@ -20,7 +20,7 @@ const CREDENTIALS_PATH = path.join(CONFIG_DIR, 'credentials.json');
|
|
|
20
20
|
const ACCOUNTS_PATH = path.join(CONFIG_DIR, 'accounts.json');
|
|
21
21
|
// Legacy single-token path (for migration)
|
|
22
22
|
const LEGACY_TOKEN_PATH = path.join(CONFIG_DIR, 'token.json');
|
|
23
|
-
const SCOPES = ['https://www.googleapis.com/auth/gmail.readonly'];
|
|
23
|
+
const SCOPES = ['https://www.googleapis.com/auth/gmail.readonly', 'https://www.googleapis.com/auth/gmail.compose'];
|
|
24
24
|
const DEFAULT_TEXT_BODY_LIMIT = 10000;
|
|
25
25
|
// --- Account discovery ---
|
|
26
26
|
function getAccounts() {
|
|
@@ -293,6 +293,36 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
293
293
|
required: ['items'],
|
|
294
294
|
},
|
|
295
295
|
},
|
|
296
|
+
{
|
|
297
|
+
name: 'prepare_outbox',
|
|
298
|
+
description: 'Stage a new message for review with optional file attachments. Saved to outbox for user approval before delivery.',
|
|
299
|
+
inputSchema: {
|
|
300
|
+
type: 'object',
|
|
301
|
+
properties: {
|
|
302
|
+
account: { type: 'string', description: 'Sender email account' },
|
|
303
|
+
to: { type: 'array', items: { type: 'string' }, description: 'Recipient email addresses' },
|
|
304
|
+
cc: { type: 'array', items: { type: 'string' }, description: 'CC email addresses (optional)' },
|
|
305
|
+
bcc: { type: 'array', items: { type: 'string' }, description: 'BCC email addresses (optional)' },
|
|
306
|
+
subject: { type: 'string', description: 'Email subject' },
|
|
307
|
+
body: { type: 'string', description: 'Email body (plain text)' },
|
|
308
|
+
html_body: { type: 'string', description: 'Email body (HTML, optional - overrides body if set)' },
|
|
309
|
+
attachments: {
|
|
310
|
+
type: 'array',
|
|
311
|
+
items: {
|
|
312
|
+
type: 'object',
|
|
313
|
+
properties: {
|
|
314
|
+
file_path: { type: 'string', description: 'Absolute path to the file to attach' },
|
|
315
|
+
filename: { type: 'string', description: 'Override filename (optional, defaults to basename)' },
|
|
316
|
+
},
|
|
317
|
+
required: ['file_path'],
|
|
318
|
+
},
|
|
319
|
+
description: 'File attachments (optional)',
|
|
320
|
+
},
|
|
321
|
+
reply_to_message_id: { type: 'string', description: 'Gmail message ID to reply to (optional, sets In-Reply-To and References headers)' },
|
|
322
|
+
},
|
|
323
|
+
required: ['account', 'to', 'subject', 'body'],
|
|
324
|
+
},
|
|
325
|
+
},
|
|
296
326
|
],
|
|
297
327
|
}));
|
|
298
328
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
@@ -505,6 +535,116 @@ ${htmlBody}
|
|
|
505
535
|
}
|
|
506
536
|
return { content: [{ type: 'text', text: JSON.stringify(results, null, 2) }] };
|
|
507
537
|
}
|
|
538
|
+
case 'prepare_outbox': {
|
|
539
|
+
const { account, to, cc, bcc, subject, body, html_body, attachments, reply_to_message_id } = args;
|
|
540
|
+
const auth = getAuthenticatedClient(account);
|
|
541
|
+
if (!auth) {
|
|
542
|
+
return {
|
|
543
|
+
content: [{ type: 'text', text: `Account ${account} not authenticated. Use authorize_account first.` }],
|
|
544
|
+
isError: true,
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
const gmail = getGmail(auth);
|
|
548
|
+
const boundary = `boundary_${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
|
549
|
+
const mimeLines = [];
|
|
550
|
+
mimeLines.push(`To: ${to.join(', ')}`);
|
|
551
|
+
if (cc?.length) mimeLines.push(`Cc: ${cc.join(', ')}`);
|
|
552
|
+
if (bcc?.length) mimeLines.push(`Bcc: ${bcc.join(', ')}`);
|
|
553
|
+
mimeLines.push(`Subject: =?UTF-8?B?${Buffer.from(subject).toString('base64')}?=`);
|
|
554
|
+
mimeLines.push('MIME-Version: 1.0');
|
|
555
|
+
if (reply_to_message_id) {
|
|
556
|
+
try {
|
|
557
|
+
const origMsg = await gmail.users.messages.get({ userId: 'me', id: reply_to_message_id, format: 'metadata', metadataHeaders: ['Message-ID'] });
|
|
558
|
+
const origMsgId = getHeader(origMsg.data.payload?.headers || [], 'Message-ID');
|
|
559
|
+
if (origMsgId) {
|
|
560
|
+
mimeLines.push(`In-Reply-To: ${origMsgId}`);
|
|
561
|
+
mimeLines.push(`References: ${origMsgId}`);
|
|
562
|
+
}
|
|
563
|
+
} catch {}
|
|
564
|
+
}
|
|
565
|
+
const hasAttachments = attachments?.length > 0;
|
|
566
|
+
if (hasAttachments) {
|
|
567
|
+
mimeLines.push(`Content-Type: multipart/mixed; boundary="${boundary}"`);
|
|
568
|
+
mimeLines.push('');
|
|
569
|
+
mimeLines.push(`--${boundary}`);
|
|
570
|
+
}
|
|
571
|
+
if (html_body) {
|
|
572
|
+
mimeLines.push('Content-Type: text/html; charset=UTF-8');
|
|
573
|
+
mimeLines.push('Content-Transfer-Encoding: base64');
|
|
574
|
+
mimeLines.push('');
|
|
575
|
+
mimeLines.push(Buffer.from(html_body).toString('base64'));
|
|
576
|
+
} else {
|
|
577
|
+
mimeLines.push('Content-Type: text/plain; charset=UTF-8');
|
|
578
|
+
mimeLines.push('Content-Transfer-Encoding: base64');
|
|
579
|
+
mimeLines.push('');
|
|
580
|
+
mimeLines.push(Buffer.from(body).toString('base64'));
|
|
581
|
+
}
|
|
582
|
+
if (hasAttachments) {
|
|
583
|
+
for (const att of attachments) {
|
|
584
|
+
const filePath = att.file_path;
|
|
585
|
+
if (!fs.existsSync(filePath)) {
|
|
586
|
+
return { content: [{ type: 'text', text: `Attachment file not found: ${filePath}` }], isError: true };
|
|
587
|
+
}
|
|
588
|
+
const fileData = fs.readFileSync(filePath);
|
|
589
|
+
const fileName = att.filename || path.basename(filePath);
|
|
590
|
+
const ext = path.extname(fileName).toLowerCase();
|
|
591
|
+
const mimeTypes = {
|
|
592
|
+
'.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
593
|
+
'.xls': 'application/vnd.ms-excel',
|
|
594
|
+
'.pdf': 'application/pdf',
|
|
595
|
+
'.csv': 'text/csv',
|
|
596
|
+
'.doc': 'application/msword',
|
|
597
|
+
'.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
598
|
+
'.zip': 'application/zip',
|
|
599
|
+
'.png': 'image/png',
|
|
600
|
+
'.jpg': 'image/jpeg',
|
|
601
|
+
'.jpeg': 'image/jpeg',
|
|
602
|
+
};
|
|
603
|
+
const mimeType = mimeTypes[ext] || 'application/octet-stream';
|
|
604
|
+
const encodedFilename = `=?UTF-8?B?${Buffer.from(fileName).toString('base64')}?=`;
|
|
605
|
+
mimeLines.push(`--${boundary}`);
|
|
606
|
+
mimeLines.push(`Content-Type: ${mimeType}; name="${encodedFilename}"`);
|
|
607
|
+
mimeLines.push(`Content-Disposition: attachment; filename="${encodedFilename}"`);
|
|
608
|
+
mimeLines.push('Content-Transfer-Encoding: base64');
|
|
609
|
+
mimeLines.push('');
|
|
610
|
+
mimeLines.push(fileData.toString('base64'));
|
|
611
|
+
}
|
|
612
|
+
mimeLines.push(`--${boundary}--`);
|
|
613
|
+
}
|
|
614
|
+
const rawMessage = mimeLines.join('\r\n');
|
|
615
|
+
const encodedMessage = Buffer.from(rawMessage)
|
|
616
|
+
.toString('base64')
|
|
617
|
+
.replace(/\+/g, '-')
|
|
618
|
+
.replace(/\//g, '_')
|
|
619
|
+
.replace(/=+$/, '');
|
|
620
|
+
const draft = await gmail.users.drafts.create({
|
|
621
|
+
userId: 'me',
|
|
622
|
+
requestBody: {
|
|
623
|
+
message: {
|
|
624
|
+
raw: encodedMessage,
|
|
625
|
+
threadId: reply_to_message_id ? (await gmail.users.messages.get({ userId: 'me', id: reply_to_message_id, format: 'minimal' })).data.threadId || undefined : undefined,
|
|
626
|
+
},
|
|
627
|
+
},
|
|
628
|
+
});
|
|
629
|
+
const attNames = hasAttachments
|
|
630
|
+
? attachments.map(a => a.filename || path.basename(a.file_path))
|
|
631
|
+
: [];
|
|
632
|
+
return {
|
|
633
|
+
content: [{
|
|
634
|
+
type: 'text',
|
|
635
|
+
text: JSON.stringify({
|
|
636
|
+
success: true,
|
|
637
|
+
draftId: draft.data.id,
|
|
638
|
+
messageId: draft.data.message?.id,
|
|
639
|
+
to,
|
|
640
|
+
cc: cc || [],
|
|
641
|
+
subject,
|
|
642
|
+
attachments: attNames,
|
|
643
|
+
message: 'Draft created successfully. Open Gmail to review and send.',
|
|
644
|
+
}, null, 2),
|
|
645
|
+
}],
|
|
646
|
+
};
|
|
647
|
+
}
|
|
508
648
|
default:
|
|
509
649
|
return {
|
|
510
650
|
content: [{ type: 'text', text: `Unknown tool: ${name}` }],
|