@thehammer/danx-dashboard-mcp 0.1.165 → 0.1.168
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 +2 -1
- package/dist/http-client.js +18 -10
- package/dist/index.js +8 -4
- package/package.json +2 -2
- package/dist/field-tree.js +0 -45
- package/dist/priority.js +0 -70
package/README.md
CHANGED
|
@@ -24,7 +24,8 @@ All exposed as `mcp__danx-dashboard__<name>` once wired through the workspace `m
|
|
|
24
24
|
| `danxbot_api` | `{method, path, query?, body?}` — calls any dashboard route the caller's credential may reach (card/plan CRUD, comments, dependencies, problems/solutions, quality gates, retro, triage, transitions, …). Returns `{ok, status, body}`; `body` carries the route's own response, or its structured error verbatim when `ok:false`. |
|
|
25
25
|
| `danxbot_api_list` | No args. Lists the routes this caller's credential may reach, one line each (`{method, path, summary, permission}`) — call this first when you don't know the route for what you need to do. |
|
|
26
26
|
| `danxbot_api_spec` | `{endpoints}` (comma-joined `METHOD /path` list, required, min 1). Returns the exact OpenAPI contract for those routes — call this when you know the route but not its exact request/response shape. |
|
|
27
|
-
|
|
27
|
+
|
|
28
|
+
There is no dedicated `resource_fields` tool (removed DX-3465 — it was a thin wrapper `danxbot_api` already reaches with no agent-specific behavior earning it its own tool). What an opt-in field TREE may name for one resource (`{resource, description, always, hashes, fields, relations}`) is `danxbot_api({method:"GET", path:"/api/resources/<resource>/fields"})` — install-global, no `board`; `resource: "issue"` is the root, and a relation's own `resource` in the response is what to call this again with, one level deeper.
|
|
28
29
|
|
|
29
30
|
A dedicated tool survives only for something about the agent itself, never a card/plan operation: `plan_connect` (binds the session to a plan; the event-bridge hook keys on it) and the per-dispatch worker callbacks (`danxbot_complete`, `danxbot_slack_reply`, `danxbot_slack_post_update`, `load_template_app`/`save_template_app`, `quality_gate_complete`, `link_thread_to_issue`, `attach_file`, the `slack_recovery_*` trio, `danxbot_set_evaluator_summary`) — see `src/lifecycle.ts`. The live list is always `tool-defs.json` in this package, regenerated by `npm run gen-tool-defs` — read it rather than trusting a list copied into prose.
|
|
30
31
|
|
package/dist/http-client.js
CHANGED
|
@@ -102,16 +102,24 @@ export class DashboardHttpClient {
|
|
|
102
102
|
for (const [k, v] of Object.entries(extraQuery)) {
|
|
103
103
|
if (v === undefined || v === null)
|
|
104
104
|
continue;
|
|
105
|
-
// DX-3465 (code-quality gate finding, at
|
|
106
|
-
// (`fields`,
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
//
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
//
|
|
114
|
-
|
|
105
|
+
// DX-3465 round 2 (code-quality gate finding, at 27c9e4005) — a
|
|
106
|
+
// field TREE (`fields`, a plain object) is the only query param the
|
|
107
|
+
// server parses as JSON (`src/resources/field-tree.ts`); every
|
|
108
|
+
// LIST-valued param it has (`ids` on `GET /api/issues/batch`,
|
|
109
|
+
// `status` on the boards list, `endpoints` on `GET /api/openapi`)
|
|
110
|
+
// splits on commas instead. Round 1 JSON-encoded both objects and
|
|
111
|
+
// arrays alike, which silently broke every array-valued param: the
|
|
112
|
+
// server saw a JSON literal where it expected a comma list and
|
|
113
|
+
// returned a wrong-but-200 answer (e.g. `ids=["DX-1","DX-2"]`
|
|
114
|
+
// resolves zero cards) instead of the old loud zod refusal. Arrays
|
|
115
|
+
// are `typeof "object"` too, so they must be checked BEFORE the
|
|
116
|
+
// plain-object branch. A plain scalar still stringifies the same
|
|
117
|
+
// way it always did.
|
|
118
|
+
params.set(k, Array.isArray(v)
|
|
119
|
+
? v.map((item) => String(item)).join(",")
|
|
120
|
+
: typeof v === "object"
|
|
121
|
+
? JSON.stringify(v)
|
|
122
|
+
: String(v));
|
|
115
123
|
}
|
|
116
124
|
}
|
|
117
125
|
return `${base}${basePath}${cleanPath}?${params.toString()}`;
|
package/dist/index.js
CHANGED
|
@@ -338,13 +338,17 @@ strictTool("danxbot_api", "Call any dashboard API route your credential may reac
|
|
|
338
338
|
.startsWith("/api/")
|
|
339
339
|
.describe('Full path starting with "/api/" — never a route template.'),
|
|
340
340
|
// DX-3465 (code-quality gate finding, at 275f82c0d) — a query value may
|
|
341
|
-
// be a field TREE (a plain object
|
|
341
|
+
// be a field TREE (a plain object, e.g.
|
|
342
342
|
// `{"description":true,"comments":{"limit":20}}`), not just a scalar —
|
|
343
343
|
// SERVER_INSTRUCTIONS below says "ALWAYS pass `fields` … a field tree"
|
|
344
344
|
// and the OpenAPI spec agrees (`fields`'s schema is `{type:"object"}`),
|
|
345
345
|
// so the schema here must accept what those two tell the caller to
|
|
346
|
-
// send. `
|
|
347
|
-
//
|
|
346
|
+
// send. It may also be an array (e.g. `ids` on the batch-get route) —
|
|
347
|
+
// DX-3465 round 2 (code-quality gate finding, at 27c9e4005) —
|
|
348
|
+
// `DashboardHttpClient#buildUrl` JSON-encodes a plain-object value but
|
|
349
|
+
// comma-joins an array, matching the two shapes the server actually
|
|
350
|
+
// parses (a JSON field tree vs. a comma-separated list param); a scalar
|
|
351
|
+
// serializes as before.
|
|
348
352
|
query: z
|
|
349
353
|
.record(z.union([
|
|
350
354
|
z.string(),
|
|
@@ -420,7 +424,7 @@ strictTool("attach_file", "Attach a LOCAL file to a card: `card_id` + `path` (an
|
|
|
420
424
|
// no existing plan for an id to name yet.
|
|
421
425
|
strictTool("plan_connect",
|
|
422
426
|
// DX-2735: trimmed with the problem tools to stay inside the work-profile budget.
|
|
423
|
-
"Connect THIS session to a plan. ONE CALL IS ENOUGH TO START: the reply carries `{session, movedFrom, browserInstruction, briefing, listenerHealth}` — `browserInstruction` is a server-built action to take NOW: it names the plan's URL and tells you to open it (in-app browser if you have one, else default), keep that tab open for the whole session without navigating it away, and use a different tab for your own browsing — the operator's tab for following and talking to you. Returned on every connect, including a re-connect, so it doubles as the post-context-loss reminder. `briefing` is every goal/rule/caveat (ref+body), every architecture section (id+title), the plan's own ref/name/status, a first page of open cards (id/type/status/title/openProblemCount/assignedAgent) with a `morePagesHint` when more exist, and the closed-card count — usually replacing the plan read + card batch-read a fresh session used to need. A session is on AT MOST ONE plan: connecting elsewhere MOVES it, and `movedFrom: {id, name} | null` says which plan it left (null = no plan, or already this one). Binds only your OWN session, resolved from the session id this server forwards; afterwards
|
|
427
|
+
"Connect THIS session to a plan. ONE CALL IS ENOUGH TO START: the reply carries `{session, movedFrom, browserInstruction, briefing, listenerHealth}` — `browserInstruction` is a server-built action to take NOW: it names the plan's URL and tells you to open it (in-app browser if you have one, else default), keep that tab open for the whole session without navigating it away, and use a different tab for your own browsing — the operator's tab for following and talking to you. Returned on every connect, including a re-connect, so it doubles as the post-context-loss reminder. `briefing` is every goal/rule/caveat (ref+body), every architecture section (id+title), the plan's own ref/name/status, a first page of open cards (id/type/status/title/openProblemCount/assignedAgent) with a `morePagesHint` when more exist, and the closed-card count — usually replacing the plan read + card batch-read a fresh session used to need. A session is on AT MOST ONE plan: connecting elsewhere MOVES it, and `movedFrom: {id, name} | null` says which plan it left (null = no plan, or already this one). Binds only your OWN session, resolved from the session id this server forwards; afterwards a `danxbot_api` call to a `/api/plans/mine/...` route (there is no dedicated plan-write tool) acts on this plan and takes no plan id. `listenerHealth` reports the event bridge's state — `null` (no session), or `{attached, state: \"unattached\"|\"credential_mismatch\"|\"healthy\", nextStep}` naming a concrete fix per unhealthy state (same shape the plan list/get routes report). Once healthy, every comment, answer, problem added and block/unblock on this plan's cards reaches the session unpolled, relayed by the plugin's event bridge as a line like `[DX-8 \"Title\" repo:board] newms87 answered \"<statement>\": chose \"Pause E2E\"`. Pass `title` (call `get_session({session_id:\"self\"})` first and forward its `title` verbatim) so the dashboard shows the same name Claude does — this server cannot read it itself. The response's `reminders[]` (when present) carries the mantra — read and follow it.", {
|
|
424
428
|
plan_id: z.number().int().positive().describe("The plan id, from the plans list (`danxbot_api` `GET /api/plans`)."),
|
|
425
429
|
title: z
|
|
426
430
|
.string()
|
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.168",
|
|
4
4
|
"description": "Stdio MCP server fronting danxbot's dashboard API for agents (operator sessions and dispatched workers alike): the generic danxbot_api/danxbot_api_list/danxbot_api_spec tools reach any route the caller's credential may call, plus dedicated agent-specific tools (plan_connect, danxbot_complete, and other per-dispatch lifecycle callbacks) (DX-3465, PLN-11 R-25).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"node": ">=20"
|
|
22
22
|
},
|
|
23
23
|
"scripts": {
|
|
24
|
-
"build": "tsc -p tsconfig.json && node -e \"require('fs').chmodSync('dist/index.js', 0o755)\"",
|
|
24
|
+
"build": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.json && node -e \"require('fs').chmodSync('dist/index.js', 0o755)\"",
|
|
25
25
|
"verify:boot": "bash scripts/verify-boot.sh",
|
|
26
26
|
"start": "node dist/index.js",
|
|
27
27
|
"dev": "tsx src/index.ts",
|
package/dist/field-tree.js
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* DX-3426 — the shared recursive FIELD-TREE shape.
|
|
3
|
-
*
|
|
4
|
-
* The dashboard's single-card, batch card and plan reads (`danxbot_api`
|
|
5
|
-
* against `GET /api/issues/:id`, `GET /api/issues`, `GET /api/plans/:planId`
|
|
6
|
-
* — DX-3428) take a nested JSON tree instead of a
|
|
7
|
-
* flat field-GROUP list: each key names a field or relation of the resource
|
|
8
|
-
* at that level, and each value is either `true` (the field, or a relation's
|
|
9
|
-
* child with its default fields), a non-negative integer (a cursor-paged
|
|
10
|
-
* relation's own `limit` argument), an opaque cursor STRING (a paged
|
|
11
|
-
* relation's `before` — DX-3428: not every relation's cursor is a plain
|
|
12
|
-
* integer, e.g. a plan event's visibility-scanned row id), a non-empty
|
|
13
|
-
* STRING or ARRAY OF STRINGS (a relation's own declared, non-paging argument
|
|
14
|
-
* — e.g. `records`' `kind`, `events`' `kinds`/`origin`/`writer`), or another
|
|
15
|
-
* field tree (a relation's child with a NAMED field selection). Example:
|
|
16
|
-
*
|
|
17
|
-
* {"description": true, "ac": true, "problems": {"solutions": {"steps": true}, "decisions": true}, "comments": {"limit": 10}, "records": {"kind": "goal"}}
|
|
18
|
-
*
|
|
19
|
-
* One schema, one module — every tool that accepts a field tree imports THIS
|
|
20
|
-
* export rather than redeclaring the shape, so the recursion is defined once
|
|
21
|
-
* and every caller's JSON Schema serializes the same way. `z.lazy` is
|
|
22
|
-
* required because the schema refers to itself; `zod-to-json-schema` (the
|
|
23
|
-
* MCP SDK's own zod v3 → JSON Schema converter, see
|
|
24
|
-
* `@modelcontextprotocol/sdk`'s `zod-json-schema-compat.ts`) resolves that
|
|
25
|
-
* self-reference to a local `$ref` rather than unrolling it infinitely —
|
|
26
|
-
* verified directly against the listed tool schema in
|
|
27
|
-
* `__tests__/tool-defs.test.ts`, not assumed from reading the library's
|
|
28
|
-
* source. The problem-solutions route's pre-existing `steps` field (DX-3310)
|
|
29
|
-
* is the same `z.lazy` self-reference pattern already shipping on this server.
|
|
30
|
-
*/
|
|
31
|
-
import { z } from "zod";
|
|
32
|
-
/**
|
|
33
|
-
* The shared schema. A record whose values are `true`, a non-negative
|
|
34
|
-
* integer, a non-empty string, a non-empty array of non-empty strings, or
|
|
35
|
-
* the same schema recursively — generic on purpose (no issue/plan-specific
|
|
36
|
-
* field names baked in here) so every tool that reads a resource tree
|
|
37
|
-
* reuses it unchanged.
|
|
38
|
-
*/
|
|
39
|
-
export const fieldTreeSchema = z.lazy(() => z.record(z.union([
|
|
40
|
-
z.literal(true),
|
|
41
|
-
z.number().int().nonnegative(),
|
|
42
|
-
z.string().min(1),
|
|
43
|
-
z.array(z.string().min(1)).min(1),
|
|
44
|
-
fieldTreeSchema,
|
|
45
|
-
])));
|
package/dist/priority.js
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tier-word → numeric priority resolution for `danxbot_api`'s
|
|
3
|
-
* `PATCH /api/issues/:id/edit` route (DX-1532).
|
|
4
|
-
*
|
|
5
|
-
* Priority is a STRUCTURED numeric column `issues.priority` (float in `[0,6)`).
|
|
6
|
-
* Both the Trello "priority" label and the dashboard priority badge read it via
|
|
7
|
-
* `priorityTier(issue.priority)` — description PROSE ("Priority: Low") is read
|
|
8
|
-
* by neither. So an agent that "sets priority" by editing description text
|
|
9
|
-
* produces a silent false-positive: nothing downstream changes. The fix is to
|
|
10
|
-
* let the agent write the numeric column through the issue-edit route.
|
|
11
|
-
*
|
|
12
|
-
* Agents (and operators) speak in tier WORDS ("low" / "high"), so the tool
|
|
13
|
-
* accepts a tier word and resolves it to the tier's MIDPOINT here — the inverse
|
|
14
|
-
* of `src/issue-tracker/priority-tier.ts#priorityTier`. A raw numeric is passed
|
|
15
|
-
* through unchanged for the server route to range-validate (it 400s on
|
|
16
|
-
* out-of-`[0,6)`), so the MCP boundary never has to duplicate that bound.
|
|
17
|
-
*
|
|
18
|
-
* This package is a standalone published artifact and cannot import the
|
|
19
|
-
* backend's `priority-tier` module, so the midpoint table is duplicated here.
|
|
20
|
-
* Each value IS the `[min,max)` bucket midpoint by construction and equals the
|
|
21
|
-
* backend's `PRIORITY_TIERS.defaultValue`; `src/issue-tracker/priority-tier.test.ts`
|
|
22
|
-
* asserts `priorityTier(defaultValue) === key` on the canonical side, pinning
|
|
23
|
-
* the round-trip these midpoints rely on.
|
|
24
|
-
*/
|
|
25
|
-
/**
|
|
26
|
-
* The six priority tier words, low → high. A const tuple so it doubles as the
|
|
27
|
-
* `z.enum` source in `index.ts`.
|
|
28
|
-
*/
|
|
29
|
-
export const PRIORITY_TIER_WORDS = [
|
|
30
|
-
"lowest",
|
|
31
|
-
"low",
|
|
32
|
-
"medium",
|
|
33
|
-
"high",
|
|
34
|
-
"very_high",
|
|
35
|
-
"critical",
|
|
36
|
-
];
|
|
37
|
-
/**
|
|
38
|
-
* Tier word → bucket midpoint (inverse of `priorityTier`). Lands the numeric
|
|
39
|
-
* column squarely inside the requested tier so a later `priorityTier` read
|
|
40
|
-
* returns the same word.
|
|
41
|
-
*/
|
|
42
|
-
export const PRIORITY_TIER_MIDPOINTS = {
|
|
43
|
-
lowest: 0.5,
|
|
44
|
-
low: 1.5,
|
|
45
|
-
medium: 2.5,
|
|
46
|
-
high: 3.5,
|
|
47
|
-
very_high: 4.5,
|
|
48
|
-
critical: 5.5,
|
|
49
|
-
};
|
|
50
|
-
/**
|
|
51
|
-
* Resolve a priority input — a tier WORD or a raw numeric — to the numeric
|
|
52
|
-
* value the `PATCH /api/issues/:id/edit` route stores in `issues.priority`.
|
|
53
|
-
*
|
|
54
|
-
* - tier word → its bucket midpoint (`PRIORITY_TIER_MIDPOINTS`).
|
|
55
|
-
* - number → passed through unchanged; the server route range-validates it to
|
|
56
|
-
* `[0,6)` and 400s otherwise (the MCP boundary deliberately does not
|
|
57
|
-
* re-validate the bound — single source of truth on the route).
|
|
58
|
-
* - unknown string → throws here at the MCP boundary (no round-trip). The Zod
|
|
59
|
-
* enum on the tool schema already rejects this for tool callers; the explicit
|
|
60
|
-
* throw keeps the helper safe for any direct caller.
|
|
61
|
-
*/
|
|
62
|
-
export function resolvePriority(input) {
|
|
63
|
-
if (typeof input === "number")
|
|
64
|
-
return input;
|
|
65
|
-
const midpoint = PRIORITY_TIER_MIDPOINTS[input];
|
|
66
|
-
if (midpoint === undefined) {
|
|
67
|
-
throw new Error(`issue-edit: unknown priority tier word "${input}" (expected one of ${PRIORITY_TIER_WORDS.join(", ")} or a number in [0,6))`);
|
|
68
|
-
}
|
|
69
|
-
return midpoint;
|
|
70
|
-
}
|