@kaminari-ad/mcp 0.9.1 → 0.10.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.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,56 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.10.0] - 2026-07-28
11
+
12
+ ### Added
13
+
14
+ - **Repeats and retries.** `create_scan`, `create_bulk_scans`,
15
+ `create_campaign`, and `update_campaign` accept `repeat_count` (1-20),
16
+ `repeat_mode` (`isolated` | `shared`), and `retry_max_attempts` (0-5).
17
+ `repeat_count` multiplies the scans created — and billed — per
18
+ `url x country x device` combination; `shared` runs a combination's repeats
19
+ in one browser behind one IP with cookies carried over (rejected with 422
20
+ together with ad discovery); `retry_max_attempts` re-crawls the same scan
21
+ after a transient failure without billing twice.
22
+ - **Repeat / retry fields on the read side.** Scan detail surfaces
23
+ `repeat_index`, `repeat_total`, `repeat_session_id`, `repeat_scan_ids`,
24
+ `retry_attempt`, and `retry_max_attempts`; the scan-list brief carries all of
25
+ those except `repeat_scan_ids`; campaign responses echo `repeat_count`,
26
+ `repeat_mode`, and `retry_max_attempts`.
27
+
28
+ ### Fixed
29
+
30
+ - **`list_balance_history` can filter on `card_top_up`.** The API has emitted
31
+ that transaction type for a while — an unfiltered call already returned the
32
+ rows — but the tool's own enum stopped at `crypto_top_up`, so
33
+ `type: ["card_top_up"]` was rejected locally and never reached the API. The
34
+ filter's length cap was one short for the same reason.
35
+ `BalanceTransactionType` is now aliased off the generated schema instead of
36
+ hand-mirrored, so the next regen turns a new value into a compile error.
37
+
38
+ ### Changed
39
+
40
+ - Regenerated the OpenAPI type + zod-schema snapshots against `/api/v1`
41
+ v1.27.0. Besides the repeat / retry fields this picks up the
42
+ `creative-html` / `creative-video` / `vast-xml` artifact endpoints,
43
+ `video.click_through`, `creative_kind` as an enum, `tag_visibility` on custom
44
+ rules, the `card_top_up` balance-transaction type, and the `tag_match` scan
45
+ filter.
46
+ - `creative_kind` is surfaced as an open string rather than the regenerated
47
+ `banner | video` enum, following the same forward-compat policy as
48
+ `block_reason`. A creative kind added on the API side now degrades that one
49
+ field instead of failing the entire `get_scan` / `create_scan` /
50
+ `create_bulk_scans` response until an MCP release ships.
51
+ - `get_scan_screenshot` and `get_scan_landing_screenshot` warn that a resized
52
+ capture is top-cropped past 2.5x its width, and to omit `width` when the
53
+ whole page matters. `get_scan_creative_screenshot` never crops and is
54
+ unchanged.
55
+ - `get_campaign` and `list_campaigns` describe the repeat / retry settings they
56
+ echo, `list_scan_children` explains why those fields are always neutral on a
57
+ discovered-ad child, and `get_scan` documents the creative's `click_through`
58
+ destination.
59
+
10
60
  ## [0.8.0] - 2026-07-10
11
61
 
12
62
  ### Added
package/dist/bin.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { NAME, VERSION, err, ok } from './chunk-JEGBPX3R.js';
2
+ import { NAME, VERSION, err, ok } from './chunk-KFVVS55N.js';
3
3
  import process from 'process';
4
4
  import { z } from 'zod';
5
5
 
@@ -160,10 +160,10 @@ async function main() {
160
160
  }
161
161
  const config = configResult.value;
162
162
  if (config.transport === "stdio") {
163
- const { bootstrapStdio } = await import('./stdio-bootstrap-YQFKQP2G.js');
163
+ const { bootstrapStdio } = await import('./stdio-bootstrap-7EXISZ66.js');
164
164
  return bootstrapStdio(config);
165
165
  }
166
- const { bootstrapHttp } = await import('./http-bootstrap-JS2O6I22.js');
166
+ const { bootstrapHttp } = await import('./http-bootstrap-3MGAQHND.js');
167
167
  return bootstrapHttp(config);
168
168
  }
169
169
  main().then(
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { err, ok } from './chunk-JEGBPX3R.js';
2
+ import { err, ok } from './chunk-KFVVS55N.js';
3
3
  import { createHash, randomUUID } from 'crypto';
4
4
  import createClient from 'openapi-fetch';
5
5
  import { fetch } from 'undici';
@@ -240,6 +240,7 @@ var ProxyTargetRequest = z.object({
240
240
  city: z.string().default(""),
241
241
  isp: z.string().default("")
242
242
  }).partial().passthrough();
243
+ var RepeatModeType = z.enum(["isolated", "shared"]);
243
244
  z.object({
244
245
  url: z.union([z.string(), z.null()]).optional(),
245
246
  ad_tag: z.union([z.string(), z.null()]).optional(),
@@ -250,7 +251,10 @@ z.object({
250
251
  labels: z.record(z.string()).optional(),
251
252
  campaign_id: z.union([z.string(), z.null()]).optional(),
252
253
  run_id: z.union([z.string(), z.null()]).optional(),
253
- ad_discovery: z.boolean().optional().default(false)
254
+ ad_discovery: z.boolean().optional().default(false),
255
+ repeat_count: z.number().int().gte(1).optional().default(1),
256
+ repeat_mode: RepeatModeType.optional(),
257
+ retry_max_attempts: z.number().int().gte(0).optional().default(0)
254
258
  }).passthrough();
255
259
  var ScanStatus = z.enum([
256
260
  "pending",
@@ -290,7 +294,8 @@ var VideoMetaResponse = z.object({
290
294
  vast_version: z.string().default(""),
291
295
  ad_system: z.string().default(""),
292
296
  is_vpaid: z.boolean().default(false),
293
- wrapper_depth: z.number().int().default(0)
297
+ wrapper_depth: z.number().int().default(0),
298
+ click_through: z.string().default("")
294
299
  }).partial().passthrough();
295
300
  var ProxyTargetResponse = z.object({
296
301
  proxy_type: z.string(),
@@ -357,8 +362,11 @@ var ScanResponse = z.object({
357
362
  public_report_url: z.string().optional().default(""),
358
363
  ad_tag: z.union([z.string(), z.null()]).optional(),
359
364
  vast_tag: z.union([z.string(), z.null()]).optional(),
360
- creative_kind: z.string().optional().default("banner"),
365
+ creative_kind: z.enum(["banner", "video"]).optional().default("banner"),
361
366
  creative_screenshot_url: z.string().optional().default(""),
367
+ creative_video_url: z.string().optional().default(""),
368
+ vast_xml_url: z.string().optional().default(""),
369
+ creative_html_url: z.string().optional().default(""),
362
370
  creative_width: z.number().int().optional().default(0),
363
371
  creative_height: z.number().int().optional().default(0),
364
372
  video: z.union([VideoMetaResponse, z.null()]).optional(),
@@ -374,7 +382,13 @@ var ScanResponse = z.object({
374
382
  campaign_group_name: z.union([z.string(), z.null()]).optional(),
375
383
  created_at: z.string().datetime({ offset: true }),
376
384
  completed_at: z.union([z.string(), z.null()]),
377
- landings: z.array(LandingResponse).optional()
385
+ landings: z.array(LandingResponse).optional(),
386
+ repeat_index: z.number().int().optional().default(0),
387
+ repeat_total: z.number().int().optional().default(1),
388
+ repeat_session_id: z.union([z.string(), z.null()]).optional(),
389
+ repeat_scan_ids: z.array(z.string().uuid()).optional(),
390
+ retry_attempt: z.number().int().optional().default(0),
391
+ retry_max_attempts: z.number().int().optional().default(0)
378
392
  }).passthrough();
379
393
  z.union([z.string(), z.null()]).optional();
380
394
  var ScanBriefResponse = z.object({
@@ -401,7 +415,12 @@ var ScanBriefResponse = z.object({
401
415
  ad_kind: z.union([z.string(), z.null()]).optional(),
402
416
  network: z.string().optional().default(""),
403
417
  emulator_display_name: z.string().optional().default(""),
404
- emulator_category: z.string().optional().default("")
418
+ emulator_category: z.string().optional().default(""),
419
+ repeat_index: z.number().int().optional().default(0),
420
+ repeat_total: z.number().int().optional().default(1),
421
+ repeat_session_id: z.union([z.string(), z.null()]).optional(),
422
+ retry_attempt: z.number().int().optional().default(0),
423
+ retry_max_attempts: z.number().int().optional().default(0)
405
424
  }).passthrough();
406
425
  z.object({
407
426
  items: z.array(ScanBriefResponse),
@@ -417,7 +436,10 @@ z.object({
417
436
  country_codes: z.array(z.string()).min(1),
418
437
  emulator_id: z.string().min(1).max(100),
419
438
  proxy: ProxyTargetRequest.optional(),
420
- labels: z.record(z.string()).optional()
439
+ labels: z.record(z.string()).optional(),
440
+ repeat_count: z.number().int().gte(1).optional().default(1),
441
+ repeat_mode: RepeatModeType.optional(),
442
+ retry_max_attempts: z.number().int().gte(0).optional().default(0)
421
443
  }).passthrough();
422
444
  z.object({
423
445
  scope_type: z.enum(["last_n", "hours"]),
@@ -461,6 +483,7 @@ var GroupActionResponse = z.object({
461
483
  run_ids: z.array(z.string().uuid()).optional(),
462
484
  failures: z.array(BulkCampaignFailure).optional()
463
485
  }).passthrough();
486
+ var RepeatMode = z.enum(["isolated", "shared"]);
464
487
  z.object({
465
488
  name: z.string().min(1).max(200),
466
489
  campaign_type: z.string().optional().default("url"),
@@ -476,6 +499,9 @@ z.object({
476
499
  proxy_region: z.string().optional().default(""),
477
500
  proxy_city: z.string().optional().default(""),
478
501
  proxy_isp: z.string().optional().default(""),
502
+ repeat_count: z.number().int().gte(1).optional().default(1),
503
+ repeat_mode: RepeatMode.optional(),
504
+ retry_max_attempts: z.number().int().gte(0).optional().default(0),
479
505
  labels: z.record(z.string()).optional(),
480
506
  policy_set_id: z.union([z.string(), z.null()]).optional(),
481
507
  schedule_type: z.union([z.string(), z.null()]).optional(),
@@ -503,6 +529,9 @@ var CampaignResponse = z.object({
503
529
  proxy_region: z.string().optional().default(""),
504
530
  proxy_city: z.string().optional().default(""),
505
531
  proxy_isp: z.string().optional().default(""),
532
+ repeat_count: z.number().int().optional().default(1),
533
+ repeat_mode: RepeatMode.optional(),
534
+ retry_max_attempts: z.number().int().optional().default(0),
506
535
  schedule_type: z.union([z.string(), z.null()]).optional(),
507
536
  schedule_weekly: z.union([z.record(z.array(z.number().int())), z.null()]).optional(),
508
537
  schedule_interval_seconds: z.union([z.number(), z.null()]).optional(),
@@ -542,6 +571,9 @@ z.object({
542
571
  proxy_region: z.union([z.string(), z.null()]),
543
572
  proxy_city: z.union([z.string(), z.null()]),
544
573
  proxy_isp: z.union([z.string(), z.null()]),
574
+ repeat_count: z.union([z.number(), z.null()]),
575
+ repeat_mode: z.union([RepeatMode, z.null()]),
576
+ retry_max_attempts: z.union([z.number(), z.null()]),
545
577
  labels: z.union([z.record(z.string()), z.null()]),
546
578
  policy_set_id: z.union([z.string(), z.null()]),
547
579
  schedule_type: z.union([z.string(), z.null()]),
@@ -649,7 +681,8 @@ z.object({
649
681
  tag_slug: z.string().max(100).optional().default(""),
650
682
  rule_type: z.string().max(50),
651
683
  config: z.object({}).partial().passthrough(),
652
- target: z.string().max(30).optional().default("page")
684
+ target: z.string().max(30).optional().default("page"),
685
+ tag_visibility: z.union([z.record(TagVisibility), z.null()]).optional()
653
686
  }).passthrough();
654
687
  var CustomRuleResponse = z.object({
655
688
  id: z.string().uuid(),
@@ -661,7 +694,8 @@ var CustomRuleResponse = z.object({
661
694
  target: z.string(),
662
695
  is_active: z.boolean(),
663
696
  created_at: z.string().datetime({ offset: true }),
664
- scope: z.string().optional().default("personal")
697
+ scope: z.string().optional().default("personal"),
698
+ tag_visibility: z.record(z.string()).optional()
665
699
  }).passthrough();
666
700
  z.object({
667
701
  items: z.array(CustomRuleResponse),
@@ -675,7 +709,8 @@ z.object({
675
709
  tag_slug: z.union([z.string(), z.null()]),
676
710
  config: z.union([z.object({}).partial().passthrough(), z.null()]),
677
711
  target: z.union([z.string(), z.null()]),
678
- is_active: z.union([z.boolean(), z.null()])
712
+ is_active: z.union([z.boolean(), z.null()]),
713
+ tag_visibility: z.union([z.record(TagVisibility), z.null()])
679
714
  }).partial().passthrough();
680
715
  z.object({
681
716
  scan_id: z.string().uuid(),
@@ -906,7 +941,8 @@ var BalanceTransactionType = z.enum([
906
941
  "admin_adjustment",
907
942
  "refund",
908
943
  "invoice_settlement",
909
- "crypto_top_up"
944
+ "crypto_top_up",
945
+ "card_top_up"
910
946
  ]);
911
947
  z.union([z.array(BalanceTransactionType), z.null()]).optional();
912
948
  var BalanceTransactionResponse = z.object({
@@ -1153,6 +1189,7 @@ z.object({
1153
1189
  description: z.string().max(500).optional().default(""),
1154
1190
  nodes: z.array(TaxonomyNodeRequest).optional().default([])
1155
1191
  });
1192
+ z.enum(["any", "all"]);
1156
1193
  var schemas = {
1157
1194
  OrgResponse,
1158
1195
  LabelDefinitionResponse,
@@ -1166,6 +1203,7 @@ var schemas = {
1166
1203
  EmulatorResponse,
1167
1204
  CampaignGroupResponse,
1168
1205
  GroupActionResponse,
1206
+ RepeatMode,
1169
1207
  CampaignResponse,
1170
1208
  CampaignPickerItem,
1171
1209
  RunResponse,
@@ -1308,6 +1346,9 @@ var CampaignSchema = schemas.CampaignResponse.pick({
1308
1346
  proxy_region: true,
1309
1347
  proxy_city: true,
1310
1348
  proxy_isp: true,
1349
+ repeat_count: true,
1350
+ repeat_mode: true,
1351
+ retry_max_attempts: true,
1311
1352
  labels: true,
1312
1353
  policy_set_id: true,
1313
1354
  schedule_enabled: true,
@@ -1318,7 +1359,7 @@ var CampaignSchema = schemas.CampaignResponse.pick({
1318
1359
  is_archived: true,
1319
1360
  created_at: true,
1320
1361
  last_run_at: true
1321
- }).strip();
1362
+ }).extend({ repeat_mode: schemas.RepeatMode.default("isolated") }).strip();
1322
1363
  var parseCampaign = (raw) => parseWithSchema(CampaignSchema, raw, "campaign");
1323
1364
  var parseCampaignPage = (raw) => parsePagedWithItemSchema(CampaignSchema, raw, "campaigns");
1324
1365
 
@@ -1746,8 +1787,14 @@ var ScanSchema = schemas.ScanResponse.pick({
1746
1787
  ad_kind: true,
1747
1788
  network: true,
1748
1789
  created_at: true,
1749
- completed_at: true
1750
- }).strip();
1790
+ completed_at: true,
1791
+ repeat_index: true,
1792
+ repeat_total: true,
1793
+ repeat_session_id: true,
1794
+ repeat_scan_ids: true,
1795
+ retry_attempt: true,
1796
+ retry_max_attempts: true
1797
+ }).extend({ creative_kind: z.string().default("banner") }).strip();
1751
1798
  var ScanArraySchema = z.array(ScanSchema);
1752
1799
  var parseScan = (raw) => parseWithSchema(ScanSchema, raw, "scan");
1753
1800
  var parseScanArray = (raw) => parseWithSchema(ScanArraySchema, raw, "scans");
@@ -1773,7 +1820,12 @@ var ScanBriefSchema = schemas.ScanBriefResponse.pick({
1773
1820
  slot_index: true,
1774
1821
  ad_kind: true,
1775
1822
  network: true,
1776
- created_at: true
1823
+ created_at: true,
1824
+ repeat_index: true,
1825
+ repeat_total: true,
1826
+ repeat_session_id: true,
1827
+ retry_attempt: true,
1828
+ retry_max_attempts: true
1777
1829
  }).strip();
1778
1830
  var parseScanPage = (raw) => parsePagedWithItemSchema(ScanBriefSchema, raw, "scans");
1779
1831
  var TagDefinitionSchema = schemas.TagDefinitionWithStatsResponse.pick({
@@ -1865,10 +1917,7 @@ function createHttpApiGateway(config) {
1865
1917
  if (dispatcher !== void 0) {
1866
1918
  baseInit["dispatcher"] = dispatcher;
1867
1919
  }
1868
- return fetch(
1869
- input.url,
1870
- baseInit
1871
- );
1920
+ return fetch(input.url, baseInit);
1872
1921
  };
1873
1922
  const client = createClient({
1874
1923
  baseUrl,
@@ -1933,10 +1982,7 @@ function createHttpApiGateway(config) {
1933
1982
  }
1934
1983
  };
1935
1984
  if (dispatcher !== void 0) init["dispatcher"] = dispatcher;
1936
- response = await fetch(
1937
- url,
1938
- init
1939
- );
1985
+ response = await fetch(url, init);
1940
1986
  } catch (cause) {
1941
1987
  logger.warn({ api_path: path, elapsed_ms: Date.now() - startedAtMs }, "api.network_error");
1942
1988
  const inner = cause instanceof Error && cause.cause instanceof Error ? cause.cause : cause instanceof Error ? cause : null;
@@ -3245,13 +3291,14 @@ var TransactionTypeEnum = z.enum([
3245
3291
  "admin_adjustment",
3246
3292
  "refund",
3247
3293
  "invoice_settlement",
3248
- "crypto_top_up"
3294
+ "crypto_top_up",
3295
+ "card_top_up"
3249
3296
  ]);
3250
3297
  var ListBalanceHistoryInputShape = {
3251
3298
  date_from: z.string().date().optional().describe("ISO date, inclusive."),
3252
3299
  date_to: z.string().date().optional().describe("ISO date, inclusive."),
3253
- type: z.array(TransactionTypeEnum).max(9).optional().describe(
3254
- "Filter by transaction kind. Pass several values to OR them (e.g. ['top_up_manual','crypto_top_up'] for credits-only)."
3300
+ type: z.array(TransactionTypeEnum).max(TransactionTypeEnum.options.length).optional().describe(
3301
+ "Filter by transaction kind. Pass several values to OR them (e.g. ['top_up_manual','crypto_top_up','card_top_up'] for credits-only)."
3255
3302
  ),
3256
3303
  page: z.number().int().min(1).max(500).default(1).describe("1-indexed page."),
3257
3304
  limit: z.number().int().min(1).max(200).default(50).describe("Page size.")
@@ -3558,6 +3605,28 @@ var cancelCampaignTool = {
3558
3605
  return ok(result.value);
3559
3606
  }
3560
3607
  };
3608
+ var repeatRetryFields = {
3609
+ repeat_count: z.number().int().min(1).max(20).optional().describe(
3610
+ "How many times to repeat every scan this call already produces (1-20). It is a multiplier on top of the tool's own fan-out \u2014 see that tool's description for the exact formula \u2014 and each resulting repeat is a full scan with its own report and its own billing. 1-20 is the API's default ceiling; an operator can lower it during an incident, and then a value accepted here still comes back as a 422 from the API. Default: 1."
3611
+ ),
3612
+ repeat_mode: z.enum(["isolated", "shared"]).optional().describe(
3613
+ "How the repeats of one combination relate to each other. 'isolated' gives every repeat a fresh browser and a new IP, so the repeats are statistically independent. 'shared' runs all repeats of one combination in a single browser behind one IP, carrying cookies and localStorage from one repeat to the next \u2014 use it to reproduce a cloaker or a frequency cap that only misbehaves on the second or third visit. 'shared' is rejected with 422 on an ad-discovery target: when creating, that is `ad_discovery: true` on a scan or `campaign_type: \"ad_discovery\"` on a campaign; when updating a campaign, it is a campaign whose existing type is already ad_discovery, since campaign_type itself cannot be changed. Default: isolated."
3614
+ ),
3615
+ retry_max_attempts: z.number().int().min(0).max(5).optional().describe(
3616
+ "Extra crawl attempts when a scan fails for a technical reason \u2014 dead proxy, navigation timeout, browser crash (0-5). Permanent failures are never retried. The same scan is reused and only a completed scan is billed, so a retry never double-charges. 0-5 is the API's default ceiling; an operator can lower it, and then a value accepted here still comes back as a 422 from the API. Default: 0."
3617
+ )
3618
+ };
3619
+ function pickRepeatRetryBody(input) {
3620
+ const body = {};
3621
+ if (input.repeat_count !== void 0) body.repeat_count = input.repeat_count;
3622
+ if (input.repeat_mode !== void 0) body.repeat_mode = input.repeat_mode;
3623
+ if (input.retry_max_attempts !== void 0) {
3624
+ body.retry_max_attempts = input.retry_max_attempts;
3625
+ }
3626
+ return body;
3627
+ }
3628
+
3629
+ // src/application/tools/campaigns/_campaign-config-fields.ts
3561
3630
  var campaignConfigFields = {
3562
3631
  emulator_categories: z.array(z.string()).optional().describe(
3563
3632
  "Device categories to rotate through, e.g. ['android_phone']. Valid: android_phone, android_tablet, iphone, ipad, windows_desktop, macos_desktop. On create, omitting this defaults to ['android_phone']; pass [] together with emulator_specific_ids to target ONLY specific devices."
@@ -3572,6 +3641,7 @@ var campaignConfigFields = {
3572
3641
  proxy_region: z.string().optional().describe("Proxy region/state targeting (free-text; only honoured for a single country)."),
3573
3642
  proxy_city: z.string().optional().describe("Proxy city targeting."),
3574
3643
  proxy_isp: z.string().optional().describe("Proxy ISP targeting."),
3644
+ ...repeatRetryFields,
3575
3645
  schedule_type: z.enum(["weekly", "interval"]).optional().describe(
3576
3646
  "Scheduling mode: 'weekly' (run on a weekday/hour grid via schedule_weekly) or 'interval' (run every schedule_interval_seconds). Omit for a manual (run-on-demand) campaign."
3577
3647
  ),
@@ -3584,7 +3654,7 @@ var campaignConfigFields = {
3584
3654
  schedule_timezone: z.string().optional().describe("IANA timezone for the weekly grid, e.g. 'Europe/Berlin'. Default: UTC.")
3585
3655
  };
3586
3656
  function pickCampaignConfigBody(input) {
3587
- const body = {};
3657
+ const body = { ...pickRepeatRetryBody(input) };
3588
3658
  if (input.emulator_categories !== void 0) body.emulator_categories = input.emulator_categories;
3589
3659
  if (input.emulator_specific_ids !== void 0) {
3590
3660
  body.emulator_specific_ids = input.emulator_specific_ids;
@@ -3625,7 +3695,7 @@ var CreateCampaignInputShape = {
3625
3695
  };
3626
3696
  var createCampaignTool = {
3627
3697
  name: "create_campaign",
3628
- description: "Create a recurring scan campaign (template). The schedule produces N scans per run where N = number of countries times number of device profiles. Scans cost credits when they run, not when the campaign is created.",
3698
+ description: "Create a recurring scan campaign (template). The schedule produces N scans per run where N = number of countries times number of device profiles times `repeat_count`. Scans cost credits when they run, not when the campaign is created, so a high `repeat_count` multiplies the bill on every run. The returned campaign echoes `repeat_count`, `repeat_mode`, and `retry_max_attempts`, so read it back to confirm what the campaign will do.",
3629
3699
  annotations: {
3630
3700
  title: "Create Campaign",
3631
3701
  readOnlyHint: false,
@@ -3658,7 +3728,7 @@ var GetCampaignInputShape = {
3658
3728
  };
3659
3729
  var getCampaignTool = {
3660
3730
  name: "get_campaign",
3661
- description: "Get one campaign by UUID: name, type (url|ad_tag|vast|ad_discovery), target URL / ad-tag / VAST tag (vast_tag), countries, schedule status, archive status, parent group. `ad_discovery` campaigns store the publisher page in the URL field.",
3731
+ description: "Get one campaign by UUID: name, type (url|ad_tag|vast|ad_discovery), target URL / ad-tag / VAST tag (vast_tag), countries, emulator selection, proxy targeting, schedule status, archive status, parent group. Also echoes the repeat / retry settings \u2014 `repeat_count`, `repeat_mode`, `retry_max_attempts` \u2014 so this is how you confirm what `create_campaign` or `update_campaign` actually saved, and how many scans a run will produce (countries x device profiles x `repeat_count`). `ad_discovery` campaigns store the publisher page in the URL field.",
3662
3732
  annotations: {
3663
3733
  title: "Get Campaign",
3664
3734
  readOnlyHint: true,
@@ -3709,7 +3779,7 @@ var ListCampaignsInputShape = {
3709
3779
  };
3710
3780
  var listCampaignsTool = {
3711
3781
  name: "list_campaigns",
3712
- description: "List campaigns for the caller's organization, optionally filtered by group, archived flag, or name substring. Paginated.",
3782
+ description: "List campaigns for the caller's organization, optionally filtered by group, archived flag, or name substring. Paginated. Each row carries the same fields as `get_campaign`, including the repeat / retry settings (`repeat_count`, `repeat_mode`, `retry_max_attempts`) \u2014 use them to spot the campaigns that multiply their per-run scan count.",
3713
3783
  annotations: {
3714
3784
  title: "List Campaigns",
3715
3785
  readOnlyHint: true,
@@ -4676,11 +4746,12 @@ var CreateBulkScansInputShape = {
4676
4746
  country_codes: z.array(z.string().length(2)).min(1).max(50).describe("List of ISO 3166-1 alpha-2 country codes; one scan per country is created."),
4677
4747
  emulator_id: z.string().min(1).max(100).describe("Device/OS profile slug; same for every country in the batch."),
4678
4748
  proxy: scanProxyField,
4679
- labels: z.record(z.string()).optional().describe("Arbitrary metadata copied onto every created scan.")
4749
+ labels: z.record(z.string()).optional().describe("Arbitrary metadata copied onto every created scan."),
4750
+ ...repeatRetryFields
4680
4751
  };
4681
4752
  var createBulkScansTool = {
4682
4753
  name: "create_bulk_scans",
4683
- description: "Queue one new scan per country in a single call (e.g. test the same URL, ad-tag, or VAST video tag from US + DE + JP). COSTS N CREDITS where N = number of countries. Returns the list of created scans.",
4754
+ description: "Queue one new scan per country in a single call (e.g. test the same URL, ad-tag, or VAST video tag from US + DE + JP). COSTS N CREDITS where N = number of countries times `repeat_count`. Returns one entry per country; each entry's `repeat_scan_ids` lists that country's extra repeats.",
4684
4755
  annotations: {
4685
4756
  title: "Create Bulk Scans",
4686
4757
  readOnlyHint: false,
@@ -4697,7 +4768,8 @@ var createBulkScansTool = {
4697
4768
  ...input.ad_tag !== void 0 ? { ad_tag: input.ad_tag } : {},
4698
4769
  ...input.vast_tag !== void 0 ? { vast_tag: input.vast_tag } : {},
4699
4770
  ...input.proxy !== void 0 ? { proxy: input.proxy } : {},
4700
- ...input.labels !== void 0 ? { labels: input.labels } : {}
4771
+ ...input.labels !== void 0 ? { labels: input.labels } : {},
4772
+ ...pickRepeatRetryBody(input)
4701
4773
  };
4702
4774
  const result = await ctx.api.createBulkScans(body);
4703
4775
  if (result.isErr()) return err(mapApiError(result.error));
@@ -4722,11 +4794,12 @@ var CreateScanInputShape = {
4722
4794
  run_id: z.string().uuid().optional().describe("Optional run UUID inside the campaign."),
4723
4795
  ad_discovery: z.boolean().optional().describe(
4724
4796
  "Publisher ad discovery: detect ad blocks on the page and spawn one child scan per detected ad (banner/pop). Only valid with `url`. Each child is a separate billed scan; list them with `list_scan_children`."
4725
- )
4797
+ ),
4798
+ ...repeatRetryFields
4726
4799
  };
4727
4800
  var createScanTool = {
4728
4801
  name: "create_scan",
4729
- description: "Queue a single new scan for a URL, ad-tag, or VAST video tag against one country. COSTS CREDITS and bills the caller's organization. Returns the newly-created scan record.",
4802
+ description: "Queue a single new scan for a URL, ad-tag, or VAST video tag against one country. Takes one target and one country \u2014 there is no multi-URL form; call it once per URL. COSTS CREDITS and bills the caller's organization: this call creates exactly `repeat_count` scans (default 1) and bills every one of them. Returns the newly-created scan record; when `repeat_count` > 1 the response's `repeat_scan_ids` lists the sibling scans this call also created.",
4730
4803
  annotations: {
4731
4804
  title: "Create Scan",
4732
4805
  readOnlyHint: false,
@@ -4746,7 +4819,8 @@ var createScanTool = {
4746
4819
  ...input.labels !== void 0 ? { labels: input.labels } : {},
4747
4820
  ...input.campaign_id !== void 0 ? { campaign_id: input.campaign_id } : {},
4748
4821
  ...input.run_id !== void 0 ? { run_id: input.run_id } : {},
4749
- ...input.ad_discovery !== void 0 ? { ad_discovery: input.ad_discovery } : {}
4822
+ ...input.ad_discovery !== void 0 ? { ad_discovery: input.ad_discovery } : {},
4823
+ ...pickRepeatRetryBody(input)
4750
4824
  };
4751
4825
  const result = await ctx.api.createScan(body);
4752
4826
  if (result.isErr()) return err(mapApiError(result.error));
@@ -4758,7 +4832,7 @@ var GetScanInputShape = {
4758
4832
  };
4759
4833
  var getScanTool = {
4760
4834
  name: "get_scan",
4761
- description: "Get full detail for one scan by UUID: status, offer URL, absolute screenshot URL, report_url + public_report_url deep-links, timing, labels, and the parent campaign if any. VAST video scans also carry `vast_tag`, `creative_kind` (banner|video), and a `video` block (duration, media-file URL, VAST version, ad system, VPAID flag, wrapper depth). Link users with the returned `report_url` / `public_report_url` \u2014 never construct URLs yourself.",
4835
+ description: "Get full detail for one scan by UUID: status, offer URL, absolute screenshot URL, report_url + public_report_url deep-links, timing, labels, and the parent campaign if any. VAST video scans also carry `vast_tag`, `creative_kind` (`banner` or `video` today; treat it as an open string), and a `video` block (duration, media-file URL, the creative's `click_through` destination, VAST version, ad system, VPAID flag, wrapper depth). Repeats and retries: `repeat_index` / `repeat_total` place this scan inside its repeat group (0-based, so 2 of 5 reads as repeat_index 1), a non-null `repeat_session_id` means the group ran in `shared` mode (same browser, same IP, cookies carried over) and is the key to correlate its members \u2014 a shared group of one has nothing to carry over, so it stays null \u2014 `repeat_scan_ids` is filled only on the create response, and `retry_attempt` / `retry_max_attempts` show how many technical re-crawls this scan already consumed. Link users with the returned `report_url` / `public_report_url` \u2014 never construct URLs yourself.",
4762
4836
  annotations: {
4763
4837
  title: "Get Scan",
4764
4838
  readOnlyHint: true,
@@ -4797,7 +4871,9 @@ var getScanCreativeScreenshotTool = {
4797
4871
  var GetScanLandingScreenshotInputShape = {
4798
4872
  scan_id: z.string().uuid().describe("Scan UUID (ad-tag scan)."),
4799
4873
  landing_ord: z.number().int().min(0).max(50).describe("0-indexed landing slot (see `get_scan(...).landings[*].ord`)."),
4800
- width: z.number().int().min(50).max(2e3).optional().describe("Optional resize width in pixels.")
4874
+ width: z.number().int().min(50).max(2e3).optional().describe(
4875
+ "Optional resize width in pixels. The API top-crops a resized landing screenshot whose height exceeds 2.5x its width, so a long landing page comes back as its top region only \u2014 never conclude that something is absent from the page from a resized capture. Fetch without `width` when you need the whole page."
4876
+ )
4801
4877
  };
4802
4878
  var getScanLandingScreenshotTool = {
4803
4879
  name: "get_scan_landing_screenshot",
@@ -4823,7 +4899,7 @@ var getScanLandingScreenshotTool = {
4823
4899
  var GetScanScreenshotInputShape = {
4824
4900
  scan_id: z.string().uuid().describe("Scan UUID."),
4825
4901
  width: z.number().int().min(50).max(2e3).optional().describe(
4826
- "Optional resize width in pixels. The API resizes server-side to keep the image small for the agent's context window."
4902
+ "Optional resize width in pixels. The API resizes server-side to keep the image small for the agent's context window. It also top-crops a resized page screenshot whose height exceeds 2.5x its width, so a long page comes back as its top region only \u2014 never conclude that something is absent from the page from a resized capture. Fetch without `width` when you need the whole page."
4827
4903
  )
4828
4904
  };
4829
4905
  var getScanScreenshotTool = {
@@ -4850,7 +4926,7 @@ var ListScanChildrenInputShape = {
4850
4926
  };
4851
4927
  var listScanChildrenTool = {
4852
4928
  name: "list_scan_children",
4853
- description: "List the discovered-ad child scans of a publisher ad-discovery scan \u2014 one per detected ad block on the page. Each child brief carries ad_kind (banner|pop), the attributed ad network, and slot_index. Returns a paginated envelope with screenshot + report deep-links; link users with those, never hand-build URLs.",
4929
+ description: "List the discovered-ad child scans of a publisher ad-discovery scan \u2014 one per detected ad block on the page. Each child brief carries ad_kind (banner|pop), the attributed ad network, and slot_index. The repeat / retry fields are present but never meaningful here: a child is always its own group of one (`repeat_index` 0, `repeat_total` 1, `repeat_session_id` null) and is never re-crawled, because repeats and retries apply to the publisher scan \u2014 repeating an ad-discovery scan gives you several parents, each with its own children. Returns a paginated envelope with screenshot + report deep-links; link users with those, never hand-build URLs.",
4854
4930
  annotations: {
4855
4931
  title: "List Scan Children",
4856
4932
  readOnlyHint: true,
@@ -4901,7 +4977,7 @@ var ListScansInputShape = {
4901
4977
  };
4902
4978
  var listScansTool = {
4903
4979
  name: "list_scans",
4904
- description: "List scans for the caller's organization with optional filters (status, country, URL substring, date range, run/campaign/group, tag, AI/IAB/brand category, dynamic labels). Returns a paginated envelope; each brief carries `is_ad_tag` and `is_vast` flags, an absolute screenshot URL plus report_url (auth dashboard) and public_report_url (shareable) deep-links \u2014 link users with those, never hand-build URLs.",
4980
+ description: "List scans for the caller's organization with optional filters (status, country, URL substring, date range, run/campaign/group, tag, AI/IAB/brand category, dynamic labels). Returns a paginated envelope; each brief carries `is_ad_tag` and `is_vast` flags, an absolute screenshot URL plus report_url (auth dashboard) and public_report_url (shareable) deep-links \u2014 link users with those, never hand-build URLs. Repeated scans look like near-duplicate rows: `repeat_index` / `repeat_total` place each one inside its repeat group (0-based) and a non-null `repeat_session_id` means the group ran in `shared` mode (one browser, one IP, cookies carried across repeats) \u2014 group by it to compare the repeats of one combination. `retry_attempt` / `retry_max_attempts` count technical re-crawls of that same scan, not extra scans. `repeat_scan_ids` is not part of this response; use `get_scan` for a single scan's full detail.",
4905
4981
  annotations: {
4906
4982
  title: "List Scans",
4907
4983
  readOnlyHint: true,
@@ -5535,5 +5611,5 @@ function formatToolError(error) {
5535
5611
  }
5536
5612
 
5537
5613
  export { BearerToken, SERVER_INSTRUCTIONS, createHttpApiGateway, createPinoLogger, declareEmptyResourcesAndPrompts, newRequestId, wireToolsIntoMcpServer };
5538
- //# sourceMappingURL=chunk-R56UDCOA.js.map
5539
- //# sourceMappingURL=chunk-R56UDCOA.js.map
5614
+ //# sourceMappingURL=chunk-5UVZYMC3.js.map
5615
+ //# sourceMappingURL=chunk-5UVZYMC3.js.map