haas-mcp 0.2.0

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 (74) hide show
  1. package/README.md +98 -0
  2. package/dist/check.d.ts +23 -0
  3. package/dist/check.d.ts.map +1 -0
  4. package/dist/check.js +34 -0
  5. package/dist/check.js.map +1 -0
  6. package/dist/cli.d.ts +3 -0
  7. package/dist/cli.d.ts.map +1 -0
  8. package/dist/cli.js +173 -0
  9. package/dist/cli.js.map +1 -0
  10. package/dist/client.d.ts +44 -0
  11. package/dist/client.d.ts.map +1 -0
  12. package/dist/client.js +168 -0
  13. package/dist/client.js.map +1 -0
  14. package/dist/config.d.ts +33 -0
  15. package/dist/config.d.ts.map +1 -0
  16. package/dist/config.js +53 -0
  17. package/dist/config.js.map +1 -0
  18. package/dist/connect.d.ts +68 -0
  19. package/dist/connect.d.ts.map +1 -0
  20. package/dist/connect.js +117 -0
  21. package/dist/connect.js.map +1 -0
  22. package/dist/server.d.ts +20 -0
  23. package/dist/server.d.ts.map +1 -0
  24. package/dist/server.js +77 -0
  25. package/dist/server.js.map +1 -0
  26. package/dist/setup.d.ts +94 -0
  27. package/dist/setup.d.ts.map +1 -0
  28. package/dist/setup.js +149 -0
  29. package/dist/setup.js.map +1 -0
  30. package/dist/tools/cancel_job.d.ts +3 -0
  31. package/dist/tools/cancel_job.d.ts.map +1 -0
  32. package/dist/tools/cancel_job.js +22 -0
  33. package/dist/tools/cancel_job.js.map +1 -0
  34. package/dist/tools/estimate_cost.d.ts +3 -0
  35. package/dist/tools/estimate_cost.d.ts.map +1 -0
  36. package/dist/tools/estimate_cost.js +28 -0
  37. package/dist/tools/estimate_cost.js.map +1 -0
  38. package/dist/tools/get_balance.d.ts +3 -0
  39. package/dist/tools/get_balance.d.ts.map +1 -0
  40. package/dist/tools/get_balance.js +13 -0
  41. package/dist/tools/get_balance.js.map +1 -0
  42. package/dist/tools/get_job_status.d.ts +3 -0
  43. package/dist/tools/get_job_status.d.ts.map +1 -0
  44. package/dist/tools/get_job_status.js +19 -0
  45. package/dist/tools/get_job_status.js.map +1 -0
  46. package/dist/tools/list_devices.d.ts +3 -0
  47. package/dist/tools/list_devices.d.ts.map +1 -0
  48. package/dist/tools/list_devices.js +13 -0
  49. package/dist/tools/list_devices.js.map +1 -0
  50. package/dist/tools/purchase_lease.d.ts +16 -0
  51. package/dist/tools/purchase_lease.d.ts.map +1 -0
  52. package/dist/tools/purchase_lease.js +50 -0
  53. package/dist/tools/purchase_lease.js.map +1 -0
  54. package/dist/tools/submit_batch.d.ts +3 -0
  55. package/dist/tools/submit_batch.d.ts.map +1 -0
  56. package/dist/tools/submit_batch.js +58 -0
  57. package/dist/tools/submit_batch.js.map +1 -0
  58. package/dist/tools/submit_job.d.ts +3 -0
  59. package/dist/tools/submit_job.d.ts.map +1 -0
  60. package/dist/tools/submit_job.js +49 -0
  61. package/dist/tools/submit_job.js.map +1 -0
  62. package/dist/tools/submit_source.d.ts +3 -0
  63. package/dist/tools/submit_source.d.ts.map +1 -0
  64. package/dist/tools/submit_source.js +55 -0
  65. package/dist/tools/submit_source.js.map +1 -0
  66. package/dist/tools/tail_serial.d.ts +3 -0
  67. package/dist/tools/tail_serial.d.ts.map +1 -0
  68. package/dist/tools/tail_serial.js +26 -0
  69. package/dist/tools/tail_serial.js.map +1 -0
  70. package/dist/tools/types.d.ts +9 -0
  71. package/dist/tools/types.d.ts.map +1 -0
  72. package/dist/tools/types.js +2 -0
  73. package/dist/tools/types.js.map +1 -0
  74. package/package.json +35 -0
package/dist/config.js ADDED
@@ -0,0 +1,53 @@
1
+ /**
2
+ * What `haas-mcp` is told, and what it assumes when told nothing (HAAS-142).
3
+ *
4
+ * Pure, so the one-line install can be tested: the whole point of a one-liner
5
+ * is that the only thing a user supplies is a token, which means every other
6
+ * value has to default to the right thing. The base URL defaulted to a
7
+ * localhost port for the first months of this package's life, so a user who
8
+ * ran the documented `npx haas-mcp` against production would have been
9
+ * talking to nothing.
10
+ */
11
+ /** Where the hosted service is. A developer running the app locally overrides it. */
12
+ export const PRODUCTION_BASE_URL = "https://haas-lab.com";
13
+ /**
14
+ * Flags win over the environment, which wins over the default. `--token` and
15
+ * `--base-url` take a value; `--check` and `--help` stand alone.
16
+ */
17
+ export function readConfig(argv, env) {
18
+ const valueOf = (name) => {
19
+ const at = argv.indexOf(`--${name}`);
20
+ return at < 0 ? undefined : argv[at + 1];
21
+ };
22
+ const has = (name) => argv.includes(`--${name}`);
23
+ const baseUrl = (valueOf("base-url") ?? env.HAAS_BASE_URL ?? PRODUCTION_BASE_URL).replace(/\/+$/, "");
24
+ return {
25
+ pat: valueOf("token") ?? env.HAAS_PAT ?? null,
26
+ baseUrl,
27
+ check: has("check"),
28
+ setup: argv[0] === "setup",
29
+ paste: has("paste"),
30
+ help: has("help") || has("h"),
31
+ };
32
+ }
33
+ /** Printed by `--help`, and the same lines the README and the docs page carry. */
34
+ export const USAGE = `haas-mcp — flash firmware on real boards from any AI client, over MCP.
35
+
36
+ npx -y haas-mcp setup opens the token page; click Connect there; every AI client here is connected
37
+ npx -y haas-mcp setup --paste the same, pasting the token instead (a Node older than 22 has no WebSocket)
38
+ HAAS_PAT=pat_... npx -y haas-mcp serve (stdio)
39
+ HAAS_PAT=pat_... npx -y haas-mcp --check prove the token, then exit 0 or 1
40
+ npx -y haas-mcp --help
41
+
42
+ Flags --token <pat> --base-url <url> --check --paste --help
43
+ Environment HAAS_PAT (required to serve) HAAS_BASE_URL (default ${PRODUCTION_BASE_URL})
44
+
45
+ Mint a token at ${PRODUCTION_BASE_URL}/dashboard/tokens with the read and submit scopes.
46
+
47
+ By hand, one line per client:
48
+ Claude Code claude mcp add haas-lab -e HAAS_PAT=pat_... -- npx -y haas-mcp
49
+ Codex CLI codex mcp add haas-lab --env HAAS_PAT=pat_... -- npx -y haas-mcp
50
+ Claude Desktop, Cursor and others take the JSON form:
51
+ {"mcpServers":{"haas-lab":{"command":"npx","args":["-y","haas-mcp"],"env":{"HAAS_PAT":"pat_..."}}}}
52
+ `;
53
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,qFAAqF;AACrF,MAAM,CAAC,MAAM,mBAAmB,GAAG,sBAAsB,CAAC;AAgB1D;;;GAGG;AACH,MAAM,UAAU,UAAU,CACxB,IAAuB,EACvB,GAAiD;IAEjD,MAAM,OAAO,GAAG,CAAC,IAAY,EAAsB,EAAE;QACnD,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;QACrC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;IAC3C,CAAC,CAAC;IACF,MAAM,GAAG,GAAG,CAAC,IAAY,EAAW,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;IAElE,MAAM,OAAO,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,GAAG,CAAC,aAAa,IAAI,mBAAmB,CAAC,CAAC,OAAO,CACvF,MAAM,EACN,EAAE,CACH,CAAC;IAEF,OAAO;QACL,GAAG,EAAE,OAAO,CAAC,OAAO,CAAC,IAAI,GAAG,CAAC,QAAQ,IAAI,IAAI;QAC7C,OAAO;QACP,KAAK,EAAE,GAAG,CAAC,OAAO,CAAC;QACnB,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,KAAK,OAAO;QAC1B,KAAK,EAAE,GAAG,CAAC,OAAO,CAAC;QACnB,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC,GAAG,CAAC;KAC9B,CAAC;AACJ,CAAC;AAED,kFAAkF;AAClF,MAAM,CAAC,MAAM,KAAK,GAAG;;;;;;;;;oEAS+C,mBAAmB;;kBAErE,mBAAmB;;;;;;;CAOpC,CAAC"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Receiving the token without a paste (HAAS-144).
3
+ *
4
+ * The wizard mints a random code, tells the server it is waiting on it, opens
5
+ * the token page with the code in the URL, and holds a WebSocket on the code's
6
+ * feed. When the signed-in person clicks, the server publishes the token on
7
+ * that feed and this receives it. The socket is what the server's object
8
+ * hibernates, so a person taking ten minutes to sign in costs nothing.
9
+ *
10
+ * Everything here is pure or takes its socket as an argument, so the tests
11
+ * drive it with a fake and the CLI passes the platform's WebSocket. Node has
12
+ * had a global WebSocket since 22; on older Nodes the CLI falls back to the
13
+ * paste prompt and says so.
14
+ */
15
+ /** How long the command waits for the click before giving up. Matches the
16
+ * server's own expiry, so a code the page would refuse is one this has
17
+ * already stopped waiting on. */
18
+ export declare const CONNECT_TIMEOUT_MS: number;
19
+ /** The object's auto-responder answers a text `ping` without waking, so this
20
+ * keeps an idle connection alive through a proxy for free. */
21
+ export declare const PING_INTERVAL_MS = 20000;
22
+ /**
23
+ * Four hex characters the person compares between this terminal and the page.
24
+ * The same derivation as the server's `setupCheck` in web/lib/setup-code.ts;
25
+ * both tests pin it. Not a secret and not proof of anything — it gives a page
26
+ * opened from somebody else's link a value the person has no terminal to
27
+ * match, which is the cue to close it (HAAS-144).
28
+ */
29
+ export declare function pairingCheck(code: string, sha256hex: (input: string) => string): string;
30
+ /** Sixteen random bytes as hex: the shape the server accepts and nothing else. */
31
+ export declare function newSetupCode(random: (bytes: number) => Uint8Array): string;
32
+ export declare function connectPageUrl(baseUrl: string, code: string): string;
33
+ export declare function registerUrl(baseUrl: string, code: string): string;
34
+ /** The feed's URL on the WebSocket scheme: https becomes wss, http becomes ws. */
35
+ export declare function setupStreamUrl(baseUrl: string, code: string): string;
36
+ /**
37
+ * The token in a frame, or null for any other frame.
38
+ *
39
+ * The object sends one JSON text frame per event: `{ id, event, data }`. The
40
+ * waiting event this command published, a `pong`, and a `done` with no token
41
+ * before it are all "not the token"; only `event: "token"` carries it.
42
+ */
43
+ export declare function tokenFromFrame(raw: unknown): string | null;
44
+ /** The least of a WebSocket this needs, so a test can hand in a fake. */
45
+ export interface SocketLike {
46
+ addEventListener(type: "message", listener: (event: {
47
+ data: unknown;
48
+ }) => void): void;
49
+ addEventListener(type: "close" | "error", listener: () => void): void;
50
+ send(data: string): void;
51
+ close(): void;
52
+ }
53
+ /** A timer handle on Node or in a browser, or the number a fake clock hands out. */
54
+ type Handle = ReturnType<typeof setTimeout> | number;
55
+ export interface ConnectClock {
56
+ setTimeout: (fn: () => void, ms: number) => Handle;
57
+ clearTimeout: (handle: Handle) => void;
58
+ setInterval: (fn: () => void, ms: number) => Handle;
59
+ clearInterval: (handle: Handle) => void;
60
+ }
61
+ /**
62
+ * Wait on an open socket for the token. Resolves with it; rejects when the
63
+ * socket closes or errors first, or when `timeoutMs` passes. Closes the socket
64
+ * on every outcome — the token is delivered once and the feed is done.
65
+ */
66
+ export declare function receiveToken(socket: SocketLike, timeoutMs: number, clock?: ConnectClock): Promise<string>;
67
+ export {};
68
+ //# sourceMappingURL=connect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connect.d.ts","sourceRoot":"","sources":["../src/connect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH;;iCAEiC;AACjC,eAAO,MAAM,kBAAkB,QAAiB,CAAC;AAEjD;8DAC8D;AAC9D,eAAO,MAAM,gBAAgB,QAAS,CAAC;AAEvC;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,MAAM,CAEvF;AAED,kFAAkF;AAClF,wBAAgB,YAAY,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,UAAU,GAAG,MAAM,CAE1E;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAEpE;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAEjE;AAED,kFAAkF;AAClF,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAGpE;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAY1D;AAED,yEAAyE;AACzE,MAAM,WAAW,UAAU;IACzB,gBAAgB,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,GAAG,IAAI,CAAC;IACtF,gBAAgB,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IACtE,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,KAAK,IAAI,IAAI,CAAC;CACf;AAED,oFAAoF;AACpF,KAAK,MAAM,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,GAAG,MAAM,CAAC;AAErD,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,CAAC,EAAE,EAAE,MAAM,IAAI,EAAE,EAAE,EAAE,MAAM,KAAK,MAAM,CAAC;IACnD,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,WAAW,EAAE,CAAC,EAAE,EAAE,MAAM,IAAI,EAAE,EAAE,EAAE,MAAM,KAAK,MAAM,CAAC;IACpD,aAAa,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CACzC;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,UAAU,EAClB,SAAS,EAAE,MAAM,EACjB,KAAK,GAAE,YAAyB,GAC/B,OAAO,CAAC,MAAM,CAAC,CAsCjB"}
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Receiving the token without a paste (HAAS-144).
3
+ *
4
+ * The wizard mints a random code, tells the server it is waiting on it, opens
5
+ * the token page with the code in the URL, and holds a WebSocket on the code's
6
+ * feed. When the signed-in person clicks, the server publishes the token on
7
+ * that feed and this receives it. The socket is what the server's object
8
+ * hibernates, so a person taking ten minutes to sign in costs nothing.
9
+ *
10
+ * Everything here is pure or takes its socket as an argument, so the tests
11
+ * drive it with a fake and the CLI passes the platform's WebSocket. Node has
12
+ * had a global WebSocket since 22; on older Nodes the CLI falls back to the
13
+ * paste prompt and says so.
14
+ */
15
+ /** How long the command waits for the click before giving up. Matches the
16
+ * server's own expiry, so a code the page would refuse is one this has
17
+ * already stopped waiting on. */
18
+ export const CONNECT_TIMEOUT_MS = 10 * 60 * 1000;
19
+ /** The object's auto-responder answers a text `ping` without waking, so this
20
+ * keeps an idle connection alive through a proxy for free. */
21
+ export const PING_INTERVAL_MS = 20_000;
22
+ /**
23
+ * Four hex characters the person compares between this terminal and the page.
24
+ * The same derivation as the server's `setupCheck` in web/lib/setup-code.ts;
25
+ * both tests pin it. Not a secret and not proof of anything — it gives a page
26
+ * opened from somebody else's link a value the person has no terminal to
27
+ * match, which is the cue to close it (HAAS-144).
28
+ */
29
+ export function pairingCheck(code, sha256hex) {
30
+ return sha256hex(code).slice(0, 4);
31
+ }
32
+ /** Sixteen random bytes as hex: the shape the server accepts and nothing else. */
33
+ export function newSetupCode(random) {
34
+ return Array.from(random(16), (b) => b.toString(16).padStart(2, "0")).join("");
35
+ }
36
+ export function connectPageUrl(baseUrl, code) {
37
+ return `${baseUrl}/dashboard/tokens?connect=${code}`;
38
+ }
39
+ export function registerUrl(baseUrl, code) {
40
+ return `${baseUrl}/api/setup/${code}`;
41
+ }
42
+ /** The feed's URL on the WebSocket scheme: https becomes wss, http becomes ws. */
43
+ export function setupStreamUrl(baseUrl, code) {
44
+ const base = baseUrl.replace(/^https:/, "wss:").replace(/^http:/, "ws:");
45
+ return `${base}/api/setup/${code}/stream`;
46
+ }
47
+ /**
48
+ * The token in a frame, or null for any other frame.
49
+ *
50
+ * The object sends one JSON text frame per event: `{ id, event, data }`. The
51
+ * waiting event this command published, a `pong`, and a `done` with no token
52
+ * before it are all "not the token"; only `event: "token"` carries it.
53
+ */
54
+ export function tokenFromFrame(raw) {
55
+ if (typeof raw !== "string")
56
+ return null;
57
+ let parsed;
58
+ try {
59
+ parsed = JSON.parse(raw);
60
+ }
61
+ catch {
62
+ return null;
63
+ }
64
+ if (typeof parsed !== "object" || parsed === null)
65
+ return null;
66
+ const frame = parsed;
67
+ if (frame.event !== "token" || typeof frame.data !== "string" || !frame.data)
68
+ return null;
69
+ return frame.data;
70
+ }
71
+ /**
72
+ * Wait on an open socket for the token. Resolves with it; rejects when the
73
+ * socket closes or errors first, or when `timeoutMs` passes. Closes the socket
74
+ * on every outcome — the token is delivered once and the feed is done.
75
+ */
76
+ export function receiveToken(socket, timeoutMs, clock = globalThis) {
77
+ return new Promise((resolve, reject) => {
78
+ let settled = false;
79
+ const ping = clock.setInterval(() => {
80
+ try {
81
+ socket.send("ping");
82
+ }
83
+ catch {
84
+ // Closing; the close listener settles this.
85
+ }
86
+ }, PING_INTERVAL_MS);
87
+ const timer = clock.setTimeout(() => {
88
+ finish(() => reject(new Error(`no token arrived within ${Math.round(timeoutMs / 60_000)} minutes`)));
89
+ }, timeoutMs);
90
+ function finish(outcome) {
91
+ if (settled)
92
+ return;
93
+ settled = true;
94
+ clock.clearInterval(ping);
95
+ clock.clearTimeout(timer);
96
+ try {
97
+ socket.close();
98
+ }
99
+ catch {
100
+ // Already closed.
101
+ }
102
+ outcome();
103
+ }
104
+ socket.addEventListener("message", (event) => {
105
+ const token = tokenFromFrame(event.data);
106
+ if (token !== null)
107
+ finish(() => resolve(token));
108
+ });
109
+ socket.addEventListener("close", () => {
110
+ finish(() => reject(new Error("the connection closed before a token arrived")));
111
+ });
112
+ socket.addEventListener("error", () => {
113
+ finish(() => reject(new Error("the connection failed before a token arrived")));
114
+ });
115
+ });
116
+ }
117
+ //# sourceMappingURL=connect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connect.js","sourceRoot":"","sources":["../src/connect.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import type { HaasClient } from "./client.js";
2
+ import type { ToolDef } from "./tools/types.js";
3
+ /**
4
+ * Every tool this server offers, in the order a client sees them.
5
+ *
6
+ * Exported so a test can compare it against the contents of `src/tools/`
7
+ * (HAAS-122). The failure this invites is a tool file that exists and is never
8
+ * added here: it type-checks, it builds, and the capability is simply missing
9
+ * from ListTools with nothing to say so. Checking a hand-maintained list
10
+ * against the directory it is supposed to mirror is the only way to see that.
11
+ */
12
+ export declare const TOOLS: ToolDef[];
13
+ /**
14
+ * What the server tells a client it is, read from the package rather than
15
+ * written here. It said 0.1.0 while the package said 0.2.0, so a client
16
+ * reading serverInfo was told an older release than it had (HAAS-211).
17
+ */
18
+ export declare const SERVER_VERSION: string;
19
+ export declare function startMcpServer(client: HaasClient): Promise<void>;
20
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAahD;;;;;;;;GAQG;AACH,eAAO,MAAM,KAAK,EAAE,OAAO,EAW1B,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,cAAc,EAAE,MAEpB,CAAC;AAEV,wBAAsB,cAAc,CAAC,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAuCtE"}
package/dist/server.js ADDED
@@ -0,0 +1,77 @@
1
+ import { createRequire } from "node:module";
2
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
5
+ import { listDevicesTool } from "./tools/list_devices.js";
6
+ import { submitJobTool } from "./tools/submit_job.js";
7
+ import { submitSourceTool } from "./tools/submit_source.js";
8
+ import { submitBatchTool } from "./tools/submit_batch.js";
9
+ import { getJobStatusTool } from "./tools/get_job_status.js";
10
+ import { tailSerialTool } from "./tools/tail_serial.js";
11
+ import { getBalanceTool } from "./tools/get_balance.js";
12
+ import { estimateCostTool } from "./tools/estimate_cost.js";
13
+ import { cancelJobTool } from "./tools/cancel_job.js";
14
+ import { purchaseLeaseTool } from "./tools/purchase_lease.js";
15
+ /**
16
+ * Every tool this server offers, in the order a client sees them.
17
+ *
18
+ * Exported so a test can compare it against the contents of `src/tools/`
19
+ * (HAAS-122). The failure this invites is a tool file that exists and is never
20
+ * added here: it type-checks, it builds, and the capability is simply missing
21
+ * from ListTools with nothing to say so. Checking a hand-maintained list
22
+ * against the directory it is supposed to mirror is the only way to see that.
23
+ */
24
+ export const TOOLS = [
25
+ listDevicesTool,
26
+ submitJobTool,
27
+ submitSourceTool,
28
+ submitBatchTool,
29
+ getJobStatusTool,
30
+ tailSerialTool,
31
+ getBalanceTool,
32
+ estimateCostTool,
33
+ cancelJobTool,
34
+ purchaseLeaseTool,
35
+ ];
36
+ /**
37
+ * What the server tells a client it is, read from the package rather than
38
+ * written here. It said 0.1.0 while the package said 0.2.0, so a client
39
+ * reading serverInfo was told an older release than it had (HAAS-211).
40
+ */
41
+ export const SERVER_VERSION = createRequire(import.meta.url)("../package.json").version;
42
+ export async function startMcpServer(client) {
43
+ const server = new Server({ name: "haas-mcp", version: SERVER_VERSION }, { capabilities: { tools: {} } });
44
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
45
+ tools: TOOLS.map((t) => ({
46
+ name: t.name,
47
+ description: t.description,
48
+ inputSchema: t.inputSchema,
49
+ })),
50
+ }));
51
+ server.setRequestHandler(CallToolRequestSchema, async (req) => {
52
+ const tool = TOOLS.find((t) => t.name === req.params.name);
53
+ if (!tool) {
54
+ return {
55
+ isError: true,
56
+ content: [{ type: "text", text: `unknown tool: ${req.params.name}` }],
57
+ };
58
+ }
59
+ try {
60
+ const args = (req.params.arguments ?? {});
61
+ const result = await tool.handler(args, client);
62
+ return {
63
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
64
+ };
65
+ }
66
+ catch (err) {
67
+ const message = err instanceof Error ? err.message : String(err);
68
+ return {
69
+ isError: true,
70
+ content: [{ type: "text", text: message }],
71
+ };
72
+ }
73
+ });
74
+ const transport = new StdioServerTransport();
75
+ await server.connect(transport);
76
+ }
77
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EACL,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,oCAAoC,CAAC;AAK5C,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAE9D;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,KAAK,GAAc;IAC9B,eAAe;IACf,aAAa;IACb,gBAAgB;IAChB,eAAe;IACf,gBAAgB;IAChB,cAAc;IACd,cAAc;IACd,gBAAgB;IAChB,aAAa;IACb,iBAAiB;CAClB,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,cAAc,GACzB,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,iBAAiB,CACjD,CAAC,OAAO,CAAC;AAEV,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,MAAkB;IACrD,MAAM,MAAM,GAAG,IAAI,MAAM,CACvB,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,cAAc,EAAE,EAC7C,EAAE,YAAY,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,CAChC,CAAC;IAEF,MAAM,CAAC,iBAAiB,CAAC,sBAAsB,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;QAC5D,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACvB,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,WAAW,EAAE,CAAC,CAAC,WAAW;YAC1B,WAAW,EAAE,CAAC,CAAC,WAAW;SAC3B,CAAC,CAAC;KACJ,CAAC,CAAC,CAAC;IAEJ,MAAM,CAAC,iBAAiB,CAAC,qBAAqB,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;QAC5D,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAC3D,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,OAAO;gBACL,OAAO,EAAE,IAAI;gBACb,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC;aACtE,CAAC;QACJ,CAAC;QACD,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,SAAS,IAAI,EAAE,CAA4B,CAAC;YACrE,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;YAChD,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;aACnE,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACjE,OAAO;gBACL,OAAO,EAAE,IAAI;gBACb,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;aAC3C,CAAC;QACJ,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC"}
@@ -0,0 +1,94 @@
1
+ /**
2
+ * `haas-mcp setup`: one question, then every AI client on this machine is
3
+ * connected (HAAS-142).
4
+ *
5
+ * The owner's brief was a frictionless connection, so the wizard is as short
6
+ * as it can be: paste a token. Everything else is detected — which clients are
7
+ * installed, where each keeps its server list — and written for them. The
8
+ * token is proved against the API first, so a client is never restarted
9
+ * around a token that does not work.
10
+ *
11
+ * Planning is pure and takes the machine as arguments (`platform`, `env`,
12
+ * `exists`, `onPath`), which is what lets the decisions be tested on any
13
+ * machine; `runSetup` performs the plan with an injected `io`.
14
+ */
15
+ export declare const SERVER_NAME = "haas-lab";
16
+ /**
17
+ * Where the wizard sends the browser: the token page itself. A signed-out
18
+ * visitor is taken to sign-in by the site's middleware with this page as the
19
+ * return destination, so one URL covers both the signed-in and signed-out
20
+ * case and the person lands on the mint button either way.
21
+ */
22
+ export declare function tokenPageUrl(baseUrl: string): string;
23
+ /** The command that opens a URL in the default browser on this platform. */
24
+ export declare function browserCommand(platform: NodeJS.Platform, url: string): {
25
+ program: string;
26
+ args: string[];
27
+ };
28
+ /** What every client is told to run. The token travels in the environment. */
29
+ export declare function serverEntry(token: string): {
30
+ command: string;
31
+ args: string[];
32
+ env: Record<string, string>;
33
+ };
34
+ /** One client this wizard can connect, and how. */
35
+ export type SetupAction =
36
+ /** A CLI that registers servers itself, run once. */
37
+ {
38
+ kind: "command";
39
+ client: string;
40
+ program: string;
41
+ args: string[];
42
+ }
43
+ /** A client that reads a JSON server list, merged in place. */
44
+ | {
45
+ kind: "json";
46
+ client: string;
47
+ path: string;
48
+ };
49
+ export interface Machine {
50
+ platform: NodeJS.Platform;
51
+ env: Readonly<Record<string, string | undefined>>;
52
+ home: string;
53
+ /** Whether a file exists. */
54
+ exists: (path: string) => boolean;
55
+ /** Where a program is on PATH, or null. */
56
+ onPath: (name: string) => string | null;
57
+ }
58
+ /**
59
+ * Which clients are here, and what each needs.
60
+ *
61
+ * A CLI is preferred where it exists, because it owns its own config format.
62
+ * A JSON client is included when its config file already exists OR its
63
+ * directory does — a Cursor that has never had an MCP server still has a
64
+ * `.cursor` directory — and skipped otherwise, so the wizard never invents a
65
+ * client the user does not have.
66
+ */
67
+ export declare function planSetup(token: string, machine: Machine): SetupAction[];
68
+ /**
69
+ * A client's server list with ours in it, leaving everything else as it was.
70
+ *
71
+ * Takes the file's text rather than a parsed object so an unreadable file is
72
+ * refused here — with the reason — instead of being overwritten with only our
73
+ * entry. `null` is a file that does not exist yet.
74
+ */
75
+ export declare function mergeServer(existing: string | null, token: string): string;
76
+ /** The side effects, injected so the orchestration can be tested without any. */
77
+ export interface SetupIo {
78
+ run: (program: string, args: string[]) => {
79
+ ok: boolean;
80
+ output: string;
81
+ };
82
+ read: (path: string) => string | null;
83
+ write: (path: string, text: string) => void;
84
+ }
85
+ export interface SetupOutcome {
86
+ client: string;
87
+ ok: boolean;
88
+ detail: string;
89
+ }
90
+ /** Perform every action, reporting each; one client failing does not stop the rest. */
91
+ export declare function runSetup(actions: readonly SetupAction[], token: string, io: SetupIo): SetupOutcome[];
92
+ /** One line per client, and what to do next. */
93
+ export declare function describeOutcomes(outcomes: readonly SetupOutcome[]): string;
94
+ //# sourceMappingURL=setup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,eAAO,MAAM,WAAW,aAAa,CAAC;AAEtC;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED,4EAA4E;AAC5E,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAA;CAAE,CAI1G;AAED,8EAA8E;AAC9E,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC7B,CAEA;AAED,mDAAmD;AACnD,MAAM,MAAM,WAAW;AACrB,qDAAqD;AACnD;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAA;CAAE;AACtE,+DAA+D;GAC7D;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnD,MAAM,WAAW,OAAO;IACtB,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC;IAC1B,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,6BAA6B;IAC7B,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;IAClC,2CAA2C;IAC3C,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;CACzC;AAED;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,WAAW,EAAE,CA6CxE;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAsB1E;AAED,iFAAiF;AACjF,MAAM,WAAW,OAAO;IACtB,GAAG,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1E,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;IACtC,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7C;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,uFAAuF;AACvF,wBAAgB,QAAQ,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,GAAG,YAAY,EAAE,CAqBpG;AAED,gDAAgD;AAChD,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,SAAS,YAAY,EAAE,GAAG,MAAM,CAY1E"}
package/dist/setup.js ADDED
@@ -0,0 +1,149 @@
1
+ /**
2
+ * `haas-mcp setup`: one question, then every AI client on this machine is
3
+ * connected (HAAS-142).
4
+ *
5
+ * The owner's brief was a frictionless connection, so the wizard is as short
6
+ * as it can be: paste a token. Everything else is detected — which clients are
7
+ * installed, where each keeps its server list — and written for them. The
8
+ * token is proved against the API first, so a client is never restarted
9
+ * around a token that does not work.
10
+ *
11
+ * Planning is pure and takes the machine as arguments (`platform`, `env`,
12
+ * `exists`, `onPath`), which is what lets the decisions be tested on any
13
+ * machine; `runSetup` performs the plan with an injected `io`.
14
+ */
15
+ export const SERVER_NAME = "haas-lab";
16
+ /**
17
+ * Where the wizard sends the browser: the token page itself. A signed-out
18
+ * visitor is taken to sign-in by the site's middleware with this page as the
19
+ * return destination, so one URL covers both the signed-in and signed-out
20
+ * case and the person lands on the mint button either way.
21
+ */
22
+ export function tokenPageUrl(baseUrl) {
23
+ return `${baseUrl}/dashboard/tokens`;
24
+ }
25
+ /** The command that opens a URL in the default browser on this platform. */
26
+ export function browserCommand(platform, url) {
27
+ if (platform === "win32")
28
+ return { program: "cmd", args: ["/c", "start", "", url] };
29
+ if (platform === "darwin")
30
+ return { program: "open", args: [url] };
31
+ return { program: "xdg-open", args: [url] };
32
+ }
33
+ /** What every client is told to run. The token travels in the environment. */
34
+ export function serverEntry(token) {
35
+ return { command: "npx", args: ["-y", "haas-mcp"], env: { HAAS_PAT: token } };
36
+ }
37
+ /**
38
+ * Which clients are here, and what each needs.
39
+ *
40
+ * A CLI is preferred where it exists, because it owns its own config format.
41
+ * A JSON client is included when its config file already exists OR its
42
+ * directory does — a Cursor that has never had an MCP server still has a
43
+ * `.cursor` directory — and skipped otherwise, so the wizard never invents a
44
+ * client the user does not have.
45
+ */
46
+ export function planSetup(token, machine) {
47
+ const actions = [];
48
+ const join = (...parts) => parts.join(machine.platform === "win32" ? "\\" : "/").replace(/[\\/]+/g, (s) => s[0] ?? "/");
49
+ const claude = machine.onPath("claude");
50
+ if (claude) {
51
+ actions.push({
52
+ kind: "command",
53
+ client: "Claude Code",
54
+ program: claude,
55
+ args: ["mcp", "add", "--scope", "user", SERVER_NAME, "-e", `HAAS_PAT=${token}`, "--", "npx", "-y", "haas-mcp"],
56
+ });
57
+ }
58
+ const codex = machine.onPath("codex");
59
+ if (codex) {
60
+ actions.push({
61
+ kind: "command",
62
+ client: "Codex CLI",
63
+ program: codex,
64
+ args: ["mcp", "add", SERVER_NAME, "--env", `HAAS_PAT=${token}`, "--", "npx", "-y", "haas-mcp"],
65
+ });
66
+ }
67
+ const desktopDir = machine.platform === "darwin"
68
+ ? join(machine.home, "Library", "Application Support", "Claude")
69
+ : machine.platform === "win32"
70
+ ? join(machine.env.APPDATA ?? join(machine.home, "AppData", "Roaming"), "Claude")
71
+ : join(machine.home, ".config", "Claude");
72
+ if (machine.exists(desktopDir)) {
73
+ actions.push({
74
+ kind: "json",
75
+ client: "Claude Desktop",
76
+ path: join(desktopDir, "claude_desktop_config.json"),
77
+ });
78
+ }
79
+ const cursorDir = join(machine.home, ".cursor");
80
+ if (machine.exists(cursorDir)) {
81
+ actions.push({ kind: "json", client: "Cursor", path: join(cursorDir, "mcp.json") });
82
+ }
83
+ return actions;
84
+ }
85
+ /**
86
+ * A client's server list with ours in it, leaving everything else as it was.
87
+ *
88
+ * Takes the file's text rather than a parsed object so an unreadable file is
89
+ * refused here — with the reason — instead of being overwritten with only our
90
+ * entry. `null` is a file that does not exist yet.
91
+ */
92
+ export function mergeServer(existing, token) {
93
+ let document = {};
94
+ if (existing !== null && existing.trim() !== "") {
95
+ let parsed;
96
+ try {
97
+ parsed = JSON.parse(existing);
98
+ }
99
+ catch (error) {
100
+ throw new Error(`the existing config is not valid JSON, so it was left alone: ${error instanceof Error ? error.message : String(error)}`);
101
+ }
102
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
103
+ throw new Error("the existing config is not a JSON object, so it was left alone");
104
+ }
105
+ document = parsed;
106
+ }
107
+ const servers = typeof document.mcpServers === "object" && document.mcpServers !== null
108
+ ? document.mcpServers
109
+ : {};
110
+ document.mcpServers = { ...servers, [SERVER_NAME]: serverEntry(token) };
111
+ return `${JSON.stringify(document, null, 2)}\n`;
112
+ }
113
+ /** Perform every action, reporting each; one client failing does not stop the rest. */
114
+ export function runSetup(actions, token, io) {
115
+ return actions.map((action) => {
116
+ if (action.kind === "command") {
117
+ const result = io.run(action.program, action.args);
118
+ return {
119
+ client: action.client,
120
+ ok: result.ok,
121
+ detail: result.ok ? "registered" : result.output.trim() || "the command failed",
122
+ };
123
+ }
124
+ try {
125
+ io.write(action.path, mergeServer(io.read(action.path), token));
126
+ return { client: action.client, ok: true, detail: `written to ${action.path}` };
127
+ }
128
+ catch (error) {
129
+ return {
130
+ client: action.client,
131
+ ok: false,
132
+ detail: error instanceof Error ? error.message : String(error),
133
+ };
134
+ }
135
+ });
136
+ }
137
+ /** One line per client, and what to do next. */
138
+ export function describeOutcomes(outcomes) {
139
+ if (outcomes.length === 0) {
140
+ return ("haas-mcp: no AI client found on this machine (looked for Claude Code, Codex CLI, " +
141
+ "Claude Desktop and Cursor). Add this to your client by hand:\n" +
142
+ ` ${JSON.stringify({ mcpServers: { [SERVER_NAME]: serverEntry("pat_...") } })}\n`);
143
+ }
144
+ const lines = outcomes.map((o) => ` ${o.ok ? "connected" : "FAILED "} ${o.client}: ${o.detail}`);
145
+ const restart = outcomes.filter((o) => o.ok).map((o) => o.client);
146
+ const next = restart.length ? `\nRestart ${restart.join(", ")} and ask: "List the haas-lab devices."\n` : "\n";
147
+ return `haas-mcp setup:\n${lines.join("\n")}${next}`;
148
+ }
149
+ //# sourceMappingURL=setup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.js","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { ToolDef } from "./types.js";
2
+ export declare const cancelJobTool: ToolDef;
3
+ //# sourceMappingURL=cancel_job.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cancel_job.d.ts","sourceRoot":"","sources":["../../src/tools/cancel_job.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE1C,eAAO,MAAM,aAAa,EAAE,OAoB3B,CAAC"}