@thehammer/danx-dashboard-mcp 0.1.190 → 0.1.193
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 -2
- package/dist/bridge.js +1 -1
- package/dist/credential.js +5 -5
- package/dist/danxbot-api-examples.js +59 -0
- package/dist/handlers.js +1 -1
- package/dist/index.js +9 -4
- package/dist/session-connection.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ Stdio MCP server fronting danxbot's dashboard HTTP API. Since DX-3465 it exposes
|
|
|
9
9
|
| Var | Purpose |
|
|
10
10
|
|---|---|
|
|
11
11
|
| `DANXBOT_DASHBOARD_URL` | Dashboard base, e.g. `http://danxbot-dashboard:5555` (inside compose) or `http://localhost:5555` (host) |
|
|
12
|
-
| `DANXBOT_DASHBOARD_TOKEN_FILE` **or** `
|
|
12
|
+
| `DANXBOT_DASHBOARD_TOKEN_FILE` **or** `DANX_DASHBOARD_CREDENTIAL` | The credential, declared exactly one of two ways (DX-2862, `src/credential.ts`). A FILE holding the bearer (a leading `~` is the user's home) — how an interactive operator session declares one, since its `.mcp.json` is git-tracked and can never carry the token itself. Or the bearer VERBATIM — how a dispatch declares one, substituted from its overlay. A declared file is AUTHORITATIVE and its failure is FATAL: an inherited `DANX_DASHBOARD_CREDENTIAL` is never consulted instead, because on an operator machine that value routinely names a different (local-dev) dashboard, which answers healthily and empty. The same resolver serves the `bridge` subcommand below, so a session's tools and its event stream can never sign in as two different identities |
|
|
13
13
|
| `DANX_REPO_NAME` | Repo half of the qualified board id |
|
|
14
14
|
| `DANXBOT_BOARD_NAME` | Board-slug half of the qualified board id (the dispatch injects `board.slug` here) |
|
|
15
15
|
|
|
@@ -39,7 +39,7 @@ A program runs this, not an agent: the danxbot Claude Code plugin's plan event b
|
|
|
39
39
|
CLAUDE_CODE_SESSION_ID=<session> npx -y @thehammer/danx-dashboard-mcp@<version> bridge [--resume-ids <id>,<id>,...]
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
-
- **Credential — the session's own, never the ambient one (DX-2862).** The session id is the only thing `bridge` takes from its environment; `--resume-ids` is the only argument and holds no secret. Everything else comes from the connection record this session's own danx-dashboard MCP server wrote on `plan_connect` (`src/session-connection.ts`): the dashboard URL, the credential's SOURCE, and its FINGERPRINT. `bridge` resolves that source through the same resolver the server used and refuses to run unless the result fingerprints identically — `credential_mismatch`. Before that fix it used whatever `
|
|
42
|
+
- **Credential — the session's own, never the ambient one (DX-2862).** The session id is the only thing `bridge` takes from its environment; `--resume-ids` is the only argument and holds no secret. Everything else comes from the connection record this session's own danx-dashboard MCP server wrote on `plan_connect` (`src/session-connection.ts`): the dashboard URL, the credential's SOURCE, and its FINGERPRINT. `bridge` resolves that source through the same resolver the server used and refuses to run unless the result fingerprints identically — `credential_mismatch`. Before that fix it used whatever `DANX_DASHBOARD_CREDENTIAL` the session's environment held, and on a machine where that differed from the server's credential the dashboard admitted the stream and dropped every event, with nothing logged anywhere.
|
|
43
43
|
- **No client-side reach check (DX-2920).** `bridge` used to prove board reach itself, before ever opening the stream (`GET /api/plans/mine?fields=cards` + a per-board `GET /api/issues/:id/problems`). That check is DELETED. The dashboard briefly enforced the identical requirement server-side (DX-2863: refusing a mint or stream admission with `403 issuer_cannot_read_plan_boards`, and ending a live stream `{"reason":"scope_narrowed","boards":[…]}`), but DX-3468 removed that mechanism too — DX-3409 made every credential team-bound, so a plan session's issuer and every board its connected plan covers are always in the same team, and the check could never fire any more. `bridge` no longer independently verifies board reach at all, client- or server-side. Once a ticket is minted, `bridge` emits, once, `{"type":"ready"}` — no `boards` field, since reach is no longer proven client-side.
|
|
44
44
|
- **Ticket.** `bridge` mints the session's listener ticket (`POST /api/plan-sessions/me/stream-ticket`, 10 s timeout) and keeps it in the process. A ticket authorizes reading that one session's event stream and nothing else, and is only issued while the session is connected to a plan. Minting a new one ends the previous listener.
|
|
45
45
|
- **Output — JSON Lines.** One `{"type":"event","id":<id|null>,"text":"…"}` per event on the connected plan's cards, where `text` is `[DX-8 "Title" repo:board] newms87 answered "Which rollout order?": chose "Pause E2E" — note: "only this week"`, `… answered "…": "<free-form answer>"`, `… commented: "…"`, `… opened a problem: "…"`, `… blocked the card: "…"`, or `… unblocked the card`. An event it cannot read still produces one, with a `could not read event` text. Nothing for keep-alives, reconnects or re-mints. The session's own writes are never echoed back to it.
|
package/dist/bridge.js
CHANGED
|
@@ -283,7 +283,7 @@ export class BridgeStartError extends Error {
|
|
|
283
283
|
/**
|
|
284
284
|
* The session's connection, as its own MCP server recorded it — and the proof
|
|
285
285
|
* that this process holds the SAME credential that server does. Never falls back
|
|
286
|
-
* to an ambient
|
|
286
|
+
* to an ambient env credential: that fallback is precisely DX-2862.
|
|
287
287
|
*/
|
|
288
288
|
const INSTANCE_ID_ENV = "DANX_BRIDGE_INSTANCE_ID";
|
|
289
289
|
/**
|
package/dist/credential.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* and `danx-dashboard-mcp bridge` (the session's event stream, run by the
|
|
7
7
|
* danxbot plugin's plan event bridge). Before this module they resolved their
|
|
8
8
|
* credential independently — the server through danxbot's operator launcher,
|
|
9
|
-
* which read a token file, and the bridge from whatever
|
|
9
|
+
* which read a token file, and the bridge from whatever credential variable
|
|
10
10
|
* happened to be in the session's environment. On a machine where those two
|
|
11
11
|
* differ (DX-2862: `ffe9f8a882f0…` in the environment, `6f9bc6e94b83…` in
|
|
12
12
|
* `~/.config/danxbot/dashboard-token-gpt`) the session's tools worked, the
|
|
@@ -21,15 +21,15 @@
|
|
|
21
21
|
* home). The file's trimmed contents are the credential. This is how an
|
|
22
22
|
* interactive operator session declares one, because its `.mcp.json` is
|
|
23
23
|
* git-tracked and can never carry the bearer itself.
|
|
24
|
-
* - `
|
|
24
|
+
* - `DANX_DASHBOARD_CREDENTIAL` — the credential verbatim. This is how a
|
|
25
25
|
* DISPATCH declares one: the worker substitutes it into the materialized
|
|
26
26
|
* `.mcp.json` from the dispatch overlay.
|
|
27
27
|
*
|
|
28
28
|
* A DECLARED FILE IS AUTHORITATIVE, AND ITS FAILURE IS FATAL. When the file
|
|
29
|
-
* variable is set, `
|
|
29
|
+
* variable is set, `DANX_DASHBOARD_CREDENTIAL` is never consulted — not even when
|
|
30
30
|
* the file is missing or empty. That is the DX-2483 rule, moved here from the
|
|
31
31
|
* launcher it used to live in: an operator machine routinely carries an
|
|
32
|
-
* inherited
|
|
32
|
+
* inherited env credential naming the LOCAL dev dashboard, which
|
|
33
33
|
* answers healthily and empty for a board it does not carry, so preferring it
|
|
34
34
|
* after a failed file read would turn a loud, fixable error into exactly the
|
|
35
35
|
* silent wrong-dashboard answer this whole module exists to prevent.
|
|
@@ -39,7 +39,7 @@ import { readFileSync } from "node:fs";
|
|
|
39
39
|
import { homedir } from "node:os";
|
|
40
40
|
import { isAbsolute, join, resolve } from "node:path";
|
|
41
41
|
export const CREDENTIAL_FILE_ENV = "DANXBOT_DASHBOARD_TOKEN_FILE";
|
|
42
|
-
export const CREDENTIAL_TOKEN_ENV = "
|
|
42
|
+
export const CREDENTIAL_TOKEN_ENV = "DANX_DASHBOARD_CREDENTIAL";
|
|
43
43
|
/**
|
|
44
44
|
* A credential that cannot be resolved. Carries the remedy beside the reason,
|
|
45
45
|
* because both of this module's callers report failures to a human who is not
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DX-3923 — correct first calls for the routes every card session uses,
|
|
3
|
+
* rendered into the `danxbot_api` tool description (index.ts). The DX-3678
|
|
4
|
+
* grading run found the same avoidable 4xx in session after session (a card
|
|
5
|
+
* read's `fields` sent as a comma string, `fields.parent`, top-level list
|
|
6
|
+
* filters, a card edit sent to `PATCH /api/issues/:id`, a comment keyed
|
|
7
|
+
* `body`, retro `tests[].kind: "unit"`, the AC-tick route hunted through a
|
|
8
|
+
* 36 KB `danxbot_api_list`) because nothing an agent
|
|
9
|
+
* reads before its first call showed the right shape. The tool description
|
|
10
|
+
* is the one surface every profile and every operator session carries.
|
|
11
|
+
*
|
|
12
|
+
* Kept as data, not prose, so danxbot's own tests can check each example
|
|
13
|
+
* against the live server contract (route table, resource fields, body
|
|
14
|
+
* schemas) and fail when one drifts — this package cannot import the
|
|
15
|
+
* server's constants itself (it is published on its own).
|
|
16
|
+
*/
|
|
17
|
+
export const DANXBOT_API_EXAMPLES = {
|
|
18
|
+
cardRead: {
|
|
19
|
+
method: "GET",
|
|
20
|
+
path: "/api/issues/<id>",
|
|
21
|
+
query: { fields: { description: true, ac: true, comments: true, checklists: true, parent_title: true } },
|
|
22
|
+
},
|
|
23
|
+
cardList: {
|
|
24
|
+
method: "GET",
|
|
25
|
+
path: "/api/issues",
|
|
26
|
+
query: { filter: { status_derived: "ToDo", q: "<text>" } },
|
|
27
|
+
},
|
|
28
|
+
cardEdit: {
|
|
29
|
+
method: "PATCH",
|
|
30
|
+
path: "/api/issues/<id>/edit",
|
|
31
|
+
body: { description: "<markdown>", content_hash: "<content_hash from your read>" },
|
|
32
|
+
},
|
|
33
|
+
comment: {
|
|
34
|
+
method: "POST",
|
|
35
|
+
path: "/api/issues/<id>/comments",
|
|
36
|
+
body: { text: "<markdown>" },
|
|
37
|
+
},
|
|
38
|
+
acTick: {
|
|
39
|
+
method: "PATCH",
|
|
40
|
+
path: "/api/issues/<id>/checklists/<checklists[].id>/items/<items[].id>",
|
|
41
|
+
body: { status: "passing" },
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
/** The retro route's accepted `tests[].kind` values. */
|
|
45
|
+
export const RETRO_TEST_KIND_EXAMPLES = ["group", "e2e"];
|
|
46
|
+
function render(example) {
|
|
47
|
+
const { method, path, query, body } = example;
|
|
48
|
+
return JSON.stringify({ method, path, ...(query ? { query } : {}), ...(body ? { body } : {}) });
|
|
49
|
+
}
|
|
50
|
+
/** The description sentence `danxbot_api` carries. */
|
|
51
|
+
export function danxbotApiExamplesText() {
|
|
52
|
+
const e = DANXBOT_API_EXAMPLES;
|
|
53
|
+
return (`Correct first calls: card read ${render(e.cardRead)} (\`fields\` is an object, never a comma string); ` +
|
|
54
|
+
`card list ${render(e.cardList)} (filters only inside \`filter\`); ` +
|
|
55
|
+
`card edit ${render(e.cardEdit)}; ` +
|
|
56
|
+
`comment ${render(e.comment)}; ` +
|
|
57
|
+
`AC tick ${render(e.acTick)}; ` +
|
|
58
|
+
`retro \`tests[].kind\` is ${RETRO_TEST_KIND_EXAMPLES.map((k) => `"${k}"`).join(" or ")}.`);
|
|
59
|
+
}
|
package/dist/handlers.js
CHANGED
|
@@ -81,7 +81,7 @@ export function isDanxbotApiMethod(value) {
|
|
|
81
81
|
*
|
|
82
82
|
* The credential lives inside `client` (resolved once at boot — the
|
|
83
83
|
* operator's declared token file for a session, the per-dispatch
|
|
84
|
-
* `
|
|
84
|
+
* `DANX_DASHBOARD_CREDENTIAL` for a worker) and never appears in this
|
|
85
85
|
* function's arguments or return value — nothing in `DanxbotApiArgs` can
|
|
86
86
|
* carry one. The session id (`x-danx-session-id`) rides every call the SAME
|
|
87
87
|
* way it already does for every other tool on this server
|
package/dist/index.js
CHANGED
|
@@ -55,8 +55,8 @@
|
|
|
55
55
|
* session, whose `.mcp.json` is git-tracked and so
|
|
56
56
|
* can never carry one); authoritative when set, and
|
|
57
57
|
* its failure is fatal — an inherited
|
|
58
|
-
*
|
|
59
|
-
*
|
|
58
|
+
* DANX_DASHBOARD_CREDENTIAL is never consulted instead
|
|
59
|
+
* DANX_DASHBOARD_CREDENTIAL the bearer verbatim (a dispatch, substituted into
|
|
60
60
|
* the materialized `.mcp.json` from its overlay)
|
|
61
61
|
* DANX_REPO_NAME repo half of the qualified board id
|
|
62
62
|
* DANXBOT_BOARD_NAME board-slug half of the qualified board id
|
|
@@ -85,6 +85,7 @@ import { z } from "zod";
|
|
|
85
85
|
import { DashboardHttpClient } from "./http-client.js";
|
|
86
86
|
import { planConnect, danxbotApi, danxbotApiList, danxbotApiSpec, attachFile, DANXBOT_API_METHODS, } from "./handlers.js";
|
|
87
87
|
import { bodyValueSchema, queryValueSchema } from "./types.js";
|
|
88
|
+
import { danxbotApiExamplesText } from "./danxbot-api-examples.js";
|
|
88
89
|
import { registerLifecycleTools, applyLifecycleVisibility, readLifecycleUrlsFromEnv, } from "./lifecycle.js";
|
|
89
90
|
function readEnvOrDie(name) {
|
|
90
91
|
const v = process.env[name];
|
|
@@ -227,6 +228,7 @@ same four verbs:
|
|
|
227
228
|
- create POST <collection> — the resource's own required fields; a 400 names any missing/invalid one.
|
|
228
229
|
- update PATCH/PUT <collection>/:id — send only the fields you are changing; most resources require the
|
|
229
230
|
concurrency token below when the edit touches contested text fields.
|
|
231
|
+
A card is the exception: PATCH /api/issues/<id>/edit.
|
|
230
232
|
- delete DELETE <collection>/:id — usually a soft-delete; the resource's own docs say so.
|
|
231
233
|
Don't know the route for what you need to do? Call \`danxbot_api_list()\` — it lists exactly the routes your
|
|
232
234
|
credential may call, one line each. Then call \`danxbot_api_spec({endpoints: ["METHOD /path/template"]})\` for the
|
|
@@ -331,7 +333,10 @@ strictTool("danxbot_api", "Call any dashboard API route your credential may reac
|
|
|
331
333
|
"route attaches one. `board` overrides this dispatch's configured board for a board-scoped route only " +
|
|
332
334
|
"(ignored by a route that isn't board-scoped). See this server's own instructions (shown once, at connect) " +
|
|
333
335
|
"for the shared resource shape, the stale-hash rule, and how to read a refusal — don't know the route? call " +
|
|
334
|
-
"`danxbot_api_list` first; unsure of its exact shape? call `danxbot_api_spec` first."
|
|
336
|
+
"`danxbot_api_list` first; unsure of its exact shape? call `danxbot_api_spec` first. " +
|
|
337
|
+
// DX-3923 — the shapes agents guessed wrong most often, shown before the
|
|
338
|
+
// first call instead of learned from a 4xx.
|
|
339
|
+
danxbotApiExamplesText(), {
|
|
335
340
|
method: z.enum(DANXBOT_API_METHODS),
|
|
336
341
|
path: z
|
|
337
342
|
.string()
|
|
@@ -466,7 +471,7 @@ async function main() {
|
|
|
466
471
|
// the dashboard URL, credential source, and credential itself come from the
|
|
467
472
|
// session-connection record this server's own `plan_connect` wrote
|
|
468
473
|
// (`session-connection.ts`), never from the bridge's own environment
|
|
469
|
-
// (DX-2862: a bridge trusting its own ambient
|
|
474
|
+
// (DX-2862: a bridge trusting its own ambient env credential is the
|
|
470
475
|
// exact bug this repo fixed).
|
|
471
476
|
//
|
|
472
477
|
// Any OTHER argument is refused rather than ignored, so a typo cannot
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* in the SESSION's environment — not in the MCP server's, which is the only
|
|
7
7
|
* place the server's `.mcp.json` env block exists. So the bridge cannot see
|
|
8
8
|
* what the server was configured with, and before this it simply used its own
|
|
9
|
-
* ambient
|
|
9
|
+
* ambient env credential. When that was a different credential from
|
|
10
10
|
* the server's, every event was dropped silently (the card's incident).
|
|
11
11
|
*
|
|
12
12
|
* WHAT IS WRITTEN, AND WHAT IS NOT. On a successful `plan_connect` the server
|
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.193",
|
|
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",
|