@lengelhard/imap-email-mcp 1.7.3 → 1.7.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/CHANGELOG.md +5 -0
- package/README.md +1 -1
- package/index.js +1 -1
- package/lib/local-index.js +23 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.7.4
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- `search_index` results carry `index.warning` when the index may be behind: the last full sync finished more than 15 minutes ago (for example "index last synced 3 hours ago, results may be missing recent mail"), the index has never completed a full sync, or a searched folder recorded an error. The existing `index` fields are unchanged. This makes a stalled background sync visible in every search instead of silent, following the overnight freeze of 18 Sep 2026.
|
|
7
|
+
|
|
3
8
|
## 1.7.3
|
|
4
9
|
|
|
5
10
|
### Fixed
|
package/README.md
CHANGED
|
@@ -136,7 +136,7 @@ Some IMAP servers return incomplete results for body searches without reporting
|
|
|
136
136
|
| `list_emails` | List emails newest first, with unread, flagged and date filters and paging |
|
|
137
137
|
| `get_email` | Get an email by UID. Text by default; HTML (without inline images) on request |
|
|
138
138
|
| `search_emails` | Live IMAP search across one or more folders: subject, from, to, cc, body, text, dates, flags |
|
|
139
|
-
| `search_index` | Full-text search of the local index (when `INDEX_ENABLED=true`) |
|
|
139
|
+
| `search_index` | Full-text search of the local index (when `INDEX_ENABLED=true`); `index.warning` flags a stale index or folder errors |
|
|
140
140
|
| `index_status` | Per-folder index coverage, last sync and errors |
|
|
141
141
|
| `sync_index` | Run an index sync now |
|
|
142
142
|
| `list_drafts` | List all draft emails |
|
package/index.js
CHANGED
|
@@ -378,7 +378,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
378
378
|
...(localIndex ? [
|
|
379
379
|
{
|
|
380
380
|
name: 'search_index',
|
|
381
|
-
description: 'Full-text search of the local read-only mail index (subject, sender, recipients, attachment names and body text). Accent-insensitive: "Loic" matches "Loïc". Use this for content searches. Results carry folder and uid for get_email; a uid can be stale if the message was moved since the last sync, in which case get_email reports it as not found. Check index.complete in the response before treating zero results as proof that nothing exists.',
|
|
381
|
+
description: 'Full-text search of the local read-only mail index (subject, sender, recipients, attachment names and body text). Accent-insensitive: "Loic" matches "Loïc". Use this for content searches. Results carry folder and uid for get_email; a uid can be stale if the message was moved since the last sync, in which case get_email reports it as not found. Check index.complete in the response before treating zero results as proof that nothing exists. index.warning is present when the last full sync is older than 15 minutes or a searched folder has an error; pass it on to the user.',
|
|
382
382
|
inputSchema: {
|
|
383
383
|
type: 'object',
|
|
384
384
|
properties: {
|
package/lib/local-index.js
CHANGED
|
@@ -28,6 +28,16 @@ const LOCK_HEARTBEAT_MS = 30 * 1000;
|
|
|
28
28
|
const REQUEST_RETRY_MS = 15 * 1000;
|
|
29
29
|
const STALL_MS = 5 * 60 * 1000;
|
|
30
30
|
const REQUEST_MAX_ATTEMPTS = 6;
|
|
31
|
+
const STALE_AFTER_MS = 15 * 60 * 1000;
|
|
32
|
+
|
|
33
|
+
function describeAge(ms) {
|
|
34
|
+
const minutes = Math.round(ms / 60000);
|
|
35
|
+
if (minutes < 60) return `${minutes} minute${minutes === 1 ? '' : 's'}`;
|
|
36
|
+
const hours = Math.round(ms / 3600000);
|
|
37
|
+
if (hours < 48) return `${hours} hour${hours === 1 ? '' : 's'}`;
|
|
38
|
+
const days = Math.round(ms / 86400000);
|
|
39
|
+
return `${days} day${days === 1 ? '' : 's'}`;
|
|
40
|
+
}
|
|
31
41
|
|
|
32
42
|
class LockLostError extends Error {
|
|
33
43
|
constructor() {
|
|
@@ -637,6 +647,17 @@ export class LocalIndex {
|
|
|
637
647
|
|
|
638
648
|
const st = this.status();
|
|
639
649
|
const scope = folderList.length ? st.folders.filter((f) => folderList.includes(f.folder)) : st.folders;
|
|
650
|
+
const warnings = [];
|
|
651
|
+
const lastFullMs = Number(this._meta('last_full_sync') || 0);
|
|
652
|
+
if (!lastFullMs) {
|
|
653
|
+
warnings.push('index has never completed a full sync, results may be missing mail');
|
|
654
|
+
} else if (Date.now() - lastFullMs > STALE_AFTER_MS) {
|
|
655
|
+
warnings.push(`index last synced ${describeAge(Date.now() - lastFullMs)} ago, results may be missing recent mail`);
|
|
656
|
+
}
|
|
657
|
+
const errored = scope.filter((f) => f.last_error);
|
|
658
|
+
if (errored.length) {
|
|
659
|
+
warnings.push(`folder error${errored.length === 1 ? '' : 's'}: ${errored.map((f) => `${f.folder} (${f.last_error})`).join(', ')}; results from ${errored.length === 1 ? 'that folder' : 'those folders'} may be incomplete`);
|
|
660
|
+
}
|
|
640
661
|
return {
|
|
641
662
|
total_matches: total,
|
|
642
663
|
returned: rows.length,
|
|
@@ -645,7 +666,8 @@ export class LocalIndex {
|
|
|
645
666
|
complete: scope.length > 0 && scope.every((f) => f.complete && !f.last_error),
|
|
646
667
|
sync_running: st.sync_running,
|
|
647
668
|
oldest_folder_sync: scope.reduce((min, f) => (f.last_sync && (!min || f.last_sync < min) ? f.last_sync : min), null),
|
|
648
|
-
incomplete_folders: scope.filter((f) => !f.complete || f.last_error).map((f) => f.folder)
|
|
669
|
+
incomplete_folders: scope.filter((f) => !f.complete || f.last_error).map((f) => f.folder),
|
|
670
|
+
...(warnings.length && { warning: warnings.join('; ') })
|
|
649
671
|
},
|
|
650
672
|
results: rows.map((r) => ({
|
|
651
673
|
folder: r.folder,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lengelhard/imap-email-mcp",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.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",
|