@lengelhard/imap-email-mcp 1.1.0 → 1.2.1
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 +105 -3
- 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, partID: node.partID });
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
|
|
@@ -347,6 +347,33 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
347
347
|
required: ['to', 'subject']
|
|
348
348
|
}
|
|
349
349
|
},
|
|
350
|
+
{
|
|
351
|
+
name: 'download_attachment',
|
|
352
|
+
description: 'Download a specific email attachment as base64-encoded content. Use the partID from get_email response.',
|
|
353
|
+
inputSchema: {
|
|
354
|
+
type: 'object',
|
|
355
|
+
properties: {
|
|
356
|
+
uid: {
|
|
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)'
|
|
363
|
+
},
|
|
364
|
+
filename: {
|
|
365
|
+
type: 'string',
|
|
366
|
+
description: 'Filename of the attachment (used as fallback to find part_id if not provided)'
|
|
367
|
+
},
|
|
368
|
+
folder: {
|
|
369
|
+
type: 'string',
|
|
370
|
+
description: 'Folder name (default: INBOX)',
|
|
371
|
+
default: 'INBOX'
|
|
372
|
+
}
|
|
373
|
+
},
|
|
374
|
+
required: ['uid']
|
|
375
|
+
}
|
|
376
|
+
},
|
|
350
377
|
{
|
|
351
378
|
name: 'delete_email',
|
|
352
379
|
description: 'Delete an email by UID',
|
|
@@ -900,6 +927,83 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
900
927
|
};
|
|
901
928
|
}
|
|
902
929
|
|
|
930
|
+
case 'download_attachment': {
|
|
931
|
+
const folder = args.folder || 'INBOX';
|
|
932
|
+
const connection = await connectIMAP();
|
|
933
|
+
|
|
934
|
+
try {
|
|
935
|
+
await connection.openBox(folder);
|
|
936
|
+
|
|
937
|
+
// Fetch struct to locate the part
|
|
938
|
+
const structFetch = await connection.search([['UID', args.uid]], {
|
|
939
|
+
bodies: [],
|
|
940
|
+
struct: true
|
|
941
|
+
});
|
|
942
|
+
|
|
943
|
+
if (structFetch.length === 0) {
|
|
944
|
+
return { content: [{ type: 'text', text: 'Email not found' }] };
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
const msg = structFetch[0];
|
|
948
|
+
let partID = args.part_id;
|
|
949
|
+
|
|
950
|
+
// If no partID given, find it by filename
|
|
951
|
+
if (!partID && args.filename) {
|
|
952
|
+
const attachmentParts = findAttachmentParts(msg.attributes.struct || []);
|
|
953
|
+
const match = attachmentParts.find(p =>
|
|
954
|
+
(p.filename || '').toLowerCase() === args.filename.toLowerCase()
|
|
955
|
+
);
|
|
956
|
+
if (match) partID = match.partID;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
if (!partID) {
|
|
960
|
+
return {
|
|
961
|
+
content: [{ type: 'text', text: 'Could not determine part ID. Please provide part_id from get_email response.' }],
|
|
962
|
+
isError: true
|
|
963
|
+
};
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
// Fetch the specific MIME part
|
|
967
|
+
const partFetch = await connection.search([['UID', args.uid]], {
|
|
968
|
+
bodies: [partID],
|
|
969
|
+
struct: true
|
|
970
|
+
});
|
|
971
|
+
|
|
972
|
+
if (partFetch.length === 0) {
|
|
973
|
+
return { content: [{ type: 'text', text: 'Attachment part not found' }] };
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
const partData = partFetch[0].parts.find(p => p.which === partID);
|
|
977
|
+
|
|
978
|
+
if (!partData) {
|
|
979
|
+
return { content: [{ type: 'text', text: `Part ${partID} not found in message` }] };
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
// imap-simple returns the body already base64-encoded by the mail server.
|
|
983
|
+
// Re-encoding would corrupt the output — strip whitespace and return directly.
|
|
984
|
+
const base64Content = String(partData.body).replace(/\s+/g, '');
|
|
985
|
+
|
|
986
|
+
const attachmentParts = findAttachmentParts(msg.attributes.struct || []);
|
|
987
|
+
const attachmentInfo = attachmentParts.find(p => p.partID === partID);
|
|
988
|
+
|
|
989
|
+
return {
|
|
990
|
+
content: [{
|
|
991
|
+
type: 'text',
|
|
992
|
+
text: JSON.stringify({
|
|
993
|
+
uid: args.uid,
|
|
994
|
+
partID,
|
|
995
|
+
filename: attachmentInfo?.filename || args.filename || 'attachment',
|
|
996
|
+
contentType: attachmentInfo?.contentType || 'application/octet-stream',
|
|
997
|
+
encoding: 'base64',
|
|
998
|
+
data: base64Content
|
|
999
|
+
}, null, 2)
|
|
1000
|
+
}]
|
|
1001
|
+
};
|
|
1002
|
+
} finally {
|
|
1003
|
+
connection.end();
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
|
|
903
1007
|
case 'delete_email': {
|
|
904
1008
|
const folder = args.folder || 'INBOX';
|
|
905
1009
|
const connection = await connectIMAP();
|
|
@@ -923,8 +1027,6 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
923
1027
|
try {
|
|
924
1028
|
await connection.openBox(fromFolder);
|
|
925
1029
|
|
|
926
|
-
// node-imap exposes move() on the underlying imap object.
|
|
927
|
-
// We wrap it in a Promise so it fits the async/await flow.
|
|
928
1030
|
await new Promise((resolve, reject) => {
|
|
929
1031
|
connection.imap.move(args.uid, toFolder, (err) => {
|
|
930
1032
|
if (err) reject(err);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lengelhard/imap-email-mcp",
|
|
3
|
-
"version": "1.1
|
|
3
|
+
"version": "1.2.1",
|
|
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",
|