@lengelhard/imap-email-mcp 1.0.4 → 1.1.0
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 +184 -23
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -347,9 +347,28 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
347
347
|
required: ['to', 'subject']
|
|
348
348
|
}
|
|
349
349
|
},
|
|
350
|
+
{
|
|
351
|
+
name: 'delete_email',
|
|
352
|
+
description: 'Delete an email by UID',
|
|
353
|
+
inputSchema: {
|
|
354
|
+
type: 'object',
|
|
355
|
+
properties: {
|
|
356
|
+
uid: {
|
|
357
|
+
type: 'number',
|
|
358
|
+
description: 'Email UID to delete'
|
|
359
|
+
},
|
|
360
|
+
folder: {
|
|
361
|
+
type: 'string',
|
|
362
|
+
description: 'Folder name (default: INBOX)',
|
|
363
|
+
default: 'INBOX'
|
|
364
|
+
}
|
|
365
|
+
},
|
|
366
|
+
required: ['uid']
|
|
367
|
+
}
|
|
368
|
+
},
|
|
350
369
|
{
|
|
351
370
|
name: 'move_email',
|
|
352
|
-
description: 'Move an email from one folder to another (e.g.
|
|
371
|
+
description: 'Move an email from one folder to another (e.g. Inbox → Action, Waiting, Delegated, Hold)',
|
|
353
372
|
inputSchema: {
|
|
354
373
|
type: 'object',
|
|
355
374
|
properties: {
|
|
@@ -359,35 +378,61 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
359
378
|
},
|
|
360
379
|
from_folder: {
|
|
361
380
|
type: 'string',
|
|
362
|
-
description: 'Source folder (default: INBOX)',
|
|
381
|
+
description: 'Source folder name (default: INBOX)',
|
|
363
382
|
default: 'INBOX'
|
|
364
383
|
},
|
|
365
384
|
to_folder: {
|
|
366
385
|
type: 'string',
|
|
367
|
-
description: 'Destination folder (e.g.
|
|
368
|
-
default: 'Archive'
|
|
386
|
+
description: 'Destination folder name (e.g. Action, Waiting, Delegated, Hold, Archive)'
|
|
369
387
|
}
|
|
370
388
|
},
|
|
371
|
-
required: ['uid']
|
|
389
|
+
required: ['uid', 'to_folder']
|
|
372
390
|
}
|
|
373
391
|
},
|
|
374
392
|
{
|
|
375
|
-
name: '
|
|
376
|
-
description: '
|
|
393
|
+
name: 'flag_email',
|
|
394
|
+
description: 'Add or remove the \\Flagged flag on an email (star/unstar). Used to manage the /Flagged urgency overlay.',
|
|
377
395
|
inputSchema: {
|
|
378
396
|
type: 'object',
|
|
379
397
|
properties: {
|
|
380
398
|
uid: {
|
|
381
399
|
type: 'number',
|
|
382
|
-
description: 'Email UID
|
|
400
|
+
description: 'Email UID'
|
|
383
401
|
},
|
|
384
402
|
folder: {
|
|
385
403
|
type: 'string',
|
|
386
|
-
description: 'Folder
|
|
404
|
+
description: 'Folder containing the email (default: INBOX)',
|
|
405
|
+
default: 'INBOX'
|
|
406
|
+
},
|
|
407
|
+
flagged: {
|
|
408
|
+
type: 'boolean',
|
|
409
|
+
description: 'true to add the flag (star), false to remove it (unstar)'
|
|
410
|
+
}
|
|
411
|
+
},
|
|
412
|
+
required: ['uid', 'flagged']
|
|
413
|
+
}
|
|
414
|
+
},
|
|
415
|
+
{
|
|
416
|
+
name: 'mark_seen',
|
|
417
|
+
description: 'Mark an email as read (seen) or unread (unseen). Used to signal triage state via the \\Seen flag.',
|
|
418
|
+
inputSchema: {
|
|
419
|
+
type: 'object',
|
|
420
|
+
properties: {
|
|
421
|
+
uid: {
|
|
422
|
+
type: 'number',
|
|
423
|
+
description: 'Email UID'
|
|
424
|
+
},
|
|
425
|
+
folder: {
|
|
426
|
+
type: 'string',
|
|
427
|
+
description: 'Folder containing the email (default: INBOX)',
|
|
387
428
|
default: 'INBOX'
|
|
429
|
+
},
|
|
430
|
+
seen: {
|
|
431
|
+
type: 'boolean',
|
|
432
|
+
description: 'true to mark as read, false to mark as unread'
|
|
388
433
|
}
|
|
389
434
|
},
|
|
390
|
-
required: ['uid']
|
|
435
|
+
required: ['uid', 'seen']
|
|
391
436
|
}
|
|
392
437
|
}
|
|
393
438
|
]
|
|
@@ -781,47 +826,149 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
781
826
|
bcc: args.bcc
|
|
782
827
|
};
|
|
783
828
|
|
|
829
|
+
// Send via SMTP and capture the raw RFC 5322 message
|
|
784
830
|
const info = await transporter.sendMail(mailOptions);
|
|
785
831
|
|
|
832
|
+
// Build RFC 5322 message string to APPEND to Sent folder via IMAP
|
|
833
|
+
// Use the same content that was sent so the Sent copy is identical
|
|
834
|
+
const sentDate = new Date();
|
|
835
|
+
const boundary = `----=_Part_${Date.now()}`;
|
|
836
|
+
let rawMessage = '';
|
|
837
|
+
rawMessage += `From: ${SMTP_CONFIG.auth.user}\r\n`;
|
|
838
|
+
rawMessage += `To: ${args.to}\r\n`;
|
|
839
|
+
if (args.cc) rawMessage += `Cc: ${args.cc}\r\n`;
|
|
840
|
+
if (args.bcc) rawMessage += `Bcc: ${args.bcc}\r\n`;
|
|
841
|
+
rawMessage += `Subject: ${args.subject}\r\n`;
|
|
842
|
+
rawMessage += `Date: ${sentDate.toUTCString()}\r\n`;
|
|
843
|
+
rawMessage += `Message-ID: ${info.messageId}\r\n`;
|
|
844
|
+
rawMessage += `MIME-Version: 1.0\r\n`;
|
|
845
|
+
|
|
846
|
+
if (args.html) {
|
|
847
|
+
rawMessage += `Content-Type: multipart/alternative; boundary="${boundary}"\r\n\r\n`;
|
|
848
|
+
rawMessage += `--${boundary}\r\n`;
|
|
849
|
+
rawMessage += `Content-Type: text/plain; charset=utf-8\r\n\r\n`;
|
|
850
|
+
rawMessage += `${args.body || ''}\r\n`;
|
|
851
|
+
rawMessage += `--${boundary}\r\n`;
|
|
852
|
+
rawMessage += `Content-Type: text/html; charset=utf-8\r\n\r\n`;
|
|
853
|
+
rawMessage += `${args.html}\r\n`;
|
|
854
|
+
rawMessage += `--${boundary}--\r\n`;
|
|
855
|
+
} else {
|
|
856
|
+
rawMessage += `Content-Type: text/plain; charset=utf-8\r\n\r\n`;
|
|
857
|
+
rawMessage += `${args.body || ''}\r\n`;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
// APPEND to Sent folder via IMAP
|
|
861
|
+
let sentFolderResult = 'skipped';
|
|
862
|
+
try {
|
|
863
|
+
const connection = await connectIMAP();
|
|
864
|
+
try {
|
|
865
|
+
// Try common Sent folder names in order
|
|
866
|
+
const sentFolderCandidates = ['Sent', 'INBOX.Sent', 'Sent Items', 'Sent Mail', '[Gmail]/Sent Mail'];
|
|
867
|
+
let sentFolder = 'Sent'; // default fallback
|
|
868
|
+
|
|
869
|
+
const folders = await connection.getBoxes();
|
|
870
|
+
const flatFolders = Object.keys(folders);
|
|
871
|
+
for (const candidate of sentFolderCandidates) {
|
|
872
|
+
if (flatFolders.some(f => f.toLowerCase() === candidate.toLowerCase())) {
|
|
873
|
+
sentFolder = candidate;
|
|
874
|
+
break;
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
await connection.append(rawMessage, {
|
|
879
|
+
mailbox: sentFolder,
|
|
880
|
+
flags: ['\\Seen']
|
|
881
|
+
});
|
|
882
|
+
sentFolderResult = `saved to ${sentFolder}`;
|
|
883
|
+
} finally {
|
|
884
|
+
connection.end();
|
|
885
|
+
}
|
|
886
|
+
} catch (appendErr) {
|
|
887
|
+
sentFolderResult = `failed: ${appendErr.message}`;
|
|
888
|
+
}
|
|
889
|
+
|
|
786
890
|
return {
|
|
787
891
|
content: [{
|
|
788
892
|
type: 'text',
|
|
789
893
|
text: JSON.stringify({
|
|
790
894
|
success: true,
|
|
791
895
|
messageId: info.messageId,
|
|
792
|
-
response: info.response
|
|
896
|
+
response: info.response,
|
|
897
|
+
sentFolder: sentFolderResult
|
|
793
898
|
}, null, 2)
|
|
794
899
|
}]
|
|
795
900
|
};
|
|
796
901
|
}
|
|
797
902
|
|
|
903
|
+
case 'delete_email': {
|
|
904
|
+
const folder = args.folder || 'INBOX';
|
|
905
|
+
const connection = await connectIMAP();
|
|
906
|
+
|
|
907
|
+
try {
|
|
908
|
+
await connection.openBox(folder);
|
|
909
|
+
await connection.addFlags(args.uid, ['\\Deleted']);
|
|
910
|
+
await connection.closeBox(true); // Expunge
|
|
911
|
+
|
|
912
|
+
return { content: [{ type: 'text', text: 'Email deleted successfully' }] };
|
|
913
|
+
} finally {
|
|
914
|
+
connection.end();
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
|
|
798
918
|
case 'move_email': {
|
|
799
919
|
const fromFolder = args.from_folder || 'INBOX';
|
|
800
|
-
const toFolder = args.to_folder
|
|
920
|
+
const toFolder = args.to_folder;
|
|
801
921
|
const connection = await connectIMAP();
|
|
802
922
|
|
|
803
923
|
try {
|
|
804
924
|
await connection.openBox(fromFolder);
|
|
805
925
|
|
|
806
|
-
// imap
|
|
807
|
-
//
|
|
926
|
+
// node-imap exposes move() on the underlying imap object.
|
|
927
|
+
// We wrap it in a Promise so it fits the async/await flow.
|
|
808
928
|
await new Promise((resolve, reject) => {
|
|
809
|
-
connection.imap.
|
|
929
|
+
connection.imap.move(args.uid, toFolder, (err) => {
|
|
810
930
|
if (err) reject(err);
|
|
811
931
|
else resolve();
|
|
812
932
|
});
|
|
813
933
|
});
|
|
814
934
|
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
935
|
+
return {
|
|
936
|
+
content: [{
|
|
937
|
+
type: 'text',
|
|
938
|
+
text: JSON.stringify({
|
|
939
|
+
success: true,
|
|
940
|
+
uid: args.uid,
|
|
941
|
+
from: fromFolder,
|
|
942
|
+
to: toFolder
|
|
943
|
+
}, null, 2)
|
|
944
|
+
}]
|
|
945
|
+
};
|
|
946
|
+
} finally {
|
|
947
|
+
connection.end();
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
case 'flag_email': {
|
|
952
|
+
const folder = args.folder || 'INBOX';
|
|
953
|
+
const connection = await connectIMAP();
|
|
954
|
+
|
|
955
|
+
try {
|
|
956
|
+
await connection.openBox(folder);
|
|
957
|
+
|
|
958
|
+
if (args.flagged) {
|
|
959
|
+
await connection.addFlags(args.uid, ['\\Flagged']);
|
|
960
|
+
} else {
|
|
961
|
+
await connection.delFlags(args.uid, ['\\Flagged']);
|
|
962
|
+
}
|
|
818
963
|
|
|
819
964
|
return {
|
|
820
965
|
content: [{
|
|
821
966
|
type: 'text',
|
|
822
967
|
text: JSON.stringify({
|
|
823
968
|
success: true,
|
|
824
|
-
|
|
969
|
+
uid: args.uid,
|
|
970
|
+
folder,
|
|
971
|
+
flagged: args.flagged
|
|
825
972
|
}, null, 2)
|
|
826
973
|
}]
|
|
827
974
|
};
|
|
@@ -830,16 +977,30 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
830
977
|
}
|
|
831
978
|
}
|
|
832
979
|
|
|
833
|
-
case '
|
|
980
|
+
case 'mark_seen': {
|
|
834
981
|
const folder = args.folder || 'INBOX';
|
|
835
982
|
const connection = await connectIMAP();
|
|
836
983
|
|
|
837
984
|
try {
|
|
838
985
|
await connection.openBox(folder);
|
|
839
|
-
await connection.addFlags(args.uid, ['\\Deleted']);
|
|
840
|
-
await connection.closeBox(true); // Expunge
|
|
841
986
|
|
|
842
|
-
|
|
987
|
+
if (args.seen) {
|
|
988
|
+
await connection.addFlags(args.uid, ['\\Seen']);
|
|
989
|
+
} else {
|
|
990
|
+
await connection.delFlags(args.uid, ['\\Seen']);
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
return {
|
|
994
|
+
content: [{
|
|
995
|
+
type: 'text',
|
|
996
|
+
text: JSON.stringify({
|
|
997
|
+
success: true,
|
|
998
|
+
uid: args.uid,
|
|
999
|
+
folder,
|
|
1000
|
+
seen: args.seen
|
|
1001
|
+
}, null, 2)
|
|
1002
|
+
}]
|
|
1003
|
+
};
|
|
843
1004
|
} finally {
|
|
844
1005
|
connection.end();
|
|
845
1006
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lengelhard/imap-email-mcp",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
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",
|