@50arms/platform-mcp 0.0.0-stage → 0.1.436

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.
Files changed (75) hide show
  1. package/README.md +114 -2
  2. package/dist/access-request.js +101 -0
  3. package/dist/agent-gate.js +69 -0
  4. package/dist/board-id.js +27 -0
  5. package/dist/bounded-retry.js +25 -0
  6. package/dist/checkout-docs-root.js +54 -0
  7. package/dist/clear-control.js +77 -0
  8. package/dist/compaction-control.js +142 -0
  9. package/dist/credential.js +104 -0
  10. package/dist/danxbot-api-examples.js +97 -0
  11. package/dist/dashboard-identity.js +14 -0
  12. package/dist/design-screenshot.js +138 -0
  13. package/dist/digest-hold.js +67 -0
  14. package/dist/docs-tools.js +140 -0
  15. package/dist/durable-write.js +56 -0
  16. package/dist/entrypoint.js +35 -0
  17. package/dist/forbidden-hint.js +23 -0
  18. package/dist/guarded-calls.js +21 -0
  19. package/dist/handlers.js +385 -0
  20. package/dist/heartbeat-reporter.js +156 -0
  21. package/dist/http-client.js +190 -0
  22. package/dist/index.js +1128 -0
  23. package/dist/json-body.js +12 -0
  24. package/dist/key-revoked-halt.js +30 -0
  25. package/dist/lifecycle.js +113 -0
  26. package/dist/listen.js +999 -0
  27. package/dist/listener-reminder.js +119 -0
  28. package/dist/machine-daemon/autostart.js +160 -0
  29. package/dist/machine-daemon/beat.js +77 -0
  30. package/dist/machine-daemon/cli.js +42 -0
  31. package/dist/machine-daemon/install.js +168 -0
  32. package/dist/machine-daemon/state.js +64 -0
  33. package/dist/machine-daemon/version.js +13 -0
  34. package/dist/machine-daemon.js +553 -0
  35. package/dist/machine-toolkit.js +460 -0
  36. package/dist/mini-app-build.js +159 -0
  37. package/dist/mini-app-checkout.js +215 -0
  38. package/dist/mini-app-output.js +111 -0
  39. package/dist/mini-app-publish.js +154 -0
  40. package/dist/mini-app-restore.js +175 -0
  41. package/dist/one-line.js +25 -0
  42. package/dist/pacing-relay.js +16 -0
  43. package/dist/permission-request.js +134 -0
  44. package/dist/permission-watch.js +145 -0
  45. package/dist/plan-events-relay.js +572 -0
  46. package/dist/plan-events-tool.js +9 -0
  47. package/dist/plugin-publish.js +38 -0
  48. package/dist/plugin-screenshot.js +81 -0
  49. package/dist/request-bound.js +21 -0
  50. package/dist/restart-notice.js +271 -0
  51. package/dist/result-limit.js +31 -0
  52. package/dist/role-instruction-file.js +36 -0
  53. package/dist/route-board-scope.generated.js +93 -0
  54. package/dist/route-board-scope.js +71 -0
  55. package/dist/session-access.js +322 -0
  56. package/dist/session-connection.js +264 -0
  57. package/dist/session-control-file.js +58 -0
  58. package/dist/session-key.js +125 -0
  59. package/dist/session-options.js +60 -0
  60. package/dist/session-stats.js +235 -0
  61. package/dist/session-stream.js +345 -0
  62. package/dist/session-title.js +129 -0
  63. package/dist/shutdown.js +38 -0
  64. package/dist/stop-fixes.js +50 -0
  65. package/dist/subagents-live.js +162 -0
  66. package/dist/terms-halt.js +39 -0
  67. package/dist/text-limit.js +24 -0
  68. package/dist/tool-access.js +114 -0
  69. package/dist/tool-defs.js +58 -0
  70. package/dist/tool-surface.js +59 -0
  71. package/dist/transcript-lineage.js +105 -0
  72. package/dist/types.js +45 -0
  73. package/dist/urgent-records.js +47 -0
  74. package/dist/version-floor.js +146 -0
  75. package/package.json +51 -4
@@ -0,0 +1,190 @@
1
+ import { boardStampOf, takesBoardInBody } from "./route-board-scope.js";
2
+ import { assertVersionFloorSatisfied, MCP_MIN_VERSION_HEADER, MCP_VERSION_HEADER, readOwnVersion } from "./version-floor.js";
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
+ }
11
+ export class DashboardHttpClient {
12
+ config;
13
+ fetchImpl;
14
+ // DX-3597 — true once this instance has made ONE request, regardless of
15
+ // outcome. Drives the "first call OR any 4xx" version-floor check below.
16
+ hasCheckedVersionFloor = false;
17
+ constructor(config, fetchImpl = fetch) {
18
+ this.config = config;
19
+ this.fetchImpl = fetchImpl;
20
+ }
21
+ /**
22
+ * DX-4390 — the bearer this client stamps. A session signs in (or out) while the server runs, so
23
+ * the key is not fixed at construction; "" means signed out.
24
+ */
25
+ setToken(token) {
26
+ this.config.token = token;
27
+ }
28
+ /**
29
+ * DX-5076 - the ONE entry for a request this process makes on its own (the listener's health read, the key-permission read, the permission claim
30
+ * poll): it is never the agent acting, so it carries the background mark and ends no agent pause. A call the agent makes (`50arms_api`, a
31
+ * permission-request create) goes through `request` instead. No call site sets `background` by hand.
32
+ */
33
+ requestOwn(args) {
34
+ return this.request({ ...args, background: true });
35
+ }
36
+ async request(args) {
37
+ if (this.config.token === "") {
38
+ // DX-4390 — a caller reaches here only past the sign-in halt; a bearer-less call is a bug.
39
+ throw new Error("[platform-mcp] request attempted while signed out");
40
+ }
41
+ const url = this.buildUrl(args.method, args.path, args.query, args.board, args.basePath);
42
+ const headers = {
43
+ Authorization: `Bearer ${this.config.token}`,
44
+ Accept: "application/json",
45
+ };
46
+ // DX-1398 Hop 3 (outbound) — stamp the cross-process trace at the SAME
47
+ // single choke point that stamps Authorization, so every call (GET + every
48
+ // write) chains under the originating launch. Omitted when the dispatch
49
+ // carries no trace context (the dashboard then mints a fresh root).
50
+ if (this.config.traceparent) {
51
+ headers["traceparent"] = this.config.traceparent;
52
+ }
53
+ // DX-2683 — the working session's identity, stamped at the SAME single
54
+ // choke point as Authorization above, and for the same reason: it must
55
+ // ride EVERY call, not the plan calls. That is what lets the dashboard
56
+ // learn a session exists — and keep its last-active time current — purely
57
+ // from the card work the agent was already doing, with no cooperation
58
+ // from the agent and no per-tool parameter it could omit or falsify.
59
+ // Absent outside a Claude Code session; then nothing is stamped.
60
+ //
61
+ // NO TITLE HEADER (DX-2816, revised). This server has no way to read
62
+ // Claude's own session title — see `readSessionConfig` in `index.ts`.
63
+ // The title travels instead as `plan_connect`'s own `title` argument,
64
+ // straight in that call's body, because the AGENT (not this process) is
65
+ // the one thing able to read it.
66
+ if (this.config.session) {
67
+ headers["x-danx-session-id"] = this.config.session.id;
68
+ }
69
+ // DX-5897 - this server's own version rides every call: a route with a surface floor of its own (the agent-profile fetch) refuses a stale session by it.
70
+ headers[MCP_VERSION_HEADER] = readOwnVersion();
71
+ if (args.background === true) {
72
+ headers[PLUGIN_BACKGROUND_HEADER] = "1";
73
+ }
74
+ let payload;
75
+ if (args.rawBody !== undefined) {
76
+ if (args.body !== undefined)
77
+ throw new Error("[platform-mcp] a request sends `body` or `rawBody`, never both");
78
+ headers["Content-Type"] = args.rawBody.contentType;
79
+ payload = new Blob([args.rawBody.bytes]);
80
+ }
81
+ else if (args.body !== undefined) {
82
+ headers["Content-Type"] = "application/json";
83
+ payload = JSON.stringify(this.withBodyBoard(args));
84
+ }
85
+ let res;
86
+ try {
87
+ res = await this.fetchImpl(url, {
88
+ method: args.method,
89
+ headers,
90
+ body: payload,
91
+ });
92
+ }
93
+ catch (err) {
94
+ throw new Error(`[platform-mcp] network failure on ${args.method} ${url}: ${err instanceof Error ? err.message : String(err)}`);
95
+ }
96
+ // DX-3597 — proactive on the first call this instance ever makes
97
+ // (regardless of outcome: a stale client can otherwise look fine for a
98
+ // while on routes whose shape didn't change), and on every subsequent
99
+ // 4xx (a response that might otherwise read as an ordinary caller
100
+ // error when it's really "you're running an old MCP"). Throws
101
+ // McpOutdatedError instead of returning below when this package is
102
+ // behind the floor — see version-floor.ts's own doc.
103
+ const isFirstCall = !this.hasCheckedVersionFloor;
104
+ this.hasCheckedVersionFloor = true;
105
+ if (isFirstCall || (res.status >= 400 && res.status < 500)) {
106
+ assertVersionFloorSatisfied(res.headers.get(MCP_MIN_VERSION_HEADER));
107
+ }
108
+ const text = await res.text();
109
+ let parsed = null;
110
+ if (text !== "") {
111
+ try {
112
+ parsed = JSON.parse(text);
113
+ }
114
+ catch {
115
+ // Non-JSON response from a v2 route is a server-side fault —
116
+ // throw rather than fabricate an envelope.
117
+ throw new Error(`[platform-mcp] non-JSON response (status ${res.status}) from ${args.method} ${url}: ${text.slice(0, 200)}`);
118
+ }
119
+ }
120
+ if (res.status >= 500) {
121
+ throw new Error(`[platform-mcp] server error ${res.status} on ${args.method} ${url}: ${text.slice(0, 500)}`);
122
+ }
123
+ if (res.status >= 200 && res.status < 300) {
124
+ return { ok: true, status: res.status, body: parsed };
125
+ }
126
+ return { ok: false, status: res.status, body: parsed };
127
+ }
128
+ /**
129
+ * 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;
130
+ * 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
131
+ * 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
132
+ * and was refused 400. A board-less server with no `board` argument fills nothing: the dashboard's own 400 names the missing board.
133
+ */
134
+ withBodyBoard(args) {
135
+ const body = args.body;
136
+ if (!takesBoardInBody(args.method, routePathOf(args.path, args.basePath)))
137
+ return body;
138
+ if (typeof body !== "object" || body === null || Array.isArray(body))
139
+ return body;
140
+ if (body.board !== undefined)
141
+ return body;
142
+ const board = args.board ?? this.config.board;
143
+ return board === undefined ? body : { ...body, board };
144
+ }
145
+ buildUrl(method, path, extraQuery, boardOverride, basePath = "/api/issues") {
146
+ const base = this.config.baseUrl.replace(/\/+$/, "");
147
+ const [, existingQs] = path.split("?", 2);
148
+ const params = new URLSearchParams(existingQs ?? "");
149
+ // DX-3750 / DX-4303 — only a route the dashboard takes `?board=` on gets the stamp; see `route-board-scope.ts`.
150
+ // DX-4542 — a route whose board is OPTIONAL (no board = the whole team) gets only the board the caller named: stamping the dispatch's
151
+ // board there would make the team-wide list impossible to ask for.
152
+ const routePath = routePathOf(path, basePath);
153
+ const stamp = boardStampOf(method, routePath);
154
+ // DX-5337: issue create/import take the board in the body only (`withBodyBoard`), so no `?board=` rides them: one path, never two.
155
+ const boardInQuery = !takesBoardInBody(method, routePath);
156
+ if (boardInQuery && stamp === "stamp") {
157
+ const board = boardOverride ?? this.config.board;
158
+ // DX-4592: a board-less server (the 50arms plugin's, no DANXBOT_BOARD_ID) stamps nothing when the call names no
159
+ // board. Whether the route NEEDS one is the dashboard's to say: a route that does (quality-gate and settings routes, the dispatch
160
+ // verbs) answers 400 naming `board`, while a card-id, plan or plan-session route takes its board from the card
161
+ // or the plan. Refusing here (DX-4555) wrongly stopped every plan route, `plan_connect` first.
162
+ if (board !== undefined)
163
+ params.set("board", String(board));
164
+ }
165
+ else if (boardInQuery && stamp === "named" && boardOverride !== undefined) {
166
+ params.set("board", String(boardOverride));
167
+ }
168
+ if (extraQuery) {
169
+ for (const [k, v] of Object.entries(extraQuery)) {
170
+ if (v === undefined || v === null)
171
+ continue;
172
+ // DX-3465 — a field TREE (`fields`, a plain object) is the only
173
+ // query param the server parses as JSON
174
+ // (`src/resources/field-tree.ts`); every LIST-valued param it has
175
+ // (`ids` on `GET /api/issues/batch`, `status` on the boards list,
176
+ // `endpoints` on `GET /api/openapi`) splits on commas instead, never
177
+ // JSON. Arrays are `typeof "object"` too, so the array check must
178
+ // come BEFORE the plain-object branch. A plain scalar stringifies
179
+ // as before.
180
+ params.set(k, Array.isArray(v)
181
+ ? v.map((item) => String(item)).join(",")
182
+ : typeof v === "object"
183
+ ? JSON.stringify(v)
184
+ : String(v));
185
+ }
186
+ }
187
+ const query = params.toString();
188
+ return `${base}${routePath}${query === "" ? "" : `?${query}`}`;
189
+ }
190
+ }