@bli-cockpit/mcp 0.1.105 → 0.1.106
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/server.js +3 -0
- package/dist/speed-tools.d.ts +60 -0
- package/dist/speed-tools.js +209 -0
- package/dist/tool-catalog.d.ts +1 -1
- package/dist/tool-catalog.js +1 -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
|
+
**137 of 142 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
|
|
@@ -244,6 +244,7 @@ three tables below.
|
|
|
244
244
|
| `cockpit setup` | `setup_read` | `GET /api/setup` |
|
|
245
245
|
| `cockpit slack coverage` | `slack_coverage` | `GET /api/ops/slack/coverage` |
|
|
246
246
|
| `cockpit slack read` | `slack_read` | `POST /api/ops/slack/read` |
|
|
247
|
+
| `cockpit speed` | `speed_read` | `GET /api/* (every rail page's data door, see speed-tools.ts)` |
|
|
247
248
|
| `cockpit team device doctor` | `team_device_doctor` | `POST /api/ambient/devices/[deviceId]/doctor` |
|
|
248
249
|
| `cockpit team device list` | `team_device_list` | `GET /api/team/devices` |
|
|
249
250
|
| `cockpit team device revoke` | `team_device_revoke` | `POST /api/ambient/devices/[deviceId]/revoke` |
|
package/dist/server.js
CHANGED
|
@@ -36,6 +36,7 @@ import { registerWorkTools } from "./work-tools.js";
|
|
|
36
36
|
import { registerCareersTools } from "./careers-tools.js";
|
|
37
37
|
import { registerRenameTools } from "./rename-tools.js";
|
|
38
38
|
import { registerSetupTools } from "./setup-tools.js";
|
|
39
|
+
import { registerSpeedTools } from "./speed-tools.js";
|
|
39
40
|
import { registerUsageTools } from "./usage-tools.js";
|
|
40
41
|
import { registerLogsTools } from "./logs-tools.js";
|
|
41
42
|
import { registerBrainTools } from "./brain-tools.js";
|
|
@@ -440,6 +441,8 @@ export function createServer(deps, options = {}) {
|
|
|
440
441
|
// BLI-12385: GitHub PR read, and merge / retitle / re-run behind the two-step code.
|
|
441
442
|
registerGithubTools(server, deps);
|
|
442
443
|
registerSetupTools(server, { fetchImpl: deps.fetchImpl });
|
|
444
|
+
// BLI-13033: how long each rail page's data door takes, the twin of `cockpit speed`.
|
|
445
|
+
registerSpeedTools(server, { fetchImpl: deps.fetchImpl });
|
|
443
446
|
registerUsageTools(server, { fetchImpl: deps.fetchImpl });
|
|
444
447
|
// BLI-3708: the mailboxes a person attached. Reads, a send and a sync over
|
|
445
448
|
// the same /api/mail/** doors `cockpit mail` calls; `add-imap` deliberately
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `speed_read` (BLI-13033) — the MCP twin of `cockpit speed`: how long each
|
|
3
|
+
* Tower rail page's data door takes, measured from where this server runs,
|
|
4
|
+
* with this machine's device token.
|
|
5
|
+
*
|
|
6
|
+
* This package declares no dependency on `@bli-cockpit/local-collector`, so
|
|
7
|
+
* the page list and the arithmetic below are COPIES of
|
|
8
|
+
* `cockpit-local-collector/src/commands/speed-pages.ts` and `speed-math.ts`.
|
|
9
|
+
* `speed-tools.test.ts` fails when the two page lists drift.
|
|
10
|
+
*
|
|
11
|
+
* Reads only. Every request is a plain GET on a door the browser also uses;
|
|
12
|
+
* nothing is cached on this side, so the number is Tower's, not ours.
|
|
13
|
+
*/
|
|
14
|
+
import { type ToolDeps } from "./tool-result.js";
|
|
15
|
+
export interface SpeedPage {
|
|
16
|
+
name: string;
|
|
17
|
+
label: string;
|
|
18
|
+
path: string;
|
|
19
|
+
note?: string;
|
|
20
|
+
}
|
|
21
|
+
export declare function speedPages(memoryContainer: string): SpeedPage[];
|
|
22
|
+
export declare const SPEED_PAGE_NAMES: string[];
|
|
23
|
+
export interface SpeedSample {
|
|
24
|
+
status: number | null;
|
|
25
|
+
ms: number;
|
|
26
|
+
bytes: number;
|
|
27
|
+
serverTiming: string | null;
|
|
28
|
+
failure?: string;
|
|
29
|
+
}
|
|
30
|
+
export type SpeedOutcome = "ok" | "not_permitted" | "failed";
|
|
31
|
+
export interface PageSpeed {
|
|
32
|
+
page: string;
|
|
33
|
+
label: string;
|
|
34
|
+
door: string;
|
|
35
|
+
outcome: SpeedOutcome;
|
|
36
|
+
cold_ms: number | null;
|
|
37
|
+
p50_ms: number | null;
|
|
38
|
+
p95_ms: number | null;
|
|
39
|
+
runs: number;
|
|
40
|
+
failed_runs: number;
|
|
41
|
+
http_status: number | null;
|
|
42
|
+
bytes: number | null;
|
|
43
|
+
server_timing: string | null;
|
|
44
|
+
server_total_ms: number | null;
|
|
45
|
+
reason?: string;
|
|
46
|
+
note?: string;
|
|
47
|
+
}
|
|
48
|
+
export declare function classifyStatus(status: number | null): SpeedOutcome;
|
|
49
|
+
export declare function percentile(values: readonly number[], p: number): number | null;
|
|
50
|
+
export declare function serverTotalMs(header: string | null): number | null;
|
|
51
|
+
export declare function summarizeSamples(page: {
|
|
52
|
+
name: string;
|
|
53
|
+
label: string;
|
|
54
|
+
path: string;
|
|
55
|
+
note?: string;
|
|
56
|
+
}, samples: readonly SpeedSample[]): PageSpeed;
|
|
57
|
+
export declare function rankPages(pages: readonly PageSpeed[]): PageSpeed[];
|
|
58
|
+
export declare function registerSpeedTools(server: {
|
|
59
|
+
registerTool: (...args: never[]) => unknown;
|
|
60
|
+
}, deps: ToolDeps): void;
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `speed_read` (BLI-13033) — the MCP twin of `cockpit speed`: how long each
|
|
3
|
+
* Tower rail page's data door takes, measured from where this server runs,
|
|
4
|
+
* with this machine's device token.
|
|
5
|
+
*
|
|
6
|
+
* This package declares no dependency on `@bli-cockpit/local-collector`, so
|
|
7
|
+
* the page list and the arithmetic below are COPIES of
|
|
8
|
+
* `cockpit-local-collector/src/commands/speed-pages.ts` and `speed-math.ts`.
|
|
9
|
+
* `speed-tools.test.ts` fails when the two page lists drift.
|
|
10
|
+
*
|
|
11
|
+
* Reads only. Every request is a plain GET on a door the browser also uses;
|
|
12
|
+
* nothing is cached on this side, so the number is Tower's, not ours.
|
|
13
|
+
*/
|
|
14
|
+
import { z } from "zod";
|
|
15
|
+
import { dataResult, errorResult, registrarFor, withSession } from "./tool-result.js";
|
|
16
|
+
export function speedPages(memoryContainer) {
|
|
17
|
+
return [
|
|
18
|
+
{ name: "today", label: "Today (JARVIS home)", path: "/api/jarvis/brief" },
|
|
19
|
+
{ name: "meeting-notes", label: "Meeting notes", path: "/api/notes/library" },
|
|
20
|
+
{ name: "careers", label: "Careers", path: "/api/careers/applications" },
|
|
21
|
+
{ name: "scout", label: "Scout", path: "/api/cockpit/scout" },
|
|
22
|
+
{ name: "messages", label: "Messages", path: "/api/msg/channels" },
|
|
23
|
+
{ name: "mail", label: "Mail", path: "/api/mail/inbox" },
|
|
24
|
+
{ name: "docs", label: "Docs", path: "/api/docs/tree" },
|
|
25
|
+
{
|
|
26
|
+
name: "memory",
|
|
27
|
+
label: "Memory",
|
|
28
|
+
path: `/api/memory/profile?container=${encodeURIComponent(memoryContainer)}`,
|
|
29
|
+
note: "the page reads agent_memories on the server; the profile door is the nearest read",
|
|
30
|
+
},
|
|
31
|
+
{ name: "tickets", label: "Tickets", path: "/api/work/issues" },
|
|
32
|
+
{ name: "setup", label: "Setup", path: "/api/setup" },
|
|
33
|
+
{ name: "usage", label: "Usage", path: "/api/usage/people?since=30d" },
|
|
34
|
+
{ name: "calendar", label: "Calendar", path: "/api/cal/week" },
|
|
35
|
+
{ name: "workbook", label: "Workbook", path: "/api/cockpit/workbook" },
|
|
36
|
+
{ name: "settings", label: "Settings", path: "/api/settings/personal" },
|
|
37
|
+
];
|
|
38
|
+
}
|
|
39
|
+
// END copied
|
|
40
|
+
export const SPEED_PAGE_NAMES = speedPages("x").map((page) => page.name);
|
|
41
|
+
export function classifyStatus(status) {
|
|
42
|
+
if (status === 403)
|
|
43
|
+
return "not_permitted";
|
|
44
|
+
if (status !== null && status >= 200 && status < 300)
|
|
45
|
+
return "ok";
|
|
46
|
+
return "failed";
|
|
47
|
+
}
|
|
48
|
+
export function percentile(values, p) {
|
|
49
|
+
if (values.length === 0)
|
|
50
|
+
return null;
|
|
51
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
52
|
+
const rank = Math.max(1, Math.ceil((p / 100) * sorted.length));
|
|
53
|
+
return sorted[Math.min(rank, sorted.length) - 1] ?? null;
|
|
54
|
+
}
|
|
55
|
+
export function serverTotalMs(header) {
|
|
56
|
+
if (!header)
|
|
57
|
+
return null;
|
|
58
|
+
for (const part of header.split(",")) {
|
|
59
|
+
const [name, ...params] = part.trim().split(";");
|
|
60
|
+
if (name?.trim() !== "total")
|
|
61
|
+
continue;
|
|
62
|
+
for (const param of params) {
|
|
63
|
+
const [key, value] = param.trim().split("=");
|
|
64
|
+
if (key === "dur" && value !== undefined && Number.isFinite(Number(value)))
|
|
65
|
+
return Number(value);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
const round1 = (value) => (value === null ? null : Math.round(value * 10) / 10);
|
|
71
|
+
export function summarizeSamples(page, samples) {
|
|
72
|
+
const base = { page: page.name, label: page.label, door: page.path, ...(page.note ? { note: page.note } : {}) };
|
|
73
|
+
const last = samples[samples.length - 1];
|
|
74
|
+
const good = samples.filter((sample) => classifyStatus(sample.status) === "ok");
|
|
75
|
+
const denied = samples.find((sample) => classifyStatus(sample.status) === "not_permitted");
|
|
76
|
+
if (good.length === 0) {
|
|
77
|
+
const first = samples[0];
|
|
78
|
+
const outcome = denied ? "not_permitted" : "failed";
|
|
79
|
+
return {
|
|
80
|
+
...base,
|
|
81
|
+
outcome,
|
|
82
|
+
cold_ms: null,
|
|
83
|
+
p50_ms: null,
|
|
84
|
+
p95_ms: null,
|
|
85
|
+
runs: samples.length,
|
|
86
|
+
failed_runs: denied ? 0 : samples.length,
|
|
87
|
+
http_status: (denied ?? first)?.status ?? null,
|
|
88
|
+
bytes: null,
|
|
89
|
+
server_timing: null,
|
|
90
|
+
server_total_ms: null,
|
|
91
|
+
reason: denied
|
|
92
|
+
? "not permitted for this account"
|
|
93
|
+
: (first?.failure ?? (first?.status === null || first === undefined ? "no_response" : `http_${first.status}`)),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
const cold = samples[0] && classifyStatus(samples[0].status) === "ok" ? samples[0] : null;
|
|
97
|
+
const warm = good.filter((sample) => sample !== cold);
|
|
98
|
+
const timed = (warm.length > 0 ? warm : good).map((sample) => sample.ms);
|
|
99
|
+
const lastGood = good[good.length - 1] ?? last;
|
|
100
|
+
return {
|
|
101
|
+
...base,
|
|
102
|
+
outcome: "ok",
|
|
103
|
+
cold_ms: round1(cold ? cold.ms : null),
|
|
104
|
+
p50_ms: round1(percentile(timed, 50)),
|
|
105
|
+
p95_ms: round1(percentile(timed, 95)),
|
|
106
|
+
runs: samples.length,
|
|
107
|
+
failed_runs: samples.length - good.length,
|
|
108
|
+
http_status: lastGood?.status ?? null,
|
|
109
|
+
bytes: lastGood?.bytes ?? null,
|
|
110
|
+
server_timing: lastGood?.serverTiming ?? null,
|
|
111
|
+
server_total_ms: serverTotalMs(lastGood?.serverTiming ?? null),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
export function rankPages(pages) {
|
|
115
|
+
const tier = (page) => (page.outcome === "ok" ? 0 : page.outcome === "failed" ? 1 : 2);
|
|
116
|
+
return [...pages].sort((a, b) => {
|
|
117
|
+
if (tier(a) !== tier(b))
|
|
118
|
+
return tier(a) - tier(b);
|
|
119
|
+
return (b.p50_ms ?? 0) - (a.p50_ms ?? 0) || a.page.localeCompare(b.page);
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
// END copied
|
|
123
|
+
const REQUEST_DEADLINE_MS = 60_000;
|
|
124
|
+
const MAX_RUNS = 50;
|
|
125
|
+
const DEFAULT_RUNS = 5;
|
|
126
|
+
async function timeOneRead(fetchImpl, session, page) {
|
|
127
|
+
const startedAt = performance.now();
|
|
128
|
+
const controller = new AbortController();
|
|
129
|
+
const timer = setTimeout(() => controller.abort(), REQUEST_DEADLINE_MS);
|
|
130
|
+
timer.unref?.();
|
|
131
|
+
try {
|
|
132
|
+
const response = await fetchImpl(`${session.dashboardUrl}${page.path}`, {
|
|
133
|
+
method: "GET",
|
|
134
|
+
headers: { authorization: `Bearer ${session.deviceToken}`, accept: "application/json" },
|
|
135
|
+
signal: controller.signal,
|
|
136
|
+
});
|
|
137
|
+
const bytes = (await response.arrayBuffer()).byteLength;
|
|
138
|
+
return {
|
|
139
|
+
status: response.status,
|
|
140
|
+
ms: performance.now() - startedAt,
|
|
141
|
+
bytes,
|
|
142
|
+
serverTiming: response.headers.get("server-timing"),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
const aborted = error instanceof Error && (error.name === "AbortError" || error.name === "TimeoutError");
|
|
147
|
+
return {
|
|
148
|
+
status: null,
|
|
149
|
+
ms: performance.now() - startedAt,
|
|
150
|
+
bytes: 0,
|
|
151
|
+
serverTiming: null,
|
|
152
|
+
failure: aborted ? "turn_timed_out" : "gateway_unreachable",
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
finally {
|
|
156
|
+
clearTimeout(timer);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
export function registerSpeedTools(server, deps) {
|
|
160
|
+
registrarFor(server)("speed_read", {
|
|
161
|
+
title: "Time every Tower page's data door",
|
|
162
|
+
description: "How long each Tower rail page's data door takes, measured from where this server runs with this machine's "
|
|
163
|
+
+ "device token: per page the first (cold) run, p50 and p95 over the warm runs, HTTP status, response bytes and "
|
|
164
|
+
+ "the door's own Server-Timing total, slowest first. A page the account may not open reads `not_permitted`, "
|
|
165
|
+
+ "not a failure. Times the data door, not the page's HTML or paint. Read only. Twin of `cockpit speed`.",
|
|
166
|
+
inputSchema: {
|
|
167
|
+
pages: z.array(z.enum(SPEED_PAGE_NAMES)).max(14).optional()
|
|
168
|
+
.describe("Only these pages. Omit for every page."),
|
|
169
|
+
runs: z.number().int().min(1).max(MAX_RUNS).optional().describe("Requests per page (default 5)."),
|
|
170
|
+
container: z.string().min(1).max(200).optional()
|
|
171
|
+
.describe("The memory container tag to time the Memory page with. Without it the Memory page is left out."),
|
|
172
|
+
},
|
|
173
|
+
}, async (args) => withSession(deps, async (session) => {
|
|
174
|
+
const runs = typeof args.runs === "number" ? args.runs : DEFAULT_RUNS;
|
|
175
|
+
const asked = Array.isArray(args.pages) ? args.pages : [];
|
|
176
|
+
const container = typeof args.container === "string" ? args.container : null;
|
|
177
|
+
const skipped = [];
|
|
178
|
+
const chosen = speedPages(container ?? "-").filter((page) => {
|
|
179
|
+
if (asked.length > 0 && !asked.includes(page.name))
|
|
180
|
+
return false;
|
|
181
|
+
if (page.name === "memory" && container === null) {
|
|
182
|
+
skipped.push("memory");
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
return true;
|
|
186
|
+
});
|
|
187
|
+
if (chosen.length === 0) {
|
|
188
|
+
return errorResult("speed_read has nothing to time: the Memory page needs a `container` tag.");
|
|
189
|
+
}
|
|
190
|
+
const results = [];
|
|
191
|
+
for (const page of chosen) {
|
|
192
|
+
const samples = [];
|
|
193
|
+
for (let run = 0; run < runs; run += 1) {
|
|
194
|
+
const sample = await timeOneRead(deps.fetchImpl, session, page);
|
|
195
|
+
samples.push(sample);
|
|
196
|
+
if (classifyStatus(sample.status) === "not_permitted")
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
results.push(summarizeSamples(page, samples));
|
|
200
|
+
}
|
|
201
|
+
const ranked = rankPages(results);
|
|
202
|
+
const failed = ranked.filter((page) => page.outcome === "failed").length;
|
|
203
|
+
const lines = ranked.map((page, index) => page.outcome === "ok"
|
|
204
|
+
? `${index + 1}. ${page.page} cold ${Math.round(page.cold_ms ?? 0)} ms p50 ${Math.round(page.p50_ms ?? 0)} ms p95 ${Math.round(page.p95_ms ?? 0)} ms HTTP ${page.http_status} ${page.bytes} B${page.server_total_ms === null ? "" : ` server ${Math.round(page.server_total_ms)} ms`}`
|
|
205
|
+
: `${index + 1}. ${page.page} ${page.outcome === "not_permitted" ? "not permitted" : `FAILED (${page.reason})`}`);
|
|
206
|
+
return dataResult(`Tower page speed, slowest first (${runs} run${runs === 1 ? "" : "s"} per page; first is cold, p50/p95 over warm runs):\n${lines.join("\n")}`
|
|
207
|
+
+ (skipped.length ? `\nLeft out: ${skipped.join(", ")} (pass container).` : ""), { ok: failed === 0, runs, pages: ranked, skipped });
|
|
208
|
+
}));
|
|
209
|
+
}
|
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_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", "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", "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"];
|
|
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).
|
package/dist/tool-catalog.js
CHANGED
package/dist/verb-census.js
CHANGED
|
@@ -242,6 +242,8 @@ export const MCP_TWINS = {
|
|
|
242
242
|
"brain status": { tool: "brain_status", door: "GET /api/brain/incidents" },
|
|
243
243
|
"usage sessions": { tool: "usage_sessions", door: "GET /api/usage/sessions" },
|
|
244
244
|
"setup": { tool: "setup_read", door: "GET /api/setup" },
|
|
245
|
+
// BLI-13033: times the data door of every rail page; the twin runs the same GETs.
|
|
246
|
+
"speed": { tool: "speed_read", door: "GET /api/* (every rail page's data door, see speed-tools.ts)" },
|
|
245
247
|
"usage people": { tool: "usage_people", door: "GET /api/usage/people" },
|
|
246
248
|
"glossary list": { tool: "glossary_list", door: "GET /api/glossary" },
|
|
247
249
|
"glossary add": { tool: "glossary_add", door: "POST /api/glossary" },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bli-cockpit/mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.106",
|
|
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",
|