@thehammer/danx-dashboard-mcp 0.1.152 → 0.1.156
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 +1 -1
- package/dist/handlers.js +14 -49
- package/dist/index.js +9 -41
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@ All exposed as `mcp__danx-dashboard__<name>` once wired through the workspace `m
|
|
|
21
21
|
|
|
22
22
|
| Tool | HTTP | Notes |
|
|
23
23
|
|---|---|---|
|
|
24
|
-
| `issue_list` | `GET /api/issues` | filters: `type`, `parent_id` (null → root-only), `dispatchable_derived`, `assigned_agent`, `include_closed`, `limit`, `offset` |
|
|
24
|
+
| `issue_list` | `GET /api/issues` | filters: `type`, `parent_id` (null → root-only), `dispatchable_derived`, `assigned_agent`, `include_closed`, `limit`, `offset`. `fields` (DX-3427) is the SAME recursive field tree `issue_get` takes, against the same issue resource — list-row fields (`ac_total`, `comments_count`, `waiting_on`, `quality_gates`, `next_pre_gate`, …) sit alongside every detail field. See `resource_fields` |
|
|
25
25
|
| `issue_get` | `GET /api/issues/:id` or `GET /api/issues/batch` | Pass `id` for one card, or `ids[]` (DX-2727, at most 100) to resolve many across boards in ONE call — global, so `ids` with `board` throws; per-id `not_found` rather than a whole-call 404. Minimal scalars by default; `fields` (DX-3426) opts in via a recursive field TREE — `{"description": true, "problems": {"solutions": true}, "comments": {"limit": 10}}` — not a flat group list; a cursor-paged relation (today only `comments`) takes its page args (`limit`/`before`) inside its own nested object, so paging now works on the batch form too. See `resource_fields` |
|
|
26
26
|
| `resource_fields` | `GET /api/resources/:resource/fields` | DX-3426 — what an `issue_get`-style field tree may name for one resource: `{resource, description, always, hashes, fields, relations}`. Install-global, no `board`. `resource: "issue"` is the root; a relation's own `resource` in the response is what to call this again with, one level deeper |
|
|
27
27
|
| `issue_create` | `POST /api/issues` | Epic REQUIRES non-empty `phase_children[]` (atomic insert). `title` = short domain-naming label; `summary` = 1–3 plain-language sentences, always shown; `description` = the collapsed "Context" body. Root and every phase child take their own `summary` |
|
package/dist/handlers.js
CHANGED
|
@@ -25,19 +25,21 @@ import { readFile } from "node:fs/promises";
|
|
|
25
25
|
import { basename, extname, isAbsolute } from "node:path";
|
|
26
26
|
import { resolvePriority } from "./priority.js";
|
|
27
27
|
/**
|
|
28
|
-
* `filter`/`fields`/`sort` are JSON
|
|
29
|
-
*
|
|
30
|
-
* `src/issues/read/reader.ts#parseEnvelope`); each is
|
|
31
|
-
* empty so the wire carries no `{}`/`[]` noise. `board`
|
|
32
|
-
* top-level param, resolved by the HTTP client exactly as
|
|
28
|
+
* `filter`/`fields`/`sort` are JSON-encoded onto the query string (the route
|
|
29
|
+
* parses `?filter=<JSON>`, `?fields=<JSON>`, `?sort=<JSON>` — see
|
|
30
|
+
* `src/issues/read/reader.ts#parseEnvelope` + `handleGetIssues`); each is
|
|
31
|
+
* omitted entirely when empty so the wire carries no `{}`/`[]` noise. `board`
|
|
32
|
+
* stays a flat top-level param, resolved by the HTTP client exactly as
|
|
33
|
+
* before.
|
|
33
34
|
*/
|
|
34
35
|
export async function issueList(client, args) {
|
|
35
36
|
const query = {};
|
|
36
37
|
if (args.filter !== undefined && Object.keys(args.filter).length > 0) {
|
|
37
38
|
query.filter = JSON.stringify(args.filter);
|
|
38
39
|
}
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
// DX-3427 — a JSON-encoded field tree, mirroring `issue_get` below.
|
|
41
|
+
if (args.fields !== undefined && Object.keys(args.fields).length > 0) {
|
|
42
|
+
query.fields = JSON.stringify(args.fields);
|
|
41
43
|
}
|
|
42
44
|
if (args.sort !== undefined && args.sort.length > 0) {
|
|
43
45
|
query.sort = JSON.stringify(args.sort);
|
|
@@ -124,47 +126,11 @@ export async function issueGet(client, args) {
|
|
|
124
126
|
board: args.board,
|
|
125
127
|
});
|
|
126
128
|
}
|
|
127
|
-
// ---------------- repo_knowledge_get / repo_knowledge_set ----------------
|
|
128
|
-
const REPO_KNOWLEDGE_BASE_PATH = "/api/repo-knowledge";
|
|
129
|
-
/**
|
|
130
|
-
* Fetch the board's working-knowledge doc via GET /api/repo-knowledge
|
|
131
|
-
* (DX-1128, Story 2). Mirrors `issueGet` — a bare board-scoped GET, no id
|
|
132
|
-
* (the doc is 1-per-board). Board resolves the same way every other tool's
|
|
133
|
-
* `board` arg does: per-call override, else the dispatch's env-derived board.
|
|
134
|
-
*/
|
|
135
|
-
export async function repoKnowledgeGet(client, args = {}) {
|
|
136
|
-
return client.request({
|
|
137
|
-
method: "GET",
|
|
138
|
-
path: "",
|
|
139
|
-
basePath: REPO_KNOWLEDGE_BASE_PATH,
|
|
140
|
-
board: args.board,
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
/**
|
|
144
|
-
* Write the board's working-knowledge doc via PUT /api/repo-knowledge
|
|
145
|
-
* (DX-1128, Story 2). Mirrors `issueEdit`'s shape (a PATCH-like body write)
|
|
146
|
-
* but targets the repo-knowledge route family, not `/api/issues`. The
|
|
147
|
-
* server's optimistic-concurrency guard rejects a stale `base_hash` — the
|
|
148
|
-
* refusal envelope (`{ok: false, body: {error, currentHash}}`) passes
|
|
149
|
-
* through verbatim so the caller can re-get, re-merge, and retry.
|
|
150
|
-
*/
|
|
151
|
-
export async function repoKnowledgeSet(client, args) {
|
|
152
|
-
const { board, ...body } = args;
|
|
153
|
-
return client.request({
|
|
154
|
-
method: "PUT",
|
|
155
|
-
path: "",
|
|
156
|
-
basePath: REPO_KNOWLEDGE_BASE_PATH,
|
|
157
|
-
body,
|
|
158
|
-
board,
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
129
|
// ---------------- brief_list / brief_get_page / brief_set_page ----------------
|
|
162
130
|
const BRIEF_BASE_PATH = "/api/brief";
|
|
163
131
|
/**
|
|
164
132
|
* List the board's named Brief pages via GET /api/brief (DX-2083).
|
|
165
|
-
*
|
|
166
|
-
* targets the list-shaped sibling surface — many named pages per board,
|
|
167
|
-
* not one document.
|
|
133
|
+
* A bare board-scoped GET, no id — many named pages per board.
|
|
168
134
|
*/
|
|
169
135
|
export async function briefList(client, args = {}) {
|
|
170
136
|
return client.request({
|
|
@@ -177,7 +143,7 @@ export async function briefList(client, args = {}) {
|
|
|
177
143
|
/**
|
|
178
144
|
* Fetch one Brief page by slug via GET /api/brief/page?slug=
|
|
179
145
|
* (DX-2083). A missing row (including a not-yet-created page) reads as the
|
|
180
|
-
*
|
|
146
|
+
* empty view — never a 404.
|
|
181
147
|
*/
|
|
182
148
|
export async function briefGetPage(client, args) {
|
|
183
149
|
return client.request({
|
|
@@ -190,10 +156,9 @@ export async function briefGetPage(client, args) {
|
|
|
190
156
|
}
|
|
191
157
|
/**
|
|
192
158
|
* Write one Brief page via PUT /api/brief/page?slug= (DX-2083).
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
* query string (matching the route), never the body.
|
|
159
|
+
* Optimistic-concurrency `base_hash` with a verbatim 409 passthrough on a
|
|
160
|
+
* stale write. `slug` rides the query string (matching the route), never
|
|
161
|
+
* the body.
|
|
197
162
|
*/
|
|
198
163
|
export async function briefSetPage(client, args) {
|
|
199
164
|
const { slug, board, ...body } = args;
|
package/dist/index.js
CHANGED
|
@@ -34,8 +34,6 @@
|
|
|
34
34
|
* - issue_attach POST /api/issues/:id/attachments (reads a local file)
|
|
35
35
|
* - quality_gate_instruction
|
|
36
36
|
* GET /api/quality-gates/:gate/instruction (DX-3341, not card-scoped)
|
|
37
|
-
* - repo_knowledge_get GET /api/repo-knowledge
|
|
38
|
-
* - repo_knowledge_set PUT /api/repo-knowledge (DX-1128, Story 2)
|
|
39
37
|
* - brief_list GET /api/brief
|
|
40
38
|
* - brief_get_page GET /api/brief/page (DX-2083 / DX-2484)
|
|
41
39
|
* - brief_set_page PUT /api/brief/page (DX-2083 / DX-2484)
|
|
@@ -110,7 +108,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
110
108
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
111
109
|
import { z } from "zod";
|
|
112
110
|
import { DashboardHttpClient } from "./http-client.js";
|
|
113
|
-
import { issueAttach, issueChecklist, issueComment, issueCreate, issueDependency, issueEdit, issueGet, issueList, issueProblem, issueQualityGate, issueQualityGateVerdict, issueRetireBranch, issueRetro, issueSolution, issueTransition, issueTriage, briefGetPage, briefList, briefSetPage, failureCategoryCreate, failureCategoryList, failureCategoryUpdate, dispatchTranscriptSearch, planAddArchitectureSection, planAddCard, planAddNote, planAddRecord, planConnect, planCreate, planDeleteArchitectureSection, planDeleteNote, planDeleteRecord, planGet, PLAN_FIELD_GROUPS, PLAN_EVENT_KINDS, PLAN_EVENT_ORIGINS, PLAN_STATUSES, ISSUE_BATCH_GET_MAX, LIST_PAGE_MAX_LIMIT, PLAN_GET_CARDS_DEFAULT_LIMIT, PLAN_GET_EVENTS_DEFAULT_LIMIT, PLAN_GET_EVENTS_MAX_LIMIT, planGetArchitectureSection, planGetRecord, planList, planRemoveCard, planRename, planReorderArchitectureSection, planUpdateArchitectureSection, planUpdateNote, planUpdateRecord,
|
|
111
|
+
import { issueAttach, issueChecklist, issueComment, issueCreate, issueDependency, issueEdit, issueGet, issueList, issueProblem, issueQualityGate, issueQualityGateVerdict, issueRetireBranch, issueRetro, issueSolution, issueTransition, issueTriage, briefGetPage, briefList, briefSetPage, failureCategoryCreate, failureCategoryList, failureCategoryUpdate, dispatchTranscriptSearch, planAddArchitectureSection, planAddCard, planAddNote, planAddRecord, planConnect, planCreate, planDeleteArchitectureSection, planDeleteNote, planDeleteRecord, planGet, PLAN_FIELD_GROUPS, PLAN_EVENT_KINDS, PLAN_EVENT_ORIGINS, PLAN_STATUSES, ISSUE_BATCH_GET_MAX, LIST_PAGE_MAX_LIMIT, PLAN_GET_CARDS_DEFAULT_LIMIT, PLAN_GET_EVENTS_DEFAULT_LIMIT, PLAN_GET_EVENTS_MAX_LIMIT, planGetArchitectureSection, planGetRecord, planList, planRemoveCard, planRename, planReorderArchitectureSection, planUpdateArchitectureSection, planUpdateNote, planUpdateRecord, qualityGateInstruction, resourceFields, } from "./handlers.js";
|
|
114
112
|
import { PRIORITY_TIER_WORDS } from "./priority.js";
|
|
115
113
|
import { fieldTreeSchema } from "./field-tree.js";
|
|
116
114
|
function readEnvOrDie(name) {
|
|
@@ -277,25 +275,6 @@ const EFFORT_VALUES = [
|
|
|
277
275
|
// still be CREATED through this MCP.
|
|
278
276
|
const ISSUE_TYPES = ["Epic", "Bug", "Feature", "Story", "Chore", "Task"];
|
|
279
277
|
const NON_EPIC_TYPES = ["Bug", "Feature", "Story", "Chore", "Task"];
|
|
280
|
-
// DX-935 / DX-937 — field-group taxonomy for the LIST read envelope,
|
|
281
|
-
// hand-copied from `src/issues/read/field-groups.ts` (`LIST_GROUPS` — this
|
|
282
|
-
// package cannot import server source). Drift surfaces at runtime as a server
|
|
283
|
-
// 400, not silently. DX-3426 moved `issue_get` to the field tree
|
|
284
|
-
// (`field-tree.ts`); DX-3427 moves the list the same way and deletes this.
|
|
285
|
-
const LIST_FIELD_GROUPS = [
|
|
286
|
-
"description",
|
|
287
|
-
// DX-2735: replaced the flat "solutions" group (hard cut, no alias).
|
|
288
|
-
"problems",
|
|
289
|
-
"ac",
|
|
290
|
-
"comments",
|
|
291
|
-
"retro",
|
|
292
|
-
"dependencies",
|
|
293
|
-
"triage",
|
|
294
|
-
"assignment",
|
|
295
|
-
"quality_gates",
|
|
296
|
-
"children",
|
|
297
|
-
"effort",
|
|
298
|
-
];
|
|
299
278
|
const SORT_ORDERS = ["asc", "desc"];
|
|
300
279
|
const sortField = z
|
|
301
280
|
.array(z.object({
|
|
@@ -360,7 +339,7 @@ const MARKDOWN_STYLE_DESCRIBE = "Renders as markdown here. Use `##`/`###` header
|
|
|
360
339
|
strictTool("issue_list",
|
|
361
340
|
// DX-2735: trimmed to pay for the problem tools inside the work-profile
|
|
362
341
|
// injected-surface budget — same facts, no repeated prose.
|
|
363
|
-
"List cards. Board-scoped; see `board`. `filter`: type, parent_id, dispatchable_derived, status_derived[], self_dispatchable_derived, assigned_agent, include_closed, include_deleted, q (free text over id+title+description). THE DEFAULT ROW IS MINIMAL — scalars only (id, type, title, status, parent_id, priority, created_at, updated_at, assigned_agent, content_hash), no joins;
|
|
342
|
+
"List cards. Board-scoped; see `board`. `filter`: type, parent_id, dispatchable_derived, status_derived[], self_dispatchable_derived, assigned_agent, include_closed, include_deleted, q (free text over id+title+description). THE DEFAULT ROW IS MINIMAL — scalars only (id, type, title, status, parent_id, priority, created_at, updated_at, assigned_agent, content_hash), no joins; `fields` opts into more — the SAME recursive field-tree JSON `issue_get` takes, against the SAME issue resource (list-row fields like `ac_total`/`comments_count`/`waiting_on`/`quality_gates`/`next_pre_gate` sit alongside every detail field `issue_get` can name). Call `resource_fields({resource:\"issue\"})` for the full, current list of what `fields` may name — never guess a name; unknown → 400 `unknown_field`. `sort`: [{column, order}] over id|priority|repo_name|title|type|status_derived|triage_ice_total|created_at|updated_at (default order: see `sort`'s own field description). `limit`/`offset` page (uncapped by default). `include_closed` defaults to FALSE: a bare call silently excludes every Done/Cancelled card (leaf AND container alike). The response always carries `total` (the full count matching every filter except limit/offset — compare against `issues.length` to tell an exhausted list from a truncated one) and, whenever `include_closed` was not explicitly `true`, `closed_excluded` (how many additional terminal cards the default withheld — re-call with `include_closed: true` to see them). issue_get reads one card in full.", {
|
|
364
343
|
filter: z
|
|
365
344
|
.object({
|
|
366
345
|
q: z.string().optional(),
|
|
@@ -381,10 +360,12 @@ strictTool("issue_list",
|
|
|
381
360
|
// its own `.strict()`.
|
|
382
361
|
.strict()
|
|
383
362
|
.optional(),
|
|
384
|
-
fields
|
|
385
|
-
|
|
363
|
+
// DX-3427 — a field TREE, exactly like `issue_get`'s `fields` below
|
|
364
|
+
// (against the same shared issue resource) — replaces the retired
|
|
365
|
+
// CSV field-GROUP list.
|
|
366
|
+
fields: fieldTreeSchema
|
|
386
367
|
.optional()
|
|
387
|
-
.describe("
|
|
368
|
+
.describe("A field tree (see tool description); absent/empty = minimal scalars. `resource_fields({resource:\"issue\"})` names every valid key."),
|
|
388
369
|
sort: sortField,
|
|
389
370
|
limit: z.number().int().positive().max(LIST_PAGE_MAX_LIMIT).optional(),
|
|
390
371
|
offset: z.number().int().nonnegative().optional(),
|
|
@@ -891,21 +872,8 @@ strictTool("issue_attach", "Attach a LOCAL file to an issue card: `id` (the card
|
|
|
891
872
|
.describe("Absolute path to a local file on the dispatch's shared filesystem (must start with `/`)."),
|
|
892
873
|
...boardField,
|
|
893
874
|
}, async (args) => jsonResult(await issueAttach(client, args)));
|
|
894
|
-
// ---------------- repo_knowledge_get ----------------
|
|
895
|
-
strictTool("repo_knowledge_get", "Fetch the board's working-knowledge markdown doc. Board-scoped; see `board`. Returns `{ok, status, body: {content, contentHash, updatedAt, updatedBy, boardId}}` — an unset doc reads as the empty view (content/contentHash `\"\"`), not a 404. Before `repo_knowledge_set`, always get immediately first and pass `contentHash` back as `base_hash` — the concurrency guard rejects a stale write.", {
|
|
896
|
-
...boardField,
|
|
897
|
-
}, async (args) => jsonResult(await repoKnowledgeGet(client, args)));
|
|
898
|
-
// ---------------- repo_knowledge_set ----------------
|
|
899
|
-
strictTool("repo_knowledge_set", 'Write the board\'s working-knowledge markdown doc. Board-scoped; see `board`. `base_hash` must be the `contentHash` from the immediately-prior `repo_knowledge_get` ("" for the true first write). On mismatch, fails loud with `{error: "stale_repo_knowledge", currentHash}` rather than overwriting — re-get, re-merge, retry with the new hash. On success persists to the DB, publishes `repo-knowledge:updated` over SSE, and returns the new view.', {
|
|
900
|
-
content: z.string(),
|
|
901
|
-
base_hash: z
|
|
902
|
-
.string()
|
|
903
|
-
.optional()
|
|
904
|
-
.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.'),
|
|
905
|
-
...boardField,
|
|
906
|
-
}, async (args) => jsonResult(await repoKnowledgeSet(client, args)));
|
|
907
875
|
// ---------------- brief_list ----------------
|
|
908
|
-
strictTool("brief_list", "List the board's named Brief pages. Board-scoped; see `board`. Returns `{boardId, pages: [{slug, title, contentHash, sortOrder, updatedAt, updatedBy}]}` — metadata only, no content (use `brief_get_page`).
|
|
876
|
+
strictTool("brief_list", "List the board's named Brief pages. Board-scoped; see `board`. Returns `{boardId, pages: [{slug, title, contentHash, sortOrder, updatedAt, updatedBy}]}` — metadata only, no content (use `brief_get_page`). Brief pages are MANY named pages per board (Goals/Architecture/Rules/Caveats tabs), keyed by (board, slug). The reserved `index` slug always exists.", {
|
|
909
877
|
...boardField,
|
|
910
878
|
}, async (args) => jsonResult(await briefList(client, args)));
|
|
911
879
|
// ---------------- brief_get_page ----------------
|
|
@@ -917,7 +885,7 @@ strictTool("brief_get_page", 'Fetch one Brief page by slug. Board-scoped; see `b
|
|
|
917
885
|
...boardField,
|
|
918
886
|
}, async (args) => jsonResult(await briefGetPage(client, args)));
|
|
919
887
|
// ---------------- brief_set_page ----------------
|
|
920
|
-
strictTool("brief_set_page", 'Write one Brief page. Board-scoped; see `board`. Body: `{content, title?, sortOrder?, base_hash?}` —
|
|
888
|
+
strictTool("brief_set_page", 'Write one Brief page. Board-scoped; see `board`. Body: `{content, title?, sortOrder?, base_hash?}` — optimistic-concurrency, targeting one named page. `base_hash` must be the `contentHash` from the immediately-prior `brief_get_page` (`""` for a true first write). On mismatch fails loud with `{error: "stale_brief_page", currentHash}` — re-get, re-merge, retry, never overwrite blindly. On success persists, publishes `brief:updated` over SSE, and returns the new view. No delete tool on this surface; the reserved `index` slug is never deletable — removing a non-index page is dashboard-UI-only.', {
|
|
921
889
|
slug: z
|
|
922
890
|
.string()
|
|
923
891
|
.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.156",
|
|
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",
|