@bli-cockpit/mcp 0.1.22 → 0.1.24

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
@@ -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
- **76 of 79 Tower verbs have an MCP twin.**
37
+ **77 of 80 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
@@ -81,6 +81,7 @@ three tables below.
81
81
  | `cockpit memory log` | `memory_experience` | `POST /api/memory/experience` |
82
82
  | `cockpit model set` | `model_set` | `POST /api/settings/jarvis-model` |
83
83
  | `cockpit model show` | `model_show` | `GET /api/settings/jarvis-model` |
84
+ | `cockpit models compare` | `models_compare` | `GET /api/models/cards?compare=` |
84
85
  | `cockpit models list` | `models_list` | `GET /api/models/cards` |
85
86
  | `cockpit models show` | `models_card` | `GET /api/models/cards?id=` |
86
87
  | `cockpit msg channels` | `msg_channels` | `GET /api/msg/channels` |
@@ -46,9 +46,13 @@ export function registerModelsTools(server, deps) {
46
46
  }));
47
47
  register("models_card", {
48
48
  title: "Read one model card",
49
- description: "One model in full: every documented value with the provider page it was read from, the quirks quoted "
50
- + "verbatim, our measured latency and tokens/s, each probe verdict, and any stale mark. A field the "
51
- + "provider does not state reads unknown with the reasonnever a guess.",
49
+ description: "One model in full, as six questions: what's going on (window, price, today's spend and turns), what's "
50
+ + "wrong (stale fields, refused probes, today's failures), what's right (probe verdicts, bench record), "
51
+ + "what should we do / double down / forget about it each a verbatim quotation from a committed bench "
52
+ + "report or the words `no bench verdict — not recommended either way`, never advice this tool composed. "
53
+ + "The provider's claim and our measurement stay in separate columns, and a legend names the window and "
54
+ + "row count behind every measured figure. A field the provider does not state reads unknown with the "
55
+ + "reason — never a guess.",
52
56
  inputSchema: {
53
57
  id: z
54
58
  .string()
@@ -59,6 +63,9 @@ export function registerModelsTools(server, deps) {
59
63
  }, async (args) => withSession(deps, async (session) => {
60
64
  const params = new URLSearchParams();
61
65
  params.set("id", String(args.id));
66
+ // The six sections (BLI-3918) — the same answer the model page renders
67
+ // and `cockpit models show` prints.
68
+ params.set("sections", "1");
62
69
  const response = await callAgentDoor(session, deps.fetchImpl, "GET", `/api/models/cards${queryString(params)}`);
63
70
  // A 404 here is the door's own sentence naming the id and the command
64
71
  // that would add it, which is more useful than anything this file
@@ -67,7 +74,63 @@ export function registerModelsTools(server, deps) {
67
74
  return errorResult(doorFailureText("models_card", response));
68
75
  const body = response.body;
69
76
  const card = (body.card ?? {});
70
- const lines = (Array.isArray(body.lines) ? body.lines : []);
71
- return textResult(lines.join("\n") || `Model card ${card.id ?? args.id}.`, { card });
77
+ const sections = (body.sections ?? null);
78
+ const sectionLines = (Array.isArray(body.section_lines) ? body.section_lines : []);
79
+ // A server that predates the sections still answers the whole card, and
80
+ // an agent gets that rather than nothing.
81
+ const lines = sectionLines.length > 0
82
+ ? sectionLines
83
+ : (Array.isArray(body.lines) ? body.lines : []);
84
+ return textResult(lines.join("\n") || `Model card ${card.id ?? args.id}.`, {
85
+ card,
86
+ ...(sections ? { sections } : {}),
87
+ });
88
+ }));
89
+ register("models_compare", {
90
+ title: "Compare two to four model cards",
91
+ description: "Two to four models side by side — models are columns, facts are rows — over the same door and the same "
92
+ + "builder the page at /settings/models/compare renders and `cockpit models compare` prints. Groups: "
93
+ + "overview, price (including `$ per 100 JARVIS turns`, our own measured tokens a turn times the "
94
+ + "provider's price, labelled with the window it was measured over — `not measured` where no profile "
95
+ + "exists, never a per-million price relabelled), our measurements, the bench and the quirks. The bench "
96
+ + "rows appear only when every model was judged in the SAME run against the SAME baseline; otherwise the "
97
+ + "group says `benched separately — not a head-to-head` rather than subtracting two win rates. A group "
98
+ + "with nothing in it for one column says so once, in that column; a single missing cell is `—`, and a "
99
+ + "measured one with no rows behind it is `not measured` — the two are never blurred. Read-only.",
100
+ inputSchema: {
101
+ ids: z
102
+ .array(z.string().min(1).max(200))
103
+ .min(2)
104
+ .max(4)
105
+ .describe("Two to four card ids, `provider:model` (e.g. openai:gpt-5.6-luna), in column order."),
106
+ highlight: z
107
+ .boolean()
108
+ .optional()
109
+ .describe("Mark the best cell in each scored row (lowest wins on price and latency, highest on tokens/s, "
110
+ + "most wins on the bench). Off by default; a row where any column is missing or unmeasured is "
111
+ + "never scored, and says why."),
112
+ },
113
+ }, async (args) => withSession(deps, async (session) => {
114
+ const ids = args.ids.map((id) => String(id));
115
+ const params = new URLSearchParams();
116
+ params.set("compare", ids.join(","));
117
+ if (args.highlight === true)
118
+ params.set("highlight", "1");
119
+ const response = await callAgentDoor(session, deps.fetchImpl, "GET", `/api/models/cards${queryString(params)}`);
120
+ // The door's own refusal names the id nothing answers to, or the column
121
+ // count it would not draw — both more useful than anything composed here.
122
+ if (!response.ok)
123
+ return errorResult(doorFailureText("models_compare", response));
124
+ const body = response.body;
125
+ const compare = (body.compare ?? {});
126
+ const lines = (Array.isArray(body.compare_lines) ? body.compare_lines : []);
127
+ // Every column names its PROVIDER as well as its model, because the
128
+ // same weights are reachable through two doors and two columns headed
129
+ // alike would be indistinguishable.
130
+ const headers = (compare.columns ?? [])
131
+ .map((column) => column.header ?? column.id ?? "")
132
+ .filter((header) => header.length > 0);
133
+ return textResult(lines.join("\n")
134
+ || `Compared ${ids.length} model cards: ${(headers.length > 0 ? headers : ids).join(", ")}.`, { compare });
72
135
  }));
73
136
  }
@@ -241,6 +241,8 @@ export const MCP_TWINS = {
241
241
  // the org setting that picks from it.
242
242
  "models list": { tool: "models_list", door: "GET /api/models/cards" },
243
243
  "models show": { tool: "models_card", door: "GET /api/models/cards?id=" },
244
+ // BLI-3919: two to four cards side by side, on the same door.
245
+ "models compare": { tool: "models_compare", door: "GET /api/models/cards?compare=" },
244
246
  "scout board": { tool: "scout_board", door: "GET /api/cockpit/scout" },
245
247
  "scout start": { tool: "scout_start", door: "POST /api/cockpit/scout (start)" },
246
248
  "scout dismiss": { tool: "scout_dismiss", door: "POST /api/cockpit/scout (dismiss)" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bli-cockpit/mcp",
3
- "version": "0.1.22",
3
+ "version": "0.1.24",
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",
@@ -30,7 +30,7 @@
30
30
  "readme": "npm run build && node scripts/write-readme-census.mjs"
31
31
  },
32
32
  "dependencies": {
33
- "@bli-cockpit/telemetry-core": "0.1.37",
33
+ "@bli-cockpit/telemetry-core": "0.1.38",
34
34
  "@modelcontextprotocol/sdk": "^1.29.0",
35
35
  "zod": "^4.3.6"
36
36
  },