@buoy-gg/mcp 7.0.13 → 7.0.15
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/{chunk-5OLMOVFE.mjs → chunk-I3Z3IWDH.mjs} +224 -2
- package/dist/cli.js +224 -2
- package/dist/cli.mjs +1 -1
- package/dist/index.js +224 -2
- package/dist/index.mjs +1 -1
- package/package.json +2 -2
|
@@ -301,7 +301,7 @@ async function waitForBroker(url, attempts = 20) {
|
|
|
301
301
|
var delay = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
302
302
|
|
|
303
303
|
// src/version.ts
|
|
304
|
-
var VERSION = "7.0.
|
|
304
|
+
var VERSION = "7.0.15" ? "7.0.15" : "0.0.0-dev";
|
|
305
305
|
var PACKAGE_NAME = "@buoy-gg/mcp";
|
|
306
306
|
|
|
307
307
|
// src/update/checkForUpdate.ts
|
|
@@ -2033,6 +2033,10 @@ var NETWORK_TOOL_ID = "network";
|
|
|
2033
2033
|
var text10 = (s) => ({ content: [{ type: "text", text: s }] });
|
|
2034
2034
|
var fail12 = (message) => ({ isError: true, content: [{ type: "text", text: message }] });
|
|
2035
2035
|
var errMsg12 = (e) => e instanceof Error ? e.message : String(e);
|
|
2036
|
+
var masterNote = (state) => {
|
|
2037
|
+
if (state?.enabled !== false) return "";
|
|
2038
|
+
return state.autoPaused ? ' \u2014 AUTO-PAUSED after the app failed to settle on launch with overrides active; nothing is firing. Re-arm with network_override({action:"enable"}).' : " \u2014 MASTER SWITCH IS OFF, nothing is firing";
|
|
2039
|
+
};
|
|
2036
2040
|
function parseSnapshot(payload) {
|
|
2037
2041
|
if (Array.isArray(payload)) return { events: payload, saved: [] };
|
|
2038
2042
|
if (payload && typeof payload === "object") {
|
|
@@ -2246,6 +2250,222 @@ _\u{1F4CC} pinned \xB7 \u{1F516} saved. Act on one with its id, e.g. network_act
|
|
|
2246
2250
|
}
|
|
2247
2251
|
}
|
|
2248
2252
|
);
|
|
2253
|
+
server.registerTool(
|
|
2254
|
+
"network_override",
|
|
2255
|
+
{
|
|
2256
|
+
description: "Force matching HTTP requests to return a chosen status/body, to fail outright, or to arrive late \u2014 without touching the backend or the app's code. This is how you drive an app into a state it won't reach on its own: force a 500 and see whether the error UI appears, return [] and check the empty state, simulate offline or a slow connection. Rules run INSIDE the app and survive an app reload, so they also cover the boot path. After adding one, trigger the request (navigate/tap_element) and read the result with get_network_requests or describe_screen. ALWAYS remove your rules when done \u2014 a forgotten rule looks exactly like a real bug. Requires Buoy Pro.",
|
|
2257
|
+
inputSchema: {
|
|
2258
|
+
action: z12.enum(["list", "add", "remove", "clear", "enable", "disable", "debug"]).describe(
|
|
2259
|
+
"add creates a rule (or replaces one when `id` is given). remove drops one by `id`. clear drops all. enable/disable flip ONE rule when `id` is given, otherwise the master switch \u2014 rules are kept either way. list shows every rule with its hit counts; pass `id` to dump one rule in full, body included. debug reports why a rule is or isn't firing."
|
|
2260
|
+
),
|
|
2261
|
+
urlPattern: z12.string().optional().describe(
|
|
2262
|
+
"For `add`: glob matched against the WHOLE request URL, query string included. `*` is the only wildcard, e.g. '*pokeapi.co*' or 'https://api.example.com/v1/users*'. Careful with an exact URL copied from get_network_requests \u2014 if the client cache-busts or pages, the next call won't match it and the rule sits at 0 hits. Prefer `fromRequestId`, which handles that for you."
|
|
2263
|
+
),
|
|
2264
|
+
fromRequestId: z12.string().optional().describe(
|
|
2265
|
+
"For `add`: build the rule from a captured request id (from get_network_requests) \u2014 the same 'Override this request' the developer gets in the UI. Seeds the pattern (query string replaced with `*` so it keeps matching), the method, the status, and the REAL response body, so you can change two fields of a payload you just saw instead of authoring one from scratch. Anything you pass explicitly overrides the prefill."
|
|
2266
|
+
),
|
|
2267
|
+
methods: z12.array(z12.string()).optional().describe("For `add`: restrict to these HTTP methods. Omit for any."),
|
|
2268
|
+
kind: z12.enum(["respond", "fail", "delay"]).optional().describe(
|
|
2269
|
+
"respond = return your status/body, never hitting the network (default). fail = a transport failure, as if the device were offline. delay = let the real request run, just late."
|
|
2270
|
+
),
|
|
2271
|
+
status: z12.number().optional().describe(
|
|
2272
|
+
"For kind 'respond': HTTP status, 200\u2013599 (default 200). Outside that range fetch itself throws, so use kind 'fail' for connection errors rather than status 0."
|
|
2273
|
+
),
|
|
2274
|
+
body: z12.union([z12.string(), z12.record(z12.unknown()), z12.array(z12.unknown())]).optional().describe(
|
|
2275
|
+
"For kind 'respond': the response body. Pass JSON directly (an object or array) or a raw string; content-type is inferred."
|
|
2276
|
+
),
|
|
2277
|
+
statusText: z12.string().optional().describe(
|
|
2278
|
+
"For kind 'respond': the status line's text, e.g. 'Service Unavailable'. Cosmetic \u2014 React Native's XMLHttpRequest has no statusText field, so the app never sees it; it only makes the developer's rule list read correctly."
|
|
2279
|
+
),
|
|
2280
|
+
headers: z12.record(z12.string()).optional().describe("For kind 'respond': response headers to send."),
|
|
2281
|
+
failKind: z12.enum(["network", "timeout"]).optional().describe("For kind 'fail': 'network' (default) or 'timeout'."),
|
|
2282
|
+
delayMs: z12.number().optional().describe("Latency before the outcome, in ms. Works with every kind."),
|
|
2283
|
+
times: z12.number().optional().describe(
|
|
2284
|
+
"Auto-disable the rule after N matches. Use 1 for 'make the next call fail' so it can't linger."
|
|
2285
|
+
),
|
|
2286
|
+
alternate: z12.boolean().optional().describe(
|
|
2287
|
+
"Apply to every OTHER matching request, forever: the first goes through, the second is overridden, and so on. This is how you test retry logic and flaky endpoints \u2014 a rule that is always on or always off can't reach those paths."
|
|
2288
|
+
),
|
|
2289
|
+
name: z12.string().optional().describe("Label shown in the developer's UI."),
|
|
2290
|
+
enabled: z12.boolean().optional().describe(
|
|
2291
|
+
"For `add`: create the rule switched off (default true). Useful for staging a rule you'll arm later with enable({id})."
|
|
2292
|
+
),
|
|
2293
|
+
id: z12.string().optional().describe(
|
|
2294
|
+
"Rule id. Required for `remove`. For `add` it replaces that rule. For `enable`/`disable` it targets ONE rule instead of the master switch. For `list` it dumps that rule in full."
|
|
2295
|
+
),
|
|
2296
|
+
url: z12.string().optional().describe(
|
|
2297
|
+
"For `debug`: a concrete URL to test the rules against, so the report says which rule would match it and why."
|
|
2298
|
+
),
|
|
2299
|
+
deviceId: z12.string().optional().describe("Target device id (auto-selected if one is connected).")
|
|
2300
|
+
}
|
|
2301
|
+
},
|
|
2302
|
+
async (args) => {
|
|
2303
|
+
try {
|
|
2304
|
+
const target = sync.requireProDevice(args.deviceId);
|
|
2305
|
+
switch (args.action) {
|
|
2306
|
+
case "add": {
|
|
2307
|
+
if (!args.urlPattern && !args.fromRequestId) {
|
|
2308
|
+
return fail12(
|
|
2309
|
+
"`add` needs a `urlPattern` or a `fromRequestId`. Take an id from get_network_requests to override a request you just saw, or pass a glob like '*pokeapi.co*'."
|
|
2310
|
+
);
|
|
2311
|
+
}
|
|
2312
|
+
const res = await sync.callAction(
|
|
2313
|
+
NETWORK_TOOL_ID,
|
|
2314
|
+
"upsertOverrideRule",
|
|
2315
|
+
{
|
|
2316
|
+
fromRequestId: args.fromRequestId,
|
|
2317
|
+
rule: {
|
|
2318
|
+
id: args.id,
|
|
2319
|
+
name: args.name,
|
|
2320
|
+
urlPattern: args.urlPattern,
|
|
2321
|
+
methods: args.methods,
|
|
2322
|
+
// Not defaulted here — the device defaults to "respond" too,
|
|
2323
|
+
// and leaving it absent lets a `fromRequestId` prefill win.
|
|
2324
|
+
kind: args.kind,
|
|
2325
|
+
status: args.status,
|
|
2326
|
+
statusText: args.statusText,
|
|
2327
|
+
body: args.body,
|
|
2328
|
+
headers: args.headers,
|
|
2329
|
+
failKind: args.failKind,
|
|
2330
|
+
delayMs: args.delayMs,
|
|
2331
|
+
times: args.times,
|
|
2332
|
+
alternate: args.alternate,
|
|
2333
|
+
enabled: args.enabled !== false
|
|
2334
|
+
}
|
|
2335
|
+
},
|
|
2336
|
+
target
|
|
2337
|
+
);
|
|
2338
|
+
const shaped = res;
|
|
2339
|
+
if (!shaped?.ok) {
|
|
2340
|
+
return fail12(shaped?.error ?? `Could not add the rule on ${target}.`);
|
|
2341
|
+
}
|
|
2342
|
+
const pattern = shaped.rule?.urlPattern ?? args.urlPattern;
|
|
2343
|
+
const armed = shaped.rule?.enabled === false ? `
|
|
2344
|
+
|
|
2345
|
+
It is switched OFF \u2014 arm it with network_override({action:"enable", id:"${shaped.rule?.id}"}).` : "";
|
|
2346
|
+
return text10(
|
|
2347
|
+
`Override added on ${target} \u2014 \`${shaped.rule?.id}\` matching \`${pattern}\`.${armed}
|
|
2348
|
+
|
|
2349
|
+
Trigger the request now (navigate / tap_element), then check it with get_network_requests or describe_screen. Remove it with network_override({action:"remove", id:"${shaped.rule?.id}"}) when you're done.`
|
|
2350
|
+
);
|
|
2351
|
+
}
|
|
2352
|
+
case "remove": {
|
|
2353
|
+
if (!args.id) {
|
|
2354
|
+
return fail12('`remove` needs an `id`. List them with network_override({action:"list"}).');
|
|
2355
|
+
}
|
|
2356
|
+
await sync.callAction(NETWORK_TOOL_ID, "deleteOverrideRule", { id: args.id }, target);
|
|
2357
|
+
return text10(`Removed override \`${args.id}\` on ${target}.`);
|
|
2358
|
+
}
|
|
2359
|
+
case "clear":
|
|
2360
|
+
await sync.callAction(NETWORK_TOOL_ID, "clearOverrideRules", {}, target);
|
|
2361
|
+
return text10(`Removed all override rules on ${target}. Traffic is real again.`);
|
|
2362
|
+
case "enable":
|
|
2363
|
+
case "disable": {
|
|
2364
|
+
const enabled = args.action === "enable";
|
|
2365
|
+
if (args.id) {
|
|
2366
|
+
await sync.callAction(
|
|
2367
|
+
NETWORK_TOOL_ID,
|
|
2368
|
+
"setOverrideRuleEnabled",
|
|
2369
|
+
{ id: args.id, enabled },
|
|
2370
|
+
target
|
|
2371
|
+
);
|
|
2372
|
+
return text10(
|
|
2373
|
+
`Rule \`${args.id}\` is now ${enabled ? "ON" : "OFF"} on ${target}. The other rules and the master switch are unchanged.`
|
|
2374
|
+
);
|
|
2375
|
+
}
|
|
2376
|
+
await sync.callAction(NETWORK_TOOL_ID, "setOverridesEnabled", { enabled }, target);
|
|
2377
|
+
return text10(
|
|
2378
|
+
enabled ? `Overrides switched ON for ${target}.` : `Overrides switched OFF for ${target}. Rules are kept \u2014 re-enable with network_override({action:"enable"}).`
|
|
2379
|
+
);
|
|
2380
|
+
}
|
|
2381
|
+
case "list": {
|
|
2382
|
+
const res = await sync.callAction(
|
|
2383
|
+
NETWORK_TOOL_ID,
|
|
2384
|
+
"listOverrideRules",
|
|
2385
|
+
{},
|
|
2386
|
+
target
|
|
2387
|
+
);
|
|
2388
|
+
const rules = res?.rules ?? [];
|
|
2389
|
+
if (args.id) {
|
|
2390
|
+
const rule = rules.find((r) => r.id === args.id);
|
|
2391
|
+
if (!rule) {
|
|
2392
|
+
return fail12(
|
|
2393
|
+
`No override rule \`${args.id}\` on ${target}. List them with network_override({action:"list"}).`
|
|
2394
|
+
);
|
|
2395
|
+
}
|
|
2396
|
+
return text10(
|
|
2397
|
+
`**Override rule \`${rule.id}\`** on ${target}${masterNote(res)}
|
|
2398
|
+
|
|
2399
|
+
\`\`\`json
|
|
2400
|
+
${JSON.stringify(rule, null, 2)}
|
|
2401
|
+
\`\`\``
|
|
2402
|
+
);
|
|
2403
|
+
}
|
|
2404
|
+
if (rules.length === 0) {
|
|
2405
|
+
return text10(
|
|
2406
|
+
`No override rules on ${target}.
|
|
2407
|
+
|
|
2408
|
+
Add one with network_override({action:"add", urlPattern:"*", status:500}) to force an error state.`
|
|
2409
|
+
);
|
|
2410
|
+
}
|
|
2411
|
+
const rows = rules.map((rule, i) => {
|
|
2412
|
+
const what = rule.kind === "respond" ? `\u2192 ${rule.status ?? 200}${rule.statusText ? ` ${rule.statusText}` : ""}` : rule.kind === "fail" ? `\u2192 ${rule.failKind === "timeout" ? "timeout" : "network error"}` : "\u2192 real response";
|
|
2413
|
+
const bits = [
|
|
2414
|
+
rule.methods?.length ? rule.methods.join("/") : "any method",
|
|
2415
|
+
what,
|
|
2416
|
+
// Enough to tell WHICH body this is without printing it.
|
|
2417
|
+
rule.kind === "respond" && rule.body ? `body ${rule.body.length}B` : "",
|
|
2418
|
+
rule.headers && Object.keys(rule.headers).length ? `${Object.keys(rule.headers).length} header(s)` : "",
|
|
2419
|
+
rule.delayMs ? `${rule.delayMs}ms delay` : "",
|
|
2420
|
+
rule.alternate ? "every other request" : "",
|
|
2421
|
+
typeof rule.times === "number" ? `${rule.times}\xD7 then off` : "",
|
|
2422
|
+
`${rule.hits} hit${rule.hits === 1 ? "" : "s"}`,
|
|
2423
|
+
// Only interesting when the two can disagree: `alternate` and
|
|
2424
|
+
// spent `times` rules match without applying, and a gap between
|
|
2425
|
+
// the numbers is the explanation for "it matched but nothing
|
|
2426
|
+
// happened".
|
|
2427
|
+
rule.seen != null && rule.seen !== rule.hits ? `${rule.seen} matched` : "",
|
|
2428
|
+
rule.enabled ? "" : "DISABLED"
|
|
2429
|
+
].filter(Boolean);
|
|
2430
|
+
return `${i + 1}. \`${rule.id}\`${rule.name ? ` ${rule.name}` : ""}
|
|
2431
|
+
\u2514\u2500 \`${rule.urlPattern}\` \xB7 ${bits.join(" \xB7 ")}`;
|
|
2432
|
+
});
|
|
2433
|
+
return text10(
|
|
2434
|
+
`**Override rules on ${target}**${masterNote(res)}
|
|
2435
|
+
|
|
2436
|
+
${rows.join("\n")}
|
|
2437
|
+
|
|
2438
|
+
Full detail incl. body: network_override({action:"list", id:"${rules[0]?.id}"}).`
|
|
2439
|
+
);
|
|
2440
|
+
}
|
|
2441
|
+
case "debug": {
|
|
2442
|
+
const res = await sync.callAction(
|
|
2443
|
+
NETWORK_TOOL_ID,
|
|
2444
|
+
"debugOverrides",
|
|
2445
|
+
{ url: args.url },
|
|
2446
|
+
target
|
|
2447
|
+
);
|
|
2448
|
+
if (!res) {
|
|
2449
|
+
return fail12(
|
|
2450
|
+
`${target} did not answer the diagnostics call \u2014 the network tool may not be mounted.`
|
|
2451
|
+
);
|
|
2452
|
+
}
|
|
2453
|
+
return text10(
|
|
2454
|
+
`**Override diagnostics on ${target}**${args.url ? ` for \`${args.url}\`` : ""}
|
|
2455
|
+
|
|
2456
|
+
\`\`\`json
|
|
2457
|
+
${JSON.stringify(res, null, 2)}
|
|
2458
|
+
\`\`\`
|
|
2459
|
+
|
|
2460
|
+
A rule that never fires is usually one of: the master switch is off, the interceptor isn't installed (nothing is subscribed to the network tool), or the pattern doesn't match the FULL url including its query string.`
|
|
2461
|
+
);
|
|
2462
|
+
}
|
|
2463
|
+
}
|
|
2464
|
+
} catch (e) {
|
|
2465
|
+
return fail12(errMsg12(e));
|
|
2466
|
+
}
|
|
2467
|
+
}
|
|
2468
|
+
);
|
|
2249
2469
|
}
|
|
2250
2470
|
|
|
2251
2471
|
// src/tools/packs/interact.ts
|
|
@@ -2659,7 +2879,9 @@ function registerImagesTools(server, sync) {
|
|
|
2659
2879
|
error: r.error,
|
|
2660
2880
|
errorCode: r.errorCode,
|
|
2661
2881
|
overrideLabel: r.overrideLabel,
|
|
2662
|
-
loadCount: r.loadCount
|
|
2882
|
+
loadCount: r.loadCount,
|
|
2883
|
+
hasAltText: r.hasAltText,
|
|
2884
|
+
layoutShifts: r.layoutShifts
|
|
2663
2885
|
}))
|
|
2664
2886
|
};
|
|
2665
2887
|
return text13(`${md}
|
package/dist/cli.js
CHANGED
|
@@ -341,7 +341,7 @@ var import_node_os = __toESM(require("os"));
|
|
|
341
341
|
var import_node_path = __toESM(require("path"));
|
|
342
342
|
|
|
343
343
|
// src/version.ts
|
|
344
|
-
var VERSION = "7.0.
|
|
344
|
+
var VERSION = "7.0.15" ? "7.0.15" : "0.0.0-dev";
|
|
345
345
|
var PACKAGE_NAME = "@buoy-gg/mcp";
|
|
346
346
|
|
|
347
347
|
// src/update/checkForUpdate.ts
|
|
@@ -1296,6 +1296,10 @@ var NETWORK_TOOL_ID = "network";
|
|
|
1296
1296
|
var text3 = (s) => ({ content: [{ type: "text", text: s }] });
|
|
1297
1297
|
var fail4 = (message) => ({ isError: true, content: [{ type: "text", text: message }] });
|
|
1298
1298
|
var errMsg4 = (e) => e instanceof Error ? e.message : String(e);
|
|
1299
|
+
var masterNote = (state) => {
|
|
1300
|
+
if (state?.enabled !== false) return "";
|
|
1301
|
+
return state.autoPaused ? ' \u2014 AUTO-PAUSED after the app failed to settle on launch with overrides active; nothing is firing. Re-arm with network_override({action:"enable"}).' : " \u2014 MASTER SWITCH IS OFF, nothing is firing";
|
|
1302
|
+
};
|
|
1299
1303
|
function parseSnapshot(payload) {
|
|
1300
1304
|
if (Array.isArray(payload)) return { events: payload, saved: [] };
|
|
1301
1305
|
if (payload && typeof payload === "object") {
|
|
@@ -1509,6 +1513,222 @@ _\u{1F4CC} pinned \xB7 \u{1F516} saved. Act on one with its id, e.g. network_act
|
|
|
1509
1513
|
}
|
|
1510
1514
|
}
|
|
1511
1515
|
);
|
|
1516
|
+
server.registerTool(
|
|
1517
|
+
"network_override",
|
|
1518
|
+
{
|
|
1519
|
+
description: "Force matching HTTP requests to return a chosen status/body, to fail outright, or to arrive late \u2014 without touching the backend or the app's code. This is how you drive an app into a state it won't reach on its own: force a 500 and see whether the error UI appears, return [] and check the empty state, simulate offline or a slow connection. Rules run INSIDE the app and survive an app reload, so they also cover the boot path. After adding one, trigger the request (navigate/tap_element) and read the result with get_network_requests or describe_screen. ALWAYS remove your rules when done \u2014 a forgotten rule looks exactly like a real bug. Requires Buoy Pro.",
|
|
1520
|
+
inputSchema: {
|
|
1521
|
+
action: import_zod4.z.enum(["list", "add", "remove", "clear", "enable", "disable", "debug"]).describe(
|
|
1522
|
+
"add creates a rule (or replaces one when `id` is given). remove drops one by `id`. clear drops all. enable/disable flip ONE rule when `id` is given, otherwise the master switch \u2014 rules are kept either way. list shows every rule with its hit counts; pass `id` to dump one rule in full, body included. debug reports why a rule is or isn't firing."
|
|
1523
|
+
),
|
|
1524
|
+
urlPattern: import_zod4.z.string().optional().describe(
|
|
1525
|
+
"For `add`: glob matched against the WHOLE request URL, query string included. `*` is the only wildcard, e.g. '*pokeapi.co*' or 'https://api.example.com/v1/users*'. Careful with an exact URL copied from get_network_requests \u2014 if the client cache-busts or pages, the next call won't match it and the rule sits at 0 hits. Prefer `fromRequestId`, which handles that for you."
|
|
1526
|
+
),
|
|
1527
|
+
fromRequestId: import_zod4.z.string().optional().describe(
|
|
1528
|
+
"For `add`: build the rule from a captured request id (from get_network_requests) \u2014 the same 'Override this request' the developer gets in the UI. Seeds the pattern (query string replaced with `*` so it keeps matching), the method, the status, and the REAL response body, so you can change two fields of a payload you just saw instead of authoring one from scratch. Anything you pass explicitly overrides the prefill."
|
|
1529
|
+
),
|
|
1530
|
+
methods: import_zod4.z.array(import_zod4.z.string()).optional().describe("For `add`: restrict to these HTTP methods. Omit for any."),
|
|
1531
|
+
kind: import_zod4.z.enum(["respond", "fail", "delay"]).optional().describe(
|
|
1532
|
+
"respond = return your status/body, never hitting the network (default). fail = a transport failure, as if the device were offline. delay = let the real request run, just late."
|
|
1533
|
+
),
|
|
1534
|
+
status: import_zod4.z.number().optional().describe(
|
|
1535
|
+
"For kind 'respond': HTTP status, 200\u2013599 (default 200). Outside that range fetch itself throws, so use kind 'fail' for connection errors rather than status 0."
|
|
1536
|
+
),
|
|
1537
|
+
body: import_zod4.z.union([import_zod4.z.string(), import_zod4.z.record(import_zod4.z.unknown()), import_zod4.z.array(import_zod4.z.unknown())]).optional().describe(
|
|
1538
|
+
"For kind 'respond': the response body. Pass JSON directly (an object or array) or a raw string; content-type is inferred."
|
|
1539
|
+
),
|
|
1540
|
+
statusText: import_zod4.z.string().optional().describe(
|
|
1541
|
+
"For kind 'respond': the status line's text, e.g. 'Service Unavailable'. Cosmetic \u2014 React Native's XMLHttpRequest has no statusText field, so the app never sees it; it only makes the developer's rule list read correctly."
|
|
1542
|
+
),
|
|
1543
|
+
headers: import_zod4.z.record(import_zod4.z.string()).optional().describe("For kind 'respond': response headers to send."),
|
|
1544
|
+
failKind: import_zod4.z.enum(["network", "timeout"]).optional().describe("For kind 'fail': 'network' (default) or 'timeout'."),
|
|
1545
|
+
delayMs: import_zod4.z.number().optional().describe("Latency before the outcome, in ms. Works with every kind."),
|
|
1546
|
+
times: import_zod4.z.number().optional().describe(
|
|
1547
|
+
"Auto-disable the rule after N matches. Use 1 for 'make the next call fail' so it can't linger."
|
|
1548
|
+
),
|
|
1549
|
+
alternate: import_zod4.z.boolean().optional().describe(
|
|
1550
|
+
"Apply to every OTHER matching request, forever: the first goes through, the second is overridden, and so on. This is how you test retry logic and flaky endpoints \u2014 a rule that is always on or always off can't reach those paths."
|
|
1551
|
+
),
|
|
1552
|
+
name: import_zod4.z.string().optional().describe("Label shown in the developer's UI."),
|
|
1553
|
+
enabled: import_zod4.z.boolean().optional().describe(
|
|
1554
|
+
"For `add`: create the rule switched off (default true). Useful for staging a rule you'll arm later with enable({id})."
|
|
1555
|
+
),
|
|
1556
|
+
id: import_zod4.z.string().optional().describe(
|
|
1557
|
+
"Rule id. Required for `remove`. For `add` it replaces that rule. For `enable`/`disable` it targets ONE rule instead of the master switch. For `list` it dumps that rule in full."
|
|
1558
|
+
),
|
|
1559
|
+
url: import_zod4.z.string().optional().describe(
|
|
1560
|
+
"For `debug`: a concrete URL to test the rules against, so the report says which rule would match it and why."
|
|
1561
|
+
),
|
|
1562
|
+
deviceId: import_zod4.z.string().optional().describe("Target device id (auto-selected if one is connected).")
|
|
1563
|
+
}
|
|
1564
|
+
},
|
|
1565
|
+
async (args) => {
|
|
1566
|
+
try {
|
|
1567
|
+
const target = sync.requireProDevice(args.deviceId);
|
|
1568
|
+
switch (args.action) {
|
|
1569
|
+
case "add": {
|
|
1570
|
+
if (!args.urlPattern && !args.fromRequestId) {
|
|
1571
|
+
return fail4(
|
|
1572
|
+
"`add` needs a `urlPattern` or a `fromRequestId`. Take an id from get_network_requests to override a request you just saw, or pass a glob like '*pokeapi.co*'."
|
|
1573
|
+
);
|
|
1574
|
+
}
|
|
1575
|
+
const res = await sync.callAction(
|
|
1576
|
+
NETWORK_TOOL_ID,
|
|
1577
|
+
"upsertOverrideRule",
|
|
1578
|
+
{
|
|
1579
|
+
fromRequestId: args.fromRequestId,
|
|
1580
|
+
rule: {
|
|
1581
|
+
id: args.id,
|
|
1582
|
+
name: args.name,
|
|
1583
|
+
urlPattern: args.urlPattern,
|
|
1584
|
+
methods: args.methods,
|
|
1585
|
+
// Not defaulted here — the device defaults to "respond" too,
|
|
1586
|
+
// and leaving it absent lets a `fromRequestId` prefill win.
|
|
1587
|
+
kind: args.kind,
|
|
1588
|
+
status: args.status,
|
|
1589
|
+
statusText: args.statusText,
|
|
1590
|
+
body: args.body,
|
|
1591
|
+
headers: args.headers,
|
|
1592
|
+
failKind: args.failKind,
|
|
1593
|
+
delayMs: args.delayMs,
|
|
1594
|
+
times: args.times,
|
|
1595
|
+
alternate: args.alternate,
|
|
1596
|
+
enabled: args.enabled !== false
|
|
1597
|
+
}
|
|
1598
|
+
},
|
|
1599
|
+
target
|
|
1600
|
+
);
|
|
1601
|
+
const shaped = res;
|
|
1602
|
+
if (!shaped?.ok) {
|
|
1603
|
+
return fail4(shaped?.error ?? `Could not add the rule on ${target}.`);
|
|
1604
|
+
}
|
|
1605
|
+
const pattern = shaped.rule?.urlPattern ?? args.urlPattern;
|
|
1606
|
+
const armed = shaped.rule?.enabled === false ? `
|
|
1607
|
+
|
|
1608
|
+
It is switched OFF \u2014 arm it with network_override({action:"enable", id:"${shaped.rule?.id}"}).` : "";
|
|
1609
|
+
return text3(
|
|
1610
|
+
`Override added on ${target} \u2014 \`${shaped.rule?.id}\` matching \`${pattern}\`.${armed}
|
|
1611
|
+
|
|
1612
|
+
Trigger the request now (navigate / tap_element), then check it with get_network_requests or describe_screen. Remove it with network_override({action:"remove", id:"${shaped.rule?.id}"}) when you're done.`
|
|
1613
|
+
);
|
|
1614
|
+
}
|
|
1615
|
+
case "remove": {
|
|
1616
|
+
if (!args.id) {
|
|
1617
|
+
return fail4('`remove` needs an `id`. List them with network_override({action:"list"}).');
|
|
1618
|
+
}
|
|
1619
|
+
await sync.callAction(NETWORK_TOOL_ID, "deleteOverrideRule", { id: args.id }, target);
|
|
1620
|
+
return text3(`Removed override \`${args.id}\` on ${target}.`);
|
|
1621
|
+
}
|
|
1622
|
+
case "clear":
|
|
1623
|
+
await sync.callAction(NETWORK_TOOL_ID, "clearOverrideRules", {}, target);
|
|
1624
|
+
return text3(`Removed all override rules on ${target}. Traffic is real again.`);
|
|
1625
|
+
case "enable":
|
|
1626
|
+
case "disable": {
|
|
1627
|
+
const enabled = args.action === "enable";
|
|
1628
|
+
if (args.id) {
|
|
1629
|
+
await sync.callAction(
|
|
1630
|
+
NETWORK_TOOL_ID,
|
|
1631
|
+
"setOverrideRuleEnabled",
|
|
1632
|
+
{ id: args.id, enabled },
|
|
1633
|
+
target
|
|
1634
|
+
);
|
|
1635
|
+
return text3(
|
|
1636
|
+
`Rule \`${args.id}\` is now ${enabled ? "ON" : "OFF"} on ${target}. The other rules and the master switch are unchanged.`
|
|
1637
|
+
);
|
|
1638
|
+
}
|
|
1639
|
+
await sync.callAction(NETWORK_TOOL_ID, "setOverridesEnabled", { enabled }, target);
|
|
1640
|
+
return text3(
|
|
1641
|
+
enabled ? `Overrides switched ON for ${target}.` : `Overrides switched OFF for ${target}. Rules are kept \u2014 re-enable with network_override({action:"enable"}).`
|
|
1642
|
+
);
|
|
1643
|
+
}
|
|
1644
|
+
case "list": {
|
|
1645
|
+
const res = await sync.callAction(
|
|
1646
|
+
NETWORK_TOOL_ID,
|
|
1647
|
+
"listOverrideRules",
|
|
1648
|
+
{},
|
|
1649
|
+
target
|
|
1650
|
+
);
|
|
1651
|
+
const rules = res?.rules ?? [];
|
|
1652
|
+
if (args.id) {
|
|
1653
|
+
const rule = rules.find((r) => r.id === args.id);
|
|
1654
|
+
if (!rule) {
|
|
1655
|
+
return fail4(
|
|
1656
|
+
`No override rule \`${args.id}\` on ${target}. List them with network_override({action:"list"}).`
|
|
1657
|
+
);
|
|
1658
|
+
}
|
|
1659
|
+
return text3(
|
|
1660
|
+
`**Override rule \`${rule.id}\`** on ${target}${masterNote(res)}
|
|
1661
|
+
|
|
1662
|
+
\`\`\`json
|
|
1663
|
+
${JSON.stringify(rule, null, 2)}
|
|
1664
|
+
\`\`\``
|
|
1665
|
+
);
|
|
1666
|
+
}
|
|
1667
|
+
if (rules.length === 0) {
|
|
1668
|
+
return text3(
|
|
1669
|
+
`No override rules on ${target}.
|
|
1670
|
+
|
|
1671
|
+
Add one with network_override({action:"add", urlPattern:"*", status:500}) to force an error state.`
|
|
1672
|
+
);
|
|
1673
|
+
}
|
|
1674
|
+
const rows = rules.map((rule, i) => {
|
|
1675
|
+
const what = rule.kind === "respond" ? `\u2192 ${rule.status ?? 200}${rule.statusText ? ` ${rule.statusText}` : ""}` : rule.kind === "fail" ? `\u2192 ${rule.failKind === "timeout" ? "timeout" : "network error"}` : "\u2192 real response";
|
|
1676
|
+
const bits = [
|
|
1677
|
+
rule.methods?.length ? rule.methods.join("/") : "any method",
|
|
1678
|
+
what,
|
|
1679
|
+
// Enough to tell WHICH body this is without printing it.
|
|
1680
|
+
rule.kind === "respond" && rule.body ? `body ${rule.body.length}B` : "",
|
|
1681
|
+
rule.headers && Object.keys(rule.headers).length ? `${Object.keys(rule.headers).length} header(s)` : "",
|
|
1682
|
+
rule.delayMs ? `${rule.delayMs}ms delay` : "",
|
|
1683
|
+
rule.alternate ? "every other request" : "",
|
|
1684
|
+
typeof rule.times === "number" ? `${rule.times}\xD7 then off` : "",
|
|
1685
|
+
`${rule.hits} hit${rule.hits === 1 ? "" : "s"}`,
|
|
1686
|
+
// Only interesting when the two can disagree: `alternate` and
|
|
1687
|
+
// spent `times` rules match without applying, and a gap between
|
|
1688
|
+
// the numbers is the explanation for "it matched but nothing
|
|
1689
|
+
// happened".
|
|
1690
|
+
rule.seen != null && rule.seen !== rule.hits ? `${rule.seen} matched` : "",
|
|
1691
|
+
rule.enabled ? "" : "DISABLED"
|
|
1692
|
+
].filter(Boolean);
|
|
1693
|
+
return `${i + 1}. \`${rule.id}\`${rule.name ? ` ${rule.name}` : ""}
|
|
1694
|
+
\u2514\u2500 \`${rule.urlPattern}\` \xB7 ${bits.join(" \xB7 ")}`;
|
|
1695
|
+
});
|
|
1696
|
+
return text3(
|
|
1697
|
+
`**Override rules on ${target}**${masterNote(res)}
|
|
1698
|
+
|
|
1699
|
+
${rows.join("\n")}
|
|
1700
|
+
|
|
1701
|
+
Full detail incl. body: network_override({action:"list", id:"${rules[0]?.id}"}).`
|
|
1702
|
+
);
|
|
1703
|
+
}
|
|
1704
|
+
case "debug": {
|
|
1705
|
+
const res = await sync.callAction(
|
|
1706
|
+
NETWORK_TOOL_ID,
|
|
1707
|
+
"debugOverrides",
|
|
1708
|
+
{ url: args.url },
|
|
1709
|
+
target
|
|
1710
|
+
);
|
|
1711
|
+
if (!res) {
|
|
1712
|
+
return fail4(
|
|
1713
|
+
`${target} did not answer the diagnostics call \u2014 the network tool may not be mounted.`
|
|
1714
|
+
);
|
|
1715
|
+
}
|
|
1716
|
+
return text3(
|
|
1717
|
+
`**Override diagnostics on ${target}**${args.url ? ` for \`${args.url}\`` : ""}
|
|
1718
|
+
|
|
1719
|
+
\`\`\`json
|
|
1720
|
+
${JSON.stringify(res, null, 2)}
|
|
1721
|
+
\`\`\`
|
|
1722
|
+
|
|
1723
|
+
A rule that never fires is usually one of: the master switch is off, the interceptor isn't installed (nothing is subscribed to the network tool), or the pattern doesn't match the FULL url including its query string.`
|
|
1724
|
+
);
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
} catch (e) {
|
|
1728
|
+
return fail4(errMsg4(e));
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
);
|
|
1512
1732
|
}
|
|
1513
1733
|
|
|
1514
1734
|
// src/tools/packs/reactQuery.ts
|
|
@@ -2691,7 +2911,9 @@ function registerImagesTools(server, sync) {
|
|
|
2691
2911
|
error: r.error,
|
|
2692
2912
|
errorCode: r.errorCode,
|
|
2693
2913
|
overrideLabel: r.overrideLabel,
|
|
2694
|
-
loadCount: r.loadCount
|
|
2914
|
+
loadCount: r.loadCount,
|
|
2915
|
+
hasAltText: r.hasAltText,
|
|
2916
|
+
layoutShifts: r.layoutShifts
|
|
2695
2917
|
}))
|
|
2696
2918
|
};
|
|
2697
2919
|
return text13(`${md}
|
package/dist/cli.mjs
CHANGED
package/dist/index.js
CHANGED
|
@@ -376,7 +376,7 @@ var import_node_os = __toESM(require("os"));
|
|
|
376
376
|
var import_node_path = __toESM(require("path"));
|
|
377
377
|
|
|
378
378
|
// src/version.ts
|
|
379
|
-
var VERSION = "7.0.
|
|
379
|
+
var VERSION = "7.0.15" ? "7.0.15" : "0.0.0-dev";
|
|
380
380
|
var PACKAGE_NAME = "@buoy-gg/mcp";
|
|
381
381
|
|
|
382
382
|
// src/update/checkForUpdate.ts
|
|
@@ -1331,6 +1331,10 @@ var NETWORK_TOOL_ID = "network";
|
|
|
1331
1331
|
var text3 = (s) => ({ content: [{ type: "text", text: s }] });
|
|
1332
1332
|
var fail4 = (message) => ({ isError: true, content: [{ type: "text", text: message }] });
|
|
1333
1333
|
var errMsg4 = (e) => e instanceof Error ? e.message : String(e);
|
|
1334
|
+
var masterNote = (state) => {
|
|
1335
|
+
if (state?.enabled !== false) return "";
|
|
1336
|
+
return state.autoPaused ? ' \u2014 AUTO-PAUSED after the app failed to settle on launch with overrides active; nothing is firing. Re-arm with network_override({action:"enable"}).' : " \u2014 MASTER SWITCH IS OFF, nothing is firing";
|
|
1337
|
+
};
|
|
1334
1338
|
function parseSnapshot(payload) {
|
|
1335
1339
|
if (Array.isArray(payload)) return { events: payload, saved: [] };
|
|
1336
1340
|
if (payload && typeof payload === "object") {
|
|
@@ -1544,6 +1548,222 @@ _\u{1F4CC} pinned \xB7 \u{1F516} saved. Act on one with its id, e.g. network_act
|
|
|
1544
1548
|
}
|
|
1545
1549
|
}
|
|
1546
1550
|
);
|
|
1551
|
+
server.registerTool(
|
|
1552
|
+
"network_override",
|
|
1553
|
+
{
|
|
1554
|
+
description: "Force matching HTTP requests to return a chosen status/body, to fail outright, or to arrive late \u2014 without touching the backend or the app's code. This is how you drive an app into a state it won't reach on its own: force a 500 and see whether the error UI appears, return [] and check the empty state, simulate offline or a slow connection. Rules run INSIDE the app and survive an app reload, so they also cover the boot path. After adding one, trigger the request (navigate/tap_element) and read the result with get_network_requests or describe_screen. ALWAYS remove your rules when done \u2014 a forgotten rule looks exactly like a real bug. Requires Buoy Pro.",
|
|
1555
|
+
inputSchema: {
|
|
1556
|
+
action: import_zod4.z.enum(["list", "add", "remove", "clear", "enable", "disable", "debug"]).describe(
|
|
1557
|
+
"add creates a rule (or replaces one when `id` is given). remove drops one by `id`. clear drops all. enable/disable flip ONE rule when `id` is given, otherwise the master switch \u2014 rules are kept either way. list shows every rule with its hit counts; pass `id` to dump one rule in full, body included. debug reports why a rule is or isn't firing."
|
|
1558
|
+
),
|
|
1559
|
+
urlPattern: import_zod4.z.string().optional().describe(
|
|
1560
|
+
"For `add`: glob matched against the WHOLE request URL, query string included. `*` is the only wildcard, e.g. '*pokeapi.co*' or 'https://api.example.com/v1/users*'. Careful with an exact URL copied from get_network_requests \u2014 if the client cache-busts or pages, the next call won't match it and the rule sits at 0 hits. Prefer `fromRequestId`, which handles that for you."
|
|
1561
|
+
),
|
|
1562
|
+
fromRequestId: import_zod4.z.string().optional().describe(
|
|
1563
|
+
"For `add`: build the rule from a captured request id (from get_network_requests) \u2014 the same 'Override this request' the developer gets in the UI. Seeds the pattern (query string replaced with `*` so it keeps matching), the method, the status, and the REAL response body, so you can change two fields of a payload you just saw instead of authoring one from scratch. Anything you pass explicitly overrides the prefill."
|
|
1564
|
+
),
|
|
1565
|
+
methods: import_zod4.z.array(import_zod4.z.string()).optional().describe("For `add`: restrict to these HTTP methods. Omit for any."),
|
|
1566
|
+
kind: import_zod4.z.enum(["respond", "fail", "delay"]).optional().describe(
|
|
1567
|
+
"respond = return your status/body, never hitting the network (default). fail = a transport failure, as if the device were offline. delay = let the real request run, just late."
|
|
1568
|
+
),
|
|
1569
|
+
status: import_zod4.z.number().optional().describe(
|
|
1570
|
+
"For kind 'respond': HTTP status, 200\u2013599 (default 200). Outside that range fetch itself throws, so use kind 'fail' for connection errors rather than status 0."
|
|
1571
|
+
),
|
|
1572
|
+
body: import_zod4.z.union([import_zod4.z.string(), import_zod4.z.record(import_zod4.z.unknown()), import_zod4.z.array(import_zod4.z.unknown())]).optional().describe(
|
|
1573
|
+
"For kind 'respond': the response body. Pass JSON directly (an object or array) or a raw string; content-type is inferred."
|
|
1574
|
+
),
|
|
1575
|
+
statusText: import_zod4.z.string().optional().describe(
|
|
1576
|
+
"For kind 'respond': the status line's text, e.g. 'Service Unavailable'. Cosmetic \u2014 React Native's XMLHttpRequest has no statusText field, so the app never sees it; it only makes the developer's rule list read correctly."
|
|
1577
|
+
),
|
|
1578
|
+
headers: import_zod4.z.record(import_zod4.z.string()).optional().describe("For kind 'respond': response headers to send."),
|
|
1579
|
+
failKind: import_zod4.z.enum(["network", "timeout"]).optional().describe("For kind 'fail': 'network' (default) or 'timeout'."),
|
|
1580
|
+
delayMs: import_zod4.z.number().optional().describe("Latency before the outcome, in ms. Works with every kind."),
|
|
1581
|
+
times: import_zod4.z.number().optional().describe(
|
|
1582
|
+
"Auto-disable the rule after N matches. Use 1 for 'make the next call fail' so it can't linger."
|
|
1583
|
+
),
|
|
1584
|
+
alternate: import_zod4.z.boolean().optional().describe(
|
|
1585
|
+
"Apply to every OTHER matching request, forever: the first goes through, the second is overridden, and so on. This is how you test retry logic and flaky endpoints \u2014 a rule that is always on or always off can't reach those paths."
|
|
1586
|
+
),
|
|
1587
|
+
name: import_zod4.z.string().optional().describe("Label shown in the developer's UI."),
|
|
1588
|
+
enabled: import_zod4.z.boolean().optional().describe(
|
|
1589
|
+
"For `add`: create the rule switched off (default true). Useful for staging a rule you'll arm later with enable({id})."
|
|
1590
|
+
),
|
|
1591
|
+
id: import_zod4.z.string().optional().describe(
|
|
1592
|
+
"Rule id. Required for `remove`. For `add` it replaces that rule. For `enable`/`disable` it targets ONE rule instead of the master switch. For `list` it dumps that rule in full."
|
|
1593
|
+
),
|
|
1594
|
+
url: import_zod4.z.string().optional().describe(
|
|
1595
|
+
"For `debug`: a concrete URL to test the rules against, so the report says which rule would match it and why."
|
|
1596
|
+
),
|
|
1597
|
+
deviceId: import_zod4.z.string().optional().describe("Target device id (auto-selected if one is connected).")
|
|
1598
|
+
}
|
|
1599
|
+
},
|
|
1600
|
+
async (args) => {
|
|
1601
|
+
try {
|
|
1602
|
+
const target = sync.requireProDevice(args.deviceId);
|
|
1603
|
+
switch (args.action) {
|
|
1604
|
+
case "add": {
|
|
1605
|
+
if (!args.urlPattern && !args.fromRequestId) {
|
|
1606
|
+
return fail4(
|
|
1607
|
+
"`add` needs a `urlPattern` or a `fromRequestId`. Take an id from get_network_requests to override a request you just saw, or pass a glob like '*pokeapi.co*'."
|
|
1608
|
+
);
|
|
1609
|
+
}
|
|
1610
|
+
const res = await sync.callAction(
|
|
1611
|
+
NETWORK_TOOL_ID,
|
|
1612
|
+
"upsertOverrideRule",
|
|
1613
|
+
{
|
|
1614
|
+
fromRequestId: args.fromRequestId,
|
|
1615
|
+
rule: {
|
|
1616
|
+
id: args.id,
|
|
1617
|
+
name: args.name,
|
|
1618
|
+
urlPattern: args.urlPattern,
|
|
1619
|
+
methods: args.methods,
|
|
1620
|
+
// Not defaulted here — the device defaults to "respond" too,
|
|
1621
|
+
// and leaving it absent lets a `fromRequestId` prefill win.
|
|
1622
|
+
kind: args.kind,
|
|
1623
|
+
status: args.status,
|
|
1624
|
+
statusText: args.statusText,
|
|
1625
|
+
body: args.body,
|
|
1626
|
+
headers: args.headers,
|
|
1627
|
+
failKind: args.failKind,
|
|
1628
|
+
delayMs: args.delayMs,
|
|
1629
|
+
times: args.times,
|
|
1630
|
+
alternate: args.alternate,
|
|
1631
|
+
enabled: args.enabled !== false
|
|
1632
|
+
}
|
|
1633
|
+
},
|
|
1634
|
+
target
|
|
1635
|
+
);
|
|
1636
|
+
const shaped = res;
|
|
1637
|
+
if (!shaped?.ok) {
|
|
1638
|
+
return fail4(shaped?.error ?? `Could not add the rule on ${target}.`);
|
|
1639
|
+
}
|
|
1640
|
+
const pattern = shaped.rule?.urlPattern ?? args.urlPattern;
|
|
1641
|
+
const armed = shaped.rule?.enabled === false ? `
|
|
1642
|
+
|
|
1643
|
+
It is switched OFF \u2014 arm it with network_override({action:"enable", id:"${shaped.rule?.id}"}).` : "";
|
|
1644
|
+
return text3(
|
|
1645
|
+
`Override added on ${target} \u2014 \`${shaped.rule?.id}\` matching \`${pattern}\`.${armed}
|
|
1646
|
+
|
|
1647
|
+
Trigger the request now (navigate / tap_element), then check it with get_network_requests or describe_screen. Remove it with network_override({action:"remove", id:"${shaped.rule?.id}"}) when you're done.`
|
|
1648
|
+
);
|
|
1649
|
+
}
|
|
1650
|
+
case "remove": {
|
|
1651
|
+
if (!args.id) {
|
|
1652
|
+
return fail4('`remove` needs an `id`. List them with network_override({action:"list"}).');
|
|
1653
|
+
}
|
|
1654
|
+
await sync.callAction(NETWORK_TOOL_ID, "deleteOverrideRule", { id: args.id }, target);
|
|
1655
|
+
return text3(`Removed override \`${args.id}\` on ${target}.`);
|
|
1656
|
+
}
|
|
1657
|
+
case "clear":
|
|
1658
|
+
await sync.callAction(NETWORK_TOOL_ID, "clearOverrideRules", {}, target);
|
|
1659
|
+
return text3(`Removed all override rules on ${target}. Traffic is real again.`);
|
|
1660
|
+
case "enable":
|
|
1661
|
+
case "disable": {
|
|
1662
|
+
const enabled = args.action === "enable";
|
|
1663
|
+
if (args.id) {
|
|
1664
|
+
await sync.callAction(
|
|
1665
|
+
NETWORK_TOOL_ID,
|
|
1666
|
+
"setOverrideRuleEnabled",
|
|
1667
|
+
{ id: args.id, enabled },
|
|
1668
|
+
target
|
|
1669
|
+
);
|
|
1670
|
+
return text3(
|
|
1671
|
+
`Rule \`${args.id}\` is now ${enabled ? "ON" : "OFF"} on ${target}. The other rules and the master switch are unchanged.`
|
|
1672
|
+
);
|
|
1673
|
+
}
|
|
1674
|
+
await sync.callAction(NETWORK_TOOL_ID, "setOverridesEnabled", { enabled }, target);
|
|
1675
|
+
return text3(
|
|
1676
|
+
enabled ? `Overrides switched ON for ${target}.` : `Overrides switched OFF for ${target}. Rules are kept \u2014 re-enable with network_override({action:"enable"}).`
|
|
1677
|
+
);
|
|
1678
|
+
}
|
|
1679
|
+
case "list": {
|
|
1680
|
+
const res = await sync.callAction(
|
|
1681
|
+
NETWORK_TOOL_ID,
|
|
1682
|
+
"listOverrideRules",
|
|
1683
|
+
{},
|
|
1684
|
+
target
|
|
1685
|
+
);
|
|
1686
|
+
const rules = res?.rules ?? [];
|
|
1687
|
+
if (args.id) {
|
|
1688
|
+
const rule = rules.find((r) => r.id === args.id);
|
|
1689
|
+
if (!rule) {
|
|
1690
|
+
return fail4(
|
|
1691
|
+
`No override rule \`${args.id}\` on ${target}. List them with network_override({action:"list"}).`
|
|
1692
|
+
);
|
|
1693
|
+
}
|
|
1694
|
+
return text3(
|
|
1695
|
+
`**Override rule \`${rule.id}\`** on ${target}${masterNote(res)}
|
|
1696
|
+
|
|
1697
|
+
\`\`\`json
|
|
1698
|
+
${JSON.stringify(rule, null, 2)}
|
|
1699
|
+
\`\`\``
|
|
1700
|
+
);
|
|
1701
|
+
}
|
|
1702
|
+
if (rules.length === 0) {
|
|
1703
|
+
return text3(
|
|
1704
|
+
`No override rules on ${target}.
|
|
1705
|
+
|
|
1706
|
+
Add one with network_override({action:"add", urlPattern:"*", status:500}) to force an error state.`
|
|
1707
|
+
);
|
|
1708
|
+
}
|
|
1709
|
+
const rows = rules.map((rule, i) => {
|
|
1710
|
+
const what = rule.kind === "respond" ? `\u2192 ${rule.status ?? 200}${rule.statusText ? ` ${rule.statusText}` : ""}` : rule.kind === "fail" ? `\u2192 ${rule.failKind === "timeout" ? "timeout" : "network error"}` : "\u2192 real response";
|
|
1711
|
+
const bits = [
|
|
1712
|
+
rule.methods?.length ? rule.methods.join("/") : "any method",
|
|
1713
|
+
what,
|
|
1714
|
+
// Enough to tell WHICH body this is without printing it.
|
|
1715
|
+
rule.kind === "respond" && rule.body ? `body ${rule.body.length}B` : "",
|
|
1716
|
+
rule.headers && Object.keys(rule.headers).length ? `${Object.keys(rule.headers).length} header(s)` : "",
|
|
1717
|
+
rule.delayMs ? `${rule.delayMs}ms delay` : "",
|
|
1718
|
+
rule.alternate ? "every other request" : "",
|
|
1719
|
+
typeof rule.times === "number" ? `${rule.times}\xD7 then off` : "",
|
|
1720
|
+
`${rule.hits} hit${rule.hits === 1 ? "" : "s"}`,
|
|
1721
|
+
// Only interesting when the two can disagree: `alternate` and
|
|
1722
|
+
// spent `times` rules match without applying, and a gap between
|
|
1723
|
+
// the numbers is the explanation for "it matched but nothing
|
|
1724
|
+
// happened".
|
|
1725
|
+
rule.seen != null && rule.seen !== rule.hits ? `${rule.seen} matched` : "",
|
|
1726
|
+
rule.enabled ? "" : "DISABLED"
|
|
1727
|
+
].filter(Boolean);
|
|
1728
|
+
return `${i + 1}. \`${rule.id}\`${rule.name ? ` ${rule.name}` : ""}
|
|
1729
|
+
\u2514\u2500 \`${rule.urlPattern}\` \xB7 ${bits.join(" \xB7 ")}`;
|
|
1730
|
+
});
|
|
1731
|
+
return text3(
|
|
1732
|
+
`**Override rules on ${target}**${masterNote(res)}
|
|
1733
|
+
|
|
1734
|
+
${rows.join("\n")}
|
|
1735
|
+
|
|
1736
|
+
Full detail incl. body: network_override({action:"list", id:"${rules[0]?.id}"}).`
|
|
1737
|
+
);
|
|
1738
|
+
}
|
|
1739
|
+
case "debug": {
|
|
1740
|
+
const res = await sync.callAction(
|
|
1741
|
+
NETWORK_TOOL_ID,
|
|
1742
|
+
"debugOverrides",
|
|
1743
|
+
{ url: args.url },
|
|
1744
|
+
target
|
|
1745
|
+
);
|
|
1746
|
+
if (!res) {
|
|
1747
|
+
return fail4(
|
|
1748
|
+
`${target} did not answer the diagnostics call \u2014 the network tool may not be mounted.`
|
|
1749
|
+
);
|
|
1750
|
+
}
|
|
1751
|
+
return text3(
|
|
1752
|
+
`**Override diagnostics on ${target}**${args.url ? ` for \`${args.url}\`` : ""}
|
|
1753
|
+
|
|
1754
|
+
\`\`\`json
|
|
1755
|
+
${JSON.stringify(res, null, 2)}
|
|
1756
|
+
\`\`\`
|
|
1757
|
+
|
|
1758
|
+
A rule that never fires is usually one of: the master switch is off, the interceptor isn't installed (nothing is subscribed to the network tool), or the pattern doesn't match the FULL url including its query string.`
|
|
1759
|
+
);
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
} catch (e) {
|
|
1763
|
+
return fail4(errMsg4(e));
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1766
|
+
);
|
|
1547
1767
|
}
|
|
1548
1768
|
|
|
1549
1769
|
// src/tools/packs/reactQuery.ts
|
|
@@ -2726,7 +2946,9 @@ function registerImagesTools(server, sync) {
|
|
|
2726
2946
|
error: r.error,
|
|
2727
2947
|
errorCode: r.errorCode,
|
|
2728
2948
|
overrideLabel: r.overrideLabel,
|
|
2729
|
-
loadCount: r.loadCount
|
|
2949
|
+
loadCount: r.loadCount,
|
|
2950
|
+
hasAltText: r.hasAltText,
|
|
2951
|
+
layoutShifts: r.layoutShifts
|
|
2730
2952
|
}))
|
|
2731
2953
|
};
|
|
2732
2954
|
return text13(`${md}
|
package/dist/index.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@buoy-gg/mcp",
|
|
3
3
|
"mcpName": "io.github.buoy-gg/mcp",
|
|
4
|
-
"version": "7.0.
|
|
4
|
+
"version": "7.0.15",
|
|
5
5
|
"description": "MCP server for Buoy devtools — lets any LLM copilot drive Buoy tools (perf-monitor benchmarks, etc.) over the devtool sync protocol",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"module": "dist/index.mjs",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
32
32
|
"socket.io-client": "^4.8.1",
|
|
33
33
|
"zod": "^3.25.0",
|
|
34
|
-
"@buoy-gg/sync-broker": "^7.0.
|
|
34
|
+
"@buoy-gg/sync-broker": "^7.0.15"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@types/node": "^22.0.0",
|