@bli-cockpit/mcp 0.1.89 → 0.1.94
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 +1 -1
- package/dist/logs-tools.d.ts +8 -1
- package/dist/logs-tools.js +53 -8
- package/dist/server.js +1 -1
- package/dist/verb-census.js +2 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -169,7 +169,7 @@ three tables below.
|
|
|
169
169
|
| `cockpit issue update` | `work_update_issue` | `PATCH /api/work/issues/[id]` |
|
|
170
170
|
| `cockpit jarvis` | `jarvis_ask` | `POST /api/jarvis/cli` |
|
|
171
171
|
| `cockpit jarvis --trace` | `jarvis_trace` | `GET /api/ops/trace/[id]` |
|
|
172
|
-
| `cockpit logs` | `logs_digest` | `LOCAL @bli-cockpit/telemetry-core/log-digest (this machine's logs;
|
|
172
|
+
| `cockpit logs` | `logs_digest` | `LOCAL @bli-cockpit/telemetry-core/log-digest (this machine's logs); `project` reads GET /api/logs (the stored digests)` |
|
|
173
173
|
| `cockpit mail accounts` | `mail_accounts` | `GET /api/mail/accounts` |
|
|
174
174
|
| `cockpit mail attachment` | `mail_attachment` | `GET /api/mail/attachments/[id]?meta=1` |
|
|
175
175
|
| `cockpit mail detach` | `mail_detach` | `DELETE /api/mail/accounts` |
|
package/dist/logs-tools.d.ts
CHANGED
|
@@ -13,11 +13,18 @@
|
|
|
13
13
|
* as `text`. A CLI that is missing or not logged in is a named refusal
|
|
14
14
|
* (`vercel_cli_missing`, `railway_not_logged_in`), never a stack trace.
|
|
15
15
|
*
|
|
16
|
+
* `project` (BLI-12855) reads the digests Tower's own 15-minute cron STORED for a
|
|
17
|
+
* BLI project (`kolm`, `tower`, ...) through the dashboard's `GET /api/logs` door,
|
|
18
|
+
* the same door `cockpit logs <project>` calls: no `vercel` or `railway` login, no
|
|
19
|
+
* local file. A `dashboard` or `railway` read whose local CLI is missing or logged
|
|
20
|
+
* out falls back to the stored digest of project `tower` and says so.
|
|
21
|
+
*
|
|
16
22
|
* Not on the hosted server: it reads the machine's files and spawns programs
|
|
17
23
|
* there (`REMOTE_EXCLUDED_TOOLS`).
|
|
18
24
|
*/
|
|
19
25
|
import { digestSource, type DigestOutcome } from "@bli-cockpit/telemetry-core/log-digest";
|
|
20
|
-
|
|
26
|
+
import { type ToolDeps } from "./tool-result.js";
|
|
27
|
+
export interface LogsToolDeps extends Partial<ToolDeps> {
|
|
21
28
|
/** Test seam: replaces the digest. */
|
|
22
29
|
digest?: (...args: Parameters<typeof digestSource>) => Promise<DigestOutcome>;
|
|
23
30
|
/** Test seam: where the collector's state lives; defaults to this machine's. */
|
package/dist/logs-tools.js
CHANGED
|
@@ -13,19 +13,27 @@
|
|
|
13
13
|
* as `text`. A CLI that is missing or not logged in is a named refusal
|
|
14
14
|
* (`vercel_cli_missing`, `railway_not_logged_in`), never a stack trace.
|
|
15
15
|
*
|
|
16
|
+
* `project` (BLI-12855) reads the digests Tower's own 15-minute cron STORED for a
|
|
17
|
+
* BLI project (`kolm`, `tower`, ...) through the dashboard's `GET /api/logs` door,
|
|
18
|
+
* the same door `cockpit logs <project>` calls: no `vercel` or `railway` login, no
|
|
19
|
+
* local file. A `dashboard` or `railway` read whose local CLI is missing or logged
|
|
20
|
+
* out falls back to the stored digest of project `tower` and says so.
|
|
21
|
+
*
|
|
16
22
|
* Not on the hosted server: it reads the machine's files and spawns programs
|
|
17
23
|
* there (`REMOTE_EXCLUDED_TOOLS`).
|
|
18
24
|
*/
|
|
19
25
|
import os from "node:os";
|
|
20
|
-
import { digestSource, routineText } from "@bli-cockpit/telemetry-core/log-digest";
|
|
26
|
+
import { LOCAL_ACCESS_REFUSALS, LOG_PROJECT_PATTERN, digestSource, routineText } from "@bli-cockpit/telemetry-core/log-digest";
|
|
21
27
|
import { getUserLocalCockpitPaths } from "@bli-cockpit/telemetry-core";
|
|
22
28
|
import { z } from "zod";
|
|
23
|
-
import {
|
|
29
|
+
import { callAgentDoor } from "./agent-door.js";
|
|
30
|
+
import { doorFailureText, errorResult, registrarFor, textResult, withSession } from "./tool-result.js";
|
|
24
31
|
/** Past this the structured half would crowd the 32 KB per-half read budget. */
|
|
25
32
|
const MAX_TEMPLATES_RETURNED = 40;
|
|
26
33
|
const MAX_TEXT_CHARS = 256_000;
|
|
27
34
|
export function registerLogsTools(server, deps = {}) {
|
|
28
35
|
const digest = deps.digest ?? digestSource;
|
|
36
|
+
const door = { fetchImpl: deps.fetchImpl ?? fetch, ...(deps.loadSession ? { loadSession: deps.loadSession } : {}) };
|
|
29
37
|
registrarFor(server)("logs_digest", {
|
|
30
38
|
title: "Digest Tower's logs (about 2 KB)",
|
|
31
39
|
description: "A token-cheap digest of Tower's own logs, read on THIS machine and never uploaded. Use it BEFORE reading raw logs: a 15-minute window of a "
|
|
@@ -35,30 +43,67 @@ export function registerLogsTools(server, deps = {}) {
|
|
|
35
43
|
+ "(this machine's sync.log and sync.err.log), `dashboard` (Vercel runtime logs through the `vercel` CLI), `railway` (every Tower service) "
|
|
36
44
|
+ "or `railway:<service>` (through the `railway` CLI), `-` (lines passed as `text`). `since` is 15m, 1h, 2d or an ISO time (default 15m). "
|
|
37
45
|
+ "Pass `raw` (an 8-hex template id from a digest line) to get the original lines behind that template, redacted, newest last. A missing or "
|
|
38
|
-
+ "logged-out CLI is a named refusal (vercel_cli_missing, vercel_not_logged_in, railway_cli_missing, railway_not_logged_in)
|
|
46
|
+
+ "logged-out CLI is a named refusal (vercel_cli_missing, vercel_not_logged_in, railway_cli_missing, railway_not_logged_in), except that "
|
|
47
|
+
+ "`dashboard` and `railway` then fall back to the digest Tower STORED. Pass `project` (tower, kolm, portpal, pearl-portal, triage-crm, "
|
|
48
|
+
+ "triage-ocr, blue-pearl-website, kaiben, black-knight-tire, sublimation-squad, bli-website) instead of `source` to read what Tower's "
|
|
49
|
+
+ "15-minute cron stored from that project's Vercel, Railway and Supabase logs, merged over `since` (default 24h, at most 30d), no login "
|
|
50
|
+
+ "needed. A line `NOT READ: ...` and the structured `still_refusing` ([{source, reason, since, last_seen}]) name every source whose newest "
|
|
51
|
+
+ "stored row is a refusal, however old, plus `not_wired`: a digest with ERROR 0 and a NOT READ line is a blind spot, never all clear. A stored "
|
|
52
|
+
+ "digest keeps no raw line, so `raw` does not apply. `cockpit logs`, same code.",
|
|
39
53
|
inputSchema: {
|
|
40
|
-
source: z.string().min(1).max(80).describe("collector, dashboard, railway, railway:<service> or - (lines in `text`)."),
|
|
54
|
+
source: z.string().min(1).max(80).optional().describe("collector, dashboard, railway, railway:<service> or - (lines in `text`). Omit when `project` is given."),
|
|
55
|
+
project: z.string().regex(LOG_PROJECT_PATTERN).optional().describe("A BLI project id: read the digest Tower's cron stored for it (no local CLI needed). Takes `since`; `raw` does not apply."),
|
|
41
56
|
since: z.string().min(1).max(40).optional().describe("Window: 15m, 1h, 2d or an ISO time. Default 15m."),
|
|
42
57
|
raw: z.string().regex(/^[0-9a-f]{8}$/).optional().describe("A template id from a digest line: return the original lines behind it."),
|
|
43
58
|
limit: z.number().int().min(1).max(200).optional().describe("With raw: how many original lines. Default 20."),
|
|
44
59
|
all_levels: z.boolean().optional().describe("dashboard only: also read info-only requests (slow, minutes)."),
|
|
45
60
|
text: z.string().max(MAX_TEXT_CHARS).optional().describe("The lines to digest when source is -."),
|
|
46
61
|
},
|
|
47
|
-
}, async (args) => answer(digest, deps, args));
|
|
62
|
+
}, async (args) => answer(digest, deps, door, args));
|
|
63
|
+
}
|
|
64
|
+
/** The stored digest of a project, through `GET /api/logs` with this machine's device token. */
|
|
65
|
+
async function storedAnswer(door, project, since, fallbackReason) {
|
|
66
|
+
return withSession(door, async (session) => {
|
|
67
|
+
const query = new URLSearchParams({ project });
|
|
68
|
+
if (since)
|
|
69
|
+
query.set("since", since);
|
|
70
|
+
const response = await callAgentDoor(session, door.fetchImpl, "GET", `/api/logs?${query.toString()}`);
|
|
71
|
+
if (!response.ok)
|
|
72
|
+
return errorResult(doorFailureText("logs_digest", response));
|
|
73
|
+
const body = response.body;
|
|
74
|
+
const text = typeof body["text"] === "string" ? body["text"] : "";
|
|
75
|
+
return textResult(fallbackReason ? `(no local access: ${fallbackReason}; this is the digest Tower stored)\n${text}` : text, {
|
|
76
|
+
...body,
|
|
77
|
+
...(fallbackReason ? { fallback_from: fallbackReason } : {}),
|
|
78
|
+
});
|
|
79
|
+
});
|
|
48
80
|
}
|
|
49
|
-
async function answer(digest, deps, args) {
|
|
50
|
-
const
|
|
81
|
+
async function answer(digest, deps, door, args) {
|
|
82
|
+
const since = typeof args["since"] === "string" ? args["since"] : undefined;
|
|
83
|
+
if (typeof args["project"] === "string") {
|
|
84
|
+
if (typeof args["raw"] === "string")
|
|
85
|
+
return errorResult("logs_digest refused (raw_not_stored): a stored digest keeps no raw line; raw reads a local source.");
|
|
86
|
+
return storedAnswer(door, args["project"], since);
|
|
87
|
+
}
|
|
88
|
+
if (typeof args["source"] !== "string")
|
|
89
|
+
return errorResult("logs_digest refused (source_or_project_required): pass `source` (a local log) or `project` (a stored digest).");
|
|
90
|
+
const source = args["source"];
|
|
51
91
|
const text = typeof args["text"] === "string" ? args["text"] : undefined;
|
|
52
92
|
if (source === "-" && !text) {
|
|
53
93
|
return errorResult('logs_digest refused (stdin_empty): source "-" digests the lines you pass in `text`; none were given.');
|
|
54
94
|
}
|
|
55
95
|
const outcome = await digest({
|
|
56
96
|
source,
|
|
57
|
-
since
|
|
97
|
+
since,
|
|
58
98
|
raw: typeof args["raw"] === "string" ? args["raw"] : undefined,
|
|
59
99
|
rawLimit: typeof args["limit"] === "number" ? args["limit"] : undefined,
|
|
60
100
|
allLevels: args["all_levels"] === true,
|
|
61
101
|
}, { stateDir: deps.stateDir ?? getUserLocalCockpitPaths(os.homedir()).state_dir, stdinText: text, stdinIsTerminal: false });
|
|
102
|
+
if (!outcome.ok && LOCAL_ACCESS_REFUSALS.has(outcome.reason) && (source === "dashboard" || source === "railway") && typeof args["raw"] !== "string") {
|
|
103
|
+
const stored = await storedAnswer(door, "tower", since, outcome.reason);
|
|
104
|
+
// No pairing either: the local refusal is the honest answer.
|
|
105
|
+
return stored.isError ? errorResult(`logs_digest refused (${outcome.reason}): ${outcome.detail} The stored digest could not be read either: ${stored.content[0]?.text ?? "unknown"}`) : stored;
|
|
106
|
+
}
|
|
62
107
|
if (!outcome.ok)
|
|
63
108
|
return errorResult(`logs_digest refused (${outcome.reason}): ${outcome.detail}`);
|
|
64
109
|
if (outcome.kind === "raw") {
|
package/dist/server.js
CHANGED
|
@@ -503,6 +503,6 @@ export function createServer(deps, options = {}) {
|
|
|
503
503
|
registerSourcesTools(server);
|
|
504
504
|
// BLI-12833: a token-cheap digest of Tower's own logs, read on this machine
|
|
505
505
|
// with the shared `@bli-cockpit/telemetry-core/log-digest`. Read-only; not on the hosted server.
|
|
506
|
-
registerLogsTools(server);
|
|
506
|
+
registerLogsTools(server, { fetchImpl: deps.fetchImpl });
|
|
507
507
|
return server;
|
|
508
508
|
}
|
package/dist/verb-census.js
CHANGED
|
@@ -241,7 +241,8 @@ export const MCP_TWINS = {
|
|
|
241
241
|
"github rerun": { tool: "github_rerun", door: "POST /api/github/pull-request (rerun)" },
|
|
242
242
|
// BLI-12833: a local read, not a Tower door: it runs the shared log digest over
|
|
243
243
|
// this machine's collector logs and the `vercel` / `railway` CLIs.
|
|
244
|
-
|
|
244
|
+
// BLI-12855: a project name (or --stored, or a local CLI that is missing) reads the digests Tower's cron stored, through GET /api/logs.
|
|
245
|
+
"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
246
|
"usage sessions": { tool: "usage_sessions", door: "GET /api/usage/sessions" },
|
|
246
247
|
"usage people": { tool: "usage_people", door: "GET /api/usage/people" },
|
|
247
248
|
"docs list": { tool: "docs_list", door: "GET /api/docs/documents" },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bli-cockpit/mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.94",
|
|
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",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"readme": "npm run build && node scripts/write-readme-census.mjs"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@bli-cockpit/telemetry-core": "0.1.
|
|
40
|
+
"@bli-cockpit/telemetry-core": "0.1.64",
|
|
41
41
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
42
42
|
"zod": "^4.3.6"
|
|
43
43
|
},
|