@bli-cockpit/mcp 0.1.103 → 0.1.105
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/dist/search-tool.d.ts +1 -1
- package/dist/search-tool.js +47 -11
- package/dist/work-tools.js +18 -18
- package/package.json +1 -1
package/dist/search-tool.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `tower_search` — one bar over
|
|
2
|
+
* `tower_search` — one bar over every corpus, as an MCP tool (BLI-3728).
|
|
3
3
|
*
|
|
4
4
|
* The same `GET /api/search` door the browser's search bar and `cockpit
|
|
5
5
|
* search` press, over this machine's device token
|
package/dist/search-tool.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `tower_search` — one bar over
|
|
2
|
+
* `tower_search` — one bar over every corpus, as an MCP tool (BLI-3728).
|
|
3
3
|
*
|
|
4
4
|
* The same `GET /api/search` door the browser's search bar and `cockpit
|
|
5
5
|
* search` press, over this machine's device token
|
|
@@ -25,18 +25,20 @@
|
|
|
25
25
|
import { z } from "zod";
|
|
26
26
|
import { loadAgentDoorSession } from "./agent-door-session.js";
|
|
27
27
|
import { callAgentDoor } from "./agent-door.js";
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
|
|
28
|
+
// What `kinds` may name is the SERVER's to say (BLI-13027): this used to be a
|
|
29
|
+
// z.enum copy of the dashboard's list, so an installed MCP server refused a
|
|
30
|
+
// corpus the day it shipped. Kinds are forwarded as typed; the door answers
|
|
31
|
+
// `unknown_kind` with the list, and `categories: true` asks for it up front.
|
|
32
32
|
const KIND_LABELS = {
|
|
33
33
|
doc: "Document",
|
|
34
34
|
msg: "Message",
|
|
35
|
-
issue: "
|
|
35
|
+
issue: "Ticket",
|
|
36
36
|
note: "Meeting note",
|
|
37
37
|
mail: "Email",
|
|
38
38
|
cal: "Calendar event",
|
|
39
39
|
memory: "Memory",
|
|
40
|
+
scout: "Scout card",
|
|
41
|
+
careers: "Applicant",
|
|
40
42
|
};
|
|
41
43
|
function textResult(text, structured) {
|
|
42
44
|
return {
|
|
@@ -51,13 +53,22 @@ export function registerSearchTool(server, deps) {
|
|
|
51
53
|
const register = server.registerTool.bind(server);
|
|
52
54
|
register("tower_search", {
|
|
53
55
|
title: "Search everything in Tower",
|
|
54
|
-
description: "One search over
|
|
56
|
+
description: "One search over Tower tickets, documents, meeting notes, messages, the caller's own mail, their calendar, BLI Memory, Scout and (only where the caller may read applications) Careers. Use it when you know WHAT you are looking for but not WHERE it lives. Every result names the corpus it came from and where to open it; a memory has no page, so its text is the whole record. Mail and calendar are the caller's OWN — their mailboxes, their calendars and the ones shared with the organisation — never the whole company's. A calendar hit is the stored event, so a recurring meeting comes back once as its series rather than once per occurrence. A corpus that could not answer is reported separately from one that found nothing — do not read a failure as silence.",
|
|
55
57
|
inputSchema: {
|
|
56
|
-
query: z
|
|
58
|
+
query: z
|
|
59
|
+
.string()
|
|
60
|
+
.min(2)
|
|
61
|
+
.max(1000)
|
|
62
|
+
.optional()
|
|
63
|
+
.describe("What to look for, in the words a person would type. Required unless categories is true."),
|
|
57
64
|
kinds: z
|
|
58
|
-
.array(z.
|
|
65
|
+
.array(z.string().min(1).max(40))
|
|
66
|
+
.optional()
|
|
67
|
+
.describe("Narrow to these categories (issue, doc, note, msg, mail, cal, memory, scout, careers; ticket works for issue). Omit to search every one. Call with categories: true for the exact list this account is offered."),
|
|
68
|
+
categories: z
|
|
69
|
+
.boolean()
|
|
59
70
|
.optional()
|
|
60
|
-
.describe("
|
|
71
|
+
.describe("True returns the categories this account can narrow to, instead of searching."),
|
|
61
72
|
limit: z.number().int().min(1).max(50).optional().describe("Results to return (default 20)."),
|
|
62
73
|
},
|
|
63
74
|
}, async (args) => {
|
|
@@ -66,10 +77,15 @@ export function registerSearchTool(server, deps) {
|
|
|
66
77
|
if (!loaded.ok) {
|
|
67
78
|
return errorResult(`This machine is not paired with Tower (${loaded.reason}). ${loaded.message}`);
|
|
68
79
|
}
|
|
80
|
+
if (args.categories === true)
|
|
81
|
+
return await listCategories(loaded.session, deps);
|
|
69
82
|
const query = String(args.query ?? "");
|
|
83
|
+
if (query.trim().length < 2) {
|
|
84
|
+
return errorResult("tower_search needs a query of at least 2 characters, or categories: true to list the categories.");
|
|
85
|
+
}
|
|
70
86
|
const params = new URLSearchParams({ q: query });
|
|
71
87
|
if (Array.isArray(args.kinds) && args.kinds.length > 0) {
|
|
72
|
-
params.set("kinds", args.kinds.join(","));
|
|
88
|
+
params.set("kinds", args.kinds.map((kind) => kind.trim().toLowerCase()).join(","));
|
|
73
89
|
}
|
|
74
90
|
if (typeof args.limit === "number")
|
|
75
91
|
params.set("limit", String(args.limit));
|
|
@@ -94,6 +110,26 @@ export function registerSearchTool(server, deps) {
|
|
|
94
110
|
});
|
|
95
111
|
});
|
|
96
112
|
}
|
|
113
|
+
/** `categories: true` — the list the browser's Command-K chips are drawn from. */
|
|
114
|
+
async function listCategories(session, deps) {
|
|
115
|
+
const response = await callAgentDoor(session, deps.fetchImpl, "GET", "/api/search?categories=1");
|
|
116
|
+
if (!response.ok) {
|
|
117
|
+
if (response.transportError) {
|
|
118
|
+
return errorResult(`Tower could not be reached for tower_search (${response.transportError}). The categories were not read.`);
|
|
119
|
+
}
|
|
120
|
+
const reason = typeof response.body.reason === "string" ? response.body.reason : "unknown_error";
|
|
121
|
+
return errorResult(`Tower refused tower_search categories (${reason}).`);
|
|
122
|
+
}
|
|
123
|
+
const categories = (Array.isArray(response.body.categories) ? response.body.categories : []);
|
|
124
|
+
const lines = [
|
|
125
|
+
`${categories.length} categories this account can search. Name them in kinds; omit kinds to search all of them.`,
|
|
126
|
+
...categories.map((entry) => {
|
|
127
|
+
const aliases = (entry.aliases ?? []).length > 0 ? ` (also: ${(entry.aliases ?? []).join(", ")})` : "";
|
|
128
|
+
return `- ${entry.kind}: ${entry.label}${aliases}`;
|
|
129
|
+
}),
|
|
130
|
+
];
|
|
131
|
+
return textResult(lines.join("\n"), { categories });
|
|
132
|
+
}
|
|
97
133
|
/**
|
|
98
134
|
* The sentence a model reads.
|
|
99
135
|
*
|
package/dist/work-tools.js
CHANGED
|
@@ -84,13 +84,13 @@ async function resolveParentId(deps, session, ref) {
|
|
|
84
84
|
export function registerWorkTools(server, deps) {
|
|
85
85
|
const register = registrarFor(server);
|
|
86
86
|
register("work_list_issues", {
|
|
87
|
-
title: "List Tower
|
|
88
|
-
description: "
|
|
87
|
+
title: "List Tower tickets",
|
|
88
|
+
description: "Tower tickets you may see, most recently updated first: identifier, state, title, priority, assignee, project. "
|
|
89
89
|
+ "Filter by state, assignee (\"me\", \"unassigned\", or a uuid) or project (name or uuid). "
|
|
90
|
-
+ `Default ${DEFAULT_LIST_LIMIT}
|
|
91
|
-
+ "Never a description — call work_get_issue for one
|
|
90
|
+
+ `Default ${DEFAULT_LIST_LIMIT} tickets; the answer says when more exist. `
|
|
91
|
+
+ "Never a description — call work_get_issue for one ticket's full text.",
|
|
92
92
|
inputSchema: {
|
|
93
|
-
state: z.enum(WORK_ISSUE_STATES).optional().describe("Only
|
|
93
|
+
state: z.enum(WORK_ISSUE_STATES).optional().describe("Only tickets in this state."),
|
|
94
94
|
assignee: z.string().min(1).max(100).optional().describe('"me", "unassigned", or a person\'s uuid.'),
|
|
95
95
|
project: z.string().min(1).max(200).optional().describe("A project name or uuid."),
|
|
96
96
|
limit: z.number().int().min(1).max(ISSUE_LIST_MAX).optional().describe(`At most this many issues. Default ${DEFAULT_LIST_LIMIT}.`),
|
|
@@ -144,8 +144,8 @@ export function registerWorkTools(server, deps) {
|
|
|
144
144
|
});
|
|
145
145
|
}));
|
|
146
146
|
register("work_get_issue", {
|
|
147
|
-
title: "Read a Tower
|
|
148
|
-
description: "One
|
|
147
|
+
title: "Read a Tower ticket",
|
|
148
|
+
description: "One Tower ticket's title, state, priority, assignee (name and email, not just its id), description and every "
|
|
149
149
|
+ "comment on it. `ids` reads several in one call — still one request per issue plus its comments, "
|
|
150
150
|
+ "no bulk door exists yet. A single id answers `{ issue, comments }`; several answer "
|
|
151
151
|
+ "`{ issues: [{ ref, ok, issue, comments } | { ref, ok: false, error, detail }] }` so one bad id never "
|
|
@@ -189,8 +189,8 @@ export function registerWorkTools(server, deps) {
|
|
|
189
189
|
});
|
|
190
190
|
}));
|
|
191
191
|
register("work_create_issue", {
|
|
192
|
-
title: "File a Tower
|
|
193
|
-
description: "Files a new
|
|
192
|
+
title: "File a Tower ticket",
|
|
193
|
+
description: "Files a new Tower ticket and returns the BLI-#### identifier it was given. Use this the way you would file a Linear ticket.",
|
|
194
194
|
inputSchema: {
|
|
195
195
|
title: z.string().min(1).max(300).describe("The issue title."),
|
|
196
196
|
description: z.string().max(200_000).optional().describe("The issue body, markdown."),
|
|
@@ -236,8 +236,8 @@ export function registerWorkTools(server, deps) {
|
|
|
236
236
|
return textResult(`Filed ${issue?.identifier ?? ""} — ${issue?.title ?? ""} (${issue?.id ?? ""}).`, { issue: issue ? withSizeLabel(issue) : issue });
|
|
237
237
|
}));
|
|
238
238
|
register("work_update_issue", {
|
|
239
|
-
title: "Update a Tower
|
|
240
|
-
description: "Changes
|
|
239
|
+
title: "Update a Tower ticket",
|
|
240
|
+
description: "Changes a Tower ticket's title, description, priority, assignee or project. Only the fields you pass change. "
|
|
241
241
|
+ "State does NOT move here — call work_move_issue, which records the move.",
|
|
242
242
|
inputSchema: {
|
|
243
243
|
id: ISSUE_REF,
|
|
@@ -285,8 +285,8 @@ export function registerWorkTools(server, deps) {
|
|
|
285
285
|
return textResult(`Updated ${issue?.identifier ?? ref} — ${issue?.title ?? ""}.`, { issue: issue ? withSizeLabel(issue) : issue });
|
|
286
286
|
}));
|
|
287
287
|
register("work_move_issue", {
|
|
288
|
-
title: "Move a Tower
|
|
289
|
-
description: "Moves
|
|
288
|
+
title: "Move a Tower ticket",
|
|
289
|
+
description: "Moves a Tower ticket to a new state and records the move in its history. This is how a ticket's state changes; "
|
|
290
290
|
+ "there is no other door.",
|
|
291
291
|
inputSchema: { id: ISSUE_REF, state: z.enum(WORK_ISSUE_STATES).describe("The state to move it to.") },
|
|
292
292
|
}, async (args) => withSession(deps, async (session) => {
|
|
@@ -300,8 +300,8 @@ export function registerWorkTools(server, deps) {
|
|
|
300
300
|
return textResult(`${issue?.identifier ?? ref} is now ${issue?.state ?? String(args.state)}.`, { issue });
|
|
301
301
|
}));
|
|
302
302
|
register("work_comment_issue", {
|
|
303
|
-
title: "Comment on a Tower
|
|
304
|
-
description: "Posts a comment. Comments are append-only — nothing here edits or deletes one.",
|
|
303
|
+
title: "Comment on a Tower ticket",
|
|
304
|
+
description: "Posts a comment on a Tower ticket. Comments are append-only — nothing here edits or deletes one.",
|
|
305
305
|
inputSchema: { id: ISSUE_REF, body_markdown: z.string().min(1).max(50_000).describe("The comment, markdown.") },
|
|
306
306
|
}, async (args) => withSession(deps, async (session) => {
|
|
307
307
|
const ref = String(args.id ?? "");
|
|
@@ -312,8 +312,8 @@ export function registerWorkTools(server, deps) {
|
|
|
312
312
|
return textResult(`Commented on ${ref} (${comment?.id ?? "?"}).`, { comment });
|
|
313
313
|
}));
|
|
314
314
|
register("work_issue_history", {
|
|
315
|
-
title: "Read a Tower
|
|
316
|
-
description: "Every recorded change to one
|
|
315
|
+
title: "Read a Tower ticket's history",
|
|
316
|
+
description: "Every recorded change to one Tower ticket — state moves, assignee changes — newest first, with who made it and "
|
|
317
317
|
+ "when. History rows are never rewritten; a row imported without state names says so rather than showing "
|
|
318
318
|
+ "\"(none) -> (none)\".",
|
|
319
319
|
inputSchema: {
|
|
@@ -336,7 +336,7 @@ export function registerWorkTools(server, deps) {
|
|
|
336
336
|
}));
|
|
337
337
|
register("work_list_projects", {
|
|
338
338
|
title: "List Tower projects",
|
|
339
|
-
description: "The projects
|
|
339
|
+
description: "The projects Tower tickets are filed under. Use a project's name with work_list_issues or work_create_issue.",
|
|
340
340
|
inputSchema: { include_archived: z.boolean().optional().describe("true: archived projects too.") },
|
|
341
341
|
}, async (args) => withSession(deps, async (session) => {
|
|
342
342
|
const response = await callAgentDoor(session, deps.fetchImpl, "GET", `/api/work/projects${args.include_archived ? "?include_archived=true" : ""}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bli-cockpit/mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.105",
|
|
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",
|