@kaminari-ad/mcp 0.13.0 → 0.14.1

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-VFDW3UL2.js';
2
+ import { err, ok } from './chunk-YS7IDW24.js';
3
3
  import { createHash, randomUUID } from 'crypto';
4
4
  import createClient from 'openapi-fetch';
5
5
  import { fetch } from 'undici';
@@ -103,6 +103,20 @@ function newRequestId() {
103
103
  return randomUUID();
104
104
  }
105
105
 
106
+ // src/shared/artifact-limits.ts
107
+ var MAX_TEXT_ARTIFACT_BYTES = 256 * 1024;
108
+ var MAX_BINARY_ARTIFACT_BYTES = 8 * 1024 * 1024;
109
+ function formatBytes(bytes) {
110
+ const kib = 1024;
111
+ if (bytes < kib) return `${String(bytes)} B`;
112
+ const mib = kib * kib;
113
+ if (bytes < mib) return `${(bytes / kib).toFixed(1)} KiB`;
114
+ return `${(bytes / mib).toFixed(1)} MiB`;
115
+ }
116
+ function decodeUtf8(bytes) {
117
+ return new TextDecoder("utf-8").decode(bytes);
118
+ }
119
+
106
120
  // src/infrastructure/api/error-mapping.ts
107
121
  function detail(parsed) {
108
122
  if (parsed === null || typeof parsed !== "object") return "Upstream error";
@@ -893,7 +907,7 @@ z.object({
893
907
  filter_date_to: z.union([z.string(), z.null()]).optional(),
894
908
  filter_timezone: z.union([z.string(), z.null()]).optional()
895
909
  }).passthrough();
896
- z.object({ updated: z.number().int(), skipped: z.number().int() }).passthrough();
910
+ var BulkUpdateAlertStatusResponse = z.object({ updated: z.number().int(), skipped: z.number().int() }).passthrough();
897
911
  var AlertStatsResponse = z.object({
898
912
  open: z.number().int(),
899
913
  escalated: z.number().int(),
@@ -1231,9 +1245,11 @@ var schemas = {
1231
1245
  CustomRuleResponse,
1232
1246
  RuleTestResponse,
1233
1247
  PolicyEntryResponse,
1248
+ LinkedCampaignResponse,
1234
1249
  PolicySetResponse,
1235
1250
  PolicySetListItem,
1236
1251
  AlertResponse,
1252
+ BulkUpdateAlertStatusResponse,
1237
1253
  AlertStatsResponse,
1238
1254
  BillingSummaryResponse,
1239
1255
  UsageResponse,
@@ -1388,7 +1404,8 @@ var CampaignGroupSchema = schemas.CampaignGroupResponse.pick({
1388
1404
  is_archived: true,
1389
1405
  schedule_paused: true,
1390
1406
  campaign_count: true,
1391
- created_at: true
1407
+ created_at: true,
1408
+ last_run_at: true
1392
1409
  }).strip();
1393
1410
  var parseCampaignGroup = (raw) => parseWithSchema(CampaignGroupSchema, raw, "campaign-group");
1394
1411
  var parseCampaignGroupArray = (raw) => parseArrayOrItemsWithSchema(CampaignGroupSchema, raw, "campaign-groups");
@@ -1600,6 +1617,11 @@ var AlertStatsSchema = schemas.AlertStatsResponse.pick({
1600
1617
  dismissed: true
1601
1618
  }).strip();
1602
1619
  var parseAlertStats = (raw) => parseWithSchema(AlertStatsSchema, raw, "alert-stats");
1620
+ var BulkUpdateAlertStatusSchema = schemas.BulkUpdateAlertStatusResponse.pick({
1621
+ updated: true,
1622
+ skipped: true
1623
+ }).strip();
1624
+ var parseBulkUpdateAlertStatus = (raw) => parseWithSchema(BulkUpdateAlertStatusSchema, raw, "bulk-update-alert-status");
1603
1625
  var UsageSchema = schemas.UsageResponse.pick({
1604
1626
  id: true,
1605
1627
  scan_id: true,
@@ -1724,6 +1746,14 @@ var GeoSchema = schemas.GeoResponse.pick({
1724
1746
  var GeoListSchema = z.array(GeoSchema);
1725
1747
  var parseGeoList = (raw) => parseWithSchema(GeoListSchema, raw, "geos");
1726
1748
 
1749
+ // src/infrastructure/api/parsers/parse-linked-campaign-page.ts
1750
+ var LinkedCampaignSchema = schemas.LinkedCampaignResponse.pick({
1751
+ id: true,
1752
+ name: true,
1753
+ is_archived: true
1754
+ }).strip();
1755
+ var parseLinkedCampaignPage = (raw) => parsePagedWithItemSchema(LinkedCampaignSchema, raw, "policy-set-campaigns");
1756
+
1727
1757
  // src/infrastructure/api/parsers/parse-policy-set.ts
1728
1758
  var PolicySetSchema = schemas.PolicySetResponse.pick({
1729
1759
  id: true,
@@ -1979,7 +2009,50 @@ function createHttpApiGateway(config) {
1979
2009
  }
1980
2010
  return err(toApiError(status2, error ?? data, response.headers.get("retry-after") ?? void 0));
1981
2011
  }
1982
- async function binaryGet(path, query) {
2012
+ async function readCapped(response, maxBytes) {
2013
+ const tooLarge = err({
2014
+ kind: "upstream",
2015
+ detail: `Artifact is larger than the ${formatBytes(maxBytes)} this tool will transfer. Open the report URL from \`get_scan\` to view it instead.`
2016
+ });
2017
+ const body = response.body;
2018
+ if (body === null) {
2019
+ return err({ kind: "upstream", detail: "Artifact response had no body." });
2020
+ }
2021
+ const declared = Number(response.headers.get("content-length") ?? Number.NaN);
2022
+ if (Number.isFinite(declared) && declared > maxBytes) {
2023
+ await body.cancel().catch(() => void 0);
2024
+ return tooLarge;
2025
+ }
2026
+ const reader = body.getReader();
2027
+ const chunks = [];
2028
+ let total = 0;
2029
+ try {
2030
+ for (; ; ) {
2031
+ const { done, value } = await reader.read();
2032
+ if (done) break;
2033
+ total += value.byteLength;
2034
+ if (total > maxBytes) {
2035
+ await reader.cancel().catch(() => void 0);
2036
+ return tooLarge;
2037
+ }
2038
+ chunks.push(value);
2039
+ }
2040
+ } catch (cause) {
2041
+ await reader.cancel().catch(() => void 0);
2042
+ return err({
2043
+ kind: "upstream",
2044
+ detail: cause instanceof Error ? cause.message : "artifact stream failed"
2045
+ });
2046
+ }
2047
+ const out = new Uint8Array(total);
2048
+ let offset = 0;
2049
+ for (const chunk of chunks) {
2050
+ out.set(chunk, offset);
2051
+ offset += chunk.byteLength;
2052
+ }
2053
+ return ok(out);
2054
+ }
2055
+ async function binaryGet(path, maxBytes, query) {
1983
2056
  const startedAtMs = Date.now();
1984
2057
  const qs = query && Object.keys(query).length > 0 ? `?${new URLSearchParams(
1985
2058
  Object.fromEntries(Object.entries(query).map(([k, v]) => [k, String(v)]))
@@ -1994,7 +2067,7 @@ function createHttpApiGateway(config) {
1994
2067
  // update + a tenant-isolation review.
1995
2068
  headers: {
1996
2069
  authorization: bearer.toAuthorizationHeader(),
1997
- accept: "image/*, application/pdf, application/octet-stream",
2070
+ accept: "image/*, application/pdf, video/mp4, text/plain, application/xml, text/xml, application/octet-stream",
1998
2071
  "user-agent": "kaminari-ad-mcp",
1999
2072
  "x-request-id": requestId
2000
2073
  }
@@ -2012,9 +2085,10 @@ function createHttpApiGateway(config) {
2012
2085
  "api.done"
2013
2086
  );
2014
2087
  if (status2 >= 200 && status2 < 300) {
2015
- const buf = await response.arrayBuffer();
2088
+ const read = await readCapped(response, maxBytes);
2089
+ if (read.isErr()) return err(read.error);
2016
2090
  return ok({
2017
- bytes: new Uint8Array(buf),
2091
+ bytes: read.value,
2018
2092
  contentType: response.headers.get("content-type") ?? "application/octet-stream"
2019
2093
  });
2020
2094
  }
@@ -2077,22 +2151,37 @@ function createHttpApiGateway(config) {
2077
2151
  return call("PUT", "/api/v1/account/labels", { body }, parseArrayOf(parseLabelDefinition));
2078
2152
  },
2079
2153
  async getScanScreenshot(scanId, w2) {
2080
- return binaryGet(`/api/v1/scans/${scanId}/screenshot`, w2 !== void 0 ? { w: w2 } : void 0);
2154
+ return binaryGet(
2155
+ `/api/v1/scans/${scanId}/screenshot`,
2156
+ MAX_BINARY_ARTIFACT_BYTES,
2157
+ w2 !== void 0 ? { w: w2 } : void 0
2158
+ );
2081
2159
  },
2082
2160
  async getScanCreativeScreenshot(scanId, w2) {
2083
2161
  return binaryGet(
2084
2162
  `/api/v1/scans/${scanId}/creative-screenshot`,
2163
+ MAX_BINARY_ARTIFACT_BYTES,
2085
2164
  w2 !== void 0 ? { w: w2 } : void 0
2086
2165
  );
2087
2166
  },
2088
2167
  async getScanLandingScreenshot(scanId, landingOrd, w2) {
2089
2168
  return binaryGet(
2090
2169
  `/api/v1/scans/${scanId}/landings/${String(landingOrd)}/screenshot`,
2170
+ MAX_BINARY_ARTIFACT_BYTES,
2091
2171
  w2 !== void 0 ? { w: w2 } : void 0
2092
2172
  );
2093
2173
  },
2174
+ async getScanCreativeHtml(scanId) {
2175
+ return binaryGet(`/api/v1/scans/${scanId}/creative-html`, MAX_TEXT_ARTIFACT_BYTES);
2176
+ },
2177
+ async getScanCreativeVideo(scanId) {
2178
+ return binaryGet(`/api/v1/scans/${scanId}/creative-video`, MAX_BINARY_ARTIFACT_BYTES);
2179
+ },
2180
+ async getScanVastXml(scanId) {
2181
+ return binaryGet(`/api/v1/scans/${scanId}/vast-xml`, MAX_TEXT_ARTIFACT_BYTES);
2182
+ },
2094
2183
  async getInvoicePdf(invoiceId) {
2095
- return binaryGet(`/api/v1/invoices/${invoiceId}/pdf`);
2184
+ return binaryGet(`/api/v1/invoices/${invoiceId}/pdf`, MAX_BINARY_ARTIFACT_BYTES);
2096
2185
  },
2097
2186
  async listApiKeys() {
2098
2187
  return call("GET", "/api/v1/account/api-keys", {}, parseApiKeyList);
@@ -2435,6 +2524,30 @@ function createHttpApiGateway(config) {
2435
2524
  parseEmpty
2436
2525
  );
2437
2526
  },
2527
+ async listPolicySetCampaigns(id, filters) {
2528
+ return call(
2529
+ "GET",
2530
+ "/api/v1/policy-sets/{policy_set_id}/campaigns",
2531
+ { params: { path: { policy_set_id: id }, query: filters } },
2532
+ parseLinkedCampaignPage
2533
+ );
2534
+ },
2535
+ async attachPolicySetCampaigns(id, body) {
2536
+ return call(
2537
+ "POST",
2538
+ "/api/v1/policy-sets/{policy_set_id}/campaigns/attach",
2539
+ { params: { path: { policy_set_id: id } }, body },
2540
+ parseEmpty
2541
+ );
2542
+ },
2543
+ async detachPolicySetCampaigns(id, body) {
2544
+ return call(
2545
+ "POST",
2546
+ "/api/v1/policy-sets/{policy_set_id}/campaigns/detach",
2547
+ { params: { path: { policy_set_id: id } }, body },
2548
+ parseEmpty
2549
+ );
2550
+ },
2438
2551
  async requestPolicySetApproval(id) {
2439
2552
  return call(
2440
2553
  "POST",
@@ -2444,8 +2557,13 @@ function createHttpApiGateway(config) {
2444
2557
  );
2445
2558
  },
2446
2559
  // ── Custom taxonomies ─────────────────────────────────────────
2447
- async listCustomTaxonomies() {
2448
- return call("GET", "/api/v1/custom-taxonomies", {}, parseCustomTaxonomyList);
2560
+ async listCustomTaxonomies(filters) {
2561
+ return call(
2562
+ "GET",
2563
+ "/api/v1/custom-taxonomies",
2564
+ { params: { query: filters ?? {} } },
2565
+ parseCustomTaxonomyList
2566
+ );
2449
2567
  },
2450
2568
  async getCustomTaxonomy(id) {
2451
2569
  return call(
@@ -2502,8 +2620,11 @@ function createHttpApiGateway(config) {
2502
2620
  parseEmpty
2503
2621
  );
2504
2622
  },
2505
- async getAlertStats() {
2506
- return call("GET", "/api/v1/alerts/stats", {}, parseAlertStats);
2623
+ async bulkUpdateAlertStatus(body) {
2624
+ return call("POST", "/api/v1/alerts/bulk-status", { body }, parseBulkUpdateAlertStatus);
2625
+ },
2626
+ async getAlertStats(filters) {
2627
+ return call("GET", "/api/v1/alerts/stats", { params: { query: filters } }, parseAlertStats);
2507
2628
  },
2508
2629
  // ── Webhooks ──────────────────────────────────────────────────
2509
2630
  async listWebhooks() {
@@ -3107,7 +3228,7 @@ var GetCampaignAlertOverridesInputShape = {
3107
3228
  };
3108
3229
  var getCampaignAlertOverridesTool = {
3109
3230
  name: "get_campaign_alert_overrides",
3110
- description: "Get the per-campaign override of which alert destinations receive its alerts. `mode` is one of `inherit` (use org defaults), `include` (use the listed destinations), or `exclude` (use everything EXCEPT the listed destinations).",
3231
+ 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).",
3111
3232
  annotations: {
3112
3233
  title: "Get Campaign Alert Overrides",
3113
3234
  readOnlyHint: true,
@@ -3167,16 +3288,21 @@ var setAlertDestinationVersionTool = {
3167
3288
  };
3168
3289
  var SetCampaignAlertOverridesInputShape = {
3169
3290
  campaign_id: z.string().uuid().describe("Campaign UUID."),
3170
- mode: z.enum(["inherit", "include", "exclude"]).describe(
3171
- "Routing mode: `inherit` (use org defaults), `include` (route ONLY to listed destinations), `exclude` (route everywhere EXCEPT listed)."
3291
+ // enum-drift: allow — the API validates these three values but types
3292
+ // the field as a plain string, so no generated schema constrains it.
3293
+ // Swap to `schemas.CampaignOverrideMode` after the api enum change
3294
+ // (`fix/type-override-mode-and-unlist-forms`) reaches prod and the
3295
+ // next `make gen-api-types` picks it up.
3296
+ mode: z.enum(["inherit", "override", "silence"]).describe(
3297
+ "Routing mode: `inherit` (fall back to the org-wide destinations), `override` (route ONLY to `destination_ids`), `silence` (send nothing for this campaign)."
3172
3298
  ),
3173
3299
  destination_ids: z.array(z.string().uuid()).max(50).default([]).describe(
3174
- "Destination UUIDs the mode acts on. Required for `include`/`exclude`; ignored for `inherit`."
3300
+ "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`."
3175
3301
  )
3176
3302
  };
3177
3303
  var setCampaignAlertOverridesTool = {
3178
3304
  name: "set_campaign_alert_overrides",
3179
- description: "REPLACE the per-campaign alert-routing override. `mode=inherit` falls back to org defaults; `mode=include` routes ONLY to the listed destinations; `mode=exclude` routes everywhere EXCEPT the listed destinations. To read the new state, follow up with `get_campaign_alert_overrides`.",
3305
+ 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`.",
3180
3306
  annotations: {
3181
3307
  title: "Set Campaign Alert Overrides",
3182
3308
  readOnlyHint: false,
@@ -3186,6 +3312,13 @@ var setCampaignAlertOverridesTool = {
3186
3312
  },
3187
3313
  inputSchema: z.object(SetCampaignAlertOverridesInputShape),
3188
3314
  handler: async (input, ctx) => {
3315
+ if (input.mode !== "override" && input.destination_ids.length > 0) {
3316
+ return err({
3317
+ kind: "invalid-input",
3318
+ message: `destination_ids is only accepted with mode "override", got "${input.mode}". Use mode "override" to route to specific destinations.`,
3319
+ fieldErrors: { destination_ids: ["only valid with mode 'override'"] }
3320
+ });
3321
+ }
3189
3322
  const result = await ctx.api.setCampaignAlertOverrides(input.campaign_id, {
3190
3323
  mode: input.mode,
3191
3324
  destination_ids: input.destination_ids
@@ -3194,10 +3327,116 @@ var setCampaignAlertOverridesTool = {
3194
3327
  return ok({ updated: true });
3195
3328
  }
3196
3329
  };
3197
- var GetAlertStatsInputShape = {};
3330
+ var MAX_IDS = 1e3;
3331
+ var FILTER_FIELDS = [
3332
+ "filter_status",
3333
+ "filter_campaign_id",
3334
+ "filter_policy_set_ids",
3335
+ "filter_tag_slugs",
3336
+ "filter_country_codes",
3337
+ "filter_date_from",
3338
+ "filter_date_to",
3339
+ "filter_timezone"
3340
+ ];
3341
+ var BulkUpdateAlertStatusInputShape = {
3342
+ status: z.enum(["open", "escalated", "resolved", "dismissed"]).describe("Status to move the selected alerts to."),
3343
+ ids: z.array(z.string().uuid()).min(1).max(MAX_IDS).optional().describe(
3344
+ `Explicit alert UUIDs, at most ${String(MAX_IDS)} per call. Mutually exclusive with \`all_matching\`.`
3345
+ ),
3346
+ all_matching: z.boolean().optional().describe(
3347
+ "Apply to every alert matching the `filter_*` fields instead of an id list. With no filters this means EVERY alert in the organization, so filter first and confirm the scope with `get_alert_stats` using the same filters."
3348
+ ),
3349
+ filter_status: z.enum(["open", "escalated", "resolved", "dismissed"]).optional().describe("Only with `all_matching`: restrict to alerts currently in this status."),
3350
+ filter_campaign_id: z.string().uuid().optional().describe("Only with `all_matching`: restrict to one campaign."),
3351
+ filter_policy_set_ids: z.array(z.string().uuid()).min(1).optional().describe("Only with `all_matching`: restrict to alerts raised by any of these policy sets."),
3352
+ filter_tag_slugs: z.array(z.string().min(1)).min(1).optional().describe("Only with `all_matching`: restrict to alerts carrying any of these tag slugs."),
3353
+ filter_country_codes: z.array(z.string().min(2).max(2)).min(1).optional().describe("Only with `all_matching`: restrict to these ISO 3166-1 alpha-2 countries."),
3354
+ filter_date_from: z.string().date().optional().describe("Only with `all_matching`: inclusive lower bound (YYYY-MM-DD) on alert creation."),
3355
+ filter_date_to: z.string().date().optional().describe("Only with `all_matching`: inclusive upper bound (YYYY-MM-DD) on alert creation."),
3356
+ filter_timezone: z.string().optional().describe(
3357
+ "IANA timezone for the filter date bounds. REQUIRED by the API whenever either date is set."
3358
+ )
3359
+ };
3360
+ var bulkUpdateAlertStatusTool = {
3361
+ name: "bulk_update_alert_status",
3362
+ description: "Change the status of many alerts at once. Select EITHER an explicit `ids` list (max 1000) OR `all_matching: true` with the `filter_*` fields, which mirror `list_alerts`. Alerts already in a state the transition cannot leave are skipped rather than failing the call, so the response reports `updated` and `skipped` counts \u2014 compare `updated + skipped` against the selection size to see how many were no-ops. Prefer `update_alert_status` for a single alert.",
3363
+ annotations: {
3364
+ title: "Bulk Update Alert Status",
3365
+ readOnlyHint: false,
3366
+ destructiveHint: false,
3367
+ idempotentHint: true,
3368
+ openWorldHint: false
3369
+ },
3370
+ inputSchema: z.object(BulkUpdateAlertStatusInputShape),
3371
+ handler: async (input, ctx) => {
3372
+ const matchesAll = input.all_matching === true;
3373
+ if (input.ids !== void 0 === matchesAll) {
3374
+ return err({
3375
+ kind: "invalid-input",
3376
+ message: "Select alerts with either `ids` or `all_matching: true` \u2014 not both, and not neither."
3377
+ });
3378
+ }
3379
+ const suppliedFilters = FILTER_FIELDS.filter((field) => input[field] !== void 0);
3380
+ if (!matchesAll && suppliedFilters.length > 0) {
3381
+ return err({
3382
+ kind: "invalid-input",
3383
+ message: `\`${suppliedFilters.join("`, `")}\` applies only with \`all_matching: true\`. An explicit \`ids\` list is already the selection, so the filters would be ignored.`
3384
+ });
3385
+ }
3386
+ if ((input.filter_date_from !== void 0 || input.filter_date_to !== void 0) && input.filter_timezone === void 0) {
3387
+ return err({
3388
+ kind: "invalid-input",
3389
+ message: "filter_timezone is required whenever filter_date_from or filter_date_to is set.",
3390
+ fieldErrors: { filter_timezone: ["required alongside a filter date bound"] }
3391
+ });
3392
+ }
3393
+ const result = await ctx.api.bulkUpdateAlertStatus({
3394
+ status: input.status,
3395
+ all_matching: matchesAll,
3396
+ ...input.ids !== void 0 ? { ids: [...input.ids] } : {},
3397
+ ...input.filter_status !== void 0 ? { filter_status: input.filter_status } : {},
3398
+ ...input.filter_campaign_id !== void 0 ? { filter_campaign_id: input.filter_campaign_id } : {},
3399
+ ...input.filter_policy_set_ids !== void 0 ? { filter_policy_set_ids: [...input.filter_policy_set_ids] } : {},
3400
+ ...input.filter_tag_slugs !== void 0 ? { filter_tag_slugs: [...input.filter_tag_slugs] } : {},
3401
+ ...input.filter_country_codes !== void 0 ? { filter_country_codes: [...input.filter_country_codes] } : {},
3402
+ ...input.filter_date_from !== void 0 ? { filter_date_from: input.filter_date_from } : {},
3403
+ ...input.filter_date_to !== void 0 ? { filter_date_to: input.filter_date_to } : {},
3404
+ ...input.filter_timezone !== void 0 ? { filter_timezone: input.filter_timezone } : {}
3405
+ });
3406
+ if (result.isErr()) return err(mapApiError(result.error));
3407
+ return ok(result.value);
3408
+ }
3409
+ };
3410
+ var alertFilterFields = {
3411
+ campaign_id: z.string().uuid().optional().describe("Filter to one campaign's alerts."),
3412
+ policy_set_id: z.string().optional().describe(
3413
+ "Comma-separated policy-set UUIDs. Matches alerts raised by any of them; see `list_policy_sets`."
3414
+ ),
3415
+ tag: z.string().optional().describe("Comma-separated tag slugs. Matches alerts carrying any of them; see `list_tags`."),
3416
+ country_code: z.string().optional().describe("Comma-separated ISO 3166-1 alpha-2 country codes, e.g. US,DE,JP."),
3417
+ date_from: z.string().date().optional().describe("ISO date (YYYY-MM-DD), inclusive lower bound on alert creation."),
3418
+ date_to: z.string().date().optional().describe("ISO date (YYYY-MM-DD), inclusive upper bound on alert creation."),
3419
+ timezone: z.string().optional().describe(
3420
+ "IANA timezone (e.g. 'Europe/Berlin') the date bounds are read in. Defaults to UTC. Omitting both dates spans all time."
3421
+ )
3422
+ };
3423
+ function toAlertFilterQuery(input) {
3424
+ return {
3425
+ ...input.campaign_id !== void 0 ? { campaign_id: input.campaign_id } : {},
3426
+ ...input.policy_set_id !== void 0 ? { policy_set_id: input.policy_set_id } : {},
3427
+ ...input.tag !== void 0 ? { tag: input.tag } : {},
3428
+ ...input.country_code !== void 0 ? { country_code: input.country_code } : {},
3429
+ ...input.date_from !== void 0 ? { date_from: input.date_from } : {},
3430
+ ...input.date_to !== void 0 ? { date_to: input.date_to } : {},
3431
+ ...input.timezone !== void 0 ? { timezone: input.timezone } : {}
3432
+ };
3433
+ }
3434
+
3435
+ // src/application/tools/alerts/get-alert-stats.tool.ts
3436
+ var GetAlertStatsInputShape = alertFilterFields;
3198
3437
  var getAlertStatsTool = {
3199
3438
  name: "get_alert_stats",
3200
- description: "Get alert counts grouped by status: open, escalated, resolved, dismissed. Counts cover the caller's organization over all time and are not filtered, so the four sum to the `total` an unfiltered `list_alerts` reports. Status names match the canonical AlertStatus enum used by `list_alerts` and `update_alert_status`.",
3439
+ description: "Get alert counts grouped by status: open, escalated, resolved, dismissed. Accepts every filter `list_alerts` does except `status` \u2014 the response buckets BY status \u2014 so passing the same filters to both makes the four counts sum to the `total` that `list_alerts` reports. Unfiltered, the counts cover the whole organization over all time. Use this to size a selection before calling `bulk_update_alert_status` with `all_matching`.",
3201
3440
  annotations: {
3202
3441
  title: "Get Alert Stats",
3203
3442
  readOnlyHint: true,
@@ -3206,14 +3445,14 @@ var getAlertStatsTool = {
3206
3445
  openWorldHint: false
3207
3446
  },
3208
3447
  inputSchema: z.object(GetAlertStatsInputShape),
3209
- handler: async (_input, ctx) => {
3210
- const result = await ctx.api.getAlertStats();
3448
+ handler: async (input, ctx) => {
3449
+ const result = await ctx.api.getAlertStats(toAlertFilterQuery(input));
3211
3450
  if (result.isErr()) return err(mapApiError(result.error));
3212
3451
  return ok(result.value);
3213
3452
  }
3214
3453
  };
3215
3454
  var ListAlertsInputShape = {
3216
- campaign_id: z.string().uuid().optional().describe("Filter to one campaign's alerts."),
3455
+ ...alertFilterFields,
3217
3456
  status: z.enum(["open", "escalated", "resolved", "dismissed"]).optional().describe(
3218
3457
  "Filter by alert status. Open = unhandled; escalated = flagged for attention; resolved = closed; dismissed = closed without action."
3219
3458
  ),
@@ -3222,7 +3461,7 @@ var ListAlertsInputShape = {
3222
3461
  };
3223
3462
  var listAlertsTool = {
3224
3463
  name: "list_alerts",
3225
- description: "List violation alerts (one per scan + violating rule combo) with offer URL, country, status, scan back-reference, and the kind-aware fields `rule_type` (tag / iab_v3 / brand / ai_category / custom_taxonomy) + `matched_value` (the canonical text the scan matched against).",
3464
+ description: "List violation alerts (one per scan + violating rule combo) with offer URL, country, status, scan back-reference, and the kind-aware fields `rule_type` (tag / iab_v3 / brand / ai_category / custom_taxonomy) + `matched_value` (the canonical text the scan matched against). Filter by campaign, status, policy set, tag, country and creation-date range; `policy_set_id`, `tag` and `country_code` take comma-separated values and match any of them. Pass the same filters to `get_alert_stats` to get counts that agree with this list.",
3226
3465
  annotations: {
3227
3466
  title: "List Alerts",
3228
3467
  readOnlyHint: true,
@@ -3235,7 +3474,7 @@ var listAlertsTool = {
3235
3474
  const filters = {
3236
3475
  page: input.page,
3237
3476
  limit: input.limit,
3238
- ...input.campaign_id !== void 0 ? { campaign_id: input.campaign_id } : {},
3477
+ ...toAlertFilterQuery(input),
3239
3478
  ...input.status !== void 0 ? { status: input.status } : {}
3240
3479
  };
3241
3480
  const result = await ctx.api.listAlerts(filters);
@@ -3458,12 +3697,34 @@ var getCampaignGroupTool = {
3458
3697
  return ok(result.value);
3459
3698
  }
3460
3699
  };
3700
+ var campaignDateFilterFields = {
3701
+ created_from: z.string().date().optional().describe("ISO date (YYYY-MM-DD), inclusive lower bound on the creation day."),
3702
+ created_to: z.string().date().optional().describe("ISO date (YYYY-MM-DD), inclusive upper bound on the creation day."),
3703
+ last_run_from: z.string().date().optional().describe(
3704
+ "ISO date (YYYY-MM-DD), inclusive lower bound on the most recent run. Setting either last-run bound excludes anything that has never run."
3705
+ ),
3706
+ last_run_to: z.string().date().optional().describe("ISO date (YYYY-MM-DD), inclusive upper bound on the most recent run."),
3707
+ timezone: z.string().optional().describe("IANA timezone (e.g. 'Europe/Berlin') the date bounds are read in. Defaults to UTC.")
3708
+ };
3709
+ function toCampaignDateFilterQuery(input) {
3710
+ return {
3711
+ ...input.created_from !== void 0 ? { created_from: input.created_from } : {},
3712
+ ...input.created_to !== void 0 ? { created_to: input.created_to } : {},
3713
+ ...input.last_run_from !== void 0 ? { last_run_from: input.last_run_from } : {},
3714
+ ...input.last_run_to !== void 0 ? { last_run_to: input.last_run_to } : {},
3715
+ ...input.timezone !== void 0 ? { timezone: input.timezone } : {}
3716
+ };
3717
+ }
3718
+
3719
+ // src/application/tools/campaign-groups/list-campaign-groups.tool.ts
3461
3720
  var ListCampaignGroupsInputShape = {
3462
- archived: z.boolean().optional().describe("If true, list ONLY archived groups. Default: only active groups.")
3721
+ archived: z.boolean().optional().describe("If true, list ONLY archived groups. Default: only active groups."),
3722
+ q: z.string().min(1).max(200).optional().describe("Substring search against group name (case-insensitive)."),
3723
+ ...campaignDateFilterFields
3463
3724
  };
3464
3725
  var listCampaignGroupsTool = {
3465
3726
  name: "list_campaign_groups",
3466
- description: "List campaign groups \u2014 folders that group related campaigns. Includes per-group campaign count. Not paginated; the org-scoped list is typically small (a few dozen groups max).",
3727
+ description: "List campaign groups \u2014 folders that group related campaigns \u2014 with per-group campaign count and `last_run_at`. Filter by archived flag, name substring, or creation / last-run date range; a group's last run is the newest run across its campaigns. Not paginated; the org-scoped list is typically small (a few dozen groups max).",
3467
3728
  annotations: {
3468
3729
  title: "List Campaign Groups",
3469
3730
  readOnlyHint: true,
@@ -3473,7 +3734,11 @@ var listCampaignGroupsTool = {
3473
3734
  },
3474
3735
  inputSchema: z.object(ListCampaignGroupsInputShape),
3475
3736
  handler: async (input, ctx) => {
3476
- const filters = input.archived === void 0 ? {} : { archived: input.archived };
3737
+ const filters = {
3738
+ ...toCampaignDateFilterQuery(input),
3739
+ ...input.archived !== void 0 ? { archived: input.archived } : {},
3740
+ ...input.q !== void 0 ? { q: input.q } : {}
3741
+ };
3477
3742
  const result = await ctx.api.listCampaignGroups(filters);
3478
3743
  if (result.isErr()) return err(mapApiError(result.error));
3479
3744
  return ok({ items: result.value });
@@ -3798,12 +4063,13 @@ var ListCampaignsInputShape = {
3798
4063
  "When omitted, the API returns active campaigns only. Pass `true` to see archived campaigns, `false` for active-only (explicit)."
3799
4064
  ),
3800
4065
  q: z.string().min(1).max(200).optional().describe("Substring search against campaign name (case-insensitive)."),
4066
+ ...campaignDateFilterFields,
3801
4067
  page: z.number().int().min(1).max(500).default(1).describe("1-indexed page number."),
3802
4068
  limit: z.number().int().min(1).max(200).default(50).describe("Page size (1-200).")
3803
4069
  };
3804
4070
  var listCampaignsTool = {
3805
4071
  name: "list_campaigns",
3806
- 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.",
4072
+ description: "List campaigns for the caller's organization, optionally filtered by group, archived flag, name substring, or creation / last-run date range. 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.",
3807
4073
  annotations: {
3808
4074
  title: "List Campaigns",
3809
4075
  readOnlyHint: true,
@@ -3816,6 +4082,7 @@ var listCampaignsTool = {
3816
4082
  const filters = {
3817
4083
  page: input.page,
3818
4084
  limit: input.limit,
4085
+ ...toCampaignDateFilterQuery(input),
3819
4086
  ...input.group_id !== void 0 ? { group_id: input.group_id } : {},
3820
4087
  ...input.archived !== void 0 ? { archived: input.archived } : {},
3821
4088
  ...input.q !== void 0 ? { q: input.q } : {}
@@ -4259,10 +4526,14 @@ var getCustomTaxonomyTool = {
4259
4526
  return ok(result.value);
4260
4527
  }
4261
4528
  };
4262
- var ListCustomTaxonomiesInputShape = {};
4529
+ var ListCustomTaxonomiesInputShape = {
4530
+ include_inactive: z.boolean().optional().describe(
4531
+ "Include soft-deleted taxonomies, which are excluded by default. Pass `true` to find one to `restore_custom_taxonomy`, or to resolve a taxonomy id seen on an older scan."
4532
+ )
4533
+ };
4263
4534
  var listCustomTaxonomiesTool = {
4264
4535
  name: "list_custom_taxonomies",
4265
- description: "List the calling org's custom classification taxonomies. Returns slim summaries (id, name, slug, version, node_count, is_active). Soft-deleted taxonomies are included with `is_active=false`. Fetch the full tree via `get_custom_taxonomy`.",
4536
+ description: "List the calling org's custom classification taxonomies. Returns slim summaries (id, name, slug, version, node_count, is_active). Only active taxonomies are listed unless you pass `include_inactive: true`; soft-deleted ones then appear with `is_active=false`. Fetch the full tree via `get_custom_taxonomy`.",
4266
4537
  annotations: {
4267
4538
  title: "List Custom Taxonomies",
4268
4539
  readOnlyHint: true,
@@ -4271,8 +4542,10 @@ var listCustomTaxonomiesTool = {
4271
4542
  openWorldHint: false
4272
4543
  },
4273
4544
  inputSchema: z.object(ListCustomTaxonomiesInputShape),
4274
- handler: async (_input, ctx) => {
4275
- const result = await ctx.api.listCustomTaxonomies();
4545
+ handler: async (input, ctx) => {
4546
+ const result = await ctx.api.listCustomTaxonomies(
4547
+ input.include_inactive !== void 0 ? { include_inactive: input.include_inactive } : {}
4548
+ );
4276
4549
  if (result.isErr()) return err(mapApiError(result.error));
4277
4550
  return ok({ items: result.value });
4278
4551
  }
@@ -4478,6 +4751,34 @@ var listInvoicesTool = {
4478
4751
  return ok(result.value);
4479
4752
  }
4480
4753
  };
4754
+ var MAX_CAMPAIGNS = 500;
4755
+ var AttachPolicySetCampaignsInputShape = {
4756
+ policy_set_id: z.string().uuid().describe("Policy set UUID."),
4757
+ campaign_ids: z.array(z.string().uuid()).min(1).max(MAX_CAMPAIGNS).describe(
4758
+ `Campaign UUIDs to bind, at most ${String(MAX_CAMPAIGNS)} per call. A campaign already on THIS set is a no-op; a campaign on a DIFFERENT set is moved to this one.`
4759
+ )
4760
+ };
4761
+ var attachPolicySetCampaignsTool = {
4762
+ name: "attach_policy_set_campaigns",
4763
+ description: "Bind campaigns to a policy set. This and `detach_policy_set_campaigns` are the only way to change membership \u2014 `update_policy_set` edits the set's name, description and rules, never its campaigns. Membership changes here are incremental: this set's other members are left alone. IMPORTANT: a campaign can belong to only one policy set, so naming a campaign currently on a DIFFERENT set MOVES it here \u2014 it stops being evaluated against its old set's rules, and no warning is returned. Check `list_campaigns` (`policy_set_id`) first when the campaign may already be bound elsewhere. Split lists longer than 500 across calls; verify with `list_policy_set_campaigns`.",
4764
+ annotations: {
4765
+ title: "Attach Policy Set Campaigns",
4766
+ readOnlyHint: false,
4767
+ // Reassigns a campaign away from whatever set it was on, and the
4768
+ // previous binding is not recoverable from this tool's output.
4769
+ destructiveHint: true,
4770
+ idempotentHint: true,
4771
+ openWorldHint: false
4772
+ },
4773
+ inputSchema: z.object(AttachPolicySetCampaignsInputShape),
4774
+ handler: async (input, ctx) => {
4775
+ const result = await ctx.api.attachPolicySetCampaigns(input.policy_set_id, {
4776
+ campaign_ids: [...input.campaign_ids]
4777
+ });
4778
+ if (result.isErr()) return err(mapApiError(result.error));
4779
+ return ok({ attached: input.campaign_ids.length });
4780
+ }
4781
+ };
4481
4782
  var TIER1 = z.string().min(1).max(200);
4482
4783
  var TIER_OPT = z.string().max(200).nullable().optional();
4483
4784
  var IabV3Block = z.object({
@@ -4622,6 +4923,43 @@ var deletePolicySetTool = {
4622
4923
  return ok({ deleted: true });
4623
4924
  }
4624
4925
  };
4926
+ var MAX_CAMPAIGNS2 = 500;
4927
+ var DetachPolicySetCampaignsInputShape = {
4928
+ policy_set_id: z.string().uuid().describe("Policy set UUID."),
4929
+ campaign_ids: z.array(z.string().uuid()).min(1).max(MAX_CAMPAIGNS2).optional().describe(
4930
+ `Campaign UUIDs to unbind, at most ${String(MAX_CAMPAIGNS2)} per call. Omit when using \`detach_all\`.`
4931
+ ),
4932
+ detach_all: z.boolean().optional().describe(
4933
+ "Unbind EVERY campaign from this policy set. Check the current membership with `list_policy_set_campaigns` first \u2014 the campaigns stop being evaluated against these rules, so alerts they would have raised simply stop."
4934
+ )
4935
+ };
4936
+ var detachPolicySetCampaignsTool = {
4937
+ name: "detach_policy_set_campaigns",
4938
+ description: "Unbind campaigns from a policy set INCREMENTALLY \u2014 the bindings you don't name survive. Pass `campaign_ids` for specific campaigns or `detach_all: true` to clear the whole membership. This and `attach_policy_set_campaigns` are the only way to change membership; `update_policy_set` edits the set's name, description and rules, never its campaigns. Detaching leaves the policy set and its rules intact, and alerts already raised under it are kept.",
4939
+ annotations: {
4940
+ title: "Detach Policy Set Campaigns",
4941
+ readOnlyHint: false,
4942
+ destructiveHint: true,
4943
+ idempotentHint: true,
4944
+ openWorldHint: false
4945
+ },
4946
+ inputSchema: z.object(DetachPolicySetCampaignsInputShape),
4947
+ handler: async (input, ctx) => {
4948
+ const detachAll = input.detach_all === true;
4949
+ if (input.campaign_ids !== void 0 === detachAll) {
4950
+ return err({
4951
+ kind: "invalid-input",
4952
+ message: "Choose either `campaign_ids` or `detach_all: true` \u2014 not both, and not neither. Passing neither would be a silent no-op."
4953
+ });
4954
+ }
4955
+ const result = await ctx.api.detachPolicySetCampaigns(input.policy_set_id, {
4956
+ detach_all: detachAll,
4957
+ ...input.campaign_ids !== void 0 ? { campaign_ids: [...input.campaign_ids] } : {}
4958
+ });
4959
+ if (result.isErr()) return err(mapApiError(result.error));
4960
+ return ok({ detached: detachAll ? "all" : input.campaign_ids?.length ?? 0 });
4961
+ }
4962
+ };
4625
4963
  var GetPolicySetInputShape = {
4626
4964
  policy_set_id: z.string().uuid().describe("Policy set UUID.")
4627
4965
  };
@@ -4642,6 +4980,33 @@ var getPolicySetTool = {
4642
4980
  return ok(result.value);
4643
4981
  }
4644
4982
  };
4983
+ var ListPolicySetCampaignsInputShape = {
4984
+ policy_set_id: z.string().uuid().describe("Policy set UUID."),
4985
+ q: z.string().min(1).max(200).optional().describe("Substring search against campaign name (case-insensitive)."),
4986
+ page: z.number().int().min(1).max(500).default(1).describe("1-indexed page number."),
4987
+ limit: z.number().int().min(1).max(200).default(50).describe("Page size (1-200).")
4988
+ };
4989
+ var listPolicySetCampaignsTool = {
4990
+ name: "list_policy_set_campaigns",
4991
+ description: "List the campaigns bound to a policy set, paginated, each with `id`, `name` and `is_archived`. `get_policy_set` returns only the first page of bindings plus a `campaigns_total`, so use this tool when a set has more campaigns than that page holds, or to search them by name. Read the membership here before `detach_policy_set_campaigns` \u2014 and before `delete_policy_set`, which the API refuses while any active campaign is still bound.",
4992
+ annotations: {
4993
+ title: "List Policy Set Campaigns",
4994
+ readOnlyHint: true,
4995
+ destructiveHint: false,
4996
+ idempotentHint: true,
4997
+ openWorldHint: false
4998
+ },
4999
+ inputSchema: z.object(ListPolicySetCampaignsInputShape),
5000
+ handler: async (input, ctx) => {
5001
+ const result = await ctx.api.listPolicySetCampaigns(input.policy_set_id, {
5002
+ page: input.page,
5003
+ limit: input.limit,
5004
+ ...input.q !== void 0 ? { q: input.q } : {}
5005
+ });
5006
+ if (result.isErr()) return err(mapApiError(result.error));
5007
+ return ok(result.value);
5008
+ }
5009
+ };
4645
5010
  var ListPolicySetsInputShape = {
4646
5011
  page: z.number().int().min(1).max(500).default(1).describe("1-indexed page number."),
4647
5012
  limit: z.number().int().min(1).max(200).default(50).describe("Page size."),
@@ -4927,6 +5292,32 @@ var getScanTool = {
4927
5292
  return ok(result.value);
4928
5293
  }
4929
5294
  };
5295
+ var GetScanCreativeHtmlInputShape = {
5296
+ scan_id: z.string().uuid().describe("Scan UUID.")
5297
+ };
5298
+ var getScanCreativeHtmlTool = {
5299
+ name: "get_scan_creative_html",
5300
+ description: "Fetch the generated creative markup for an ad-tag scan as text \u2014 the HTML the ad tag produced, with the scripts, iframes and click-through URLs it embedded. Use it to explain WHY a creative was tagged when a screenshot cannot show it (obfuscated redirectors, hidden trackers, injected handlers). Only ad-tag scans have this artifact; a URL or VAST scan returns not-found. The markup is returned inert as text and is never executed. Markup over 256 KiB is refused \u2014 open the report URL from `get_scan` for those.",
5301
+ annotations: {
5302
+ title: "Get Scan Creative HTML",
5303
+ readOnlyHint: true,
5304
+ destructiveHint: false,
5305
+ idempotentHint: true,
5306
+ openWorldHint: false
5307
+ },
5308
+ inputSchema: z.object(GetScanCreativeHtmlInputShape),
5309
+ handler: async (input, ctx) => {
5310
+ const result = await ctx.api.getScanCreativeHtml(input.scan_id);
5311
+ if (result.isErr()) return err(mapApiError(result.error));
5312
+ const { bytes, contentType } = result.value;
5313
+ return ok({
5314
+ scan_id: input.scan_id,
5315
+ content_type: contentType,
5316
+ byte_size: bytes.byteLength,
5317
+ html: decodeUtf8(bytes)
5318
+ });
5319
+ }
5320
+ };
4930
5321
  var GetScanCreativeScreenshotInputShape = {
4931
5322
  scan_id: z.string().uuid().describe("Scan UUID \u2014 must be an ad-tag scan (is_ad_tag=true)."),
4932
5323
  width: z.number().int().min(50).max(2e3).optional().describe("Optional resize width in pixels.")
@@ -4948,6 +5339,27 @@ var getScanCreativeScreenshotTool = {
4948
5339
  return ok(imageBlock(result.value.bytes, result.value.contentType));
4949
5340
  }
4950
5341
  };
5342
+ var GetScanCreativeVideoInputShape = {
5343
+ scan_id: z.string().uuid().describe("Scan UUID.")
5344
+ };
5345
+ var getScanCreativeVideoTool = {
5346
+ name: "get_scan_creative_video",
5347
+ description: "Fetch the MP4 MediaFile a VAST scan downloaded, as one inline resource block. Only VAST scans have it; others return not-found. Prefer `get_scan_creative_screenshot` for a still frame and `get_scan_vast_xml` for the declared metadata \u2014 both are far cheaper. Reach for the video itself only when the moving image is the evidence. Files over 8 MiB are refused rather than inlined; fall back to `get_scan_creative_screenshot` or the report URL from `get_scan` for those.",
5348
+ annotations: {
5349
+ title: "Get Scan Creative Video",
5350
+ readOnlyHint: true,
5351
+ destructiveHint: false,
5352
+ idempotentHint: true,
5353
+ openWorldHint: false
5354
+ },
5355
+ inputSchema: z.object(GetScanCreativeVideoInputShape),
5356
+ handler: async (input, ctx) => {
5357
+ const result = await ctx.api.getScanCreativeVideo(input.scan_id);
5358
+ if (result.isErr()) return err(mapApiError(result.error));
5359
+ const { bytes, contentType } = result.value;
5360
+ return ok(resourceBlock(bytes, contentType, `/api/v1/scans/${input.scan_id}/creative-video`));
5361
+ }
5362
+ };
4951
5363
  var GetScanLandingScreenshotInputShape = {
4952
5364
  scan_id: z.string().uuid().describe("Scan UUID (ad-tag scan)."),
4953
5365
  landing_ord: z.number().int().min(0).max(50).describe("0-indexed landing slot (see `get_scan(...).landings[*].ord`)."),
@@ -4999,6 +5411,32 @@ var getScanScreenshotTool = {
4999
5411
  return ok(imageBlock(result.value.bytes, result.value.contentType));
5000
5412
  }
5001
5413
  };
5414
+ var GetScanVastXmlInputShape = {
5415
+ scan_id: z.string().uuid().describe("Scan UUID.")
5416
+ };
5417
+ var getScanVastXmlTool = {
5418
+ name: "get_scan_vast_xml",
5419
+ description: "Fetch the VAST document for a VAST scan as text, with wrappers already resolved so the whole chain is visible in one payload. Use it to inspect the tracking pixels, verification vendors, companion ads and click-through targets a video ad declared \u2014 the parsed summary on `get_scan` (`video` block) covers duration, media file and ad system, but not the full element tree. Only VAST scans have this artifact; others return not-found. Documents over 256 KiB are refused.",
5420
+ annotations: {
5421
+ title: "Get Scan VAST XML",
5422
+ readOnlyHint: true,
5423
+ destructiveHint: false,
5424
+ idempotentHint: true,
5425
+ openWorldHint: false
5426
+ },
5427
+ inputSchema: z.object(GetScanVastXmlInputShape),
5428
+ handler: async (input, ctx) => {
5429
+ const result = await ctx.api.getScanVastXml(input.scan_id);
5430
+ if (result.isErr()) return err(mapApiError(result.error));
5431
+ const { bytes, contentType } = result.value;
5432
+ return ok({
5433
+ scan_id: input.scan_id,
5434
+ content_type: contentType,
5435
+ byte_size: bytes.byteLength,
5436
+ xml: decodeUtf8(bytes)
5437
+ });
5438
+ }
5439
+ };
5002
5440
  var ListScanChildrenInputShape = {
5003
5441
  scan_id: z.string().uuid().describe("The publisher scan's UUID (an ad-discovery scan created with ad_discovery=true)."),
5004
5442
  page: z.number().int().min(1).max(500).default(1).describe("1-indexed page number."),
@@ -5026,7 +5464,7 @@ var listScanChildrenTool = {
5026
5464
  };
5027
5465
  var ListScansInputShape = {
5028
5466
  status: z.string().optional().describe(
5029
- "Comma-separated statuses to filter by. Lifecycle values: pending, running, crawled, checking, checking_async, completed, partial, failed, cancelled."
5467
+ "Comma-separated statuses to filter by. Lifecycle values: pending, running, crawled, checking, checking_async, rechecking, completed, partial, failed, cancelled."
5030
5468
  ),
5031
5469
  country_code: z.string().optional().describe("Comma-separated ISO 3166-1 alpha-2 country codes, e.g. US,DE,JP."),
5032
5470
  url: z.string().optional().describe("Substring match against the scanned URL."),
@@ -5039,7 +5477,13 @@ var ListScansInputShape = {
5039
5477
  run_id: z.string().uuid().optional().describe("Filter to scans of one run."),
5040
5478
  campaign_id: z.string().uuid().optional().describe("Filter to scans of one campaign."),
5041
5479
  group_id: z.string().uuid().optional().describe("Filter to scans of one campaign group."),
5480
+ parent_scan_id: z.string().uuid().optional().describe(
5481
+ "Filter to the ad-discovery child scans of one parent scan. Setting this lifts the default 7-day window, so old children stay reachable. `list_scan_children` is the paginated equivalent."
5482
+ ),
5042
5483
  tag: z.string().optional().describe("Comma-separated tag slugs to filter by."),
5484
+ tag_match: z.enum(["any", "all"]).optional().describe(
5485
+ "How multiple `tag` slugs combine: `any` (default) returns scans carrying at least one, `all` requires every one of them."
5486
+ ),
5043
5487
  ai_category: z.string().optional().describe(
5044
5488
  "Filter by the freeform LLM-generated category prefix on the scan (slash-separated tier path, e.g. 'Gambling/Online Casinos')."
5045
5489
  ),
@@ -5086,7 +5530,9 @@ var listScansTool = {
5086
5530
  ...input.run_id !== void 0 ? { run_id: input.run_id } : {},
5087
5531
  ...input.campaign_id !== void 0 ? { campaign_id: input.campaign_id } : {},
5088
5532
  ...input.group_id !== void 0 ? { group_id: input.group_id } : {},
5533
+ ...input.parent_scan_id !== void 0 ? { parent_scan_id: input.parent_scan_id } : {},
5089
5534
  ...input.tag !== void 0 ? { tag: input.tag } : {},
5535
+ ...input.tag_match !== void 0 ? { tag_match: input.tag_match } : {},
5090
5536
  ...input.ai_category !== void 0 ? { ai_category: input.ai_category } : {},
5091
5537
  ...input.iab_v3_category !== void 0 ? { iab_v3_category: input.iab_v3_category } : {},
5092
5538
  ...input.iab_category !== void 0 ? { iab_category: input.iab_category } : {},
@@ -5153,7 +5599,7 @@ var GetTagDefinitionInputShape = {
5153
5599
  };
5154
5600
  var getTagDefinitionTool = {
5155
5601
  name: "get_tag_definition",
5156
- description: "Get full definition of one tag: display name, description, severity, category, source (system vs custom), public-report visibility, usage counts, plus `linked_rules` \u2014 the custom rules currently producing this tag (id, name, active flag). Fetch a specific rule's full config via `get_custom_rule`.",
5602
+ description: "Get full definition of one tag: display name, description, severity, category, source (system vs custom), public-report visibility, usage counts, plus `linked_rules` \u2014 the custom rules currently producing this tag (id, name, active flag). Fetch a specific rule's full config via `get_custom_rule`. Returns 404 for a retired (archived) tag this organization does not own, including every archived system tag.",
5157
5603
  annotations: {
5158
5604
  title: "Get Tag Definition",
5159
5605
  readOnlyHint: true,
@@ -5173,7 +5619,7 @@ var ListScanTagsInputShape = {
5173
5619
  };
5174
5620
  var listScanTagsTool = {
5175
5621
  name: "list_scan_tags",
5176
- description: "List every tag (system + custom) attached to one scan by the checker pipeline, with display name, category, and severity.",
5622
+ description: "List every tag (system + custom) attached to one scan by the checker pipeline, with display name, category, and severity. Tags whose definition has since been archived are omitted \u2014 the scan can therefore report fewer tags than when it ran.",
5177
5623
  annotations: {
5178
5624
  title: "List Scan Tags",
5179
5625
  readOnlyHint: true,
@@ -5191,11 +5637,14 @@ var listScanTagsTool = {
5191
5637
  var ListTagsInputShape = {
5192
5638
  category: z.string().min(1).max(100).optional().describe(
5193
5639
  "Filter to tags in one category slug (e.g. 'security', 'malware', 'compliance'). Use the slug exactly as it appears in `category` of an existing tag."
5640
+ ),
5641
+ include_archived: z.boolean().optional().describe(
5642
+ "Also return the archived tag definitions this organization owns, which are excluded by default. An archived tag is retired: it is no longer assigned to scans and no longer raises alerts, and archived platform (system) tags are never returned at all \u2014 so this flag cannot resolve a slug seen on an older scan."
5194
5643
  )
5195
5644
  };
5196
5645
  var listTagsTool = {
5197
5646
  name: "list_tags",
5198
- description: "List every tag definition the platform knows (system tags + organization custom tags) with category, severity, visibility, and usage counters (scans + rules per tag). Optionally filter by category.",
5647
+ description: "List every tag definition in service for this organization (system tags + organization custom tags) with category, severity, visibility, and usage counters (scans + rules per tag). Optionally filter by category, and pass `include_archived` to also see the retired definitions this organization owns.",
5199
5648
  annotations: {
5200
5649
  title: "List Tags",
5201
5650
  readOnlyHint: true,
@@ -5205,7 +5654,10 @@ var listTagsTool = {
5205
5654
  },
5206
5655
  inputSchema: z.object(ListTagsInputShape),
5207
5656
  handler: async (input, ctx) => {
5208
- const filters = input.category !== void 0 ? { category: input.category } : void 0;
5657
+ const filters = {
5658
+ ...input.category !== void 0 ? { category: input.category } : {},
5659
+ ...input.include_archived !== void 0 ? { include_archived: input.include_archived } : {}
5660
+ };
5209
5661
  const result = await ctx.api.listTags(filters);
5210
5662
  if (result.isErr()) return err(mapApiError(result.error));
5211
5663
  return ok({ items: result.value, total: result.value.length });
@@ -5539,6 +5991,9 @@ function registerAllTools(register) {
5539
5991
  register(getScanScreenshotTool);
5540
5992
  register(getScanCreativeScreenshotTool);
5541
5993
  register(getScanLandingScreenshotTool);
5994
+ register(getScanCreativeHtmlTool);
5995
+ register(getScanCreativeVideoTool);
5996
+ register(getScanVastXmlTool);
5542
5997
  register(getCampaignTool);
5543
5998
  register(listCampaignsTool);
5544
5999
  register(archiveCampaignTool);
@@ -5586,8 +6041,12 @@ function registerAllTools(register) {
5586
6041
  register(updatePolicySetTool);
5587
6042
  register(deletePolicySetTool);
5588
6043
  register(requestPolicySetApprovalTool);
6044
+ register(listPolicySetCampaignsTool);
6045
+ register(attachPolicySetCampaignsTool);
6046
+ register(detachPolicySetCampaignsTool);
5589
6047
  register(listAlertsTool);
5590
6048
  register(updateAlertStatusTool);
6049
+ register(bulkUpdateAlertStatusTool);
5591
6050
  register(getAlertStatsTool);
5592
6051
  register(listWebhooksTool);
5593
6052
  register(getWebhookTool);
@@ -5691,5 +6150,5 @@ function formatToolError(error) {
5691
6150
  }
5692
6151
 
5693
6152
  export { BearerToken, SERVER_INSTRUCTIONS, createHttpApiGateway, createPinoLogger, declareEmptyResourcesAndPrompts, newRequestId, wireToolsIntoMcpServer };
5694
- //# sourceMappingURL=chunk-HOKDLKJN.js.map
5695
- //# sourceMappingURL=chunk-HOKDLKJN.js.map
6153
+ //# sourceMappingURL=chunk-IEOPWWSC.js.map
6154
+ //# sourceMappingURL=chunk-IEOPWWSC.js.map