@lengelhard/imap-email-mcp 1.0.3 → 1.0.4
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/index.js +31 -86
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -83,7 +83,7 @@ function findAttachmentParts(struct, parts = []) {
|
|
|
83
83
|
);
|
|
84
84
|
|
|
85
85
|
if (isAttachment) {
|
|
86
|
-
parts.push({ node, filename, contentType, disposition
|
|
86
|
+
parts.push({ node, filename, contentType, disposition });
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
|
|
@@ -348,27 +348,24 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
348
348
|
}
|
|
349
349
|
},
|
|
350
350
|
{
|
|
351
|
-
name: '
|
|
352
|
-
description: '
|
|
351
|
+
name: 'move_email',
|
|
352
|
+
description: 'Move an email from one folder to another (e.g. INBOX to Archive)',
|
|
353
353
|
inputSchema: {
|
|
354
354
|
type: 'object',
|
|
355
355
|
properties: {
|
|
356
356
|
uid: {
|
|
357
357
|
type: 'number',
|
|
358
|
-
description: 'Email UID'
|
|
359
|
-
},
|
|
360
|
-
part_id: {
|
|
361
|
-
type: 'string',
|
|
362
|
-
description: 'MIME part ID of the attachment (from get_email response)'
|
|
358
|
+
description: 'Email UID to move'
|
|
363
359
|
},
|
|
364
|
-
|
|
360
|
+
from_folder: {
|
|
365
361
|
type: 'string',
|
|
366
|
-
description: '
|
|
362
|
+
description: 'Source folder (default: INBOX)',
|
|
363
|
+
default: 'INBOX'
|
|
367
364
|
},
|
|
368
|
-
|
|
365
|
+
to_folder: {
|
|
369
366
|
type: 'string',
|
|
370
|
-
description: '
|
|
371
|
-
default: '
|
|
367
|
+
description: 'Destination folder (e.g. Archive, Sent, Trash)',
|
|
368
|
+
default: 'Archive'
|
|
372
369
|
}
|
|
373
370
|
},
|
|
374
371
|
required: ['uid']
|
|
@@ -542,7 +539,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
542
539
|
const fromParser = new Map(
|
|
543
540
|
(parsed.attachments || []).map(a => [
|
|
544
541
|
(a.filename || '').toLowerCase(),
|
|
545
|
-
{ filename: a.filename, contentType: a.contentType, size: a.size
|
|
542
|
+
{ filename: a.filename, contentType: a.contentType, size: a.size }
|
|
546
543
|
])
|
|
547
544
|
);
|
|
548
545
|
|
|
@@ -550,15 +547,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
550
547
|
// may have skipped: inline parts with filenames and parts that have
|
|
551
548
|
// no Content-Disposition at all but carry a Content-Type "name" param.
|
|
552
549
|
const fromStruct = findAttachmentParts(msg.attributes.struct || []);
|
|
553
|
-
for (const { filename, contentType
|
|
550
|
+
for (const { filename, contentType } of fromStruct) {
|
|
554
551
|
const key = (filename || '').toLowerCase();
|
|
555
|
-
if (filename) {
|
|
556
|
-
|
|
557
|
-
fromParser.set(key, { filename, contentType, size: null, partID });
|
|
558
|
-
} else {
|
|
559
|
-
// Enrich existing entry with partID from struct
|
|
560
|
-
fromParser.get(key).partID = partID;
|
|
561
|
-
}
|
|
552
|
+
if (filename && !fromParser.has(key)) {
|
|
553
|
+
fromParser.set(key, { filename, contentType, size: null });
|
|
562
554
|
}
|
|
563
555
|
}
|
|
564
556
|
|
|
@@ -803,80 +795,33 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
803
795
|
};
|
|
804
796
|
}
|
|
805
797
|
|
|
806
|
-
case '
|
|
807
|
-
const
|
|
798
|
+
case 'move_email': {
|
|
799
|
+
const fromFolder = args.from_folder || 'INBOX';
|
|
800
|
+
const toFolder = args.to_folder || 'Archive';
|
|
808
801
|
const connection = await connectIMAP();
|
|
809
802
|
|
|
810
803
|
try {
|
|
811
|
-
await connection.openBox(
|
|
812
|
-
|
|
813
|
-
//
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
804
|
+
await connection.openBox(fromFolder);
|
|
805
|
+
|
|
806
|
+
// imap-simple exposes the underlying imap connection via ._imap
|
|
807
|
+
// Use it to copy the message then delete the original
|
|
808
|
+
await new Promise((resolve, reject) => {
|
|
809
|
+
connection.imap.copy(args.uid, toFolder, (err) => {
|
|
810
|
+
if (err) reject(err);
|
|
811
|
+
else resolve();
|
|
812
|
+
});
|
|
817
813
|
});
|
|
818
814
|
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
const msg = structFetch[0];
|
|
824
|
-
let partID = args.part_id;
|
|
825
|
-
|
|
826
|
-
// If no partID given, find it by filename
|
|
827
|
-
if (!partID && args.filename) {
|
|
828
|
-
const attachmentParts = findAttachmentParts(msg.attributes.struct || []);
|
|
829
|
-
const match = attachmentParts.find(p =>
|
|
830
|
-
(p.filename || '').toLowerCase() === args.filename.toLowerCase()
|
|
831
|
-
);
|
|
832
|
-
if (match) partID = match.partID;
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
if (!partID) {
|
|
836
|
-
return {
|
|
837
|
-
content: [{ type: 'text', text: 'Could not determine part ID. Please provide part_id from get_email response.' }],
|
|
838
|
-
isError: true
|
|
839
|
-
};
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
// Fetch the specific MIME part
|
|
843
|
-
const partFetch = await connection.search([['UID', args.uid]], {
|
|
844
|
-
bodies: [partID],
|
|
845
|
-
struct: true
|
|
846
|
-
});
|
|
847
|
-
|
|
848
|
-
if (partFetch.length === 0) {
|
|
849
|
-
return { content: [{ type: 'text', text: 'Attachment part not found' }] };
|
|
850
|
-
}
|
|
851
|
-
|
|
852
|
-
const partMsg = partFetch[0];
|
|
853
|
-
const partData = partMsg.parts.find(p => p.which === partID);
|
|
854
|
-
|
|
855
|
-
if (!partData) {
|
|
856
|
-
return { content: [{ type: 'text', text: `Part ${partID} not found in message` }] };
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
// IMAP attachment parts are already base64-encoded by the mail server.
|
|
860
|
-
// Return the raw content as-is — do NOT re-encode, which would double-encode.
|
|
861
|
-
const rawContent = partData.body;
|
|
862
|
-
const base64Content = Buffer.isBuffer(rawContent)
|
|
863
|
-
? rawContent.toString('ascii').replace(/\s/g, '') // buffer → clean base64 string
|
|
864
|
-
: rawContent.replace(/\s/g, ''); // string → strip whitespace/newlines
|
|
865
|
-
|
|
866
|
-
// Get filename from struct for reference
|
|
867
|
-
const attachmentParts = findAttachmentParts(msg.attributes.struct || []);
|
|
868
|
-
const attachmentInfo = attachmentParts.find(p => p.partID === partID);
|
|
815
|
+
// Mark original as deleted and expunge
|
|
816
|
+
await connection.addFlags(args.uid, ['\\Deleted']);
|
|
817
|
+
await connection.closeBox(true); // true = expunge on close
|
|
869
818
|
|
|
870
819
|
return {
|
|
871
820
|
content: [{
|
|
872
821
|
type: 'text',
|
|
873
822
|
text: JSON.stringify({
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
filename: attachmentInfo?.filename || args.filename || 'attachment',
|
|
877
|
-
contentType: attachmentInfo?.contentType || 'application/octet-stream',
|
|
878
|
-
encoding: 'base64',
|
|
879
|
-
data: base64Content
|
|
823
|
+
success: true,
|
|
824
|
+
message: `Email ${args.uid} moved from ${fromFolder} to ${toFolder}`
|
|
880
825
|
}, null, 2)
|
|
881
826
|
}]
|
|
882
827
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lengelhard/imap-email-mcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "MCP server for Claude Code that provides email capabilities through IMAP/SMTP. Read, search, compose, and manage emails from any IMAP provider.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|