@bli-cockpit/mcp 0.1.12 → 0.1.13

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
@@ -367,7 +367,13 @@ The server resolves an access_token before every authenticated request using the
367
367
  1. `BLI_OPERATOR_TOKEN` (verbatim, no refresh) — CI path.
368
368
  2. Session helper — shells out to `node scripts/bli-event-session.mjs get-token`, which reads `~/.config/bli-event/session.json` and refreshes the access_token when near expiry. Run `bli-event login` once on your laptop to create the session. Tokens are cached in-process for 30s to avoid spawning a subprocess on every tool call.
369
369
 
370
- If both paths are unavailable (no env override, and no session file on disk), the server exits at startup with an actionable error.
370
+ If both paths are unavailable (no env override, and no session file on disk),
371
+ the server **still starts** and only those three tools refuse, by name, when
372
+ called — every device-token tool is unaffected (BLI-3804). It used to exit at
373
+ startup, which meant a machine that installed the published package registered
374
+ a `bli-tower` server with NO tools at all: `scripts/bli-event-session.mjs`
375
+ exists in the bli-cockpit repo and nowhere else. `src/startup-auth.ts` carries
376
+ the account.
371
377
 
372
378
  ## Install
373
379
 
@@ -453,10 +459,68 @@ The server surfaces API error codes from the canonical catalog
453
459
  | `BLI-E403` | Authenticated caller cannot access the requested ticket. |
454
460
  | `BLI-W003` | Idempotency replay (informational; not an error). |
455
461
 
462
+ ## The smoke — does this server actually answer? (BLI-3804)
463
+
464
+ The unit suites prove each tool's shape against an injected fetch, and
465
+ `verb-census.test.ts` proves a twin EXISTS for every Tower CLI verb. Neither
466
+ starts a server, opens a transport or reaches Tower, which is how five QA ticks
467
+ (`docs/reports/jarvis-qa-loop/tick15.md` … `tick19.md`) recorded these tools as
468
+ untested end to end while the published server was exiting at startup.
469
+
470
+ ```bash
471
+ npm run mcp:smoke --workspace=@bli-cockpit/mcp # all six families
472
+ npm run mcp:smoke --workspace=@bli-cockpit/mcp -- --skip jarvis_ask # no model spend
473
+ npm run mcp:smoke --workspace=@bli-cockpit/mcp -- --only docs_list --json
474
+ ```
475
+
476
+ It builds the server, spawns `dist/index.js` over stdio the way an agent host
477
+ does, lists the tools, and calls one cheap READ per family — `docs_list`
478
+ (limit 1), `msg_channels`, `work_list_issues` (limit 1), `cal_calendars`,
479
+ `mail_accounts`, `jarvis_ask` — never a write, never a dispatch. The device
480
+ token is never passed to it: the server reads
481
+ `~/.config/bli-cockpit/session.json` itself, so the smoke exercises the real
482
+ auth path. Output is one line per tool — name, verdict, the door's own reason
483
+ label, milliseconds:
484
+
485
+ ```
486
+ bli-tower smoke — 78 tools registered, 6 called
487
+ tool verdict reason ms
488
+ docs_list ok answered 719
489
+ msg_channels ok answered 795
490
+ work_list_issues ok answered 760
491
+ cal_calendars ok answered 762
492
+ mail_accounts ok answered 478
493
+ jarvis_ask ok answered 6483
494
+ ```
495
+
496
+ **A refusal by name is a pass.** The question is whether the server answers,
497
+ not whether this account has a mailbox: `cal_calendars` and `mail_accounts`
498
+ answering "nothing is attached" is a healthy machine. The exit code turns
499
+ non-zero only when something never answered — the server would not start, the
500
+ transport broke, a call timed out, a tool was not registered, or Tower could
501
+ not be reached.
502
+
503
+ `--server <path>` points it at an INSTALLED server instead of this checkout's
504
+ build, which matters more than it sounds: the repo build resolves a credential
505
+ the published install cannot, so a green smoke here has never proven the
506
+ fleet's server starts.
507
+
508
+ ```bash
509
+ npm run mcp:smoke --workspace=@bli-cockpit/mcp -- \
510
+ --server "$(npm root -g)/@bli-cockpit/cli/node_modules/@bli-cockpit/mcp/dist/index.js"
511
+ ```
512
+
513
+ On a laptop, `cockpit doctor`'s `mcp-answers` row asks the same first question
514
+ of the REGISTERED server every run
515
+ (`packages/cockpit-local-collector/src/commands/doctor-mcp.ts`), so a machine
516
+ whose MCP is registered and not answering says so without anyone running this
517
+ script.
518
+
456
519
  ## Development
457
520
 
458
521
  ```bash
459
- npm run typecheck --workspace=@bli-cockpit/mcp
522
+ npm run typecheck --workspace=@bli-cockpit/mcp # also typechecks scripts/
460
523
  npm test --workspace=@bli-cockpit/mcp
461
524
  npm run build --workspace=@bli-cockpit/mcp
525
+ npm run mcp:smoke --workspace=@bli-cockpit/mcp # needs a paired machine
462
526
  ```
package/dist/index.d.ts CHANGED
@@ -1,18 +1,22 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * @bli-cockpit/mcp — MCP server adapter for the BLI Cockpit event stream.
3
+ * @bli-cockpit/mcp — `bli-tower`, the MCP entry point.
4
4
  *
5
- * Exposes three tools backed by the canonical REST contract at
6
- * `POST /api/events/emit` (see docs/plans/cockpit-agent-ops-control-plane.md §6).
5
+ * TWO credentials, and only one of them is needed to serve (BLI-3804):
7
6
  *
8
- * Auth (precedence order, matches the `bli-event` bash CLI):
9
- * 1. `BLI_OPERATOR_TOKEN` env var — used verbatim (CI / override).
10
- * 2. Otherwise: shell out to `node scripts/bli-event-session.mjs get-token`
11
- * which reads `~/.config/bli-event/session.json`, refreshes the
12
- * access_token if near expiry, and prints a fresh token on stdout.
13
- * Set `BLI_SESSION_HELPER` to an absolute path if the helper can't be
14
- * discovered by walking up from this module's location.
7
+ * * Every agent-door tool `docs_*`, `msg_*`, `work_*`, `mail_*`, `cal_*`,
8
+ * `jarvis_*`, `brief_*`, `notes_*`, `ops_*`, `settings_*`, `scout_*`,
9
+ * `search`, `workbook_*` reads this machine's COLLECTOR DEVICE TOKEN
10
+ * per call from `~/.config/bli-cockpit/session.json`
11
+ * (`agent-door-session.ts`). Nothing about it is resolved here.
12
+ * * `emit_event` / `get_ticket_timeline` / `get_active_tickets` the legacy
13
+ * event stream need `BLI_OPERATOR_TOKEN`, or `scripts/bli-event-session.mjs`
14
+ * found by walking up from this module (set `BLI_SESSION_HELPER` to point
15
+ * at it directly).
15
16
  *
16
- * If neither path is available, the server prints a clear error and exits.
17
+ * When the second one is absent the server STILL STARTS and those three tools
18
+ * refuse by name when called. It used to exit 1 before connecting the
19
+ * transport, which meant no tools at all on every machine outside the repo —
20
+ * `startup-auth.ts` carries the full account.
17
21
  */
18
22
  export {};
package/dist/index.js CHANGED
@@ -1,42 +1,38 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * @bli-cockpit/mcp — MCP server adapter for the BLI Cockpit event stream.
3
+ * @bli-cockpit/mcp — `bli-tower`, the MCP entry point.
4
4
  *
5
- * Exposes three tools backed by the canonical REST contract at
6
- * `POST /api/events/emit` (see docs/plans/cockpit-agent-ops-control-plane.md §6).
5
+ * TWO credentials, and only one of them is needed to serve (BLI-3804):
7
6
  *
8
- * Auth (precedence order, matches the `bli-event` bash CLI):
9
- * 1. `BLI_OPERATOR_TOKEN` env var — used verbatim (CI / override).
10
- * 2. Otherwise: shell out to `node scripts/bli-event-session.mjs get-token`
11
- * which reads `~/.config/bli-event/session.json`, refreshes the
12
- * access_token if near expiry, and prints a fresh token on stdout.
13
- * Set `BLI_SESSION_HELPER` to an absolute path if the helper can't be
14
- * discovered by walking up from this module's location.
7
+ * * Every agent-door tool `docs_*`, `msg_*`, `work_*`, `mail_*`, `cal_*`,
8
+ * `jarvis_*`, `brief_*`, `notes_*`, `ops_*`, `settings_*`, `scout_*`,
9
+ * `search`, `workbook_*` reads this machine's COLLECTOR DEVICE TOKEN
10
+ * per call from `~/.config/bli-cockpit/session.json`
11
+ * (`agent-door-session.ts`). Nothing about it is resolved here.
12
+ * * `emit_event` / `get_ticket_timeline` / `get_active_tickets` the legacy
13
+ * event stream need `BLI_OPERATOR_TOKEN`, or `scripts/bli-event-session.mjs`
14
+ * found by walking up from this module (set `BLI_SESSION_HELPER` to point
15
+ * at it directly).
15
16
  *
16
- * If neither path is available, the server prints a clear error and exits.
17
+ * When the second one is absent the server STILL STARTS and those three tools
18
+ * refuse by name when called. It used to exit 1 before connecting the
19
+ * transport, which meant no tools at all on every machine outside the repo —
20
+ * `startup-auth.ts` carries the full account.
17
21
  */
18
22
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
19
23
  import { createServer } from "./server.js";
20
- import { createDefaultTokenResolver } from "./token-resolver.js";
24
+ import { resolveStartupAuth } from "./startup-auth.js";
21
25
  async function main() {
22
26
  const baseUrl = process.env.BLI_API_BASE_URL ?? "http://127.0.0.1:3100";
23
- let resolver;
24
- let sourceLabel;
25
- try {
26
- const result = createDefaultTokenResolver({ env: process.env });
27
- resolver = result.resolver;
28
- sourceLabel =
29
- result.source === "env"
30
- ? "BLI_OPERATOR_TOKEN env var"
31
- : `session helper at ${result.helperPath}`;
32
- }
33
- catch (err) {
34
- process.stderr.write(`[bli-cockpit-mcp] ${err instanceof Error ? err.message : String(err)}\n`);
35
- process.exit(1);
36
- }
37
- process.stderr.write(`[bli-cockpit-mcp] auth source: ${sourceLabel}\n`);
27
+ // BLI-3804: a missing LEGACY credential degrades three tools, it does not
28
+ // kill the server. This used to `process.exit(1)` before the transport was
29
+ // connected, so every machine that installed the published package — where
30
+ // `scripts/bli-event-session.mjs` does not exist registered no `bli-tower`
31
+ // tools at all. See `startup-auth.ts` for the whole story.
32
+ const auth = resolveStartupAuth({ env: process.env });
33
+ process.stderr.write(`[bli-cockpit-mcp] auth source: ${auth.sourceLabel}\n`);
38
34
  const deps = {
39
- resolveToken: resolver,
35
+ resolveToken: auth.resolver,
40
36
  baseUrl,
41
37
  fetchImpl: globalThis.fetch,
42
38
  };
@@ -0,0 +1,44 @@
1
+ /**
2
+ * What this server does when the LEGACY event-stream credential is missing
3
+ * (BLI-3804).
4
+ *
5
+ * Two auth paths live on this one server and they are unrelated
6
+ * (`agent-door-session.ts` says why): every `docs_*`, `msg_*`, `work_*`,
7
+ * `mail_*`, `cal_*`, `jarvis_*`, `brief_*`, `notes_*`, `ops_*`, `settings_*`,
8
+ * `scout_*`, `search` and `workbook_*` tool — 78 of the 81 — authenticates
9
+ * with this machine's COLLECTOR DEVICE TOKEN, read per call from
10
+ * `~/.config/bli-cockpit/session.json`. Only `emit_event`,
11
+ * `get_ticket_timeline` and `get_active_tickets` need `BLI_OPERATOR_TOKEN` or
12
+ * the `bli-event-session.mjs` helper.
13
+ *
14
+ * Until this module existed, `index.ts` resolved that legacy credential at
15
+ * STARTUP and called `process.exit(1)` when it could not — before the
16
+ * transport was connected and before a single tool was registered. The helper
17
+ * is discovered by walking up from this module's own location looking for
18
+ * `scripts/bli-event-session.mjs`, which exists in the bli-cockpit repo and
19
+ * nowhere else, so on every machine that installed the published package the
20
+ * server died at startup and the agent host reported no `bli-tower` tools at
21
+ * all. Proven 2026-09-06 against the registered bin on the reference Mac:
22
+ * `[bli-cockpit-mcp] No auth available: …`, exit 1. Five QA ticks
23
+ * (`docs/reports/jarvis-qa-loop/tick15.md` … `tick19.md`) recorded the
24
+ * symptom — "no `mcp__bli-tower__*` in this session" — as a client-side gap.
25
+ *
26
+ * `server.ts` already stated the intended behaviour in a comment beside the
27
+ * agent-door registrations: "Registered unconditionally — … a machine with no
28
+ * `cockpit login` pairing yet still serves the three event tools; only a
29
+ * docs/msg call on that machine fails, by name." This module makes the
30
+ * converse true as well: a machine with no LEGACY operator token still serves
31
+ * every agent-door tool, and only the three event-stream tools fail — by name,
32
+ * with the reason, when they are called.
33
+ */
34
+ import type { TokenResolver } from "./token-resolver.js";
35
+ export interface StartupAuth {
36
+ resolver: TokenResolver;
37
+ /** One line for stderr. Says which half of the server is live and which is not. */
38
+ sourceLabel: string;
39
+ /** `true` when only the three legacy event-stream tools are unavailable. */
40
+ legacyToolsDegraded: boolean;
41
+ }
42
+ export declare function resolveStartupAuth(options: {
43
+ env: NodeJS.ProcessEnv;
44
+ }): StartupAuth;
@@ -0,0 +1,61 @@
1
+ /**
2
+ * What this server does when the LEGACY event-stream credential is missing
3
+ * (BLI-3804).
4
+ *
5
+ * Two auth paths live on this one server and they are unrelated
6
+ * (`agent-door-session.ts` says why): every `docs_*`, `msg_*`, `work_*`,
7
+ * `mail_*`, `cal_*`, `jarvis_*`, `brief_*`, `notes_*`, `ops_*`, `settings_*`,
8
+ * `scout_*`, `search` and `workbook_*` tool — 78 of the 81 — authenticates
9
+ * with this machine's COLLECTOR DEVICE TOKEN, read per call from
10
+ * `~/.config/bli-cockpit/session.json`. Only `emit_event`,
11
+ * `get_ticket_timeline` and `get_active_tickets` need `BLI_OPERATOR_TOKEN` or
12
+ * the `bli-event-session.mjs` helper.
13
+ *
14
+ * Until this module existed, `index.ts` resolved that legacy credential at
15
+ * STARTUP and called `process.exit(1)` when it could not — before the
16
+ * transport was connected and before a single tool was registered. The helper
17
+ * is discovered by walking up from this module's own location looking for
18
+ * `scripts/bli-event-session.mjs`, which exists in the bli-cockpit repo and
19
+ * nowhere else, so on every machine that installed the published package the
20
+ * server died at startup and the agent host reported no `bli-tower` tools at
21
+ * all. Proven 2026-09-06 against the registered bin on the reference Mac:
22
+ * `[bli-cockpit-mcp] No auth available: …`, exit 1. Five QA ticks
23
+ * (`docs/reports/jarvis-qa-loop/tick15.md` … `tick19.md`) recorded the
24
+ * symptom — "no `mcp__bli-tower__*` in this session" — as a client-side gap.
25
+ *
26
+ * `server.ts` already stated the intended behaviour in a comment beside the
27
+ * agent-door registrations: "Registered unconditionally — … a machine with no
28
+ * `cockpit login` pairing yet still serves the three event tools; only a
29
+ * docs/msg call on that machine fails, by name." This module makes the
30
+ * converse true as well: a machine with no LEGACY operator token still serves
31
+ * every agent-door tool, and only the three event-stream tools fail — by name,
32
+ * with the reason, when they are called.
33
+ */
34
+ import { createDefaultTokenResolver } from "./token-resolver.js";
35
+ export function resolveStartupAuth(options) {
36
+ try {
37
+ const result = createDefaultTokenResolver({ env: options.env });
38
+ return {
39
+ resolver: result.resolver,
40
+ sourceLabel: result.source === "env"
41
+ ? "BLI_OPERATOR_TOKEN env var"
42
+ : `session helper at ${result.helperPath}`,
43
+ legacyToolsDegraded: false,
44
+ };
45
+ }
46
+ catch (error) {
47
+ const reason = error instanceof Error ? error.message : String(error);
48
+ return {
49
+ // Not a startup failure — a per-call one. `server.ts`'s `callApi` turns
50
+ // this throw into BLI-E003 on the ONE tool that needed it, which is the
51
+ // difference between three unavailable tools and no server at all.
52
+ resolver: async () => {
53
+ throw new Error(reason);
54
+ },
55
+ sourceLabel: "none for the legacy event-stream tools (emit_event, get_ticket_timeline, get_active_tickets); "
56
+ + `they will refuse by name when called — ${reason} `
57
+ + "Every device-token tool (docs_*, msg_*, work_*, mail_*, cal_*, jarvis_*, …) is unaffected.",
58
+ legacyToolsDegraded: true,
59
+ };
60
+ }
61
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bli-cockpit/mcp",
3
- "version": "0.1.12",
3
+ "version": "0.1.13",
4
4
  "private": false,
5
5
  "description": "bli-tower \u2014 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",
@@ -22,10 +22,11 @@
22
22
  "build": "tsc",
23
23
  "prepack": "node ../../scripts/build-workspace-dep.mjs @bli-cockpit/telemetry-core && rm -rf dist && tsc",
24
24
  "pretypecheck": "node ../../scripts/build-workspace-dep.mjs @bli-cockpit/telemetry-core",
25
- "typecheck": "tsc --noEmit",
25
+ "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.scripts.json",
26
26
  "pretest": "node ../../scripts/build-workspace-dep.mjs @bli-cockpit/telemetry-core",
27
27
  "test": "vitest run",
28
28
  "start": "node dist/index.js",
29
+ "mcp:smoke": "npm run build --silent && node scripts/smoke.ts",
29
30
  "readme": "npm run build && node scripts/write-readme-census.mjs"
30
31
  },
31
32
  "dependencies": {