@thehammer/danx-dashboard-mcp 0.1.47 → 0.1.49
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 +11 -11
- package/dist/index.js +29 -18
- package/package.json +1 -1
package/dist/handlers.js
CHANGED
|
@@ -95,49 +95,49 @@ export async function repoKnowledgeSet(client, args) {
|
|
|
95
95
|
board,
|
|
96
96
|
});
|
|
97
97
|
}
|
|
98
|
-
// ----------------
|
|
99
|
-
const
|
|
98
|
+
// ---------------- brief_list / brief_get_page / brief_set_page ----------------
|
|
99
|
+
const BRIEF_BASE_PATH = "/api/brief";
|
|
100
100
|
/**
|
|
101
|
-
* List the board's named
|
|
101
|
+
* List the board's named Brief pages via GET /api/brief (DX-2083).
|
|
102
102
|
* Mirrors `repoKnowledgeGet`'s shape (a bare board-scoped GET, no id) but
|
|
103
103
|
* targets the list-shaped sibling surface — many named pages per board,
|
|
104
104
|
* not one document.
|
|
105
105
|
*/
|
|
106
|
-
export async function
|
|
106
|
+
export async function briefList(client, args = {}) {
|
|
107
107
|
return client.request({
|
|
108
108
|
method: "GET",
|
|
109
109
|
path: "",
|
|
110
|
-
basePath:
|
|
110
|
+
basePath: BRIEF_BASE_PATH,
|
|
111
111
|
board: args.board,
|
|
112
112
|
});
|
|
113
113
|
}
|
|
114
114
|
/**
|
|
115
|
-
* Fetch one
|
|
115
|
+
* Fetch one Brief page by slug via GET /api/brief/page?slug=
|
|
116
116
|
* (DX-2083). A missing row (including a not-yet-created page) reads as the
|
|
117
117
|
* same empty-view convention `repoKnowledgeGet` uses — never a 404.
|
|
118
118
|
*/
|
|
119
|
-
export async function
|
|
119
|
+
export async function briefGetPage(client, args) {
|
|
120
120
|
return client.request({
|
|
121
121
|
method: "GET",
|
|
122
122
|
path: "/page",
|
|
123
|
-
basePath:
|
|
123
|
+
basePath: BRIEF_BASE_PATH,
|
|
124
124
|
query: { slug: args.slug },
|
|
125
125
|
board: args.board,
|
|
126
126
|
});
|
|
127
127
|
}
|
|
128
128
|
/**
|
|
129
|
-
* Write one
|
|
129
|
+
* Write one Brief page via PUT /api/brief/page?slug= (DX-2083).
|
|
130
130
|
* Mirrors `repoKnowledgeSet`'s shape (optimistic-concurrency `base_hash`,
|
|
131
131
|
* verbatim 409 passthrough on a stale write) but targets one named page
|
|
132
132
|
* instead of the board's single working-knowledge doc. `slug` rides the
|
|
133
133
|
* query string (matching the route), never the body.
|
|
134
134
|
*/
|
|
135
|
-
export async function
|
|
135
|
+
export async function briefSetPage(client, args) {
|
|
136
136
|
const { slug, board, ...body } = args;
|
|
137
137
|
return client.request({
|
|
138
138
|
method: "PUT",
|
|
139
139
|
path: "/page",
|
|
140
|
-
basePath:
|
|
140
|
+
basePath: BRIEF_BASE_PATH,
|
|
141
141
|
query: { slug },
|
|
142
142
|
body,
|
|
143
143
|
board,
|
package/dist/index.js
CHANGED
|
@@ -28,9 +28,9 @@
|
|
|
28
28
|
* - issue_attach POST /api/issues/:id/attachments (reads a local file)
|
|
29
29
|
* - repo_knowledge_get GET /api/repo-knowledge
|
|
30
30
|
* - repo_knowledge_set PUT /api/repo-knowledge (DX-1128, Story 2)
|
|
31
|
-
* -
|
|
32
|
-
* -
|
|
33
|
-
* -
|
|
31
|
+
* - brief_list GET /api/brief
|
|
32
|
+
* - brief_get_page GET /api/brief/page (DX-2083 / DX-2484)
|
|
33
|
+
* - brief_set_page PUT /api/brief/page (DX-2083 / DX-2484)
|
|
34
34
|
* - plan_list GET /api/plans (DX-2683)
|
|
35
35
|
* - plan_get GET /api/plans/:id/full | /api/plans/mine
|
|
36
36
|
* - plan_connect POST /api/plan-sessions/me/plan
|
|
@@ -75,7 +75,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
75
75
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
76
76
|
import { z } from "zod";
|
|
77
77
|
import { DashboardHttpClient } from "./http-client.js";
|
|
78
|
-
import { issueAttach, issueChecklist, issueComment, issueCreate, issueDependency, issueEdit, issueGet, issueList, issueQualityGate, issueQualityGateVerdict, issueRequiresHuman, issueRetro, issueTransition, issueTriage,
|
|
78
|
+
import { issueAttach, issueChecklist, issueComment, issueCreate, issueDependency, issueEdit, issueGet, issueList, issueQualityGate, issueQualityGateVerdict, issueRequiresHuman, issueRetro, issueTransition, issueTriage, briefGetPage, briefList, briefSetPage, planAddCard, planAddRecord, planConnect, planGet, planList, planSetArchitecture, repoKnowledgeGet, repoKnowledgeSet, } from "./handlers.js";
|
|
79
79
|
import { PRIORITY_TIER_WORDS } from "./priority.js";
|
|
80
80
|
function readEnvOrDie(name) {
|
|
81
81
|
const v = process.env[name];
|
|
@@ -215,12 +215,15 @@ const sortField = z
|
|
|
215
215
|
}))
|
|
216
216
|
.optional()
|
|
217
217
|
.describe("Multi-column sort — ordered list of {column, order}. Absent → the server's default order (priority desc, repo_name asc, with a numeric-id tiebreaker always appended).");
|
|
218
|
-
// DX-1290 — the uniform
|
|
218
|
+
// DX-1290 — the uniform checklist-item status, extended DX-2653 with
|
|
219
|
+
// `deferred` (a named real-world/post-deploy check still outstanding).
|
|
220
|
+
// Terminal = passing|cancelled|deferred.
|
|
219
221
|
const CHECKLIST_ITEM_STATUSES = [
|
|
220
222
|
"incomplete",
|
|
221
223
|
"failing",
|
|
222
224
|
"passing",
|
|
223
225
|
"cancelled",
|
|
226
|
+
"deferred",
|
|
224
227
|
];
|
|
225
228
|
const TRANSITION_ACTIONS = [
|
|
226
229
|
"ready",
|
|
@@ -329,7 +332,7 @@ server.tool("issue_create", 'Create a fresh card via POST /api/issues. Board-sco
|
|
|
329
332
|
...boardField,
|
|
330
333
|
}, async (args) => jsonResult(await issueCreate(client, args, config.board)));
|
|
331
334
|
// ---------------- issue_edit ----------------
|
|
332
|
-
server.tool("issue_edit", 'Patch prose + structured fields via PATCH /api/issues/:id/edit. ALLOWED keys: title, description, ac, checklists, effort_level, parent_id, priority, list_id, triage_enabled, type. ANY OTHER KEY (lifecycle timestamps, triage state, dependencies, retro, requires_human, blocked/dispatch gates) returns 400 with offending_keys[] and a pointer to the dedicated semantic handler — use issue_transition / issue_triage / issue_comment / issue_dependency / issue_requires_human / issue_retro instead. TYPE (DX-2484): change the card\'s `type` via the `type` key — the route recomputes `dispatchable_derived` immediately when it changes. Changing type TO `Story`, `Bug`, or `Chore` makes the card eligible for autonomous pickup (subject to every other dispatch gate); changing it TO `Task` or a container (`Epic`/`Feature`) REMOVES that eligibility — a planning record or container is never dispatched, in ANY status. This is the way to promote a planning item into real work. PRIORITY (DX-1532): set card priority via the `priority` key — a tier WORD ("lowest"/"low"/"medium"/"high"/"very_high"/"critical", resolved to the tier midpoint) OR a raw number in [0,6). This is the ONLY way to change priority: the numeric `issues.priority` column is what the Trello priority label AND the dashboard badge read — editing a "Priority: <x>" line in the DESCRIPTION changes nothing downstream (a silent false-positive). To honor a "set priority" request, write `priority` here, do NOT edit description prose. CHECKLISTS (DX-1290): a card carries 0..N named checklists, each item ONE
|
|
335
|
+
server.tool("issue_edit", 'Patch prose + structured fields via PATCH /api/issues/:id/edit. ALLOWED keys: title, description, ac, checklists, effort_level, parent_id, priority, list_id, triage_enabled, type. ANY OTHER KEY (lifecycle timestamps, triage state, dependencies, retro, requires_human, blocked/dispatch gates) returns 400 with offending_keys[] and a pointer to the dedicated semantic handler — use issue_transition / issue_triage / issue_comment / issue_dependency / issue_requires_human / issue_retro instead. TYPE (DX-2484): change the card\'s `type` via the `type` key — the route recomputes `dispatchable_derived` immediately when it changes. Changing type TO `Story`, `Bug`, or `Chore` makes the card eligible for autonomous pickup (subject to every other dispatch gate); changing it TO `Task` or a container (`Epic`/`Feature`) REMOVES that eligibility — a planning record or container is never dispatched, in ANY status. This is the way to promote a planning item into real work. PRIORITY (DX-1532): set card priority via the `priority` key — a tier WORD ("lowest"/"low"/"medium"/"high"/"very_high"/"critical", resolved to the tier midpoint) OR a raw number in [0,6). This is the ONLY way to change priority: the numeric `issues.priority` column is what the Trello priority label AND the dashboard badge read — editing a "Priority: <x>" line in the DESCRIPTION changes nothing downstream (a silent false-positive). To honor a "set priority" request, write `priority` here, do NOT edit description prose. CHECKLISTS (DX-1290, extended DX-2653): a card carries 0..N named checklists, each item ONE status `incomplete|failing|passing|cancelled|deferred` (terminal = passing|cancelled|deferred). `deferred` is the honest disposition for a criterion whose work is done but names a real-world/post-deploy check still outstanding — REQUIRES a non-empty `detail`; a criterion marked with the `📡` post-deploy-evidence prefix can never be `passing` at all (refused 409 at completion, resolve to `deferred` instead). `ac` is the 2-state CONVENIENCE onto the default "Acceptance Criteria" checklist (checked:true ↔ passing, false ↔ incomplete BY DEFAULT — pass `status` alongside `checked` to reach `cancelled`/`failing`/`deferred` instead) — wholesale soft-delete + reinsert of that checklist\'s items. `checklists` is the GENERIC wholesale write path: it REPLACES every named checklist on the card with full status control (each `{name, items:[{label, detail?, status}]}`) — use it to author named checklists like "Feature Tests". Send EITHER `ac` OR `checklists`, NOT both (400). list_id (DX-1192 / DX-1200) PINS the card to a specific board list. **Pass EITHER a board_lists id OR the list\'s display NAME (case-insensitive, e.g. a queue name like "⚙️ Fulfillment Queue") — the server resolves a name to its id.** Its type MUST match the card\'s CURRENT derived-status list-type (so to route a ToDo card into a `ready`-type queue, ready it first; mismatch / unknown name or id → 400); pass null to clear the pin (back to default-for-type).', {
|
|
333
336
|
id: z.string().min(1),
|
|
334
337
|
title: z.string().min(1).optional(),
|
|
335
338
|
description: z.string().optional(),
|
|
@@ -341,6 +344,14 @@ server.tool("issue_edit", 'Patch prose + structured fields via PATCH /api/issues
|
|
|
341
344
|
.array(z.object({
|
|
342
345
|
title: z.string(),
|
|
343
346
|
checked: z.boolean().optional(),
|
|
347
|
+
status: z
|
|
348
|
+
.enum(CHECKLIST_ITEM_STATUSES)
|
|
349
|
+
.optional()
|
|
350
|
+
.describe('DX-2653 — optional full-status override. Omitted → derived from `checked` (`true`→passing, `false`→incomplete), same as before. Present → used verbatim, reaching `cancelled`/`failing`/`deferred` from this everyday convenience path instead of only via `checklists` or `issue_checklist`. `deferred` REQUIRES a non-empty `detail` naming the outstanding real-world/post-deploy check — a marker-prefixed ("📡") item can never be `passing` at all (refused 409 at completion).'),
|
|
351
|
+
detail: z
|
|
352
|
+
.string()
|
|
353
|
+
.optional()
|
|
354
|
+
.describe("DX-2653 — paired with `status`; required (non-empty) when `status` is `deferred`."),
|
|
344
355
|
}))
|
|
345
356
|
.optional(),
|
|
346
357
|
checklists: z
|
|
@@ -403,7 +414,7 @@ const CHECKLIST_ITEM_INPUT = z.object({
|
|
|
403
414
|
detail: z.string().optional(),
|
|
404
415
|
status: z.enum(CHECKLIST_ITEM_STATUSES).optional(),
|
|
405
416
|
});
|
|
406
|
-
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.
|
|
417
|
+
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. Status: incomplete|failing|passing|cancelled|deferred (terminal = passing|cancelled|deferred; DX-2653 added `deferred` — the honest disposition for a criterion whose work is done but names a real-world/post-deploy check still outstanding, REQUIRES a non-empty `detail`). 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.", {
|
|
407
418
|
id: z.string().min(1),
|
|
408
419
|
action: z.enum([
|
|
409
420
|
"add_list",
|
|
@@ -522,20 +533,20 @@ server.tool("repo_knowledge_set", 'Write the board\'s working-knowledge markdown
|
|
|
522
533
|
.describe('The contentHash last read via repo_knowledge_get ("" for a true first write). Omitted also normalizes to "" server-side, so it only succeeds against an empty/absent doc — always get immediately before set.'),
|
|
523
534
|
...boardField,
|
|
524
535
|
}, async (args) => jsonResult(await repoKnowledgeSet(client, args)));
|
|
525
|
-
// ----------------
|
|
526
|
-
server.tool("
|
|
536
|
+
// ---------------- brief_list ----------------
|
|
537
|
+
server.tool("brief_list", "List the board's named Brief pages via GET /api/brief (DX-2083 / DX-2484). Board-scoped; defaults to the dispatch's board. Pass `board` (a qualified id `<repo>:<slug>`) to list another board's pages. Returns `{boardId, pages: [{slug, title, contentHash, sortOrder, updatedAt, updatedBy}]}` — metadata only, no page content (use `brief_get_page` for that). This is the list+page-shaped sibling of `repo_knowledge_get`/`repo_knowledge_set` (one board-level doc) — Brief pages are MANY named pages per board (the Goals / Architecture / Rules / Caveats tabs), keyed by `(board, slug)`. The reserved `index` slug always exists — every board carries exactly one.", {
|
|
527
538
|
...boardField,
|
|
528
|
-
}, async (args) => jsonResult(await
|
|
529
|
-
// ----------------
|
|
530
|
-
server.tool("
|
|
539
|
+
}, async (args) => jsonResult(await briefList(client, args)));
|
|
540
|
+
// ---------------- brief_get_page ----------------
|
|
541
|
+
server.tool("brief_get_page", 'Fetch one Brief page by slug via GET /api/brief/page?slug=<slug> (DX-2083 / DX-2484). Board-scoped; defaults to the dispatch\'s board. Returns `{boardId, slug, title, content, contentHash, sortOrder, updatedAt, updatedBy}` — a missing/not-yet-created page reads as the empty view (`content: ""`, `contentHash: ""`), NOT a 404, matching `repo_knowledge_get`\'s convention. Before `brief_set_page`, ALWAYS `brief_get_page` immediately first and pass its `contentHash` back as `base_hash` — the server\'s optimistic-concurrency guard rejects a stale write.', {
|
|
531
542
|
slug: z
|
|
532
543
|
.string()
|
|
533
544
|
.min(1)
|
|
534
545
|
.describe("The page's slug. The reserved `index` slug always exists on every board."),
|
|
535
546
|
...boardField,
|
|
536
|
-
}, async (args) => jsonResult(await
|
|
537
|
-
// ----------------
|
|
538
|
-
server.tool("
|
|
547
|
+
}, async (args) => jsonResult(await briefGetPage(client, args)));
|
|
548
|
+
// ---------------- brief_set_page ----------------
|
|
549
|
+
server.tool("brief_set_page", 'Write one Brief page via PUT /api/brief/page?slug=<slug> (DX-2083 / DX-2484). Board-scoped; defaults to the dispatch\'s board. Body: `{content, title?, sortOrder?, base_hash?}` — mirrors `repo_knowledge_set`\'s optimistic-concurrency shape but targets one named page instead of the board\'s single working-knowledge doc. `base_hash` MUST be the `contentHash` from the immediately-prior `brief_get_page` call ("" for a true first write, when the page doesn\'t exist yet) — the server compares it against the CURRENT hash and, on mismatch, fails loud with `{ok: false, body: {error: "stale_brief_page", currentHash}}` rather than silently overwriting a concurrent write — re-get, re-merge, and retry on that refusal, never retry blindly or overwrite. On success, persists to the DB, publishes `brief:updated` over SSE, and returns the new view. NO delete tool is exposed on this surface — the reserved `index` slug can never be deleted through the tool surface, matching the route\'s own refusal; deleting a non-index page is dashboard-UI-only for now.', {
|
|
539
550
|
slug: z
|
|
540
551
|
.string()
|
|
541
552
|
.min(1)
|
|
@@ -546,9 +557,9 @@ server.tool("master_plan_set_page", 'Write one Master Plan page via PUT /api/mas
|
|
|
546
557
|
base_hash: z
|
|
547
558
|
.string()
|
|
548
559
|
.optional()
|
|
549
|
-
.describe('The contentHash last read via
|
|
560
|
+
.describe('The contentHash last read via brief_get_page ("" for a true first write). Omitted also normalizes to "" server-side, so it only succeeds against an empty/absent page — always get immediately before set.'),
|
|
550
561
|
...boardField,
|
|
551
|
-
}, async (args) => jsonResult(await
|
|
562
|
+
}, async (args) => jsonResult(await briefSetPage(client, args)));
|
|
552
563
|
// ---------------- plans (DX-2683) ----------------
|
|
553
564
|
// THE BINDING ASYMMETRY IS IN THE SCHEMAS, NOT IN PROSE. `plan_get` takes an
|
|
554
565
|
// optional `plan_id`; `plan_add_record`, `plan_add_card` and
|
|
@@ -556,7 +567,7 @@ server.tool("master_plan_set_page", 'Write one Master Plan page via PUT /api/mas
|
|
|
556
567
|
// cannot even express "write to that other plan". `plan_connect` takes one
|
|
557
568
|
// because binding a session to a plan is the one operation that is ABOUT a
|
|
558
569
|
// plan id — and it can only ever bind the caller's own session.
|
|
559
|
-
server.tool("plan_list", "List every plan via GET /api/plans (DX-2683), and learn which plan THIS session is connected to. Plans are GLOBAL, not board-scoped: a plan is a named, dated set of cards an operator assembled by hand, and its cards may come from any repository. Returns `{ok, status, body: {plans: [{id, name, createdAt, cardCount, boards}], session}}`. `session` is your own registration — `{sessionId, title, planId, planName, firstSeenAt, lastActiveAt}` — or `null` if this process is not running inside a Claude Code session. A `planId` of null means you are connected to no plan: read any plan with `plan_get`, then `plan_connect` to the one you are working on (or ask the operator to connect you from the Plans list). NOTE this is NOT the board
|
|
570
|
+
server.tool("plan_list", "List every plan via GET /api/plans (DX-2683), and learn which plan THIS session is connected to. Plans are GLOBAL, not board-scoped: a plan is a named, dated set of cards an operator assembled by hand, and its cards may come from any repository. Returns `{ok, status, body: {plans: [{id, name, createdAt, cardCount, boards}], session}}`. `session` is your own registration — `{sessionId, title, planId, planName, firstSeenAt, lastActiveAt}` — or `null` if this process is not running inside a Claude Code session. A `planId` of null means you are connected to no plan: read any plan with `plan_get`, then `plan_connect` to the one you are working on (or ask the operator to connect you from the Plans list). NOTE this is NOT the board Brief (`brief_list`), which is a different feature entirely.", {}, async () => jsonResult(await planList(client)));
|
|
560
571
|
server.tool("plan_get", "Read one plan WHOLE via GET /api/plans (DX-2683) — its member cards (with the boards they cover), its goals, rules and caveats, its architecture document, the sessions working on it, and your own session state. One call, not five. Pass `plan_id` to read ANY plan (browsing another plan is useful and changes nothing); OMIT it to read the plan this session is connected to. Omitting it while connected to no plan fails loud with `{error: \"session_not_connected\"}` — connect first. Returns `{plan, cards, boards, records: {goal: [], rule: [], caveat: []}, architecture: {content, contentHash, updatedAt, updatedBy}, sessions, session}`. ALWAYS `plan_get` immediately before `plan_set_architecture` and pass the returned `architecture.contentHash` back as `base_hash`.", {
|
|
561
572
|
plan_id: z
|
|
562
573
|
.number()
|
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.49",
|
|
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",
|