@bli-cockpit/mcp 0.1.115 → 0.1.116
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 +10 -1
- package/dist/approvals-tools.d.ts +4 -0
- package/dist/approvals-tools.js +191 -0
- package/dist/server.js +3 -0
- package/dist/tool-annotations.d.ts +1 -1
- package/dist/tool-annotations.js +1 -0
- package/dist/tool-catalog.d.ts +2 -2
- package/dist/tool-catalog.js +11 -0
- package/dist/verb-census-spellings.js +2 -0
- package/dist/verb-census.js +9 -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
|
+
**169 of 174 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
|
|
@@ -117,6 +117,15 @@ three tables below.
|
|
|
117
117
|
|
|
118
118
|
| CLI verb | MCP tool | Door |
|
|
119
119
|
| --- | --- | --- |
|
|
120
|
+
| `cockpit approvals approve` | `approvals_approve` | `POST /api/approvals/[id]/approve` |
|
|
121
|
+
| `cockpit approvals create` | `approvals_create` | `POST /api/approvals` |
|
|
122
|
+
| `cockpit approvals edit` | `approvals_edit` | `PATCH /api/approvals/[id]` |
|
|
123
|
+
| `cockpit approvals list` | `approvals_list` | `GET /api/approvals` |
|
|
124
|
+
| `cockpit approvals reject` | `approvals_reject` | `POST /api/approvals/[id]/reject` |
|
|
125
|
+
| `cockpit approvals rules` | `approvals_rules` | `GET /api/approvals/rules` |
|
|
126
|
+
| `cockpit approvals rules create` | `approvals_rules_create` | `POST /api/approvals/rules` |
|
|
127
|
+
| `cockpit approvals rules revoke` | `approvals_rules_revoke` | `POST /api/approvals/rules/[id]/revoke` |
|
|
128
|
+
| `cockpit approvals show` | `approvals_show` | `GET /api/approvals/[id]` |
|
|
120
129
|
| `cockpit brain retry` | `brain_retry` | `POST /api/brain/incidents/retry` |
|
|
121
130
|
| `cockpit brain status` | `brain_status` | `GET /api/brain/incidents` |
|
|
122
131
|
| `cockpit brief done` | `brief_done` | `POST /api/jarvis/ticks` |
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The approvals inbox over MCP (BLI-16945): the nine twins of `cockpit approvals`.
|
|
3
|
+
*
|
|
4
|
+
* Plain words: an agent PREPARES an outward act (a mail, a message) as a card; the person
|
|
5
|
+
* approves it, edits it and approves it, or rejects it. After a few approvals of one kind the
|
|
6
|
+
* person can turn it into a standing rule ("send without asking"). Every run leaves a receipt.
|
|
7
|
+
* These tools ride the SAME `/api/approvals` doors the browser page and the CLI call, on this
|
|
8
|
+
* machine's device token, never a second route.
|
|
9
|
+
*
|
|
10
|
+
* Three of them are deliberate acts and refuse without `confirm: true`, the same gate as
|
|
11
|
+
* `mail_send` and the CLI's `--yes`: `approvals_approve` (the guarded act runs and cannot be
|
|
12
|
+
* recalled), `approvals_rules_create` (future cards send without asking) and
|
|
13
|
+
* `approvals_rules_revoke` (a standing rule stops). Creating a card, editing one and rejecting one
|
|
14
|
+
* reach nobody, so they need none.
|
|
15
|
+
*
|
|
16
|
+
* A refused send answers 502 with the door's reason and its "Not sent" sentence; this relays both
|
|
17
|
+
* verbatim, as an error, and approving again retries.
|
|
18
|
+
*/
|
|
19
|
+
import { z } from "zod";
|
|
20
|
+
import { callAgentDoor } from "./agent-door.js";
|
|
21
|
+
import { CONFIRM_INPUT, EMAIL_INPUT, dataResult, doorFailureText, errorResult, queryString, registrarFor, unconfirmed, withSession, } from "./tool-result.js";
|
|
22
|
+
const LIST_STATUSES = ["open", "done", "pending", "executing", "executed", "failed", "rejected"];
|
|
23
|
+
const SOURCE_KINDS = ["session", "meeting", "ticket", "message", "mail", "jarvis", "schedule", "manual"];
|
|
24
|
+
const CARD_ID = z.string().uuid().describe("The card id, from approvals_list.");
|
|
25
|
+
const ADDRESS = z.object({
|
|
26
|
+
name: z.string().max(200).optional().describe("The person's display name."),
|
|
27
|
+
address: EMAIL_INPUT.describe("The email address."),
|
|
28
|
+
});
|
|
29
|
+
/** Every field a card's `payload` can carry today (mail.send and msg.send); the door checks the fields of the kind named. */
|
|
30
|
+
const PAYLOAD = z.looseObject({
|
|
31
|
+
account: z.string().max(100).optional().describe("mail.send: the exact connected mailbox label to send from. Omit for the default."),
|
|
32
|
+
account_id: z.string().uuid().optional().describe("mail.send: the mailbox id to send from, instead of a label."),
|
|
33
|
+
to: z.array(ADDRESS).max(50).optional().describe("mail.send: recipients as { name?, address }."),
|
|
34
|
+
cc: z.array(ADDRESS).max(50).optional().describe("mail.send: copied recipients as { name?, address }."),
|
|
35
|
+
subject: z.string().max(998).optional().describe("mail.send: the subject line."),
|
|
36
|
+
text: z.string().max(500_000).optional().describe("mail.send: the plain-text body."),
|
|
37
|
+
in_reply_to_message_id: z.string().uuid().optional().describe("mail.send: a message id from mail_read, to send as a reply in that thread."),
|
|
38
|
+
channel_id: z.string().uuid().optional().describe("msg.send: the channel id from msg_channels."),
|
|
39
|
+
content: z.string().max(8000).optional().describe("msg.send: the message text."),
|
|
40
|
+
thread_parent_id: z.string().uuid().optional().describe("msg.send: a message id to reply under."),
|
|
41
|
+
});
|
|
42
|
+
const EDIT = z.looseObject({
|
|
43
|
+
subject: z.string().max(998).optional().describe("mail.send: the new subject line."),
|
|
44
|
+
text: z.string().max(500_000).optional().describe("mail.send: the new plain-text body."),
|
|
45
|
+
content: z.string().max(8000).optional().describe("msg.send: the new message text."),
|
|
46
|
+
});
|
|
47
|
+
export function registerApprovalsTools(server, deps) {
|
|
48
|
+
const register = registrarFor(server);
|
|
49
|
+
/** One door call, relayed: the data on success, the door's own reason and sentence on a refusal. */
|
|
50
|
+
async function ask(tool, method, path, body, headline) {
|
|
51
|
+
return withSession(deps, async (session) => {
|
|
52
|
+
const response = await callAgentDoor(session, deps.fetchImpl, method, path, body);
|
|
53
|
+
if (!response.ok)
|
|
54
|
+
return errorResult(doorFailureText(tool, response));
|
|
55
|
+
return dataResult(headline(response.body), response.body);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
const titleOf = (answer) => {
|
|
59
|
+
const card = answer.card;
|
|
60
|
+
return typeof card?.title === "string" ? card.title : "that card";
|
|
61
|
+
};
|
|
62
|
+
register("approvals_list", {
|
|
63
|
+
title: "List approval cards",
|
|
64
|
+
description: "The approvals inbox: outward acts (a mail, a message) that an agent prepared and a person has yet to decide. `status` open (default) is everything still waiting "
|
|
65
|
+
+ "(pending, executing, failed); done is everything decided; or name one status. Each card has its title, who it goes to, why, where it came from, the exact content and its "
|
|
66
|
+
+ "latest receipt. Newest first; `has_more` and `next_before` page backwards. Read-only. `cockpit approvals`, same door.",
|
|
67
|
+
inputSchema: {
|
|
68
|
+
status: z.enum(LIST_STATUSES).optional().describe("open (default), done, or one of pending, executing, executed, failed, rejected."),
|
|
69
|
+
limit: z.number().int().min(1).max(100).optional().describe("How many cards, 1 to 100. Default 50."),
|
|
70
|
+
before: z.string().min(1).max(100).optional().describe("The next_before cursor of the previous page, to read older cards."),
|
|
71
|
+
},
|
|
72
|
+
}, async (args) => {
|
|
73
|
+
const query = new URLSearchParams();
|
|
74
|
+
if (typeof args.status === "string")
|
|
75
|
+
query.set("status", args.status);
|
|
76
|
+
if (typeof args.limit === "number")
|
|
77
|
+
query.set("limit", String(args.limit));
|
|
78
|
+
if (typeof args.before === "string")
|
|
79
|
+
query.set("before", args.before);
|
|
80
|
+
return ask("approvals_list", "GET", `/api/approvals${queryString(query)}`, undefined, (answer) => `${String(answer.returned ?? 0)} card(s) shown, ${String(answer.waiting ?? 0)} waiting for a decision.${answer.has_more === true ? " More exist: pass next_before as before." : ""}`);
|
|
81
|
+
});
|
|
82
|
+
register("approvals_show", {
|
|
83
|
+
title: "Read one approval card",
|
|
84
|
+
description: "One card in full: who it goes to, why, where it came from, the exact content that will be (or was) sent, every receipt with the content the act door was given, "
|
|
85
|
+
+ "and whether a standing rule is worth offering. Read-only. `cockpit approvals show`, same door.",
|
|
86
|
+
inputSchema: { id: CARD_ID },
|
|
87
|
+
}, async (args) => ask("approvals_show", "GET", `/api/approvals/${encodeURIComponent(String(args.id))}`, undefined, (answer) => `${titleOf(answer)}.`));
|
|
88
|
+
register("approvals_create", {
|
|
89
|
+
title: "Prepare an approval card",
|
|
90
|
+
description: "Prepares an outward act as a card for the person to approve; nothing is sent. `act_kind` is mail.send (payload: to, subject, text, and optionally account, cc, in_reply_to_message_id) "
|
|
91
|
+
+ "or msg.send (payload: channel_id, content, optionally thread_parent_id). `reason` is why, in a sentence the person reads. `dedupe_key` makes a retry return the same card. "
|
|
92
|
+
+ "If a standing rule already covers the act it is sent at once and the answer says so. `cockpit approvals create`, same door.",
|
|
93
|
+
inputSchema: {
|
|
94
|
+
act_kind: z.enum(["mail.send", "msg.send"]).describe("Which kind of act: mail.send or msg.send."),
|
|
95
|
+
payload: PAYLOAD.describe("The act's exact content; the fields of the kind named."),
|
|
96
|
+
reason: z.string().trim().min(1).max(2000).describe("Why this should go out, in a sentence the person reads."),
|
|
97
|
+
kind_label: z.string().trim().min(1).max(120).optional().describe("A short name for this sort of card, like Client follow-up email."),
|
|
98
|
+
source_kind: z.enum(SOURCE_KINDS).optional().describe("Where the act came from. Default manual."),
|
|
99
|
+
source_ref: z.string().trim().min(1).max(200).optional().describe("An id or link for the source, like a ticket identifier."),
|
|
100
|
+
source_label: z.string().trim().min(1).max(300).optional().describe("A short human label for the source, shown on the card."),
|
|
101
|
+
prepared_by: z.string().trim().min(1).max(80).optional().describe("Who prepared it, like Claude. Default an agent on a paired machine."),
|
|
102
|
+
dedupe_key: z.string().trim().min(1).max(200).optional().describe("A stable key so preparing the same card twice returns the first."),
|
|
103
|
+
},
|
|
104
|
+
}, async (args) => ask("approvals_create", "POST", "/api/approvals", args, (answer) => {
|
|
105
|
+
if (answer.deduped === true)
|
|
106
|
+
return `${titleOf(answer)} was already prepared. Nothing new was created.`;
|
|
107
|
+
if (answer.executed_by_rule === true)
|
|
108
|
+
return `${titleOf(answer)}: a standing rule sent it without asking.`;
|
|
109
|
+
return `${titleOf(answer)} is prepared and waiting for the person's decision.`;
|
|
110
|
+
}));
|
|
111
|
+
register("approvals_approve", {
|
|
112
|
+
title: "Approve an approval card",
|
|
113
|
+
description: "Approves a card: the guarded act RUNS (the mail goes out, the message posts) and cannot be taken back, so confirm must be true. `edit` ({ subject, text } or { content }) "
|
|
114
|
+
+ "is applied first, in one step. If the send is refused the answer is an error carrying the door's reason and its \"Not sent\" sentence, the card is marked failed, and approving again retries. "
|
|
115
|
+
+ "A card already decided is refused with already_decided. `cockpit approvals approve`, same door.",
|
|
116
|
+
inputSchema: {
|
|
117
|
+
id: CARD_ID,
|
|
118
|
+
edit: EDIT.optional().describe("An edit to apply before approving: { subject, text } for mail, { content } for a message."),
|
|
119
|
+
confirm: CONFIRM_INPUT,
|
|
120
|
+
},
|
|
121
|
+
}, async (args) => {
|
|
122
|
+
const refusal = unconfirmed(args, "Approving lets the guarded act run, and a sent mail or message cannot be recalled.");
|
|
123
|
+
if (refusal)
|
|
124
|
+
return refusal;
|
|
125
|
+
return ask("approvals_approve", "POST", `/api/approvals/${encodeURIComponent(String(args.id))}/approve`, args.edit ? { edit: args.edit } : {}, (answer) => {
|
|
126
|
+
const receipt = answer.receipt;
|
|
127
|
+
return `Approved. ${typeof receipt?.summary === "string" ? receipt.summary : "Done."}`;
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
register("approvals_edit", {
|
|
131
|
+
title: "Edit an approval card",
|
|
132
|
+
description: "Saves an edit to a waiting card ({ subject, text } for mail, { content } for a message). The card stays waiting and nothing is sent; to edit and send in one step, call "
|
|
133
|
+
+ "approvals_approve with `edit`. Recipients cannot be edited (field_not_editable). `cockpit approvals edit`, same door.",
|
|
134
|
+
inputSchema: { id: CARD_ID, edit: EDIT.describe("The edit: { subject, text } for mail, { content } for a message.") },
|
|
135
|
+
}, async (args) => ask("approvals_edit", "PATCH", `/api/approvals/${encodeURIComponent(String(args.id))}`, { edit: args.edit }, (answer) => `Saved your edit to ${titleOf(answer)}. It is still waiting for a decision.`));
|
|
136
|
+
register("approvals_reject", {
|
|
137
|
+
title: "Reject an approval card",
|
|
138
|
+
description: "Declines a waiting card. Nothing is sent and the card is closed; a card already decided is refused with already_decided. `cockpit approvals reject`, same door.",
|
|
139
|
+
inputSchema: { id: CARD_ID },
|
|
140
|
+
}, async (args) => ask("approvals_reject", "POST", `/api/approvals/${encodeURIComponent(String(args.id))}/reject`, {}, (answer) => `Rejected ${titleOf(answer)}. Nothing was sent.`));
|
|
141
|
+
register("approvals_rules", {
|
|
142
|
+
title: "List standing approval rules",
|
|
143
|
+
description: "The person's standing rules: kinds of card that send without asking, who they cover, how often each has fired and whether it is still active. Read-only. `cockpit approvals rules`, same door.",
|
|
144
|
+
inputSchema: {
|
|
145
|
+
active: z.boolean().optional().describe("true for rules still in force, false for revoked ones. Omit for both."),
|
|
146
|
+
limit: z.number().int().min(1).max(100).optional().describe("How many rules, 1 to 100. Default 50."),
|
|
147
|
+
},
|
|
148
|
+
}, async (args) => {
|
|
149
|
+
const query = new URLSearchParams();
|
|
150
|
+
if (typeof args.active === "boolean")
|
|
151
|
+
query.set("active", String(args.active));
|
|
152
|
+
if (typeof args.limit === "number")
|
|
153
|
+
query.set("limit", String(args.limit));
|
|
154
|
+
return ask("approvals_rules", "GET", `/api/approvals/rules${queryString(query)}`, undefined, (answer) => `${String(answer.returned ?? 0)} standing rule(s).`);
|
|
155
|
+
});
|
|
156
|
+
register("approvals_rules_create", {
|
|
157
|
+
title: "Create a standing approval rule",
|
|
158
|
+
description: "Turns a card the person approved into a standing rule: from now on this kind of card sends WITHOUT asking, so confirm must be true. `audience` same (default) covers the same people; "
|
|
159
|
+
+ "any covers every audience of this kind. Refused unless the person approved the card themselves (card_not_approved) and when a rule already exists (rule_exists). `cockpit approvals rules create`, same door.",
|
|
160
|
+
inputSchema: {
|
|
161
|
+
card_id: z.string().uuid().describe("The id of a card the person approved."),
|
|
162
|
+
audience: z.enum(["same", "any"]).optional().describe("same (default): the same people. any: every audience of this kind."),
|
|
163
|
+
label: z.string().trim().min(1).max(400).optional().describe("A name for the rule, shown in the rules list."),
|
|
164
|
+
confirm: CONFIRM_INPUT,
|
|
165
|
+
},
|
|
166
|
+
}, async (args) => {
|
|
167
|
+
const refusal = unconfirmed(args, "A standing rule makes this kind of card send without asking from now on.");
|
|
168
|
+
if (refusal)
|
|
169
|
+
return refusal;
|
|
170
|
+
const { confirm: _confirm, ...body } = args;
|
|
171
|
+
return ask("approvals_rules_create", "POST", "/api/approvals/rules", body, (answer) => {
|
|
172
|
+
const rule = answer.rule;
|
|
173
|
+
return `Standing rule created: ${typeof rule?.label === "string" ? rule.label : "see the rule"}.`;
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
register("approvals_rules_revoke", {
|
|
177
|
+
title: "Revoke a standing approval rule",
|
|
178
|
+
description: "Takes a standing rule back: matching cards wait for a decision again. The rule stays in the list as revoked, with the reason. Confirm must be true. "
|
|
179
|
+
+ "`cockpit approvals rules revoke`, same door.",
|
|
180
|
+
inputSchema: {
|
|
181
|
+
id: z.string().uuid().describe("The rule id, from approvals_rules."),
|
|
182
|
+
reason: z.string().trim().min(1).max(300).optional().describe("Why the rule is taken back."),
|
|
183
|
+
confirm: CONFIRM_INPUT,
|
|
184
|
+
},
|
|
185
|
+
}, async (args) => {
|
|
186
|
+
const refusal = unconfirmed(args, "Revoking a standing rule changes what Tower sends without asking.");
|
|
187
|
+
if (refusal)
|
|
188
|
+
return refusal;
|
|
189
|
+
return ask("approvals_rules_revoke", "POST", `/api/approvals/rules/${encodeURIComponent(String(args.id))}/revoke`, args.reason ? { reason: args.reason } : {}, () => "Standing rule revoked.");
|
|
190
|
+
});
|
|
191
|
+
}
|
package/dist/server.js
CHANGED
|
@@ -42,6 +42,7 @@ import { registerSpeedTools } from "./speed-tools.js";
|
|
|
42
42
|
import { registerUsageTools } from "./usage-tools.js";
|
|
43
43
|
import { registerLogsTools } from "./logs-tools.js";
|
|
44
44
|
import { registerBrainTools } from "./brain-tools.js";
|
|
45
|
+
import { registerApprovalsTools } from "./approvals-tools.js";
|
|
45
46
|
import { registerSourcesTools } from "./sources-tools.js";
|
|
46
47
|
import { TOWER_SERVER_ICONS } from "./server-icons.js";
|
|
47
48
|
import { installToolRegistration } from "./tool-registration.js";
|
|
@@ -522,5 +523,7 @@ export function createServer(deps, options = {}) {
|
|
|
522
523
|
// BLI-12859: Tower's incident queue (one incident, one issue, per recurring log problem), over
|
|
523
524
|
// the same GET /api/brain/incidents door `cockpit brain status` reads. Read-only.
|
|
524
525
|
registerBrainTools(server, { fetchImpl: deps.fetchImpl });
|
|
526
|
+
// BLI-16945: the approvals inbox, over the same /api/approvals doors the page and `cockpit approvals` call.
|
|
527
|
+
registerApprovalsTools(server, { fetchImpl: deps.fetchImpl });
|
|
525
528
|
return server;
|
|
526
529
|
}
|
|
@@ -17,7 +17,7 @@ export interface TowerToolAnnotations {
|
|
|
17
17
|
* Writes a person cannot take back by writing again: deletes, revokes,
|
|
18
18
|
* detaches and a merge. These also carry `destructiveHint: true`.
|
|
19
19
|
*/
|
|
20
|
-
export declare const DESTRUCTIVE_TOOLS: readonly ["docs_delete", "cal_delete", "cal_detach", "mail_detach", "notes_detach", "notes_rmdir", "glossary_remove", "settings_delete", "team_device_revoke", "github_merge"];
|
|
20
|
+
export declare const DESTRUCTIVE_TOOLS: readonly ["docs_delete", "cal_delete", "cal_detach", "mail_detach", "notes_detach", "notes_rmdir", "glossary_remove", "settings_delete", "team_device_revoke", "github_merge", "approvals_rules_revoke"];
|
|
21
21
|
/**
|
|
22
22
|
* Tools the HOSTED server leaves out, each with the reason. A hosted process
|
|
23
23
|
* is one machine answering many people: a tool that reads that machine's
|
package/dist/tool-annotations.js
CHANGED
package/dist/tool-catalog.d.ts
CHANGED
|
@@ -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_board", "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_connect", "slack_disconnect", "slack_read", "memory_map", "memory_rules", "memory_knowledge", "memory_profile", "memory_consolidation", "memory_chain", "settings_show", "settings_list", "model_show", "team_members", "team_github_login_add", "team_github_login_remove", "team_github_login_list", "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_board", "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_connect", "slack_disconnect", "slack_read", "memory_map", "memory_rules", "memory_knowledge", "memory_profile", "memory_consolidation", "memory_chain", "settings_show", "settings_list", "model_show", "team_members", "team_github_login_add", "team_github_login_remove", "team_github_login_list", "team_device_list", "scout_board", "workbook_read", "tower_search", "sources_check", "logs_digest", "brain_status", "approvals_list", "approvals_show", "approvals_rules"];
|
|
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 ["logs_key_mint", "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_grant", "team_revoke", "team_device_revoke", "team_device_doctor"];
|
|
24
|
+
export declare const WRITE_TOOLS: readonly ["approvals_create", "approvals_approve", "approvals_edit", "approvals_reject", "approvals_rules_create", "approvals_rules_revoke", "logs_key_mint", "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_grant", "team_revoke", "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
|
/**
|
package/dist/tool-catalog.js
CHANGED
|
@@ -101,6 +101,10 @@ export const READ_TOOLS = [
|
|
|
101
101
|
"logs_digest",
|
|
102
102
|
// BLI-12859: Tower's incident queue, per project. Reads only.
|
|
103
103
|
"brain_status",
|
|
104
|
+
// BLI-16945: the approvals inbox and its standing rules. Reads only.
|
|
105
|
+
"approvals_list",
|
|
106
|
+
"approvals_show",
|
|
107
|
+
"approvals_rules",
|
|
104
108
|
];
|
|
105
109
|
/**
|
|
106
110
|
* Tools that write, but answer a dry run that writes nothing. The smoke calls
|
|
@@ -113,6 +117,13 @@ export const DRY_RUN_TOOLS = ["ops_recompile", "memory_requeue"];
|
|
|
113
117
|
* suites (`write-tools.test.ts` and each family's own).
|
|
114
118
|
*/
|
|
115
119
|
export const WRITE_TOOLS = [
|
|
120
|
+
// BLI-16945: approve runs the guarded act; a rule sends without asking. Never smoked.
|
|
121
|
+
"approvals_create",
|
|
122
|
+
"approvals_approve",
|
|
123
|
+
"approvals_edit",
|
|
124
|
+
"approvals_reject",
|
|
125
|
+
"approvals_rules_create",
|
|
126
|
+
"approvals_rules_revoke",
|
|
116
127
|
"logs_key_mint",
|
|
117
128
|
"brain_retry",
|
|
118
129
|
"emit_event",
|
|
@@ -22,6 +22,8 @@
|
|
|
22
22
|
export const TYPED_SPELLING = {
|
|
23
23
|
// The action is `invite`; the button, the help wall and the docs all say
|
|
24
24
|
// send-takehome, so that is what a stranger is told to type.
|
|
25
|
+
"approvals rules-create": "approvals rules create",
|
|
26
|
+
"approvals rules-revoke": "approvals rules revoke",
|
|
25
27
|
"careers invite": "careers send-takehome",
|
|
26
28
|
"careers takehome-show": "careers takehome show",
|
|
27
29
|
"careers takehome-set": "careers takehome set",
|
package/dist/verb-census.js
CHANGED
|
@@ -244,6 +244,15 @@ export const MCP_TWINS = {
|
|
|
244
244
|
"logs": { tool: "logs_digest", door: "LOCAL @bli-cockpit/telemetry-core/log-digest (this machine's logs); `project` reads GET /api/logs (the stored digests)" },
|
|
245
245
|
"brain retry": { tool: "brain_retry", door: "POST /api/brain/incidents/retry" },
|
|
246
246
|
"brain status": { tool: "brain_status", door: "GET /api/brain/incidents" },
|
|
247
|
+
"approvals list": { tool: "approvals_list", door: "GET /api/approvals" },
|
|
248
|
+
"approvals show": { tool: "approvals_show", door: "GET /api/approvals/[id]" },
|
|
249
|
+
"approvals create": { tool: "approvals_create", door: "POST /api/approvals" },
|
|
250
|
+
"approvals approve": { tool: "approvals_approve", door: "POST /api/approvals/[id]/approve" },
|
|
251
|
+
"approvals edit": { tool: "approvals_edit", door: "PATCH /api/approvals/[id]" },
|
|
252
|
+
"approvals reject": { tool: "approvals_reject", door: "POST /api/approvals/[id]/reject" },
|
|
253
|
+
"approvals rules": { tool: "approvals_rules", door: "GET /api/approvals/rules" },
|
|
254
|
+
"approvals rules-create": { tool: "approvals_rules_create", door: "POST /api/approvals/rules" },
|
|
255
|
+
"approvals rules-revoke": { tool: "approvals_rules_revoke", door: "POST /api/approvals/rules/[id]/revoke" },
|
|
247
256
|
"usage sessions": { tool: "usage_sessions", door: "GET /api/usage/sessions" },
|
|
248
257
|
"setup": { tool: "setup_read", door: "GET /api/setup" },
|
|
249
258
|
// BLI-13033: times the data door of every rail page; the twin runs the same GETs.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bli-cockpit/mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.116",
|
|
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",
|