@kaminari-ad/mcp 0.3.0 → 0.5.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.
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { err, ok } from './chunk-KTQBHJUB.js';
2
+ import { err, ok } from './chunk-PG5IDZWQ.js';
3
3
  import { createHash, randomUUID } from 'crypto';
4
4
  import createClient from 'openapi-fetch';
5
5
  import { fetch } from 'undici';
@@ -332,6 +332,8 @@ var ScanResponse = z.object({
332
332
  offer_url: z.string(),
333
333
  redirect_chain: z.array(RedirectHopResponse),
334
334
  screenshot_url: z.string().optional().default(""),
335
+ report_url: z.string().optional().default(""),
336
+ public_report_url: z.string().optional().default(""),
335
337
  ad_tag: z.union([z.string(), z.null()]).optional(),
336
338
  creative_screenshot_url: z.string().optional().default(""),
337
339
  creative_width: z.number().int().optional().default(0),
@@ -357,6 +359,8 @@ var ScanBriefResponse = z.object({
357
359
  status: ScanStatus,
358
360
  offer_url: z.string(),
359
361
  screenshot_url: z.string().optional().default(""),
362
+ report_url: z.string().optional().default(""),
363
+ public_report_url: z.string().optional().default(""),
360
364
  labels: z.record(z.string()).optional(),
361
365
  classification: z.union([ScanClassificationResponse, z.null()]).optional(),
362
366
  elapsed_ms: z.number().int(),
@@ -533,6 +537,8 @@ var ScanTileResponse = z.object({
533
537
  status: z.string(),
534
538
  offer_url: z.string().optional().default(""),
535
539
  screenshot_url: z.string().optional().default(""),
540
+ report_url: z.string().optional().default(""),
541
+ public_report_url: z.string().optional().default(""),
536
542
  elapsed_ms: z.number().int().optional().default(0),
537
543
  error: z.string().optional().default("")
538
544
  }).passthrough();
@@ -1201,7 +1207,13 @@ var BillingSummarySchema = schemas.BillingSummaryResponse.pick({
1201
1207
  is_suspended: true,
1202
1208
  can_create_scan: true,
1203
1209
  billing_mode: true,
1204
- block_reason: true
1210
+ block_reason: true,
1211
+ current_plan_is_custom: true,
1212
+ credit_limit_micros: true,
1213
+ effective_minimum_balance_micros: true,
1214
+ scheduled_next_plan_id: true,
1215
+ scheduled_next_plan_name: true,
1216
+ scheduled_effective_at: true
1205
1217
  }).strip();
1206
1218
  var parseBillingSummary = (raw) => parseWithSchema(BillingSummarySchema, raw, "billing-summary");
1207
1219
 
@@ -1214,10 +1226,18 @@ var CampaignSchema = schemas.CampaignResponse.pick({
1214
1226
  ad_tag: true,
1215
1227
  country_codes: true,
1216
1228
  group_id: true,
1229
+ emulator_selection: true,
1230
+ proxy_type: true,
1231
+ proxy_region: true,
1232
+ proxy_city: true,
1233
+ proxy_isp: true,
1217
1234
  labels: true,
1218
1235
  policy_set_id: true,
1219
1236
  schedule_enabled: true,
1220
1237
  schedule_type: true,
1238
+ schedule_weekly: true,
1239
+ schedule_interval_seconds: true,
1240
+ schedule_timezone: true,
1221
1241
  is_archived: true,
1222
1242
  created_at: true,
1223
1243
  last_run_at: true
@@ -1390,6 +1410,7 @@ var UserSchema = schemas.UserResponse.pick({
1390
1410
  id: true,
1391
1411
  email: true,
1392
1412
  name: true,
1413
+ role_id: true,
1393
1414
  role_name: true,
1394
1415
  is_active: true,
1395
1416
  created_at: true
@@ -1615,6 +1636,8 @@ var ScanTileSchema = schemas.ScanTileResponse.pick({
1615
1636
  status: true,
1616
1637
  offer_url: true,
1617
1638
  screenshot_url: true,
1639
+ report_url: true,
1640
+ public_report_url: true,
1618
1641
  elapsed_ms: true,
1619
1642
  error: true
1620
1643
  }).strip();
@@ -1627,6 +1650,8 @@ var ScanSchema = schemas.ScanResponse.pick({
1627
1650
  status: true,
1628
1651
  offer_url: true,
1629
1652
  screenshot_url: true,
1653
+ report_url: true,
1654
+ public_report_url: true,
1630
1655
  ad_tag: true,
1631
1656
  creative_screenshot_url: true,
1632
1657
  page_title: true,
@@ -1650,6 +1675,8 @@ var ScanBriefSchema = schemas.ScanBriefResponse.pick({
1650
1675
  status: true,
1651
1676
  offer_url: true,
1652
1677
  screenshot_url: true,
1678
+ report_url: true,
1679
+ public_report_url: true,
1653
1680
  labels: true,
1654
1681
  elapsed_ms: true,
1655
1682
  campaign_id: true,
@@ -2519,6 +2546,18 @@ function wrap(impl) {
2519
2546
  }
2520
2547
  };
2521
2548
  }
2549
+
2550
+ // src/shared/server-instructions.ts
2551
+ var SERVER_INSTRUCTIONS = [
2552
+ "Kaminari Ad \u2014 ad-verification API (malvertising, scams, policy violations).",
2553
+ "",
2554
+ "Linking to a scan: use the URL fields the tools return \u2014 never build or guess them.",
2555
+ "- `report_url`: authenticated dashboard report (requires login).",
2556
+ "- `public_report_url`: shareable report (no login).",
2557
+ "- `screenshot_url` / `creative_screenshot_url`: absolute, directly followable.",
2558
+ "These come from `get_scan`, `list_scans`, and `list_run_scans`. Do NOT assemble",
2559
+ "scan/app URLs from a base host yourself \u2014 the apex domain does not serve scan pages."
2560
+ ].join("\n");
2522
2561
  function declareEmptyResourcesAndPrompts(server) {
2523
2562
  server.server.registerCapabilities({ resources: {}, prompts: {} });
2524
2563
  server.server.setRequestHandler(ListResourcesRequestSchema, async () => {
@@ -2637,7 +2676,8 @@ var getAccountTool = {
2637
2676
  var InviteUserInputShape = {
2638
2677
  email: z.string().email().describe("Email of the person to invite. They get a signup link."),
2639
2678
  role_id: z.string().uuid().describe("UUID of the role to assign on accept. Get UUIDs from `list_org_roles`."),
2640
- name: z.string().min(1).optional().describe("Optional display name for the invitee.")
2679
+ name: z.string().min(1).optional().describe("Optional display name for the invitee."),
2680
+ timezone: z.string().optional().describe("Optional IANA timezone for the invitee, e.g. 'Europe/Berlin'. Default: UTC.")
2641
2681
  };
2642
2682
  var inviteUserTool = {
2643
2683
  name: "invite_user",
@@ -2656,6 +2696,7 @@ var inviteUserTool = {
2656
2696
  role_id: input.role_id
2657
2697
  };
2658
2698
  if (input.name !== void 0) body.name = input.name;
2699
+ if (input.timezone !== void 0) body.timezone = input.timezone;
2659
2700
  const result = await ctx.api.inviteUser(body);
2660
2701
  if (result.isErr()) return err(mapApiError(result.error));
2661
2702
  return ok(result.value);
@@ -2833,12 +2874,11 @@ var updateAccountLabelsTool = {
2833
2874
  }
2834
2875
  };
2835
2876
  var UpdateOrgInputShape = {
2836
- name: z.string().min(1).max(200).optional().describe("New organization display name."),
2837
- settings: z.record(z.unknown()).optional().describe("Replacement settings object. Fields not supplied are left unchanged.")
2877
+ name: z.string().min(1).max(200).optional().describe("New organization display name.")
2838
2878
  };
2839
2879
  var updateOrgTool = {
2840
2880
  name: "update_org",
2841
- description: "Update the caller's organization \u2014 display name and/or settings object. Only supplied fields are touched.",
2881
+ description: "Update the caller's organization display name.",
2842
2882
  annotations: {
2843
2883
  title: "Update Organization",
2844
2884
  readOnlyHint: false,
@@ -2849,8 +2889,7 @@ var updateOrgTool = {
2849
2889
  inputSchema: z.object(UpdateOrgInputShape),
2850
2890
  handler: async (input, ctx) => {
2851
2891
  const body = {
2852
- ...input.name !== void 0 ? { name: input.name } : {},
2853
- ...input.settings !== void 0 ? { settings: input.settings } : {}
2892
+ ...input.name !== void 0 ? { name: input.name } : {}
2854
2893
  };
2855
2894
  const result = await ctx.api.updateOrg(body);
2856
2895
  if (result.isErr()) return err(mapApiError(result.error));
@@ -3356,12 +3395,11 @@ var unarchiveCampaignGroupTool = {
3356
3395
  };
3357
3396
  var UpdateCampaignGroupInputShape = {
3358
3397
  group_id: z.string().uuid().describe("Group UUID to update."),
3359
- name: z.string().min(1).max(200).optional().describe("New display name."),
3360
- schedule_paused: z.boolean().optional().describe("Pause/resume the scheduler for every campaign in this group.")
3398
+ name: z.string().min(1).max(200).optional().describe("New display name.")
3361
3399
  };
3362
3400
  var updateCampaignGroupTool = {
3363
3401
  name: "update_campaign_group",
3364
- description: "Update a campaign group: rename, or pause/resume the scheduler (the pause cascades to every campaign in the group).",
3402
+ description: "Rename a campaign group. To pause/resume its scheduler, use `pause_campaign_group_schedule` / `resume_campaign_group_schedule` instead.",
3365
3403
  annotations: {
3366
3404
  title: "Update Campaign Group",
3367
3405
  readOnlyHint: false,
@@ -3372,8 +3410,7 @@ var updateCampaignGroupTool = {
3372
3410
  inputSchema: z.object(UpdateCampaignGroupInputShape),
3373
3411
  handler: async (input, ctx) => {
3374
3412
  const body = {
3375
- ...input.name !== void 0 ? { name: input.name } : {},
3376
- ...input.schedule_paused !== void 0 ? { schedule_paused: input.schedule_paused } : {}
3413
+ ...input.name !== void 0 ? { name: input.name } : {}
3377
3414
  };
3378
3415
  const result = await ctx.api.updateCampaignGroup(input.group_id, body);
3379
3416
  if (result.isErr()) return err(mapApiError(result.error));
@@ -3420,6 +3457,52 @@ var cancelCampaignTool = {
3420
3457
  return ok(result.value);
3421
3458
  }
3422
3459
  };
3460
+ var campaignConfigFields = {
3461
+ emulator_categories: z.array(z.string()).optional().describe(
3462
+ "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."
3463
+ ),
3464
+ emulator_specific_ids: z.array(z.string()).optional().describe(
3465
+ "Pin exact device profile slugs from `list_emulators` (the `id` field), e.g. ['samsung_galaxy_s23_ultra_android16']. Always scanned regardless of emulator_mode."
3466
+ ),
3467
+ emulator_mode: z.enum(["random", "all"]).optional().describe(
3468
+ "How selected categories expand per run: 'random' = one random device per category (the UI's 'Random (1 per group)'), 'all' = every device in each category ('All checked'). Does not affect emulator_specific_ids. Default: random."
3469
+ ),
3470
+ proxy_type: z.enum(["residential", "mobile"]).optional().describe("Proxy network type. Default: residential."),
3471
+ proxy_region: z.string().optional().describe("Proxy region/state targeting (free-text; only honoured for a single country)."),
3472
+ proxy_city: z.string().optional().describe("Proxy city targeting."),
3473
+ proxy_isp: z.string().optional().describe("Proxy ISP targeting."),
3474
+ schedule_type: z.enum(["weekly", "interval"]).optional().describe(
3475
+ "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."
3476
+ ),
3477
+ schedule_weekly: z.record(z.array(z.number().int().min(0).max(23))).optional().describe(
3478
+ "Weekly run grid for schedule_type='weekly'. Keys are weekdays '0'-'6' (Mon-Sun); values are hours 0-23 in schedule_timezone, e.g. { '0': [9, 17], '4': [12] }."
3479
+ ),
3480
+ schedule_interval_seconds: z.number().int().optional().describe(
3481
+ "Run interval for schedule_type='interval'. Allowed presets (seconds): 60, 120, 300, 600, 900, 1800, 2700, 3600, 7200, 14400, 28800, 86400."
3482
+ ),
3483
+ schedule_timezone: z.string().optional().describe("IANA timezone for the weekly grid, e.g. 'Europe/Berlin'. Default: UTC.")
3484
+ };
3485
+ function pickCampaignConfigBody(input) {
3486
+ const body = {};
3487
+ if (input.emulator_categories !== void 0) body.emulator_categories = input.emulator_categories;
3488
+ if (input.emulator_specific_ids !== void 0) {
3489
+ body.emulator_specific_ids = input.emulator_specific_ids;
3490
+ }
3491
+ if (input.emulator_mode !== void 0) body.emulator_mode = input.emulator_mode;
3492
+ if (input.proxy_type !== void 0) body.proxy_type = input.proxy_type;
3493
+ if (input.proxy_region !== void 0) body.proxy_region = input.proxy_region;
3494
+ if (input.proxy_city !== void 0) body.proxy_city = input.proxy_city;
3495
+ if (input.proxy_isp !== void 0) body.proxy_isp = input.proxy_isp;
3496
+ if (input.schedule_type !== void 0) body.schedule_type = input.schedule_type;
3497
+ if (input.schedule_weekly !== void 0) body.schedule_weekly = input.schedule_weekly;
3498
+ if (input.schedule_interval_seconds !== void 0) {
3499
+ body.schedule_interval_seconds = input.schedule_interval_seconds;
3500
+ }
3501
+ if (input.schedule_timezone !== void 0) body.schedule_timezone = input.schedule_timezone;
3502
+ return body;
3503
+ }
3504
+
3505
+ // src/application/tools/campaigns/create-campaign.tool.ts
3423
3506
  var CreateCampaignInputShape = {
3424
3507
  name: z.string().min(1).max(200).describe("Display name (1-200 chars)."),
3425
3508
  campaign_type: z.enum(["url", "ad_tag"]).describe("`url` or `ad_tag` \u2014 must match the target field below."),
@@ -3427,7 +3510,7 @@ var CreateCampaignInputShape = {
3427
3510
  ad_tag: z.string().optional().describe("Ad-tag HTML/JS (required if campaign_type=ad_tag)."),
3428
3511
  country_codes: z.array(z.string().length(2)).min(1).describe("ISO 3166-1 alpha-2 codes \u2014 one scan per country per run."),
3429
3512
  group_id: z.string().uuid().optional().describe("Parent group UUID; defaults to the org's default group."),
3430
- emulator_categories: z.array(z.string()).optional().describe("Categories of device profiles to rotate through. Default: all available."),
3513
+ ...campaignConfigFields,
3431
3514
  labels: z.record(z.string()).optional().describe("Arbitrary metadata applied to every queued scan."),
3432
3515
  policy_set_id: z.string().uuid().optional().describe("Policy set to evaluate every scan against."),
3433
3516
  schedule_enabled: z.boolean().optional().describe("If true, the scheduler runs immediately. Default: false (manual run).")
@@ -3451,10 +3534,10 @@ var createCampaignTool = {
3451
3534
  ...input.url !== void 0 ? { url: input.url } : {},
3452
3535
  ...input.ad_tag !== void 0 ? { ad_tag: input.ad_tag } : {},
3453
3536
  ...input.group_id !== void 0 ? { group_id: input.group_id } : {},
3454
- ...input.emulator_categories !== void 0 ? { emulator_categories: input.emulator_categories } : {},
3455
3537
  ...input.labels !== void 0 ? { labels: input.labels } : {},
3456
3538
  ...input.policy_set_id !== void 0 ? { policy_set_id: input.policy_set_id } : {},
3457
- ...input.schedule_enabled !== void 0 ? { schedule_enabled: input.schedule_enabled } : {}
3539
+ ...input.schedule_enabled !== void 0 ? { schedule_enabled: input.schedule_enabled } : {},
3540
+ ...pickCampaignConfigBody(input)
3458
3541
  };
3459
3542
  const result = await ctx.api.createCampaign(body);
3460
3543
  if (result.isErr()) return err(mapApiError(result.error));
@@ -3613,7 +3696,11 @@ var unarchiveCampaignTool = {
3613
3696
  var UpdateCampaignInputShape = {
3614
3697
  campaign_id: z.string().uuid().describe("Campaign UUID to update."),
3615
3698
  name: z.string().min(1).max(200).optional().describe("New display name."),
3699
+ url: z.string().url().optional().describe("New target URL (url-type campaigns)."),
3700
+ ad_tag: z.string().optional().describe("New ad-tag HTML/JS (ad_tag-type campaigns)."),
3616
3701
  country_codes: z.array(z.string().length(2)).optional().describe("Replace the country list."),
3702
+ group_id: z.string().uuid().optional().describe("Move the campaign to another group."),
3703
+ ...campaignConfigFields,
3617
3704
  labels: z.record(z.string()).optional().describe("Replace the label map."),
3618
3705
  policy_set_id: z.string().uuid().nullable().optional().describe("New policy set UUID; pass null to clear."),
3619
3706
  schedule_enabled: z.boolean().optional().describe("Pause / resume the scheduler.")
@@ -3632,10 +3719,14 @@ var updateCampaignTool = {
3632
3719
  handler: async (input, ctx) => {
3633
3720
  const body = {
3634
3721
  ...input.name !== void 0 ? { name: input.name } : {},
3722
+ ...input.url !== void 0 ? { url: input.url } : {},
3723
+ ...input.ad_tag !== void 0 ? { ad_tag: input.ad_tag } : {},
3635
3724
  ...input.country_codes !== void 0 ? { country_codes: input.country_codes } : {},
3725
+ ...input.group_id !== void 0 ? { group_id: input.group_id } : {},
3636
3726
  ...input.labels !== void 0 ? { labels: input.labels } : {},
3637
3727
  ...input.policy_set_id !== void 0 ? { policy_set_id: input.policy_set_id } : {},
3638
- ...input.schedule_enabled !== void 0 ? { schedule_enabled: input.schedule_enabled } : {}
3728
+ ...input.schedule_enabled !== void 0 ? { schedule_enabled: input.schedule_enabled } : {},
3729
+ ...pickCampaignConfigBody(input)
3639
3730
  };
3640
3731
  const result = await ctx.api.updateCampaign(input.campaign_id, body);
3641
3732
  if (result.isErr()) return err(mapApiError(result.error));
@@ -3643,17 +3734,25 @@ var updateCampaignTool = {
3643
3734
  }
3644
3735
  };
3645
3736
  var CreateCustomRuleInputShape = {
3646
- name: z.string().min(1).max(200).describe("Display name."),
3647
- tag_slug: z.string().max(100).optional().describe("Tag slug to assign on match. Empty = create-only (advanced)."),
3648
- rule_type: z.string().max(50).describe("Rule engine: regex | substring | iab_category | etc. (API validates)."),
3649
- config: z.record(z.unknown()).describe("Rule-type-specific configuration object. Shape depends on rule_type."),
3737
+ name: z.string().min(1).max(200).describe(
3738
+ "Display name. For non-LLM rules this also becomes the `display_name` of the auto-registered tag definition (see `tag_slug`)."
3739
+ ),
3740
+ tag_slug: z.string().max(100).optional().describe(
3741
+ "Tag slug to assign on match. Empty = create-only (advanced). The API auto-registers a custom tag definition for this slug with `display_name = name`. **MUST NOT collide with a built-in system tag slug** (see `list_tags` where `is_system=true`); colliding requests return 422 with code `checking.system_slug_reserved`. For `rule_type='llm'` use `config.tags` keys instead and leave `tag_slug` empty."
3742
+ ),
3743
+ rule_type: z.string().max(50).describe(
3744
+ "Rule engine. One of: `stopword_content`, `stopword_url`, `regexp_content`, `regexp_url`, `blacklist_domain`, `combo`, `llm`. The API validates."
3745
+ ),
3746
+ config: z.record(z.unknown()).describe(
3747
+ "Rule-type-specific configuration object. Shape depends on `rule_type`. For `rule_type='llm'` the shape is `{ prompt: string, tags: { <tag_slug>: <description>, ... } }`; each key in `config.tags` is auto-registered as a custom tag definition AND must not collide with a system slug (same 422 contract as `tag_slug`)."
3748
+ ),
3650
3749
  target: z.string().max(30).optional().describe(
3651
3750
  "Where to apply the rule (e.g. 'page' for landing HTML). Default: page. See API docs for the full set of valid values."
3652
3751
  )
3653
3752
  };
3654
3753
  var createCustomRuleTool = {
3655
3754
  name: "create_custom_rule",
3656
- description: "Define a custom tag-detection rule (regex / substring / category). Matches will tag every future scan; existing scans are untouched until you call `recheck_scans`.",
3755
+ description: "Define a custom tag-detection rule. The API auto-registers a tag definition for each slug the rule emits (`tag_slug` for non-LLM rules; `config.tags` keys for `rule_type='llm'`); slugs that collide with a built-in system tag are rejected with HTTP 422 / code `checking.system_slug_reserved`. Matches tag every future scan; existing scans are untouched until you call `recheck_scans`.",
3657
3756
  annotations: {
3658
3757
  title: "Create Custom Rule",
3659
3758
  readOnlyHint: false,
@@ -3735,8 +3834,12 @@ var listCustomRulesTool = {
3735
3834
  }
3736
3835
  };
3737
3836
  var TestCustomRuleInputShape = {
3738
- rule_type: z.string().max(50).describe("Rule engine type (regex, substring, ...)."),
3739
- config: z.record(z.unknown()).describe("Rule-type-specific config to test."),
3837
+ rule_type: z.string().max(50).describe(
3838
+ "Rule engine type. One of: `stopword_content`, `stopword_url`, `regexp_content`, `regexp_url`, `blacklist_domain`, `combo`, `llm`."
3839
+ ),
3840
+ config: z.record(z.unknown()).describe(
3841
+ "Rule-type-specific config to test. Same shape as `create_custom_rule`'s `config`. NOTE: `test_custom_rule` evaluates the rule against a scan WITHOUT persisting it, so slug-collision validation does NOT run here \u2014 verify slugs against `list_tags` (`is_system=true`) before promoting to `create_custom_rule`."
3842
+ ),
3740
3843
  target: z.string().max(30).describe(
3741
3844
  "Where to apply the rule (e.g. 'page' for landing HTML). See API docs for the full set of valid values."
3742
3845
  ),
@@ -3744,7 +3847,7 @@ var TestCustomRuleInputShape = {
3744
3847
  };
3745
3848
  var testCustomRuleTool = {
3746
3849
  name: "test_custom_rule",
3747
- description: "Preview-test a rule definition against an existing scan WITHOUT persisting the rule. Returns `matched: bool`, evaluation time, and per-tag-slug details. Use to validate config before `create_custom_rule`.",
3850
+ description: "Preview-test a rule definition against an existing scan WITHOUT persisting the rule. Returns `matched: bool`, evaluation time, and per-tag-slug details. Use to validate config before `create_custom_rule`. NOTE: slug-collision validation does NOT run in preview mode \u2014 verify any `tag_slug` (or LLM `config.tags` keys) against `list_tags` (`is_system=true`) before promoting to `create_custom_rule`, which would otherwise reject the same payload with HTTP 422 / code `checking.system_slug_reserved`.",
3748
3851
  annotations: {
3749
3852
  title: "Test Custom Rule",
3750
3853
  readOnlyHint: true,
@@ -3766,9 +3869,15 @@ var testCustomRuleTool = {
3766
3869
  };
3767
3870
  var UpdateCustomRuleInputShape = {
3768
3871
  rule_id: z.string().uuid().describe("Rule UUID to update."),
3769
- name: z.string().min(1).max(200).optional().describe("New display name."),
3770
- tag_slug: z.string().max(100).optional().describe("New tag slug to assign on match."),
3771
- config: z.record(z.unknown()).optional().describe("New rule-type-specific config object."),
3872
+ name: z.string().min(1).max(200).optional().describe(
3873
+ "New display name. For non-LLM rules this also becomes the new `display_name` of the auto-registered tag definition."
3874
+ ),
3875
+ tag_slug: z.string().max(100).optional().describe(
3876
+ "New tag slug to assign on match. **MUST NOT collide with a built-in system tag slug** (see `list_tags` where `is_system=true`); colliding requests return 422 with code `checking.system_slug_reserved`. Re-registering an existing custom slug refreshes its tag definition's `display_name` / `description`."
3877
+ ),
3878
+ config: z.record(z.unknown()).optional().describe(
3879
+ "New rule-type-specific config object. For `rule_type='llm'` the keys of `config.tags` are auto-registered as tag definitions; any key that collides with a system slug returns the same 422 contract."
3880
+ ),
3772
3881
  target: z.string().max(30).optional().describe(
3773
3882
  "Where to apply the rule (e.g. 'page' for landing HTML). See API docs for the full set of valid values."
3774
3883
  ),
@@ -3776,7 +3885,7 @@ var UpdateCustomRuleInputShape = {
3776
3885
  };
3777
3886
  var updateCustomRuleTool = {
3778
3887
  name: "update_custom_rule",
3779
- description: "Update a custom tag-detection rule. Only supplied fields are sent. To toggle activation, pass `is_active`. Existing tagged scans are NOT re-evaluated \u2014 call `recheck_scans` for that. (Rule engine `rule_type` cannot be changed after creation; create a new rule instead.)",
3888
+ description: "Update a custom tag-detection rule. Only supplied fields are sent. Changing `tag_slug` (or `config.tags` keys for `rule_type='llm'`) onto a built-in system slug returns HTTP 422 / code `checking.system_slug_reserved`. To toggle activation, pass `is_active`. Existing tagged scans are NOT re-evaluated \u2014 call `recheck_scans` for that. (Rule engine `rule_type` cannot be changed after creation; create a new rule instead.)",
3780
3889
  annotations: {
3781
3890
  title: "Update Custom Rule",
3782
3891
  readOnlyHint: false,
@@ -3990,7 +4099,7 @@ var updateCustomTaxonomyTool = {
3990
4099
  var ListEmulatorsInputShape = {};
3991
4100
  var listEmulatorsTool = {
3992
4101
  name: "list_emulators",
3993
- description: "List every device/OS emulator profile available for scans (id, display name, category, browser). Use the `id` as `emulator_id` in `create_scan` / `create_campaign`.",
4102
+ description: "List every device/OS emulator profile available for scans (id, display name, category, browser). For `create_scan`, use the `id` as `emulator_id`. For `create_campaign` / `update_campaign`, use the `category` in `emulator_categories` or the `id` in `emulator_specific_ids`.",
3994
4103
  annotations: {
3995
4104
  title: "List Emulators",
3996
4105
  readOnlyHint: true,
@@ -4395,7 +4504,7 @@ var ListRunScansInputShape = {
4395
4504
  };
4396
4505
  var listRunScansTool = {
4397
4506
  name: "list_run_scans",
4398
- description: "List the tile-scan items produced by one run (status, country, offer URL, screenshot, elapsed ms, error). For full scan details (input URL, labels, classification, redirect chain) fetch a specific scan via `get_scan`.",
4507
+ description: "List the tile-scan items produced by one run (status, country, offer URL, absolute screenshot URL, report_url + public_report_url deep-links, elapsed ms, error). Link users to a scan with the returned `report_url` (auth dashboard) or `public_report_url` (shareable) \u2014 never hand-build URLs. For full scan details (input URL, labels, classification, redirect chain) fetch a specific scan via `get_scan`.",
4399
4508
  annotations: {
4400
4509
  title: "List Run Scans",
4401
4510
  readOnlyHint: true,
@@ -4433,11 +4542,20 @@ var cancelScanTool = {
4433
4542
  return ok(result.value);
4434
4543
  }
4435
4544
  };
4545
+ var scanProxyField = z.object({
4546
+ proxy_type: z.enum(["residential", "mobile"]).optional().describe("Proxy network type. Default: residential."),
4547
+ region: z.string().optional().describe("Proxy region/state targeting."),
4548
+ city: z.string().optional().describe("Proxy city targeting."),
4549
+ isp: z.string().optional().describe("Proxy ISP targeting.")
4550
+ }).optional().describe("Optional proxy geo targeting for the crawl. Omit to use the org default.");
4551
+
4552
+ // src/application/tools/scans/create-bulk-scans.tool.ts
4436
4553
  var CreateBulkScansInputShape = {
4437
4554
  url: z.string().url().optional().describe("Direct URL. EITHER `url` OR `ad_tag` is required."),
4438
4555
  ad_tag: z.string().optional().describe("Raw ad-tag HTML/JS. EITHER `url` OR `ad_tag` is required."),
4439
4556
  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."),
4440
4557
  emulator_id: z.string().min(1).max(100).describe("Device/OS profile slug; same for every country in the batch."),
4558
+ proxy: scanProxyField,
4441
4559
  labels: z.record(z.string()).optional().describe("Arbitrary metadata copied onto every created scan.")
4442
4560
  };
4443
4561
  var createBulkScansTool = {
@@ -4457,6 +4575,7 @@ var createBulkScansTool = {
4457
4575
  emulator_id: input.emulator_id,
4458
4576
  ...input.url !== void 0 ? { url: input.url } : {},
4459
4577
  ...input.ad_tag !== void 0 ? { ad_tag: input.ad_tag } : {},
4578
+ ...input.proxy !== void 0 ? { proxy: input.proxy } : {},
4460
4579
  ...input.labels !== void 0 ? { labels: input.labels } : {}
4461
4580
  };
4462
4581
  const result = await ctx.api.createBulkScans(body);
@@ -4469,6 +4588,7 @@ var CreateScanInputShape = {
4469
4588
  ad_tag: z.string().optional().describe("Raw HTML/JS ad tag (script, iframe, image). EITHER `url` OR `ad_tag` is required."),
4470
4589
  country_code: z.string().length(2).describe("ISO 3166-1 alpha-2 country code, e.g. US, DE, JP. Determines proxy geo."),
4471
4590
  emulator_id: z.string().min(1).max(100).describe("Device/OS profile slug; use `list_emulators` to discover valid values."),
4591
+ proxy: scanProxyField,
4472
4592
  labels: z.record(z.string()).optional().describe("Arbitrary string -> string metadata attached to the scan."),
4473
4593
  campaign_id: z.string().uuid().optional().describe("Optional campaign UUID to attribute the scan to."),
4474
4594
  run_id: z.string().uuid().optional().describe("Optional run UUID inside the campaign.")
@@ -4490,6 +4610,7 @@ var createScanTool = {
4490
4610
  emulator_id: input.emulator_id,
4491
4611
  ...input.url !== void 0 ? { url: input.url } : {},
4492
4612
  ...input.ad_tag !== void 0 ? { ad_tag: input.ad_tag } : {},
4613
+ ...input.proxy !== void 0 ? { proxy: input.proxy } : {},
4493
4614
  ...input.labels !== void 0 ? { labels: input.labels } : {},
4494
4615
  ...input.campaign_id !== void 0 ? { campaign_id: input.campaign_id } : {},
4495
4616
  ...input.run_id !== void 0 ? { run_id: input.run_id } : {}
@@ -4504,7 +4625,7 @@ var GetScanInputShape = {
4504
4625
  };
4505
4626
  var getScanTool = {
4506
4627
  name: "get_scan",
4507
- description: "Get full detail for one scan by UUID: status, offer URL, screenshot URL, timing, labels, and the parent campaign if any.",
4628
+ 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. Link users with the returned `report_url` / `public_report_url` \u2014 never construct URLs yourself.",
4508
4629
  annotations: {
4509
4630
  title: "Get Scan",
4510
4631
  readOnlyHint: true,
@@ -4622,7 +4743,7 @@ var ListScansInputShape = {
4622
4743
  };
4623
4744
  var listScansTool = {
4624
4745
  name: "list_scans",
4625
- 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.",
4746
+ 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 scan carries 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.",
4626
4747
  annotations: {
4627
4748
  title: "List Scans",
4628
4749
  readOnlyHint: true,
@@ -5042,7 +5163,12 @@ var testWebhookTool = {
5042
5163
  var UpdateWebhookInputShape = {
5043
5164
  webhook_id: z.string().uuid().describe("Webhook UUID."),
5044
5165
  url: z.string().url().optional().describe("New endpoint URL."),
5166
+ description: z.string().max(256).optional().describe("New human-readable label."),
5045
5167
  event_types: z.array(z.string()).max(50).optional().describe("Replace the subscribed-event-types list."),
5168
+ campaign_ids: z.array(z.string().uuid()).optional().describe("Restrict deliveries to these campaign UUIDs (replaces the current set)."),
5169
+ clear_campaign_ids: z.boolean().optional().describe(
5170
+ "Set true to remove the campaign restriction so the webhook fires for all campaigns."
5171
+ ),
5046
5172
  is_active: z.boolean().optional().describe("Enable / disable delivery.")
5047
5173
  };
5048
5174
  var updateWebhookTool = {
@@ -5059,7 +5185,10 @@ var updateWebhookTool = {
5059
5185
  handler: async (input, ctx) => {
5060
5186
  const body = {
5061
5187
  ...input.url !== void 0 ? { url: input.url } : {},
5188
+ ...input.description !== void 0 ? { description: input.description } : {},
5062
5189
  ...input.event_types !== void 0 ? { event_types: input.event_types } : {},
5190
+ ...input.campaign_ids !== void 0 ? { campaign_ids: input.campaign_ids } : {},
5191
+ ...input.clear_campaign_ids !== void 0 ? { clear_campaign_ids: input.clear_campaign_ids } : {},
5063
5192
  ...input.is_active !== void 0 ? { is_active: input.is_active } : {}
5064
5193
  };
5065
5194
  const result = await ctx.api.updateWebhook(input.webhook_id, body);
@@ -5246,6 +5375,6 @@ function formatToolError(error) {
5246
5375
  }
5247
5376
  }
5248
5377
 
5249
- export { BearerToken, createHttpApiGateway, createPinoLogger, declareEmptyResourcesAndPrompts, newRequestId, wireToolsIntoMcpServer };
5250
- //# sourceMappingURL=chunk-W7TPFRJB.js.map
5251
- //# sourceMappingURL=chunk-W7TPFRJB.js.map
5378
+ export { BearerToken, SERVER_INSTRUCTIONS, createHttpApiGateway, createPinoLogger, declareEmptyResourcesAndPrompts, newRequestId, wireToolsIntoMcpServer };
5379
+ //# sourceMappingURL=chunk-6FT6HKOB.js.map
5380
+ //# sourceMappingURL=chunk-6FT6HKOB.js.map