@kaminari-ad/mcp 0.10.0 → 0.11.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,55 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.11.0] - 2026-07-28
11
+
12
+ ### Added
13
+
14
+ - **Combo-rule match scope.** `create_custom_rule`, `update_custom_rule`, and
15
+ `test_custom_rule` now document the rule-level `config.match_scope` key used
16
+ by `rule_type='combo'`: `"scan"` (the default, and the behaviour when the key
17
+ is absent) counts conditions across the union of all tags on the scan, while
18
+ `"url"` requires every condition to be satisfied by tags on the same link and
19
+ assigns the output tag to that link. The description warns about the traps a
20
+ per-link rule falls into: a combo built only from link-less tags (AI verdicts,
21
+ crawler behaviour) has no link to attach to and never matches, AI verdicts and
22
+ per-link detections are not evaluated together today, and a per-link rule needs
23
+ at least one positive condition. Any other `match_scope` value is rejected
24
+ before the request is sent, so a misspelled scope surfaces as a tool-input
25
+ error instead of a round trip; the API validates `match_scope` on create,
26
+ update, and the rule-test preview as well, so this is early feedback rather
27
+ than the durable guarantee. The rest of a combo config is still validated only
28
+ for admin-authored system rules, which is why the per-link traps are documented
29
+ rather than enforced. `get_custom_rule` and `list_custom_rules` now flag that
30
+ the returned `config` may carry `match_scope` and that it must be resent
31
+ verbatim, since `update_custom_rule` replaces `config` wholesale.
32
+ - **`referrer` on scan and campaign creation.** `create_scan`,
33
+ `create_bulk_scans`, `create_campaign`, and `update_campaign` accept an
34
+ optional http(s) page URL the check is performed from. With `ad_tag` or
35
+ `vast_tag` it is the publisher page the tag is embedded in: the browser
36
+ commits the page document on that URL without fetching the publisher, so the
37
+ creative renders as if embedded there. With `url` or `ad_discovery` it is
38
+ where the visitor came from, sent as the `Referer` of the page request.
39
+ `update_campaign` also accepts `referrer: null` to clear a stored one.
40
+ Scan and campaign responses surface `referrer` too, so an agent can confirm
41
+ what a scan actually ran with.
42
+
43
+ **Release ordering — this depends on an unreleased API.** The snapshots were
44
+ generated from api `feat/scan-referrer`
45
+ ([api!341](https://gitlab.sdev.pw/adverif/api/-/merge_requests/341)). API
46
+ request DTOs are `extra="ignore"`, so an API without the field drops
47
+ `referrer` silently: no 422, the scan runs with no referrer, and the response
48
+ carries no `referrer` key — the agent gets a green result for a check it
49
+ believes ran from a publisher page. **Do not cut the npm tag or the
50
+ `kaminariad-mcp` image until api!341 is merged and deployed to
51
+ `kaminariadprod1`.** Nothing automated enforces this.
52
+
53
+ ### Changed
54
+
55
+ - Regenerated the OpenAPI type + zod-schema snapshots so the `/api/v1` surface
56
+ they describe carries `referrer`. That field is their only delta versus the
57
+ 0.10.0 snapshots.
58
+
10
59
  ## [0.10.0] - 2026-07-28
11
60
 
12
61
  ### Added
package/dist/bin.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { NAME, VERSION, err, ok } from './chunk-KFVVS55N.js';
2
+ import { NAME, VERSION, err, ok } from './chunk-XSI6SZTX.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-7EXISZ66.js');
163
+ const { bootstrapStdio } = await import('./stdio-bootstrap-QK4ENRPP.js');
164
164
  return bootstrapStdio(config);
165
165
  }
166
- const { bootstrapHttp } = await import('./http-bootstrap-3MGAQHND.js');
166
+ const { bootstrapHttp } = await import('./http-bootstrap-XJNTLL2C.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-KFVVS55N.js';
2
+ import { err, ok } from './chunk-XSI6SZTX.js';
3
3
  import { createHash, randomUUID } from 'crypto';
4
4
  import createClient from 'openapi-fetch';
5
5
  import { fetch } from 'undici';
@@ -245,6 +245,7 @@ z.object({
245
245
  url: z.union([z.string(), z.null()]).optional(),
246
246
  ad_tag: z.union([z.string(), z.null()]).optional(),
247
247
  vast_tag: z.union([z.string(), z.null()]).optional(),
248
+ referrer: z.union([z.string(), z.null()]).optional(),
248
249
  country_code: z.string().min(2).max(2),
249
250
  emulator_id: z.string().min(1).max(100),
250
251
  proxy: ProxyTargetRequest.optional(),
@@ -362,6 +363,7 @@ var ScanResponse = z.object({
362
363
  public_report_url: z.string().optional().default(""),
363
364
  ad_tag: z.union([z.string(), z.null()]).optional(),
364
365
  vast_tag: z.union([z.string(), z.null()]).optional(),
366
+ referrer: z.union([z.string(), z.null()]).optional(),
365
367
  creative_kind: z.enum(["banner", "video"]).optional().default("banner"),
366
368
  creative_screenshot_url: z.string().optional().default(""),
367
369
  creative_video_url: z.string().optional().default(""),
@@ -433,6 +435,7 @@ z.object({
433
435
  url: z.union([z.string(), z.null()]).optional(),
434
436
  ad_tag: z.union([z.string(), z.null()]).optional(),
435
437
  vast_tag: z.union([z.string(), z.null()]).optional(),
438
+ referrer: z.union([z.string(), z.null()]).optional(),
436
439
  country_codes: z.array(z.string()).min(1),
437
440
  emulator_id: z.string().min(1).max(100),
438
441
  proxy: ProxyTargetRequest.optional(),
@@ -490,6 +493,7 @@ z.object({
490
493
  url: z.union([z.string(), z.null()]).optional(),
491
494
  ad_tag: z.union([z.string(), z.null()]).optional(),
492
495
  vast_tag: z.union([z.string(), z.null()]).optional(),
496
+ referrer: z.union([z.string(), z.null()]).optional(),
493
497
  country_codes: z.array(z.string()).min(1),
494
498
  group_id: z.union([z.string(), z.null()]).optional(),
495
499
  emulator_categories: z.array(z.string()).optional(),
@@ -522,6 +526,7 @@ var CampaignResponse = z.object({
522
526
  url: z.string(),
523
527
  ad_tag: z.union([z.string(), z.null()]).optional(),
524
528
  vast_tag: z.union([z.string(), z.null()]).optional(),
529
+ referrer: z.union([z.string(), z.null()]).optional(),
525
530
  country_codes: z.array(z.string()),
526
531
  group_id: z.string().uuid(),
527
532
  emulator_selection: EmulatorSelectionResponse,
@@ -562,6 +567,7 @@ z.object({
562
567
  url: z.union([z.string(), z.null()]),
563
568
  ad_tag: z.union([z.string(), z.null()]),
564
569
  vast_tag: z.union([z.string(), z.null()]),
570
+ referrer: z.union([z.string(), z.null()]),
565
571
  country_codes: z.union([z.array(z.string()), z.null()]),
566
572
  group_id: z.union([z.string(), z.null()]),
567
573
  emulator_categories: z.union([z.array(z.string()), z.null()]),
@@ -1339,6 +1345,7 @@ var CampaignSchema = schemas.CampaignResponse.pick({
1339
1345
  url: true,
1340
1346
  ad_tag: true,
1341
1347
  vast_tag: true,
1348
+ referrer: true,
1342
1349
  country_codes: true,
1343
1350
  group_id: true,
1344
1351
  emulator_selection: true,
@@ -1772,6 +1779,7 @@ var ScanSchema = schemas.ScanResponse.pick({
1772
1779
  public_report_url: true,
1773
1780
  ad_tag: true,
1774
1781
  vast_tag: true,
1782
+ referrer: true,
1775
1783
  creative_kind: true,
1776
1784
  video: true,
1777
1785
  creative_screenshot_url: true,
@@ -3672,6 +3680,10 @@ function pickCampaignConfigBody(input) {
3672
3680
  if (input.schedule_timezone !== void 0) body.schedule_timezone = input.schedule_timezone;
3673
3681
  return body;
3674
3682
  }
3683
+ var campaignReferrerUrl = z.string().url().regex(/^https?:\/\//i, "Must be an http(s) URL.").max(2048);
3684
+ var CAMPAIGN_REFERRER_DESCRIPTION = "Optional http(s) page URL every scan of this campaign is checked from. For `ad_tag` and `vast` campaigns this is the publisher page the tag is embedded in \u2014 NOT the creative and NOT its landing page; the browser commits the page document on this URL without fetching the publisher, so the creative renders as if embedded there, and its cross-origin subrequests receive the origin only (https://publisher.example/, no path). For `url` and `ad_discovery` campaigns this is where the visitor came from, sent in full as the Referer of the page request.";
3685
+ var campaignReferrerField = campaignReferrerUrl.optional().describe(CAMPAIGN_REFERRER_DESCRIPTION);
3686
+ var campaignReferrerUpdateField = campaignReferrerUrl.nullable().optional().describe(`${CAMPAIGN_REFERRER_DESCRIPTION} Pass null to clear it.`);
3675
3687
 
3676
3688
  // src/application/tools/campaigns/create-campaign.tool.ts
3677
3689
  var CreateCampaignInputShape = {
@@ -3686,6 +3698,7 @@ var CreateCampaignInputShape = {
3686
3698
  vast_tag: z.string().optional().describe(
3687
3699
  "VAST video ad tag: an http(s) URL of a VAST endpoint OR raw VAST XML (required if campaign_type=vast)."
3688
3700
  ),
3701
+ referrer: campaignReferrerField,
3689
3702
  country_codes: z.array(z.string().length(2)).min(1).describe("ISO 3166-1 alpha-2 codes \u2014 one scan per country per run."),
3690
3703
  group_id: z.string().uuid().optional().describe("Parent group UUID; defaults to the org's default group."),
3691
3704
  ...campaignConfigFields,
@@ -3712,6 +3725,7 @@ var createCampaignTool = {
3712
3725
  ...input.url !== void 0 ? { url: input.url } : {},
3713
3726
  ...input.ad_tag !== void 0 ? { ad_tag: input.ad_tag } : {},
3714
3727
  ...input.vast_tag !== void 0 ? { vast_tag: input.vast_tag } : {},
3728
+ ...input.referrer !== void 0 ? { referrer: input.referrer } : {},
3715
3729
  ...input.group_id !== void 0 ? { group_id: input.group_id } : {},
3716
3730
  ...input.labels !== void 0 ? { labels: input.labels } : {},
3717
3731
  ...input.policy_set_id !== void 0 ? { policy_set_id: input.policy_set_id } : {},
@@ -3882,6 +3896,7 @@ var UpdateCampaignInputShape = {
3882
3896
  vast_tag: z.string().optional().describe(
3883
3897
  "New VAST video ad tag: an http(s) URL of a VAST endpoint OR raw VAST XML (vast-type campaigns)."
3884
3898
  ),
3899
+ referrer: campaignReferrerUpdateField,
3885
3900
  country_codes: z.array(z.string().length(2)).optional().describe("Replace the country list."),
3886
3901
  group_id: z.string().uuid().optional().describe("Move the campaign to another group."),
3887
3902
  ...campaignConfigFields,
@@ -3891,7 +3906,7 @@ var UpdateCampaignInputShape = {
3891
3906
  };
3892
3907
  var updateCampaignTool = {
3893
3908
  name: "update_campaign",
3894
- description: "Update one or more fields of a campaign. Fields not supplied are left unchanged. `policy_set_id` accepts null to clear the binding.",
3909
+ description: "Update one or more fields of a campaign. Fields not supplied are left unchanged. `policy_set_id` accepts null to clear the binding, and `referrer` accepts null to clear the publisher page scans are checked from.",
3895
3910
  annotations: {
3896
3911
  title: "Update Campaign",
3897
3912
  readOnlyHint: false,
@@ -3906,6 +3921,7 @@ var updateCampaignTool = {
3906
3921
  ...input.url !== void 0 ? { url: input.url } : {},
3907
3922
  ...input.ad_tag !== void 0 ? { ad_tag: input.ad_tag } : {},
3908
3923
  ...input.vast_tag !== void 0 ? { vast_tag: input.vast_tag } : {},
3924
+ ...input.referrer !== void 0 ? { referrer: input.referrer } : {},
3909
3925
  ...input.country_codes !== void 0 ? { country_codes: input.country_codes } : {},
3910
3926
  ...input.group_id !== void 0 ? { group_id: input.group_id } : {},
3911
3927
  ...input.labels !== void 0 ? { labels: input.labels } : {},
@@ -3918,6 +3934,21 @@ var updateCampaignTool = {
3918
3934
  return ok(result.value);
3919
3935
  }
3920
3936
  };
3937
+ var COMBO_MATCH_SCOPE_DOC = 'For `rule_type=\'combo\'` the optional rule-level key `match_scope` decides where conditions are counted. `"scan"` (the default, and the behaviour when the key is absent) checks them against the union of all tags on the scan. With `"url"`, every condition must be satisfied by tags on the same link, and the tag is assigned to that link \u2014 so a rule built only from link-less tags (AI verdicts, crawler behaviour) has no link to attach to and will never match; keep those on the whole scan. Mixing the two kinds does not help either: AI verdicts and per-link detections reach the engine in different scan parts and are not evaluated together today, so such a combo fires under neither scope. Give a `"url"` rule at least one positive condition (`all_of` / `any_of` / `tag_category` + `count_gte`) \u2014 a config carrying only `none_of` matches every link that merely lacks those tags, and the API does not reject that on this path. Thresholds stay "N or more" (`count_gte` / `any_of_min`) in both scopes; no other `match_scope` value is accepted.';
3938
+ var COMBO_MATCH_SCOPE_READ_DOC = 'For `rule_type=\'combo\'` the returned `config` may carry the rule-level key `match_scope` ("scan" = count conditions across the whole scan, "url" = every condition must be satisfied by tags on the same link). Resend it verbatim when updating: `update_custom_rule` replaces `config` wholesale, so dropping the key reverts the rule to whole-scan matching.';
3939
+ var ruleConfigField = z.record(z.unknown()).superRefine((config, ctx) => {
3940
+ const scope = config["match_scope"];
3941
+ if (scope === void 0 || scope === "scan" || scope === "url") {
3942
+ return;
3943
+ }
3944
+ ctx.addIssue({
3945
+ code: z.ZodIssueCode.custom,
3946
+ path: ["match_scope"],
3947
+ message: 'config.match_scope must be "scan" or "url". Omit the key for scan-wide matching (the default).'
3948
+ });
3949
+ });
3950
+
3951
+ // src/application/tools/custom-rules/create-custom-rule.tool.ts
3921
3952
  var CreateCustomRuleInputShape = {
3922
3953
  name: z.string().min(1).max(200).describe(
3923
3954
  "Display name. For non-LLM rules this also becomes the `display_name` of the auto-registered tag definition (see `tag_slug`)."
@@ -3928,8 +3959,8 @@ var CreateCustomRuleInputShape = {
3928
3959
  rule_type: z.string().max(50).describe(
3929
3960
  "Rule engine. One of: `stopword_content`, `stopword_url`, `regexp_content`, `regexp_url`, `blacklist_domain`, `combo`, `llm`. The API validates."
3930
3961
  ),
3931
- config: z.record(z.unknown()).describe(
3932
- "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`)."
3962
+ config: ruleConfigField.describe(
3963
+ "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`). " + COMBO_MATCH_SCOPE_DOC
3933
3964
  ),
3934
3965
  target: z.string().max(30).optional().describe(
3935
3966
  "Where to apply the rule (e.g. 'page' for landing HTML). Default: page. See API docs for the full set of valid values."
@@ -3937,7 +3968,7 @@ var CreateCustomRuleInputShape = {
3937
3968
  };
3938
3969
  var createCustomRuleTool = {
3939
3970
  name: "create_custom_rule",
3940
- 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`.",
3971
+ 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`. For `rule_type='combo'` set `config.match_scope='url'` when the thresholds must be met inside one link instead of anywhere on the scan (default `'scan'`). Matches tag every future scan; existing scans are untouched until you call `recheck_scans`.",
3941
3972
  annotations: {
3942
3973
  title: "Create Custom Rule",
3943
3974
  readOnlyHint: false,
@@ -3982,7 +4013,7 @@ var deleteCustomRuleTool = {
3982
4013
  var GetCustomRuleInputShape = { rule_id: z.string().uuid().describe("Rule UUID.") };
3983
4014
  var getCustomRuleTool = {
3984
4015
  name: "get_custom_rule",
3985
- description: "Get one custom rule by UUID with name, tag-slug, type, config object, target, active flag.",
4016
+ description: "Get one custom rule by UUID with name, tag-slug, type, config object, target, active flag. " + COMBO_MATCH_SCOPE_READ_DOC,
3986
4017
  annotations: {
3987
4018
  title: "Get Custom Rule",
3988
4019
  readOnlyHint: true,
@@ -4003,7 +4034,7 @@ var ListCustomRulesInputShape = {
4003
4034
  };
4004
4035
  var listCustomRulesTool = {
4005
4036
  name: "list_custom_rules",
4006
- description: "Paginated list of the organization's custom tag-detection rules (regex / heuristics) with their config, target, and active flag. Returns `{items, total, page, limit}`. To iterate: there is a next page iff `page * limit < total` (or equivalently `items.length === limit` until the last page). `page` is 1-indexed.",
4037
+ description: "Paginated list of the organization's custom tag-detection rules (regex / heuristics) with their config, target, and active flag. Returns `{items, total, page, limit}`. To iterate: there is a next page iff `page * limit < total` (or equivalently `items.length === limit` until the last page). `page` is 1-indexed. " + COMBO_MATCH_SCOPE_READ_DOC,
4007
4038
  annotations: {
4008
4039
  title: "List Custom Rules",
4009
4040
  readOnlyHint: true,
@@ -4022,8 +4053,8 @@ var TestCustomRuleInputShape = {
4022
4053
  rule_type: z.string().max(50).describe(
4023
4054
  "Rule engine type. One of: `stopword_content`, `stopword_url`, `regexp_content`, `regexp_url`, `blacklist_domain`, `combo`, `llm`."
4024
4055
  ),
4025
- config: z.record(z.unknown()).describe(
4026
- "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` (`scope=system`) before promoting to `create_custom_rule`."
4056
+ config: ruleConfigField.describe(
4057
+ "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` (`scope=system`) before promoting to `create_custom_rule`. " + COMBO_MATCH_SCOPE_DOC
4027
4058
  ),
4028
4059
  target: z.string().max(30).describe(
4029
4060
  "Where to apply the rule (e.g. 'page' for landing HTML). See API docs for the full set of valid values."
@@ -4032,7 +4063,7 @@ var TestCustomRuleInputShape = {
4032
4063
  };
4033
4064
  var testCustomRuleTool = {
4034
4065
  name: "test_custom_rule",
4035
- 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` (`scope=system`) before promoting to `create_custom_rule`, which would otherwise reject the same payload with HTTP 422 / code `checking.system_slug_reserved`.",
4066
+ 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`. Combo rules are evaluated against the scan's existing tags, so `config.match_scope` is honoured \u2014 but per-URL hits are collapsed to one row per tag slug, so the result says whether the rule fires, not which URLs it would tag. NOTE: slug-collision validation does NOT run in preview mode \u2014 verify any `tag_slug` (or LLM `config.tags` keys) against `list_tags` (`scope=system`) before promoting to `create_custom_rule`, which would otherwise reject the same payload with HTTP 422 / code `checking.system_slug_reserved`.",
4036
4067
  annotations: {
4037
4068
  title: "Test Custom Rule",
4038
4069
  readOnlyHint: true,
@@ -4060,8 +4091,8 @@ var UpdateCustomRuleInputShape = {
4060
4091
  tag_slug: z.string().max(100).optional().describe(
4061
4092
  "New tag slug to assign on match. **MUST NOT collide with a built-in system tag slug** (see `list_tags` where `scope=system`); colliding requests return 422 with code `checking.system_slug_reserved`. Re-registering an existing custom slug refreshes its tag definition's `display_name` / `description`."
4062
4093
  ),
4063
- config: z.record(z.unknown()).optional().describe(
4064
- "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."
4094
+ config: ruleConfigField.optional().describe(
4095
+ "New rule-type-specific config object. Replaces the stored config wholesale \u2014 resend every key you want to keep, including a combo rule's `match_scope`. 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. " + COMBO_MATCH_SCOPE_DOC
4065
4096
  ),
4066
4097
  target: z.string().max(30).optional().describe(
4067
4098
  "Where to apply the rule (e.g. 'page' for landing HTML). See API docs for the full set of valid values."
@@ -4070,7 +4101,7 @@ var UpdateCustomRuleInputShape = {
4070
4101
  };
4071
4102
  var updateCustomRuleTool = {
4072
4103
  name: "update_custom_rule",
4073
- 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.)",
4104
+ description: "Update a custom tag-detection rule. Only supplied fields are sent, but a supplied `config` replaces the stored one wholesale (omitting a combo rule's `match_scope` reverts it to the scan-wide default). 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.)",
4074
4105
  annotations: {
4075
4106
  title: "Update Custom Rule",
4076
4107
  readOnlyHint: false,
@@ -4733,6 +4764,9 @@ var scanProxyField = z.object({
4733
4764
  city: z.string().optional().describe("Proxy city targeting."),
4734
4765
  isp: z.string().optional().describe("Proxy ISP targeting.")
4735
4766
  }).optional().describe("Optional proxy geo targeting for the crawl. Omit to use the org default.");
4767
+ var scanReferrerField = z.string().url().regex(/^https?:\/\//i, "Must be an http(s) URL.").max(2048).optional().describe(
4768
+ "Optional http(s) page URL the check is performed from. With `ad_tag` or `vast_tag` this is the publisher page the tag is embedded in \u2014 NOT the creative and NOT its landing page; the browser commits the page document on this URL without fetching the publisher, so the creative renders as if embedded there, and its cross-origin subrequests receive the origin only (https://publisher.example/, no path). With a `url` target this is where the visitor came from, sent in full as the Referer of the page request."
4769
+ );
4736
4770
 
4737
4771
  // src/application/tools/scans/create-bulk-scans.tool.ts
4738
4772
  var CreateBulkScansInputShape = {
@@ -4743,6 +4777,7 @@ var CreateBulkScansInputShape = {
4743
4777
  vast_tag: z.string().optional().describe(
4744
4778
  "VAST video ad tag: an http(s) URL of a VAST endpoint OR raw VAST XML. Provide exactly one of `url`, `ad_tag`, or `vast_tag`."
4745
4779
  ),
4780
+ referrer: scanReferrerField,
4746
4781
  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."),
4747
4782
  emulator_id: z.string().min(1).max(100).describe("Device/OS profile slug; same for every country in the batch."),
4748
4783
  proxy: scanProxyField,
@@ -4767,6 +4802,7 @@ var createBulkScansTool = {
4767
4802
  ...input.url !== void 0 ? { url: input.url } : {},
4768
4803
  ...input.ad_tag !== void 0 ? { ad_tag: input.ad_tag } : {},
4769
4804
  ...input.vast_tag !== void 0 ? { vast_tag: input.vast_tag } : {},
4805
+ ...input.referrer !== void 0 ? { referrer: input.referrer } : {},
4770
4806
  ...input.proxy !== void 0 ? { proxy: input.proxy } : {},
4771
4807
  ...input.labels !== void 0 ? { labels: input.labels } : {},
4772
4808
  ...pickRepeatRetryBody(input)
@@ -4786,6 +4822,7 @@ var CreateScanInputShape = {
4786
4822
  vast_tag: z.string().optional().describe(
4787
4823
  "VAST video ad tag: an http(s) URL of a VAST endpoint OR raw VAST XML (a document containing a <VAST> element). Fetched and played in a real browser. Provide exactly one of `url`, `ad_tag`, or `vast_tag`."
4788
4824
  ),
4825
+ referrer: scanReferrerField,
4789
4826
  country_code: z.string().length(2).describe("ISO 3166-1 alpha-2 country code, e.g. US, DE, JP. Determines proxy geo."),
4790
4827
  emulator_id: z.string().min(1).max(100).describe("Device/OS profile slug; use `list_emulators` to discover valid values."),
4791
4828
  proxy: scanProxyField,
@@ -4815,6 +4852,7 @@ var createScanTool = {
4815
4852
  ...input.url !== void 0 ? { url: input.url } : {},
4816
4853
  ...input.ad_tag !== void 0 ? { ad_tag: input.ad_tag } : {},
4817
4854
  ...input.vast_tag !== void 0 ? { vast_tag: input.vast_tag } : {},
4855
+ ...input.referrer !== void 0 ? { referrer: input.referrer } : {},
4818
4856
  ...input.proxy !== void 0 ? { proxy: input.proxy } : {},
4819
4857
  ...input.labels !== void 0 ? { labels: input.labels } : {},
4820
4858
  ...input.campaign_id !== void 0 ? { campaign_id: input.campaign_id } : {},
@@ -5611,5 +5649,5 @@ function formatToolError(error) {
5611
5649
  }
5612
5650
 
5613
5651
  export { BearerToken, SERVER_INSTRUCTIONS, createHttpApiGateway, createPinoLogger, declareEmptyResourcesAndPrompts, newRequestId, wireToolsIntoMcpServer };
5614
- //# sourceMappingURL=chunk-5UVZYMC3.js.map
5615
- //# sourceMappingURL=chunk-5UVZYMC3.js.map
5652
+ //# sourceMappingURL=chunk-XCAKB2QW.js.map
5653
+ //# sourceMappingURL=chunk-XCAKB2QW.js.map