@bli-cockpit/mcp 0.1.2 → 0.1.3

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.
@@ -15,8 +15,8 @@
15
15
  * server's other tools and only a `work_` call fails, by name.
16
16
  */
17
17
  import { z } from "zod";
18
- import { loadAgentDoorSession } from "./agent-door-session.js";
19
18
  import { callAgentDoor } from "./agent-door.js";
19
+ import { doorFailureText, errorResult, registrarFor, textResult, withSession, } from "./tool-result.js";
20
20
  /** The closed state vocabulary, verbatim from `lib/work/api-doors.ts` `WORK_ISSUE_STATES`. */
21
21
  export const WORK_ISSUE_STATES = [
22
22
  "backlog",
@@ -26,30 +26,17 @@ export const WORK_ISSUE_STATES = [
26
26
  "done",
27
27
  "canceled",
28
28
  ];
29
- function textResult(text, structured) {
30
- return { content: [{ type: "text", text }], ...(structured ? { structuredContent: structured } : {}) };
31
- }
32
- function errorResult(text) {
33
- return { isError: true, content: [{ type: "text", text }] };
34
- }
35
- async function withSession(deps, run) {
36
- const loadSession = deps.loadSession ?? loadAgentDoorSession;
37
- const loaded = loadSession();
38
- if (!loaded.ok)
39
- return errorResult(`This machine is not paired with Tower (${loaded.reason}). ${loaded.message}`);
40
- return run(loaded.session);
41
- }
42
- function doorFailureText(door, response) {
43
- if (response.transportError) {
44
- return `Tower could not be reached for ${door} (${response.transportError}). Nothing was read or written.`;
29
+ /**
30
+ * A row the Linear import could not give state names to says which it is.
31
+ * Printing "(none) -> (none)" is honest and useless; naming the gap keeps
32
+ * nobody reading a real gap as a broken tool (`commands/issue.ts` says the
33
+ * same thing in the same words).
34
+ */
35
+ function historyChange(row) {
36
+ if (row.from_value === null && row.to_value === null) {
37
+ return `(no state names recorded — imported from ${row.source ?? "elsewhere"})`;
45
38
  }
46
- const reason = typeof response.body.reason === "string"
47
- ? response.body.reason
48
- : typeof response.body.error === "string"
49
- ? response.body.error
50
- : "unknown_error";
51
- const message = typeof response.body.message === "string" ? response.body.message : `Tower answered ${response.status}.`;
52
- return `Tower refused ${door} (${reason}): ${message}`;
39
+ return `${row.from_value ?? "(none)"} -> ${row.to_value ?? "(none)"}`;
53
40
  }
54
41
  /** A project id from a project NAME or a uuid — exact after case-folding, never fuzzy. */
55
42
  async function resolveProjectId(deps, session, ref) {
@@ -67,7 +54,7 @@ const ISSUE_REF = z
67
54
  .max(200)
68
55
  .describe("A BLI-#### identifier (e.g. BLI-3654) or an issue's uuid. Both work.");
69
56
  export function registerWorkTools(server, deps) {
70
- const register = server.registerTool.bind(server);
57
+ const register = registrarFor(server);
71
58
  register("work_list_issues", {
72
59
  title: "List Tower issues",
73
60
  description: "Issues you may see, most recently updated first. Filter by state, assignee (\"me\", \"unassigned\", or a uuid) "
@@ -219,6 +206,29 @@ export function registerWorkTools(server, deps) {
219
206
  const comment = response.body.comment;
220
207
  return textResult(`Commented on ${ref} (${comment?.id ?? "?"}).`, { comment });
221
208
  }));
209
+ register("work_issue_history", {
210
+ title: "Read a Tower issue's history",
211
+ description: "Every recorded change to one issue — state moves, assignee changes — newest first, with who made it and "
212
+ + "when. History rows are never rewritten; a row imported without state names says so rather than showing "
213
+ + "\"(none) -> (none)\".",
214
+ inputSchema: {
215
+ id: ISSUE_REF,
216
+ limit: z.number().int().min(1).max(500).optional(),
217
+ },
218
+ }, async (args) => withSession(deps, async (session) => {
219
+ const ref = String(args.id ?? "");
220
+ const query = typeof args.limit === "number" ? `?limit=${args.limit}` : "";
221
+ const response = await callAgentDoor(session, deps.fetchImpl, "GET", `/api/work/issues/${encodeURIComponent(ref)}/history${query}`);
222
+ if (!response.ok)
223
+ return errorResult(doorFailureText("work_issue_history", response));
224
+ const history = (Array.isArray(response.body.history) ? response.body.history : []);
225
+ const lines = history
226
+ .map((row) => `${row.occurred_at ?? "?"} ${(row.change_type ?? "?").padEnd(9)} ${historyChange(row)} `
227
+ + `${row.actor_name ?? ""}`)
228
+ .map((line) => line.trimEnd())
229
+ .join("\n");
230
+ return textResult(`${history.length} change(s) on ${ref}.${lines ? `\n${lines}` : ""}`, { history });
231
+ }));
222
232
  register("work_list_projects", {
223
233
  title: "List Tower projects",
224
234
  description: "The projects issues are filed under. Use a project's name with work_list_issues or work_create_issue.",
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@bli-cockpit/mcp",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "private": false,
5
- "description": "bli-tower — an MCP server over BLI Cockpit's agent doors: JARVIS (jarvis_*), documents (docs_*), channels (msg_*), issues (work_*), plus the legacy event-stream tools (emit_event, get_ticket_timeline, get_active_tickets).",
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",
7
7
  "bin": {
8
8
  "bli-cockpit-mcp": "./dist/index.js"
@@ -25,10 +25,11 @@
25
25
  "typecheck": "tsc --noEmit",
26
26
  "pretest": "node ../../scripts/build-workspace-dep.mjs @bli-cockpit/telemetry-core",
27
27
  "test": "vitest run",
28
- "start": "node dist/index.js"
28
+ "start": "node dist/index.js",
29
+ "readme": "npm run build && node scripts/write-readme-census.mjs"
29
30
  },
30
31
  "dependencies": {
31
- "@bli-cockpit/telemetry-core": "0.1.28",
32
+ "@bli-cockpit/telemetry-core": "0.1.29",
32
33
  "@modelcontextprotocol/sdk": "^1.29.0",
33
34
  "zod": "^4.3.6"
34
35
  },