@bli-cockpit/mcp 0.1.110 → 0.1.111

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 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
- **139 of 144 Tower verbs have an MCP twin.**
112
+ **147 of 152 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
@@ -126,6 +126,8 @@ three tables below.
126
126
  | `cockpit brief status` | `brief_status` | `GET /api/ops/brief-status` |
127
127
  | `cockpit brief undone` | `brief_undone` | `POST /api/jarvis/ticks` |
128
128
  | `cockpit cal calendars` | `cal_calendars` | `GET /api/cal/calendars` |
129
+ | `cockpit cal cancel` | `cal_delete` | `DELETE /api/cal/events/[eventId]` |
130
+ | `cockpit cal change` | `cal_change` | `PATCH /api/cal/events/[eventId]` |
129
131
  | `cockpit cal create` | `cal_create` | `POST /api/cal/events` |
130
132
  | `cockpit cal delete` | `cal_delete` | `DELETE /api/cal/events/[eventId]` |
131
133
  | `cockpit cal detach` | `cal_detach` | `DELETE /api/cal/calendars` |
@@ -177,10 +179,15 @@ three tables below.
177
179
  | `cockpit logs` | `logs_digest` | `LOCAL @bli-cockpit/telemetry-core/log-digest (this machine's logs); `project` reads GET /api/logs (the stored digests)` |
178
180
  | `cockpit mail accounts` | `mail_accounts` | `GET /api/mail/accounts` |
179
181
  | `cockpit mail attachment` | `mail_attachment` | `GET /api/mail/attachments/[id]?meta=1` |
182
+ | `cockpit mail default` | `mail_default` | `PATCH /api/mail/accounts` |
180
183
  | `cockpit mail detach` | `mail_detach` | `DELETE /api/mail/accounts` |
184
+ | `cockpit mail disable` | `mail_disable` | `PATCH /api/mail/accounts` |
181
185
  | `cockpit mail draft` | `mail_draft` | `POST /api/mail/drafts` |
186
+ | `cockpit mail enable` | `mail_enable` | `PATCH /api/mail/accounts` |
182
187
  | `cockpit mail inbox` | `mail_inbox` | `GET /api/mail/inbox` |
183
188
  | `cockpit mail read` | `mail_read` | `GET /api/mail/threads/[id]` |
189
+ | `cockpit mail remove` | `mail_remove` | `DELETE /api/mail/accounts` |
190
+ | `cockpit mail rename` | `mail_rename` | `PATCH /api/mail/accounts` |
184
191
  | `cockpit mail search` | `mail_search` | `GET /api/mail/search` |
185
192
  | `cockpit mail send` | `mail_send` | `POST /api/mail/send` |
186
193
  | `cockpit mail sync` | `mail_sync` | `POST /api/mail/accounts/[id]/sync` |
@@ -208,6 +215,7 @@ three tables below.
208
215
  | `cockpit msg send` | `msg_send` | `POST /api/msg/channels/[id]/messages` |
209
216
  | `cockpit msg thread` | `msg_thread` | `GET /api/msg/channels/[id]/messages?thread_parent_id=` |
210
217
  | `cockpit notes accounts` | `notes_accounts` | `GET /api/notes/accounts` |
218
+ | `cockpit notes bot-upcoming` | `notes_bot_upcoming` | `GET /api/notetaker/upcoming` |
211
219
  | `cockpit notes detach` | `notes_detach` | `DELETE /api/notes/accounts` |
212
220
  | `cockpit notes folders` | `notes_folders` | `GET /api/notes/folders` |
213
221
  | `cockpit notes granola-status` | `notes_granola_status` | `GET /api/notes/granola-status` |
@@ -0,0 +1,2 @@
1
+ import { type RegisterTool, type ToolDeps } from "./tool-result.js";
2
+ export declare function registerAccountTools(register: RegisterTool, deps: ToolDeps): void;
@@ -0,0 +1,19 @@
1
+ import { z } from "zod";
2
+ import { callAgentDoor } from "./agent-door.js";
3
+ import { withSession, textResult, errorResult, doorFailureText } from "./tool-result.js";
4
+ export function registerAccountTools(register, deps) {
5
+ for (const action of ["rename", "enable", "disable", "default", "remove"]) {
6
+ register(`mail_${action}`, { title: `${action} a connected account`, description: "Manage the caller's shared mail/calendar account. Exact case-folded label, address or id. Removing disconnects the account and deletes Tower's stored copies; provider data is untouched.",
7
+ inputSchema: {
8
+ account: z.string().min(1).max(320).describe("The connected account: its exact label, address or id (mail_accounts lists them)."),
9
+ ...(action === "rename" ? { name: z.string().trim().min(1).max(100).describe("The new label, e.g. personal 1.") } : {}),
10
+ ...(["enable", "disable"].includes(action) ? { source: z.enum(["mail", "calendar"]).describe("Which source to switch: mail or calendar.") } : {}),
11
+ },
12
+ }, async (args) => withSession(deps, async (session) => {
13
+ const response = await callAgentDoor(session, deps.fetchImpl, action === "remove" ? "DELETE" : "PATCH", action === "remove" ? `/api/mail/accounts?account=${encodeURIComponent(String(args.account))}` : "/api/mail/accounts", action === "remove" ? undefined : { account: args.account, ...(action === "rename" ? { account_label: args.name } : action === "default" ? { is_default: true } : { [`${args.source}_enabled`]: action === "enable" }) });
14
+ if (!response.ok)
15
+ return errorResult(doorFailureText(`mail_${action}`, response));
16
+ return textResult(`Account ${args.account}: ${action} completed.`, response.body);
17
+ }));
18
+ }
19
+ }
@@ -9,6 +9,22 @@ import { z } from "zod";
9
9
  import { callAgentDoor } from "./agent-door.js";
10
10
  import { CONFIRM_INPUT, doorFailureText, errorResult, textResult, unconfirmed, withSession, } from "./tool-result.js";
11
11
  export function registerCalDeleteTool(register, deps) {
12
+ register("cal_change", { title: "Change an event", description: "Change a caller-owned event, checking the named account before writing to Google.", inputSchema: {
13
+ event_id: z.string().uuid().describe("Tower's event id (cal_today/cal_week/cal_next/cal_find return it as event_id)."),
14
+ account: z.string().min(1).max(100).optional().describe("Exact connected account label, address or id (mail_accounts lists them). Omit to use the account the event is on."),
15
+ title: z.string().min(1).max(500).optional().describe("The new title."),
16
+ starts_at: z.string().optional().describe("The new start, ISO 8601 with offset. Send with ends_at."),
17
+ ends_at: z.string().optional().describe("The new end, ISO 8601 with offset. Send with starts_at."),
18
+ location: z.string().max(1000).optional().describe("The new location."),
19
+ confirm: CONFIRM_INPUT,
20
+ } }, async (args) => withSession(deps, async (session) => {
21
+ const refusal = unconfirmed(args, "This changes the event at Google and may notify guests.");
22
+ if (refusal)
23
+ return refusal;
24
+ const { event_id, confirm: _confirm, ...changes } = args;
25
+ const response = await callAgentDoor(session, deps.fetchImpl, "PATCH", `/api/cal/events/${encodeURIComponent(String(event_id))}`, changes);
26
+ return response.ok ? textResult(`Event changed. Account: ${response.body.event?.account_name ?? "the event’s account"}.`, response.body) : errorResult(doorFailureText("cal_change", response));
27
+ }));
12
28
  register("cal_delete", {
13
29
  title: "Delete an event",
14
30
  description: "Removes ONE event, AT GOOGLE first and then here — the same order every calendar write in this server "
@@ -16,6 +32,7 @@ export function registerCalDeleteTool(register, deps) {
16
32
  + "Attendees are notified (sendUpdates=all) only when the event has any. Google already having it gone "
17
33
  + "(404/410) is answered as success, not a refusal. Requires confirm: true.",
18
34
  inputSchema: {
35
+ account: z.string().min(1).max(100).optional().describe("Exact connected account label, address or id (mail_accounts lists them). Omit to use the account the event is on."),
19
36
  event_id: z.string().uuid().describe("Tower's event id (cal_today/cal_week/cal_next/cal_find return it as event_id)."),
20
37
  confirm: CONFIRM_INPUT,
21
38
  },
@@ -23,18 +40,19 @@ export function registerCalDeleteTool(register, deps) {
23
40
  const refusal = unconfirmed(args, "This would remove that event at Google (emailing any attendees) and then here.");
24
41
  if (refusal)
25
42
  return refusal;
26
- const response = await callAgentDoor(session, deps.fetchImpl, "DELETE", `/api/cal/events/${encodeURIComponent(String(args.event_id))}`);
43
+ const response = await callAgentDoor(session, deps.fetchImpl, "DELETE", `/api/cal/events/${encodeURIComponent(String(args.event_id))}${args.account ? `?account=${encodeURIComponent(String(args.account))}` : ""}`);
27
44
  if (!response.ok)
28
45
  return errorResult(doorFailureText("cal_delete", response));
29
46
  const deleted = response.body.deleted;
30
47
  if (!deleted)
31
48
  return textResult("Tower answered without a result. Nothing to report.", response.body);
32
49
  const alreadyGone = deleted.google === "already_gone_at_google";
50
+ const accountReceipt = deleted.account_name ? ` Account: ${deleted.account_name}.` : "";
33
51
  const stored = deleted.local_store === "removed"
34
52
  ? "Tower's copy is removed."
35
53
  : "Tower's copy is still there for now — the next sync will pick up that it is gone.";
36
54
  return textResult(alreadyGone
37
- ? `Google already had that event gone. ${stored}`
38
- : `Deleted at Google${deleted.notified === "all" ? " — attendees were notified." : "."} ${stored}`, { deleted });
55
+ ? `Google already had that event gone. ${stored}${accountReceipt}`
56
+ : `Deleted at Google${deleted.notified === "all" ? " — attendees were notified." : "."} ${stored}${accountReceipt}`, { deleted });
39
57
  }));
40
58
  }
package/dist/cal-tools.js CHANGED
@@ -169,7 +169,7 @@ export function registerCalTools(server, deps) {
169
169
  ? "No calendars are attached. A person attaches one with `cockpit cal add-ical` (personal) or by connecting "
170
170
  + "Google in the browser (work)."
171
171
  : `${calendars.length} calendar(s).\n${calendars
172
- .map((calendar) => `${calendar.name} ${calendar.provider} ${calendar.org_visible ? "shared" : "private"} ${calendar.status}${calendar.status_reason ? ` (${calendar.status_reason})` : ""} ${calendar.id}`)
172
+ .map((calendar) => `${calendar.account_label ?? calendar.provider} ${calendar.name} ${calendar.provider} ${calendar.org_visible ? "shared" : "private"} ${calendar.status}${calendar.status_reason ? ` (${calendar.status_reason})` : ""} ${calendar.id}`)
173
173
  .join("\n")}`, { calendars });
174
174
  }));
175
175
  register("cal_create", {
@@ -179,7 +179,8 @@ export function registerCalTools(server, deps) {
179
179
  + "says so by name. Times are ISO instants, or plain dates with all_day. Requires confirm: true, because "
180
180
  + "attendees are emailed by Google the moment this succeeds.",
181
181
  inputSchema: {
182
- calendar_id: z.string().uuid().describe("A google_oauth calendar id you own (cal_calendars)."),
182
+ calendar_id: z.string().uuid().optional().describe("A google_oauth calendar id you own (cal_calendars)."),
183
+ account: z.string().min(1).max(100).optional().describe("Exact connected account label; omitted uses default."),
183
184
  title: z.string().min(1).max(500).describe("The event title."),
184
185
  starts_at: z.string().min(4).max(40).describe("An ISO instant, or a YYYY-MM-DD date with all_day."),
185
186
  ends_at: z.string().min(4).max(40).describe("An ISO instant, or a YYYY-MM-DD date with all_day."),
@@ -196,6 +197,7 @@ export function registerCalTools(server, deps) {
196
197
  return refusal;
197
198
  const response = await callAgentDoor(session, deps.fetchImpl, "POST", "/api/cal/events", {
198
199
  calendar_id: args.calendar_id,
200
+ ...(args.account ? { account: args.account } : {}),
199
201
  title: args.title,
200
202
  starts_at: args.starts_at,
201
203
  ends_at: args.ends_at,
@@ -209,6 +211,7 @@ export function registerCalTools(server, deps) {
209
211
  return errorResult(doorFailureText("cal_create", response));
210
212
  const event = response.body.event;
211
213
  return textResult(`Created "${event?.title ?? String(args.title)}" at ${event?.starts_at ?? String(args.starts_at)}.`
214
+ + (event?.account_name ? `\nAccount: ${event.account_name}.` : "")
212
215
  + towerIdLine(event)
213
216
  + (event?.html_link ? `\n${event.html_link}` : "")
214
217
  + (args.meet === true
@@ -28,7 +28,8 @@ export const ATTACHMENTS_INPUT = z
28
28
  /** The JSON both tools post. `attachments` is present only when there are files. */
29
29
  export function composedBody(args, attachments = []) {
30
30
  return {
31
- account_id: args.account_id,
31
+ ...(args.account_id ? { account_id: args.account_id } : {}),
32
+ ...(args.account ? { account: args.account } : {}),
32
33
  to: args.to.map((address) => ({ address })),
33
34
  ...(Array.isArray(args.cc) && args.cc.length > 0
34
35
  ? { cc: args.cc.map((address) => ({ address })) }
@@ -34,6 +34,7 @@
34
34
  * call, so a machine with no `cockpit login` pairing still serves this
35
35
  * server's other tools and only a `mail_` call fails, by name.
36
36
  */
37
+ import { registerAccountTools } from "./account-tools.js";
37
38
  import { z } from "zod";
38
39
  import { callAgentDoor } from "./agent-door.js";
39
40
  import { ATTACHMENTS_INPUT, attachmentResult, prepareMessage, unconfirmedAttachments } from "./mail-compose.js";
@@ -45,10 +46,11 @@ function messageLine(message) {
45
46
  const ADDRESS = EMAIL_INPUT.describe("An email address.");
46
47
  export function registerMailTools(server, deps) {
47
48
  const register = registrarFor(server);
49
+ registerAccountTools(register, deps);
48
50
  register("mail_accounts", {
49
51
  title: "List attached mailboxes",
50
52
  description: "Every mailbox attached to Tower that you may see — address, provider (gmail_oauth or imap), health and last sync. "
51
- + "One person may have several; there is no default mailbox anywhere in this surface, so start here when you need an account id.",
53
+ + "One person may have several, each with a label (e.g. personal 1) and per-source switches; one may be marked default. Start here when you need an account name or id.",
52
54
  inputSchema: {},
53
55
  }, async () => withSession(deps, async (session) => {
54
56
  const response = await callAgentDoor(session, deps.fetchImpl, "GET", "/api/mail/accounts");
@@ -56,7 +58,7 @@ export function registerMailTools(server, deps) {
56
58
  return errorResult(doorFailureText("mail_accounts", response));
57
59
  const accounts = (Array.isArray(response.body.accounts) ? response.body.accounts : []);
58
60
  const lines = accounts
59
- .map((account) => `${account.address} ${account.provider} ${account.status}${account.status_reason ? ` (${account.status_reason})` : ""} ${account.id}`)
61
+ .map((account) => `${account.account_label ?? account.address} ${account.is_default ? "default" : ""} mail=${account.mail_enabled !== false} calendar=${account.calendar_enabled !== false} ${account.address} ${account.provider} ${account.status}${account.status_reason ? ` (${account.status_reason})` : ""} ${account.id}`)
60
62
  .join("\n");
61
63
  return textResult(accounts.length === 0
62
64
  ? "No mailboxes are attached. A person attaches one with `cockpit mail add-imap` (personal) or the browser (work)."
@@ -187,7 +189,8 @@ export function registerMailTools(server, deps) {
187
189
  + "see that argument for what it refuses. "
188
190
  + "This actually sends mail to real people: confirm must be true.",
189
191
  inputSchema: {
190
- account_id: z.string().uuid().describe("The mailbox it goes out from (mail_accounts). Never guessed."),
192
+ account_id: z.string().uuid().optional().describe("The mailbox id, or use account name/default."),
193
+ account: z.string().min(1).max(100).optional().describe("Exact connected account label."),
191
194
  to: z.array(ADDRESS).min(1).max(50).describe("Recipient email addresses."),
192
195
  cc: z.array(ADDRESS).max(50).optional().describe("Copied email addresses."),
193
196
  subject: z.string().max(998).describe("The subject line."),
@@ -213,14 +216,14 @@ export function registerMailTools(server, deps) {
213
216
  if (notConfirmed)
214
217
  return errorResult(notConfirmed);
215
218
  const files = attachmentResult(message.summary);
216
- return textResult(`Sent from ${sent?.address ?? "that mailbox"} to ${args.to.join(", ")}${sent?.threaded ? " (as a reply)" : ""}.${files.sentence}`, { sent, ...files.structured });
219
+ return textResult(`Sent from ${sent?.account_name ?? sent?.address ?? "that mailbox"} to ${args.to.join(", ")}${sent?.threaded ? " (as a reply)" : ""}.${files.sentence}`, { sent, ...files.structured });
217
220
  }));
218
221
  register("mail_draft", {
219
222
  title: "Save a draft in one mailbox",
220
223
  description: "Saves a message as a DRAFT in one of YOUR mailboxes, for a person to read, edit and send themselves. Use it "
221
224
  + "when you are preparing mail on somebody's behalf and they should be the one to press send. NOTHING IS SENT and "
222
- + "nobody is notified, so unlike mail_send there is no confirm flag. `account_id` is REQUIRED and is the mailbox the "
223
- + "draft is saved in (mail_accounts) — with several attached, that is never something to guess. "
225
+ + "nobody is notified, so unlike mail_send there is no confirm flag. `account` is an exact label; `account_id` selects a mailbox id. Omit both for your default. The "
226
+ + "draft is saved in that mailbox (mail_accounts). With several accounts and no default, Tower asks which one. "
224
227
  + "`in_reply_to_message_id` (a message id from mail_read) threads the draft into that conversation the way mail_send "
225
228
  + "threads a reply; Gmail files it there only when the subject matches the original's, and the answer says whether it "
226
229
  + "did (`threaded`). `attachments` ([{ path }]) puts files from this machine in the draft, up to 10 and 3,000,000 bytes "
@@ -228,7 +231,8 @@ export function registerMailTools(server, deps) {
228
231
  + "mailboxes can take one: an IMAP mailbox is refused as imap_drafts_unsupported. Answers the provider draft id, "
229
232
  + "the thread id and the account id.",
230
233
  inputSchema: {
231
- account_id: z.string().uuid().describe("The mailbox the draft is saved in (mail_accounts). Never guessed."),
234
+ account_id: z.string().uuid().optional().describe("The mailbox id, or use account name/default."),
235
+ account: z.string().min(1).max(100).optional().describe("Exact account label, e.g. work 1."),
232
236
  to: z.array(ADDRESS).min(1).max(50).describe("Recipient email addresses, so the draft is ready to send as it is."),
233
237
  cc: z.array(ADDRESS).max(50).optional().describe("Copied email addresses."),
234
238
  subject: z.string().max(998).describe("The subject line. For a reply, keep the original's subject or Gmail will not thread it."),
@@ -252,7 +256,7 @@ export function registerMailTools(server, deps) {
252
256
  const files = attachmentResult(message.summary);
253
257
  // Both halves carry the ids (BLI-10432): a client that hands the model only
254
258
  // `content`, or only `structuredContent`, must still learn where the draft is.
255
- return textResult(`Saved a draft in ${draft?.address ?? "that mailbox"} for ${args.to.join(", ")}`
259
+ return textResult(`Saved a draft in ${draft?.account_name ?? draft?.address ?? "that mailbox"} for ${args.to.join(", ")}`
256
260
  + `${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)" : ""}. `
257
261
  + "Nothing was sent: it waits in Drafts for a person to read, edit and send."
258
262
  + `${files.sentence} `
@@ -0,0 +1,4 @@
1
+ import { type ToolDeps } from "./tool-result.js";
2
+ export declare function registerNotesBotUpcomingTool(server: {
3
+ registerTool: (...args: never[]) => unknown;
4
+ }, deps: ToolDeps): void;
@@ -0,0 +1,14 @@
1
+ import { z } from "zod";
2
+ import { callAgentDoor } from "./agent-door.js";
3
+ import { registrarFor, withSession, textResult, errorResult, doorFailureText } from "./tool-result.js";
4
+ export function registerNotesBotUpcomingTool(server, deps) {
5
+ registrarFor(server)("notes_bot_upcoming", {
6
+ title: "Bot meetings today", description: "Your own Google Meet meetings today the Tower bot will join, with claim state. Decline the event or mark it private to opt out.",
7
+ inputSchema: { tz: z.string().optional().describe("Your IANA time zone; defaults to UTC.") },
8
+ }, async (args) => withSession(deps, async (session) => {
9
+ const response = await callAgentDoor(session, deps.fetchImpl, "GET", `/api/notetaker/upcoming?tz=${encodeURIComponent(String(args.tz ?? "UTC"))}`);
10
+ if (!response.ok)
11
+ return errorResult(doorFailureText("notes_bot_upcoming", response));
12
+ return textResult(JSON.stringify(response.body), response.body);
13
+ }));
14
+ }
package/dist/server.js CHANGED
@@ -17,6 +17,7 @@ import { registerCalTools } from "./cal-tools.js";
17
17
  import { registerMailTools } from "./mail-tools.js";
18
18
  import { registerNotesAccountTools } from "./notes-account-tools.js";
19
19
  import { registerNotesTools } from "./notes-tools.js";
20
+ import { registerNotesBotUpcomingTool } from "./notes-bot-upcoming-tool.js";
20
21
  import { registerNotesGranolaStatusTool } from "./notes-granola-status-tool.js";
21
22
  import { registerNotesWriteTools } from "./notes-write-tools.js";
22
23
  import { registerMemoryConsolidationTools } from "./memory-consolidation-tools.js";
@@ -476,6 +477,7 @@ export function createServer(deps, options = {}) {
476
477
  registerNotesTools(server, { fetchImpl: deps.fetchImpl });
477
478
  // Ticket 10718: the Granola cron's own health, read-only, over the shared
478
479
  // server-side key rather than a `notes_accounts` row.
480
+ registerNotesBotUpcomingTool(server, { fetchImpl: deps.fetchImpl });
479
481
  registerNotesGranolaStatusTool(server, { fetchImpl: deps.fetchImpl });
480
482
  registerOpsTools(server, { fetchImpl: deps.fetchImpl });
481
483
  registerMemoryExperienceTools(server, { fetchImpl: deps.fetchImpl });
@@ -10,7 +10,7 @@
10
10
  * from `src/` under Node's type stripping, so it may import nothing.
11
11
  */
12
12
  /** Tools that change nothing. The smoke calls every one. */
13
- export declare const READ_TOOLS: readonly ["get_ticket_timeline", "get_active_tickets", "docs_list", "docs_tree", "docs_read", "docs_sync_status", "msg_channels", "msg_read", "msg_thread", "work_list_issues", "work_get_issue", "work_issue_history", "work_list_projects", "careers_list", "careers_show", "careers_resume_url", "careers_takehome_show", "careers_settings_show", "rename_pending", "github_pr", "setup_read", "speed_read", "speed_seen", "usage_people", "usage_sessions", "mail_accounts", "mail_inbox", "mail_read", "mail_search", "mail_attachment", "mail_sync_status", "cal_today", "cal_week", "cal_next", "cal_find", "cal_calendars", "cal_sync_status", "jarvis_ask", "jarvis_trace", "jarvis_check", "models_list", "models_card", "models_compare", "brief_read", "brief_history", "brief_status", "brief_missed", "notes_folders", "notes_list", "glossary_list", "notes_show", "notes_shelf", "notes_shelves", "notes_place", "notes_accounts", "notes_recordings", "notes_recording", "notes_sync_status", "notes_granola_status", "ops_status", "slack_coverage", "slack_read", "memory_rules", "memory_profile", "memory_consolidation", "settings_show", "settings_list", "model_show", "team_members", "team_device_list", "scout_board", "workbook_read", "tower_search", "sources_check", "logs_digest", "brain_status"];
13
+ export declare const READ_TOOLS: readonly ["get_ticket_timeline", "get_active_tickets", "docs_list", "docs_tree", "docs_read", "docs_sync_status", "msg_channels", "msg_read", "msg_thread", "work_list_issues", "work_get_issue", "work_issue_history", "work_list_projects", "careers_list", "careers_show", "careers_resume_url", "careers_takehome_show", "careers_settings_show", "rename_pending", "github_pr", "setup_read", "speed_read", "speed_seen", "usage_people", "usage_sessions", "mail_accounts", "mail_inbox", "mail_read", "mail_search", "mail_attachment", "mail_sync_status", "cal_today", "cal_week", "cal_next", "cal_find", "cal_calendars", "cal_sync_status", "jarvis_ask", "jarvis_trace", "jarvis_check", "models_list", "models_card", "models_compare", "brief_read", "brief_history", "brief_status", "brief_missed", "notes_folders", "notes_list", "glossary_list", "notes_show", "notes_shelf", "notes_shelves", "notes_place", "notes_accounts", "notes_recordings", "notes_recording", "notes_sync_status", "notes_granola_status", "notes_bot_upcoming", "ops_status", "slack_coverage", "slack_read", "memory_rules", "memory_profile", "memory_consolidation", "settings_show", "settings_list", "model_show", "team_members", "team_device_list", "scout_board", "workbook_read", "tower_search", "sources_check", "logs_digest", "brain_status"];
14
14
  /**
15
15
  * Tools that write, but answer a dry run that writes nothing. The smoke calls
16
16
  * each one ONLY with its dry-run flag set (`scripts/smoke.ts` says which).
@@ -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 ["brain_retry", "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", "glossary_add", "glossary_remove", "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", "team_device_doctor"];
24
+ export declare const WRITE_TOOLS: readonly ["brain_retry", "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_rename", "mail_enable", "mail_disable", "mail_default", "mail_remove", "cal_change", "mail_sync", "mail_detach", "cal_create", "cal_share", "cal_sync", "cal_detach", "cal_delete", "jarvis_dispatch", "glossary_add", "glossary_remove", "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", "team_device_doctor"];
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
  /**
@@ -71,6 +71,7 @@ export const READ_TOOLS = [
71
71
  "notes_recording",
72
72
  "notes_sync_status",
73
73
  "notes_granola_status",
74
+ "notes_bot_upcoming",
74
75
  "ops_status",
75
76
  "slack_coverage",
76
77
  "slack_read",
@@ -131,6 +132,7 @@ export const WRITE_TOOLS = [
131
132
  "mail_send",
132
133
  // BLI-12530: writes into a mailbox (a draft), reaches nobody. Never smoked.
133
134
  "mail_draft",
135
+ "mail_rename", "mail_enable", "mail_disable", "mail_default", "mail_remove", "cal_change",
134
136
  "mail_sync",
135
137
  "mail_detach",
136
138
  "cal_create",
@@ -273,6 +273,11 @@ export const MCP_TWINS = {
273
273
  "issue comment": { tool: "work_comment_issue", door: "POST /api/work/issues/[id]/comments" },
274
274
  "issue history": { tool: "work_issue_history", door: "GET /api/work/issues/[id]/history" },
275
275
  "project list": { tool: "work_list_projects", door: "GET /api/work/projects" },
276
+ "mail rename": { tool: "mail_rename", door: "PATCH /api/mail/accounts" },
277
+ "mail enable": { tool: "mail_enable", door: "PATCH /api/mail/accounts" },
278
+ "mail disable": { tool: "mail_disable", door: "PATCH /api/mail/accounts" },
279
+ "mail default": { tool: "mail_default", door: "PATCH /api/mail/accounts" },
280
+ "mail remove": { tool: "mail_remove", door: "DELETE /api/mail/accounts" },
276
281
  "mail accounts": { tool: "mail_accounts", door: "GET /api/mail/accounts" },
277
282
  "mail inbox": { tool: "mail_inbox", door: "GET /api/mail/inbox" },
278
283
  "mail read": { tool: "mail_read", door: "GET /api/mail/threads/[id]" },
@@ -289,6 +294,8 @@ export const MCP_TWINS = {
289
294
  "cal next": { tool: "cal_next", door: "GET /api/cal/next" },
290
295
  "cal find": { tool: "cal_find", door: "GET /api/cal/find" },
291
296
  "cal calendars": { tool: "cal_calendars", door: "GET /api/cal/calendars" },
297
+ "cal change": { tool: "cal_change", door: "PATCH /api/cal/events/[eventId]" },
298
+ "cal cancel": { tool: "cal_delete", door: "DELETE /api/cal/events/[eventId]" },
292
299
  "cal create": { tool: "cal_create", door: "POST /api/cal/events" },
293
300
  "cal share": { tool: "cal_share", door: "PATCH /api/cal/calendars" },
294
301
  "cal sync": { tool: "cal_sync", door: "POST /api/cal/calendars/[id]/sync" },
@@ -326,6 +333,7 @@ export const MCP_TWINS = {
326
333
  "notes sync": { tool: "notes_sync", door: "POST /api/notes/accounts/[id]/sync" },
327
334
  "notes sync-status": { tool: "notes_sync_status", door: "GET /api/notes/sync-status" },
328
335
  // Ticket 10718: Granola's own cron health, over its shared server-side key.
336
+ "notes bot-upcoming": { tool: "notes_bot_upcoming", door: "GET /api/notetaker/upcoming" },
329
337
  "notes granola-status": { tool: "notes_granola_status", door: "GET /api/notes/granola-status" },
330
338
  "memory log": { tool: "memory_experience", door: "POST /api/memory/experience" },
331
339
  // BLI-4047, BLI-12927: the memory STORE at a terminal. Its twins live on the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bli-cockpit/mcp",
3
- "version": "0.1.110",
3
+ "version": "0.1.111",
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",