@thehammer/danx-dashboard-mcp 0.1.142 → 0.1.143

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 CHANGED
@@ -336,6 +336,39 @@ function argCheckers(tool, mode) {
336
336
  },
337
337
  };
338
338
  }
339
+ /**
340
+ * Every action-dispatched tool's args carry these three regardless of
341
+ * `action` — never action-specific, so every per-action allow-list passed to
342
+ * `refuseInapplicableFields` is unioned with this rather than repeating it.
343
+ */
344
+ const STRUCTURAL_ACTION_FIELDS = ["id", "action", "board"];
345
+ /**
346
+ * DX-3310 (code-review nit) — a field the caller sent that means nothing for
347
+ * the chosen `action` used to be silently ignored: `issue_solution`'s `add`
348
+ * built its `content` object from `title`/`body`/`pro`/`con`/`recommended`
349
+ * regardless of action, then simply never read it on `remove`/`add_step`/etc,
350
+ * and `issue_problem`'s `remove` never looked at `statement`/`context`/
351
+ * `solutions` at all. Either way the caller got a 200 with the field quietly
352
+ * discarded — no different from a typo. Refused loud instead, mirroring
353
+ * `argCheckers`' own wording: every ACTUALLY-SUPPLIED (`!== undefined`) key
354
+ * outside the action's own allow-list (plus `STRUCTURAL_ACTION_FIELDS`,
355
+ * always allowed) is named in one error, so a caller fixes every offending
356
+ * field in one round trip rather than one at a time.
357
+ */
358
+ // A9 (code-review fix) — takes `object` rather than `Record<string, unknown>`
359
+ // so every caller (each with its own specific `*Args` interface, no index
360
+ // signature) passes `args` directly, with no `as unknown as Record<...>`
361
+ // bridge cast at the call site. `Object.entries` reads the key/value pairs
362
+ // without needing an index signature on the input type either.
363
+ function refuseInapplicableFields(tool, mode, args, allowed) {
364
+ const offending = Object.entries(args)
365
+ .filter(([key, value]) => value !== undefined && !STRUCTURAL_ACTION_FIELDS.includes(key) && !allowed.has(key))
366
+ .map(([key]) => key)
367
+ .sort();
368
+ if (offending.length > 0) {
369
+ throw new Error(`${tool} ${mode} refuses fields that do not apply to this action: ${offending.join(", ")}`);
370
+ }
371
+ }
339
372
  export async function issueComment(client, args) {
340
373
  const idEnc = encodeURIComponent(args.id);
341
374
  const board = args.board;
@@ -537,10 +570,25 @@ export async function issueChecklist(client, args) {
537
570
  *
538
571
  * No answer action, for the reason `issueSolution` gives.
539
572
  */
573
+ /**
574
+ * DX-3310 (nit) — per-action allow-list for `refuseInapplicableFields`.
575
+ * `id`/`action`/`board` are structural and never listed (every action takes
576
+ * them). `type` is listed under `edit` even though it can never actually be
577
+ * SENT there (see the dedicated throw below) — that keeps the generic
578
+ * refusal from firing first with a less specific message than the one that
579
+ * already names the real reason.
580
+ */
581
+ const PROBLEM_ACTION_FIELDS = {
582
+ list: [],
583
+ add: ["statement", "context", "type", "summary", "solutions"],
584
+ edit: ["problem_id", "base_hash", "statement", "context", "summary", "type"],
585
+ remove: ["problem_id", "base_hash"],
586
+ };
540
587
  export async function issueProblem(client, args) {
541
588
  const idEnc = encodeURIComponent(args.id);
542
589
  const board = args.board;
543
590
  const need = argCheckers("issue_problem", `action=${args.action}`);
591
+ refuseInapplicableFields("issue_problem", `action=${args.action}`, args, new Set(PROBLEM_ACTION_FIELDS[args.action]));
544
592
  switch (args.action) {
545
593
  case "list":
546
594
  return client.request({ method: "GET", path: `/${idEnc}/problems`, board });
@@ -621,11 +669,24 @@ export async function issueProblem(client, args) {
621
669
  * question could release the very stop it set to wait for a human. The operator
622
670
  * answers in the dashboard.
623
671
  */
672
+ const SOLUTION_ACTION_FIELDS = {
673
+ add: ["title", "body", "pro", "con", "recommended", "steps"],
674
+ edit: ["solution_id", "base_hash", "title", "body", "pro", "con", "recommended", "steps", "steps_base_hash"],
675
+ remove: ["solution_id", "base_hash"],
676
+ add_step: ["solution_id", "title", "description", "parent_step_id", "position"],
677
+ edit_step: ["solution_id", "step_id", "base_hash", "title", "description"],
678
+ remove_step: ["solution_id", "step_id", "base_hash"],
679
+ };
624
680
  export async function issueSolution(client, args) {
625
681
  const need = argCheckers("issue_solution", `action=${args.action}`);
626
682
  // DX-2735: checked at runtime too, not only by the schema — a caller that skips
627
683
  // the MCP boundary must never build `/problems/undefined/solutions`.
628
684
  const problemId = need.id(args.problem_id, "problem_id");
685
+ // DX-3310 (nit) — `problem_id` is unioned in here rather than listed in
686
+ // every `SOLUTION_ACTION_FIELDS` entry: unlike `issue_problem` (where it
687
+ // only applies to edit/remove), `issue_solution` requires it for every
688
+ // action (checked above), so it is structural FOR THIS TOOL specifically.
689
+ refuseInapplicableFields("issue_solution", `action=${args.action}`, args, new Set(["problem_id", ...SOLUTION_ACTION_FIELDS[args.action]]));
629
690
  const base = `/${encodeURIComponent(args.id)}/problems/${problemId}/solutions`;
630
691
  const board = args.board;
631
692
  const content = {};
package/dist/index.js CHANGED
@@ -342,14 +342,14 @@ const boardField = {
342
342
  .string()
343
343
  .min(1)
344
344
  .optional()
345
- .describe("Target another board by its qualified id `<repo>:<slug>`; omit for this dispatch's board. Unknown → 404."),
345
+ .describe("Board id `<repo>:<slug>`; omit for this dispatch's board. Unknown → 404."),
346
346
  };
347
347
  // The three prose fields of a card, each with ONE job. Shared by issue_create
348
348
  // (root + phase children) and issue_edit so the guidance an agent reads is
349
349
  // identical wherever it writes the field.
350
- const TITLE_DESCRIBE = 'Short, specific label naming the domain, so a reader recognises the card unopened (e.g. "Guest checkout rejects carts holding a gift card"). Never generic ("2 real decisions needed", "Fix bug", "Follow-up").';
351
- const SUMMARY_DESCRIBE = "1–3 plain-language sentences, no markdown/jargon, for someone new to this codebase: what the card is and why it matters. Always shown, never collapsed — not a second title, not a teaser for the description.";
352
- const DESCRIPTION_DESCRIBE = 'The full body ("Context"): evidence, examples, technical detail; markdown, collapsed by default. A question for the operator and its options go in issue_problem, not here. Style rules: see issue_comment\'s `text` param.';
350
+ const TITLE_DESCRIBE = 'Short, specific label naming the domain (e.g. "Guest checkout rejects gift-card carts"). Never generic ("Fix bug", "Follow-up").';
351
+ const SUMMARY_DESCRIBE = "1–3 plain-language sentences, no markdown/jargon: what the card is and why it matters. Always shown, never collapsed — not a second title, not a teaser.";
352
+ const DESCRIPTION_DESCRIBE = 'The full body ("Context"): evidence, examples, technical detail; markdown, collapsed by default. Operator questions go in issue_problem. Style: see issue_comment\'s `text` param.';
353
353
  // DX-3335 — condensed from the now-deleted danxbot:comment-style skill (PLN-11
354
354
  // R-16: fold a skill's rule into the tool description it governs rather than
355
355
  // require a separate load). Shared by every prose-writing field below so an
@@ -412,7 +412,7 @@ strictTool("issue_get",
412
412
  .optional()
413
413
  .describe("Single-id form only. How many comments the page holds (server default 20), the window anchored at the " +
414
414
  "NEWEST comment. An out-of-range value (server max 200) is refused by the server with its own 400, " +
415
- "never silently clamped here. Requires `fields` to include `comments`."),
415
+ "never silently clamped here. Requires `fields:[\"comments\"]`."),
416
416
  comments_offset: z
417
417
  .number()
418
418
  .int()
@@ -488,7 +488,7 @@ strictTool("issue_create", '`plan` is REQUIRED on every create — no default, n
488
488
  priority: z
489
489
  .union([z.enum(PRIORITY_TIER_WORDS), z.number()])
490
490
  .optional()
491
- .describe('A tier word ("lowest"=0–1…"critical"=5–6; higher number = more urgent, prefer tier word) or number in [0,6); higher numbers are more urgent. Omit for the route\'s own default.'),
491
+ .describe('Tier word ("lowest"…"critical", prefer this) or number in [0,6); higher = more urgent. Omit for the route\'s own default.'),
492
492
  // DX-3238 — `assigned_agent` is DELIBERATELY left undeclared here, unlike
493
493
  // `priority` above. The route accepts it (`ALLOWED_CREATE_KEYS`,
494
494
  // create.ts:39-56) but it only matters for the create→`list_id`-lands-
@@ -525,7 +525,7 @@ strictTool("issue_edit", 'Patch a card. ALLOWED keys: title, summary, descriptio
525
525
  status: z
526
526
  .enum(CHECKLIST_ITEM_STATUSES)
527
527
  .optional()
528
- .describe("Optional full status; omitted → from `checked`. `deferred` REQUIRES a non-empty `detail`; a 📡 item can never be `passing`."),
528
+ .describe("Optional full status; omitted → from `checked`. See tool description for the deferred/📡 rules."),
529
529
  detail: z
530
530
  .string()
531
531
  .optional()
@@ -552,7 +552,7 @@ strictTool("issue_edit", 'Patch a card. ALLOWED keys: title, summary, descriptio
552
552
  priority: z
553
553
  .union([z.enum(PRIORITY_TIER_WORDS), z.number()])
554
554
  .optional()
555
- .describe('A tier word ("lowest"=0–1…"critical"=5–6; higher number = more urgent, prefer tier word) or number in [0,6); higher numbers are more urgent. The only way to set priority.'),
555
+ .describe('Tier word ("lowest"…"critical", prefer this) or number in [0,6); higher = more urgent — see tool description for why this is the only way to set it.'),
556
556
  list_id: z.string().min(1).nullable().optional(),
557
557
  triage_enabled: z
558
558
  .boolean()
@@ -647,15 +647,15 @@ const STEP_INPUT = z.lazy(() => z
647
647
  .int()
648
648
  .positive()
649
649
  .optional()
650
- .describe("present -> this node IS an existing step (matched on this id; 400 if not a live child of this exact " +
651
- "parent scope). absent -> matched against unclaimed siblings by TITLE (first match), else created new. " +
652
- "An id-less MIDDLE insert recreates every later id-less sibling's row — send ids whenever an existing " +
653
- "step's identity matters (checked progress, a later edit)."),
650
+ .describe("present -> matches an existing step by this id (400 if not a live child of this exact parent scope, " +
651
+ "never claimed by the fallback below); absent -> matches the first unclaimed same-titled sibling " +
652
+ "under the same parent, else creates new (title-based, never positional — keeps later id-less " +
653
+ "siblings' ids intact on a middle insert). Send ids anyway when identity matters (checked progress, " +
654
+ "a later edit) or siblings share a title."),
654
655
  title: z
655
656
  .string()
656
657
  .min(1)
657
- .describe("one imperative line, the thing to do — labels (\"1.\", \"2a\") are DERIVED on read and must never appear " +
658
- "in the title itself."),
658
+ .describe("one imperative line — labels (\"1.\", \"2a\") are DERIVED on read, never put one in the title."),
659
659
  description: z.string().nullable().optional().describe("markdown detail, or null/omitted for none"),
660
660
  steps: z.array(STEP_INPUT).optional().describe("this step's own children, nested — refused past 3 levels total"),
661
661
  })
@@ -669,9 +669,7 @@ strictTool("issue_problem", "A card's PROBLEMS: one statement the operator must
669
669
  .string()
670
670
  .min(1)
671
671
  .optional()
672
- .describe("add/edit; ONE plain sentence, at most 200 characters. For a question: the question itself. For an action: " +
673
- "WHAT IS TO BE DONE — the deed itself, never phrased as a question (\"Rotate the staging DB credential\", " +
674
- "not \"Should we rotate it?\")."),
672
+ .describe("add/edit; ONE plain sentence, at most 200 characters — see `type` below for question vs action phrasing."),
675
673
  context: z
676
674
  .string()
677
675
  .nullable()
@@ -697,16 +695,14 @@ strictTool("issue_problem", "A card's PROBLEMS: one statement the operator must
697
695
  .string()
698
696
  .nullable()
699
697
  .optional()
700
- .describe("add/edit. Plain text, 1-3 sentences — never markdown detail (that's `context`'s job). For a question: " +
701
- "optional, why it matters. For an action: REQUIRED and non-empty — why the action is needed AND why you " +
702
- "(the agent raising it) cannot do it yourself; omitting it on an action add is refused 400 naming this " +
703
- "field, and an edit that would clear it on a live action problem is refused the same way. add: sent only " +
704
- "when given (omit/null on a question means none). edit: omit to keep the stored summary, null to clear it " +
705
- "(refused if the problem is a live action)."),
698
+ .describe("add/edit. Plain text, 1-3 sentences — never markdown (that's `context`'s job). Required+non-empty for an " +
699
+ "action problem (why needed + why you can't do it yourself — see `type`; refused 400 if missing on add or " +
700
+ "cleared on a live action edit); optional for a question. add: sent only when given (omit/null on a " +
701
+ "question means none). edit: omit keeps the stored summary, null clears it."),
706
702
  solutions: z
707
703
  .array(z.object({ title: z.string().min(1), ...SOLUTION_FIELDS, steps: z.array(STEP_INPUT).optional() }).strict())
708
704
  .optional()
709
- .describe("add only; fields as issue_solution add, INCLUDING steps (S6/AC 35332) — an ACTION problem's inline " +
705
+ .describe("add only; fields as issue_solution add, INCLUDING steps — an ACTION problem's inline " +
710
706
  "solutions must each carry at least one step (refused 400 otherwise); a QUESTION's may carry any number, " +
711
707
  "including none."),
712
708
  ...boardField,
@@ -715,27 +711,29 @@ strictTool("issue_problem", "A card's PROBLEMS: one statement the operator must
715
711
  strictTool("issue_solution", "One problem's options, AND one solution's individual " +
716
712
  "procedure steps; `problem_id` is REQUIRED (from issue_problem list/add; " +
717
713
  "another problem's solution id → 404). add {title, body?, pro?, con?, recommended?, steps?}: title names the " +
718
- "option/route, body its markdown detail, pro/con the case for and against; `steps` authors its WHOLE procedure " +
719
- "in one call — an ordered array of {title, description?, steps?}, nestable to 3 levels (a 4th refused 400 " +
720
- "naming the step and the limit). AC 35332 — under an ACTION problem `steps` must be non-empty (400 otherwise); " +
721
- "under a QUESTION it may be empty/omitted. Labels (\"1\", \"2a\", \"2a.i\") are ALWAYS DERIVED on read from " +
722
- "position — never put one in a title. edit :sid {base_hash, ...changed fields, steps?, steps_base_hash?}: " +
723
- "`steps` omitted leaves the procedure untouched; an explicit array (including [], refused if it would leave " +
724
- "an action's solution with zero steps) DIFFS against the stored tree the same way STEP_INPUT's own `id` field " +
725
- "describes. `steps_base_hash` is REQUIRED whenever `steps` is sent — the solution's current `steps_hash`; " +
726
- "stale → 409 `stale_steps` with the current tree. Prefer add_step/edit_step/remove_step below over resending " +
727
- "the whole tree for a one-word fix. remove :sid {base_hash}. A stale base_hash → 409 `stale_solution` with " +
728
- "currentHash + currentSolution: merge, then retry. At most ONE live recommended per problem (second → 409 " +
729
- "naming `recommended_solution_id`). A CHOSEN option's words AND whole procedure are frozen — every route below " +
730
- "also refuses once a decision has chosen this solution (add a new solution instead).\n\n" +
731
- "Granular single-step edits: add_step {solution_id, title, " +
732
- "description?, parent_step_id?, position?} — title is ONE imperative line, never a label (derived on read); " +
733
- "parent_step_id omitted/null = top-level; position 1-indexed among current live children, omitted = append, " +
734
- "beyond sibling count refused 400; nesting past depth 3 → 400. edit_step {solution_id, step_id, base_hash, " +
735
- "title?, description?} — never moves a step. remove_step {solution_id, step_id, base_hash} — soft-deletes it " +
736
- "and its own live children; refused 409 if this solution belongs to an ACTION problem and removing it would " +
737
- "leave zero live steps. A stale base_hash on a step → 409 `stale_step` with currentHash + currentStep (its " +
738
- "derived `label`).", {
714
+ "option, body/pro/con its markdown detail and case for/against; `steps` authors the WHOLE procedure in one " +
715
+ "call — {title, description?, steps?}[], nestable to 3 levels (4th refused 400, naming the step + limit). " +
716
+ "ACTION problem: `steps` must be non-empty (400 otherwise); QUESTION: may be omitted/empty. Labels " +
717
+ "(\"1\", \"2a\", \"2a.i\") are DERIVED on read from position — never put one in a title. edit :sid {base_hash, " +
718
+ "...changed fields, steps?, steps_base_hash?}: `steps` omitted leaves the procedure untouched; an explicit " +
719
+ "array (incl. [], refused if it leaves an action's solution with zero steps) DIFFS against it — send an " +
720
+ "existing step's `id` to keep it (even while retitling/reordering), omit `id` for a new step; any existing " +
721
+ "step left out is removed. An id-less node matches the first unclaimed same-titled sibling under the same " +
722
+ "parent (never positional), so an id-less MIDDLE insert keeps later id-less siblings' ids — send ids anyway " +
723
+ "when identity matters. `steps_base_hash` REQUIRED whenever `steps` is sent — the solution's current " +
724
+ "`steps_hash`; stale → 409 `stale_steps` with the current tree. Never resend the whole tree to fix one word " +
725
+ "— use add_step/edit_step/remove_step below. remove :sid {base_hash}: stale → 409 `stale_solution` with " +
726
+ "currentHash + currentSolution, merge and retry. At most ONE live recommended per problem — a second → 409 " +
727
+ "naming `recommended_solution_id`. A CHOSEN option's words and WHOLE PROCEDURE are frozen (409 — add a new " +
728
+ "one instead); every steps route below refuses once a decision has chosen this solution.\n\n" +
729
+ "Granular single-step actions, no tree resend: add_step {solution_id, title, description?, parent_step_id?, " +
730
+ "position?} — title is one imperative line (labels derived, never typed); parent_step_id omitted/null = " +
731
+ "top-level; position 1-indexed among current live children, omitted = append, beyond sibling count → 400 " +
732
+ "(never clamped); nesting past depth 3 → 400. edit_step {solution_id, step_id, base_hash, title?, " +
733
+ "description?} — never moves a step. remove_step {solution_id, step_id, base_hash} — soft-deletes it and " +
734
+ "its live children; refused 409 if this solution belongs to an ACTION problem and removing it leaves zero " +
735
+ "live steps. Stale base_hash on a step → 409 `stale_step` with currentHash + currentStep (real derived " +
736
+ "`label`).", {
739
737
  id: z.string().min(1),
740
738
  action: z.enum(["add", "edit", "remove", "add_step", "edit_step", "remove_step"]),
741
739
  problem_id: z.number().int().positive(),
@@ -833,8 +831,8 @@ strictTool("quality_gate_instruction", "Fetch a named quality gate's current ins
833
831
  // ---------------- issue_retro ----------------
834
832
  strictTool("issue_retro", "Replace the retro block: {good, bad, correctable_danxbot_problem, correctable_danxbot_problem_description, action_item_ids[], commits[], tests[]}. Refuses 409 unless the card is terminal (completed_at or cancelled_at). good/bad upsert; the three list fields REPLACE (soft-delete prior live rows, fresh ordinals). action_item_ids[] entries match <PREFIX>-N. commits[] entries: {sha, subject?}. tests[] is required (empty array = ran none): one row per test GROUP that ran (name the group, not individual unit tests) or per e2e test (kind:'e2e', listed explicitly since few/expensive); each row {name, kind:'group'|'e2e', num_tests, num_passing_tests, duration_ms} required, num_assertions/num_passing_assertions nullable (omit when the runner has no assertion totals).", {
835
833
  id: z.string().min(1),
836
- good: z.string().describe("Free-form markdown (prose or a list — pick whichever reads fastest, they render identically). Style rules: see issue_comment's `text` param."),
837
- bad: z.string().describe("Free-form markdown (prose or a list — pick whichever reads fastest, they render identically). Style rules: see issue_comment's `text` param."),
834
+ good: z.string().describe("Free-form markdown (prose or list, they render identically). Style: see issue_comment's `text` param."),
835
+ bad: z.string().describe("Free-form markdown (prose or list, they render identically). Style: see issue_comment's `text` param."),
838
836
  correctable_danxbot_problem: z
839
837
  .boolean()
840
838
  .describe("Required. Did this dispatch hit a problem in danxbot's OWN code/configuration (not \"this card was hard\") that danxbot could change so it stops happening? A true+described answer starts exactly one automated repair (fix or a ready card) — the only reliable channel for a danxbot defect found mid-dispatch. Answer explicitly every time, never a default."),
@@ -947,14 +945,14 @@ strictTool("plan_get", "Read a plan. Pass `plan_id` to read ANY plan; OMIT to re
947
945
  .int()
948
946
  .nonnegative()
949
947
  .optional()
950
- .describe("Where the `cards` page starts (default 0). Requires `fields` to include `cards`. Page with cards_offset while cards_offset + cards.length < cards_total."),
948
+ .describe("Where the `cards` page starts (default 0). Requires `fields:[\"cards\"]`. Page with cards_offset while cards_offset + cards.length < cards_total."),
951
949
  cards_limit: z
952
950
  .number()
953
951
  .int()
954
952
  .positive()
955
953
  .max(LIST_PAGE_MAX_LIMIT)
956
954
  .optional()
957
- .describe("How many cards one page holds, 1.." + LIST_PAGE_MAX_LIMIT + " (default " + PLAN_GET_CARDS_DEFAULT_LIMIT + "). Requires `fields` to include `cards`."),
955
+ .describe("How many cards one page holds, 1.." + LIST_PAGE_MAX_LIMIT + " (default " + PLAN_GET_CARDS_DEFAULT_LIMIT + "). Requires `fields:[\"cards\"]`."),
958
956
  events_limit: z
959
957
  .number()
960
958
  .int()
@@ -1049,9 +1047,9 @@ strictTool("plan_update_note", 'Edit a plan note. `content_hash` must be the not
1049
1047
  content_hash: z.string().describe("The note's `contentHash` from your last read. Required."),
1050
1048
  title: z.string().min(1).optional().describe("New title, at most 60 characters. Omit to keep the stored title."),
1051
1049
  body: z.string().min(1).optional().describe("New wrap-up, at most 250 characters. Omit to keep the stored body."),
1052
- card_ids: z.array(z.string().min(1)).optional().describe("REPLACES the whole link set when sent (with record_refs/section_ids)."),
1053
- record_refs: z.array(z.string().min(1)).optional().describe("REPLACES the whole link set when sent (with card_ids/section_ids)."),
1054
- section_ids: z.array(z.number().int().positive()).optional().describe("REPLACES the whole link set when sent (with card_ids/record_refs)."),
1050
+ card_ids: z.array(z.string().min(1)).optional().describe("Part of the link-set group — see tool description."),
1051
+ record_refs: z.array(z.string().min(1)).optional().describe("Part of the link-set group — see tool description."),
1052
+ section_ids: z.array(z.number().int().positive()).optional().describe("Part of the link-set group — see tool description."),
1055
1053
  }, async (args) => jsonResult(await planUpdateNote(client, args)));
1056
1054
  strictTool("plan_delete_note", 'Soft-delete a plan note. `content_hash` must be the note\'s last-read `contentHash` — stale deletes nothing, returns `{error: "stale_plan_note", currentHash, currentTitle, currentBody, currentLinks}` (same shape as `plan_update_note`). Takes an explicit `plan_id` (see `plan_add_note`). Unknown plan, note, or already-deleted note → 404. Returns `{notes_count}`.', {
1057
1055
  plan_id: z.number().int().positive().describe("The plan id, from `plan_list`."),
package/package.json CHANGED
@@ -1,42 +1,42 @@
1
- {
2
- "name": "@thehammer/danx-dashboard-mcp",
3
- "version": "0.1.142",
4
- "description": "Stdio MCP server wrapping danxbot's dashboard /api/issues/* normalized DB-backed HTTP routes for dispatched agents (DX-704 Phase 2).",
5
- "license": "MIT",
6
- "type": "module",
7
- "main": "dist/index.js",
8
- "bin": {
9
- "danx-dashboard-mcp": "dist/index.js"
10
- },
11
- "files": [
12
- "dist",
13
- "README.md"
14
- ],
15
- "repository": {
16
- "type": "git",
17
- "url": "https://github.com/newms87/danxbot.git",
18
- "directory": "packages/danx-dashboard-mcp"
19
- },
20
- "engines": {
21
- "node": ">=20"
22
- },
23
- "scripts": {
24
- "build": "tsc -p tsconfig.json && node -e \"require('fs').chmodSync('dist/index.js', 0o755)\"",
25
- "verify:boot": "bash scripts/verify-boot.sh",
26
- "start": "node dist/index.js",
27
- "dev": "tsx src/index.ts",
28
- "gen-tool-defs": "tsx scripts/gen-tool-defs.ts",
29
- "test": "vitest run",
30
- "test:watch": "vitest"
31
- },
32
- "dependencies": {
33
- "@modelcontextprotocol/sdk": "1.29.0",
34
- "zod": "^3.25.76"
35
- },
36
- "devDependencies": {
37
- "@types/node": "^22.0.0",
38
- "tsx": "^4.0.0",
39
- "typescript": "^5.7.0",
40
- "vitest": "^4.1.5"
41
- }
42
- }
1
+ {
2
+ "name": "@thehammer/danx-dashboard-mcp",
3
+ "version": "0.1.143",
4
+ "description": "Stdio MCP server wrapping danxbot's dashboard /api/issues/* normalized DB-backed HTTP routes for dispatched agents (DX-704 Phase 2).",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "dist/index.js",
8
+ "bin": {
9
+ "danx-dashboard-mcp": "dist/index.js"
10
+ },
11
+ "files": [
12
+ "dist",
13
+ "README.md"
14
+ ],
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "https://github.com/newms87/danxbot.git",
18
+ "directory": "packages/danx-dashboard-mcp"
19
+ },
20
+ "engines": {
21
+ "node": ">=20"
22
+ },
23
+ "scripts": {
24
+ "build": "tsc -p tsconfig.json && node -e \"require('fs').chmodSync('dist/index.js', 0o755)\"",
25
+ "verify:boot": "bash scripts/verify-boot.sh",
26
+ "start": "node dist/index.js",
27
+ "dev": "tsx src/index.ts",
28
+ "gen-tool-defs": "tsx scripts/gen-tool-defs.ts",
29
+ "test": "vitest run",
30
+ "test:watch": "vitest"
31
+ },
32
+ "dependencies": {
33
+ "@modelcontextprotocol/sdk": "1.29.0",
34
+ "zod": "^3.25.76"
35
+ },
36
+ "devDependencies": {
37
+ "@types/node": "^22.0.0",
38
+ "tsx": "^4.0.0",
39
+ "typescript": "^5.7.0",
40
+ "vitest": "^4.1.5"
41
+ }
42
+ }