@thehammer/danx-dashboard-mcp 0.1.166 → 0.1.170
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 +4 -3
- package/dist/http-client.js +13 -10
- package/dist/index.js +12 -19
- package/dist/types.js +16 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# @thehammer/danx-dashboard-mcp
|
|
2
2
|
|
|
3
|
-
Stdio MCP server
|
|
3
|
+
Stdio MCP server fronting danxbot's dashboard HTTP API. Since DX-3465 it exposes three generic tools (`danxbot_api`, `danxbot_api_list`, `danxbot_api_spec` — see "Tool surface" below) that reach any route the caller's credential may reach, plus a handful of dedicated tools for agent-specific operations (`plan_connect`, `danxbot_complete`, Slack/template-app callbacks, …) — there is no per-operation tool any more.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
`danxbot_api` is Zod-validated at the MCP boundary, fetches under the hood, and passes the server response back to the agent verbatim. Refusal envelopes (`{error, ...extra}` with `failed_gate`, `non_terminal_phases`, `offending_keys`, etc.) come through as `{ok: false, status, body}` so the agent can pick the right next action without guessing. 5xx and network failures throw.
|
|
6
6
|
|
|
7
7
|
## Env vars (required, fail-loud at boot)
|
|
8
8
|
|
|
@@ -24,7 +24,8 @@ All exposed as `mcp__danx-dashboard__<name>` once wired through the workspace `m
|
|
|
24
24
|
| `danxbot_api` | `{method, path, query?, body?}` — calls any dashboard route the caller's credential may reach (card/plan CRUD, comments, dependencies, problems/solutions, quality gates, retro, triage, transitions, …). Returns `{ok, status, body}`; `body` carries the route's own response, or its structured error verbatim when `ok:false`. |
|
|
25
25
|
| `danxbot_api_list` | No args. Lists the routes this caller's credential may reach, one line each (`{method, path, summary, permission}`) — call this first when you don't know the route for what you need to do. |
|
|
26
26
|
| `danxbot_api_spec` | `{endpoints}` (comma-joined `METHOD /path` list, required, min 1). Returns the exact OpenAPI contract for those routes — call this when you know the route but not its exact request/response shape. |
|
|
27
|
-
|
|
27
|
+
|
|
28
|
+
There is no dedicated `resource_fields` tool (removed DX-3465 — it was a thin wrapper `danxbot_api` already reaches with no agent-specific behavior earning it its own tool). What an opt-in field TREE may name for one resource (`{resource, description, always, hashes, fields, relations}`) is `danxbot_api({method:"GET", path:"/api/resources/<resource>/fields"})` — install-global, no `board`; `resource: "issue"` is the root, and a relation's own `resource` in the response is what to call this again with, one level deeper.
|
|
28
29
|
|
|
29
30
|
A dedicated tool survives only for something about the agent itself, never a card/plan operation: `plan_connect` (binds the session to a plan; the event-bridge hook keys on it) and the per-dispatch worker callbacks (`danxbot_complete`, `danxbot_slack_reply`, `danxbot_slack_post_update`, `load_template_app`/`save_template_app`, `quality_gate_complete`, `link_thread_to_issue`, `attach_file`, the `slack_recovery_*` trio, `danxbot_set_evaluator_summary`) — see `src/lifecycle.ts`. The live list is always `tool-defs.json` in this package, regenerated by `npm run gen-tool-defs` — read it rather than trusting a list copied into prose.
|
|
30
31
|
|
package/dist/http-client.js
CHANGED
|
@@ -102,16 +102,19 @@ export class DashboardHttpClient {
|
|
|
102
102
|
for (const [k, v] of Object.entries(extraQuery)) {
|
|
103
103
|
if (v === undefined || v === null)
|
|
104
104
|
continue;
|
|
105
|
-
// DX-3465 (
|
|
106
|
-
//
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
//
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
|
|
114
|
-
|
|
105
|
+
// DX-3465 — a field TREE (`fields`, a plain object) is the only
|
|
106
|
+
// query param the server parses as JSON
|
|
107
|
+
// (`src/resources/field-tree.ts`); every LIST-valued param it has
|
|
108
|
+
// (`ids` on `GET /api/issues/batch`, `status` on the boards list,
|
|
109
|
+
// `endpoints` on `GET /api/openapi`) splits on commas instead, never
|
|
110
|
+
// JSON. Arrays are `typeof "object"` too, so the array check must
|
|
111
|
+
// come BEFORE the plain-object branch. A plain scalar stringifies
|
|
112
|
+
// as before.
|
|
113
|
+
params.set(k, Array.isArray(v)
|
|
114
|
+
? v.map((item) => String(item)).join(",")
|
|
115
|
+
: typeof v === "object"
|
|
116
|
+
? JSON.stringify(v)
|
|
117
|
+
: String(v));
|
|
115
118
|
}
|
|
116
119
|
}
|
|
117
120
|
return `${base}${basePath}${cleanPath}?${params.toString()}`;
|
package/dist/index.js
CHANGED
|
@@ -84,6 +84,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
84
84
|
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
|
+
import { queryValueSchema } from "./types.js";
|
|
87
88
|
import { registerLifecycleTools, applyLifecycleVisibility, readLifecycleUrlsFromEnv, } from "./lifecycle.js";
|
|
88
89
|
function readEnvOrDie(name) {
|
|
89
90
|
const v = process.env[name];
|
|
@@ -337,24 +338,16 @@ strictTool("danxbot_api", "Call any dashboard API route your credential may reac
|
|
|
337
338
|
.min(1)
|
|
338
339
|
.startsWith("/api/")
|
|
339
340
|
.describe('Full path starting with "/api/" — never a route template.'),
|
|
340
|
-
// DX-3465
|
|
341
|
-
//
|
|
342
|
-
// `
|
|
343
|
-
//
|
|
344
|
-
//
|
|
345
|
-
//
|
|
346
|
-
//
|
|
347
|
-
//
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
z.string(),
|
|
351
|
-
z.number(),
|
|
352
|
-
z.boolean(),
|
|
353
|
-
z.null(),
|
|
354
|
-
z.record(z.unknown()),
|
|
355
|
-
z.array(z.unknown()),
|
|
356
|
-
]))
|
|
357
|
-
.optional(),
|
|
341
|
+
// DX-3465 — a query value may be a scalar, a field TREE (a plain
|
|
342
|
+
// object, e.g. `{"description":true,"comments":{"limit":20}}` — see
|
|
343
|
+
// SERVER_INSTRUCTIONS below and the OpenAPI spec's `fields` schema), or
|
|
344
|
+
// an array of scalars (e.g. `ids` on the batch-get route).
|
|
345
|
+
// `DashboardHttpClient#buildUrl` JSON-encodes a plain object but
|
|
346
|
+
// comma-joins an array, matching the two shapes the server actually
|
|
347
|
+
// parses (a JSON field tree vs. a comma-separated list param) — so array
|
|
348
|
+
// elements are restricted to scalars, the only thing a comma-join can
|
|
349
|
+
// represent losslessly.
|
|
350
|
+
query: queryValueSchema.optional(),
|
|
358
351
|
body: z.unknown().optional(),
|
|
359
352
|
...boardField,
|
|
360
353
|
}, async (args) => jsonResult(await danxbotApi(client, args)));
|
|
@@ -420,7 +413,7 @@ strictTool("attach_file", "Attach a LOCAL file to a card: `card_id` + `path` (an
|
|
|
420
413
|
// no existing plan for an id to name yet.
|
|
421
414
|
strictTool("plan_connect",
|
|
422
415
|
// DX-2735: trimmed with the problem tools to stay inside the work-profile budget.
|
|
423
|
-
"Connect THIS session to a plan. ONE CALL IS ENOUGH TO START: the reply carries `{session, movedFrom, browserInstruction, briefing, listenerHealth}` — `browserInstruction` is a server-built action to take NOW: it names the plan's URL and tells you to open it (in-app browser if you have one, else default), keep that tab open for the whole session without navigating it away, and use a different tab for your own browsing — the operator's tab for following and talking to you. Returned on every connect, including a re-connect, so it doubles as the post-context-loss reminder. `briefing` is every goal/rule/caveat (ref+body), every architecture section (id+title), the plan's own ref/name/status, a first page of open cards (id/type/status/title/openProblemCount/assignedAgent) with a `morePagesHint` when more exist, and the closed-card count — usually replacing the plan read + card batch-read a fresh session used to need. A session is on AT MOST ONE plan: connecting elsewhere MOVES it, and `movedFrom: {id, name} | null` says which plan it left (null = no plan, or already this one). Binds only your OWN session, resolved from the session id this server forwards; afterwards
|
|
416
|
+
"Connect THIS session to a plan. ONE CALL IS ENOUGH TO START: the reply carries `{session, movedFrom, browserInstruction, briefing, listenerHealth}` — `browserInstruction` is a server-built action to take NOW: it names the plan's URL and tells you to open it (in-app browser if you have one, else default), keep that tab open for the whole session without navigating it away, and use a different tab for your own browsing — the operator's tab for following and talking to you. Returned on every connect, including a re-connect, so it doubles as the post-context-loss reminder. `briefing` is every goal/rule/caveat (ref+body), every architecture section (id+title), the plan's own ref/name/status, a first page of open cards (id/type/status/title/openProblemCount/assignedAgent) with a `morePagesHint` when more exist, and the closed-card count — usually replacing the plan read + card batch-read a fresh session used to need. A session is on AT MOST ONE plan: connecting elsewhere MOVES it, and `movedFrom: {id, name} | null` says which plan it left (null = no plan, or already this one). Binds only your OWN session, resolved from the session id this server forwards; afterwards a `danxbot_api` call to a `/api/plans/mine/...` route (there is no dedicated plan-write tool) acts on this plan and takes no plan id. `listenerHealth` reports the event bridge's state — `null` (no session), or `{attached, state: \"unattached\"|\"credential_mismatch\"|\"healthy\", nextStep}` naming a concrete fix per unhealthy state (same shape the plan list/get routes report). Once healthy, every comment, answer, problem added and block/unblock on this plan's cards reaches the session unpolled, relayed by the plugin's event bridge as a line like `[DX-8 \"Title\" repo:board] newms87 answered \"<statement>\": chose \"Pause E2E\"`. Pass `title` (call `get_session({session_id:\"self\"})` first and forward its `title` verbatim) so the dashboard shows the same name Claude does — this server cannot read it itself. The response's `reminders[]` (when present) carries the mantra — read and follow it.", {
|
|
424
417
|
plan_id: z.number().int().positive().describe("The plan id, from the plans list (`danxbot_api` `GET /api/plans`)."),
|
|
425
418
|
title: z
|
|
426
419
|
.string()
|
package/dist/types.js
CHANGED
|
@@ -1 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* The zod schema for `danxbot_api`'s `query` argument, matching the
|
|
4
|
+
* `QueryValue` type above — a scalar, a field-tree object, or an array
|
|
5
|
+
* whose elements are themselves scalars (never objects; a comma-join
|
|
6
|
+
* cannot represent one losslessly). Exported so it can be exercised
|
|
7
|
+
* directly in tests without going through the MCP tool-call machinery.
|
|
8
|
+
*/
|
|
9
|
+
export const queryValueSchema = z.record(z.union([
|
|
10
|
+
z.string(),
|
|
11
|
+
z.number(),
|
|
12
|
+
z.boolean(),
|
|
13
|
+
z.null(),
|
|
14
|
+
z.record(z.unknown()),
|
|
15
|
+
z.array(z.union([z.string(), z.number(), z.boolean()])),
|
|
16
|
+
]));
|
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.170",
|
|
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",
|