@thehammer/danx-dashboard-mcp 0.1.200 → 0.1.202

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.
@@ -16,6 +16,11 @@
16
16
  * retro before the `complete` transition (the list went AC tick -> retro,
17
17
  * with no transition between) and 14 of 24 writing the transition with no
18
18
  * spec call: the transition is now a shown write, placed before the retro.
19
+ * DX-3923 (phase E) — four triage card reads asked `fields` for
20
+ * `status_derived`, a LIST filter key the card-list example showed, so the
21
+ * list example filters by `q`/`include_closed` instead. Two haiku AC ticks
22
+ * read `ac` (no checklist id) and sent `ac` / `0` as the checklist id: the
23
+ * card read shows only `checklists`, and both tick ids name it.
19
24
  *
20
25
  * Kept as data, not prose, so danxbot's own tests can check each example
21
26
  * against the live server contract (route table, resource fields, body
@@ -26,12 +31,12 @@ export const DANXBOT_API_EXAMPLES = {
26
31
  cardRead: {
27
32
  method: "GET",
28
33
  path: "/api/issues/<id>",
29
- query: { fields: { description: true, ac: true, comments: true, checklists: true, parent_title: true } },
34
+ query: { fields: { description: true, comments: true, checklists: true, parent_title: true } },
30
35
  },
31
36
  cardList: {
32
37
  method: "GET",
33
38
  path: "/api/issues",
34
- query: { filter: { status_derived: "ToDo", q: "<text>" } },
39
+ query: { filter: { q: "<text>", include_closed: true } },
35
40
  },
36
41
  cardCreate: {
37
42
  method: "POST",
@@ -50,7 +55,7 @@ export const DANXBOT_API_EXAMPLES = {
50
55
  },
51
56
  acTick: {
52
57
  method: "PATCH",
53
- path: "/api/issues/<id>/checklists/<checklists[].id>/items/<items[].id>",
58
+ path: "/api/issues/<id>/checklists/<checklists[].id>/items/<checklists[].items[].id>",
54
59
  body: { status: "passing" },
55
60
  },
56
61
  transition: {
package/dist/index.js CHANGED
@@ -86,6 +86,7 @@ import { z } from "zod";
86
86
  import { DashboardHttpClient } from "./http-client.js";
87
87
  import { planConnect, danxbotApi, danxbotApiList, danxbotApiSpec, attachFile, DANXBOT_API_METHODS, } from "./handlers.js";
88
88
  import { bodyValueSchema, queryValueSchema } from "./types.js";
89
+ import { termsAcceptanceCallHalt, termsHaltFor } from "./terms-halt.js";
89
90
  import { danxbotApiExamplesText } from "./danxbot-api-examples.js";
90
91
  import { registerLifecycleTools, applyLifecycleVisibility, readLifecycleUrlsFromEnv, } from "./lifecycle.js";
91
92
  function readEnvOrDie(name) {
@@ -278,6 +279,9 @@ export const server = new McpServer({
278
279
  name: "danx-dashboard-mcp",
279
280
  version: "0.1.0",
280
281
  }, { instructions: SERVER_INSTRUCTIONS });
282
+ function haltResult(text) {
283
+ return { content: [{ type: "text", text }], isError: true };
284
+ }
281
285
  function jsonResult(value) {
282
286
  return {
283
287
  content: [{ type: "text", text: JSON.stringify(value, null, 2) }],
@@ -385,7 +389,17 @@ strictTool("danxbot_api", "Call any dashboard API route your credential may reac
385
389
  },
386
390
  // DX-3997 — the result rides back verbatim, plus the dashboard's own `reminders[]` entries
387
391
  // for this plan-connected session (the server decides; `listener-reminder.ts` only relays).
388
- async (args) => jsonResult(await listenerReminder.apply(await danxbotApi(client, args))));
392
+ async (args) => {
393
+ // DX-4148 — an agent never accepts the Terms, and a terms refusal halts it (see terms-halt.ts).
394
+ const refused = termsAcceptanceCallHalt(args, config.baseUrl);
395
+ if (refused)
396
+ return haltResult(refused);
397
+ const result = await danxbotApi(client, args);
398
+ const halt = termsHaltFor(result);
399
+ if (halt)
400
+ return haltResult(halt);
401
+ return jsonResult(await listenerReminder.apply(result));
402
+ });
389
403
  // ---------------- danxbot_api_list (DX-3465 A5, PLN-11 R-25) ----------------
390
404
  strictTool("danxbot_api_list", "Lists the dashboard API routes your credential may call right now — `[{method, path, summary, permission}]`, " +
391
405
  "one line each, in the route TEMPLATE form `danxbot_api_spec` also expects (e.g. " +
@@ -0,0 +1,33 @@
1
+ const TERMS_ACCEPTANCE_PATH = "/api/account/terms-acceptance";
2
+ // Mirrors `termsAcceptUrl("/")` in src/lib/terms-accept-path.ts (a published package cannot import it);
3
+ // terms-gate.integration.test.ts pins the server's link to the same literal. Only meaningful for an
4
+ // operator session: a dispatch's DANXBOT_DASHBOARD_URL is internal, and agent credentials are never terms-gated.
5
+ const ACCEPT_SCREEN_PATH = "/terms/accept?return=%2F";
6
+ /** Keep in step with `termsRefusalInstruction` in src/dashboard/route-policy.ts. */
7
+ export function haltText(acceptUrl) {
8
+ return ("STOP ALL WORK NOW. The dashboard refuses your access until the person you act for accepts the " +
9
+ "current Terms of Use.\n\n" +
10
+ `Give the person this link to accept them: ${acceptUrl}\n\n` +
11
+ "Do not retry, do not look for another route, and never accept the Terms yourself. " +
12
+ "A dispatched agent ends with danxbot_complete({status: \"failed\", summary}) naming this link, " +
13
+ "and never blocks the card.");
14
+ }
15
+ /** The halt text for a `403 terms_not_accepted` result, or null for any other result. */
16
+ export function termsHaltFor(result) {
17
+ if (result.ok || result.status !== 403)
18
+ return null;
19
+ const body = result.body;
20
+ if (body?.error !== "terms_not_accepted")
21
+ return null;
22
+ if (typeof body.acceptUrl !== "string") {
23
+ throw new Error("[danx-dashboard-mcp] a terms_not_accepted refusal carried no acceptUrl");
24
+ }
25
+ return haltText(body.acceptUrl);
26
+ }
27
+ /** The refusal for a call that would accept the Terms (never an agent's act), or null for any other call. */
28
+ export function termsAcceptanceCallHalt(args, dashboardUrl) {
29
+ const path = args.path.split("?")[0].replace(/\/+$/, "");
30
+ if (args.method !== "POST" || path !== TERMS_ACCEPTANCE_PATH)
31
+ return null;
32
+ return haltText(`${dashboardUrl.replace(/\/+$/, "")}${ACCEPT_SCREEN_PATH}`);
33
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thehammer/danx-dashboard-mcp",
3
- "version": "0.1.200",
3
+ "version": "0.1.202",
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",