@ait-co/devtools 0.1.106 → 0.1.108
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/bundle-BJm5jk56.d.ts +49 -0
- package/dist/bundle-BJm5jk56.d.ts.map +1 -0
- package/dist/cdp-connection-C0AP0tH2.d.ts +277 -0
- package/dist/cdp-connection-C0AP0tH2.d.ts.map +1 -0
- package/dist/in-app/auto.d.ts +17 -0
- package/dist/in-app/auto.d.ts.map +1 -1
- package/dist/in-app/auto.js +76 -0
- package/dist/in-app/auto.js.map +1 -1
- package/dist/in-app/index.d.ts +48 -1
- package/dist/in-app/index.d.ts.map +1 -1
- package/dist/in-app/index.js +60 -1
- package/dist/in-app/index.js.map +1 -1
- package/dist/mcp/cli.js +507 -11
- package/dist/mcp/cli.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +60 -3
- package/dist/mcp/server.js.map +1 -1
- package/dist/panel/index.js +1 -1
- package/dist/pool-Dkp7I9Bf.d.ts +14577 -0
- package/dist/pool-Dkp7I9Bf.d.ts.map +1 -0
- package/dist/{relay-secret-store-I5q2Wvvv.cjs → relay-secret-store-CPBBlV3J.cjs} +2 -2
- package/dist/{relay-secret-store-I5q2Wvvv.cjs.map → relay-secret-store-CPBBlV3J.cjs.map} +1 -1
- package/dist/{relay-secret-store-Bns5rndt.js → relay-secret-store-DBwzoCXQ.js} +2 -2
- package/dist/{relay-secret-store-Bns5rndt.js.map → relay-secret-store-DBwzoCXQ.js.map} +1 -1
- package/dist/{relay-secret-store-B0DH-8Qb.js → relay-secret-store-DhzAnnj-.js} +2 -2
- package/dist/{relay-secret-store-B0DH-8Qb.js.map → relay-secret-store-DhzAnnj-.js.map} +1 -1
- package/dist/{relay-url-store-CvmnevcO.cjs → relay-url-store-C9QLhB2p.cjs} +2 -2
- package/dist/{relay-url-store-CvmnevcO.cjs.map → relay-url-store-C9QLhB2p.cjs.map} +1 -1
- package/dist/{relay-url-store-BPeUZsiY.js → relay-url-store-CwKT7i04.js} +2 -2
- package/dist/{relay-url-store-BPeUZsiY.js.map → relay-url-store-CwKT7i04.js.map} +1 -1
- package/dist/{relay-url-store-DJHZjk8o.js → relay-url-store-j16TRTiJ.js} +2 -2
- package/dist/{relay-url-store-DJHZjk8o.js.map → relay-url-store-j16TRTiJ.js.map} +1 -1
- package/dist/relay-worker-BzFQ3fv9.d.ts +74 -0
- package/dist/relay-worker-BzFQ3fv9.d.ts.map +1 -0
- package/dist/runtime-ORdrpizY.d.ts +50 -0
- package/dist/runtime-ORdrpizY.d.ts.map +1 -0
- package/dist/test-runner/bundle.d.ts +2 -0
- package/dist/test-runner/bundle.js +95 -0
- package/dist/test-runner/bundle.js.map +1 -0
- package/dist/test-runner/cli.d.ts +417 -0
- package/dist/test-runner/cli.d.ts.map +1 -0
- package/dist/test-runner/cli.js +377 -0
- package/dist/test-runner/cli.js.map +1 -0
- package/dist/test-runner/config.d.ts +80 -0
- package/dist/test-runner/config.d.ts.map +1 -0
- package/dist/test-runner/config.js +54 -0
- package/dist/test-runner/config.js.map +1 -0
- package/dist/test-runner/pool.d.ts +2 -0
- package/dist/test-runner/pool.js +136 -0
- package/dist/test-runner/pool.js.map +1 -0
- package/dist/test-runner/relay-worker.d.ts +2 -0
- package/dist/test-runner/relay-worker.js +96 -0
- package/dist/test-runner/relay-worker.js.map +1 -0
- package/dist/test-runner/rpc.d.ts +53 -0
- package/dist/test-runner/rpc.d.ts.map +1 -0
- package/dist/test-runner/rpc.js +78 -0
- package/dist/test-runner/rpc.js.map +1 -0
- package/dist/test-runner/task-graph.d.ts +38 -0
- package/dist/test-runner/task-graph.d.ts.map +1 -0
- package/dist/test-runner/task-graph.js +182 -0
- package/dist/test-runner/task-graph.js.map +1 -0
- package/dist/{totp-BmKSPb5d.js → totp-95OAa20j.js} +2 -2
- package/dist/totp-95OAa20j.js.map +1 -0
- package/dist/{totp-BwDZ6dUT.cjs → totp-BjtoQNfu.cjs} +2 -2
- package/dist/totp-BjtoQNfu.cjs.map +1 -0
- package/dist/totp-D1pulXLa.js +3 -0
- package/dist/{totp-DYdP9N3o.js → totp-DIbrZtI7.js} +2 -2
- package/dist/totp-DIbrZtI7.js.map +1 -0
- package/dist/{totp-CNw0w89F.cjs → totp-Df252ZdA.cjs} +2 -2
- package/dist/totp-Df252ZdA.cjs.map +1 -0
- package/dist/{totp-Xq3ACwkm.js → totp-WY6l0ysP.js} +2 -2
- package/dist/totp-WY6l0ysP.js.map +1 -0
- package/dist/{tunnel-BmDcTrnU.js → tunnel-BjJROkcj.js} +2 -2
- package/dist/{tunnel-BmDcTrnU.js.map → tunnel-BjJROkcj.js.map} +1 -1
- package/dist/{tunnel-RB5zB8IK.cjs → tunnel-d_G9AIFn.cjs} +2 -2
- package/dist/{tunnel-RB5zB8IK.cjs.map → tunnel-d_G9AIFn.cjs.map} +1 -1
- package/dist/unplugin/index.cjs +4 -4
- package/dist/unplugin/index.d.cts +13 -32
- package/dist/unplugin/index.d.cts.map +1 -1
- package/dist/unplugin/index.d.ts +13 -32
- package/dist/unplugin/index.d.ts.map +1 -1
- package/dist/unplugin/index.js +4 -4
- package/dist/unplugin/tunnel.cjs +1 -1
- package/dist/unplugin/tunnel.js +1 -1
- package/package.json +13 -3
- package/dist/totp-BcBNRoDD.js +0 -3
- package/dist/totp-BmKSPb5d.js.map +0 -1
- package/dist/totp-BwDZ6dUT.cjs.map +0 -1
- package/dist/totp-CNw0w89F.cjs.map +0 -1
- package/dist/totp-DYdP9N3o.js.map +0 -1
- package/dist/totp-Xq3ACwkm.js.map +0 -1
package/dist/mcp/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { i as generateTotp, n as assertRelayAuthConfigured, r as buildRelayVerifyAuth } from "../totp-
|
|
3
|
-
import { t as loadRelaySecretReadOnly } from "../relay-secret-store-
|
|
2
|
+
import { i as generateTotp, n as assertRelayAuthConfigured, r as buildRelayVerifyAuth } from "../totp-WY6l0ysP.js";
|
|
3
|
+
import { t as loadRelaySecretReadOnly } from "../relay-secret-store-DhzAnnj-.js";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import { existsSync, mkdirSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
6
6
|
import { argv } from "node:process";
|
|
@@ -8,6 +8,10 @@ import { fileURLToPath } from "node:url";
|
|
|
8
8
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
9
9
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
10
10
|
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
11
|
+
import { parseArgs } from "node:util";
|
|
12
|
+
import { glob } from "node:fs/promises";
|
|
13
|
+
import * as path from "node:path";
|
|
14
|
+
import { isAbsolute, join, resolve } from "node:path";
|
|
11
15
|
import { EventEmitter } from "node:events";
|
|
12
16
|
import { WebSocket, WebSocketServer } from "ws";
|
|
13
17
|
import { randomBytes } from "node:crypto";
|
|
@@ -15,7 +19,6 @@ import { createServer } from "node:http";
|
|
|
15
19
|
import { spawn } from "node:child_process";
|
|
16
20
|
import net from "node:net";
|
|
17
21
|
import { homedir, platform } from "node:os";
|
|
18
|
-
import { join } from "node:path";
|
|
19
22
|
import { Tunnel, bin, install } from "cloudflared";
|
|
20
23
|
//#region \0rolldown/runtime.js
|
|
21
24
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
@@ -119,6 +122,375 @@ function startMaxAgeWatchdog(onExpired, opts = {}) {
|
|
|
119
122
|
} };
|
|
120
123
|
}
|
|
121
124
|
//#endregion
|
|
125
|
+
//#region src/test-runner/discover.ts
|
|
126
|
+
/**
|
|
127
|
+
* Test-file discovery shared by the `devtools-test` CLI and the `run_tests`
|
|
128
|
+
* MCP tool, so both expand glob patterns with identical semantics.
|
|
129
|
+
*
|
|
130
|
+
* Uses Node's built-in `fs/promises` `glob` (Node 22+) — no extra dependency,
|
|
131
|
+
* which keeps the MCP daemon install graph lean (a plain glob lib would land in
|
|
132
|
+
* the `npx … devtools-mcp` path for no benefit).
|
|
133
|
+
*
|
|
134
|
+
* Pure Node IO only (`node:fs/promises` + `node:path`) — react-free, so it is
|
|
135
|
+
* safe to import from the MCP daemon graph.
|
|
136
|
+
*/
|
|
137
|
+
/**
|
|
138
|
+
* Expands `patterns` (globs or plain paths) into a sorted, de-duplicated list of
|
|
139
|
+
* ABSOLUTE test file paths, resolved relative to `cwd`.
|
|
140
|
+
*
|
|
141
|
+
* A plain (non-glob) path passes through when it matches a real file; a glob
|
|
142
|
+
* expands against `cwd`. Absolute matches are kept as-is; relative matches are
|
|
143
|
+
* resolved against `cwd`. `bundleTestFile` requires an absolute path, so the
|
|
144
|
+
* absolute output feeds it directly.
|
|
145
|
+
*
|
|
146
|
+
* @param patterns Glob patterns or file paths (e.g. `['src/**\/*.phone.test.ts']`).
|
|
147
|
+
* @param cwd Base directory for relative patterns/results.
|
|
148
|
+
* @returns Sorted, de-duplicated absolute file paths. Empty when nothing matches.
|
|
149
|
+
*/
|
|
150
|
+
async function discoverTestFiles(patterns, cwd) {
|
|
151
|
+
const out = /* @__PURE__ */ new Set();
|
|
152
|
+
for await (const match of glob(patterns, { cwd })) out.add(isAbsolute(match) ? match : resolve(cwd, match));
|
|
153
|
+
return [...out].sort();
|
|
154
|
+
}
|
|
155
|
+
//#endregion
|
|
156
|
+
//#region src/test-runner/bundle.ts
|
|
157
|
+
/**
|
|
158
|
+
* esbuild-based bundler for user test files.
|
|
159
|
+
*
|
|
160
|
+
* Bundles a single test file into a self-contained IIFE string that can be
|
|
161
|
+
* injected into a WebView via `Runtime.evaluate`. The user's SDK imports
|
|
162
|
+
* (`@apps-in-toss/web-framework` and sub-paths) are intercepted via an
|
|
163
|
+
* esbuild plugin that redirects them to `window.__sdk`, which the in-app
|
|
164
|
+
* debug gate (`src/in-app/auto.ts`) installs as a namespace mirror of the
|
|
165
|
+
* SDK exports (works for both 2.x and 3.x SDK).
|
|
166
|
+
*
|
|
167
|
+
* SECRET-HANDLING: the returned bundle code is caller-managed; never log it.
|
|
168
|
+
*/
|
|
169
|
+
/**
|
|
170
|
+
* Matches the bare SDK package and any sub-path import
|
|
171
|
+
* (`@apps-in-toss/web-framework`, `@apps-in-toss/web-framework/foo`).
|
|
172
|
+
* Built from {@link SDK_PACKAGE} so the package name has a single source.
|
|
173
|
+
*/
|
|
174
|
+
const SDK_IMPORT_FILTER = new RegExp(`^${"@apps-in-toss/web-framework".replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`);
|
|
175
|
+
/**
|
|
176
|
+
* esbuild plugin that intercepts SDK imports and redirects them to the
|
|
177
|
+
* `window.__sdk` proxy that `src/in-app/auto.ts` installs at runtime.
|
|
178
|
+
*
|
|
179
|
+
* Strategy: for every import of `@apps-in-toss/web-framework` (or sub-paths),
|
|
180
|
+
* esbuild resolves it to a virtual module that re-exports all named exports
|
|
181
|
+
* via `window.__sdk[name]`. This avoids bundling the real SDK (which may not
|
|
182
|
+
* be available in the test environment) while still making named imports work.
|
|
183
|
+
*
|
|
184
|
+
* If `window.__sdk` is absent (non-dog-food build), every access throws a
|
|
185
|
+
* descriptive error rather than returning `undefined` silently.
|
|
186
|
+
*/
|
|
187
|
+
function sdkRedirectPlugin() {
|
|
188
|
+
return {
|
|
189
|
+
name: "sdk-redirect",
|
|
190
|
+
setup(build) {
|
|
191
|
+
build.onResolve({ filter: SDK_IMPORT_FILTER }, (args) => ({
|
|
192
|
+
path: args.path,
|
|
193
|
+
namespace: "sdk-redirect"
|
|
194
|
+
}));
|
|
195
|
+
build.onLoad({
|
|
196
|
+
filter: /.*/,
|
|
197
|
+
namespace: "sdk-redirect"
|
|
198
|
+
}, () => ({
|
|
199
|
+
contents: `
|
|
200
|
+
var __proxy = (typeof window !== 'undefined' && window.__sdk)
|
|
201
|
+
? window.__sdk
|
|
202
|
+
: new Proxy({}, {
|
|
203
|
+
get: function(_t, p) {
|
|
204
|
+
throw new Error('window.__sdk is not installed — run in a dog-food build. Missing: ' + String(p));
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
module.exports = __proxy;
|
|
208
|
+
`,
|
|
209
|
+
loader: "js"
|
|
210
|
+
}));
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Bundles `absPath` into a single IIFE string suitable for `Runtime.evaluate`.
|
|
216
|
+
*
|
|
217
|
+
* The IIFE installs `window.__testBundle` (or the custom `globalName`) with
|
|
218
|
+
* `runTestModule` as the callable entry point.
|
|
219
|
+
*
|
|
220
|
+
* @param absPath - Absolute path to the user test file.
|
|
221
|
+
* @param opts - Optional bundling overrides.
|
|
222
|
+
*/
|
|
223
|
+
async function bundleTestFile(absPath, opts) {
|
|
224
|
+
const globalName = opts?.globalName ?? "__testBundle";
|
|
225
|
+
const extraExternals = opts?.extraExternals ?? [];
|
|
226
|
+
const result = await (await import("esbuild")).build({
|
|
227
|
+
entryPoints: [absPath],
|
|
228
|
+
bundle: true,
|
|
229
|
+
format: "iife",
|
|
230
|
+
globalName,
|
|
231
|
+
platform: "browser",
|
|
232
|
+
target: "es2022",
|
|
233
|
+
write: false,
|
|
234
|
+
plugins: [sdkRedirectPlugin()],
|
|
235
|
+
external: extraExternals,
|
|
236
|
+
treeShaking: true
|
|
237
|
+
});
|
|
238
|
+
const warnings = result.warnings.map((w) => `${path.relative(process.cwd(), w.location?.file ?? "")}:${w.location?.line ?? "?"}: ${w.text}`);
|
|
239
|
+
const outputFile = result.outputFiles?.[0];
|
|
240
|
+
if (!outputFile) throw new Error("bundleTestFile: esbuild produced no output — check entryPoints");
|
|
241
|
+
return {
|
|
242
|
+
code: outputFile.text,
|
|
243
|
+
warnings
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
//#endregion
|
|
247
|
+
//#region src/test-runner/rpc.ts
|
|
248
|
+
/** Maximum milliseconds to wait for a single evaluate round-trip. */
|
|
249
|
+
const DEFAULT_TIMEOUT_MS = 3e4;
|
|
250
|
+
/**
|
|
251
|
+
* Wraps bundle code in a self-executing IIFE that:
|
|
252
|
+
* 1. Evaluates the bundle (registering describe/it/test).
|
|
253
|
+
* 2. Calls `__testBundle.runTestModule(...)` — the entry the runtime exports.
|
|
254
|
+
* 3. Returns a JSON-serialised `RunReport` string.
|
|
255
|
+
*
|
|
256
|
+
* The double-serialisation (RunReport → JSON string → returnByValue string)
|
|
257
|
+
* is intentional: CDP `returnByValue` reliably transports strings; deeply
|
|
258
|
+
* nested objects can lose fidelity across the Chii relay.
|
|
259
|
+
*
|
|
260
|
+
* SECRET-HANDLING: `bundleCode` MUST NOT be logged by callers.
|
|
261
|
+
*/
|
|
262
|
+
function buildRunTestsExpression(bundleCode) {
|
|
263
|
+
return `(async () => { try { ${bundleCode} } catch(e) { return JSON.stringify({ok:false,error:'bundle-eval: ' + String(e && e.message || e)}); } if (typeof globalThis.__testBundle !== 'object' || typeof globalThis.__testBundle.runTestModule !== 'function') { return JSON.stringify({ok:false,error:'bundle-missing-export: __testBundle.runTestModule is not a function'}); } try { const report = await globalThis.__testBundle.runTestModule(); return JSON.stringify({ok:true,value:report}); } catch(e) { return JSON.stringify({ok:false,error:'test-run: ' + String(e && e.message || e)}); }})()`;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Parses the raw CDP `returnByValue` result from a `buildRunTestsExpression`
|
|
267
|
+
* evaluate call into a typed `RpcRunResult`.
|
|
268
|
+
*
|
|
269
|
+
* Throws only on parse failure — an `ok:false` envelope is a normal result.
|
|
270
|
+
*
|
|
271
|
+
* SECRET-HANDLING: `rawValue` is not included in error messages.
|
|
272
|
+
*/
|
|
273
|
+
function parseRunTestsResult(rawValue) {
|
|
274
|
+
if (typeof rawValue !== "string") throw new Error(`rpc.parseRunTestsResult: unexpected return type "${typeof rawValue}" — expected JSON string`);
|
|
275
|
+
let parsed;
|
|
276
|
+
try {
|
|
277
|
+
parsed = JSON.parse(rawValue);
|
|
278
|
+
} catch {
|
|
279
|
+
throw new Error("rpc.parseRunTestsResult: bridge returned non-JSON string");
|
|
280
|
+
}
|
|
281
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error("rpc.parseRunTestsResult: parsed result is not an object");
|
|
282
|
+
const obj = parsed;
|
|
283
|
+
if (obj.ok === true) return {
|
|
284
|
+
ok: true,
|
|
285
|
+
report: obj.value
|
|
286
|
+
};
|
|
287
|
+
if (obj.ok === false) return {
|
|
288
|
+
ok: false,
|
|
289
|
+
error: typeof obj.error === "string" ? obj.error : String(obj.error)
|
|
290
|
+
};
|
|
291
|
+
throw new Error("rpc.parseRunTestsResult: result missing \"ok\" field");
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Injects `bundleCode` into the attached page and awaits test execution.
|
|
295
|
+
*
|
|
296
|
+
* Uses `Runtime.evaluate` with `awaitPromise: true` to wait for the
|
|
297
|
+
* async IIFE to settle. The 30-second CDP command timeout covers even
|
|
298
|
+
* long-running test suites; split into smaller files if you hit it.
|
|
299
|
+
*
|
|
300
|
+
* @param connection - Active CDP connection (relay or local).
|
|
301
|
+
* @param bundleCode - IIFE bundle string from `bundleTestFile`.
|
|
302
|
+
* @param timeoutMs - Override the default 30 s timeout.
|
|
303
|
+
*
|
|
304
|
+
* SECRET-HANDLING: `bundleCode` and the raw CDP result value are never logged.
|
|
305
|
+
*/
|
|
306
|
+
async function injectAndRunBundle(connection, bundleCode, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
307
|
+
const expression = buildRunTestsExpression(bundleCode);
|
|
308
|
+
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(/* @__PURE__ */ new Error(`rpc: evaluate timed out after ${timeoutMs}ms`)), timeoutMs));
|
|
309
|
+
const evalPromise = connection.send("Runtime.evaluate", {
|
|
310
|
+
expression,
|
|
311
|
+
returnByValue: true,
|
|
312
|
+
awaitPromise: true
|
|
313
|
+
});
|
|
314
|
+
const cdpResult = await Promise.race([evalPromise, timeoutPromise]);
|
|
315
|
+
if (cdpResult.exceptionDetails) {
|
|
316
|
+
const msg = cdpResult.exceptionDetails.exception?.description ?? cdpResult.exceptionDetails.text ?? "Runtime.evaluate threw an exception";
|
|
317
|
+
throw new Error(`rpc.injectAndRunBundle: ${msg}`);
|
|
318
|
+
}
|
|
319
|
+
return parseRunTestsResult(cdpResult.result.value);
|
|
320
|
+
}
|
|
321
|
+
//#endregion
|
|
322
|
+
//#region src/test-runner/relay-worker.ts
|
|
323
|
+
/**
|
|
324
|
+
* Runs all `files` sequentially over the given CDP `connection`.
|
|
325
|
+
*
|
|
326
|
+
* For each file:
|
|
327
|
+
* 1. Bundle with esbuild (includes SDK shim + runtime).
|
|
328
|
+
* 2. Inject into the attached page via `Runtime.evaluate`.
|
|
329
|
+
* 3. Await the `RunReport` JSON response.
|
|
330
|
+
* 4. Accumulate results.
|
|
331
|
+
*
|
|
332
|
+
* Returns a `RelayRunReport` with per-file results and flattened totals.
|
|
333
|
+
*
|
|
334
|
+
* This function does NOT open or manage the relay connection — the caller
|
|
335
|
+
* is responsible for attaching and closing it.
|
|
336
|
+
*
|
|
337
|
+
* TODO (#645): implement the Vitest `PoolRunnerInitializer` interface here
|
|
338
|
+
* so that `runTestFilesOverRelay` can be used as a Vitest pool entry.
|
|
339
|
+
*
|
|
340
|
+
* @param connection - Active CDP connection (relay or local kind).
|
|
341
|
+
* @param files - Absolute paths to test files, run in order.
|
|
342
|
+
* @param opts - Optional per-run overrides.
|
|
343
|
+
*/
|
|
344
|
+
async function runTestFilesOverRelay(connection, files, opts) {
|
|
345
|
+
const wallStart = Date.now();
|
|
346
|
+
const startedAt = new Date(wallStart).toISOString();
|
|
347
|
+
const fileResults = [];
|
|
348
|
+
for (const file of files) {
|
|
349
|
+
let fileEntry;
|
|
350
|
+
try {
|
|
351
|
+
const { code } = await bundleTestFile(file, opts?.bundleOptions);
|
|
352
|
+
const rpcResult = await injectAndRunBundle(connection, code, opts?.timeoutMs);
|
|
353
|
+
if (rpcResult.ok) fileEntry = {
|
|
354
|
+
file,
|
|
355
|
+
result: rpcResult.report
|
|
356
|
+
};
|
|
357
|
+
else fileEntry = {
|
|
358
|
+
file,
|
|
359
|
+
result: { error: rpcResult.error }
|
|
360
|
+
};
|
|
361
|
+
} catch (e) {
|
|
362
|
+
fileEntry = {
|
|
363
|
+
file,
|
|
364
|
+
result: { error: e instanceof Error ? e.message : String(e) }
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
fileResults.push(fileEntry);
|
|
368
|
+
}
|
|
369
|
+
const totals = fileResults.reduce((acc, { result }) => {
|
|
370
|
+
if ("error" in result) {
|
|
371
|
+
acc.failed += 1;
|
|
372
|
+
acc.total += 1;
|
|
373
|
+
} else {
|
|
374
|
+
acc.passed += result.passed;
|
|
375
|
+
acc.failed += result.failed;
|
|
376
|
+
acc.skipped += result.skipped;
|
|
377
|
+
acc.total += result.passed + result.failed + result.skipped;
|
|
378
|
+
}
|
|
379
|
+
return acc;
|
|
380
|
+
}, {
|
|
381
|
+
passed: 0,
|
|
382
|
+
failed: 0,
|
|
383
|
+
skipped: 0,
|
|
384
|
+
total: 0
|
|
385
|
+
});
|
|
386
|
+
return {
|
|
387
|
+
startedAt,
|
|
388
|
+
duration: Date.now() - wallStart,
|
|
389
|
+
files: fileResults,
|
|
390
|
+
totals
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
//#endregion
|
|
394
|
+
//#region src/test-runner/cli.ts
|
|
395
|
+
/**
|
|
396
|
+
* `devtools-test` CLI — MVP skeleton.
|
|
397
|
+
*
|
|
398
|
+
* Parses argv, prints usage, and delegates to `runTestFilesOverRelay` when
|
|
399
|
+
* a live CDP connection is provided. The relay connection wiring
|
|
400
|
+
* (attach → run → detach) is tracked in issue #645 / #646.
|
|
401
|
+
*
|
|
402
|
+
* MVP contract: `--help` works, `runWithConnection` is a testable pure
|
|
403
|
+
* function, and the binary entry exists in package.json.
|
|
404
|
+
*
|
|
405
|
+
* NOTE: no shebang in this source file — the tsdown entry's `banner` option
|
|
406
|
+
* injects `#!/usr/bin/env node` into the compiled output (same pattern as
|
|
407
|
+
* `src/mcp/cli.ts`).
|
|
408
|
+
*/
|
|
409
|
+
const USAGE = `
|
|
410
|
+
devtools-test — run mini-app tests on a real device WebView over the CDP relay
|
|
411
|
+
|
|
412
|
+
USAGE
|
|
413
|
+
devtools-test <glob> [<glob> ...] [options]
|
|
414
|
+
|
|
415
|
+
OPTIONS
|
|
416
|
+
--timeout <ms> Per-file evaluate timeout in ms (default: 30000)
|
|
417
|
+
--help, -h Show this help message
|
|
418
|
+
|
|
419
|
+
DESCRIPTION
|
|
420
|
+
Bundles each matched test file with esbuild (SDK imports redirected to
|
|
421
|
+
window.__sdk), injects the bundle into the attached WebView via
|
|
422
|
+
Runtime.evaluate, and returns a RunReport.
|
|
423
|
+
|
|
424
|
+
A live CDP relay connection must be active before running tests.
|
|
425
|
+
Use \`/ait debug\` (devtools-mcp) to attach and then call this CLI from
|
|
426
|
+
the same process context.
|
|
427
|
+
|
|
428
|
+
Full Vitest pool integration and the \`run_tests\` MCP tool are tracked in
|
|
429
|
+
issues #645 and #646 respectively. This MVP provides the transport layer.
|
|
430
|
+
|
|
431
|
+
EXAMPLE
|
|
432
|
+
devtools-test 'src/**/*.phone.test.ts' --timeout 60000
|
|
433
|
+
|
|
434
|
+
`.trimStart();
|
|
435
|
+
/**
|
|
436
|
+
* Runs `files` over `connection` and returns the aggregate report.
|
|
437
|
+
* This pure function is the testable core of the CLI; it is separate from
|
|
438
|
+
* `main()` so tests can call it without spawning a subprocess.
|
|
439
|
+
*
|
|
440
|
+
* TODO (#645): add real relay attach/detach lifecycle here (connect via
|
|
441
|
+
* Chii relay URL, call enableDomains, run, then close).
|
|
442
|
+
*/
|
|
443
|
+
async function runWithConnection(connection, files, opts) {
|
|
444
|
+
const report = await runTestFilesOverRelay(connection, files, opts);
|
|
445
|
+
if (opts?.printSummary) {
|
|
446
|
+
const { totals } = report;
|
|
447
|
+
process.stdout.write(`\ndevtools-test: ${totals.passed} passed, ${totals.failed} failed, ${totals.skipped} skipped (${report.duration}ms)\n`);
|
|
448
|
+
}
|
|
449
|
+
return report;
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* CLI entry point.
|
|
453
|
+
*
|
|
454
|
+
* MVP: prints usage and a "relay attach required" notice. Real relay wiring
|
|
455
|
+
* (resolve CDP URL, attach, run, close) is tracked in issues #645 / #646.
|
|
456
|
+
*/
|
|
457
|
+
async function main$1(argv = process.argv.slice(2)) {
|
|
458
|
+
let parsed;
|
|
459
|
+
try {
|
|
460
|
+
parsed = parseArgs({
|
|
461
|
+
args: argv,
|
|
462
|
+
options: {
|
|
463
|
+
help: {
|
|
464
|
+
type: "boolean",
|
|
465
|
+
short: "h"
|
|
466
|
+
},
|
|
467
|
+
timeout: { type: "string" }
|
|
468
|
+
},
|
|
469
|
+
allowPositionals: true
|
|
470
|
+
});
|
|
471
|
+
} catch (e) {
|
|
472
|
+
process.stderr.write(`devtools-test: ${e instanceof Error ? e.message : String(e)}\n`);
|
|
473
|
+
process.exitCode = 1;
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
if (parsed.values.help || argv.length === 0) {
|
|
477
|
+
process.stdout.write(USAGE);
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
const files = await discoverTestFiles(parsed.positionals, process.cwd());
|
|
481
|
+
if (files.length === 0) {
|
|
482
|
+
process.stderr.write(`devtools-test: no test files matched ${parsed.positionals.join(", ")}\n`);
|
|
483
|
+
process.exitCode = 1;
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
process.stderr.write(`devtools-test: matched ${files.length} test file(s), but direct CLI relay attach is not yet wired.\n Use the devtools-mcp server (\`devtools-mcp\`) to start a debug session,\n then the \`run_tests\` MCP tool to run these files against the attached page.\n Direct CLI relay wiring is tracked in issue #645.\n`);
|
|
487
|
+
process.exitCode = 1;
|
|
488
|
+
}
|
|
489
|
+
if (import.meta.url === new URL(process.argv[1], "file://").href) main$1().catch((e) => {
|
|
490
|
+
process.stderr.write(`devtools-test: unexpected error: ${e instanceof Error ? e.message : String(e)}\n`);
|
|
491
|
+
process.exitCode = 1;
|
|
492
|
+
});
|
|
493
|
+
//#endregion
|
|
122
494
|
//#region src/mcp/ait-chii-source.ts
|
|
123
495
|
function isObject$4(value) {
|
|
124
496
|
return typeof value === "object" && value !== null;
|
|
@@ -213,7 +585,11 @@ const ALLOWED_KEYS = new Set([
|
|
|
213
585
|
"errorKind",
|
|
214
586
|
"reason",
|
|
215
587
|
"prevTargetId",
|
|
216
|
-
"mode"
|
|
588
|
+
"mode",
|
|
589
|
+
"fileCount",
|
|
590
|
+
"passed",
|
|
591
|
+
"failed",
|
|
592
|
+
"skipped"
|
|
217
593
|
]);
|
|
218
594
|
/**
|
|
219
595
|
* Patterns that match secret values.
|
|
@@ -4033,6 +4409,34 @@ const DEBUG_TOOL_DEFINITIONS = [
|
|
|
4033
4409
|
required: []
|
|
4034
4410
|
},
|
|
4035
4411
|
availableIn: "both"
|
|
4412
|
+
},
|
|
4413
|
+
{
|
|
4414
|
+
name: "run_tests",
|
|
4415
|
+
description: "Runs mini-app test files on the attached page over CDP (Runtime.evaluate). Each matched file is bundled with esbuild (SDK imports redirected to the live mock/SDK), injected into the attached WebView, and executed; returns per-file results plus flattened totals (passed/failed/skipped/total). Requires an attached page — call list_pages first to confirm one is attached. Files run SEQUENTIALLY (single-attach model: the relay/local target serves one page), and one run_tests call runs at a time (a concurrent call is rejected). Test verification (assert/snapshot) is delegated to the in-page Vitest runtime; this tool is the transport + report. The per-file results array is the progress record — on partial failure you see exactly which files passed/failed/timed-out. In a relay-live session this is a state-mutating injection and is blocked unless confirm=true (ignored in mock/local/relay-dev/relay-mobile). debug-mode only — dev-mode (--mode=dev) has no CDP. Tier C (both mock/local and relay). Use the test-runner CLI (devtools-test) for the same run outside MCP.",
|
|
4416
|
+
inputSchema: {
|
|
4417
|
+
type: "object",
|
|
4418
|
+
properties: {
|
|
4419
|
+
files: {
|
|
4420
|
+
type: "array",
|
|
4421
|
+
items: { type: "string" },
|
|
4422
|
+
description: "Glob patterns or file paths to run (e.g. [\"src/**/*.phone.test.ts\"]). Resolved relative to projectRoot when given, else the daemon cwd. Required, non-empty."
|
|
4423
|
+
},
|
|
4424
|
+
projectRoot: {
|
|
4425
|
+
type: "string",
|
|
4426
|
+
description: "Absolute path to the mini-app project root used as the glob base. Pass this because the daemon's cwd is fixed at launch. Optional."
|
|
4427
|
+
},
|
|
4428
|
+
timeout_ms: {
|
|
4429
|
+
type: "number",
|
|
4430
|
+
description: "Per-file evaluate timeout in ms (default 30000, range 1000–600000). Out-of-range/invalid values fall back to the default."
|
|
4431
|
+
},
|
|
4432
|
+
confirm: {
|
|
4433
|
+
type: "boolean",
|
|
4434
|
+
description: "Required (true) to run in a relay-live session — test injection mutates page state. Ignored in mock/local/relay-dev/relay-mobile sessions."
|
|
4435
|
+
}
|
|
4436
|
+
},
|
|
4437
|
+
required: ["files"]
|
|
4438
|
+
},
|
|
4439
|
+
availableIn: "both"
|
|
4036
4440
|
}
|
|
4037
4441
|
];
|
|
4038
4442
|
const DEBUG_TOOL_NAMES = new Set(DEBUG_TOOL_DEFINITIONS.map((t) => t.name));
|
|
@@ -4855,7 +5259,7 @@ async function readMcpSdkVersion() {
|
|
|
4855
5259
|
* some test environments that skip the build step).
|
|
4856
5260
|
*/
|
|
4857
5261
|
function readDevtoolsVersion() {
|
|
4858
|
-
return "0.1.
|
|
5262
|
+
return "0.1.108";
|
|
4859
5263
|
}
|
|
4860
5264
|
/**
|
|
4861
5265
|
* Derives the next recommended action from a completed diagnostics snapshot.
|
|
@@ -5385,6 +5789,16 @@ function isRelayMode(mode) {
|
|
|
5385
5789
|
return mode === "relay-sandbox" || mode === "relay-staging" || mode === "relay-live";
|
|
5386
5790
|
}
|
|
5387
5791
|
/**
|
|
5792
|
+
* Single-attach guard for `run_tests` (#646). Two concurrent runs injecting
|
|
5793
|
+
* into the same single-attach page would interleave `Runtime.evaluate` and
|
|
5794
|
+
* corrupt each other's `globalThis.__testBundle`. The model is "reject the
|
|
5795
|
+
* second", not "queue" — a module-level flag is process-wide, which matches the
|
|
5796
|
+
* single physical attached page (only one target is live at a time). The
|
|
5797
|
+
* entry-time `conn` snapshot ensures a run finishes on the connection it started
|
|
5798
|
+
* on even if `router.active` flips mid-run.
|
|
5799
|
+
*/
|
|
5800
|
+
let runTestsInFlight = false;
|
|
5801
|
+
/**
|
|
5388
5802
|
* Waits for the first target matching `filterFn` to attach, using the
|
|
5389
5803
|
* event-driven `waitForFirstTarget()` when the connection supports it
|
|
5390
5804
|
* (interface-optional member, present on `ChiiCdpConnection`), or falling
|
|
@@ -5447,7 +5861,7 @@ function createDebugServer(deps) {
|
|
|
5447
5861
|
const collector = collectorDep ?? new InMemoryDiagnosticsCollector();
|
|
5448
5862
|
const server = new Server({
|
|
5449
5863
|
name: "ait-debug",
|
|
5450
|
-
version: "0.1.
|
|
5864
|
+
version: "0.1.108"
|
|
5451
5865
|
}, { capabilities: { tools: { listChanged: true } } });
|
|
5452
5866
|
server.setRequestHandler(ListToolsRequestSchema, () => {
|
|
5453
5867
|
const conn = router.active;
|
|
@@ -5535,7 +5949,7 @@ function createDebugServer(deps) {
|
|
|
5535
5949
|
const buildProjectRoot = typeof rawBuildProjectRoot === "string" ? rawBuildProjectRoot : void 0;
|
|
5536
5950
|
let tunnelHttpUrl = process.env.AIT_TUNNEL_BASE_URL?.trim() ?? "";
|
|
5537
5951
|
if (tunnelHttpUrl === "" && buildProjectRoot !== void 0) {
|
|
5538
|
-
const { readRelayUrls } = await import("../relay-url-store-
|
|
5952
|
+
const { readRelayUrls } = await import("../relay-url-store-CwKT7i04.js");
|
|
5539
5953
|
tunnelHttpUrl = (await readRelayUrls({ projectRoot: buildProjectRoot }))?.tunnelBaseUrl ?? "";
|
|
5540
5954
|
}
|
|
5541
5955
|
if (tunnelHttpUrl === "") return mcpError("build_attach_url(mobile): AIT_TUNNEL_BASE_URL이 설정되지 않았습니다. dev 서버가 tunnel:{cdp:true}로 기동 중이면 .ait_urls 파일이 자동 생성돼 있어야 합니다. 자동 발견이 되지 않을 경우 앱 HTTP 터널 URL을 AIT_TUNNEL_BASE_URL 환경변수로 직접 전달하세요.");
|
|
@@ -5950,6 +6364,35 @@ ${browserResult.httpUrl}\n또는 PNG로 받기: ${browserResult.pngUrl}` + stder
|
|
|
5950
6364
|
if (!sdkResult.ok && typeof sdkResult.error === "string" && sdkResult.error.startsWith("sdk-absent:")) return sdkAbsentError("call_sdk", conn.kind === "local");
|
|
5951
6365
|
return envelopeResult$1(sdkResult, name, env, conn.listTargets().length > 0);
|
|
5952
6366
|
}
|
|
6367
|
+
case "run_tests": {
|
|
6368
|
+
const rawFiles = request.params.arguments?.files;
|
|
6369
|
+
if (!Array.isArray(rawFiles) || rawFiles.length === 0) return mcpError("run_tests: files 인자가 비어 있습니다. 실행할 테스트 파일 glob을 배열로 전달하세요.");
|
|
6370
|
+
const patterns = rawFiles.filter((p) => typeof p === "string" && p !== "");
|
|
6371
|
+
if (patterns.length === 0) return mcpError("run_tests: files 인자에 유효한 문자열 glob이 없습니다.");
|
|
6372
|
+
const rawRoot = request.params.arguments?.projectRoot;
|
|
6373
|
+
const projectRoot = typeof rawRoot === "string" ? rawRoot : process.cwd();
|
|
6374
|
+
const rawTimeout = request.params.arguments?.timeout_ms;
|
|
6375
|
+
const timeoutMs = typeof rawTimeout === "number" && rawTimeout >= 1e3 && rawTimeout <= 6e5 ? rawTimeout : void 0;
|
|
6376
|
+
if (conn.kind === "relay" && getLiveIntent() && request.params.arguments?.confirm !== true) return liveGuardError("run_tests");
|
|
6377
|
+
if (runTestsInFlight) return mcpError("run_tests: 이미 다른 테스트 실행이 진행 중입니다 (single-attach 모델: 페이지는 한 번에 하나의 실행만 처리). 완료 후 다시 시도하세요.");
|
|
6378
|
+
runTestsInFlight = true;
|
|
6379
|
+
try {
|
|
6380
|
+
const files = await discoverTestFiles(patterns, projectRoot);
|
|
6381
|
+
if (files.length === 0) return mcpError(`run_tests: 매칭된 테스트 파일이 없습니다 (patterns: ${patterns.join(", ")}).`);
|
|
6382
|
+
if (conn.listTargets().length === 0) return pageMissingError("run_tests");
|
|
6383
|
+
logInfo("run_tests.start", { fileCount: files.length });
|
|
6384
|
+
const report = await runWithConnection(conn, files, { timeoutMs });
|
|
6385
|
+
logInfo("run_tests.done", {
|
|
6386
|
+
passed: report.totals.passed,
|
|
6387
|
+
failed: report.totals.failed,
|
|
6388
|
+
skipped: report.totals.skipped
|
|
6389
|
+
});
|
|
6390
|
+
const runAttached = conn.listTargets().length > 0;
|
|
6391
|
+
return envelopeResult$1(toRunTestsResult(report), name, env, runAttached);
|
|
6392
|
+
} finally {
|
|
6393
|
+
runTestsInFlight = false;
|
|
6394
|
+
}
|
|
6395
|
+
}
|
|
5953
6396
|
default: return unknownTool(name);
|
|
5954
6397
|
}
|
|
5955
6398
|
} catch (err) {
|
|
@@ -6035,6 +6478,30 @@ function envelopeResult$1(value, tool, env, attached) {
|
|
|
6035
6478
|
text: JSON.stringify(wrapped, null, 2)
|
|
6036
6479
|
}] };
|
|
6037
6480
|
}
|
|
6481
|
+
/**
|
|
6482
|
+
* Maps a {@link RelayRunReport} to a flat, agent-friendly object for the
|
|
6483
|
+
* `run_tests` tool result. SECRET-HANDLING: a RelayRunReport carries only
|
|
6484
|
+
* startedAt/duration/totals and per-file `{file, result}` — file paths are
|
|
6485
|
+
* surfaced (allowed), relay wss/TOTP URLs never appear in it. No stripping
|
|
6486
|
+
* needed; this only reshapes for readability.
|
|
6487
|
+
*/
|
|
6488
|
+
function toRunTestsResult(report) {
|
|
6489
|
+
return {
|
|
6490
|
+
startedAt: report.startedAt,
|
|
6491
|
+
duration: report.duration,
|
|
6492
|
+
totals: report.totals,
|
|
6493
|
+
files: report.files.map((f) => "error" in f.result ? {
|
|
6494
|
+
file: f.file,
|
|
6495
|
+
error: f.result.error
|
|
6496
|
+
} : {
|
|
6497
|
+
file: f.file,
|
|
6498
|
+
passed: f.result.passed,
|
|
6499
|
+
failed: f.result.failed,
|
|
6500
|
+
skipped: f.result.skipped,
|
|
6501
|
+
tests: f.result.tests
|
|
6502
|
+
})
|
|
6503
|
+
};
|
|
6504
|
+
}
|
|
6038
6505
|
function unknownTool(name) {
|
|
6039
6506
|
return mcpError(`알 수 없는 tool: ${name}`);
|
|
6040
6507
|
}
|
|
@@ -6289,7 +6756,7 @@ async function readRelayLocalUrl(env = process.env, projectRoot) {
|
|
|
6289
6756
|
const envValue = (env.AIT_RELAY_LOCAL_URL ?? "").trim();
|
|
6290
6757
|
if (envValue !== "") return envValue;
|
|
6291
6758
|
if (projectRoot !== void 0) try {
|
|
6292
|
-
const { readRelayUrls } = await import("../relay-url-store-
|
|
6759
|
+
const { readRelayUrls } = await import("../relay-url-store-CwKT7i04.js");
|
|
6293
6760
|
const stored = await readRelayUrls({ projectRoot });
|
|
6294
6761
|
if (stored?.relayLocalUrl) return stored.relayLocalUrl;
|
|
6295
6762
|
} catch {}
|
|
@@ -6343,7 +6810,7 @@ async function readMobileRelayBaseUrl(env = process.env, projectRoot) {
|
|
|
6343
6810
|
const envValue = typeof raw === "string" ? raw.trim() : "";
|
|
6344
6811
|
if (envValue !== "") return envValue;
|
|
6345
6812
|
if (projectRoot !== void 0) {
|
|
6346
|
-
const { readRelayUrls } = await import("../relay-url-store-
|
|
6813
|
+
const { readRelayUrls } = await import("../relay-url-store-CwKT7i04.js");
|
|
6347
6814
|
const stored = await readRelayUrls({ projectRoot });
|
|
6348
6815
|
if (stored?.relayBaseUrl !== void 0) return stored.relayBaseUrl;
|
|
6349
6816
|
}
|
|
@@ -7456,6 +7923,34 @@ const DEV_TOOL_DEFINITIONS = [
|
|
|
7456
7923
|
required: []
|
|
7457
7924
|
},
|
|
7458
7925
|
availableIn: "both"
|
|
7926
|
+
},
|
|
7927
|
+
{
|
|
7928
|
+
name: "run_tests",
|
|
7929
|
+
description: "Runs mini-app test files on the attached page over CDP (Runtime.evaluate). NOT available in dev-mode (no CDP connection). Switch to `--mode=local` or `--mode=debug`.",
|
|
7930
|
+
inputSchema: {
|
|
7931
|
+
type: "object",
|
|
7932
|
+
properties: {
|
|
7933
|
+
files: {
|
|
7934
|
+
type: "array",
|
|
7935
|
+
items: { type: "string" },
|
|
7936
|
+
description: "Glob patterns or file paths to run."
|
|
7937
|
+
},
|
|
7938
|
+
projectRoot: {
|
|
7939
|
+
type: "string",
|
|
7940
|
+
description: "Glob base directory."
|
|
7941
|
+
},
|
|
7942
|
+
timeout_ms: {
|
|
7943
|
+
type: "number",
|
|
7944
|
+
description: "Per-file evaluate timeout in ms."
|
|
7945
|
+
},
|
|
7946
|
+
confirm: {
|
|
7947
|
+
type: "boolean",
|
|
7948
|
+
description: "Required in relay-live sessions."
|
|
7949
|
+
}
|
|
7950
|
+
},
|
|
7951
|
+
required: ["files"]
|
|
7952
|
+
},
|
|
7953
|
+
availableIn: "both"
|
|
7459
7954
|
}
|
|
7460
7955
|
];
|
|
7461
7956
|
/** All tool names served in dev-mode (including tier-filter stubs). */
|
|
@@ -7468,7 +7963,8 @@ const CDP_ONLY_TOOL_NAMES = new Set([
|
|
|
7468
7963
|
"take_snapshot",
|
|
7469
7964
|
"list_console_messages",
|
|
7470
7965
|
"list_network_requests",
|
|
7471
|
-
"list_exceptions"
|
|
7966
|
+
"list_exceptions",
|
|
7967
|
+
"run_tests"
|
|
7472
7968
|
]);
|
|
7473
7969
|
/**
|
|
7474
7970
|
* Tier B tools — relay-only per RFC #277.
|
|
@@ -7585,7 +8081,7 @@ function createDevServer(deps = {}) {
|
|
|
7585
8081
|
const aitSource = deps.aitSource ?? new HttpAitSource({ stateEndpoint });
|
|
7586
8082
|
const server = new Server({
|
|
7587
8083
|
name: "ait-devtools",
|
|
7588
|
-
version: "0.1.
|
|
8084
|
+
version: "0.1.108"
|
|
7589
8085
|
}, { capabilities: { tools: {} } });
|
|
7590
8086
|
server.setRequestHandler(ListToolsRequestSchema, () => ({ tools: DEV_TOOL_DEFINITIONS.map((tool) => ({ ...tool })) }));
|
|
7591
8087
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|