@thehammer/danx-dashboard-mcp 0.1.149 → 0.1.152
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/field-tree.js +35 -0
- package/dist/handlers.js +32 -16
- package/dist/index.js +30 -37
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,7 +22,8 @@ All exposed as `mcp__danx-dashboard__<name>` once wired through the workspace `m
|
|
|
22
22
|
| Tool | HTTP | Notes |
|
|
23
23
|
|---|---|---|
|
|
24
24
|
| `issue_list` | `GET /api/issues` | filters: `type`, `parent_id` (null → root-only), `dispatchable_derived`, `assigned_agent`, `include_closed`, `limit`, `offset` |
|
|
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` opts in |
|
|
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
|
+
| `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 |
|
|
26
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` |
|
|
27
28
|
| `issue_edit` | `PATCH /api/issues/:id/edit` | Prose + structured keys (`title`, `summary` (null clears), `description`, `ac`, `checklists`, `effort_level`, `parent_id`, `priority`, `list_id`); semantic keys refused with 400 + pointer to dedicated handler. `priority` (DX-1532) takes a tier word (`low`/`high`/…) or a number in `[0,6)` — the ONLY way to set the numeric column the Trello label + dashboard badge read; never set priority via description prose |
|
|
28
29
|
| `issue_transition` | `POST /api/issues/:id/transition` | Actions: ready, pickup, rollback_pickup, complete, cancel, block, unblock, archive, reopen. `block` is a dispatch hold only — it never marks the card as needing a human; use `issue_problem` add for that |
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DX-3426 — the shared recursive FIELD-TREE shape.
|
|
3
|
+
*
|
|
4
|
+
* The dashboard's single-card and batch card reads (`issue_get` today;
|
|
5
|
+
* `issue_list` / `plan_get` in later cards, per DX-3426's own follow-up scope)
|
|
6
|
+
* take a nested JSON tree instead of a flat field-GROUP list: each key names a
|
|
7
|
+
* field or relation of the resource at that level, and each value is either
|
|
8
|
+
* `true` (the field, or a relation's child with its default fields), a
|
|
9
|
+
* non-negative integer (a cursor-paged relation's own page ARGUMENT — e.g.
|
|
10
|
+
* `{"comments": {"limit": 10, "before": 812}}`, where `limit`/`before` are
|
|
11
|
+
* themselves plain integer values inside the nested object), or another
|
|
12
|
+
* field tree (a relation's child with a NAMED field selection). Example:
|
|
13
|
+
*
|
|
14
|
+
* {"description": true, "ac": true, "problems": {"solutions": {"steps": true}, "decisions": true}, "comments": {"limit": 10}}
|
|
15
|
+
*
|
|
16
|
+
* One schema, one module — every tool that accepts a field tree imports THIS
|
|
17
|
+
* export rather than redeclaring the shape, so the recursion is defined once
|
|
18
|
+
* and every caller's JSON Schema serializes the same way. `z.lazy` is
|
|
19
|
+
* required because the schema refers to itself; `zod-to-json-schema` (the
|
|
20
|
+
* MCP SDK's own zod v3 → JSON Schema converter, see
|
|
21
|
+
* `@modelcontextprotocol/sdk`'s `zod-json-schema-compat.ts`) resolves that
|
|
22
|
+
* self-reference to a local `$ref` rather than unrolling it infinitely —
|
|
23
|
+
* verified directly against the listed tool schema in
|
|
24
|
+
* `__tests__/tool-defs.test.ts`, not assumed from reading the library's
|
|
25
|
+
* source. `issue_solution`'s pre-existing `steps` field (DX-3310) is the
|
|
26
|
+
* same `z.lazy` self-reference pattern already shipping on this server.
|
|
27
|
+
*/
|
|
28
|
+
import { z } from "zod";
|
|
29
|
+
/**
|
|
30
|
+
* The shared schema. A record whose values are `true`, a non-negative
|
|
31
|
+
* integer, or the same schema recursively — generic on purpose (no
|
|
32
|
+
* issue-specific field names baked in here) so `issue_list` / `plan_get` can
|
|
33
|
+
* reuse it unchanged in their own later cards.
|
|
34
|
+
*/
|
|
35
|
+
export const fieldTreeSchema = z.lazy(() => z.record(z.union([z.literal(true), z.number().int().nonnegative(), fieldTreeSchema])));
|
package/dist/handlers.js
CHANGED
|
@@ -95,16 +95,15 @@ export async function issueGet(client, args) {
|
|
|
95
95
|
if (args.ids !== undefined && args.board !== undefined) {
|
|
96
96
|
throw new Error("issue_get: board does not apply to the ids batch form — it resolves every id globally; drop board");
|
|
97
97
|
}
|
|
98
|
-
// DX-3321 — comments paging is a SINGLE-id-route feature
|
|
99
|
-
// (`GET /api/issues/:id?comments_limit=&comments_offset=`); the batch
|
|
100
|
-
// route has no per-card paging at all. Refuse rather than silently drop,
|
|
101
|
-
// same as the `ids` + `board` refusal just above.
|
|
102
|
-
if (args.ids !== undefined && (args.comments_limit !== undefined || args.comments_offset !== undefined)) {
|
|
103
|
-
throw new Error("issue_get: comments_limit/comments_offset do not apply to the ids batch form — it has no per-card comment paging; drop them or use the single-id form");
|
|
104
|
-
}
|
|
105
98
|
const query = {};
|
|
106
|
-
|
|
107
|
-
|
|
99
|
+
// DX-3426 — the tree travels as ONE JSON-encoded query param (mirrors how
|
|
100
|
+
// `issue_list`'s `filter`/`sort` envelopes already ride the wire above),
|
|
101
|
+
// rather than the old CSV-of-group-names. Sent on both the single and
|
|
102
|
+
// batch forms — comment paging (or any other relation argument) now lives
|
|
103
|
+
// INSIDE the tree, so there is nothing left for the old ids+comments_*
|
|
104
|
+
// refusal to guard against.
|
|
105
|
+
if (args.fields !== undefined && Object.keys(args.fields).length > 0) {
|
|
106
|
+
query.fields = JSON.stringify(args.fields);
|
|
108
107
|
}
|
|
109
108
|
if (args.ids !== undefined) {
|
|
110
109
|
if (args.ids.length === 0) {
|
|
@@ -118,10 +117,6 @@ export async function issueGet(client, args) {
|
|
|
118
117
|
if (args.id === undefined) {
|
|
119
118
|
throw new Error("issue_get: pass exactly one of id or ids");
|
|
120
119
|
}
|
|
121
|
-
if (args.comments_limit !== undefined)
|
|
122
|
-
query.comments_limit = args.comments_limit;
|
|
123
|
-
if (args.comments_offset !== undefined)
|
|
124
|
-
query.comments_offset = args.comments_offset;
|
|
125
120
|
return client.request({
|
|
126
121
|
method: "GET",
|
|
127
122
|
path: `/${encodeURIComponent(args.id)}`,
|
|
@@ -1121,6 +1116,10 @@ export const PLAN_EVENT_KINDS = [
|
|
|
1121
1116
|
"session_connected",
|
|
1122
1117
|
"session_switched_away",
|
|
1123
1118
|
"idle_nudge_sent",
|
|
1119
|
+
// DX-3519 — plan sign-off.
|
|
1120
|
+
"plan_signed_off",
|
|
1121
|
+
"plan_sign_off_cleared",
|
|
1122
|
+
"plan_auto_sign_off_changed",
|
|
1124
1123
|
];
|
|
1125
1124
|
/**
|
|
1126
1125
|
* DX-3027 — every origin `plan_get`'s `events_origin` filter accepts.
|
|
@@ -1130,14 +1129,14 @@ export const PLAN_EVENT_KINDS = [
|
|
|
1130
1129
|
*/
|
|
1131
1130
|
export const PLAN_EVENT_ORIGINS = ["operator", "agent", "machine"];
|
|
1132
1131
|
/**
|
|
1133
|
-
* DX-2834 — the plan-status taxonomy, mirroring `PLAN_FIELD_GROUPS`
|
|
1134
|
-
* above: the ONE copy in this package (the `plan_list` zod enum in
|
|
1132
|
+
* DX-2834 / DX-3519 — the plan-status taxonomy, mirroring `PLAN_FIELD_GROUPS`
|
|
1133
|
+
* just above: the ONE copy in this package (the `plan_list` zod enum in
|
|
1135
1134
|
* `index.ts` reads this const), duplicated from the server's
|
|
1136
1135
|
* `src/issues/db/plans.ts#PLAN_STATUS_IDS` because the published package
|
|
1137
1136
|
* cannot import server source at runtime. `__tests__/handlers.test.ts`
|
|
1138
1137
|
* asserts the two are equal, so drift fails a test rather than a live call.
|
|
1139
1138
|
*/
|
|
1140
|
-
export const PLAN_STATUSES = ["awaiting-session", "planning", "building", "complete"];
|
|
1139
|
+
export const PLAN_STATUSES = ["awaiting-session", "planning", "building", "awaiting-sign-off", "complete"];
|
|
1141
1140
|
/** Every plan, plus which one THIS session is connected to. */
|
|
1142
1141
|
export async function planList(client, args = {}) {
|
|
1143
1142
|
return client.request({
|
|
@@ -1723,3 +1722,20 @@ export async function dispatchTranscriptSearch(client, args) {
|
|
|
1723
1722
|
},
|
|
1724
1723
|
};
|
|
1725
1724
|
}
|
|
1725
|
+
// ---------------- resource_fields (DX-3426) ----------------
|
|
1726
|
+
const RESOURCES_BASE_PATH = "/api/resources";
|
|
1727
|
+
/**
|
|
1728
|
+
* `GET /api/resources/:resource/fields` — what a field TREE (`issue_get`'s
|
|
1729
|
+
* `fields`, and any later tool built on the same shape) may name for ONE
|
|
1730
|
+
* resource: `{resource, description, always, hashes, fields, relations}`.
|
|
1731
|
+
* Install-global (no `board` — mirrors `failure_category_list`'s own
|
|
1732
|
+
* board-less rationale just above: nothing server-side would read one).
|
|
1733
|
+
* Unknown resource → 404 `unknown_resource`.
|
|
1734
|
+
*/
|
|
1735
|
+
export async function resourceFields(client, args) {
|
|
1736
|
+
return client.request({
|
|
1737
|
+
method: "GET",
|
|
1738
|
+
path: `/${encodeURIComponent(args.resource)}/fields`,
|
|
1739
|
+
basePath: RESOURCES_BASE_PATH,
|
|
1740
|
+
});
|
|
1741
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -12,7 +12,11 @@
|
|
|
12
12
|
* through the workspace `mcp.template.json`):
|
|
13
13
|
*
|
|
14
14
|
* - issue_list GET /api/issues
|
|
15
|
-
* - issue_get GET /api/issues/:id | /api/issues/batch (DX-2727 ids[] batch
|
|
15
|
+
* - issue_get GET /api/issues/:id | /api/issues/batch (DX-2727 ids[] batch;
|
|
16
|
+
* DX-3426 `fields` is a recursive field TREE, not a flat group list —
|
|
17
|
+
* see `resource_fields` below and `./field-tree.ts`)
|
|
18
|
+
* - resource_fields GET /api/resources/:resource/fields (DX-3426 — what `fields` may
|
|
19
|
+
* name for a resource; install-global, no `board`)
|
|
16
20
|
* - issue_create POST /api/issues
|
|
17
21
|
* - issue_edit PATCH /api/issues/:id/edit
|
|
18
22
|
* - issue_transition POST /api/issues/:id/transition
|
|
@@ -106,8 +110,9 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
106
110
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
107
111
|
import { z } from "zod";
|
|
108
112
|
import { DashboardHttpClient } from "./http-client.js";
|
|
109
|
-
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, repoKnowledgeGet, repoKnowledgeSet, qualityGateInstruction, } from "./handlers.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, repoKnowledgeGet, repoKnowledgeSet, qualityGateInstruction, resourceFields, } from "./handlers.js";
|
|
110
114
|
import { PRIORITY_TIER_WORDS } from "./priority.js";
|
|
115
|
+
import { fieldTreeSchema } from "./field-tree.js";
|
|
111
116
|
function readEnvOrDie(name) {
|
|
112
117
|
const v = process.env[name];
|
|
113
118
|
if (typeof v !== "string" || v === "") {
|
|
@@ -272,10 +277,11 @@ const EFFORT_VALUES = [
|
|
|
272
277
|
// still be CREATED through this MCP.
|
|
273
278
|
const ISSUE_TYPES = ["Epic", "Bug", "Feature", "Story", "Chore", "Task"];
|
|
274
279
|
const NON_EPIC_TYPES = ["Bug", "Feature", "Story", "Chore", "Task"];
|
|
275
|
-
// DX-935 / DX-937 — field-group taxonomy for the
|
|
276
|
-
// hand-copied from `src/issues/read/field-groups.ts` (LIST_GROUPS
|
|
277
|
-
//
|
|
278
|
-
//
|
|
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.
|
|
279
285
|
const LIST_FIELD_GROUPS = [
|
|
280
286
|
"description",
|
|
281
287
|
// DX-2735: replaced the flat "solutions" group (hard cut, no alias).
|
|
@@ -290,13 +296,6 @@ const LIST_FIELD_GROUPS = [
|
|
|
290
296
|
"children",
|
|
291
297
|
"effort",
|
|
292
298
|
];
|
|
293
|
-
const GET_FIELD_GROUPS = [
|
|
294
|
-
...LIST_FIELD_GROUPS,
|
|
295
|
-
"mirrors",
|
|
296
|
-
"code_review_items",
|
|
297
|
-
// DX-2835 — every plan this card is on ({id, ref, name}[]), detail only.
|
|
298
|
-
"plans",
|
|
299
|
-
];
|
|
300
299
|
const SORT_ORDERS = ["asc", "desc"];
|
|
301
300
|
const sortField = z
|
|
302
301
|
.array(z.object({
|
|
@@ -393,33 +392,27 @@ strictTool("issue_list",
|
|
|
393
392
|
}, async (args) => jsonResult(await issueList(client, args)));
|
|
394
393
|
// ---------------- issue_get ----------------
|
|
395
394
|
strictTool("issue_get",
|
|
396
|
-
// DX-
|
|
397
|
-
//
|
|
398
|
-
|
|
395
|
+
// DX-3426: field GROUPS replaced by a recursive field TREE (see
|
|
396
|
+
// `./field-tree.ts`) — comment paging now lives INSIDE the tree (a
|
|
397
|
+
// cursor-paged relation, today only `comments`, takes `limit`/`before` in
|
|
398
|
+
// its own nested object) instead of top-level `comments_limit`/
|
|
399
|
+
// `comments_offset`, which is also why the old "ids + comments_* refused"
|
|
400
|
+
// rule is gone: paging is per-relation now, so it works on the batch form
|
|
401
|
+
// too. Kept compact — call `resource_fields({resource:"issue"})` for the
|
|
402
|
+
// full, current list of what `fields` may name, rather than enumerating it
|
|
403
|
+
// here (it would drift the moment a field/relation is added or removed).
|
|
404
|
+
"Fetch one card (`id`) or many (`ids`, at most " + ISSUE_BATCH_GET_MAX + " — split larger sets); pass exactly one. Ids are globally unique, so both resolve from any board; the batch form is global and throws with `board`. THE DEFAULT ROW IS THE ALWAYS-ON FIELDS ONLY — identity, lifecycle stamps, derived status/list/blocked, assigned_agent, and content_hash (the concurrency token issue_edit needs for title/description/checklists); every child hash is likewise always returned. `fields` opts into more, per card in a batch too: a JSON object tree — each key names a field or relation, each value is `true` (that field, or a relation's child with its default fields), a nested object (a relation's child with named fields), or on the cursor-paged `comments` relation the nested object may also carry `limit`/`before` (comment id) to page — e.g. `{\"description\": true, \"problems\": {\"solutions\": {\"steps\": true}}, \"comments\": {\"limit\": 10}}`; a paged response also carries `comments_page: {limit, total, next_cursor}` (`next_cursor` → pass back as `before` for the next older page, null = none). Call `resource_fields({resource:\"issue\"})` for exactly what may be requested (every field/relation name, each relation's own child resource, and its paging shape if any) — never guess a name. Unknown field/relation → 400 `unknown_field`; bad shape → 400 `invalid_fields`; bad `limit`/`before` → 400 `invalid_field_argument`; a field you lack permission for → 403 `forbidden_field`. Single form: unknown id → 404. Batch form: `{issues: [...], not_found: [...ids]}` — an unknown or deleted id never fails the call.", {
|
|
399
405
|
id: z.string().min(1).optional(),
|
|
400
406
|
ids: z.array(z.string().min(1)).min(1).max(ISSUE_BATCH_GET_MAX).optional(),
|
|
401
|
-
fields:
|
|
402
|
-
.array(z.enum(GET_FIELD_GROUPS))
|
|
403
|
-
.optional()
|
|
404
|
-
.describe("Field groups to add; absent = minimal scalars."),
|
|
405
|
-
comments_limit: z
|
|
406
|
-
.number()
|
|
407
|
-
.int()
|
|
408
|
-
.positive()
|
|
409
|
-
.optional()
|
|
410
|
-
.describe("Single-id form only. How many comments the page holds (server default 20), the window anchored at the " +
|
|
411
|
-
"NEWEST comment. An out-of-range value (server max 200) is refused by the server with its own 400, " +
|
|
412
|
-
"never silently clamped here. Requires `fields:[\"comments\"]`."),
|
|
413
|
-
comments_offset: z
|
|
414
|
-
.number()
|
|
415
|
-
.int()
|
|
416
|
-
.nonnegative()
|
|
407
|
+
fields: fieldTreeSchema
|
|
417
408
|
.optional()
|
|
418
|
-
.describe("
|
|
419
|
-
"forward while comments_offset + comments.length < comments_total to reach older comments. Requires " +
|
|
420
|
-
"`fields` to include `comments`."),
|
|
409
|
+
.describe("A field tree (see tool description); absent/empty = minimal scalars. `resource_fields({resource:\"issue\"})` names every valid key."),
|
|
421
410
|
...boardField,
|
|
422
411
|
}, async (args) => jsonResult(await issueGet(client, args)));
|
|
412
|
+
// ---------------- resource_fields ----------------
|
|
413
|
+
strictTool("resource_fields", "DX-3426: what a field TREE (`issue_get`'s `fields`, and any later tool that takes one) may name for one resource — `{resource, description, always: string[], hashes: string[], fields: [{name, description}], relations: [{name, description, resource, paging?: {default_limit, max_limit, page_field}}]}`. `always`/`hashes` ride every response regardless of `fields` (never request them). A relation's `resource` names what to call this same tool with next, to go one level deeper. Install-global, not board-scoped. `resource: \"issue\"` is the root; unknown resource → 404 `unknown_resource`.", {
|
|
414
|
+
resource: z.string().min(1).describe('Resource name, e.g. "issue", or a relation\'s own `resource` from a prior call.'),
|
|
415
|
+
}, async (args) => jsonResult(await resourceFields(client, args)));
|
|
423
416
|
// ---------------- issue_create ----------------
|
|
424
417
|
strictTool("issue_create", '`plan` is REQUIRED on every create — no default, no inference; see its own field description for the two values and the refusal. This replaces the plan_add_card follow-up at creation time; plan_add_card remains for putting an EXISTING card on a plan. ' +
|
|
425
418
|
'Create a card. Board-scoped; see `board`. type=Epic REQUIRES non-empty phase_children[] (epic and phases inserted in one transaction; children get the epic as parent); other types refuse phase_children[] (400). Status starts at Review. `list_id` places the card straight into a column — a board_lists id or the list\'s display NAME (case-insensitive, emoji-tolerant): a `ready`-type queue lands it in ToDo, a `completed` list in Done, with no follow-up transition. Not valid on Epic; unknown name/id → 400. `quality_gates` adds gates beyond the board\'s type defaults (see its own field description); add one later with `issue_quality_gate`. ALWAYS pass `triage_enabled` explicitly on the root card and every phase child (see its own field description).', {
|
|
@@ -948,11 +941,11 @@ strictTool("brief_set_page", 'Write one Brief page. Board-scoped; see `board`. B
|
|
|
948
941
|
// plan id — and it can only ever bind the caller's own session. `plan_create`
|
|
949
942
|
// also takes no plan id, but for a different reason: it MAKES a plan rather
|
|
950
943
|
// than acting on one, so there is no existing plan for an id to name yet.
|
|
951
|
-
strictTool("plan_list", "List every plan, 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, from any repo. Returns `{plans: [{id, ref, name, createdAt, cardCount, boards, status}], session, sessionListenerAttached}` — `ref` is the plan's short reference (`PLN-<id>`), cite it rather than the bare id. `status` is computed fresh on every read, never stored: `awaiting-session` (no live session — a session row isn't released just because a session ended), `planning` (no cards, or only Review/Backlog/terminal ones with ≥1 not Done/Cancelled), `building` (a live session AND ≥1 card ToDo/In Progress or stuck-but-active — Blocked/Needs Help), `complete` (≥1 card, all Done/Cancelled — wins even with no session). Pass `status` to filter. `session` is your own registration (`{sessionId, title, planId, planName, firstSeenAt, lastActiveAt}`) or null outside a Claude Code session; `planId: null` means connected to no plan — `plan_get` to browse, `plan_connect` to bind. `sessionListenerAttached` says whether your event stream is attached (the plugin's plan bridge); false for a few seconds right after connect is normal, false after that means events aren't reaching you — tell the operator. Distinct from the board Brief (`brief_list`).", {
|
|
944
|
+
strictTool("plan_list", "List every plan, 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, from any repo. Returns `{plans: [{id, ref, name, createdAt, cardCount, boards, status, signedOffAt, signedOffBy, autoSignOff}], session, sessionListenerAttached}` — `ref` is the plan's short reference (`PLN-<id>`), cite it rather than the bare id. `status` is computed fresh on every read, never stored — except the sign-off pair itself: `awaiting-session` (no live session — a session row isn't released just because a session ended), `planning` (no cards, or only Review/Backlog/terminal ones with ≥1 not Done/Cancelled), `building` (a live session AND ≥1 card ToDo/In Progress or stuck-but-active — Blocked/Needs Help), `awaiting-sign-off` (≥1 card, all Done/Cancelled, but nobody has signed the plan off yet — DX-3519), `complete` (≥1 card, all Done/Cancelled, AND signed off — wins even with no session). `signedOffAt`/`signedOffBy` are `null` until a human (`signedOffBy` = their identity) or the `autoSignOff` toggle (`signedOffBy: 'auto'`) signs the plan off; sign-off itself is a human-only write, not exposed through this MCP surface — direct the operator to the dashboard's plan page. Pass `status` to filter. `session` is your own registration (`{sessionId, title, planId, planName, firstSeenAt, lastActiveAt}`) or null outside a Claude Code session; `planId: null` means connected to no plan — `plan_get` to browse, `plan_connect` to bind. `sessionListenerAttached` says whether your event stream is attached (the plugin's plan bridge); false for a few seconds right after connect is normal, false after that means events aren't reaching you — tell the operator. Distinct from the board Brief (`brief_list`).", {
|
|
952
945
|
status: z
|
|
953
946
|
.enum(PLAN_STATUSES)
|
|
954
947
|
.optional()
|
|
955
|
-
.describe("Filter to one computed status: awaiting-session, planning, building, complete. Omit for every plan."),
|
|
948
|
+
.describe("Filter to one computed status: awaiting-session, planning, building, awaiting-sign-off, complete. Omit for every plan."),
|
|
956
949
|
}, async (args) => jsonResult(await planList(client, args)));
|
|
957
950
|
strictTool("plan_get", "Read a plan. Pass `plan_id` to read ANY plan; OMIT to read the plan this session is connected to — omitting while connected to none fails loud `{error: \"session_not_connected\"}`, connect first. A BARE call (no `fields`) returns ONLY the plan's cheap scalars: `{plan, boards, cardCount, bucketCounts, status, session, sessionListenerAttached, available_field_groups}` — no cards, records, or architecture body. `plan` carries `{id, ref, name, createdAt}`; `ref` is the plan's short reference (`PLN-<id>`) — cite that, not the bare id. `status` is computed fresh on every read, never stored — see `plan_list` for the four values and what each means. Pass `fields` to opt into: `cards` (member cards, paged via `cards_offset`/`cards_limit` — see their own field descriptions — in stable card-reference order: board prefix then card number, pages never repeat/skip unless membership changes between reads; response carries `cards_total`), `records` (every goal+rule+caveat, keyed by kind) or `records:goal` / `records:rule` / `records:caveat` (one kind, cheaper), `architecture` (`{sections: [{id, planId, contentHash, title, content, sortOrder, createdAt, updatedAt}]}`), `sessions` (every session connected to the plan), `events` (the plan's durable event ledger — every human action and bridge message, paged/filtered via `events_limit`/`events_before`/`events_kinds`/`events_origin`/`events_writer` — see their own field descriptions; response carries `events: {items: [{id, at, kind, writer, origin, originSessionId, targetSessionId, cardId, cardTitle, boardId, detail}], next_cursor}`, `next_cursor` null on the last page; an event on a card whose board you cannot read is left out, plan-level events are always visible). `session`/`sessionListenerAttached` and `available_field_groups` ride every response regardless. `sessionListenerAttached` is `false` for a few seconds right after `plan_connect` while the plugin's event bridge starts; still `false` after that while connected means the plan's card events are not reaching you — tell the operator. ALWAYS `plan_get`/`plan_get_architecture_section` immediately before `plan_update_architecture_section`/`plan_delete_architecture_section` and pass the returned `contentHash` back as `base_hash`.", {
|
|
958
951
|
plan_id: z
|
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.152",
|
|
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",
|