@thehammer/danx-dashboard-mcp 0.1.250 → 0.1.254

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 CHANGED
@@ -11,8 +11,8 @@ Stdio MCP server fronting danxbot's dashboard HTTP API. Since DX-3465 it exposes
11
11
  | `DANXBOT_DASHBOARD_URL` | Dashboard base, e.g. `http://danxbot-dashboard:5555` (inside compose) or `http://localhost:5555` (host) |
12
12
  | `DANX_DASHBOARD_CREDENTIAL` | A DISPATCH's credential: the bearer verbatim, substituted into the materialized `.mcp.json` from its overlay (DX-2862, `src/credential.ts`). Authoritative when set; it never signs out. An operator session sets nothing: with no credential the server boots SIGNED OUT, every tool except `plan_connect` returns one "not signed in" halt, and `plan_connect` requests access (below). The same resolver serves the `bridge` subcommand, so a session's tools and its event stream can never sign in as two different identities |
13
13
  | `DANXBOT_TEAM` | OPTIONAL, non-secret (DX-4446): the team slug (or id) whose approvers see this session's access request in their danxbot site. Set it in the repo's `.mcp.json` env block or the shell. Unset, the request is answered by link only. A value that is not a team slug fails at boot; a team the dashboard does not know makes `plan_connect` answer `unknown_team` (fix `DANXBOT_TEAM`), never a silent link fallback |
14
- | `DANX_REPO_NAME` | Repo half of the qualified board id |
15
- | `DANXBOT_BOARD_NAME` | Board-slug half of the qualified board id (the dispatch injects `board.slug` here) |
14
+ | `DANX_REPO_NAME` | OPTIONAL (DX-4555): repo half of the qualified board id; sent as the access request's repo label |
15
+ | `DANXBOT_BOARD_NAME` | OPTIONAL (DX-4555; needs `DANX_REPO_NAME`): board-slug half of the qualified board id (the dispatch injects `board.slug` here). With neither variable set (the danxbot plugin's own server) the server has no default board: a board-scoped call must carry its `board` argument or it fails loud |
16
16
 
17
17
  BOARD-ONLY (DX-1171): board is the first-level concept; repo is DERIVED from board server-side, never passed. At boot the package composes the dispatch's qualified board id `<repo>:<slug>` from `DANX_REPO_NAME` + `DANXBOT_BOARD_NAME` and appends it as `?board=<id>` (and `body.board` on create) to every `/api/issues/*` request. Each tool exposes an optional `board` arg — a FULL qualified id `<repo>:<slug>` (e.g. `platform:the-supply-operations-hub`) — that overrides it per-call for cross-board reads/writes (unknown board → 404). The dashboard owns board→repo resolution; this package never sends `repo`.
18
18
 
@@ -111,7 +111,12 @@ export class DashboardHttpClient {
111
111
  const params = new URLSearchParams(existingQs ?? "");
112
112
  // DX-3750 / DX-4303 — only a route the dashboard takes `?board=` on gets the stamp; see `route-board-scope.ts`.
113
113
  if (isBoardScopedRoute(method, `${basePath}${cleanPath}`)) {
114
- params.set("board", boardOverride ?? this.config.board);
114
+ const board = boardOverride ?? this.config.board;
115
+ // DX-4555: a board-less server (the danxbot plugin's, no DANX_REPO_NAME/DANXBOT_BOARD_NAME) has no board to stamp.
116
+ if (board === undefined) {
117
+ throw new Error(`[danx-dashboard-mcp] ${method} ${basePath}${cleanPath} is board-scoped and this server has no configured board: pass the \`board\` argument (a qualified id, <repo>:<slug>)`);
118
+ }
119
+ params.set("board", board);
115
120
  }
116
121
  if (extraQuery) {
117
122
  for (const [k, v] of Object.entries(extraQuery)) {
package/dist/index.js CHANGED
@@ -52,7 +52,7 @@
52
52
  * DANX_REPO_NAME + DANXBOT_BOARD_NAME and sends it as `board` — the
53
53
  * dashboard owns board→repo resolution.
54
54
  *
55
- * Env at boot (validated fail-loud — missing → process.exit(1)):
55
+ * Env at boot (validated fail-loud — a missing required var → process.exit(1)):
56
56
  * DANXBOT_DASHBOARD_URL dashboard base (e.g. http://danxbot-dashboard:5555)
57
57
  * the CREDENTIAL (DX-4390), resolved by the ONE resolver this package's event bridge
58
58
  * also uses (`credential.ts`, DX-2862), and never a reason to exit:
@@ -67,8 +67,8 @@
67
67
  * Unset: the request is answered by link only. Set but not a
68
68
  * valid ref, or unknown to the dashboard: fails loud, never a
69
69
  * silent fall back to the link flow.
70
- * DANX_REPO_NAME repo half of the qualified board id
71
- * DANXBOT_BOARD_NAME board-slug half of the qualified board id
70
+ * DANX_REPO_NAME OPTIONAL (DX-4555): repo half of the qualified board id
71
+ * DANXBOT_BOARD_NAME OPTIONAL (DX-4555; needs DANX_REPO_NAME): board-slug half of the qualified board id
72
72
  * (the dispatch injects `board.slug` here);
73
73
  * composed as `<repo>:<slug>` and appended as
74
74
  * ?board=<id>. Each tool's optional `board` arg
@@ -172,17 +172,25 @@ let permissionWatch;
172
172
  * halves the worker injects and build the HTTP client. DANXBOT_BOARD_NAME
173
173
  * already carries the board SLUG at the spawn site (src/dispatch/core.ts sets it
174
174
  * from `board.slug`), so a plain join yields the canonical id — no transform.
175
- * Both halves are fail-loud required (Core Principle 1 — no fallback).
175
+ * Both unset (DX-4555) leaves the server board-less: a board-scoped call must then carry its own `board`
176
+ * argument (`DashboardHttpClient.buildUrl` refuses otherwise). A slug without its repo fails loud.
176
177
  */
177
178
  function boot() {
178
179
  const dashboardUrl = readEnvOrDie("DANXBOT_DASHBOARD_URL");
179
- const repo = readEnvOrDie("DANX_REPO_NAME");
180
+ const repo = readEnvOptional("DANX_REPO_NAME");
181
+ const boardName = readEnvOptional("DANXBOT_BOARD_NAME");
182
+ // DX-4555: a session started from the danxbot plugin's own server names no board; each call then
183
+ // carries a `board` argument. A board slug with no repo half is a half-written .mcp.json: fail loud.
184
+ if (boardName !== undefined && repo === undefined) {
185
+ console.error("[danx-dashboard-mcp] DANXBOT_BOARD_NAME is set but DANX_REPO_NAME is missing or empty");
186
+ process.exit(1);
187
+ }
180
188
  const session = readSessionConfig();
181
189
  config = {
182
190
  baseUrl: dashboardUrl,
183
191
  // DX-4390 — set by `SessionAccess` below; "" while signed out.
184
192
  token: "",
185
- board: `${repo}:${readEnvOrDie("DANXBOT_BOARD_NAME")}`,
193
+ board: boardName === undefined ? undefined : `${repo}:${boardName}`,
186
194
  // DX-1398 — OPTIONAL cross-process trace context. Present → stamped on every
187
195
  // outbound call so the agent's card writes chain under the launch; absent
188
196
  // (untraced dispatch) → omitted, and the dashboard mints a fresh root.
@@ -195,7 +203,7 @@ function boot() {
195
203
  access = new SessionAccess({
196
204
  dashboardUrl,
197
205
  sessionId: session?.id,
198
- repo,
206
+ repo: repo ?? null,
199
207
  clientVersion: readOwnVersion(),
200
208
  team: readTeamRef(),
201
209
  setToken: (token) => dashboardClient.setToken(token),
@@ -632,6 +640,7 @@ strictTool("request_permission", "Ask a person to grant this key ONE MORE permis
632
640
  // a different reason: it MAKES a plan rather than acting on one, so there is
633
641
  // no existing plan for an id to name yet.
634
642
  strictTool("plan_connect",
643
+ // DX-4493 — the `naming` block carries the thread title the session must take before it connects.
635
644
  // DX-4318 — states only what to do with the reply. What it used to add arrives where it is used: a rejected
636
645
  // action's rule in the event line itself (`listen.ts`), the mantra in the reply's own `reminders[]` text.
637
646
  // DX-4390 — signed out, this is also how the session asks for access.
@@ -644,7 +653,7 @@ strictTool("plan_connect",
644
653
  "titles and a first page of open cards) and `listenerHealth` (when its `state` is not `healthy`, do its " +
645
654
  "`nextStep`). A session is on at most one plan: connecting elsewhere moves it, and `movedFrom` names the " +
646
655
  "plan it left. Afterwards a `danxbot_api` call to a `/api/plans/mine/...` route acts on this plan and " +
647
- "takes no plan id. DX-4493: a reply's `naming` block (status `needed`) gives your `{Name}: PLAN-NNN {short " +
656
+ "takes no plan id. A reply's `naming` block (status `needed`) gives your `{Name}: PLAN-NNN {short " +
648
657
  "title}`; rename this thread to it (`set_session_title`, or ask for `/rename`), then connect again with that `title`.", {
649
658
  plan_id: z
650
659
  .number()
@@ -723,7 +732,7 @@ async function main() {
723
732
  process.stdin.once("end", () => permissionWatch.stop());
724
733
  const transport = new StdioServerTransport();
725
734
  await server.connect(transport);
726
- console.error(`danx-dashboard-mcp running on stdio (dashboard=${config.baseUrl}, board=${config.board})`);
735
+ console.error(`danx-dashboard-mcp running on stdio (dashboard=${config.baseUrl}, board=${config.board ?? "none, each call names its own"})`);
727
736
  }
728
737
  // Boot the stdio server ONLY when run as the entrypoint (the published bin).
729
738
  // Importing this module (the tool-defs generator + its drift test) registers
package/dist/lifecycle.js CHANGED
@@ -252,7 +252,7 @@ export function registerLifecycleTools(strictTool) {
252
252
  "register the Slack mirror so future card updates re-render the thread's card-view message. Thread " +
253
253
  "scope is IMPLICIT (channel + thread_ts come from the dispatch) — pass only the issue id. Call this " +
254
254
  "AFTER posting your substantive answer with danxbot_slack_reply (that reply becomes the card-view " +
255
- '"message #1"). A second call on an already-linked thread fails loud with a 409 — never a silent re-link.', { issue_id: z.string().describe('The card id to bind this thread to (e.g. "DX-1086").') }, async (args) => {
255
+ '"message #1"). A second call on an already-linked thread fails loud with a 409 — never a silent re-link.', { issue_id: z.string().describe('The card id to bind this thread to (e.g. "ABC-123").') }, async (args) => {
256
256
  const url = readLifecycleUrlsFromEnv().linkThreadToIssue;
257
257
  if (!url)
258
258
  throw new Error("link_thread_to_issue called outside a Slack dispatch");
@@ -310,10 +310,11 @@ export function registerLifecycleTools(strictTool) {
310
310
  .enum(["Bug", "Story", "Chore"])
311
311
  .describe("The card type. Never Epic/Feature — a single Slack request never warrants a container card."),
312
312
  title: z.string().describe("Card title."),
313
+ // DX-4515: the create route refuses a card with no summary.
313
314
  summary: z
314
315
  .string()
315
316
  .min(1)
316
- .describe("REQUIRED (DX-4515): one plain line saying what the card is, shown at the top of the card page."),
317
+ .describe("REQUIRED: one plain line saying what the card is, shown at the top of the card page."),
317
318
  description: z
318
319
  .string()
319
320
  .describe("Card description — sourced from the triggering human message's content."),
@@ -397,7 +398,7 @@ export function registerLifecycleTools(strictTool) {
397
398
  strictTool("request_code_review", "TERMINAL for this dispatch — call it when your diff is done and its own tests are green, instead of " +
398
399
  "danxbot_complete. Pushes your branch (card/<id> to origin/card/<id>), then dispatches every code " +
399
400
  "gate this card carries as its OWN independent gate-reviewer dispatch (you never sign or review your " +
400
- "own diff — DX-3741). Ends this dispatch 'completed' and releases the card to ToDo; when the card is " +
401
+ "own diff). Ends this dispatch 'completed' and releases the card to ToDo; when the card is " +
401
402
  "re-dispatched to you, read its quality_gates (through danxbot_api): every code gate pass/skipped → " +
402
403
  "run agent-finalize.sh and complete normally; any fail → fix its findings, then call " +
403
404
  "request_code_review again (never sign or remove a code gate yourself — removal is refused, 409). " +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thehammer/danx-dashboard-mcp",
3
- "version": "0.1.250",
3
+ "version": "0.1.254",
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",