@thehammer/danx-dashboard-mcp 0.1.18 → 0.1.20
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/handlers.js +105 -0
- package/dist/http-client.js +7 -0
- package/dist/index.js +42 -1
- package/package.json +1 -1
package/dist/handlers.js
CHANGED
|
@@ -205,6 +205,111 @@ export async function issueDependency(client, args) {
|
|
|
205
205
|
board,
|
|
206
206
|
});
|
|
207
207
|
}
|
|
208
|
+
/**
|
|
209
|
+
* DX-1362 — targeted checklist CUD. Action-dispatched onto the
|
|
210
|
+
* `/api/issues/:id/checklists[/:cid[/items[/:iid]]]` route family so an agent
|
|
211
|
+
* mutates ONE checklist or item without the wholesale `issue_edit({checklists})`
|
|
212
|
+
* replace (which silently drops any omitted checklist + churns every item id).
|
|
213
|
+
* A missing required arg for the chosen action throws at this boundary (no
|
|
214
|
+
* round-trip). The dashboard's structured error envelope passes through verbatim.
|
|
215
|
+
*/
|
|
216
|
+
export async function issueChecklist(client, args) {
|
|
217
|
+
const idEnc = encodeURIComponent(args.id);
|
|
218
|
+
const board = args.board;
|
|
219
|
+
switch (args.action) {
|
|
220
|
+
case "add_list": {
|
|
221
|
+
if (typeof args.name !== "string") {
|
|
222
|
+
throw new Error("issue_checklist action=add_list requires name");
|
|
223
|
+
}
|
|
224
|
+
const body = { name: args.name };
|
|
225
|
+
if (args.items !== undefined)
|
|
226
|
+
body.items = args.items;
|
|
227
|
+
return client.request({
|
|
228
|
+
method: "POST",
|
|
229
|
+
path: `/${idEnc}/checklists`,
|
|
230
|
+
body,
|
|
231
|
+
board,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
case "update_list": {
|
|
235
|
+
if (args.checklist_id === undefined) {
|
|
236
|
+
throw new Error("issue_checklist action=update_list requires checklist_id");
|
|
237
|
+
}
|
|
238
|
+
if (typeof args.name !== "string") {
|
|
239
|
+
throw new Error("issue_checklist action=update_list requires name");
|
|
240
|
+
}
|
|
241
|
+
return client.request({
|
|
242
|
+
method: "PATCH",
|
|
243
|
+
path: `/${idEnc}/checklists/${args.checklist_id}`,
|
|
244
|
+
body: { name: args.name },
|
|
245
|
+
board,
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
case "remove_list": {
|
|
249
|
+
if (args.checklist_id === undefined) {
|
|
250
|
+
throw new Error("issue_checklist action=remove_list requires checklist_id");
|
|
251
|
+
}
|
|
252
|
+
return client.request({
|
|
253
|
+
method: "DELETE",
|
|
254
|
+
path: `/${idEnc}/checklists/${args.checklist_id}`,
|
|
255
|
+
board,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
case "add_item": {
|
|
259
|
+
if (args.checklist_id === undefined) {
|
|
260
|
+
throw new Error("issue_checklist action=add_item requires checklist_id");
|
|
261
|
+
}
|
|
262
|
+
if (typeof args.label !== "string") {
|
|
263
|
+
throw new Error("issue_checklist action=add_item requires label");
|
|
264
|
+
}
|
|
265
|
+
const body = { label: args.label };
|
|
266
|
+
if (args.detail !== undefined)
|
|
267
|
+
body.detail = args.detail;
|
|
268
|
+
if (args.status !== undefined)
|
|
269
|
+
body.status = args.status;
|
|
270
|
+
return client.request({
|
|
271
|
+
method: "POST",
|
|
272
|
+
path: `/${idEnc}/checklists/${args.checklist_id}/items`,
|
|
273
|
+
body,
|
|
274
|
+
board,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
case "update_item": {
|
|
278
|
+
if (args.checklist_id === undefined) {
|
|
279
|
+
throw new Error("issue_checklist action=update_item requires checklist_id");
|
|
280
|
+
}
|
|
281
|
+
if (args.item_id === undefined) {
|
|
282
|
+
throw new Error("issue_checklist action=update_item requires item_id");
|
|
283
|
+
}
|
|
284
|
+
const body = {};
|
|
285
|
+
if (args.label !== undefined)
|
|
286
|
+
body.label = args.label;
|
|
287
|
+
if (args.detail !== undefined)
|
|
288
|
+
body.detail = args.detail;
|
|
289
|
+
if (args.status !== undefined)
|
|
290
|
+
body.status = args.status;
|
|
291
|
+
return client.request({
|
|
292
|
+
method: "PATCH",
|
|
293
|
+
path: `/${idEnc}/checklists/${args.checklist_id}/items/${args.item_id}`,
|
|
294
|
+
body,
|
|
295
|
+
board,
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
case "remove_item": {
|
|
299
|
+
if (args.checklist_id === undefined) {
|
|
300
|
+
throw new Error("issue_checklist action=remove_item requires checklist_id");
|
|
301
|
+
}
|
|
302
|
+
if (args.item_id === undefined) {
|
|
303
|
+
throw new Error("issue_checklist action=remove_item requires item_id");
|
|
304
|
+
}
|
|
305
|
+
return client.request({
|
|
306
|
+
method: "DELETE",
|
|
307
|
+
path: `/${idEnc}/checklists/${args.checklist_id}/items/${args.item_id}`,
|
|
308
|
+
board,
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
208
313
|
export async function issueRequiresHuman(client, args) {
|
|
209
314
|
const idEnc = encodeURIComponent(args.id);
|
|
210
315
|
const board = args.board;
|
package/dist/http-client.js
CHANGED
|
@@ -11,6 +11,13 @@ export class DashboardHttpClient {
|
|
|
11
11
|
Authorization: `Bearer ${this.config.token}`,
|
|
12
12
|
Accept: "application/json",
|
|
13
13
|
};
|
|
14
|
+
// DX-1398 Hop 3 (outbound) — stamp the cross-process trace at the SAME
|
|
15
|
+
// single choke point that stamps Authorization, so every call (GET + every
|
|
16
|
+
// write) chains under the originating launch. Omitted when the dispatch
|
|
17
|
+
// carries no trace context (the dashboard then mints a fresh root).
|
|
18
|
+
if (this.config.traceparent) {
|
|
19
|
+
headers["traceparent"] = this.config.traceparent;
|
|
20
|
+
}
|
|
14
21
|
let bodyString;
|
|
15
22
|
if (args.body !== undefined) {
|
|
16
23
|
headers["Content-Type"] = "application/json";
|
package/dist/index.js
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
* - issue_transition POST /api/issues/:id/transition
|
|
19
19
|
* - issue_triage POST /api/issues/:id/triage
|
|
20
20
|
* - issue_comment POST/PATCH/DELETE /api/issues/:id/comments[/:cid]
|
|
21
|
+
* - issue_checklist POST/PATCH/DELETE /api/issues/:id/checklists[/:cid[/items[/:iid]]]
|
|
21
22
|
* - issue_dependency POST/DELETE /api/issues/:id/dependencies[/:did]
|
|
22
23
|
* - issue_requires_human POST/DELETE /api/issues/:id/requires-human
|
|
23
24
|
* - issue_retro PUT /api/issues/:id/retro
|
|
@@ -50,7 +51,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
50
51
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
51
52
|
import { z } from "zod";
|
|
52
53
|
import { DashboardHttpClient } from "./http-client.js";
|
|
53
|
-
import { issueAttach, issueComment, issueCreate, issueDependency, issueEdit, issueGet, issueList, issueRequiresHuman, issueRetro, issueTransition, issueTriage, } from "./handlers.js";
|
|
54
|
+
import { issueAttach, issueChecklist, issueComment, issueCreate, issueDependency, issueEdit, issueGet, issueList, issueRequiresHuman, issueRetro, issueTransition, issueTriage, } from "./handlers.js";
|
|
54
55
|
function readEnvOrDie(name) {
|
|
55
56
|
const v = process.env[name];
|
|
56
57
|
if (typeof v !== "string" || v === "") {
|
|
@@ -59,6 +60,17 @@ function readEnvOrDie(name) {
|
|
|
59
60
|
}
|
|
60
61
|
return v;
|
|
61
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Read an OPTIONAL env var, treating empty string as absent. DX-1398 — the
|
|
65
|
+
* worker injects `DANXBOT_TRACEPARENT` for a traced dispatch and substitutes it
|
|
66
|
+
* to `""` for an untraced one (the optional-placeholder default), so empty
|
|
67
|
+
* MUST read as "no trace" rather than a literal value. Never exits — an
|
|
68
|
+
* untraced dispatch is valid.
|
|
69
|
+
*/
|
|
70
|
+
function readEnvOptional(name) {
|
|
71
|
+
const v = process.env[name];
|
|
72
|
+
return typeof v === "string" && v !== "" ? v : undefined;
|
|
73
|
+
}
|
|
62
74
|
// Compose the dispatch's qualified board id (`<repo>:<slug>`) from the
|
|
63
75
|
// two env halves the worker injects. DANXBOT_BOARD_NAME already carries
|
|
64
76
|
// the board SLUG at the spawn site (src/dispatch/core.ts sets it from
|
|
@@ -68,6 +80,10 @@ const config = {
|
|
|
68
80
|
baseUrl: readEnvOrDie("DANXBOT_DASHBOARD_URL"),
|
|
69
81
|
token: readEnvOrDie("DANXBOT_DISPATCH_TOKEN"),
|
|
70
82
|
board: `${readEnvOrDie("DANX_REPO_NAME")}:${readEnvOrDie("DANXBOT_BOARD_NAME")}`,
|
|
83
|
+
// DX-1398 — OPTIONAL cross-process trace context. Present → stamped on every
|
|
84
|
+
// outbound call so the agent's card writes chain under the launch; absent
|
|
85
|
+
// (untraced dispatch) → omitted, and the dashboard mints a fresh root.
|
|
86
|
+
traceparent: readEnvOptional("DANXBOT_TRACEPARENT"),
|
|
71
87
|
};
|
|
72
88
|
const client = new DashboardHttpClient(config);
|
|
73
89
|
const server = new McpServer({
|
|
@@ -229,6 +245,31 @@ server.tool("issue_comment", "Comment CRUD via /api/issues/:id/comments[/:cid].
|
|
|
229
245
|
text: z.string().min(1).optional(),
|
|
230
246
|
...boardField,
|
|
231
247
|
}, async (args) => jsonResult(await issueComment(client, args)));
|
|
248
|
+
// ---------------- issue_checklist ----------------
|
|
249
|
+
const CHECKLIST_ITEM_INPUT = z.object({
|
|
250
|
+
label: z.string().min(1),
|
|
251
|
+
detail: z.string().optional(),
|
|
252
|
+
status: z.enum(CHECKLIST_ITEM_STATUSES).optional(),
|
|
253
|
+
});
|
|
254
|
+
server.tool("issue_checklist", "Targeted checklist CUD via /api/issues/:id/checklists[/:cid[/items[/:iid]]] (DX-1362). Mutates ONE checklist or item WITHOUT the wholesale `issue_edit({checklists})` replace — use this for the common case (flip an item's status, add/rename a checklist, add/edit/remove an item); the wholesale path silently DROPS any checklist you omit and churns every item id (orphaning its Trello mirror), so prefer this for single-item changes. Action-dispatched: add_list (POST :id/checklists {name, items?}) — create a named checklist, optionally with initial items; update_list (PATCH :id/checklists/:cid {name}) — rename; remove_list (DELETE :id/checklists/:cid) — soft-delete the checklist (audit trail preserved); add_item (POST :id/checklists/:cid/items {label, detail?, status?}) — append an item (status defaults `incomplete`); update_item (PATCH :id/checklists/:cid/items/:iid {label?, detail?, status?}) — change ONLY the fields you pass, in place (the item keeps its id + Trello linkage; at least one field required); remove_item (DELETE :id/checklists/:cid/items/:iid) — soft-delete one item. 4-state status: incomplete|failing|passing|cancelled (terminal = passing|cancelled). checklist_id is required for every action except add_list; item_id for update_item/remove_item. Each returns the {ok,status,body} envelope; unknown card/checklist/item → 404, invalid status → 400. ADDITIVE — the wholesale `issue_edit({checklists})` path stays for bulk authoring.", {
|
|
255
|
+
id: z.string().min(1),
|
|
256
|
+
action: z.enum([
|
|
257
|
+
"add_list",
|
|
258
|
+
"update_list",
|
|
259
|
+
"remove_list",
|
|
260
|
+
"add_item",
|
|
261
|
+
"update_item",
|
|
262
|
+
"remove_item",
|
|
263
|
+
]),
|
|
264
|
+
checklist_id: z.number().int().positive().optional(),
|
|
265
|
+
item_id: z.number().int().positive().optional(),
|
|
266
|
+
name: z.string().min(1).optional(),
|
|
267
|
+
items: z.array(CHECKLIST_ITEM_INPUT).optional(),
|
|
268
|
+
label: z.string().min(1).optional(),
|
|
269
|
+
detail: z.string().optional(),
|
|
270
|
+
status: z.enum(CHECKLIST_ITEM_STATUSES).optional(),
|
|
271
|
+
...boardField,
|
|
272
|
+
}, async (args) => jsonResult(await issueChecklist(client, args)));
|
|
232
273
|
// ---------------- issue_dependency ----------------
|
|
233
274
|
server.tool("issue_dependency", "Dependency CRUD via /api/issues/:id/dependencies[/:did]. action=add → POST {kind, target_id, reason} where kind ∈ {depends_on, conflict_on}. depends_on adds are CYCLE-CHECKED (BFS from target back to source — 409 if loop). Idempotent: re-adding a live triple returns the existing id. Self-loops refuse 409. action=remove → DELETE /:did. The server REQUIRES the literal reason=\"recorded_in_error\" on removal (encodes \"removal means NOT related, never satisfied\") — this MCP boundary hardcodes it, so callers do not pass reason on remove.", {
|
|
234
275
|
id: z.string().min(1),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thehammer/danx-dashboard-mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.20",
|
|
4
4
|
"description": "Stdio MCP server wrapping danxbot's dashboard /api/issues/* normalized DB-backed HTTP routes for dispatched agents (DX-704 Phase 2).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|