@thehammer/danx-dashboard-mcp 0.1.225 → 0.1.227
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/index.js +18 -8
- package/dist/result-limit.js +25 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -87,6 +87,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
87
87
|
import { z } from "zod";
|
|
88
88
|
import { DashboardHttpClient } from "./http-client.js";
|
|
89
89
|
import { planConnect, danxbotApi, danxbotApiList, danxbotApiSpec, attachFile, DANXBOT_API_METHODS, } from "./handlers.js";
|
|
90
|
+
import { boundedResultText } from "./result-limit.js";
|
|
90
91
|
import { bodyValueSchema, queryValueSchema } from "./types.js";
|
|
91
92
|
import { termsAcceptanceCallHalt, termsHaltFor } from "./terms-halt.js";
|
|
92
93
|
import { danxbotApiExamplesText } from "./danxbot-api-examples.js";
|
|
@@ -283,18 +284,28 @@ function accessReply(ready, note) {
|
|
|
283
284
|
if (ready.halt !== undefined)
|
|
284
285
|
return haltResult(ready.halt);
|
|
285
286
|
const answer = note ? { ...ready.answer, note } : ready.answer;
|
|
286
|
-
return ready.isError ? haltResult(JSON.stringify(answer
|
|
287
|
+
return ready.isError ? haltResult(JSON.stringify(answer)) : jsonResult(answer);
|
|
287
288
|
}
|
|
288
289
|
/** DX-4390 — a tool's dashboard call behind the sign-in halt (and the 401 sign-out). */
|
|
289
290
|
async function guardedJson(run) {
|
|
290
291
|
const guarded = await access.guard(run);
|
|
291
292
|
return "halt" in guarded ? haltResult(guarded.halt) : jsonResult(guarded.result);
|
|
292
293
|
}
|
|
294
|
+
/** DX-4400 — `guardedJson` for the read tools whose answer can be a whole document: bounded. */
|
|
295
|
+
async function guardedBoundedJson(run) {
|
|
296
|
+
const guarded = await access.guard(run);
|
|
297
|
+
return "halt" in guarded ? haltResult(guarded.halt) : boundedJsonResult(guarded.result);
|
|
298
|
+
}
|
|
293
299
|
function jsonResult(value) {
|
|
294
300
|
return {
|
|
295
|
-
|
|
301
|
+
// DX-4400: compact — nothing parses the indentation and it costs every call 16% to 55% more characters.
|
|
302
|
+
content: [{ type: "text", text: JSON.stringify(value) }],
|
|
296
303
|
};
|
|
297
304
|
}
|
|
305
|
+
/** DX-4400 — a dashboard route's result, refused with the way to narrow it when over `MCP_MAX_RESULT_CHARS`. */
|
|
306
|
+
function boundedJsonResult(result) {
|
|
307
|
+
return { content: [{ type: "text", text: boundedResultText(result) }] };
|
|
308
|
+
}
|
|
298
309
|
/**
|
|
299
310
|
* DX-3238 — every tool on this server MUST reject an undeclared/mistyped
|
|
300
311
|
* argument instead of silently stripping it. `server.tool(name, description,
|
|
@@ -407,14 +418,14 @@ async (args) => {
|
|
|
407
418
|
const halt = termsHaltFor(result);
|
|
408
419
|
if (halt)
|
|
409
420
|
return haltResult(halt);
|
|
410
|
-
return
|
|
421
|
+
return boundedJsonResult(await listenerReminder.apply(result));
|
|
411
422
|
});
|
|
412
423
|
// ---------------- danxbot_api_list (DX-3465 A5, PLN-11 R-25) ----------------
|
|
413
424
|
strictTool("danxbot_api_list",
|
|
414
425
|
// DX-3528 — the list is filtered by the same permission check every request takes.
|
|
415
426
|
"Lists the dashboard API routes your credential may call: `[{method, path, summary, permission}]`, each " +
|
|
416
427
|
"`path` a route template (`/api/issues/:id/transition`), the form `danxbot_api_spec` takes. A route not " +
|
|
417
|
-
"listed is not callable by you.", {}, async () =>
|
|
428
|
+
"listed is not callable by you.", {}, async () => guardedBoundedJson(() => danxbotApiList(client)));
|
|
418
429
|
// ---------------- danxbot_api_spec (DX-3465, PLN-11 R-25) ----------------
|
|
419
430
|
strictTool("danxbot_api_spec",
|
|
420
431
|
// DX-3527 — the contract is generated from the route table.
|
|
@@ -423,7 +434,7 @@ strictTool("danxbot_api_spec",
|
|
|
423
434
|
'(e.g. `"POST /api/issues/:id/transition"`), never a real path with ids. An endpoint the dashboard does ' +
|
|
424
435
|
"not serve, or your credential may not call, is refused and named.", {
|
|
425
436
|
endpoints: z.array(z.string().min(1)).min(1),
|
|
426
|
-
}, async (args) =>
|
|
437
|
+
}, async (args) => guardedBoundedJson(() => danxbotApiSpec(client, args)));
|
|
427
438
|
// ---------------- attach_file (DX-3465 A4, checklist item 36140) ----------------
|
|
428
439
|
//
|
|
429
440
|
// The "25 MB" ceiling quoted below tracks the backend route's
|
|
@@ -436,9 +447,8 @@ strictTool("danxbot_api_spec",
|
|
|
436
447
|
// might hold locally, e.g. the Slack recovery agent's CSV path).
|
|
437
448
|
strictTool("attach_file", "Attach a LOCAL file to a card. This server reads and uploads the bytes, so they never pass through your " +
|
|
438
449
|
"context. The attachment mirrors to the card's linked Trello card and Slack thread. 25 MB limit (413). " +
|
|
439
|
-
"Returns `{issue, attachment_id,
|
|
440
|
-
"
|
|
441
|
-
"inline. Use it when a screenshot or file judges something faster than prose, or instead of pasting a " +
|
|
450
|
+
"Returns `{issue, attachment_id, url, thumbnail_url, is_primary}`: `url` is permanent, embed it in any " +
|
|
451
|
+
"markdown field as `` and it renders inline. Use it when a screenshot or file judges something faster than prose, or instead of pasting a " +
|
|
442
452
|
"large CSV or log into a card field.", {
|
|
443
453
|
card_id: z.string().min(1).describe("The card id to attach the file to."),
|
|
444
454
|
path: z
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DX-4400 — the most characters one `danxbot_api`, `danxbot_api_spec` or `danxbot_api_list` result may carry
|
|
3
|
+
* back to the agent. Every character is context the agent pays for on every later call, and Claude Code itself
|
|
4
|
+
* saves a larger result to a file with no hint how to narrow, after the cost is paid. A deliberate, narrow
|
|
5
|
+
* exception to PLAN-11 rule R-25 (verbatim forwarding): `plan_connect` and the lifecycle tools never pass here.
|
|
6
|
+
*/
|
|
7
|
+
export const MCP_MAX_RESULT_CHARS = 100_000;
|
|
8
|
+
/** A runtime result text, not a tool description: the DX-4318 text limit does not apply to it. */
|
|
9
|
+
const HOW_TO_NARROW = "Ask for less: `limit` and `offset` page a collection, `fields` names only the columns or relations you need, " +
|
|
10
|
+
"`filter` and `since` (epoch milliseconds) select rows. Then repeat the call.";
|
|
11
|
+
/**
|
|
12
|
+
* The text the agent receives for `result`: its compact JSON when that fits `MCP_MAX_RESULT_CHARS`, otherwise
|
|
13
|
+
* `{ok, status, body_omitted:{chars, limit, how_to_narrow}}`. Never truncates, never forwards a part. `ok`
|
|
14
|
+
* and `status` stay so a write that already committed is still reported as committed.
|
|
15
|
+
*/
|
|
16
|
+
export function boundedResultText(result) {
|
|
17
|
+
const text = JSON.stringify(result);
|
|
18
|
+
if (text.length <= MCP_MAX_RESULT_CHARS)
|
|
19
|
+
return text;
|
|
20
|
+
return JSON.stringify({
|
|
21
|
+
ok: result.ok,
|
|
22
|
+
status: result.status,
|
|
23
|
+
body_omitted: { chars: text.length, limit: MCP_MAX_RESULT_CHARS, how_to_narrow: HOW_TO_NARROW },
|
|
24
|
+
});
|
|
25
|
+
}
|
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.227",
|
|
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",
|