@thehammer/danx-dashboard-mcp 0.1.322 → 0.1.323
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/dist/http-client.js
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
|
-
import { boardStampOf } from "./route-board-scope.js";
|
|
1
|
+
import { boardStampOf, takesBoardInBody } from "./route-board-scope.js";
|
|
2
2
|
import { assertVersionFloorSatisfied, MCP_MIN_VERSION_HEADER } from "./version-floor.js";
|
|
3
3
|
import { PLUGIN_BACKGROUND_HEADER } from "./session-options.js";
|
|
4
|
+
/** `<basePath><path>` with the path's own query string cut off and its leading slash normalised (an empty or "/" path adds nothing). */
|
|
5
|
+
function routePathOf(path, basePath = "/api/issues") {
|
|
6
|
+
const [rawPath] = path.split("?", 2);
|
|
7
|
+
if (rawPath === "" || rawPath === "/")
|
|
8
|
+
return basePath;
|
|
9
|
+
return `${basePath}${rawPath.startsWith("/") ? rawPath : `/${rawPath}`}`;
|
|
10
|
+
}
|
|
4
11
|
export class DashboardHttpClient {
|
|
5
12
|
config;
|
|
6
13
|
fetchImpl;
|
|
@@ -71,7 +78,7 @@ export class DashboardHttpClient {
|
|
|
71
78
|
}
|
|
72
79
|
else if (args.body !== undefined) {
|
|
73
80
|
headers["Content-Type"] = "application/json";
|
|
74
|
-
payload = JSON.stringify(args
|
|
81
|
+
payload = JSON.stringify(this.withBodyBoard(args));
|
|
75
82
|
}
|
|
76
83
|
let res;
|
|
77
84
|
try {
|
|
@@ -116,31 +123,44 @@ export class DashboardHttpClient {
|
|
|
116
123
|
}
|
|
117
124
|
return { ok: false, status: res.status, body: parsed };
|
|
118
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* DX-5337 - the body of a request whose route reads the board from `body.board` (issue create/import): the board the caller wrote is kept;
|
|
128
|
+
* with none, the call's `board` argument or the dispatch's board is filled in. DX-3465 deleted the per-operation `issue_create` tool that
|
|
129
|
+
* put the dispatch's board in the body, and the create route does not read `?board=`, so a dispatched agent's create arrived with no board
|
|
130
|
+
* and was refused 400. A board-less server with no `board` argument fills nothing: the dashboard's own 400 names the missing board.
|
|
131
|
+
*/
|
|
132
|
+
withBodyBoard(args) {
|
|
133
|
+
const body = args.body;
|
|
134
|
+
if (!takesBoardInBody(args.method, routePathOf(args.path, args.basePath)))
|
|
135
|
+
return body;
|
|
136
|
+
if (typeof body !== "object" || body === null || Array.isArray(body))
|
|
137
|
+
return body;
|
|
138
|
+
if (body.board !== undefined)
|
|
139
|
+
return body;
|
|
140
|
+
const board = args.board ?? this.config.board;
|
|
141
|
+
return board === undefined ? body : { ...body, board };
|
|
142
|
+
}
|
|
119
143
|
buildUrl(method, path, extraQuery, boardOverride, basePath = "/api/issues") {
|
|
120
144
|
const base = this.config.baseUrl.replace(/\/+$/, "");
|
|
121
|
-
const [
|
|
122
|
-
let cleanPath;
|
|
123
|
-
if (rawPath === "" || rawPath === "/") {
|
|
124
|
-
cleanPath = "";
|
|
125
|
-
}
|
|
126
|
-
else {
|
|
127
|
-
cleanPath = rawPath.startsWith("/") ? rawPath : `/${rawPath}`;
|
|
128
|
-
}
|
|
145
|
+
const [, existingQs] = path.split("?", 2);
|
|
129
146
|
const params = new URLSearchParams(existingQs ?? "");
|
|
130
147
|
// DX-3750 / DX-4303 — only a route the dashboard takes `?board=` on gets the stamp; see `route-board-scope.ts`.
|
|
131
148
|
// DX-4542 — a route whose board is OPTIONAL (no board = the whole team) gets only the board the caller named: stamping the dispatch's
|
|
132
149
|
// board there would make the team-wide list impossible to ask for.
|
|
133
|
-
const
|
|
134
|
-
|
|
150
|
+
const routePath = routePathOf(path, basePath);
|
|
151
|
+
const stamp = boardStampOf(method, routePath);
|
|
152
|
+
// DX-5337: issue create/import take the board in the body only (`withBodyBoard`), so no `?board=` rides them: one path, never two.
|
|
153
|
+
const boardInQuery = !takesBoardInBody(method, routePath);
|
|
154
|
+
if (boardInQuery && stamp === "stamp") {
|
|
135
155
|
const board = boardOverride ?? this.config.board;
|
|
136
156
|
// DX-4592: a board-less server (the danxbot plugin's, no DANX_REPO_NAME/DANXBOT_BOARD_NAME) stamps nothing when the call names no
|
|
137
|
-
// board. Whether the route NEEDS one is the dashboard's to say: a route that does (
|
|
138
|
-
//
|
|
157
|
+
// board. Whether the route NEEDS one is the dashboard's to say: a route that does (quality-gate and settings routes, the dispatch
|
|
158
|
+
// verbs) answers 400 naming `board`, while a card-id, plan or plan-session route takes its board from the card
|
|
139
159
|
// or the plan. Refusing here (DX-4555) wrongly stopped every plan route, `plan_connect` first.
|
|
140
160
|
if (board !== undefined)
|
|
141
161
|
params.set("board", board);
|
|
142
162
|
}
|
|
143
|
-
else if (stamp === "named" && boardOverride !== undefined) {
|
|
163
|
+
else if (boardInQuery && stamp === "named" && boardOverride !== undefined) {
|
|
144
164
|
params.set("board", boardOverride);
|
|
145
165
|
}
|
|
146
166
|
if (extraQuery) {
|
|
@@ -163,6 +183,6 @@ export class DashboardHttpClient {
|
|
|
163
183
|
}
|
|
164
184
|
}
|
|
165
185
|
const query = params.toString();
|
|
166
|
-
return `${base}${
|
|
186
|
+
return `${base}${routePath}${query === "" ? "" : `?${query}`}`;
|
|
167
187
|
}
|
|
168
188
|
}
|
|
@@ -61,3 +61,8 @@ export const GET_ROUTE_BOARD_SCOPE = [
|
|
|
61
61
|
["/api/worker/dispatches/recent-signal-failures", "refuse"],
|
|
62
62
|
["/api/workers", "refuse"],
|
|
63
63
|
];
|
|
64
|
+
/** DX-5337 - the routes that read the card's board from `body.board` only: the client fills a dispatch's board in there and sends no `?board=`. */
|
|
65
|
+
export const BODY_BOARD_ROUTES = [
|
|
66
|
+
["POST", "/api/issues"],
|
|
67
|
+
["POST", "/api/issues/import"],
|
|
68
|
+
];
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* Only GET routes are listed - the server refuses an unknown query key on a read alone - so every other method takes the key. A
|
|
12
12
|
* GET path no template matches takes it too: the server answers it with a 404 whatever the query.
|
|
13
13
|
*/
|
|
14
|
-
import { GET_ROUTE_BOARD_SCOPE } from "./route-board-scope.generated.js";
|
|
14
|
+
import { BODY_BOARD_ROUTES, GET_ROUTE_BOARD_SCOPE } from "./route-board-scope.generated.js";
|
|
15
15
|
const RANK = { literal: 2, param: 1, rest: 0 };
|
|
16
16
|
function compile(template, stamp) {
|
|
17
17
|
const segments = template
|
|
@@ -55,3 +55,17 @@ export function boardStampOf(method, routePath) {
|
|
|
55
55
|
const parts = routePath.slice(1).split("/");
|
|
56
56
|
return ROUTES.find((route) => matches(route, parts))?.stamp ?? "stamp";
|
|
57
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* DX-5337 - the routes that read the card's board ONLY from `body.board` (issue create and import), from the generated
|
|
60
|
+
* `BODY_BOARD_ROUTES` (the server's `RouteDef.boardInBody`). A dispatch's default board reaches them through the body and through
|
|
61
|
+
* nothing else: the client fills `body.board` when the caller named none and sends no `?board=` there.
|
|
62
|
+
*/
|
|
63
|
+
const BODY_BOARD = BODY_BOARD_ROUTES.map(([method, template]) => ({
|
|
64
|
+
method,
|
|
65
|
+
route: compile(template, "stamp"),
|
|
66
|
+
}));
|
|
67
|
+
/** Whether `<method> <routePath>` (no query string) takes its board in `body.board`. */
|
|
68
|
+
export function takesBoardInBody(method, routePath) {
|
|
69
|
+
const parts = routePath.replace(/\/+$/, "").slice(1).split("/");
|
|
70
|
+
return BODY_BOARD.some((entry) => entry.method === method && matches(entry.route, parts));
|
|
71
|
+
}
|
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.323",
|
|
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, request_permission, attach_file, and danxbot_slack_reply) (DX-3465, PLN-11 R-25).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|