@thehammer/danx-dashboard-mcp 0.1.234 → 0.1.237

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
@@ -10,6 +10,7 @@ Stdio MCP server fronting danxbot's dashboard HTTP API. Since DX-3465 it exposes
10
10
  |---|---|
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
+ | `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 |
13
14
  | `DANX_REPO_NAME` | Repo half of the qualified board id |
14
15
  | `DANXBOT_BOARD_NAME` | Board-slug half of the qualified board id (the dispatch injects `board.slug` here) |
15
16
 
@@ -1,4 +1,14 @@
1
1
  import { assertVersionFloorSatisfied, MCP_MIN_VERSION_HEADER } from "./version-floor.js";
2
+ // DX-4446: mirrors TEAM_SLUG_PATTERN in src/lib/auth-rules.ts (this package cannot import the main src/); a team ref is a slug,
3
+ // and a team id's digits are a slug too. Keep the two in step.
4
+ const TEAM_REF_PATTERN = /^[a-z0-9]+(-[a-z0-9]+)*$/;
5
+ /** DX-4446: the `DANXBOT_TEAM` value, or a loud error naming the variable. */
6
+ export function assertMcpTeamRef(value) {
7
+ if (!TEAM_REF_PATTERN.test(value)) {
8
+ throw new Error(`[danx-dashboard-mcp] DANXBOT_TEAM "${value}" is not a team slug or id: lowercase letters and digits, joined by single hyphens`);
9
+ }
10
+ return value;
11
+ }
2
12
  function baseOf(dashboardUrl) {
3
13
  return dashboardUrl.replace(/\/+$/, "");
4
14
  }
@@ -51,6 +61,9 @@ function isCreated(value) {
51
61
  export async function createAccessRequest(fetchImpl, dashboardUrl, input) {
52
62
  const url = `${baseOf(dashboardUrl)}/api/access-requests`;
53
63
  const { status, headers, json } = await postPublic(fetchImpl, url, input);
64
+ if (status === 400 && input.team !== undefined && json?.error === "unknown_team") {
65
+ return { kind: "unknown_team", team: input.team };
66
+ }
54
67
  if (status === 201) {
55
68
  if (!isCreated(json)) {
56
69
  throw new Error(`[danx-dashboard-mcp] POST ${url} answered 201 without the access request fields: ${JSON.stringify(json)}`);
package/dist/index.js CHANGED
@@ -61,6 +61,12 @@
61
61
  * otherwise this session's approved key (`session-key.ts`), else none: the
62
62
  * server boots SIGNED OUT, every tool but `plan_connect`
63
63
  * halts, and `plan_connect` requests access (`session-access.ts`)
64
+ * DANXBOT_TEAM OPTIONAL (DX-4446), non-secret: the team slug (or id) whose
65
+ * approvers see this session's access request in their danxbot
66
+ * site. Set it in the repo's `.mcp.json` env block or the shell.
67
+ * Unset: the request is answered by link only. Set but not a
68
+ * valid ref, or unknown to the dashboard: fails loud, never a
69
+ * silent fall back to the link flow.
64
70
  * DANX_REPO_NAME repo half of the qualified board id
65
71
  * DANXBOT_BOARD_NAME board-slug half of the qualified board id
66
72
  * (the dispatch injects `board.slug` here);
@@ -83,6 +89,7 @@ import { EVENT_TEXT_SUBCOMMAND, runEventTextCommand } from "./event-text.js";
83
89
  import { RESTART_NOTICE_SUBCOMMAND, runRestartNoticeCommand } from "./restart-notice.js";
84
90
  import { resolveBootCredential } from "./credential.js";
85
91
  import { SessionKeyUnusableError, deleteSessionKey } from "./session-key.js";
92
+ import { assertMcpTeamRef } from "./access-request.js";
86
93
  import { SessionAccess } from "./session-access.js";
87
94
  import { PermissionRequests } from "./permission-request.js";
88
95
  import { withForbiddenHint } from "./forbidden-hint.js";
@@ -119,6 +126,19 @@ function readEnvOptional(name) {
119
126
  const v = process.env[name];
120
127
  return typeof v === "string" && v !== "" ? v : undefined;
121
128
  }
129
+ /** DX-4446: optional `DANXBOT_TEAM`; set, it must be a valid team ref or boot fails loud (no fallback to the link flow). */
130
+ function readTeamRef() {
131
+ const team = readEnvOptional("DANXBOT_TEAM");
132
+ if (team === undefined)
133
+ return undefined;
134
+ try {
135
+ return assertMcpTeamRef(team);
136
+ }
137
+ catch (err) {
138
+ console.error(err.message);
139
+ process.exit(1);
140
+ }
141
+ }
122
142
  // The dispatch config + HTTP client are LATE-BOUND (assigned by `boot()` at
123
143
  // entrypoint, below). Tool registration happens at module import and the
124
144
  // callbacks close over these `let` bindings, reading them at CALL time — so the
@@ -168,6 +188,7 @@ function boot() {
168
188
  sessionId: session?.id,
169
189
  repo,
170
190
  clientVersion: readOwnVersion(),
191
+ team: readTeamRef(),
171
192
  setToken: (token) => dashboardClient.setToken(token),
172
193
  initial: resolveInitialCredential(dashboardUrl, session?.id),
173
194
  log: (line) => console.error(line),
@@ -8,6 +8,7 @@
8
8
  export const GET_ROUTE_BOARD_SCOPE = [
9
9
  ["/api/access-requests", false],
10
10
  ["/api/account/sessions", false],
11
+ ["/api/agents/:agentId/halt-history", false],
11
12
  ["/api/audit-events", false],
12
13
  ["/api/boards", false],
13
14
  ["/api/boards/:boardId", false],
@@ -18,6 +19,7 @@ export const GET_ROUTE_BOARD_SCOPE = [
18
19
  ["/api/dispatches/:dispatchId/usage-events", false],
19
20
  ["/api/dispatches/dead-letter-ids", false],
20
21
  ["/api/docs/:repo/changes", false],
22
+ ["/api/docs/:repo/progress/:pathId", false],
21
23
  ["/api/failure-categories", false],
22
24
  ["/api/failure-categories/ambiguous", false],
23
25
  ["/api/failure-categories/uncategorized", false],
@@ -46,5 +48,10 @@ export const GET_ROUTE_BOARD_SCOPE = [
46
48
  ["/api/team/members", false],
47
49
  ["/api/team/roles", false],
48
50
  ["/api/users", false],
51
+ ["/api/worker/catalog-mcp-servers", false],
52
+ ["/api/worker/dispatches/:dispatchId", true],
53
+ ["/api/worker/dispatches/:dispatchId/usage-predecessor", false],
54
+ ["/api/worker/dispatches/non-terminal", false],
55
+ ["/api/worker/dispatches/recent-signal-failures", false],
49
56
  ["/api/workers", false],
50
57
  ];
@@ -47,6 +47,7 @@ const DENIED_INSTRUCTION = "The user denied the request. Do not retry unless the
47
47
  const DASHBOARD_OUTDATED_ERROR = "this dashboard does not support request-and-approve yet (POST /api/access-requests answered 404): " +
48
48
  "it must be updated before this session can sign in";
49
49
  const RATE_LIMITED_ERROR = "too many access requests from this address; wait, then call plan_connect again";
50
+ const unknownTeamError = (team) => `the dashboard has no team "${team}" (DANXBOT_TEAM); fix DANXBOT_TEAM in this server's env (the repo's .mcp.json env block or the shell) and restart the session`;
50
51
  const NO_SESSION_ID_ERROR = "this server has no CLAUDE_CODE_SESSION_ID, so it cannot request a session key";
51
52
  const CLAIMED_LOST_ERROR = "the dashboard says this request was already claimed, but this process never received the key; " +
52
53
  "start a new request";
@@ -212,10 +213,13 @@ export class SessionAccess {
212
213
  repo: this.deps.repo,
213
214
  machine: this.deps.machine ?? hostname(),
214
215
  clientVersion: this.deps.clientVersion,
216
+ ...(this.deps.team === undefined ? {} : { team: this.deps.team }),
215
217
  });
216
218
  switch (created.kind) {
217
219
  case "dashboard_outdated":
218
220
  return notReady({ state: "dashboard_outdated", error: DASHBOARD_OUTDATED_ERROR }, true);
221
+ case "unknown_team":
222
+ return notReady({ state: "unknown_team", team: created.team, error: unknownTeamError(created.team) }, true);
219
223
  case "rate_limited":
220
224
  return notReady({ state: "rate_limited", retryAfterSeconds: created.retryAfterSeconds, error: RATE_LIMITED_ERROR }, true);
221
225
  case "refused":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thehammer/danx-dashboard-mcp",
3
- "version": "0.1.234",
3
+ "version": "0.1.237",
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",