@agenticmail/mcp 0.7.5 → 0.7.8

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.
Files changed (3) hide show
  1. package/README.md +9 -0
  2. package/dist/index.js +108 -17
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -8,6 +8,15 @@ The MCP (Model Context Protocol) server for [AgenticMail](https://github.com/age
8
8
 
9
9
  When connected, your AI agent can send emails and texts, check inboxes, reply to messages, receive verification codes, manage contacts, schedule emails, assign tasks to other agents, and more — all through natural language. The server provides 62 tools that cover every email, SMS, and agent management operation.
10
10
 
11
+ ## ✨ What's new in 0.7.7
12
+
13
+ - **`wake` parameter on every send tool** — `send_email`, `reply_email`, `forward_email`, `template_send`, `manage_drafts(send)` all accept `wake: ["alice", "bob"]` (or comma-separated string). The dispatcher gives a Claude turn only to listed agents; the rest still receive the mail but stay asleep. Single biggest token saver on multi-agent threads.
14
+ - **`check_activity` tool** (in the `essential` set) — see which agents the dispatcher has woken right now, what they're working on, how long they've been running, plus a preview of recently-finished work. Answers "did the agent I just emailed actually start working?" without waiting for a reply.
15
+ - **Thread-close markers** — put `[FINAL]`, `[DONE]`, `[CLOSED]`, or `[WRAP]` in a subject to tell the dispatcher this thread is sealed; no more wakes on any reply to it. The wake prompt teaches agents to add these markers when they sign off the work.
16
+ - **LLM-tolerant input coercion** — `batch_mark_read({ uids: "[1,2,3,4]" })`, `wait_for_email({ timeout: "120" })`, `manage_drafts({ where: '{"id":"abc"}' })`, `manage_pending({ allowSensitive: "true" })` all now just work. Strings get parsed before zod validates. No more "expected array, received string" retries.
17
+ - **Server `instructions` field** sent on `initialize` — every connecting MCP client (Claude Code, ChatGPT, Cursor, Grok…) gets the coordination protocol in context before they touch any tool. Provider-agnostic.
18
+ - **`wait_for_email` filters** — block on a specific reply, not just "any new event". Supports `from`, `subject`, `inReplyTo`, `participants`, `includeTasks`.
19
+
11
20
  ## Install
12
21
 
13
22
  ```bash
package/dist/index.js CHANGED
@@ -22401,6 +22401,11 @@ var TOOL_SETS = {
22401
22401
  // "did the agent I just emailed actually start working?" without
22402
22402
  // having to wait for a reply or send an acknowledgment.
22403
22403
  "check_activity",
22404
+ // tail_worker complements check_activity: when a worker has been
22405
+ // running a long time or shows up as stale, tail_worker gives you
22406
+ // the running log of what it actually did — every tool call, every
22407
+ // result. Paired with check_activity so they ship in the same tier.
22408
+ "tail_worker",
22404
22409
  "check_tasks"
22405
22410
  ],
22406
22411
  /** Less-common mail operations. */
@@ -22615,7 +22620,7 @@ async function apiRequest(method, path, body, useMasterKey = false, timeoutMs =
22615
22620
  var toolDefinitions = [
22616
22621
  {
22617
22622
  name: "send_email",
22618
- description: "Send an email from the agent's mailbox. Supports multiple recipients on To and CC (comma-separated). This is the PRIMARY primitive for multi-agent coordination: kick off a thread with all participants on CC, and every local recipient is woken automatically. Each woken agent reads the thread, decides whose turn it is, and either reply-all's to contribute or stays silent. External emails are scanned for sensitive content; HIGH severity detections are BLOCKED for owner approval. You CANNOT bypass the outbound guard.",
22623
+ description: "Send an email from the agent's mailbox. Supports multiple recipients on To and CC (comma-separated). This is the PRIMARY primitive for multi-agent coordination: kick off a thread with all participants on CC, and every local recipient is woken automatically \u2014 UNLESS you set `wake` to limit the wake-up to specific agents. Use `wake` aggressively on large threads: 15 agents on CC \xD7 every reply burns 15 Claude turns per round if every recipient wakes. Naming the single next actor cuts that to one. External emails are scanned for sensitive content; HIGH severity detections are BLOCKED for owner approval. You CANNOT bypass the outbound guard.",
22619
22624
  inputSchema: {
22620
22625
  type: "object",
22621
22626
  properties: {
@@ -22623,7 +22628,12 @@ var toolDefinitions = [
22623
22628
  subject: { type: "string", description: "Email subject line" },
22624
22629
  text: { type: "string", description: "Plain text body" },
22625
22630
  html: { type: "string", description: "HTML body (optional)" },
22626
- cc: { type: "string", description: 'CC recipients \u2014 the team. Comma-separated, e.g. "vesper@localhost, orion@localhost". Every local @localhost recipient is auto-woken when this email lands.' },
22631
+ cc: { type: "string", description: 'CC recipients \u2014 the team. Comma-separated, e.g. "vesper@localhost, orion@localhost". Every local @localhost recipient is auto-woken when this email lands UNLESS you also pass `wake` to restrict.' },
22632
+ wake: {
22633
+ type: "array",
22634
+ items: { type: "string" },
22635
+ description: 'Optional. Names (or @localhost addresses) of the agents the dispatcher should give a Claude turn to when this mail lands. CC\'d agents NOT in this list still receive the email but get no Claude turn \u2014 they will see it the next time someone explicitly wakes them or they check their inbox. Use this to scope cost on large threads: send to 15 agents but wake only the 1 or 2 who need to act next. Pass an empty array `[]` for "deliver silently \u2014 wake nobody". Omit entirely to keep the default "wake everyone CC\'d" behaviour.'
22636
+ },
22627
22637
  inReplyTo: { type: "string", description: "Message-ID to reply to (optional)" },
22628
22638
  references: {
22629
22639
  type: "array",
@@ -22711,27 +22721,37 @@ var toolDefinitions = [
22711
22721
  },
22712
22722
  {
22713
22723
  name: "reply_email",
22714
- description: "Reply to an email. Fetches the original message, auto-fills To, Subject (Re:), In-Reply-To, and References, then sends with quoted body. **For multi-agent thread coordination, pass `replyAll: true`** so every CC'd participant sees your contribution and stays in context \u2014 that is how the thread-as-workspace pattern works. Outbound guard applies \u2014 HIGH severity content is held for review.",
22724
+ description: "Reply to an email. Fetches the original message, auto-fills To, Subject (Re:), In-Reply-To, and References, then sends with quoted body. **For multi-agent thread coordination, pass `replyAll: true`** so every CC'd participant sees your contribution and stays in context \u2014 that is how the thread-as-workspace pattern works. **Pass `wake` to name only the next actor(s)** so the dispatcher gives a Claude turn only to them; everyone else still receives the mail but stays asleep. Outbound guard applies \u2014 HIGH severity content is held for review.",
22715
22725
  inputSchema: {
22716
22726
  type: "object",
22717
22727
  properties: {
22718
22728
  uid: { type: "number", description: "UID of the email to reply to" },
22719
22729
  text: { type: "string", description: "Your reply text" },
22720
22730
  html: { type: "string", description: "HTML reply (optional)" },
22721
- replyAll: { type: "boolean", description: "Reply to all recipients (default: false)" }
22731
+ replyAll: { type: "boolean", description: "Reply to all recipients (default: false)" },
22732
+ wake: {
22733
+ type: "array",
22734
+ items: { type: "string" },
22735
+ description: "Optional. Names of the agents who should get a Claude turn from the dispatcher when this reply lands. CC'd agents NOT in this list still receive the email but stay asleep \u2014 saves significant tokens on large threads. Pass `[]` to deliver silently. Omit to wake everyone CC'd."
22736
+ }
22722
22737
  },
22723
22738
  required: ["uid", "text"]
22724
22739
  }
22725
22740
  },
22726
22741
  {
22727
22742
  name: "forward_email",
22728
- description: "Forward an email to another recipient. Outbound guard applies \u2014 HIGH severity content is held for review.",
22743
+ description: "Forward an email to another recipient. Outbound guard applies \u2014 HIGH severity content is held for review. Pass `wake` to limit which local recipients get a Claude turn from the dispatcher when this forward lands.",
22729
22744
  inputSchema: {
22730
22745
  type: "object",
22731
22746
  properties: {
22732
22747
  uid: { type: "number", description: "UID of the email to forward" },
22733
22748
  to: { type: "string", description: "Recipient to forward to" },
22734
- text: { type: "string", description: "Additional message (optional)" }
22749
+ text: { type: "string", description: "Additional message (optional)" },
22750
+ wake: {
22751
+ type: "array",
22752
+ items: { type: "string" },
22753
+ description: "Optional. Names of the agents who should get a Claude turn when the forward lands. Pass `[]` to deliver silently. Omit to wake everyone CC'd."
22754
+ }
22735
22755
  },
22736
22756
  required: ["uid", "to"]
22737
22757
  }
@@ -22832,7 +22852,7 @@ var toolDefinitions = [
22832
22852
  },
22833
22853
  {
22834
22854
  name: "manage_drafts",
22835
- description: "List, create, update, send, or delete drafts",
22855
+ description: "List, create, update, send, or delete drafts. On send, you can pass `wake` to limit which local recipients get a Claude turn \u2014 same semantics as send_email.",
22836
22856
  inputSchema: {
22837
22857
  type: "object",
22838
22858
  properties: {
@@ -22840,7 +22860,12 @@ var toolDefinitions = [
22840
22860
  id: { type: "string", description: "Draft ID (for update/send/delete)" },
22841
22861
  to: { type: "string", description: "Recipient (for create/update)" },
22842
22862
  subject: { type: "string", description: "Subject (for create/update)" },
22843
- text: { type: "string", description: "Body text (for create/update)" }
22863
+ text: { type: "string", description: "Body text (for create/update)" },
22864
+ wake: {
22865
+ type: "array",
22866
+ items: { type: "string" },
22867
+ description: "Optional, for action=send. Names of the agents who should get a Claude turn when the drafted mail lands. Pass `[]` to deliver silently. Omit to wake everyone CC'd."
22868
+ }
22844
22869
  },
22845
22870
  required: ["action"]
22846
22871
  }
@@ -23197,7 +23222,12 @@ var toolDefinitions = [
23197
23222
  to: { type: "string", description: "Recipient email" },
23198
23223
  variables: { type: "object", description: 'Variables to substitute: { name: "Alice" }' },
23199
23224
  cc: { type: "string", description: "CC recipients" },
23200
- bcc: { type: "string", description: "BCC recipients" }
23225
+ bcc: { type: "string", description: "BCC recipients" },
23226
+ wake: {
23227
+ type: "array",
23228
+ items: { type: "string" },
23229
+ description: "Optional. Names of the agents who should get a Claude turn when this template-rendered mail lands. Pass `[]` to deliver silently. Omit to wake everyone CC'd."
23230
+ }
23201
23231
  },
23202
23232
  required: ["id", "to"]
23203
23233
  }
@@ -23233,9 +23263,21 @@ var toolDefinitions = [
23233
23263
  required: ["action"]
23234
23264
  }
23235
23265
  },
23266
+ {
23267
+ name: "tail_worker",
23268
+ description: "Tail the log of a running (or recently-finished) dispatcher worker. Use this when check_activity shows a worker has been running a long time or is marked stale, and you want to see what it is actually doing \u2014 every tool call, tool result, and assistant chunk is logged as a one-liner. Returns the last N lines (default 80). The workerId comes from check_activity output. Requires master key.",
23269
+ inputSchema: {
23270
+ type: "object",
23271
+ properties: {
23272
+ workerId: { type: "string", description: "Worker id from check_activity output." },
23273
+ lines: { type: "number", description: "How many trailing log lines to return. Default 80, max 1000." }
23274
+ },
23275
+ required: ["workerId"]
23276
+ }
23277
+ },
23236
23278
  {
23237
23279
  name: "check_activity",
23238
- description: "Check which agents are currently being woken by the dispatcher (right now or in the last 2 minutes). Use this when you sent mail to a teammate and want to know if they have actually started working, or to audit the live multi-agent state. Returns active workers with the agent name, what triggered the wake (mail UID + subject, or task id), how long they have been running, and a preview of the most recent finished work. Requires master key \u2014 the host session has it; subagents normally do not.",
23280
+ description: "Check which agents are currently being woken by the dispatcher. Use this when you sent mail to a teammate and want to know if they have actually started working, or to audit the live multi-agent state. Returns active workers with the agent name, what triggered the wake (mail UID + subject, or task id), how long they have been running, the most recent tool they invoked, how many tool calls they have made, a `stale` flag (true if the dispatcher has not heartbeated in 90s+), and a preview of recently-finished work. Workers may run for hours \u2014 there is no auto-eviction; staleness is just a hint. Requires master key.",
23239
23281
  inputSchema: {
23240
23282
  type: "object",
23241
23283
  properties: {
@@ -23585,6 +23627,9 @@ async function dispatchToolCall(name, args2, useMaster) {
23585
23627
  ...a.encoding ? { encoding: a.encoding } : {}
23586
23628
  }));
23587
23629
  }
23630
+ if (args2.wake !== void 0) {
23631
+ sendBody.wake = args2.wake;
23632
+ }
23588
23633
  const result = await apiRequest("POST", "/mail/send", sendBody);
23589
23634
  if (result?.blocked && result?.pendingId) {
23590
23635
  scheduleFollowUp(result.pendingId, String(args2.to), String(args2.subject || "(no subject)"), makePendingCheck(result.pendingId));
@@ -23718,6 +23763,9 @@ ${quotedBody}`;
23718
23763
  inReplyTo: original.messageId,
23719
23764
  references: refs
23720
23765
  };
23766
+ if (args2.wake !== void 0) {
23767
+ replySendBody.wake = args2.wake;
23768
+ }
23721
23769
  const sendResult = await apiRequest("POST", "/mail/send", replySendBody);
23722
23770
  if (sendResult?.blocked && sendResult?.pendingId) {
23723
23771
  scheduleFollowUp(sendResult.pendingId, to, String(replySendBody.subject || "(no subject)"), makePendingCheck(sendResult.pendingId));
@@ -23766,6 +23814,9 @@ ${orig.text || ""}`;
23766
23814
  encoding: "base64"
23767
23815
  }));
23768
23816
  }
23817
+ if (args2.wake !== void 0) {
23818
+ fwdSendBody.wake = args2.wake;
23819
+ }
23769
23820
  const fwdResult = await apiRequest("POST", "/mail/send", fwdSendBody);
23770
23821
  if (fwdResult?.blocked && fwdResult?.pendingId) {
23771
23822
  scheduleFollowUp(fwdResult.pendingId, String(args2.to), fwdSubject, makePendingCheck(fwdResult.pendingId));
@@ -23870,7 +23921,9 @@ ${lines.join("\n")}`;
23870
23921
  }
23871
23922
  if (args2.action === "send") {
23872
23923
  if (!args2.id) throw new Error("id is required");
23873
- const r = await apiRequest("POST", `/drafts/${args2.id}/send`);
23924
+ const draftSendBody = {};
23925
+ if (args2.wake !== void 0) draftSendBody.wake = args2.wake;
23926
+ const r = await apiRequest("POST", `/drafts/${args2.id}/send`, draftSendBody);
23874
23927
  return `Draft sent. Message ID: ${r?.messageId ?? "unknown"}`;
23875
23928
  }
23876
23929
  if (args2.action === "delete") {
@@ -24476,12 +24529,14 @@ ${lines.join("\n\n---\n\n")}`;
24476
24529
  ${lines.join("\n")}`;
24477
24530
  }
24478
24531
  case "template_send": {
24479
- const result = await apiRequest("POST", `/templates/${args2.id}/send`, {
24532
+ const templateBody = {
24480
24533
  to: args2.to,
24481
24534
  variables: args2.variables,
24482
24535
  cc: args2.cc,
24483
24536
  bcc: args2.bcc
24484
- });
24537
+ };
24538
+ if (args2.wake !== void 0) templateBody.wake = args2.wake;
24539
+ const result = await apiRequest("POST", `/templates/${args2.id}/send`, templateBody);
24485
24540
  return `Template email sent. Message ID: ${result?.messageId ?? "unknown"}`;
24486
24541
  }
24487
24542
  case "manage_rules": {
@@ -24536,6 +24591,15 @@ ${r.agents.map(
24536
24591
  }
24537
24592
  throw new Error("Invalid action. Use: list_inactive, cleanup, or set_persistent");
24538
24593
  }
24594
+ case "tail_worker": {
24595
+ if (!args2.workerId) throw new Error("workerId is required");
24596
+ const lines = typeof args2.lines === "number" ? args2.lines : 80;
24597
+ const r = await apiRequest("GET", `/dispatcher/worker-log/${encodeURIComponent(String(args2.workerId))}?lines=${lines}`, void 0, true);
24598
+ if (!r?.tail || !Array.isArray(r.tail)) return `No log found for worker ${args2.workerId}.`;
24599
+ if (r.tail.length === 0) return `Worker ${args2.workerId} has no log entries yet.`;
24600
+ return `Worker ${args2.workerId} log (last ${r.lines} of ${r.bytes} bytes):
24601
+ ${r.tail.join("\n")}`;
24602
+ }
24539
24603
  case "check_activity": {
24540
24604
  const r = await apiRequest("GET", "/dispatcher/activity", void 0, true);
24541
24605
  const filterAgent = typeof args2.agent === "string" ? args2.agent.toLowerCase() : "";
@@ -24547,14 +24611,24 @@ ${r.agents.map(
24547
24611
  if (filterAgent) return `No dispatcher activity for "${args2.agent}" right now or in the last 2 minutes. Either the agent has not been woken on this thread yet, the dispatcher is not running, or mail to them is still in flight.`;
24548
24612
  return "No dispatcher activity right now or in the last 2 minutes. If you just sent mail and expected an agent to wake, give it a moment \u2014 the dispatcher subscribes to /system/events for sub-second wake. If nothing happens for 30s, check that the dispatcher process is running (`pm2 list`) and that the recipient is a real local agent (`list_agents`).";
24549
24613
  }
24614
+ const fmtDur = (ms) => {
24615
+ if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
24616
+ if (ms < 36e5) return `${Math.floor(ms / 6e4)}m${Math.floor(ms % 6e4 / 1e3)}s`;
24617
+ return `${Math.floor(ms / 36e5)}h${Math.floor(ms % 36e5 / 6e4)}m`;
24618
+ };
24550
24619
  const fmt = (w, prefix) => {
24551
- const dur = w.durationMs ? `${(w.durationMs / 1e3).toFixed(1)}s` : "?";
24620
+ const dur = w.durationMs ? fmtDur(w.durationMs) : "?";
24552
24621
  const trig = w.trigger?.subject ? ` \u2014 ${String(w.trigger.subject).slice(0, 60)}` : w.trigger?.taskId ? ` \u2014 task ${String(w.trigger.taskId).slice(0, 8)}` : "";
24553
24622
  const from = w.trigger?.from ? ` (from ${w.trigger.from})` : "";
24554
24623
  const preview = w.resultPreview ? `
24555
24624
  \u2192 ${String(w.resultPreview).slice(0, 140).replace(/\s+/g, " ").trim()}` : "";
24556
- const status = w.endedAtMs ? w.ok === false ? "failed" : "finished" : "running";
24557
- return ` ${prefix} ${w.agentName} [${w.kind}] ${status} ${dur}${trig}${from}${preview}`;
24625
+ let status = w.endedAtMs ? w.ok === false ? "failed" : "finished" : "running";
24626
+ if (!w.endedAtMs && w.stale) status = "running (stale heartbeat)";
24627
+ const turns = !w.endedAtMs && typeof w.turnCount === "number" ? ` \xB7 ${w.turnCount} tool calls` : "";
24628
+ const tool = !w.endedAtMs && w.lastTool ? ` \xB7 last tool: ${w.lastTool}` : "";
24629
+ const idHint = !w.endedAtMs ? `
24630
+ id: ${w.workerId} (use tail_worker for the log)` : "";
24631
+ return ` ${prefix} ${w.agentName} [${w.kind}] ${status} ${dur}${turns}${tool}${trig}${from}${preview}${idHint}`;
24558
24632
  };
24559
24633
  const lines = [];
24560
24634
  if (activeList.length > 0) {
@@ -25118,6 +25192,7 @@ var SERVER_INSTRUCTIONS = [
25118
25192
  ' to: "vesper@localhost", // primary owner of step 1',
25119
25193
  ' cc: "orion@localhost, claudecode@localhost", // teammates + yourself',
25120
25194
  ' subject: "Build a small terminal game",',
25195
+ ' wake: ["vesper"], // only Vesper gets a Claude turn',
25121
25196
  " text: [",
25122
25197
  ' "Team \u2014",',
25123
25198
  ' "",',
@@ -25130,6 +25205,14 @@ var SERVER_INSTRUCTIONS = [
25130
25205
  ' ].join("\\n"),',
25131
25206
  " })",
25132
25207
  "",
25208
+ " The `wake` parameter is the SINGLE BIGGEST TOKEN SAVER on large threads.",
25209
+ " Without it, every CC'd recipient burns one Claude turn deciding whether",
25210
+ " it is their turn. With it, only the named agents get a turn \u2014 the rest",
25211
+ " receive the mail in their inbox but stay asleep until you (or another",
25212
+ " agent) explicitly names them in a later `wake` list. Pass `wake: []`",
25213
+ ' for "deliver silently \u2014 wake nobody". Omit `wake` entirely to keep the',
25214
+ ` old "wake every CC'd agent" behaviour (backwards compatible).`,
25215
+ "",
25133
25216
  " The mail server pushes a wake-up to every local recipient simultaneously.",
25134
25217
  " Each agent reads the thread, decides if it is THEIR turn, and either",
25135
25218
  " reply-all's to contribute or stays silent. Vesper goes first because she",
@@ -25149,7 +25232,15 @@ var SERVER_INSTRUCTIONS = [
25149
25232
  " To unblock a stuck agent or change direction, just reply-all into the",
25150
25233
  " same thread.",
25151
25234
  "",
25152
- '4. Done when the last hand-off (or an explicit "complete" message) lands',
25235
+ "4. **Close the thread when work is complete.** Send a wrap-up reply",
25236
+ " with one of these markers in the subject: `[FINAL]`, `[DONE]`,",
25237
+ " `[CLOSED]`, or `[WRAP]`. The dispatcher honours those markers and",
25238
+ " stops waking workers on any further replies to that thread. Without",
25239
+ " this, the cascade can keep firing as agents critique each other's",
25240
+ " work even after the deliverables are in. Add the marker once, the",
25241
+ " thread is sealed.",
25242
+ "",
25243
+ '5. Done when the last hand-off (or an explicit "complete" message) lands',
25153
25244
  " in your inbox. Show the result to the user.",
25154
25245
  "",
25155
25246
  "Why this is right:",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agenticmail/mcp",
3
- "version": "0.7.5",
3
+ "version": "0.7.8",
4
4
  "mcpName": "io.github.agenticmail/mcp",
5
5
  "description": "MCP server for AgenticMail — give any AI client real email and SMS capabilities",
6
6
  "type": "module",