@bli-cockpit/mcp 0.1.44 → 0.1.47
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 +6 -1
- package/dist/notes-account-tools.d.ts +24 -0
- package/dist/notes-account-tools.js +78 -0
- package/dist/server.js +5 -0
- package/dist/verb-census.js +7 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -34,7 +34,7 @@ about what an agent can reach.
|
|
|
34
34
|
|
|
35
35
|
<!-- BEGIN GENERATED verb census — `npm run mcp:readme` -->
|
|
36
36
|
|
|
37
|
-
**
|
|
37
|
+
**94 of 99 Tower verbs have an MCP twin.**
|
|
38
38
|
Each tool goes through the SAME door its CLI verb calls, with the same
|
|
39
39
|
collector device token — never a second route and never a service-role
|
|
40
40
|
reader. `src/verb-census.test.ts` fails when a verb is in none of the
|
|
@@ -97,6 +97,8 @@ three tables below.
|
|
|
97
97
|
| `cockpit msg read` | `msg_read` | `GET /api/msg/channels/[id]/messages` |
|
|
98
98
|
| `cockpit msg send` | `msg_send` | `POST /api/msg/channels/[id]/messages` |
|
|
99
99
|
| `cockpit msg thread` | `msg_thread` | `GET /api/msg/channels/[id]/messages?thread_parent_id=` |
|
|
100
|
+
| `cockpit notes accounts` | `notes_accounts` | `GET /api/notes/accounts` |
|
|
101
|
+
| `cockpit notes detach` | `notes_detach` | `DELETE /api/notes/accounts` |
|
|
100
102
|
| `cockpit notes folders` | `notes_folders` | `GET /api/notes/folders` |
|
|
101
103
|
| `cockpit notes list` | `notes_list` | `GET /api/notes/library` |
|
|
102
104
|
| `cockpit notes mkdir` | `notes_mkdir` | `POST /api/notes/folders` |
|
|
@@ -109,6 +111,7 @@ three tables below.
|
|
|
109
111
|
| `cockpit notes shelf` | `notes_shelf` | `GET /api/notes/shelf` |
|
|
110
112
|
| `cockpit notes shelves` | `notes_shelves` | `GET /api/notes/library` |
|
|
111
113
|
| `cockpit notes show` | `notes_show` | `GET /api/notes/library/[id]` |
|
|
114
|
+
| `cockpit notes sync` | `notes_sync` | `POST /api/notes/accounts/[id]/sync` |
|
|
112
115
|
| `cockpit notes unshare` | `notes_unshare` | `POST /api/notes/share (share=false)` |
|
|
113
116
|
| `cockpit notes upload` | `notes_upload` | `POST /api/notes/upload` |
|
|
114
117
|
| `cockpit ops recompile` | `ops_recompile` | `POST /api/ops/recompile` |
|
|
@@ -149,6 +152,8 @@ A claim about the verb's nature, not a backlog.
|
|
|
149
152
|
| `cockpit brief edit` | opens the person's own $EDITOR on this machine and files what they changed; an agent has no editor to open (commands/editor.ts) |
|
|
150
153
|
| `cockpit cal add-ical` | carries a calendar's SECRET iCal ADDRESS — a permanent, unauthenticated, read-anything-on-that-calendar URL. The CLI reads it from STDIN precisely so it never lands in an argument list; an MCP tool argument travels through a model's context window and whatever transcript store sits behind it, so attaching a calendar stays a thing a person does at a terminal (BLI-3709) |
|
|
151
154
|
| `cockpit mail add-imap` | carries a Google app password. The CLI reads it from STDIN precisely so it never lands in an argument list; an MCP tool argument travels through a model's context window and whatever transcript store sits behind it, so attaching a mailbox stays a thing a person does at a terminal (BLI-3708) |
|
|
155
|
+
| `cockpit notes connect` | carries a notetaker API key (Fellow or Circleback). The CLI reads it from STDIN precisely so it never lands in an argument list; an MCP tool argument travels through a model's context window and whatever transcript store sits behind it, so connecting a notetaker stays a thing a person does at a terminal or in Tower's Settings (BLI-4383) |
|
|
156
|
+
| `cockpit notes webhook` | carries the VENDOR's webhook signing secret, the key an HMAC over every delivered payload is checked against. Exactly the ruling `notes connect` already carries, for the same credential shape: the CLI reads it from STDIN so it never lands in an argument list, and an MCP tool argument travels through a model's context window and whatever transcript store sits behind it (BLI-4394) |
|
|
152
157
|
|
|
153
158
|
<!-- END GENERATED verb census -->
|
|
154
159
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `notes_accounts` / `notes_detach` / `notes_sync` MCP tools (BLI-4383) — the
|
|
3
|
+
* notetaker a person connected, on the `bli-tower` server, over the same
|
|
4
|
+
* `/api/notes/accounts` doors `cockpit notes` and the Settings section call,
|
|
5
|
+
* authenticated with this machine's collector device token.
|
|
6
|
+
*
|
|
7
|
+
* ONE DELIBERATE ASYMMETRY WITH THE CLI, recorded in `verb-census.ts`:
|
|
8
|
+
* **`cockpit notes connect` has NO MCP twin.** It carries a notetaker API key,
|
|
9
|
+
* and the CLI takes that on stdin precisely so it never lands in an argument
|
|
10
|
+
* list. An MCP tool argument IS an argument list: it travels through a model's
|
|
11
|
+
* context window and whatever transcript store sits behind it. That is the
|
|
12
|
+
* same ruling `mail add-imap` and `cal add-ical` already carry, for the same
|
|
13
|
+
* credential shape, and connecting a notetaker stays a thing a person does at
|
|
14
|
+
* a terminal or in Settings.
|
|
15
|
+
*
|
|
16
|
+
* Same session discipline as `mail-tools.ts`: the session is loaded fresh per
|
|
17
|
+
* call, so a machine with no `cockpit login` pairing still serves this
|
|
18
|
+
* server's other tools and only a `notes_` account call fails, by name.
|
|
19
|
+
*/
|
|
20
|
+
import { type ToolDeps } from "./tool-result.js";
|
|
21
|
+
export type NotesAccountDeps = ToolDeps;
|
|
22
|
+
export declare function registerNotesAccountTools(server: {
|
|
23
|
+
registerTool: (...args: never[]) => unknown;
|
|
24
|
+
}, deps: NotesAccountDeps): void;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `notes_accounts` / `notes_detach` / `notes_sync` MCP tools (BLI-4383) — the
|
|
3
|
+
* notetaker a person connected, on the `bli-tower` server, over the same
|
|
4
|
+
* `/api/notes/accounts` doors `cockpit notes` and the Settings section call,
|
|
5
|
+
* authenticated with this machine's collector device token.
|
|
6
|
+
*
|
|
7
|
+
* ONE DELIBERATE ASYMMETRY WITH THE CLI, recorded in `verb-census.ts`:
|
|
8
|
+
* **`cockpit notes connect` has NO MCP twin.** It carries a notetaker API key,
|
|
9
|
+
* and the CLI takes that on stdin precisely so it never lands in an argument
|
|
10
|
+
* list. An MCP tool argument IS an argument list: it travels through a model's
|
|
11
|
+
* context window and whatever transcript store sits behind it. That is the
|
|
12
|
+
* same ruling `mail add-imap` and `cal add-ical` already carry, for the same
|
|
13
|
+
* credential shape, and connecting a notetaker stays a thing a person does at
|
|
14
|
+
* a terminal or in Settings.
|
|
15
|
+
*
|
|
16
|
+
* Same session discipline as `mail-tools.ts`: the session is loaded fresh per
|
|
17
|
+
* call, so a machine with no `cockpit login` pairing still serves this
|
|
18
|
+
* server's other tools and only a `notes_` account call fails, by name.
|
|
19
|
+
*/
|
|
20
|
+
import { z } from "zod";
|
|
21
|
+
import { callAgentDoor } from "./agent-door.js";
|
|
22
|
+
import { CONFIRM_INPUT, doorFailureText, errorResult, queryString, registrarFor, textResult, unconfirmed, withSession, } from "./tool-result.js";
|
|
23
|
+
function accountLine(account) {
|
|
24
|
+
const health = account.status === "active"
|
|
25
|
+
? "active"
|
|
26
|
+
: `${account.status}${account.status_reason ? ` (${account.status_reason})` : ""}`;
|
|
27
|
+
return `${account.label ?? account.provider} ${account.provider} ${health} last read ${account.last_synced_at ?? "never"} ${account.id}`;
|
|
28
|
+
}
|
|
29
|
+
export function registerNotesAccountTools(server, deps) {
|
|
30
|
+
const register = registrarFor(server);
|
|
31
|
+
register("notes_accounts", {
|
|
32
|
+
title: "List connected notetakers",
|
|
33
|
+
description: "Every notetaker account (Fellow or Circleback; Otter and Granola are named but not connectable) connected to "
|
|
34
|
+
+ "Tower that you may see: name, provider, workspace, health and when it was last read. Start here when you "
|
|
35
|
+
+ "need an account id. Connecting one is NOT available here on purpose — it carries an API key, so it is done "
|
|
36
|
+
+ "at a terminal with `cockpit notes connect --provider fellow --workspace <subdomain> --key-stdin` or in "
|
|
37
|
+
+ "Tower's Settings.",
|
|
38
|
+
inputSchema: {},
|
|
39
|
+
}, async () => withSession(deps, async (session) => {
|
|
40
|
+
const response = await callAgentDoor(session, deps.fetchImpl, "GET", "/api/notes/accounts");
|
|
41
|
+
if (!response.ok)
|
|
42
|
+
return errorResult(doorFailureText("notes_accounts", response));
|
|
43
|
+
const accounts = (Array.isArray(response.body.accounts) ? response.body.accounts : []);
|
|
44
|
+
return textResult(accounts.length === 0
|
|
45
|
+
? "No notetaker is connected. A person connects one with `cockpit notes connect --provider fellow --workspace <subdomain> --key-stdin`, or in Tower's Settings."
|
|
46
|
+
: `${accounts.length} notetaker(s).\n${accounts.map(accountLine).join("\n")}`, { accounts });
|
|
47
|
+
}));
|
|
48
|
+
register("notes_sync", {
|
|
49
|
+
title: "Read a notetaker now",
|
|
50
|
+
description: "Reads the meetings this notetaker has recorded since the last pass and files them as meeting notes. "
|
|
51
|
+
+ "Answers with the run's own counts and reason label, which is exactly what the ledger row says. "
|
|
52
|
+
+ "The half-hourly cron does this unasked; this is the button for when you do not want to wait.",
|
|
53
|
+
inputSchema: { account_id: z.string().uuid() },
|
|
54
|
+
}, async (args) => withSession(deps, async (session) => {
|
|
55
|
+
const response = await callAgentDoor(session, deps.fetchImpl, "POST", `/api/notes/accounts/${encodeURIComponent(String(args.account_id))}/sync`);
|
|
56
|
+
if (!response.ok)
|
|
57
|
+
return errorResult(doorFailureText("notes_sync", response));
|
|
58
|
+
const run = response.body.run;
|
|
59
|
+
return textResult(run
|
|
60
|
+
? `${run.reason}: ${run.notesAdded} stored, ${run.notesUpdated} unchanged, ${run.notesSkipped} skipped. ${run.detail}`
|
|
61
|
+
: "Tower answered without a run.", response.body);
|
|
62
|
+
}));
|
|
63
|
+
register("notes_detach", {
|
|
64
|
+
title: "Detach a notetaker",
|
|
65
|
+
description: "Removes a notetaker account and its stored key from Tower. The account itself is untouched at the provider, "
|
|
66
|
+
+ "and the meetings it already filed stay where they are. Requires confirm: true.",
|
|
67
|
+
inputSchema: { account_id: z.string().uuid(), confirm: CONFIRM_INPUT },
|
|
68
|
+
}, async (args) => withSession(deps, async (session) => {
|
|
69
|
+
const refusal = unconfirmed(args, "This would detach that notetaker and forget its stored key (the account itself is untouched at the provider, and the meetings it filed stay).");
|
|
70
|
+
if (refusal)
|
|
71
|
+
return refusal;
|
|
72
|
+
const query = new URLSearchParams({ account_id: String(args.account_id) });
|
|
73
|
+
const response = await callAgentDoor(session, deps.fetchImpl, "DELETE", `/api/notes/accounts${queryString(query)}`);
|
|
74
|
+
if (!response.ok)
|
|
75
|
+
return errorResult(doorFailureText("notes_detach", response));
|
|
76
|
+
return textResult(`Detached ${String(args.account_id)}. Its stored key went with it; the meetings it filed stay where they are.`, response.body);
|
|
77
|
+
}));
|
|
78
|
+
}
|
package/dist/server.js
CHANGED
|
@@ -12,6 +12,7 @@ import { registerDocsMsgTools } from "./docs-msg-tools.js";
|
|
|
12
12
|
import { registerJarvisTools } from "./jarvis-tools.js";
|
|
13
13
|
import { registerCalTools } from "./cal-tools.js";
|
|
14
14
|
import { registerMailTools } from "./mail-tools.js";
|
|
15
|
+
import { registerNotesAccountTools } from "./notes-account-tools.js";
|
|
15
16
|
import { registerNotesTools } from "./notes-tools.js";
|
|
16
17
|
import { registerNotesWriteTools } from "./notes-write-tools.js";
|
|
17
18
|
import { registerMemoryExperienceTools } from "./memory-experience-tools.js";
|
|
@@ -432,6 +433,10 @@ export function createServer(deps) {
|
|
|
432
433
|
// `AWAITING_TWIN` is empty as of this batch.
|
|
433
434
|
registerBriefWriteTools(server, { fetchImpl: deps.fetchImpl });
|
|
434
435
|
registerNotesWriteTools(server, { fetchImpl: deps.fetchImpl });
|
|
436
|
+
// BLI-4383: the notetaker a person connected. Connecting one is deliberately
|
|
437
|
+
// absent — it carries an API key, and a tool argument travels through a
|
|
438
|
+
// model's context window (`verb-census.ts`'s TERMINAL_ONLY says so in full).
|
|
439
|
+
registerNotesAccountTools(server, { fetchImpl: deps.fetchImpl });
|
|
435
440
|
registerSettingsWriteTools(server, { fetchImpl: deps.fetchImpl });
|
|
436
441
|
registerTeamWriteTools(server, { fetchImpl: deps.fetchImpl });
|
|
437
442
|
// BLI-3728: one search over documents, messages, issues, meeting notes and
|
package/dist/verb-census.js
CHANGED
|
@@ -269,6 +269,11 @@ export const MCP_TWINS = {
|
|
|
269
269
|
"notes unshare": { tool: "notes_unshare", door: "POST /api/notes/share (share=false)" },
|
|
270
270
|
"notes move": { tool: "notes_move", door: "POST /api/notes/move" },
|
|
271
271
|
"notes place": { tool: "notes_place", door: "POST /api/notes/place" },
|
|
272
|
+
// BLI-4383. `notes connect` is in TERMINAL_ONLY below, for the same reason
|
|
273
|
+
// `mail add-imap` and `cal add-ical` are.
|
|
274
|
+
"notes accounts": { tool: "notes_accounts", door: "GET /api/notes/accounts" },
|
|
275
|
+
"notes detach": { tool: "notes_detach", door: "DELETE /api/notes/accounts" },
|
|
276
|
+
"notes sync": { tool: "notes_sync", door: "POST /api/notes/accounts/[id]/sync" },
|
|
272
277
|
"memory log": { tool: "memory_experience", door: "POST /api/memory/experience" },
|
|
273
278
|
// BLI-4047: the memory STORE at a terminal. The twins live on the second
|
|
274
279
|
// server, `bli-memory`, over the same four doors these CLI verbs call.
|
|
@@ -350,6 +355,8 @@ export const TERMINAL_ONLY = {
|
|
|
350
355
|
"mail add-imap": "carries a Google app password. The CLI reads it from STDIN precisely so it never lands in an argument list; an MCP tool argument travels through a model's context window and whatever transcript store sits behind it, so attaching a mailbox stays a thing a person does at a terminal (BLI-3708)",
|
|
351
356
|
"cal add-ical": "carries a calendar's SECRET iCal ADDRESS — a permanent, unauthenticated, read-anything-on-that-calendar URL. The CLI reads it from STDIN precisely so it never lands in an argument list; an MCP tool argument travels through a model's context window and whatever transcript store sits behind it, so attaching a calendar stays a thing a person does at a terminal (BLI-3709)",
|
|
352
357
|
"brief edit": "opens the person's own $EDITOR on this machine and files what they changed; an agent has no editor to open (commands/editor.ts)",
|
|
358
|
+
"notes connect": "carries a notetaker API key (Fellow or Circleback). The CLI reads it from STDIN precisely so it never lands in an argument list; an MCP tool argument travels through a model's context window and whatever transcript store sits behind it, so connecting a notetaker stays a thing a person does at a terminal or in Tower's Settings (BLI-4383)",
|
|
359
|
+
"notes webhook": "carries the VENDOR's webhook signing secret, the key an HMAC over every delivered payload is checked against. Exactly the ruling `notes connect` already carries, for the same credential shape: the CLI reads it from STDIN so it never lands in an argument list, and an MCP tool argument travels through a model's context window and whatever transcript store sits behind it (BLI-4394)",
|
|
353
360
|
};
|
|
354
361
|
/**
|
|
355
362
|
* Verbs owed a twin, with who owes it. This list should only ever shrink.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bli-cockpit/mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.47",
|
|
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",
|