@kaminari-ad/mcp 0.23.0 → 0.24.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,42 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.24.0] - 2026-09-30
11
+
12
+ > Requires the API release with campaign `notifications` and label routing.
13
+ > Until it lands, production ignores the new body fields and omits the new
14
+ > response fields. Do not tag this release ahead of the API.
15
+
16
+ ### Added
17
+
18
+ - **`notifications` on `create_campaign` and `update_campaign`.** A campaign's
19
+ alert routing (`mode`, `destination_ids`, `routing_label_key`) is saved in
20
+ the same request as the campaign. The API validates the block before it
21
+ writes anything, so an invalid block leaves the campaign untouched. Omit it
22
+ to keep the current routing (update) or inherit the org defaults (create).
23
+ - **Label routing.** `set_campaign_alert_overrides` accepts
24
+ `routing_label_key` on top of `inherit` / `override`: each scan also reaches
25
+ the destinations whose label rule matches its value for that key (one DSP's
26
+ chat in a shared campaign). `get_campaign_alert_overrides` returns the key,
27
+ and `list_alert_destinations` returns each destination's rule as
28
+ `route_label_key` + `route_label_values`. The mode / destination / key
29
+ combination rules are checked locally before the call.
30
+
31
+ ### Changed
32
+
33
+ - Regenerated `openapi.ts` / `zod-schemas.ts` from the current API, which also
34
+ picks up response fields added since the last regen (`ad_formats`,
35
+ `network_host`, `creative_redirect_chain`).
36
+ - `set_campaign_alert_overrides` now replaces the label routing along with the
37
+ mode: a call without `routing_label_key` clears a key set earlier. Read
38
+ `get_campaign_alert_overrides` first to keep it.
39
+
40
+ ### Security
41
+
42
+ - `brace-expansion` 5.0.12, `fast-uri` 3.1.8 and `axios` 1.20.0 (transitive)
43
+ for GHSA-q2hr-2g5m-vwhr, GHSA-qhr7-859c-m2p7, GHSA-6j4f-fj2g-mc7p,
44
+ GHSA-hrr3-gc8f-f4qj and the axios 1.19 advisories (GHSA-vh66-26gq-q6x8 et al.).
45
+
10
46
  ## [0.23.0] - 2026-09-29
11
47
 
12
48
  > Requires the API-side ad-format deploy. Until it lands, production
package/dist/bin.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { NAME, VERSION, err, ok } from './chunk-6INS6YMW.js';
2
+ import { NAME, VERSION, err, ok } from './chunk-JTJY73XX.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-M7I2WD5F.js');
163
+ const { bootstrapStdio } = await import('./stdio-bootstrap-HDLBAPCW.js');
164
164
  return bootstrapStdio(config);
165
165
  }
166
- const { bootstrapHttp } = await import('./http-bootstrap-NSZVATPV.js');
166
+ const { bootstrapHttp } = await import('./http-bootstrap-URSEZ7YN.js');
167
167
  return bootstrapHttp(config);
168
168
  }
169
169
  main().then(
@@ -3,8 +3,8 @@ export { err, ok } from 'neverthrow';
3
3
 
4
4
  // src/shared/version.ts
5
5
  var NAME = "@kaminari-ad/mcp";
6
- var VERSION = "0.23.0";
6
+ var VERSION = "0.24.0";
7
7
 
8
8
  export { NAME, VERSION };
9
- //# sourceMappingURL=chunk-6INS6YMW.js.map
10
- //# sourceMappingURL=chunk-6INS6YMW.js.map
9
+ //# sourceMappingURL=chunk-JTJY73XX.js.map
10
+ //# sourceMappingURL=chunk-JTJY73XX.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/shared/version.ts"],"names":[],"mappings":";;;;AASO,IAAM,IAAA,GAAO;AACb,IAAM,OAAA,GAAU","file":"chunk-6INS6YMW.js","sourcesContent":["/**\n * Package version and name. Hard-coded as constants here, asserted to\n * match `package.json` by a unit test.\n *\n * Why not import `package.json`: it would force JSON-module support at\n * runtime and tsup-bundling would inline the entire manifest. Two\n * constants + one assertion test is simpler and gives the same safety.\n */\n\nexport const NAME = \"@kaminari-ad/mcp\";\nexport const VERSION = \"0.23.0\";\n"]}
1
+ {"version":3,"sources":["../src/shared/version.ts"],"names":[],"mappings":";;;;AASO,IAAM,IAAA,GAAO;AACb,IAAM,OAAA,GAAU","file":"chunk-JTJY73XX.js","sourcesContent":["/**\n * Package version and name. Hard-coded as constants here, asserted to\n * match `package.json` by a unit test.\n *\n * Why not import `package.json`: it would force JSON-module support at\n * runtime and tsup-bundling would inline the entire manifest. Two\n * constants + one assertion test is simpler and gives the same safety.\n */\n\nexport const NAME = \"@kaminari-ad/mcp\";\nexport const VERSION = \"0.24.0\";\n"]}
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { err, ok } from './chunk-6INS6YMW.js';
2
+ import { err, ok } from './chunk-JTJY73XX.js';
3
3
  import { createHash, randomUUID } from 'crypto';
4
4
  import createClient from 'openapi-fetch';
5
5
  import { fetch } from 'undici';
@@ -254,12 +254,14 @@ var ProxyTargetRequest = z.object({
254
254
  city: z.string().default(""),
255
255
  isp: z.string().default("")
256
256
  }).partial().passthrough();
257
+ var AdKind = z.enum(["banner", "pop", "video"]);
257
258
  var RepeatModeType = z.enum(["isolated", "shared"]);
258
259
  z.object({
259
260
  url: z.union([z.string(), z.null()]).optional(),
260
261
  ad_tag: z.union([z.string(), z.null()]).optional(),
261
262
  vast_tag: z.union([z.string(), z.null()]).optional(),
262
263
  referrer: z.union([z.string(), z.null()]).optional(),
264
+ ignore_first_n_domains: z.number().int().gte(0).lte(5).optional().default(0),
263
265
  country_code: z.string().min(2).max(2),
264
266
  emulator_id: z.string().min(1).max(100),
265
267
  proxy: ProxyTargetRequest.optional(),
@@ -267,6 +269,8 @@ z.object({
267
269
  campaign_id: z.union([z.string(), z.null()]).optional(),
268
270
  run_id: z.union([z.string(), z.null()]).optional(),
269
271
  ad_discovery: z.boolean().optional().default(false),
272
+ max_discovered_ads: z.union([z.number(), z.null()]).optional(),
273
+ ad_formats: z.union([z.array(AdKind), z.null()]).optional(),
270
274
  repeat_count: z.number().int().gte(1).optional().default(1),
271
275
  repeat_mode: RepeatModeType.optional(),
272
276
  retry_max_attempts: z.number().int().gte(0).optional().default(0)
@@ -367,9 +371,12 @@ var ScanResponse = z.object({
367
371
  status: ScanStatus,
368
372
  parent_scan_id: z.union([z.string(), z.null()]).optional(),
369
373
  ad_discovery: z.boolean().optional().default(false),
374
+ max_discovered_ads: z.union([z.number(), z.null()]).optional(),
375
+ ad_formats: z.union([z.array(AdKind), z.null()]).optional(),
370
376
  slot_index: z.union([z.number(), z.null()]).optional(),
371
377
  ad_kind: z.union([z.string(), z.null()]).optional(),
372
378
  network: z.string().optional().default(""),
379
+ network_host: z.string().optional().default(""),
373
380
  offer_url: z.string(),
374
381
  redirect_chain: z.array(RedirectHopResponse),
375
382
  screenshot_url: z.string().optional().default(""),
@@ -378,6 +385,7 @@ var ScanResponse = z.object({
378
385
  ad_tag: z.union([z.string(), z.null()]).optional(),
379
386
  vast_tag: z.union([z.string(), z.null()]).optional(),
380
387
  referrer: z.union([z.string(), z.null()]).optional(),
388
+ ignore_first_n_domains: z.number().int().optional().default(0),
381
389
  creative_kind: z.enum(["banner", "video"]).optional().default("banner"),
382
390
  creative_screenshot_url: z.string().optional().default(""),
383
391
  creative_video_url: z.string().optional().default(""),
@@ -385,6 +393,7 @@ var ScanResponse = z.object({
385
393
  creative_html_url: z.string().optional().default(""),
386
394
  creative_width: z.number().int().optional().default(0),
387
395
  creative_height: z.number().int().optional().default(0),
396
+ creative_redirect_chain: z.array(RedirectHopResponse).optional(),
388
397
  video: z.union([VideoMetaResponse, z.null()]).optional(),
389
398
  proxy: z.union([ProxyTargetResponse, z.null()]).optional(),
390
399
  page_title: z.string(),
@@ -430,6 +439,7 @@ var ScanBriefResponse = z.object({
430
439
  slot_index: z.union([z.number(), z.null()]).optional(),
431
440
  ad_kind: z.union([z.string(), z.null()]).optional(),
432
441
  network: z.string().optional().default(""),
442
+ network_host: z.string().optional().default(""),
433
443
  emulator_display_name: z.string().optional().default(""),
434
444
  emulator_category: z.string().optional().default(""),
435
445
  repeat_index: z.number().int().optional().default(0),
@@ -450,6 +460,7 @@ z.object({
450
460
  ad_tag: z.union([z.string(), z.null()]).optional(),
451
461
  vast_tag: z.union([z.string(), z.null()]).optional(),
452
462
  referrer: z.union([z.string(), z.null()]).optional(),
463
+ ignore_first_n_domains: z.number().int().gte(0).lte(5).optional().default(0),
453
464
  country_codes: z.array(z.string()).min(1),
454
465
  emulator_id: z.string().min(1).max(100),
455
466
  proxy: ProxyTargetRequest.optional(),
@@ -510,7 +521,13 @@ var GroupActionResponse = z.object({
510
521
  run_ids: z.array(z.string().uuid()).optional(),
511
522
  failures: z.array(BulkCampaignFailure).optional()
512
523
  }).passthrough();
524
+ var DiscoveryAdFormat = z.enum(["banner", "pop", "video"]);
513
525
  var RepeatMode = z.enum(["isolated", "shared"]);
526
+ var CampaignNotificationsRequest = z.object({
527
+ mode: z.enum(["inherit", "override", "silence"]),
528
+ destination_ids: z.array(z.string().uuid()).optional(),
529
+ routing_label_key: z.union([z.string(), z.null()]).optional()
530
+ }).passthrough();
514
531
  z.object({
515
532
  name: z.string().min(1).max(200),
516
533
  campaign_type: z.string().optional().default("url"),
@@ -518,6 +535,9 @@ z.object({
518
535
  ad_tag: z.union([z.string(), z.null()]).optional(),
519
536
  vast_tag: z.union([z.string(), z.null()]).optional(),
520
537
  referrer: z.union([z.string(), z.null()]).optional(),
538
+ ignore_first_n_domains: z.number().int().gte(0).lte(5).optional().default(0),
539
+ max_discovered_ads: z.union([z.number(), z.null()]).optional(),
540
+ ad_formats: z.union([z.array(DiscoveryAdFormat), z.null()]).optional(),
521
541
  country_codes: z.array(z.string()).min(1),
522
542
  group_id: z.union([z.string(), z.null()]).optional(),
523
543
  emulator_categories: z.array(z.string()).optional(),
@@ -536,7 +556,8 @@ z.object({
536
556
  schedule_weekly: z.union([z.record(z.array(z.number().int())), z.null()]).optional(),
537
557
  schedule_interval_seconds: z.union([z.number(), z.null()]).optional(),
538
558
  schedule_enabled: z.union([z.boolean(), z.null()]).optional(),
539
- schedule_timezone: z.union([z.string(), z.null()]).optional()
559
+ schedule_timezone: z.union([z.string(), z.null()]).optional(),
560
+ notifications: z.union([CampaignNotificationsRequest, z.null()]).optional()
540
561
  }).passthrough();
541
562
  var EmulatorSelectionResponse = z.object({
542
563
  categories: z.array(z.string()),
@@ -551,6 +572,9 @@ var CampaignResponse = z.object({
551
572
  ad_tag: z.union([z.string(), z.null()]).optional(),
552
573
  vast_tag: z.union([z.string(), z.null()]).optional(),
553
574
  referrer: z.union([z.string(), z.null()]).optional(),
575
+ ignore_first_n_domains: z.number().int().optional().default(0),
576
+ max_discovered_ads: z.union([z.number(), z.null()]).optional(),
577
+ ad_formats: z.union([z.array(DiscoveryAdFormat), z.null()]).optional(),
554
578
  country_codes: z.array(z.string()),
555
579
  group_id: z.string().uuid(),
556
580
  emulator_selection: EmulatorSelectionResponse,
@@ -592,6 +616,9 @@ z.object({
592
616
  ad_tag: z.union([z.string(), z.null()]),
593
617
  vast_tag: z.union([z.string(), z.null()]),
594
618
  referrer: z.union([z.string(), z.null()]),
619
+ ignore_first_n_domains: z.number().int().gte(0).lte(5).default(0),
620
+ max_discovered_ads: z.union([z.number(), z.null()]),
621
+ ad_formats: z.union([z.array(DiscoveryAdFormat), z.null()]),
595
622
  country_codes: z.union([z.array(z.string()), z.null()]),
596
623
  group_id: z.union([z.string(), z.null()]),
597
624
  emulator_categories: z.union([z.array(z.string()), z.null()]),
@@ -610,7 +637,8 @@ z.object({
610
637
  schedule_weekly: z.union([z.record(z.array(z.number().int())), z.null()]),
611
638
  schedule_interval_seconds: z.union([z.number(), z.null()]),
612
639
  schedule_enabled: z.union([z.boolean(), z.null()]),
613
- schedule_timezone: z.union([z.string(), z.null()])
640
+ schedule_timezone: z.union([z.string(), z.null()]),
641
+ notifications: z.union([CampaignNotificationsRequest, z.null()])
614
642
  }).partial().passthrough();
615
643
  var RunSource = z.enum(["ui", "api"]);
616
644
  var RunResponse = z.object({
@@ -1102,6 +1130,8 @@ var AlertNotificationDestinationResponse = z.object({
1102
1130
  email_address: z.union([z.string(), z.null()]),
1103
1131
  included_label_keys: z.array(z.string()),
1104
1132
  included_statuses: z.array(z.string()),
1133
+ route_label_key: z.union([z.string(), z.null()]),
1134
+ route_label_values: z.array(z.string()),
1105
1135
  created_at: z.string().datetime({ offset: true }),
1106
1136
  updated_at: z.string().datetime({ offset: true })
1107
1137
  }).passthrough();
@@ -1110,11 +1140,13 @@ var CampaignOverrideMode = z.enum(["inherit", "override", "silence"]);
1110
1140
  var CampaignOverridesResponse = z.object({
1111
1141
  campaign_id: z.string().uuid(),
1112
1142
  mode: CampaignOverrideMode,
1113
- destination_ids: z.array(z.string().uuid())
1143
+ destination_ids: z.array(z.string().uuid()),
1144
+ routing_label_key: z.union([z.string(), z.null()])
1114
1145
  }).passthrough();
1115
1146
  z.object({
1116
1147
  mode: CampaignOverrideMode,
1117
- destination_ids: z.array(z.string().uuid()).optional().default([])
1148
+ destination_ids: z.array(z.string().uuid()).optional().default([]),
1149
+ routing_label_key: z.union([z.string(), z.null()]).optional()
1118
1150
  }).passthrough();
1119
1151
  var InvoiceType = z.enum(["proforma", "final"]);
1120
1152
  z.union([InvoiceType, z.null()]).optional();
@@ -1506,8 +1538,6 @@ var EmulatorSchema = schemas.EmulatorResponse.pick({
1506
1538
  }).strip();
1507
1539
  var EmulatorListSchema = z.array(EmulatorSchema);
1508
1540
  var parseEmulatorList = (raw) => parseWithSchema(EmulatorListSchema, raw, "emulators");
1509
-
1510
- // src/infrastructure/api/parsers/parse-generic.ts
1511
1541
  function parsePageOf(parseItem) {
1512
1542
  return (raw) => {
1513
1543
  if (raw === null || typeof raw !== "object") {
@@ -1694,13 +1724,16 @@ var AlertDestinationSchema = schemas.AlertNotificationDestinationResponse.pick({
1694
1724
  included_label_keys: true,
1695
1725
  created_at: true,
1696
1726
  updated_at: true
1727
+ }).extend({
1728
+ route_label_key: z.string().nullable().optional(),
1729
+ route_label_values: z.array(z.string()).optional()
1697
1730
  }).strip();
1698
1731
  var parseAlertDestination = (raw) => parseWithSchema(AlertDestinationSchema, raw, "alert-destination");
1699
1732
  var CampaignOverridesSchema = schemas.CampaignOverridesResponse.pick({
1700
1733
  campaign_id: true,
1701
1734
  mode: true,
1702
1735
  destination_ids: true
1703
- }).strip();
1736
+ }).extend({ routing_label_key: z.string().nullable().optional() }).strip();
1704
1737
  var parseCampaignAlertOverrides = (raw) => parseWithSchema(CampaignOverridesSchema, raw, "campaign-overrides");
1705
1738
  var BulkReplaySchema = schemas.BulkReplayResponse.pick({
1706
1739
  replayed: true,
@@ -3247,7 +3280,7 @@ var GetCampaignAlertOverridesInputShape = {
3247
3280
  };
3248
3281
  var getCampaignAlertOverridesTool = {
3249
3282
  name: "get_campaign_alert_overrides",
3250
- description: "Get the per-campaign override of which alert destinations receive its alerts. `mode` is one of `inherit` (no override \u2014 the campaign follows the org-wide destinations), `override` (alerts go ONLY to the returned `destination_ids`), or `silence` (the campaign sends nothing).",
3283
+ description: "Get the per-campaign override of which alert destinations receive its alerts. `mode` is one of `inherit` (no override \u2014 the campaign follows the org-wide destinations), `override` (alerts go ONLY to the returned `destination_ids`), or `silence` (the campaign sends nothing). `routing_label_key`, when set, adds label routing on top of inherit/override: each scan also reaches the destinations whose label rule on that key matches the scan's value.",
3251
3284
  annotations: {
3252
3285
  title: "Get Campaign Alert Overrides",
3253
3286
  readOnlyHint: true,
@@ -3265,7 +3298,7 @@ var getCampaignAlertOverridesTool = {
3265
3298
  var ListAlertDestinationsInputShape = {};
3266
3299
  var listAlertDestinationsTool = {
3267
3300
  name: "list_alert_destinations",
3268
- description: "List configured alert-notification destinations (channels): Slack workspaces, Telegram chats, email lists, generic webhooks. Each entry has id, kind, display name, version, and creation timestamp.",
3301
+ description: "List configured alert-notification destinations (channels): Slack workspaces, Telegram chats, email lists, generic webhooks. Each entry has id, kind, display name, version, creation timestamp and, when set, its label routing rule (`route_label_key` + `route_label_values`) used by campaigns that route by that label key.",
3269
3302
  annotations: {
3270
3303
  title: "List Alert Destinations",
3271
3304
  readOnlyHint: true,
@@ -3305,18 +3338,43 @@ var setAlertDestinationVersionTool = {
3305
3338
  return ok({ updated: true });
3306
3339
  }
3307
3340
  };
3308
- var SetCampaignAlertOverridesInputShape = {
3309
- campaign_id: z.string().uuid().describe("Campaign UUID."),
3341
+ var alertRoutingFields = {
3310
3342
  mode: schemas.CampaignOverrideMode.describe(
3311
3343
  "Routing mode: `inherit` (fall back to the org-wide destinations), `override` (route ONLY to `destination_ids`), `silence` (send nothing for this campaign)."
3312
3344
  ),
3313
3345
  destination_ids: z.array(z.string().uuid()).max(50).default([]).describe(
3314
3346
  "Destination UUIDs to route to. Accepted ONLY with `mode: override` \u2014 the API rejects it for `inherit`/`silence`. An empty list with `override` routes nowhere, which is the same outcome as `silence`."
3347
+ ),
3348
+ routing_label_key: z.string().min(1).max(64).nullable().optional().describe(
3349
+ "Scan label key to route by on top of `inherit` or `override` (e.g. `dspName`): each scan also goes to the destinations whose label rule matches the scan's value for this key, as shown by `route_label_key` / `route_label_values` in `list_alert_destinations`. Omit or pass null for no label routing \u2014 the routing is replaced as a whole, so omitting it clears a key already set (read `get_campaign_alert_overrides` first to keep it). Not allowed with `silence`."
3315
3350
  )
3316
3351
  };
3352
+ function alertRoutingInputError(input) {
3353
+ if (input.mode !== "override" && input.destination_ids.length > 0) {
3354
+ return {
3355
+ kind: "invalid-input",
3356
+ message: `destination_ids is only accepted with mode "override", got "${input.mode}". Use mode "override" to route to specific destinations.`,
3357
+ fieldErrors: { destination_ids: ["only valid with mode 'override'"] }
3358
+ };
3359
+ }
3360
+ if (input.mode === "silence" && input.routing_label_key) {
3361
+ return {
3362
+ kind: "invalid-input",
3363
+ message: 'routing_label_key cannot be combined with mode "silence". Use "inherit" or "override" to route by a label.',
3364
+ fieldErrors: { routing_label_key: ["not valid with mode 'silence'"] }
3365
+ };
3366
+ }
3367
+ return void 0;
3368
+ }
3369
+
3370
+ // src/application/tools/alert-notifications/set-campaign-alert-overrides.tool.ts
3371
+ var SetCampaignAlertOverridesInputShape = {
3372
+ campaign_id: z.string().uuid().describe("Campaign UUID."),
3373
+ ...alertRoutingFields
3374
+ };
3317
3375
  var setCampaignAlertOverridesTool = {
3318
3376
  name: "set_campaign_alert_overrides",
3319
- description: "REPLACE the per-campaign alert-routing override. `mode=inherit` drops the override so the campaign follows the org-wide destinations; `mode=override` routes its alerts ONLY to `destination_ids`; `mode=silence` sends nothing for the campaign. There is no 'route everywhere except these' mode \u2014 to exclude one destination, pass `override` with the destinations you DO want (see `list_alert_destinations`). To read the new state, follow up with `get_campaign_alert_overrides`.",
3377
+ description: "REPLACE the per-campaign alert-routing override. `mode=inherit` drops the override so the campaign follows the org-wide destinations; `mode=override` routes its alerts ONLY to `destination_ids`; `mode=silence` sends nothing for the campaign. `routing_label_key` adds label routing on top of inherit/override: each scan also reaches the destinations whose label rule matches its value (e.g. one DSP's chat in a shared campaign). There is no 'route everywhere except these' mode \u2014 to exclude one destination, pass `override` with the destinations you DO want (see `list_alert_destinations`). To read the new state, follow up with `get_campaign_alert_overrides`.",
3320
3378
  annotations: {
3321
3379
  title: "Set Campaign Alert Overrides",
3322
3380
  readOnlyHint: false,
@@ -3326,16 +3384,12 @@ var setCampaignAlertOverridesTool = {
3326
3384
  },
3327
3385
  inputSchema: z.object(SetCampaignAlertOverridesInputShape),
3328
3386
  handler: async (input, ctx) => {
3329
- if (input.mode !== "override" && input.destination_ids.length > 0) {
3330
- return err({
3331
- kind: "invalid-input",
3332
- message: `destination_ids is only accepted with mode "override", got "${input.mode}". Use mode "override" to route to specific destinations.`,
3333
- fieldErrors: { destination_ids: ["only valid with mode 'override'"] }
3334
- });
3335
- }
3387
+ const invalid = alertRoutingInputError(input);
3388
+ if (invalid) return err(invalid);
3336
3389
  const result = await ctx.api.setCampaignAlertOverrides(input.campaign_id, {
3337
3390
  mode: input.mode,
3338
- destination_ids: input.destination_ids
3391
+ destination_ids: input.destination_ids,
3392
+ routing_label_key: input.routing_label_key ?? null
3339
3393
  });
3340
3394
  if (result.isErr()) return err(mapApiError(result.error));
3341
3395
  return ok({ updated: true });
@@ -4003,6 +4057,17 @@ function pickCampaignConfigBody(input) {
4003
4057
  if (input.schedule_timezone !== void 0) body.schedule_timezone = input.schedule_timezone;
4004
4058
  return body;
4005
4059
  }
4060
+ var notificationsBlock = z.object(alertRoutingFields);
4061
+ function notificationsBody(input) {
4062
+ return {
4063
+ mode: input.mode,
4064
+ destination_ids: input.destination_ids,
4065
+ routing_label_key: input.routing_label_key ?? null
4066
+ };
4067
+ }
4068
+ var campaignNotificationsField = notificationsBlock.optional().describe(
4069
+ "Alert routing saved together with the campaign: `mode`, `destination_ids`, `routing_label_key` \u2014 same meaning as in `set_campaign_alert_overrides`. The block REPLACES the whole routing: an omitted `destination_ids` means none and an omitted `routing_label_key` clears it. Omit the block to keep the current routing (update) or inherit the org defaults (create). Needs the alert_notifications.manage permission; the whole request is rejected before anything is written if the block is invalid."
4070
+ );
4006
4071
  var campaignReferrerUrl = z.string().url().regex(/^https?:\/\//i, "Must be an http(s) URL.").max(2048);
4007
4072
  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.";
4008
4073
  var campaignReferrerField = campaignReferrerUrl.optional().describe(CAMPAIGN_REFERRER_DESCRIPTION);
@@ -4032,7 +4097,8 @@ var CreateCampaignInputShape = {
4032
4097
  policy_set_id: z.union([z.string().uuid(), z.null()]).optional().describe(
4033
4098
  "Policy set to evaluate every scan against. Omit to bind the organization's default (if any). Pass null for no policy set and no policy alerts."
4034
4099
  ),
4035
- schedule_enabled: z.boolean().optional().describe("If true, the scheduler runs immediately. Default: false (manual run).")
4100
+ schedule_enabled: z.boolean().optional().describe("If true, the scheduler runs immediately. Default: false (manual run)."),
4101
+ notifications: campaignNotificationsField
4036
4102
  };
4037
4103
  var createCampaignTool = {
4038
4104
  name: "create_campaign",
@@ -4046,6 +4112,8 @@ var createCampaignTool = {
4046
4112
  },
4047
4113
  inputSchema: z.object(CreateCampaignInputShape),
4048
4114
  handler: async (input, ctx) => {
4115
+ const invalid = input.notifications && alertRoutingInputError(input.notifications);
4116
+ if (invalid) return err(invalid);
4049
4117
  const body = {
4050
4118
  name: input.name,
4051
4119
  campaign_type: input.campaign_type,
@@ -4061,6 +4129,7 @@ var createCampaignTool = {
4061
4129
  ...input.labels !== void 0 ? { labels: input.labels } : {},
4062
4130
  ...input.policy_set_id !== void 0 ? { policy_set_id: input.policy_set_id } : {},
4063
4131
  ...input.schedule_enabled !== void 0 ? { schedule_enabled: input.schedule_enabled } : {},
4132
+ ...input.notifications !== void 0 ? { notifications: notificationsBody(input.notifications) } : {},
4064
4133
  ...pickCampaignConfigBody(input)
4065
4134
  };
4066
4135
  const result = await ctx.api.createCampaign(body);
@@ -4238,11 +4307,12 @@ var UpdateCampaignInputShape = {
4238
4307
  ...campaignConfigFields,
4239
4308
  labels: z.record(z.string()).optional().describe("Replace the label map."),
4240
4309
  policy_set_id: z.string().uuid().nullable().optional().describe("New policy set UUID; pass null to clear."),
4241
- schedule_enabled: z.boolean().optional().describe("Pause / resume the scheduler.")
4310
+ schedule_enabled: z.boolean().optional().describe("Pause / resume the scheduler."),
4311
+ notifications: campaignNotificationsField
4242
4312
  };
4243
4313
  var updateCampaignTool = {
4244
4314
  name: "update_campaign",
4245
- description: "Update one or more fields of a campaign. Fields not supplied are left unchanged. `policy_set_id` accepts null to clear the binding, `referrer` accepts null to clear the publisher page scans are checked from, `max_discovered_ads` accepts null to go back to the platform ad cap, and `ad_formats` accepts null to go back to checking every ad format. `ignore_first_n_domains` is the exception: it is not nullable, so pass 0 to go back to skipping nothing.",
4315
+ description: "Update one or more fields of a campaign. Fields not supplied are left unchanged. `policy_set_id` accepts null to clear the binding, `referrer` accepts null to clear the publisher page scans are checked from, `max_discovered_ads` accepts null to go back to the platform ad cap, and `ad_formats` accepts null to go back to checking every ad format. `ignore_first_n_domains` is the exception: it is not nullable, so pass 0 to go back to skipping nothing. `notifications` is not merged either: when supplied it replaces the campaign's whole alert routing.",
4246
4316
  annotations: {
4247
4317
  title: "Update Campaign",
4248
4318
  readOnlyHint: false,
@@ -4252,6 +4322,8 @@ var updateCampaignTool = {
4252
4322
  },
4253
4323
  inputSchema: z.object(UpdateCampaignInputShape),
4254
4324
  handler: async (input, ctx) => {
4325
+ const invalid = input.notifications && alertRoutingInputError(input.notifications);
4326
+ if (invalid) return err(invalid);
4255
4327
  const body = {
4256
4328
  ...input.name !== void 0 ? { name: input.name } : {},
4257
4329
  ...input.url !== void 0 ? { url: input.url } : {},
@@ -4266,6 +4338,7 @@ var updateCampaignTool = {
4266
4338
  ...input.labels !== void 0 ? { labels: input.labels } : {},
4267
4339
  ...input.policy_set_id !== void 0 ? { policy_set_id: input.policy_set_id } : {},
4268
4340
  ...input.schedule_enabled !== void 0 ? { schedule_enabled: input.schedule_enabled } : {},
4341
+ ...input.notifications !== void 0 ? { notifications: notificationsBody(input.notifications) } : {},
4269
4342
  ...pickCampaignConfigBody(input)
4270
4343
  };
4271
4344
  const result = await ctx.api.updateCampaign(input.campaign_id, body);
@@ -6314,5 +6387,5 @@ function formatToolError(error) {
6314
6387
  }
6315
6388
 
6316
6389
  export { BearerToken, SERVER_INSTRUCTIONS, createHttpApiGateway, createPinoLogger, declareEmptyResourcesAndPrompts, newRequestId, wireToolsIntoMcpServer };
6317
- //# sourceMappingURL=chunk-XJZE6KTY.js.map
6318
- //# sourceMappingURL=chunk-XJZE6KTY.js.map
6390
+ //# sourceMappingURL=chunk-VLIIUTPH.js.map
6391
+ //# sourceMappingURL=chunk-VLIIUTPH.js.map