@thehammer/danx-dashboard-mcp 0.1.3 → 0.1.6
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 +11 -11
- package/dist/http-client.js +1 -1
- package/dist/index.js +24 -24
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @thehammer/danx-dashboard-mcp
|
|
2
2
|
|
|
3
|
-
Stdio MCP server wrapping danxbot's dashboard `/api/
|
|
3
|
+
Stdio MCP server wrapping danxbot's dashboard `/api/issues/*` normalized DB-backed HTTP routes. Replaces the legacy "agents Edit/Write `.yml` files directly" pattern from before DX-704 / DX-811.
|
|
4
4
|
|
|
5
5
|
Each tool is a thin envelope over one HTTP route — Zod-validated at the MCP boundary, fetch under the hood, server response passed back to the agent verbatim. Refusal envelopes (`{error, ...extra}` with `failed_gate`, `non_terminal_phases`, `offending_keys`, etc.) come through as `{ok: false, status, body}` so the agent can pick the right next action without guessing. 5xx and network failures throw.
|
|
6
6
|
|
|
@@ -18,16 +18,16 @@ All exposed as `mcp__danx_dashboard__<name>` once wired through the workspace `.
|
|
|
18
18
|
|
|
19
19
|
| Tool | HTTP | Notes |
|
|
20
20
|
|---|---|---|
|
|
21
|
-
| `issue_list` | `GET /api/
|
|
22
|
-
| `issue_get` | `GET /api/
|
|
23
|
-
| `issue_create` | `POST /api/
|
|
24
|
-
| `issue_edit` | `PATCH /api/
|
|
25
|
-
| `issue_transition` | `POST /api/
|
|
26
|
-
| `issue_triage` | `POST /api/
|
|
27
|
-
| `issue_comment` | `POST/PATCH/DELETE /api/
|
|
28
|
-
| `issue_dependency` | `POST/DELETE /api/
|
|
29
|
-
| `issue_requires_human` | `POST/DELETE /api/
|
|
30
|
-
| `issue_retro` | `PUT /api/
|
|
21
|
+
| `issue_list` | `GET /api/issues` | filters: `type`, `parent_id` (null → root-only), `dispatchable_derived`, `assigned_agent`, `include_closed`, `limit`, `offset` |
|
|
22
|
+
| `issue_get` | `GET /api/issues/:id` | Returns hydrated card + ancestor chain |
|
|
23
|
+
| `issue_create` | `POST /api/issues` | Epic REQUIRES non-empty `phase_children[]` (atomic insert) |
|
|
24
|
+
| `issue_edit` | `PATCH /api/issues/:id/edit` | Prose-only — semantic keys refused with 400 + pointer to dedicated handler |
|
|
25
|
+
| `issue_transition` | `POST /api/issues/:id/transition` | Actions: ready, pickup, rollback_pickup, complete, cancel, block, unblock, archive, reopen |
|
|
26
|
+
| `issue_triage` | `POST /api/issues/:id/triage` | Verdicts: approve, cancel, keep, defer (with optional ICE + ttl_seconds) |
|
|
27
|
+
| `issue_comment` | `POST/PATCH/DELETE /api/issues/:id/comments[/:cid]` | Author server-stamped, soft-delete preserved |
|
|
28
|
+
| `issue_dependency` | `POST/DELETE /api/issues/:id/dependencies[/:did]` | `depends_on` cycle-checked; remove hardcodes `reason: "recorded_in_error"` |
|
|
29
|
+
| `issue_requires_human` | `POST/DELETE /api/issues/:id/requires-human` | Set replaces step rows atomically; clear soft-deletes them |
|
|
30
|
+
| `issue_retro` | `PUT /api/issues/:id/retro` | Requires terminal card; replace semantics |
|
|
31
31
|
|
|
32
32
|
## Build + test
|
|
33
33
|
|
package/dist/http-client.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -2,25 +2,25 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* @thehammer/danx-dashboard-mcp
|
|
4
4
|
*
|
|
5
|
-
* Stdio MCP server wrapping danxbot's dashboard `/api/
|
|
5
|
+
* Stdio MCP server wrapping danxbot's dashboard `/api/issues/*`
|
|
6
6
|
* normalized DB-backed HTTP routes (DX-704 Phase 2 / DX-811). Replaces
|
|
7
7
|
* the legacy "agents Edit/Write YAML files directly" pattern: every
|
|
8
8
|
* tool here POSTs/GETs against the dashboard, which atomically applies
|
|
9
|
-
* the change via the
|
|
9
|
+
* the change via the transactional layer and publishes SSE.
|
|
10
10
|
*
|
|
11
11
|
* Tool surface (all exposed as `mcp__danx_dashboard__<name>` once wired
|
|
12
12
|
* through the workspace `.mcp.json`):
|
|
13
13
|
*
|
|
14
|
-
* - issue_list GET /api/
|
|
15
|
-
* - issue_get GET /api/
|
|
16
|
-
* - issue_create POST /api/
|
|
17
|
-
* - issue_edit PATCH /api/
|
|
18
|
-
* - issue_transition POST /api/
|
|
19
|
-
* - issue_triage POST /api/
|
|
20
|
-
* - issue_comment POST/PATCH/DELETE /api/
|
|
21
|
-
* - issue_dependency POST/DELETE /api/
|
|
22
|
-
* - issue_requires_human POST/DELETE /api/
|
|
23
|
-
* - issue_retro PUT /api/
|
|
14
|
+
* - issue_list GET /api/issues
|
|
15
|
+
* - issue_get GET /api/issues/:id
|
|
16
|
+
* - issue_create POST /api/issues
|
|
17
|
+
* - issue_edit PATCH /api/issues/:id/edit
|
|
18
|
+
* - issue_transition POST /api/issues/:id/transition
|
|
19
|
+
* - issue_triage POST /api/issues/:id/triage
|
|
20
|
+
* - issue_comment POST/PATCH/DELETE /api/issues/:id/comments[/:cid]
|
|
21
|
+
* - issue_dependency POST/DELETE /api/issues/:id/dependencies[/:did]
|
|
22
|
+
* - issue_requires_human POST/DELETE /api/issues/:id/requires-human
|
|
23
|
+
* - issue_retro PUT /api/issues/:id/retro
|
|
24
24
|
*
|
|
25
25
|
* Env at boot (validated fail-loud — missing → process.exit(1)):
|
|
26
26
|
* DANXBOT_DASHBOARD_URL dashboard base (e.g. http://danxbot-dashboard:5555)
|
|
@@ -76,8 +76,8 @@ const EFFORT_VALUES = [
|
|
|
76
76
|
"very_high",
|
|
77
77
|
"max",
|
|
78
78
|
];
|
|
79
|
-
const ISSUE_TYPES = ["Epic", "Bug", "Feature", "Chore"];
|
|
80
|
-
const NON_EPIC_TYPES = ["Bug", "Feature", "Chore"];
|
|
79
|
+
const ISSUE_TYPES = ["Epic", "Bug", "Feature", "Story", "Chore"];
|
|
80
|
+
const NON_EPIC_TYPES = ["Bug", "Feature", "Story", "Chore"];
|
|
81
81
|
const TRANSITION_ACTIONS = [
|
|
82
82
|
"ready",
|
|
83
83
|
"pickup",
|
|
@@ -103,7 +103,7 @@ const repoField = {
|
|
|
103
103
|
.describe("Target another registered repo; omit to use this dispatch's repo. Unknown repo → 404."),
|
|
104
104
|
};
|
|
105
105
|
// ---------------- issue_list ----------------
|
|
106
|
-
server.tool("issue_list", "List issues for the dispatch's repo by default via GET /api/
|
|
106
|
+
server.tool("issue_list", "List issues for the dispatch's repo by default via GET /api/issues. Pass `repo` to list another registered repo instead (unknown repo → 404). Filters: type, parent_id (string id, null for root-only), dispatchable_derived (boolean — server-computed pickup-ready gate), assigned_agent, include_closed (default false — excludes completed_at/cancelled_at). Response body shape: {issues: Issue[]}. Server-side numeric-suffix ordering so DX-10 follows DX-9. Use this instead of grepping .danxbot/issues/ — the DB-backed route is the source of truth post-DX-704.", {
|
|
107
107
|
status_derived: z.string().optional(),
|
|
108
108
|
type: z.enum(ISSUE_TYPES).optional(),
|
|
109
109
|
parent_id: z.string().nullable().optional(),
|
|
@@ -115,12 +115,12 @@ server.tool("issue_list", "List issues for the dispatch's repo by default via GE
|
|
|
115
115
|
...repoField,
|
|
116
116
|
}, async (args) => jsonResult(await issueList(client, args)));
|
|
117
117
|
// ---------------- issue_get ----------------
|
|
118
|
-
server.tool("issue_get", "Fetch a single hydrated issue via GET /api/
|
|
118
|
+
server.tool("issue_get", "Fetch a single hydrated issue via GET /api/issues/:id. Issue ids are globally unique across repos, so this resolves from any dispatch regardless of `repo`. Returns the full card (every joined child collection: ac, comments, dependencies, requires_human steps, retro action items + commits, triage history) plus the ancestor chain walked via parent_id. 404 envelope on unknown id.", {
|
|
119
119
|
id: z.string().min(1),
|
|
120
120
|
...repoField,
|
|
121
121
|
}, async (args) => jsonResult(await issueGet(client, args)));
|
|
122
122
|
// ---------------- issue_create ----------------
|
|
123
|
-
server.tool("issue_create", "Create a fresh card via POST /api/
|
|
123
|
+
server.tool("issue_create", "Create a fresh card via POST /api/issues. Defaults to the dispatch's repo; pass `repo` to create the card in another registered repo (forwarded into body.repo + ?repo=; unknown repo → 404). INVARIANT: type=Epic REQUIRES non-empty phase_children[] (epic-with-phases atomicity per DX-575) and the route atomically inserts the epic + every phase in ONE transaction. Non-Epic types REFUSE phase_children[] with 400. Status defaults to Review (no lifecycle timestamps stamped on create). parent_id optional. ac items take {title}; phase children inherit the new epic's id as parent_id.", {
|
|
124
124
|
type: z.enum(ISSUE_TYPES),
|
|
125
125
|
title: z.string().min(1),
|
|
126
126
|
description: z.string(),
|
|
@@ -139,7 +139,7 @@ server.tool("issue_create", "Create a fresh card via POST /api/v2/issues. Defaul
|
|
|
139
139
|
...repoField,
|
|
140
140
|
}, async (args) => jsonResult(await issueCreate(client, args, config.repo)));
|
|
141
141
|
// ---------------- issue_edit ----------------
|
|
142
|
-
server.tool("issue_edit", "Patch prose fields only via PATCH /api/
|
|
142
|
+
server.tool("issue_edit", "Patch prose fields only via PATCH /api/issues/:id/edit. ALLOWED keys: title, description, ac, effort_level, parent_id. 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. AC replacement is wholesale soft-delete + reinsert with fresh ordinals; check_item_id linkage survives via title match.", {
|
|
143
143
|
id: z.string().min(1),
|
|
144
144
|
title: z.string().min(1).optional(),
|
|
145
145
|
description: z.string().optional(),
|
|
@@ -154,7 +154,7 @@ server.tool("issue_edit", "Patch prose fields only via PATCH /api/v2/issues/:id/
|
|
|
154
154
|
...repoField,
|
|
155
155
|
}, async (args) => jsonResult(await issueEdit(client, args)));
|
|
156
156
|
// ---------------- issue_transition ----------------
|
|
157
|
-
server.tool("issue_transition", "Stamp a lifecycle transition via POST /api/
|
|
157
|
+
server.tool("issue_transition", "Stamp a lifecycle transition via POST /api/issues/:id/transition. **THIS IS THE ONLY WAY TO MOVE A CARD'S LIFECYCLE STATE** — DX-835 separated card lifecycle (this tool) from dispatch finalization (`mcp__danxbot__danxbot_complete`). The worker no longer infers card moves from `danxbot_complete.status`; agents that want the card to move MUST call this tool BEFORE calling `danxbot_complete`. Actions: ready (Review→ToDo), pickup (ToDo→In Progress — server checks every dispatch gate: ready_at, blocked_at, requires_human_reason, depends_on partners terminal, conflict_on partners idle; refuses 409 with failed_gate naming the cause), rollback_pickup, **complete** (stamps completed_at — moves card to Done; this is YOUR explicit decision, not a side effect of danxbot_complete; REFUSES 409 on Epic if any phase child non-terminal — see non_terminal_phases[]), cancel (stamps cancelled_at — terminal), **block** (requires non-empty reason — stamps blocked_at + blocked_reason + clears dispatch; USE THIS when the CARD itself cannot proceed without human intervention; distinct from env-fault dispatch failures which use `danxbot_complete({status:'failed'})`), unblock, archive (parks to Backlog, clears ready_at), reopen (terminal→active, clears completed_at/cancelled_at/archived_at). Terminal cards refuse every action except reopen. Ladder timestamps preserved — forward stamps never clear earlier ones (CLAUDE.md Core Principle 2).", {
|
|
158
158
|
id: z.string().min(1),
|
|
159
159
|
action: z.enum(TRANSITION_ACTIONS),
|
|
160
160
|
reason: z.string().optional(),
|
|
@@ -163,7 +163,7 @@ server.tool("issue_transition", "Stamp a lifecycle transition via POST /api/v2/i
|
|
|
163
163
|
...repoField,
|
|
164
164
|
}, async (args) => jsonResult(await issueTransition(client, args)));
|
|
165
165
|
// ---------------- issue_triage ----------------
|
|
166
|
-
server.tool("issue_triage", "Record a triage verdict via POST /api/
|
|
166
|
+
server.tool("issue_triage", "Record a triage verdict via POST /api/issues/:id/triage. Verdicts: approve (stamps ready_at — moves to ToDo, clears triage TTL), cancel (stamps cancelled_at — terminal), keep (refreshes triage_expires_at by ttl_seconds — defaults 7 days, card stays at Review), defer (stamps archived_at, clears ready_at — parks to Backlog). ICE components optional but recorded when present (total = i+c+e). Reason is REQUIRED non-empty. REFUSES 409 on terminal cards.", {
|
|
167
167
|
id: z.string().min(1),
|
|
168
168
|
verdict: z.enum(TRIAGE_VERDICTS),
|
|
169
169
|
reason: z.string().min(1),
|
|
@@ -178,7 +178,7 @@ server.tool("issue_triage", "Record a triage verdict via POST /api/v2/issues/:id
|
|
|
178
178
|
...repoField,
|
|
179
179
|
}, async (args) => jsonResult(await issueTriage(client, args)));
|
|
180
180
|
// ---------------- issue_comment ----------------
|
|
181
|
-
server.tool("issue_comment", "Comment CRUD via /api/
|
|
181
|
+
server.tool("issue_comment", "Comment CRUD via /api/issues/:id/comments[/:cid]. action=add → POST {text} (server stamps author from bearer + auto-incrementing ordinal); action=edit → PATCH /:cid {text}; action=delete → DELETE /:cid (soft-delete, audit trail preserved — comments are NEVER hard-deleted). Client-supplied author is IGNORED (server-stamped to prevent impersonation).", {
|
|
182
182
|
id: z.string().min(1),
|
|
183
183
|
action: z.enum(["add", "edit", "delete"]),
|
|
184
184
|
comment_id: z.number().int().positive().optional(),
|
|
@@ -186,7 +186,7 @@ server.tool("issue_comment", "Comment CRUD via /api/v2/issues/:id/comments[/:cid
|
|
|
186
186
|
...repoField,
|
|
187
187
|
}, async (args) => jsonResult(await issueComment(client, args)));
|
|
188
188
|
// ---------------- issue_dependency ----------------
|
|
189
|
-
server.tool("issue_dependency", "Dependency CRUD via /api/
|
|
189
|
+
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.", {
|
|
190
190
|
id: z.string().min(1),
|
|
191
191
|
action: z.enum(["add", "remove"]),
|
|
192
192
|
kind: z.enum(["depends_on", "conflict_on"]).optional(),
|
|
@@ -196,7 +196,7 @@ server.tool("issue_dependency", "Dependency CRUD via /api/v2/issues/:id/dependen
|
|
|
196
196
|
...repoField,
|
|
197
197
|
}, async (args) => jsonResult(await issueDependency(client, args)));
|
|
198
198
|
// ---------------- issue_requires_human ----------------
|
|
199
|
-
server.tool("issue_requires_human", "Set or clear the requires_human dispatch gate via /api/
|
|
199
|
+
server.tool("issue_requires_human", "Set or clear the requires_human dispatch gate via /api/issues/:id/requires-human. set=true → POST {reason, steps[]} — sets requires_human_reason (the dispatch gate per DX-704 — poller refuses pickup while non-null), set_by from bearer, set_at NOW(), REPLACES the step rows (prior soft-deleted, fresh ordinals). set=false → DELETE — clears the columns and soft-deletes every live step. Terminal cards refuse 409 on set.", {
|
|
200
200
|
id: z.string().min(1),
|
|
201
201
|
set: z.boolean(),
|
|
202
202
|
reason: z.string().optional(),
|
|
@@ -204,7 +204,7 @@ server.tool("issue_requires_human", "Set or clear the requires_human dispatch ga
|
|
|
204
204
|
...repoField,
|
|
205
205
|
}, async (args) => jsonResult(await issueRequiresHuman(client, args)));
|
|
206
206
|
// ---------------- issue_retro ----------------
|
|
207
|
-
server.tool("issue_retro", "Replace the retro block via PUT /api/
|
|
207
|
+
server.tool("issue_retro", "Replace the retro block via PUT /api/issues/:id/retro. Body: {good, bad, action_item_ids[], commits[]}. REFUSES 409 unless the card is terminal (completed_at OR cancelled_at) — retro ships when work concludes. Replace semantics: good/bad upsert; action_item_ids[] + commits[] soft-delete prior live rows and insert with fresh ordinals. action_item_ids[] entries MUST match <PREFIX>-N. commits[] entries take {sha, subject?}.", {
|
|
208
208
|
id: z.string().min(1),
|
|
209
209
|
good: z.string(),
|
|
210
210
|
bad: z.string(),
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thehammer/danx-dashboard-mcp",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Stdio MCP server wrapping danxbot's dashboard /api/
|
|
3
|
+
"version": "0.1.6",
|
|
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",
|
|
7
7
|
"main": "dist/index.js",
|