@bli-cockpit/mcp 0.1.83 → 0.1.84
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/README.md +2 -1
- package/dist/mail-tools.d.ts +3 -1
- package/dist/mail-tools.js +51 -11
- package/dist/tool-catalog.d.ts +1 -1
- package/dist/tool-catalog.js +2 -0
- package/dist/verb-census.js +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -109,7 +109,7 @@ about what an agent can reach.
|
|
|
109
109
|
|
|
110
110
|
<!-- BEGIN GENERATED verb census — `npm run mcp:readme` -->
|
|
111
111
|
|
|
112
|
-
**
|
|
112
|
+
**124 of 129 Tower verbs have an MCP twin.**
|
|
113
113
|
Each tool goes through the SAME door its CLI verb calls, with the same
|
|
114
114
|
collector device token — never a second route and never a service-role
|
|
115
115
|
reader. `src/verb-census.test.ts` fails when a verb is in none of the
|
|
@@ -172,6 +172,7 @@ three tables below.
|
|
|
172
172
|
| `cockpit mail accounts` | `mail_accounts` | `GET /api/mail/accounts` |
|
|
173
173
|
| `cockpit mail attachment` | `mail_attachment` | `GET /api/mail/attachments/[id]?meta=1` |
|
|
174
174
|
| `cockpit mail detach` | `mail_detach` | `DELETE /api/mail/accounts` |
|
|
175
|
+
| `cockpit mail draft` | `mail_draft` | `POST /api/mail/drafts` |
|
|
175
176
|
| `cockpit mail inbox` | `mail_inbox` | `GET /api/mail/inbox` |
|
|
176
177
|
| `cockpit mail read` | `mail_read` | `GET /api/mail/threads/[id]` |
|
|
177
178
|
| `cockpit mail search` | `mail_search` | `GET /api/mail/search` |
|
package/dist/mail-tools.d.ts
CHANGED
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
* The point, in one sentence: an agent working on somebody's behalf should be
|
|
7
7
|
* able to READ the mail that explains a ticket — the invoice, the vendor's
|
|
8
8
|
* reply, the bank notice — and send a reply, without a browser and without a
|
|
9
|
-
* second copy of anybody's credentials.
|
|
9
|
+
* second copy of anybody's credentials. Or, when the person should be the one
|
|
10
|
+
* to press send, save the reply as a DRAFT in their own mailbox (`mail_draft`,
|
|
11
|
+
* BLI-12530): it reaches nobody, so unlike `mail_send` it needs no `confirm`.
|
|
10
12
|
*
|
|
11
13
|
* TWO DELIBERATE ASYMMETRIES with the CLI, both recorded in `verb-census.ts`:
|
|
12
14
|
*
|
package/dist/mail-tools.js
CHANGED
|
@@ -6,7 +6,9 @@
|
|
|
6
6
|
* The point, in one sentence: an agent working on somebody's behalf should be
|
|
7
7
|
* able to READ the mail that explains a ticket — the invoice, the vendor's
|
|
8
8
|
* reply, the bank notice — and send a reply, without a browser and without a
|
|
9
|
-
* second copy of anybody's credentials.
|
|
9
|
+
* second copy of anybody's credentials. Or, when the person should be the one
|
|
10
|
+
* to press send, save the reply as a DRAFT in their own mailbox (`mail_draft`,
|
|
11
|
+
* BLI-12530): it reaches nobody, so unlike `mail_send` it needs no `confirm`.
|
|
10
12
|
*
|
|
11
13
|
* TWO DELIBERATE ASYMMETRIES with the CLI, both recorded in `verb-census.ts`:
|
|
12
14
|
*
|
|
@@ -32,6 +34,23 @@ function messageLine(message) {
|
|
|
32
34
|
return `${message.is_unread ? "*" : " "} ${message.internal_date.slice(0, 16)} ${from} ${message.subject ?? "(no subject)"} [thread ${message.thread_id ?? "?"}]`;
|
|
33
35
|
}
|
|
34
36
|
const ADDRESS = EMAIL_INPUT.describe("An email address.");
|
|
37
|
+
/**
|
|
38
|
+
* The JSON `mail_send` and `mail_draft` post. One shape, because the two doors
|
|
39
|
+
* take the same fields: a message that can be sent can be saved unsent, and a
|
|
40
|
+
* field added to one cannot be forgotten by the other.
|
|
41
|
+
*/
|
|
42
|
+
function composedBody(args) {
|
|
43
|
+
return {
|
|
44
|
+
account_id: args.account_id,
|
|
45
|
+
to: args.to.map((address) => ({ address })),
|
|
46
|
+
...(Array.isArray(args.cc) && args.cc.length > 0
|
|
47
|
+
? { cc: args.cc.map((address) => ({ address })) }
|
|
48
|
+
: {}),
|
|
49
|
+
subject: args.subject ?? "",
|
|
50
|
+
text: args.text,
|
|
51
|
+
...(args.in_reply_to_message_id ? { in_reply_to_message_id: args.in_reply_to_message_id } : {}),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
35
54
|
export function registerMailTools(server, deps) {
|
|
36
55
|
const register = registrarFor(server);
|
|
37
56
|
register("mail_accounts", {
|
|
@@ -186,21 +205,42 @@ export function registerMailTools(server, deps) {
|
|
|
186
205
|
const refusal = unconfirmed(args, "Sending mail reaches real people and cannot be recalled.");
|
|
187
206
|
if (refusal)
|
|
188
207
|
return refusal;
|
|
189
|
-
const response = await callAgentDoor(session, deps.fetchImpl, "POST", "/api/mail/send",
|
|
190
|
-
account_id: args.account_id,
|
|
191
|
-
to: args.to.map((address) => ({ address })),
|
|
192
|
-
...(Array.isArray(args.cc) && args.cc.length > 0
|
|
193
|
-
? { cc: args.cc.map((address) => ({ address })) }
|
|
194
|
-
: {}),
|
|
195
|
-
subject: args.subject ?? "",
|
|
196
|
-
text: args.text,
|
|
197
|
-
...(args.in_reply_to_message_id ? { in_reply_to_message_id: args.in_reply_to_message_id } : {}),
|
|
198
|
-
});
|
|
208
|
+
const response = await callAgentDoor(session, deps.fetchImpl, "POST", "/api/mail/send", composedBody(args));
|
|
199
209
|
if (!response.ok)
|
|
200
210
|
return errorResult(doorFailureText("mail_send", response));
|
|
201
211
|
const sent = response.body.sent;
|
|
202
212
|
return textResult(`Sent from ${sent?.address ?? "that mailbox"} to ${args.to.join(", ")}${sent?.threaded ? " (as a reply)" : ""}.`, { sent });
|
|
203
213
|
}));
|
|
214
|
+
register("mail_draft", {
|
|
215
|
+
title: "Save a draft in one mailbox",
|
|
216
|
+
description: "Saves a message as a DRAFT in one of YOUR mailboxes, for a person to read, edit and send themselves. Use it "
|
|
217
|
+
+ "when you are preparing mail on somebody's behalf and they should be the one to press send. NOTHING IS SENT and "
|
|
218
|
+
+ "nobody is notified, so unlike mail_send there is no confirm flag. `account_id` is REQUIRED and is the mailbox the "
|
|
219
|
+
+ "draft is saved in (mail_accounts) — with several attached, that is never something to guess. "
|
|
220
|
+
+ "`in_reply_to_message_id` (a message id from mail_read) threads the draft into that conversation the way mail_send "
|
|
221
|
+
+ "threads a reply; Gmail files it there only when the subject matches the original's, and the answer says whether it "
|
|
222
|
+
+ "did (`threaded`). Only the mailbox's OWNER can draft into it, and only Gmail mailboxes can take one: an IMAP "
|
|
223
|
+
+ "mailbox is refused as imap_drafts_unsupported. Answers the provider draft id, the thread id and the account id.",
|
|
224
|
+
inputSchema: {
|
|
225
|
+
account_id: z.string().uuid().describe("The mailbox the draft is saved in (mail_accounts). Never guessed."),
|
|
226
|
+
to: z.array(ADDRESS).min(1).max(50).describe("Recipient email addresses, so the draft is ready to send as it is."),
|
|
227
|
+
cc: z.array(ADDRESS).max(50).optional().describe("Copied email addresses."),
|
|
228
|
+
subject: z.string().max(998).describe("The subject line. For a reply, keep the original's subject or Gmail will not thread it."),
|
|
229
|
+
text: z.string().min(1).max(500_000).describe("The plain-text body."),
|
|
230
|
+
in_reply_to_message_id: z.string().uuid().optional().describe("A message id from mail_read: save this as a reply in that thread."),
|
|
231
|
+
},
|
|
232
|
+
}, async (args) => withSession(deps, async (session) => {
|
|
233
|
+
const response = await callAgentDoor(session, deps.fetchImpl, "POST", "/api/mail/drafts", composedBody(args));
|
|
234
|
+
if (!response.ok)
|
|
235
|
+
return errorResult(doorFailureText("mail_draft", response));
|
|
236
|
+
const draft = response.body.draft;
|
|
237
|
+
// Both halves carry the ids (BLI-10432): a client that hands the model only
|
|
238
|
+
// `content`, or only `structuredContent`, must still learn where the draft is.
|
|
239
|
+
return textResult(`Saved a draft in ${draft?.address ?? "that mailbox"} for ${args.to.join(", ")}`
|
|
240
|
+
+ `${draft?.threaded ? " (in the reply's thread)" : args.in_reply_to_message_id ? " (Gmail did NOT file it in that thread: its subject has to match the original's)" : ""}. `
|
|
241
|
+
+ "Nothing was sent: it waits in Drafts for a person to read, edit and send. "
|
|
242
|
+
+ `draft_id=${draft?.provider_draft_id ?? "(none returned)"} thread_id=${draft?.provider_thread_id ?? "(none)"} account_id=${draft?.account_id ?? String(args.account_id)}`, { draft });
|
|
243
|
+
}));
|
|
204
244
|
register("mail_attachment", {
|
|
205
245
|
title: "Describe an attachment",
|
|
206
246
|
description: "The filename, mime type and size of one attachment. Attachments are POINTERS in Tower — nothing is stored — and "
|
package/dist/tool-catalog.d.ts
CHANGED
|
@@ -21,7 +21,7 @@ export declare const DRY_RUN_TOOLS: readonly ["ops_recompile", "memory_requeue"]
|
|
|
21
21
|
* candidates, share calendars. Never called by the smoke; covered by the unit
|
|
22
22
|
* suites (`write-tools.test.ts` and each family's own).
|
|
23
23
|
*/
|
|
24
|
-
export declare const WRITE_TOOLS: readonly ["emit_event", "docs_create", "docs_update", "docs_delete", "docs_link", "msg_create_channel", "msg_dm", "msg_send", "work_create_issue", "work_update_issue", "work_move_issue", "work_comment_issue", "careers_rescreen", "careers_send_takehome", "careers_invite", "careers_grade", "careers_decide", "careers_followup", "careers_interview", "careers_takehome_set", "careers_settings_set", "rename_resolve", "github_merge", "github_retitle", "github_rerun", "mail_send", "mail_sync", "mail_detach", "cal_create", "cal_share", "cal_sync", "cal_detach", "cal_delete", "jarvis_dispatch", "notes_mkdir", "notes_rmdir", "notes_rename", "notes_upload", "notes_paste", "notes_share", "notes_unshare", "notes_move", "notes_sync", "notes_detach", "memory_experience", "scout_start", "scout_dismiss", "scout_undo", "brief_rewrite", "brief_correct", "brief_done", "brief_undone", "settings_set", "settings_delete", "model_set", "team_invite", "team_role", "team_device_revoke"];
|
|
24
|
+
export declare const WRITE_TOOLS: readonly ["emit_event", "docs_create", "docs_update", "docs_delete", "docs_link", "msg_create_channel", "msg_dm", "msg_send", "work_create_issue", "work_update_issue", "work_move_issue", "work_comment_issue", "careers_rescreen", "careers_send_takehome", "careers_invite", "careers_grade", "careers_decide", "careers_followup", "careers_interview", "careers_takehome_set", "careers_settings_set", "rename_resolve", "github_merge", "github_retitle", "github_rerun", "mail_send", "mail_draft", "mail_sync", "mail_detach", "cal_create", "cal_share", "cal_sync", "cal_detach", "cal_delete", "jarvis_dispatch", "notes_mkdir", "notes_rmdir", "notes_rename", "notes_upload", "notes_paste", "notes_share", "notes_unshare", "notes_move", "notes_sync", "notes_detach", "memory_experience", "scout_start", "scout_dismiss", "scout_undo", "brief_rewrite", "brief_correct", "brief_done", "brief_undone", "settings_set", "settings_delete", "model_set", "team_invite", "team_role", "team_device_revoke"];
|
|
25
25
|
/** Reads that spend a model turn. The smoke's `--no-model` leaves these out. */
|
|
26
26
|
export declare const MODEL_SPENDING_TOOLS: readonly ["jarvis_ask", "jarvis_check"];
|
|
27
27
|
/**
|
package/dist/tool-catalog.js
CHANGED
package/dist/verb-census.js
CHANGED
|
@@ -268,6 +268,8 @@ export const MCP_TWINS = {
|
|
|
268
268
|
"mail read": { tool: "mail_read", door: "GET /api/mail/threads/[id]" },
|
|
269
269
|
"mail search": { tool: "mail_search", door: "GET /api/mail/search" },
|
|
270
270
|
"mail send": { tool: "mail_send", door: "POST /api/mail/send" },
|
|
271
|
+
// BLI-12530: the same message, saved unsent in the owner's own mailbox.
|
|
272
|
+
"mail draft": { tool: "mail_draft", door: "POST /api/mail/drafts" },
|
|
271
273
|
"mail attachment": { tool: "mail_attachment", door: "GET /api/mail/attachments/[id]?meta=1" },
|
|
272
274
|
"mail sync": { tool: "mail_sync", door: "POST /api/mail/accounts/[id]/sync" },
|
|
273
275
|
"mail sync-status": { tool: "mail_sync_status", door: "GET /api/mail/sync-status" },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bli-cockpit/mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.84",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "bli-tower: an MCP server over BLI Cockpit's agent doors: JARVIS (jarvis_*), documents (docs_*), channels (msg_*), issues (work_*), the daily page (brief_*), meeting notes (notes_*), the ops board (ops_status/slack_*), settings/team/model, Scout and the workbook, plus the legacy event-stream tools (emit_event, get_ticket_timeline, get_active_tickets).",
|
|
6
6
|
"type": "module",
|