@kaminari-ad/mcp 0.2.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { err, ok } from './chunk-BAZQPU6T.js';
2
+ import { err, ok } from './chunk-RBCMUVI6.js';
3
3
  import { createHash, randomUUID } from 'crypto';
4
4
  import createClient from 'openapi-fetch';
5
5
  import { fetch } from 'undici';
@@ -148,12 +148,20 @@ function toApiError(status2, parsed, retryAfterHeader) {
148
148
  }
149
149
  return { kind: "upstream", detail: message, status: status2 };
150
150
  }
151
+ var OrganizationStatus = z.enum([
152
+ "pending_email_verification",
153
+ "pending_admin_approval",
154
+ "active",
155
+ "suspended",
156
+ "rejected"
157
+ ]);
151
158
  var OrgResponse = z.object({
152
159
  id: z.string().uuid(),
153
160
  name: z.string(),
154
161
  owner_id: z.string().uuid(),
155
- is_active: z.boolean(),
156
- created_at: z.string().datetime({ offset: true })
162
+ status: OrganizationStatus,
163
+ created_at: z.string().datetime({ offset: true }),
164
+ is_active: z.boolean()
157
165
  }).passthrough();
158
166
  z.object({ name: z.union([z.string(), z.null()]) }).partial().passthrough();
159
167
  var ValidationError = z.object({
@@ -164,7 +172,7 @@ var ValidationError = z.object({
164
172
  ctx: z.object({}).partial().passthrough().optional()
165
173
  }).passthrough();
166
174
  z.object({ detail: z.array(ValidationError) }).partial().passthrough();
167
- z.object({
175
+ var LabelDefinitionResponse = z.object({
168
176
  key: z.string(),
169
177
  display_name: z.string(),
170
178
  position: z.number().int(),
@@ -180,7 +188,10 @@ var UserResponse = z.object({
180
188
  id: z.string().uuid(),
181
189
  email: z.string(),
182
190
  name: z.string(),
191
+ role_id: z.string().uuid(),
183
192
  role_name: z.string(),
193
+ organization_id: z.string().uuid(),
194
+ organization_name: z.string(),
184
195
  is_active: z.boolean(),
185
196
  created_at: z.string().datetime({ offset: true })
186
197
  }).passthrough();
@@ -271,16 +282,35 @@ var ProxyTargetResponse = z.object({
271
282
  city: z.string().optional().default(""),
272
283
  isp: z.string().optional().default("")
273
284
  }).passthrough();
274
- var IabCategoryResponse = z.object({
285
+ var AiCategoryResponse = z.object({
275
286
  tier1: z.string(),
276
287
  tier2: z.union([z.string(), z.null()]).optional(),
277
288
  tier3: z.union([z.string(), z.null()]).optional(),
278
289
  tier4: z.union([z.string(), z.null()]).optional()
279
290
  }).passthrough();
291
+ var IabV3CategoryResponse = z.object({
292
+ tier1: z.string(),
293
+ tier2: z.union([z.string(), z.null()]).optional(),
294
+ tier3: z.union([z.string(), z.null()]).optional(),
295
+ tier4: z.union([z.string(), z.null()]).optional()
296
+ }).passthrough();
297
+ var ScanTaxonomyClassificationResponse = z.object({
298
+ taxonomy_id: z.string().uuid(),
299
+ taxonomy_name: z.string(),
300
+ taxonomy_slug: z.string(),
301
+ taxonomy_version: z.number().int(),
302
+ leaf_node_id: z.union([z.string(), z.null()]),
303
+ tier1: z.union([z.string(), z.null()]),
304
+ tier2: z.union([z.string(), z.null()]).optional(),
305
+ tier3: z.union([z.string(), z.null()]).optional(),
306
+ tier4: z.union([z.string(), z.null()]).optional(),
307
+ used_default: z.boolean().optional().default(false)
308
+ }).passthrough();
280
309
  var ScanClassificationResponse = z.object({
281
310
  brand: z.union([z.string(), z.null()]),
282
- iab_v2: z.union([IabCategoryResponse, z.null()]),
283
- iab_v3: z.union([IabCategoryResponse, z.null()])
311
+ ai_category: z.union([AiCategoryResponse, z.null()]),
312
+ iab_v3: z.union([IabV3CategoryResponse, z.null()]),
313
+ custom_taxonomies: z.array(ScanTaxonomyClassificationResponse).default([])
284
314
  }).partial().passthrough();
285
315
  var LandingResponse = z.object({
286
316
  ord: z.number().int(),
@@ -532,7 +562,7 @@ var TagDefinitionWithStatsResponse = z.object({
532
562
  description: z.string(),
533
563
  is_system: z.boolean(),
534
564
  organization_id: z.union([z.string(), z.null()]),
535
- show_in_public_report: z.boolean(),
565
+ visibility: z.string(),
536
566
  severity: z.string(),
537
567
  scans_count: z.number().int(),
538
568
  rules_count: z.number().int()
@@ -552,17 +582,18 @@ var TagDefinitionDetailResponse = z.object({
552
582
  description: z.string(),
553
583
  is_system: z.boolean(),
554
584
  organization_id: z.union([z.string(), z.null()]),
555
- show_in_public_report: z.boolean(),
585
+ visibility: z.string(),
556
586
  severity: z.string(),
557
587
  scans_count: z.number().int(),
558
588
  rules_count: z.number().int(),
559
589
  linked_rules: z.array(LinkedRuleResponse).optional()
560
590
  }).passthrough();
591
+ var TagVisibility = z.enum(["hidden", "internal", "public"]);
561
592
  var TagSeverity = z.enum(["high", "medium", "low"]);
562
593
  z.object({
563
594
  display_name: z.union([z.string(), z.null()]),
564
595
  description: z.union([z.string(), z.null()]),
565
- show_in_public_report: z.union([z.boolean(), z.null()]),
596
+ visibility: z.union([TagVisibility, z.null()]),
566
597
  severity: z.union([TagSeverity, z.null()])
567
598
  }).partial().passthrough();
568
599
  z.object({
@@ -613,18 +644,66 @@ var RuleTestResponse = z.object({
613
644
  llm_prompt_url: z.string().optional().default(""),
614
645
  llm_response_url: z.string().optional().default("")
615
646
  }).passthrough();
616
- var PolicyEntryRequest = z.object({
617
- tag_slug: z.string().min(1).max(100),
618
- country_codes: z.array(z.string()).max(50).optional()
647
+ var IabV3PolicyCategoryRequest = z.object({
648
+ tier1: z.string().min(1).max(200),
649
+ tier2: z.union([z.string(), z.null()]).optional(),
650
+ tier3: z.union([z.string(), z.null()]).optional(),
651
+ tier4: z.union([z.string(), z.null()]).optional()
652
+ }).passthrough();
653
+ var AiCategoryRequest = z.object({
654
+ tier1: z.string().min(1).max(200),
655
+ tier2: z.union([z.string(), z.null()]).optional(),
656
+ tier3: z.union([z.string(), z.null()]).optional(),
657
+ tier4: z.union([z.string(), z.null()]).optional()
658
+ }).passthrough();
659
+ var CustomTaxonomyRefRequest = z.object({
660
+ taxonomy_id: z.string().uuid(),
661
+ tier1: z.string().min(1).max(200),
662
+ tier2: z.union([z.string(), z.null()]).optional(),
663
+ tier3: z.union([z.string(), z.null()]).optional(),
664
+ tier4: z.union([z.string(), z.null()]).optional()
619
665
  }).passthrough();
666
+ var PolicyEntryRequest = z.object({
667
+ rule_type: z.enum(["tag", "iab_v3", "brand", "ai_category", "custom_taxonomy"]).default("tag"),
668
+ tag_slug: z.union([z.string(), z.null()]),
669
+ iab_v3: z.union([IabV3PolicyCategoryRequest, z.null()]),
670
+ brand: z.union([z.string(), z.null()]),
671
+ ai_category: z.union([AiCategoryRequest, z.null()]),
672
+ custom_taxonomy: z.union([CustomTaxonomyRefRequest, z.null()]),
673
+ country_codes: z.array(z.string()).max(50)
674
+ }).partial().passthrough();
620
675
  z.object({
621
676
  name: z.string().min(1).max(200),
622
677
  description: z.string().max(2e3).optional().default(""),
623
678
  entries: z.array(PolicyEntryRequest).min(1).max(500)
624
679
  }).passthrough();
680
+ var PolicyEntryIabV3Response = z.object({
681
+ tier1: z.string(),
682
+ tier2: z.union([z.string(), z.null()]).optional(),
683
+ tier3: z.union([z.string(), z.null()]).optional(),
684
+ tier4: z.union([z.string(), z.null()]).optional()
685
+ }).passthrough();
686
+ var PolicyEntryAiCategoryResponse = z.object({
687
+ tier1: z.string(),
688
+ tier2: z.union([z.string(), z.null()]).optional(),
689
+ tier3: z.union([z.string(), z.null()]).optional(),
690
+ tier4: z.union([z.string(), z.null()]).optional()
691
+ }).passthrough();
692
+ var PolicyEntryCustomTaxonomyResponse = z.object({
693
+ taxonomy_id: z.string().uuid(),
694
+ tier1: z.string(),
695
+ tier2: z.union([z.string(), z.null()]).optional(),
696
+ tier3: z.union([z.string(), z.null()]).optional(),
697
+ tier4: z.union([z.string(), z.null()]).optional()
698
+ }).passthrough();
625
699
  var PolicyEntryResponse = z.object({
626
700
  id: z.string().uuid(),
627
- tag_slug: z.string(),
701
+ rule_type: z.string(),
702
+ tag_slug: z.union([z.string(), z.null()]).optional(),
703
+ iab_v3: z.union([PolicyEntryIabV3Response, z.null()]).optional(),
704
+ brand: z.union([z.string(), z.null()]).optional(),
705
+ ai_category: z.union([PolicyEntryAiCategoryResponse, z.null()]).optional(),
706
+ custom_taxonomy: z.union([PolicyEntryCustomTaxonomyResponse, z.null()]).optional(),
628
707
  country_codes: z.array(z.string())
629
708
  }).passthrough();
630
709
  var PolicySetResponse = z.object({
@@ -677,7 +756,9 @@ var AlertResponse = z.object({
677
756
  updated_at: z.union([z.string(), z.null()]),
678
757
  scan_url: z.string(),
679
758
  offer_url: z.string(),
680
- tag_display_name: z.string()
759
+ tag_display_name: z.string(),
760
+ rule_type: z.string().optional().default("tag"),
761
+ matched_value: z.union([z.string(), z.null()]).optional()
681
762
  }).passthrough();
682
763
  z.object({
683
764
  items: z.array(AlertResponse),
@@ -932,8 +1013,67 @@ z.object({
932
1013
  id: z.string().uuid(),
933
1014
  received_at: z.string().datetime({ offset: true })
934
1015
  }).passthrough();
1016
+ var CustomTaxonomyListItem = z.object({
1017
+ id: z.string().uuid(),
1018
+ name: z.string(),
1019
+ slug: z.string(),
1020
+ description: z.string(),
1021
+ is_active: z.boolean(),
1022
+ version: z.number().int(),
1023
+ node_count: z.number().int(),
1024
+ created_at: z.string().datetime({ offset: true }),
1025
+ updated_at: z.string().datetime({ offset: true })
1026
+ }).passthrough();
1027
+ var TaxonomyNodeRequest = z.object({
1028
+ client_id: z.string().min(1).max(100),
1029
+ parent_client_id: z.union([z.string(), z.null()]).optional(),
1030
+ name: z.string().min(1).max(100),
1031
+ description: z.string().max(200).optional().default(""),
1032
+ is_default: z.boolean().optional().default(false)
1033
+ });
1034
+ z.object({
1035
+ name: z.string().min(1).max(100),
1036
+ description: z.string().max(500).optional().default(""),
1037
+ nodes: z.array(TaxonomyNodeRequest).optional().default([])
1038
+ });
1039
+ var TaxonomyNodeResponse = z.object({
1040
+ id: z.string().uuid(),
1041
+ parent_id: z.union([z.string(), z.null()]),
1042
+ level: z.number().int(),
1043
+ position: z.number().int(),
1044
+ name: z.string(),
1045
+ description: z.string(),
1046
+ is_default: z.boolean()
1047
+ }).passthrough();
1048
+ var CustomTaxonomyResponse = z.object({
1049
+ id: z.string().uuid(),
1050
+ organization_id: z.string().uuid(),
1051
+ name: z.string(),
1052
+ slug: z.string(),
1053
+ description: z.string(),
1054
+ is_active: z.boolean(),
1055
+ version: z.number().int(),
1056
+ nodes: z.array(TaxonomyNodeResponse),
1057
+ created_at: z.string().datetime({ offset: true }),
1058
+ updated_at: z.string().datetime({ offset: true })
1059
+ }).passthrough();
1060
+ z.object({
1061
+ text: z.string().min(1).max(5e4)
1062
+ });
1063
+ var ParsedTaxonomyNode = z.object({
1064
+ level: z.number().int(),
1065
+ name: z.string(),
1066
+ description: z.string()
1067
+ }).passthrough();
1068
+ var ParseTaxonomyTextResponse = z.object({ nodes: z.array(ParsedTaxonomyNode), warnings: z.array(z.string()) }).passthrough();
1069
+ z.object({
1070
+ name: z.string().min(1).max(100),
1071
+ description: z.string().max(500).optional().default(""),
1072
+ nodes: z.array(TaxonomyNodeRequest).optional().default([])
1073
+ });
935
1074
  var schemas = {
936
1075
  OrgResponse,
1076
+ LabelDefinitionResponse,
937
1077
  UserResponse,
938
1078
  ApiKeyResponse,
939
1079
  ApiKeyCreatedResponse,
@@ -970,7 +1110,12 @@ var schemas = {
970
1110
  BulkReplayResponse,
971
1111
  AlertNotificationDestinationResponse,
972
1112
  CampaignOverridesResponse,
973
- InvoiceResponse};
1113
+ InvoiceResponse,
1114
+ CustomTaxonomyListItem,
1115
+ TaxonomyNodeResponse,
1116
+ CustomTaxonomyResponse,
1117
+ ParsedTaxonomyNode,
1118
+ ParseTaxonomyTextResponse};
974
1119
  function parseWithSchema(schema, raw, label) {
975
1120
  const parsed = schema.safeParse(raw);
976
1121
  if (parsed.success) return ok(stripUndefinedKeys(parsed.data));
@@ -1020,6 +1165,8 @@ var AlertSchema = schemas.AlertResponse.pick({
1020
1165
  campaign_id: true,
1021
1166
  policy_set_id: true,
1022
1167
  violation_rule_id: true,
1168
+ rule_type: true,
1169
+ matched_value: true,
1023
1170
  tag_slug: true,
1024
1171
  tag_display_name: true,
1025
1172
  country_code: true,
@@ -1054,7 +1201,13 @@ var BillingSummarySchema = schemas.BillingSummaryResponse.pick({
1054
1201
  is_suspended: true,
1055
1202
  can_create_scan: true,
1056
1203
  billing_mode: true,
1057
- block_reason: true
1204
+ block_reason: true,
1205
+ current_plan_is_custom: true,
1206
+ credit_limit_micros: true,
1207
+ effective_minimum_balance_micros: true,
1208
+ scheduled_next_plan_id: true,
1209
+ scheduled_next_plan_name: true,
1210
+ scheduled_effective_at: true
1058
1211
  }).strip();
1059
1212
  var parseBillingSummary = (raw) => parseWithSchema(BillingSummarySchema, raw, "billing-summary");
1060
1213
 
@@ -1067,10 +1220,18 @@ var CampaignSchema = schemas.CampaignResponse.pick({
1067
1220
  ad_tag: true,
1068
1221
  country_codes: true,
1069
1222
  group_id: true,
1223
+ emulator_selection: true,
1224
+ proxy_type: true,
1225
+ proxy_region: true,
1226
+ proxy_city: true,
1227
+ proxy_isp: true,
1070
1228
  labels: true,
1071
1229
  policy_set_id: true,
1072
1230
  schedule_enabled: true,
1073
1231
  schedule_type: true,
1232
+ schedule_weekly: true,
1233
+ schedule_interval_seconds: true,
1234
+ schedule_timezone: true,
1074
1235
  is_archived: true,
1075
1236
  created_at: true,
1076
1237
  last_run_at: true
@@ -1143,6 +1304,49 @@ var CustomRuleSchema2 = schemas.CustomRuleResponse.pick({
1143
1304
  created_at: true
1144
1305
  }).strip();
1145
1306
  var parseCustomRulePage = (raw) => parsePagedWithItemSchema(CustomRuleSchema2, raw, "custom-rules");
1307
+ var CustomTaxonomyListItemSchema = schemas.CustomTaxonomyListItem.pick({
1308
+ id: true,
1309
+ name: true,
1310
+ slug: true,
1311
+ description: true,
1312
+ is_active: true,
1313
+ version: true,
1314
+ node_count: true,
1315
+ created_at: true,
1316
+ updated_at: true
1317
+ }).strip();
1318
+ var CustomTaxonomyListSchema = z.array(CustomTaxonomyListItemSchema);
1319
+ var TaxonomyNodeSchema = schemas.TaxonomyNodeResponse.pick({
1320
+ id: true,
1321
+ parent_id: true,
1322
+ level: true,
1323
+ position: true,
1324
+ name: true,
1325
+ description: true,
1326
+ is_default: true
1327
+ }).strip();
1328
+ var CustomTaxonomySchema = schemas.CustomTaxonomyResponse.pick({
1329
+ id: true,
1330
+ organization_id: true,
1331
+ name: true,
1332
+ slug: true,
1333
+ description: true,
1334
+ is_active: true,
1335
+ version: true,
1336
+ created_at: true,
1337
+ updated_at: true
1338
+ }).extend({ nodes: z.array(TaxonomyNodeSchema) }).strip();
1339
+ var ParsedTaxonomyNodeSchema = schemas.ParsedTaxonomyNode.pick({
1340
+ level: true,
1341
+ name: true,
1342
+ description: true
1343
+ }).strip();
1344
+ var ParseTaxonomyTextResponseSchema = schemas.ParseTaxonomyTextResponse.pick({
1345
+ warnings: true
1346
+ }).extend({ nodes: z.array(ParsedTaxonomyNodeSchema) }).strip();
1347
+ var parseCustomTaxonomyList = (raw) => parseWithSchema(CustomTaxonomyListSchema, raw, "custom-taxonomies");
1348
+ var parseCustomTaxonomy = (raw) => parseWithSchema(CustomTaxonomySchema, raw, "custom-taxonomy");
1349
+ var parseTaxonomyTextPreview = (raw) => parseWithSchema(ParseTaxonomyTextResponseSchema, raw, "custom-taxonomy-parse-text");
1146
1350
 
1147
1351
  // src/infrastructure/api/parsers/parse-empty.ts
1148
1352
  function parseEmpty(_raw) {
@@ -1200,6 +1404,7 @@ var UserSchema = schemas.UserResponse.pick({
1200
1404
  id: true,
1201
1405
  email: true,
1202
1406
  name: true,
1407
+ role_id: true,
1203
1408
  role_name: true,
1204
1409
  is_active: true,
1205
1410
  created_at: true
@@ -1213,6 +1418,13 @@ var RoleSchema = schemas.RoleResponse.pick({
1213
1418
  permissions: true
1214
1419
  }).strip();
1215
1420
  var parseRole = (raw) => parseWithSchema(RoleSchema, raw, "role");
1421
+ var LabelDefinitionSchema = schemas.LabelDefinitionResponse.pick({
1422
+ key: true,
1423
+ display_name: true,
1424
+ position: true,
1425
+ auto_extract: true
1426
+ }).strip();
1427
+ var parseLabelDefinition = (raw) => parseWithSchema(LabelDefinitionSchema, raw, "label-definition");
1216
1428
  var ApiKeyCreatedSchema = schemas.ApiKeyCreatedResponse.pick({
1217
1429
  id: true,
1218
1430
  key_prefix: true,
@@ -1354,7 +1566,12 @@ var GroupActionSchema = schemas.GroupActionResponse.pick({
1354
1566
  var parseGroupAction = (raw) => parseWithSchema(GroupActionSchema, raw, "group-action");
1355
1567
  schemas.PolicyEntryResponse.pick({
1356
1568
  id: true,
1569
+ rule_type: true,
1357
1570
  tag_slug: true,
1571
+ iab_v3: true,
1572
+ brand: true,
1573
+ ai_category: true,
1574
+ custom_taxonomy: true,
1358
1575
  country_codes: true
1359
1576
  }).strip();
1360
1577
  var GeoSchema = schemas.GeoResponse.pick({
@@ -1465,7 +1682,7 @@ var TagDefinitionSchema = schemas.TagDefinitionWithStatsResponse.pick({
1465
1682
  severity: true,
1466
1683
  is_system: true,
1467
1684
  organization_id: true,
1468
- show_in_public_report: true,
1685
+ visibility: true,
1469
1686
  scans_count: true,
1470
1687
  rules_count: true
1471
1688
  }).strip();
@@ -1484,7 +1701,7 @@ var TagDetailSchema = schemas.TagDefinitionDetailResponse.pick({
1484
1701
  severity: true,
1485
1702
  is_system: true,
1486
1703
  organization_id: true,
1487
- show_in_public_report: true,
1704
+ visibility: true,
1488
1705
  scans_count: true,
1489
1706
  rules_count: true
1490
1707
  }).extend({ linked_rules: z.array(LinkedRuleSchema).optional() }).strip();
@@ -1592,6 +1809,56 @@ function createHttpApiGateway(config) {
1592
1809
  }
1593
1810
  return err(toApiError(status2, error ?? data, response.headers.get("retry-after") ?? void 0));
1594
1811
  }
1812
+ async function binaryGet(path, query) {
1813
+ const startedAtMs = Date.now();
1814
+ const qs = query && Object.keys(query).length > 0 ? `?${new URLSearchParams(
1815
+ Object.fromEntries(Object.entries(query).map(([k, v]) => [k, String(v)]))
1816
+ ).toString()}` : "";
1817
+ const url = `${baseUrl}${path}${qs}`;
1818
+ let response;
1819
+ try {
1820
+ const init = {
1821
+ method: "GET",
1822
+ // Header allowlist: see binaryGet docstring above. Keep this
1823
+ // map minimal; any future addition needs a CONTRIBUTING.md
1824
+ // update + a tenant-isolation review.
1825
+ headers: {
1826
+ authorization: bearer.toAuthorizationHeader(),
1827
+ accept: "image/*, application/pdf, application/octet-stream",
1828
+ "user-agent": "kaminari-ad-mcp",
1829
+ "x-request-id": requestId
1830
+ }
1831
+ };
1832
+ if (dispatcher !== void 0) init["dispatcher"] = dispatcher;
1833
+ response = await fetch(
1834
+ url,
1835
+ init
1836
+ );
1837
+ } catch (cause) {
1838
+ logger.warn({ api_path: path, elapsed_ms: Date.now() - startedAtMs }, "api.network_error");
1839
+ const inner = cause instanceof Error && cause.cause instanceof Error ? cause.cause : cause instanceof Error ? cause : null;
1840
+ return err({ kind: "upstream", detail: inner !== null ? inner.message : "network error" });
1841
+ }
1842
+ const status2 = response.status;
1843
+ logger.info(
1844
+ { api_path: path, api_status: status2, elapsed_ms: Date.now() - startedAtMs },
1845
+ "api.done"
1846
+ );
1847
+ if (status2 >= 200 && status2 < 300) {
1848
+ const buf = await response.arrayBuffer();
1849
+ return ok({
1850
+ bytes: new Uint8Array(buf),
1851
+ contentType: response.headers.get("content-type") ?? "application/octet-stream"
1852
+ });
1853
+ }
1854
+ let errBody;
1855
+ try {
1856
+ errBody = await response.json();
1857
+ } catch {
1858
+ errBody = void 0;
1859
+ }
1860
+ return err(toApiError(status2, errBody, response.headers.get("retry-after") ?? void 0));
1861
+ }
1595
1862
  return {
1596
1863
  // ── Account ───────────────────────────────────────────────────
1597
1864
  async getAccount() {
@@ -1633,6 +1900,33 @@ function createHttpApiGateway(config) {
1633
1900
  async listOrgRoles() {
1634
1901
  return call("GET", "/api/v1/account/roles", {}, parseArrayOf(parseRole));
1635
1902
  },
1903
+ async createCustomRole(body) {
1904
+ return call("POST", "/api/v1/account/roles", { body }, parseRole);
1905
+ },
1906
+ async listAccountLabels() {
1907
+ return call("GET", "/api/v1/account/labels", {}, parseArrayOf(parseLabelDefinition));
1908
+ },
1909
+ async updateAccountLabels(body) {
1910
+ return call("PUT", "/api/v1/account/labels", { body }, parseArrayOf(parseLabelDefinition));
1911
+ },
1912
+ async getScanScreenshot(scanId, w2) {
1913
+ return binaryGet(`/api/v1/scans/${scanId}/screenshot`, w2 !== void 0 ? { w: w2 } : void 0);
1914
+ },
1915
+ async getScanCreativeScreenshot(scanId, w2) {
1916
+ return binaryGet(
1917
+ `/api/v1/scans/${scanId}/creative-screenshot`,
1918
+ w2 !== void 0 ? { w: w2 } : void 0
1919
+ );
1920
+ },
1921
+ async getScanLandingScreenshot(scanId, landingOrd, w2) {
1922
+ return binaryGet(
1923
+ `/api/v1/scans/${scanId}/landings/${String(landingOrd)}/screenshot`,
1924
+ w2 !== void 0 ? { w: w2 } : void 0
1925
+ );
1926
+ },
1927
+ async getInvoicePdf(invoiceId) {
1928
+ return binaryGet(`/api/v1/invoices/${invoiceId}/pdf`);
1929
+ },
1636
1930
  async listApiKeys() {
1637
1931
  return call("GET", "/api/v1/account/api-keys", {}, parseApiKeyList);
1638
1932
  },
@@ -1759,8 +2053,13 @@ function createHttpApiGateway(config) {
1759
2053
  parsePageOf(parseRun)
1760
2054
  );
1761
2055
  },
1762
- async listCampaignsPicker() {
1763
- return call("GET", "/api/v1/campaigns/picker", {}, parseCampaignPickerArray);
2056
+ async listCampaignsPicker(filters) {
2057
+ return call(
2058
+ "GET",
2059
+ "/api/v1/campaigns/picker",
2060
+ filters ? { params: { query: filters } } : {},
2061
+ parseCampaignPickerArray
2062
+ );
1764
2063
  },
1765
2064
  // ── Runs ──────────────────────────────────────────────────────
1766
2065
  async getRun(id) {
@@ -1859,8 +2158,13 @@ function createHttpApiGateway(config) {
1859
2158
  );
1860
2159
  },
1861
2160
  // ── Tag definitions ───────────────────────────────────────────
1862
- async listTags() {
1863
- return call("GET", "/api/v1/tag-definitions", {}, parseTagDefinitionArray);
2161
+ async listTags(filters) {
2162
+ return call(
2163
+ "GET",
2164
+ "/api/v1/tag-definitions",
2165
+ filters ? { params: { query: filters } } : {},
2166
+ parseTagDefinitionArray
2167
+ );
1864
2168
  },
1865
2169
  async getTagDefinition(slug) {
1866
2170
  return call(
@@ -1964,6 +2268,53 @@ function createHttpApiGateway(config) {
1964
2268
  parseEmpty
1965
2269
  );
1966
2270
  },
2271
+ // ── Custom taxonomies ─────────────────────────────────────────
2272
+ async listCustomTaxonomies() {
2273
+ return call("GET", "/api/v1/custom-taxonomies", {}, parseCustomTaxonomyList);
2274
+ },
2275
+ async getCustomTaxonomy(id) {
2276
+ return call(
2277
+ "GET",
2278
+ "/api/v1/custom-taxonomies/{taxonomy_id}",
2279
+ { params: { path: { taxonomy_id: id } } },
2280
+ parseCustomTaxonomy
2281
+ );
2282
+ },
2283
+ async createCustomTaxonomy(body) {
2284
+ return call("POST", "/api/v1/custom-taxonomies", { body }, parseCustomTaxonomy);
2285
+ },
2286
+ async updateCustomTaxonomy(id, body) {
2287
+ return call(
2288
+ "PUT",
2289
+ "/api/v1/custom-taxonomies/{taxonomy_id}",
2290
+ { params: { path: { taxonomy_id: id } }, body },
2291
+ parseCustomTaxonomy
2292
+ );
2293
+ },
2294
+ async deleteCustomTaxonomy(id) {
2295
+ return call(
2296
+ "DELETE",
2297
+ "/api/v1/custom-taxonomies/{taxonomy_id}",
2298
+ { params: { path: { taxonomy_id: id } } },
2299
+ parseEmpty
2300
+ );
2301
+ },
2302
+ async restoreCustomTaxonomy(id) {
2303
+ return call(
2304
+ "POST",
2305
+ "/api/v1/custom-taxonomies/{taxonomy_id}/restore",
2306
+ { params: { path: { taxonomy_id: id } } },
2307
+ parseCustomTaxonomy
2308
+ );
2309
+ },
2310
+ async parseCustomTaxonomyText(body) {
2311
+ return call(
2312
+ "POST",
2313
+ "/api/v1/custom-taxonomies/parse-text",
2314
+ { body },
2315
+ parseTaxonomyTextPreview
2316
+ );
2317
+ },
1967
2318
  // ── Alerts ────────────────────────────────────────────────────
1968
2319
  async listAlerts(filters) {
1969
2320
  return call("GET", "/api/v1/alerts", { params: { query: filters } }, parseAlertPage);
@@ -2254,6 +2605,32 @@ var createApiKeyTool = {
2254
2605
  return ok(result.value);
2255
2606
  }
2256
2607
  };
2608
+ var CreateCustomRoleInputShape = {
2609
+ name: z.string().min(1).max(100).describe("Display name for the role."),
2610
+ permissions: z.array(z.string().min(1).max(100)).min(1).max(200).describe(
2611
+ "Permission slugs the role grants (e.g. 'scans.read', 'campaigns.write'). Use `list_org_roles` to see permissions on existing system roles."
2612
+ )
2613
+ };
2614
+ var createCustomRoleTool = {
2615
+ name: "create_custom_role",
2616
+ description: "Create a custom organization role with the supplied permission set. Returns the persisted role; assign it to users via `update_user_role`.",
2617
+ annotations: {
2618
+ title: "Create Custom Role",
2619
+ readOnlyHint: false,
2620
+ destructiveHint: false,
2621
+ idempotentHint: false,
2622
+ openWorldHint: false
2623
+ },
2624
+ inputSchema: z.object(CreateCustomRoleInputShape),
2625
+ handler: async (input, ctx) => {
2626
+ const result = await ctx.api.createCustomRole({
2627
+ name: input.name,
2628
+ permissions: [...input.permissions]
2629
+ });
2630
+ if (result.isErr()) return err(mapApiError(result.error));
2631
+ return ok(result.value);
2632
+ }
2633
+ };
2257
2634
  var GetAccountInputShape = {};
2258
2635
  var getAccountTool = {
2259
2636
  name: "get_account",
@@ -2275,7 +2652,8 @@ var getAccountTool = {
2275
2652
  var InviteUserInputShape = {
2276
2653
  email: z.string().email().describe("Email of the person to invite. They get a signup link."),
2277
2654
  role_id: z.string().uuid().describe("UUID of the role to assign on accept. Get UUIDs from `list_org_roles`."),
2278
- name: z.string().min(1).optional().describe("Optional display name for the invitee.")
2655
+ name: z.string().min(1).optional().describe("Optional display name for the invitee."),
2656
+ timezone: z.string().optional().describe("Optional IANA timezone for the invitee, e.g. 'Europe/Berlin'. Default: UTC.")
2279
2657
  };
2280
2658
  var inviteUserTool = {
2281
2659
  name: "invite_user",
@@ -2294,11 +2672,30 @@ var inviteUserTool = {
2294
2672
  role_id: input.role_id
2295
2673
  };
2296
2674
  if (input.name !== void 0) body.name = input.name;
2675
+ if (input.timezone !== void 0) body.timezone = input.timezone;
2297
2676
  const result = await ctx.api.inviteUser(body);
2298
2677
  if (result.isErr()) return err(mapApiError(result.error));
2299
2678
  return ok(result.value);
2300
2679
  }
2301
2680
  };
2681
+ var ListAccountLabelsInputShape = {};
2682
+ var listAccountLabelsTool = {
2683
+ name: "list_account_labels",
2684
+ description: "List the organization's custom label definitions (key, display_name, position, auto_extract). Labels enrich scans and can be used as filters in `list_scans`.",
2685
+ annotations: {
2686
+ title: "List Account Labels",
2687
+ readOnlyHint: true,
2688
+ destructiveHint: false,
2689
+ idempotentHint: true,
2690
+ openWorldHint: false
2691
+ },
2692
+ inputSchema: z.object(ListAccountLabelsInputShape),
2693
+ handler: async (_input, ctx) => {
2694
+ const result = await ctx.api.listAccountLabels();
2695
+ if (result.isErr()) return err(mapApiError(result.error));
2696
+ return ok({ items: result.value });
2697
+ }
2698
+ };
2302
2699
  var ListApiKeysInputShape = {};
2303
2700
  var listApiKeysTool = {
2304
2701
  name: "list_api_keys",
@@ -2413,13 +2810,51 @@ var transferOwnershipTool = {
2413
2810
  return ok({ transferred: true });
2414
2811
  }
2415
2812
  };
2813
+ var LabelItem = z.object({
2814
+ key: z.string().min(1).max(100).regex(/^[a-z0-9_]+$/, "key must be snake_case ([a-z0-9_]+)").describe("Stable snake_case identifier used in scan filters."),
2815
+ display_name: z.string().min(1).max(200).describe("Human-readable name shown in the UI."),
2816
+ auto_extract: z.boolean().optional().describe(
2817
+ "When true, the LLM auto-populates this label's value during scan classification. Defaults to false."
2818
+ )
2819
+ });
2820
+ var UpdateAccountLabelsInputShape = {
2821
+ labels: z.array(LabelItem).max(20).describe(
2822
+ "REPLACEMENT list of all labels \u2014 pass every label you want to keep. Order in the array becomes `position`. Empty array removes all labels."
2823
+ )
2824
+ };
2825
+ var updateAccountLabelsTool = {
2826
+ name: "update_account_labels",
2827
+ description: "REPLACE the organization's full set of custom label definitions. The API replaces the list (no merge); read the current list with `list_account_labels` first. Returns the persisted list with allocated positions.",
2828
+ annotations: {
2829
+ title: "Update Account Labels",
2830
+ readOnlyHint: false,
2831
+ // REPLACE semantics: passing `labels: []` wipes every label and
2832
+ // detaches the value on every scan that referenced one. Hint as
2833
+ // destructive so MCP clients that gate dangerous tools warn the
2834
+ // user before this call runs.
2835
+ destructiveHint: true,
2836
+ idempotentHint: true,
2837
+ openWorldHint: false
2838
+ },
2839
+ inputSchema: z.object(UpdateAccountLabelsInputShape),
2840
+ handler: async (input, ctx) => {
2841
+ const result = await ctx.api.updateAccountLabels({
2842
+ labels: input.labels.map((l) => ({
2843
+ key: l.key,
2844
+ display_name: l.display_name,
2845
+ auto_extract: l.auto_extract ?? false
2846
+ }))
2847
+ });
2848
+ if (result.isErr()) return err(mapApiError(result.error));
2849
+ return ok({ items: result.value });
2850
+ }
2851
+ };
2416
2852
  var UpdateOrgInputShape = {
2417
- name: z.string().min(1).max(200).optional().describe("New organization display name."),
2418
- settings: z.record(z.unknown()).optional().describe("Replacement settings object. Fields not supplied are left unchanged.")
2853
+ name: z.string().min(1).max(200).optional().describe("New organization display name.")
2419
2854
  };
2420
2855
  var updateOrgTool = {
2421
2856
  name: "update_org",
2422
- description: "Update the caller's organization \u2014 display name and/or settings object. Only supplied fields are touched.",
2857
+ description: "Update the caller's organization display name.",
2423
2858
  annotations: {
2424
2859
  title: "Update Organization",
2425
2860
  readOnlyHint: false,
@@ -2430,8 +2865,7 @@ var updateOrgTool = {
2430
2865
  inputSchema: z.object(UpdateOrgInputShape),
2431
2866
  handler: async (input, ctx) => {
2432
2867
  const body = {
2433
- ...input.name !== void 0 ? { name: input.name } : {},
2434
- ...input.settings !== void 0 ? { settings: input.settings } : {}
2868
+ ...input.name !== void 0 ? { name: input.name } : {}
2435
2869
  };
2436
2870
  const result = await ctx.api.updateOrg(body);
2437
2871
  if (result.isErr()) return err(mapApiError(result.error));
@@ -2574,7 +3008,7 @@ var setCampaignAlertOverridesTool = {
2574
3008
  var GetAlertStatsInputShape = {};
2575
3009
  var getAlertStatsTool = {
2576
3010
  name: "get_alert_stats",
2577
- description: "Get alert counts grouped by status: open, ack, resolved, ignored, total.",
3011
+ description: "Get alert counts grouped by status: open, acknowledged, resolved, dismissed (total is implicit in the sum). Status names match the canonical AlertStatus enum used by `list_alerts` and `update_alert_status`.",
2578
3012
  annotations: {
2579
3013
  title: "Get Alert Stats",
2580
3014
  readOnlyHint: true,
@@ -2591,13 +3025,15 @@ var getAlertStatsTool = {
2591
3025
  };
2592
3026
  var ListAlertsInputShape = {
2593
3027
  campaign_id: z.string().uuid().optional().describe("Filter to one campaign's alerts."),
2594
- status: z.enum(["open", "ack", "resolved", "ignored"]).optional().describe("Filter by alert status."),
3028
+ status: z.enum(["open", "acknowledged", "resolved", "dismissed"]).optional().describe(
3029
+ "Filter by alert status. Open = unhandled; acknowledged = seen by an operator; resolved = closed; dismissed = closed without action."
3030
+ ),
2595
3031
  page: z.number().int().min(1).max(500).default(1).describe("1-indexed page number."),
2596
3032
  limit: z.number().int().min(1).max(200).default(50).describe("Page size.")
2597
3033
  };
2598
3034
  var listAlertsTool = {
2599
3035
  name: "list_alerts",
2600
- description: "List violation alerts (one per scan + violating-tag combo) with offer URL, tag, country, status, scan back-reference.",
3036
+ 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).",
2601
3037
  annotations: {
2602
3038
  title: "List Alerts",
2603
3039
  readOnlyHint: true,
@@ -2675,15 +3111,29 @@ var getUsageSummaryTool = {
2675
3111
  return ok(result.value);
2676
3112
  }
2677
3113
  };
3114
+ var TransactionTypeEnum = z.enum([
3115
+ "initial_balance",
3116
+ "top_up_manual",
3117
+ "usage_charge",
3118
+ "subscription_renewal",
3119
+ "subscription_upgrade",
3120
+ "admin_adjustment",
3121
+ "refund",
3122
+ "invoice_settlement",
3123
+ "crypto_top_up"
3124
+ ]);
2678
3125
  var ListBalanceHistoryInputShape = {
2679
3126
  date_from: z.string().date().optional().describe("ISO date, inclusive."),
2680
3127
  date_to: z.string().date().optional().describe("ISO date, inclusive."),
3128
+ type: z.array(TransactionTypeEnum).max(9).optional().describe(
3129
+ "Filter by transaction kind. Pass several values to OR them (e.g. ['top_up_manual','crypto_top_up'] for credits-only)."
3130
+ ),
2681
3131
  page: z.number().int().min(1).max(500).default(1).describe("1-indexed page."),
2682
3132
  limit: z.number().int().min(1).max(200).default(50).describe("Page size.")
2683
3133
  };
2684
3134
  var listBalanceHistoryTool = {
2685
3135
  name: "list_balance_history",
2686
- description: "List ledger transactions (charges, refunds, top-ups, invoice settlements) on the organization's balance. Each row: type, amount in micros, description, timestamp.",
3136
+ description: "List ledger transactions (charges, refunds, top-ups, invoice settlements) on the organization's balance. Each row: type, amount in micros, description, timestamp. Filter by `type` (multi-select) and / or date range.",
2687
3137
  annotations: {
2688
3138
  title: "List Balance History",
2689
3139
  readOnlyHint: true,
@@ -2697,7 +3147,8 @@ var listBalanceHistoryTool = {
2697
3147
  page: input.page,
2698
3148
  limit: input.limit,
2699
3149
  ...input.date_from !== void 0 ? { date_from: input.date_from } : {},
2700
- ...input.date_to !== void 0 ? { date_to: input.date_to } : {}
3150
+ ...input.date_to !== void 0 ? { date_to: input.date_to } : {},
3151
+ ...input.type !== void 0 ? { type: input.type } : {}
2701
3152
  };
2702
3153
  const result = await ctx.api.listBalanceHistory(filters);
2703
3154
  if (result.isErr()) return err(mapApiError(result.error));
@@ -2705,8 +3156,10 @@ var listBalanceHistoryTool = {
2705
3156
  }
2706
3157
  };
2707
3158
  var ListUsageInputShape = {
2708
- date_from: z.string().date().optional().describe("ISO date (YYYY-MM-DD), inclusive."),
2709
- date_to: z.string().date().optional().describe("ISO date (YYYY-MM-DD), inclusive."),
3159
+ date_from: z.string().datetime({ offset: true }).optional().describe(
3160
+ "ISO 8601 datetime (with timezone offset), inclusive lower bound on the usage timestamp. Plain dates also work \u2014 the API normalises to UTC midnight."
3161
+ ),
3162
+ date_to: z.string().datetime({ offset: true }).optional().describe("ISO 8601 datetime (with offset), inclusive upper bound."),
2710
3163
  scan_id: z.string().uuid().optional().describe("Filter to one scan's cost rows."),
2711
3164
  page: z.number().int().min(1).max(500).default(1).describe("1-indexed page."),
2712
3165
  limit: z.number().int().min(1).max(200).default(50).describe("Page size.")
@@ -2918,12 +3371,11 @@ var unarchiveCampaignGroupTool = {
2918
3371
  };
2919
3372
  var UpdateCampaignGroupInputShape = {
2920
3373
  group_id: z.string().uuid().describe("Group UUID to update."),
2921
- name: z.string().min(1).max(200).optional().describe("New display name."),
2922
- schedule_paused: z.boolean().optional().describe("Pause/resume the scheduler for every campaign in this group.")
3374
+ name: z.string().min(1).max(200).optional().describe("New display name.")
2923
3375
  };
2924
3376
  var updateCampaignGroupTool = {
2925
3377
  name: "update_campaign_group",
2926
- description: "Update a campaign group: rename, or pause/resume the scheduler (the pause cascades to every campaign in the group).",
3378
+ description: "Rename a campaign group. To pause/resume its scheduler, use `pause_campaign_group_schedule` / `resume_campaign_group_schedule` instead.",
2927
3379
  annotations: {
2928
3380
  title: "Update Campaign Group",
2929
3381
  readOnlyHint: false,
@@ -2934,8 +3386,7 @@ var updateCampaignGroupTool = {
2934
3386
  inputSchema: z.object(UpdateCampaignGroupInputShape),
2935
3387
  handler: async (input, ctx) => {
2936
3388
  const body = {
2937
- ...input.name !== void 0 ? { name: input.name } : {},
2938
- ...input.schedule_paused !== void 0 ? { schedule_paused: input.schedule_paused } : {}
3389
+ ...input.name !== void 0 ? { name: input.name } : {}
2939
3390
  };
2940
3391
  const result = await ctx.api.updateCampaignGroup(input.group_id, body);
2941
3392
  if (result.isErr()) return err(mapApiError(result.error));
@@ -2982,6 +3433,52 @@ var cancelCampaignTool = {
2982
3433
  return ok(result.value);
2983
3434
  }
2984
3435
  };
3436
+ var campaignConfigFields = {
3437
+ emulator_categories: z.array(z.string()).optional().describe(
3438
+ "Device categories to rotate through, e.g. ['android_phone']. Valid: android_phone, android_tablet, iphone, ipad, windows_desktop, macos_desktop. On create, omitting this defaults to ['android_phone']; pass [] together with emulator_specific_ids to target ONLY specific devices."
3439
+ ),
3440
+ emulator_specific_ids: z.array(z.string()).optional().describe(
3441
+ "Pin exact device profile slugs from `list_emulators` (the `id` field), e.g. ['samsung_galaxy_s23_ultra_android16']. Always scanned regardless of emulator_mode."
3442
+ ),
3443
+ emulator_mode: z.enum(["random", "all"]).optional().describe(
3444
+ "How selected categories expand per run: 'random' = one random device per category (the UI's 'Random (1 per group)'), 'all' = every device in each category ('All checked'). Does not affect emulator_specific_ids. Default: random."
3445
+ ),
3446
+ proxy_type: z.enum(["residential", "mobile"]).optional().describe("Proxy network type. Default: residential."),
3447
+ proxy_region: z.string().optional().describe("Proxy region/state targeting (free-text; only honoured for a single country)."),
3448
+ proxy_city: z.string().optional().describe("Proxy city targeting."),
3449
+ proxy_isp: z.string().optional().describe("Proxy ISP targeting."),
3450
+ schedule_type: z.enum(["weekly", "interval"]).optional().describe(
3451
+ "Scheduling mode: 'weekly' (run on a weekday/hour grid via schedule_weekly) or 'interval' (run every schedule_interval_seconds). Omit for a manual (run-on-demand) campaign."
3452
+ ),
3453
+ schedule_weekly: z.record(z.array(z.number().int().min(0).max(23))).optional().describe(
3454
+ "Weekly run grid for schedule_type='weekly'. Keys are weekdays '0'-'6' (Mon-Sun); values are hours 0-23 in schedule_timezone, e.g. { '0': [9, 17], '4': [12] }."
3455
+ ),
3456
+ schedule_interval_seconds: z.number().int().optional().describe(
3457
+ "Run interval for schedule_type='interval'. Allowed presets (seconds): 60, 120, 300, 600, 900, 1800, 2700, 3600, 7200, 14400, 28800, 86400."
3458
+ ),
3459
+ schedule_timezone: z.string().optional().describe("IANA timezone for the weekly grid, e.g. 'Europe/Berlin'. Default: UTC.")
3460
+ };
3461
+ function pickCampaignConfigBody(input) {
3462
+ const body = {};
3463
+ if (input.emulator_categories !== void 0) body.emulator_categories = input.emulator_categories;
3464
+ if (input.emulator_specific_ids !== void 0) {
3465
+ body.emulator_specific_ids = input.emulator_specific_ids;
3466
+ }
3467
+ if (input.emulator_mode !== void 0) body.emulator_mode = input.emulator_mode;
3468
+ if (input.proxy_type !== void 0) body.proxy_type = input.proxy_type;
3469
+ if (input.proxy_region !== void 0) body.proxy_region = input.proxy_region;
3470
+ if (input.proxy_city !== void 0) body.proxy_city = input.proxy_city;
3471
+ if (input.proxy_isp !== void 0) body.proxy_isp = input.proxy_isp;
3472
+ if (input.schedule_type !== void 0) body.schedule_type = input.schedule_type;
3473
+ if (input.schedule_weekly !== void 0) body.schedule_weekly = input.schedule_weekly;
3474
+ if (input.schedule_interval_seconds !== void 0) {
3475
+ body.schedule_interval_seconds = input.schedule_interval_seconds;
3476
+ }
3477
+ if (input.schedule_timezone !== void 0) body.schedule_timezone = input.schedule_timezone;
3478
+ return body;
3479
+ }
3480
+
3481
+ // src/application/tools/campaigns/create-campaign.tool.ts
2985
3482
  var CreateCampaignInputShape = {
2986
3483
  name: z.string().min(1).max(200).describe("Display name (1-200 chars)."),
2987
3484
  campaign_type: z.enum(["url", "ad_tag"]).describe("`url` or `ad_tag` \u2014 must match the target field below."),
@@ -2989,7 +3486,7 @@ var CreateCampaignInputShape = {
2989
3486
  ad_tag: z.string().optional().describe("Ad-tag HTML/JS (required if campaign_type=ad_tag)."),
2990
3487
  country_codes: z.array(z.string().length(2)).min(1).describe("ISO 3166-1 alpha-2 codes \u2014 one scan per country per run."),
2991
3488
  group_id: z.string().uuid().optional().describe("Parent group UUID; defaults to the org's default group."),
2992
- emulator_categories: z.array(z.string()).optional().describe("Categories of device profiles to rotate through. Default: all available."),
3489
+ ...campaignConfigFields,
2993
3490
  labels: z.record(z.string()).optional().describe("Arbitrary metadata applied to every queued scan."),
2994
3491
  policy_set_id: z.string().uuid().optional().describe("Policy set to evaluate every scan against."),
2995
3492
  schedule_enabled: z.boolean().optional().describe("If true, the scheduler runs immediately. Default: false (manual run).")
@@ -3013,10 +3510,10 @@ var createCampaignTool = {
3013
3510
  ...input.url !== void 0 ? { url: input.url } : {},
3014
3511
  ...input.ad_tag !== void 0 ? { ad_tag: input.ad_tag } : {},
3015
3512
  ...input.group_id !== void 0 ? { group_id: input.group_id } : {},
3016
- ...input.emulator_categories !== void 0 ? { emulator_categories: input.emulator_categories } : {},
3017
3513
  ...input.labels !== void 0 ? { labels: input.labels } : {},
3018
3514
  ...input.policy_set_id !== void 0 ? { policy_set_id: input.policy_set_id } : {},
3019
- ...input.schedule_enabled !== void 0 ? { schedule_enabled: input.schedule_enabled } : {}
3515
+ ...input.schedule_enabled !== void 0 ? { schedule_enabled: input.schedule_enabled } : {},
3516
+ ...pickCampaignConfigBody(input)
3020
3517
  };
3021
3518
  const result = await ctx.api.createCampaign(body);
3022
3519
  if (result.isErr()) return err(mapApiError(result.error));
@@ -3070,12 +3567,16 @@ var listCampaignRunsTool = {
3070
3567
  };
3071
3568
  var ListCampaignsInputShape = {
3072
3569
  group_id: z.string().uuid().optional().describe("Optional campaign-group UUID to filter by."),
3570
+ archived: z.boolean().optional().describe(
3571
+ "When omitted, the API returns active campaigns only. Pass `true` to see archived campaigns, `false` for active-only (explicit)."
3572
+ ),
3573
+ q: z.string().min(1).max(200).optional().describe("Substring search against campaign name (case-insensitive)."),
3073
3574
  page: z.number().int().min(1).max(500).default(1).describe("1-indexed page number."),
3074
3575
  limit: z.number().int().min(1).max(200).default(50).describe("Page size (1-200).")
3075
3576
  };
3076
3577
  var listCampaignsTool = {
3077
3578
  name: "list_campaigns",
3078
- description: "List campaigns for the caller's organization, optionally filtered by group, paginated.",
3579
+ description: "List campaigns for the caller's organization, optionally filtered by group, archived flag, or name substring. Paginated.",
3079
3580
  annotations: {
3080
3581
  title: "List Campaigns",
3081
3582
  readOnlyHint: true,
@@ -3088,14 +3589,21 @@ var listCampaignsTool = {
3088
3589
  const filters = {
3089
3590
  page: input.page,
3090
3591
  limit: input.limit,
3091
- ...input.group_id !== void 0 ? { group_id: input.group_id } : {}
3592
+ ...input.group_id !== void 0 ? { group_id: input.group_id } : {},
3593
+ ...input.archived !== void 0 ? { archived: input.archived } : {},
3594
+ ...input.q !== void 0 ? { q: input.q } : {}
3092
3595
  };
3093
3596
  const result = await ctx.api.listCampaigns(filters);
3094
3597
  if (result.isErr()) return err(mapApiError(result.error));
3095
3598
  return ok(result.value);
3096
3599
  }
3097
3600
  };
3098
- var ListCampaignsPickerInputShape = {};
3601
+ var ListCampaignsPickerInputShape = {
3602
+ archived: z.boolean().optional().describe("Pass true to include archived campaigns. Defaults to active-only."),
3603
+ group_id: z.string().uuid().optional().describe("Filter to one campaign group."),
3604
+ q: z.string().min(1).max(200).optional().describe("Substring search against campaign name (case-insensitive)."),
3605
+ limit: z.number().int().min(1).max(500).optional().describe("Maximum rows to return (default per API, typically 200).")
3606
+ };
3099
3607
  var listCampaignsPickerTool = {
3100
3608
  name: "list_campaigns_picker",
3101
3609
  description: "Slim per-row campaign list for selection UIs \u2014 id, name, group_id, is_archived. Cheaper than `list_campaigns` for orgs with thousands of campaigns. Use `get_campaign(id)` after a selection to fetch full details.",
@@ -3107,8 +3615,16 @@ var listCampaignsPickerTool = {
3107
3615
  openWorldHint: false
3108
3616
  },
3109
3617
  inputSchema: z.object(ListCampaignsPickerInputShape),
3110
- handler: async (_input, ctx) => {
3111
- const result = await ctx.api.listCampaignsPicker();
3618
+ handler: async (input, ctx) => {
3619
+ const filters = {
3620
+ ...input.archived !== void 0 ? { archived: input.archived } : {},
3621
+ ...input.group_id !== void 0 ? { group_id: input.group_id } : {},
3622
+ ...input.q !== void 0 ? { q: input.q } : {},
3623
+ ...input.limit !== void 0 ? { limit: input.limit } : {}
3624
+ };
3625
+ const result = await ctx.api.listCampaignsPicker(
3626
+ Object.keys(filters).length > 0 ? filters : void 0
3627
+ );
3112
3628
  if (result.isErr()) return err(mapApiError(result.error));
3113
3629
  return ok(result.value);
3114
3630
  }
@@ -3156,7 +3672,11 @@ var unarchiveCampaignTool = {
3156
3672
  var UpdateCampaignInputShape = {
3157
3673
  campaign_id: z.string().uuid().describe("Campaign UUID to update."),
3158
3674
  name: z.string().min(1).max(200).optional().describe("New display name."),
3675
+ url: z.string().url().optional().describe("New target URL (url-type campaigns)."),
3676
+ ad_tag: z.string().optional().describe("New ad-tag HTML/JS (ad_tag-type campaigns)."),
3159
3677
  country_codes: z.array(z.string().length(2)).optional().describe("Replace the country list."),
3678
+ group_id: z.string().uuid().optional().describe("Move the campaign to another group."),
3679
+ ...campaignConfigFields,
3160
3680
  labels: z.record(z.string()).optional().describe("Replace the label map."),
3161
3681
  policy_set_id: z.string().uuid().nullable().optional().describe("New policy set UUID; pass null to clear."),
3162
3682
  schedule_enabled: z.boolean().optional().describe("Pause / resume the scheduler.")
@@ -3175,10 +3695,14 @@ var updateCampaignTool = {
3175
3695
  handler: async (input, ctx) => {
3176
3696
  const body = {
3177
3697
  ...input.name !== void 0 ? { name: input.name } : {},
3698
+ ...input.url !== void 0 ? { url: input.url } : {},
3699
+ ...input.ad_tag !== void 0 ? { ad_tag: input.ad_tag } : {},
3178
3700
  ...input.country_codes !== void 0 ? { country_codes: input.country_codes } : {},
3701
+ ...input.group_id !== void 0 ? { group_id: input.group_id } : {},
3179
3702
  ...input.labels !== void 0 ? { labels: input.labels } : {},
3180
3703
  ...input.policy_set_id !== void 0 ? { policy_set_id: input.policy_set_id } : {},
3181
- ...input.schedule_enabled !== void 0 ? { schedule_enabled: input.schedule_enabled } : {}
3704
+ ...input.schedule_enabled !== void 0 ? { schedule_enabled: input.schedule_enabled } : {},
3705
+ ...pickCampaignConfigBody(input)
3182
3706
  };
3183
3707
  const result = await ctx.api.updateCampaign(input.campaign_id, body);
3184
3708
  if (result.isErr()) return err(mapApiError(result.error));
@@ -3186,17 +3710,25 @@ var updateCampaignTool = {
3186
3710
  }
3187
3711
  };
3188
3712
  var CreateCustomRuleInputShape = {
3189
- name: z.string().min(1).max(200).describe("Display name."),
3190
- tag_slug: z.string().max(100).optional().describe("Tag slug to assign on match. Empty = create-only (advanced)."),
3191
- rule_type: z.string().max(50).describe("Rule engine: regex | substring | iab_category | etc. (API validates)."),
3192
- config: z.record(z.unknown()).describe("Rule-type-specific configuration object. Shape depends on rule_type."),
3713
+ name: z.string().min(1).max(200).describe(
3714
+ "Display name. For non-LLM rules this also becomes the `display_name` of the auto-registered tag definition (see `tag_slug`)."
3715
+ ),
3716
+ tag_slug: z.string().max(100).optional().describe(
3717
+ "Tag slug to assign on match. Empty = create-only (advanced). The API auto-registers a custom tag definition for this slug with `display_name = name`. **MUST NOT collide with a built-in system tag slug** (see `list_tags` where `is_system=true`); colliding requests return 422 with code `checking.system_slug_reserved`. For `rule_type='llm'` use `config.tags` keys instead and leave `tag_slug` empty."
3718
+ ),
3719
+ rule_type: z.string().max(50).describe(
3720
+ "Rule engine. One of: `stopword_content`, `stopword_url`, `regexp_content`, `regexp_url`, `blacklist_domain`, `combo`, `llm`. The API validates."
3721
+ ),
3722
+ config: z.record(z.unknown()).describe(
3723
+ "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`)."
3724
+ ),
3193
3725
  target: z.string().max(30).optional().describe(
3194
3726
  "Where to apply the rule (e.g. 'page' for landing HTML). Default: page. See API docs for the full set of valid values."
3195
3727
  )
3196
3728
  };
3197
3729
  var createCustomRuleTool = {
3198
3730
  name: "create_custom_rule",
3199
- description: "Define a custom tag-detection rule (regex / substring / category). Matches will tag every future scan; existing scans are untouched until you call `recheck_scans`.",
3731
+ 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`.",
3200
3732
  annotations: {
3201
3733
  title: "Create Custom Rule",
3202
3734
  readOnlyHint: false,
@@ -3278,8 +3810,12 @@ var listCustomRulesTool = {
3278
3810
  }
3279
3811
  };
3280
3812
  var TestCustomRuleInputShape = {
3281
- rule_type: z.string().max(50).describe("Rule engine type (regex, substring, ...)."),
3282
- config: z.record(z.unknown()).describe("Rule-type-specific config to test."),
3813
+ rule_type: z.string().max(50).describe(
3814
+ "Rule engine type. One of: `stopword_content`, `stopword_url`, `regexp_content`, `regexp_url`, `blacklist_domain`, `combo`, `llm`."
3815
+ ),
3816
+ config: z.record(z.unknown()).describe(
3817
+ "Rule-type-specific config to test. Same shape as `create_custom_rule`'s `config`. NOTE: `test_custom_rule` evaluates the rule against a scan WITHOUT persisting it, so slug-collision validation does NOT run here \u2014 verify slugs against `list_tags` (`is_system=true`) before promoting to `create_custom_rule`."
3818
+ ),
3283
3819
  target: z.string().max(30).describe(
3284
3820
  "Where to apply the rule (e.g. 'page' for landing HTML). See API docs for the full set of valid values."
3285
3821
  ),
@@ -3287,7 +3823,7 @@ var TestCustomRuleInputShape = {
3287
3823
  };
3288
3824
  var testCustomRuleTool = {
3289
3825
  name: "test_custom_rule",
3290
- 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`.",
3826
+ description: "Preview-test a rule definition against an existing scan WITHOUT persisting the rule. Returns `matched: bool`, evaluation time, and per-tag-slug details. Use to validate config before `create_custom_rule`. NOTE: slug-collision validation does NOT run in preview mode \u2014 verify any `tag_slug` (or LLM `config.tags` keys) against `list_tags` (`is_system=true`) before promoting to `create_custom_rule`, which would otherwise reject the same payload with HTTP 422 / code `checking.system_slug_reserved`.",
3291
3827
  annotations: {
3292
3828
  title: "Test Custom Rule",
3293
3829
  readOnlyHint: true,
@@ -3309,9 +3845,15 @@ var testCustomRuleTool = {
3309
3845
  };
3310
3846
  var UpdateCustomRuleInputShape = {
3311
3847
  rule_id: z.string().uuid().describe("Rule UUID to update."),
3312
- name: z.string().min(1).max(200).optional().describe("New display name."),
3313
- tag_slug: z.string().max(100).optional().describe("New tag slug to assign on match."),
3314
- config: z.record(z.unknown()).optional().describe("New rule-type-specific config object."),
3848
+ name: z.string().min(1).max(200).optional().describe(
3849
+ "New display name. For non-LLM rules this also becomes the new `display_name` of the auto-registered tag definition."
3850
+ ),
3851
+ tag_slug: z.string().max(100).optional().describe(
3852
+ "New tag slug to assign on match. **MUST NOT collide with a built-in system tag slug** (see `list_tags` where `is_system=true`); colliding requests return 422 with code `checking.system_slug_reserved`. Re-registering an existing custom slug refreshes its tag definition's `display_name` / `description`."
3853
+ ),
3854
+ config: z.record(z.unknown()).optional().describe(
3855
+ "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."
3856
+ ),
3315
3857
  target: z.string().max(30).optional().describe(
3316
3858
  "Where to apply the rule (e.g. 'page' for landing HTML). See API docs for the full set of valid values."
3317
3859
  ),
@@ -3319,7 +3861,7 @@ var UpdateCustomRuleInputShape = {
3319
3861
  };
3320
3862
  var updateCustomRuleTool = {
3321
3863
  name: "update_custom_rule",
3322
- description: "Update a custom tag-detection rule. Only supplied fields are sent. To toggle activation, pass `is_active`. Existing tagged scans are NOT re-evaluated \u2014 call `recheck_scans` for that. (Rule engine `rule_type` cannot be changed after creation; create a new rule instead.)",
3864
+ 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.)",
3323
3865
  annotations: {
3324
3866
  title: "Update Custom Rule",
3325
3867
  readOnlyHint: false,
@@ -3340,10 +3882,200 @@ var updateCustomRuleTool = {
3340
3882
  return ok(result.value);
3341
3883
  }
3342
3884
  };
3885
+ var TaxonomyNodeShape = z.object({
3886
+ client_id: z.string().min(1).max(100).describe(
3887
+ "Caller-chosen string id used to wire parent/child links inside this request only. The API allocates real UUIDs on response."
3888
+ ),
3889
+ parent_client_id: z.string().min(1).max(100).nullable().optional().describe("client_id of this node's parent. null/omitted = top-level node."),
3890
+ name: z.string().min(1).max(100).describe("Display name of the node."),
3891
+ description: z.string().max(200).optional().describe(
3892
+ "Free-form description shown to the LLM during classification \u2014 keep it concrete (one sentence)."
3893
+ ),
3894
+ is_default: z.boolean().optional().describe(
3895
+ "Mark this node as the fallback bucket. Exactly ONE node per taxonomy must be is_default=true."
3896
+ )
3897
+ });
3898
+
3899
+ // src/application/tools/custom-taxonomies/create-custom-taxonomy.tool.ts
3900
+ var CreateCustomTaxonomyInputShape = {
3901
+ name: z.string().min(1).max(100).describe("Display name (1-100 chars)."),
3902
+ description: z.string().max(500).optional().describe("Free-form description (defaults to empty string)."),
3903
+ nodes: z.array(TaxonomyNodeShape).max(2e3).optional().describe(
3904
+ "Initial tree as a flat array (parents before children, linked via client_id / parent_client_id). Empty / omitted = empty taxonomy."
3905
+ )
3906
+ };
3907
+ var createCustomTaxonomyTool = {
3908
+ name: "create_custom_taxonomy",
3909
+ description: "Create a custom classification taxonomy with its initial tree. Exactly one node should have is_default=true (fallback for scans the LLM cannot classify confidently). Returns the persisted taxonomy with allocated node UUIDs and version=1.",
3910
+ annotations: {
3911
+ title: "Create Custom Taxonomy",
3912
+ readOnlyHint: false,
3913
+ destructiveHint: false,
3914
+ idempotentHint: false,
3915
+ openWorldHint: false
3916
+ },
3917
+ inputSchema: z.object(CreateCustomTaxonomyInputShape),
3918
+ handler: async (input, ctx) => {
3919
+ const result = await ctx.api.createCustomTaxonomy({
3920
+ name: input.name,
3921
+ description: input.description ?? "",
3922
+ nodes: (input.nodes ?? []).map(toRequestNode)
3923
+ });
3924
+ if (result.isErr()) return err(mapApiError(result.error));
3925
+ return ok(result.value);
3926
+ }
3927
+ };
3928
+ function toRequestNode(n) {
3929
+ return {
3930
+ client_id: n.client_id,
3931
+ parent_client_id: n.parent_client_id ?? null,
3932
+ name: n.name,
3933
+ description: n.description ?? "",
3934
+ is_default: n.is_default ?? false
3935
+ };
3936
+ }
3937
+ var DeleteCustomTaxonomyInputShape = {
3938
+ taxonomy_id: z.string().uuid().describe("Custom taxonomy UUID.")
3939
+ };
3940
+ var deleteCustomTaxonomyTool = {
3941
+ name: "delete_custom_taxonomy",
3942
+ description: "Soft-delete a custom taxonomy (sets is_active=false). Historical scan classifications keep referencing the id; new scans skip this taxonomy. Use `restore_custom_taxonomy` to undo.",
3943
+ annotations: {
3944
+ title: "Delete Custom Taxonomy",
3945
+ readOnlyHint: false,
3946
+ destructiveHint: true,
3947
+ // Soft-delete: a second call returns 404 for the same id but
3948
+ // does not change observable state — same convention used by
3949
+ // `delete_policy_set` and `delete_tag_definition`.
3950
+ idempotentHint: true,
3951
+ openWorldHint: false
3952
+ },
3953
+ inputSchema: z.object(DeleteCustomTaxonomyInputShape),
3954
+ handler: async (input, ctx) => {
3955
+ const result = await ctx.api.deleteCustomTaxonomy(input.taxonomy_id);
3956
+ if (result.isErr()) return err(mapApiError(result.error));
3957
+ return ok({ deleted: true });
3958
+ }
3959
+ };
3960
+ var GetCustomTaxonomyInputShape = {
3961
+ taxonomy_id: z.string().uuid().describe("Custom taxonomy UUID.")
3962
+ };
3963
+ var getCustomTaxonomyTool = {
3964
+ name: "get_custom_taxonomy",
3965
+ description: "Get one custom taxonomy by UUID with its full node tree (each node has id, parent_id, level, position, name, description, is_default).",
3966
+ annotations: {
3967
+ title: "Get Custom Taxonomy",
3968
+ readOnlyHint: true,
3969
+ destructiveHint: false,
3970
+ idempotentHint: true,
3971
+ openWorldHint: false
3972
+ },
3973
+ inputSchema: z.object(GetCustomTaxonomyInputShape),
3974
+ handler: async (input, ctx) => {
3975
+ const result = await ctx.api.getCustomTaxonomy(input.taxonomy_id);
3976
+ if (result.isErr()) return err(mapApiError(result.error));
3977
+ return ok(result.value);
3978
+ }
3979
+ };
3980
+ var ListCustomTaxonomiesInputShape = {};
3981
+ var listCustomTaxonomiesTool = {
3982
+ name: "list_custom_taxonomies",
3983
+ 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`.",
3984
+ annotations: {
3985
+ title: "List Custom Taxonomies",
3986
+ readOnlyHint: true,
3987
+ destructiveHint: false,
3988
+ idempotentHint: true,
3989
+ openWorldHint: false
3990
+ },
3991
+ inputSchema: z.object(ListCustomTaxonomiesInputShape),
3992
+ handler: async (_input, ctx) => {
3993
+ const result = await ctx.api.listCustomTaxonomies();
3994
+ if (result.isErr()) return err(mapApiError(result.error));
3995
+ return ok({ items: result.value });
3996
+ }
3997
+ };
3998
+ var ParseCustomTaxonomyTextInputShape = {
3999
+ text: z.string().min(1).max(5e4).describe(
4000
+ "Free-form text describing the tree (one node per line; indentation expresses depth). Up to 50 000 chars."
4001
+ )
4002
+ };
4003
+ var parseCustomTaxonomyTextTool = {
4004
+ name: "parse_custom_taxonomy_text",
4005
+ description: "Preview-parse pasted free-form text into a taxonomy tree (NOT persisted). Returns `nodes: [{level, name, description}]` + `warnings: string[]`. After review, persist via `create_custom_taxonomy` (remember to flag exactly one node as is_default).",
4006
+ annotations: {
4007
+ title: "Parse Custom Taxonomy Text",
4008
+ readOnlyHint: true,
4009
+ destructiveHint: false,
4010
+ idempotentHint: true,
4011
+ openWorldHint: false
4012
+ },
4013
+ inputSchema: z.object(ParseCustomTaxonomyTextInputShape),
4014
+ handler: async (input, ctx) => {
4015
+ const result = await ctx.api.parseCustomTaxonomyText({ text: input.text });
4016
+ if (result.isErr()) return err(mapApiError(result.error));
4017
+ return ok(result.value);
4018
+ }
4019
+ };
4020
+ var RestoreCustomTaxonomyInputShape = {
4021
+ taxonomy_id: z.string().uuid().describe("Custom taxonomy UUID (must currently be is_active=false).")
4022
+ };
4023
+ var restoreCustomTaxonomyTool = {
4024
+ name: "restore_custom_taxonomy",
4025
+ description: "Re-activate a previously soft-deleted custom taxonomy. Returns the restored taxonomy with its full tree (is_active=true).",
4026
+ annotations: {
4027
+ title: "Restore Custom Taxonomy",
4028
+ readOnlyHint: false,
4029
+ destructiveHint: false,
4030
+ idempotentHint: true,
4031
+ openWorldHint: false
4032
+ },
4033
+ inputSchema: z.object(RestoreCustomTaxonomyInputShape),
4034
+ handler: async (input, ctx) => {
4035
+ const result = await ctx.api.restoreCustomTaxonomy(input.taxonomy_id);
4036
+ if (result.isErr()) return err(mapApiError(result.error));
4037
+ return ok(result.value);
4038
+ }
4039
+ };
4040
+ var UpdateCustomTaxonomyInputShape = {
4041
+ taxonomy_id: z.string().uuid().describe("Custom taxonomy UUID."),
4042
+ name: z.string().min(1).max(100).describe("New display name."),
4043
+ description: z.string().max(500).optional().describe("New description (defaults to empty string)."),
4044
+ nodes: z.array(TaxonomyNodeShape).max(2e3).optional().describe(
4045
+ "REPLACEMENT tree as a flat array (REPLACES the existing tree, not a merge). Read the current tree via `get_custom_taxonomy` first if you only want to tweak one node."
4046
+ )
4047
+ };
4048
+ var updateCustomTaxonomyTool = {
4049
+ name: "update_custom_taxonomy",
4050
+ description: "REPLACE a custom taxonomy's name, description, and full tree atomically. The API bumps `version` on every successful update; ongoing scan classifications continue with the previous tree until they finish, so updates are safe but not retroactive.",
4051
+ annotations: {
4052
+ title: "Update Custom Taxonomy",
4053
+ readOnlyHint: false,
4054
+ destructiveHint: false,
4055
+ idempotentHint: true,
4056
+ openWorldHint: false
4057
+ },
4058
+ inputSchema: z.object(UpdateCustomTaxonomyInputShape),
4059
+ handler: async (input, ctx) => {
4060
+ const result = await ctx.api.updateCustomTaxonomy(input.taxonomy_id, {
4061
+ name: input.name,
4062
+ description: input.description ?? "",
4063
+ nodes: (input.nodes ?? []).map((n) => ({
4064
+ client_id: n.client_id,
4065
+ parent_client_id: n.parent_client_id ?? null,
4066
+ name: n.name,
4067
+ description: n.description ?? "",
4068
+ is_default: n.is_default ?? false
4069
+ }))
4070
+ });
4071
+ if (result.isErr()) return err(mapApiError(result.error));
4072
+ return ok(result.value);
4073
+ }
4074
+ };
3343
4075
  var ListEmulatorsInputShape = {};
3344
4076
  var listEmulatorsTool = {
3345
4077
  name: "list_emulators",
3346
- description: "List every device/OS emulator profile available for scans (id, display name, category, browser). Use the `id` as `emulator_id` in `create_scan` / `create_campaign`.",
4078
+ description: "List every device/OS emulator profile available for scans (id, display name, category, browser). For `create_scan`, use the `id` as `emulator_id`. For `create_campaign` / `update_campaign`, use the `category` in `emulator_categories` or the `id` in `emulator_specific_ids`.",
3347
4079
  annotations: {
3348
4080
  title: "List Emulators",
3349
4081
  readOnlyHint: true,
@@ -3378,13 +4110,72 @@ var listGeosTool = {
3378
4110
  return ok({ items: result.value, total: result.value.length });
3379
4111
  }
3380
4112
  };
4113
+
4114
+ // src/application/tools/_shared/binary-content.ts
4115
+ function imageBlock(bytes, mimeType) {
4116
+ return {
4117
+ content: [
4118
+ {
4119
+ type: "image",
4120
+ data: Buffer.from(bytes).toString("base64"),
4121
+ mimeType
4122
+ }
4123
+ ]
4124
+ };
4125
+ }
4126
+ function resourceBlock(bytes, mimeType, uri) {
4127
+ return {
4128
+ content: [
4129
+ {
4130
+ type: "resource",
4131
+ resource: {
4132
+ uri,
4133
+ mimeType,
4134
+ blob: Buffer.from(bytes).toString("base64")
4135
+ }
4136
+ }
4137
+ ]
4138
+ };
4139
+ }
4140
+
4141
+ // src/application/tools/invoicing/get-invoice-pdf.tool.ts
4142
+ var GetInvoicePdfInputShape = {
4143
+ invoice_id: z.string().uuid().describe("Invoice UUID \u2014 see `list_invoices`.")
4144
+ };
4145
+ var getInvoicePdfTool = {
4146
+ name: "get_invoice_pdf",
4147
+ description: "Download one invoice as an inline PDF (base64-encoded). Returned as a single MCP resource content block \u2014 agents can save / forward it without a second API call.",
4148
+ annotations: {
4149
+ title: "Get Invoice PDF",
4150
+ readOnlyHint: true,
4151
+ destructiveHint: false,
4152
+ idempotentHint: true,
4153
+ openWorldHint: false
4154
+ },
4155
+ inputSchema: z.object(GetInvoicePdfInputShape),
4156
+ handler: async (input, ctx) => {
4157
+ const result = await ctx.api.getInvoicePdf(input.invoice_id);
4158
+ if (result.isErr()) return err(mapApiError(result.error));
4159
+ return ok(
4160
+ resourceBlock(
4161
+ result.value.bytes,
4162
+ result.value.contentType,
4163
+ `kaminari-ad://invoices/${input.invoice_id}.pdf`
4164
+ )
4165
+ );
4166
+ }
4167
+ };
3381
4168
  var ListInvoicesInputShape = {
4169
+ type: z.enum(["proforma", "final"]).optional().describe(
4170
+ "Filter by invoice kind: proforma (advance bill) or final (issued after the period)."
4171
+ ),
4172
+ status: z.enum(["draft", "issued", "paid", "voided", "overdue"]).optional().describe("Filter by lifecycle status."),
3382
4173
  page: z.number().int().min(1).max(500).default(1).describe("1-indexed page."),
3383
4174
  limit: z.number().int().min(1).max(200).default(50).describe("Page size.")
3384
4175
  };
3385
4176
  var listInvoicesTool = {
3386
4177
  name: "list_invoices",
3387
- description: "List invoices issued to the organization with number, status, total in micros, currency, due/paid dates. Use to find an invoice id; PDFs are available at https://app.kaminari.ad/api/v1/invoices/{id}/pdf (not exposed via MCP \u2014 agents return the URL to the user).",
4178
+ description: "List invoices issued to the organization with number, type, status, total in micros, currency, due/paid dates. Filter by `type` and / or `status`. Use `get_invoice_pdf` to download the PDF for a specific invoice.",
3388
4179
  annotations: {
3389
4180
  title: "List Invoices",
3390
4181
  readOnlyHint: true,
@@ -3394,23 +4185,123 @@ var listInvoicesTool = {
3394
4185
  },
3395
4186
  inputSchema: z.object(ListInvoicesInputShape),
3396
4187
  handler: async (input, ctx) => {
3397
- const result = await ctx.api.listInvoices({ page: input.page, limit: input.limit });
4188
+ const filters = {
4189
+ page: input.page,
4190
+ limit: input.limit,
4191
+ ...input.type !== void 0 ? { type: input.type } : {},
4192
+ ...input.status !== void 0 ? { status: input.status } : {}
4193
+ };
4194
+ const result = await ctx.api.listInvoices(filters);
3398
4195
  if (result.isErr()) return err(mapApiError(result.error));
3399
4196
  return ok(result.value);
3400
4197
  }
3401
4198
  };
3402
- var PolicyEntryShape = z.object({
3403
- tag_slug: z.string().min(1).max(100).describe("Tag slug that triggers a violation."),
3404
- country_codes: z.array(z.string().length(2)).max(50).describe("Restrict the violation to these countries. Empty array = all countries.")
4199
+ var TIER1 = z.string().min(1).max(200);
4200
+ var TIER_OPT = z.string().max(200).nullable().optional();
4201
+ var IabV3Block = z.object({
4202
+ tier1: TIER1.describe("Top-level IAB Content Taxonomy V3 category (e.g. 'Sensitive Topics')."),
4203
+ tier2: TIER_OPT.describe("Sub-category (optional). Empty/null = subtree match."),
4204
+ tier3: TIER_OPT.describe("Sub-sub-category (optional)."),
4205
+ tier4: TIER_OPT.describe(
4206
+ "Leaf-level category (optional). When all 4 tiers set, leaf-only match."
4207
+ )
4208
+ });
4209
+ var AiCategoryBlock = z.object({
4210
+ tier1: TIER1.describe("Top-level freeform AI-generated category (e.g. 'Gambling')."),
4211
+ tier2: TIER_OPT.describe("Sub-category (optional)."),
4212
+ tier3: TIER_OPT.describe("Sub-sub-category (optional)."),
4213
+ tier4: TIER_OPT.describe("Leaf-level category (optional).")
3405
4214
  });
4215
+ var CustomTaxonomyBlock = z.object({
4216
+ taxonomy_id: z.string().uuid().describe("ID of the per-org custom taxonomy (see `list_custom_taxonomies`)."),
4217
+ tier1: TIER1.describe("Top-level node name in the taxonomy."),
4218
+ tier2: TIER_OPT.describe("Tier 2 node name (optional)."),
4219
+ tier3: TIER_OPT.describe("Tier 3 node name (optional)."),
4220
+ tier4: TIER_OPT.describe("Leaf node name (optional).")
4221
+ });
4222
+ var COUNTRY_CODES = z.array(z.string().min(2).max(3)).max(50).describe("Restrict the rule to these countries (ISO 3166-1 alpha-2 / alpha-3). Empty = all.");
4223
+ var PolicyEntryInput = z.discriminatedUnion("rule_type", [
4224
+ z.object({
4225
+ rule_type: z.literal("tag").describe("Match a specific tag slug emitted by the scan pipeline."),
4226
+ tag_slug: z.string().min(1).max(100).describe("Tag slug (see `list_tags` for the catalogue)."),
4227
+ country_codes: COUNTRY_CODES
4228
+ }),
4229
+ z.object({
4230
+ rule_type: z.literal("iab_v3").describe("Match the scan's canonical IAB V3 category prefix."),
4231
+ iab_v3: IabV3Block,
4232
+ country_codes: COUNTRY_CODES
4233
+ }),
4234
+ z.object({
4235
+ rule_type: z.literal("brand").describe("Match the scan's advertiser brand (case-insensitive)."),
4236
+ brand: z.string().min(1).max(200).describe("Brand string."),
4237
+ country_codes: COUNTRY_CODES
4238
+ }),
4239
+ z.object({
4240
+ rule_type: z.literal("ai_category").describe("Match the freeform LLM-generated category prefix on the scan."),
4241
+ ai_category: AiCategoryBlock,
4242
+ country_codes: COUNTRY_CODES
4243
+ }),
4244
+ z.object({
4245
+ rule_type: z.literal("custom_taxonomy").describe("Match a per-org custom taxonomy node prefix on the scan."),
4246
+ custom_taxonomy: CustomTaxonomyBlock,
4247
+ country_codes: COUNTRY_CODES
4248
+ })
4249
+ ]).describe(
4250
+ "One policy rule. Discriminated by `rule_type`; populate the matching value-block (tag_slug / iab_v3 / brand / ai_category / custom_taxonomy)."
4251
+ );
4252
+ function policyEntryToRequest(entry) {
4253
+ const base = {
4254
+ rule_type: entry.rule_type,
4255
+ tag_slug: null,
4256
+ iab_v3: null,
4257
+ brand: null,
4258
+ ai_category: null,
4259
+ custom_taxonomy: null,
4260
+ country_codes: [...entry.country_codes]
4261
+ };
4262
+ if (entry.rule_type === "tag") {
4263
+ return { ...base, tag_slug: entry.tag_slug };
4264
+ }
4265
+ if (entry.rule_type === "iab_v3") {
4266
+ return { ...base, iab_v3: normaliseTiers(entry.iab_v3) };
4267
+ }
4268
+ if (entry.rule_type === "brand") {
4269
+ return { ...base, brand: entry.brand };
4270
+ }
4271
+ if (entry.rule_type === "ai_category") {
4272
+ return { ...base, ai_category: normaliseTiers(entry.ai_category) };
4273
+ }
4274
+ return { ...base, custom_taxonomy: normaliseCustomTaxonomy(entry.custom_taxonomy) };
4275
+ }
4276
+ function normaliseTiers(t) {
4277
+ return {
4278
+ tier1: t.tier1,
4279
+ tier2: t.tier2 ?? null,
4280
+ tier3: t.tier3 ?? null,
4281
+ tier4: t.tier4 ?? null
4282
+ };
4283
+ }
4284
+ function normaliseCustomTaxonomy(t) {
4285
+ return {
4286
+ taxonomy_id: t.taxonomy_id,
4287
+ tier1: t.tier1,
4288
+ tier2: t.tier2 ?? null,
4289
+ tier3: t.tier3 ?? null,
4290
+ tier4: t.tier4 ?? null
4291
+ };
4292
+ }
4293
+
4294
+ // src/application/tools/policy-sets/create-policy-set.tool.ts
3406
4295
  var CreatePolicySetInputShape = {
3407
4296
  name: z.string().min(1).max(200).describe("Display name."),
3408
4297
  description: z.string().max(2e3).describe("Free-form description (use empty string for none)."),
3409
- entries: z.array(PolicyEntryShape).min(1).max(500).describe("At least one entry. Each entry pairs a tag-slug with country codes.")
4298
+ entries: z.array(PolicyEntryInput).min(1).max(500).describe(
4299
+ "At least one entry. Each entry is a discriminated union over five rule kinds: tag / iab_v3 / brand / ai_category / custom_taxonomy."
4300
+ )
3410
4301
  };
3411
4302
  var createPolicySetTool = {
3412
4303
  name: "create_policy_set",
3413
- description: "Create a new policy set (named collection of tag + country-list entries). Once created, you can bind campaigns to it via `update_campaign`.",
4304
+ description: "Create a new policy set (named collection of violation rules). Each rule is one of: tag slug, IAB V3 category prefix, advertiser brand, freeform AI category, or per-org custom-taxonomy node. Bind campaigns to the set via `update_campaign`.",
3414
4305
  annotations: {
3415
4306
  title: "Create Policy Set",
3416
4307
  readOnlyHint: false,
@@ -3423,7 +4314,7 @@ var createPolicySetTool = {
3423
4314
  const result = await ctx.api.createPolicySet({
3424
4315
  name: input.name,
3425
4316
  description: input.description,
3426
- entries: input.entries.map((e) => ({ tag_slug: e.tag_slug, country_codes: e.country_codes }))
4317
+ entries: input.entries.map(policyEntryToRequest)
3427
4318
  });
3428
4319
  if (result.isErr()) return err(mapApiError(result.error));
3429
4320
  return ok(result.value);
@@ -3454,7 +4345,7 @@ var GetPolicySetInputShape = {
3454
4345
  };
3455
4346
  var getPolicySetTool = {
3456
4347
  name: "get_policy_set",
3457
- description: "Get one policy set by UUID with its complete list of entries (tag-slug + applicable country codes).",
4348
+ description: "Get one policy set by UUID with its complete list of entries (each entry is one of five rule kinds \u2014 tag / iab_v3 / brand / ai_category / custom_taxonomy \u2014 plus applicable country codes).",
3458
4349
  annotations: {
3459
4350
  title: "Get Policy Set",
3460
4351
  readOnlyHint: true,
@@ -3471,11 +4362,14 @@ var getPolicySetTool = {
3471
4362
  };
3472
4363
  var ListPolicySetsInputShape = {
3473
4364
  page: z.number().int().min(1).max(500).default(1).describe("1-indexed page number."),
3474
- limit: z.number().int().min(1).max(200).default(50).describe("Page size.")
4365
+ limit: z.number().int().min(1).max(200).default(50).describe("Page size."),
4366
+ visibility: z.enum(["private", "public"]).optional().describe(
4367
+ "Narrow to one scope. `private` = org-owned only, `public` = Kaminari Ad-curated sets visible to every org. Omit the filter to see BOTH combined."
4368
+ )
3475
4369
  };
3476
4370
  var listPolicySetsTool = {
3477
4371
  name: "list_policy_sets",
3478
- description: "Paginated list of policy sets: named collections of (tag, country-list) entries that define what counts as a violation. Campaigns bind to one policy set. Returns `{items, total, page, limit}`. List items omit `entries` for payload size \u2014 fetch a single set via `get_policy_set` when you need them.",
4372
+ description: "Paginated list of policy sets: named collections of violation rules (tag / IAB V3 / brand / AI category / custom-taxonomy entries) that define what counts as a violation. Campaigns bind to one policy set. Returns `{items, total, page, limit}`. List items omit `entries` for payload size \u2014 fetch a single set via `get_policy_set` when you need them. Use `visibility=public` to discover Kaminari Ad-curated sets.",
3479
4373
  annotations: {
3480
4374
  title: "List Policy Sets",
3481
4375
  readOnlyHint: true,
@@ -3485,7 +4379,11 @@ var listPolicySetsTool = {
3485
4379
  },
3486
4380
  inputSchema: z.object(ListPolicySetsInputShape),
3487
4381
  handler: async (input, ctx) => {
3488
- const result = await ctx.api.listPolicySets({ page: input.page, limit: input.limit });
4382
+ const result = await ctx.api.listPolicySets({
4383
+ page: input.page,
4384
+ limit: input.limit,
4385
+ ...input.visibility !== void 0 ? { visibility: input.visibility } : {}
4386
+ });
3489
4387
  if (result.isErr()) return err(mapApiError(result.error));
3490
4388
  return ok(result.value);
3491
4389
  }
@@ -3510,19 +4408,17 @@ var requestPolicySetApprovalTool = {
3510
4408
  return ok({ requested: true });
3511
4409
  }
3512
4410
  };
3513
- var PolicyEntryShape2 = z.object({
3514
- tag_slug: z.string().min(1).max(100),
3515
- country_codes: z.array(z.string().length(2)).max(50)
3516
- });
3517
4411
  var UpdatePolicySetInputShape = {
3518
4412
  policy_set_id: z.string().uuid().describe("Policy set UUID."),
3519
4413
  name: z.string().min(1).max(200).describe("New name (always required by the API on update)."),
3520
4414
  description: z.string().max(2e3).describe("New description (empty string allowed)."),
3521
- entries: z.array(PolicyEntryShape2).min(1).max(500).describe("REPLACEMENT entries list \u2014 REPLACES the current list, not a merge.")
4415
+ entries: z.array(PolicyEntryInput).min(1).max(500).describe(
4416
+ "REPLACEMENT entries list \u2014 REPLACES the current list, not a merge. Each entry is a discriminated union over five rule kinds (tag / iab_v3 / brand / ai_category / custom_taxonomy)."
4417
+ )
3522
4418
  };
3523
4419
  var updatePolicySetTool = {
3524
4420
  name: "update_policy_set",
3525
- description: "REPLACE a policy set's name, description, and entry list. The API requires all three fields on every update \u2014 read the current set with `get_policy_set` first if you only want to change one thing.",
4421
+ description: "REPLACE a policy set's name, description, and entry list. The API requires all three fields on every update \u2014 read the current set with `get_policy_set` first if you only want to change one thing. Supports all five rule kinds (tag / iab_v3 / brand / ai_category / custom_taxonomy).",
3526
4422
  annotations: {
3527
4423
  title: "Update Policy Set",
3528
4424
  readOnlyHint: false,
@@ -3535,7 +4431,7 @@ var updatePolicySetTool = {
3535
4431
  const result = await ctx.api.updatePolicySet(input.policy_set_id, {
3536
4432
  name: input.name,
3537
4433
  description: input.description,
3538
- entries: input.entries.map((e) => ({ tag_slug: e.tag_slug, country_codes: e.country_codes }))
4434
+ entries: input.entries.map(policyEntryToRequest)
3539
4435
  });
3540
4436
  if (result.isErr()) return err(mapApiError(result.error));
3541
4437
  return ok(result.value);
@@ -3622,11 +4518,20 @@ var cancelScanTool = {
3622
4518
  return ok(result.value);
3623
4519
  }
3624
4520
  };
4521
+ var scanProxyField = z.object({
4522
+ proxy_type: z.enum(["residential", "mobile"]).optional().describe("Proxy network type. Default: residential."),
4523
+ region: z.string().optional().describe("Proxy region/state targeting."),
4524
+ city: z.string().optional().describe("Proxy city targeting."),
4525
+ isp: z.string().optional().describe("Proxy ISP targeting.")
4526
+ }).optional().describe("Optional proxy geo targeting for the crawl. Omit to use the org default.");
4527
+
4528
+ // src/application/tools/scans/create-bulk-scans.tool.ts
3625
4529
  var CreateBulkScansInputShape = {
3626
4530
  url: z.string().url().optional().describe("Direct URL. EITHER `url` OR `ad_tag` is required."),
3627
4531
  ad_tag: z.string().optional().describe("Raw ad-tag HTML/JS. EITHER `url` OR `ad_tag` is required."),
3628
4532
  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."),
3629
4533
  emulator_id: z.string().min(1).max(100).describe("Device/OS profile slug; same for every country in the batch."),
4534
+ proxy: scanProxyField,
3630
4535
  labels: z.record(z.string()).optional().describe("Arbitrary metadata copied onto every created scan.")
3631
4536
  };
3632
4537
  var createBulkScansTool = {
@@ -3646,6 +4551,7 @@ var createBulkScansTool = {
3646
4551
  emulator_id: input.emulator_id,
3647
4552
  ...input.url !== void 0 ? { url: input.url } : {},
3648
4553
  ...input.ad_tag !== void 0 ? { ad_tag: input.ad_tag } : {},
4554
+ ...input.proxy !== void 0 ? { proxy: input.proxy } : {},
3649
4555
  ...input.labels !== void 0 ? { labels: input.labels } : {}
3650
4556
  };
3651
4557
  const result = await ctx.api.createBulkScans(body);
@@ -3658,6 +4564,7 @@ var CreateScanInputShape = {
3658
4564
  ad_tag: z.string().optional().describe("Raw HTML/JS ad tag (script, iframe, image). EITHER `url` OR `ad_tag` is required."),
3659
4565
  country_code: z.string().length(2).describe("ISO 3166-1 alpha-2 country code, e.g. US, DE, JP. Determines proxy geo."),
3660
4566
  emulator_id: z.string().min(1).max(100).describe("Device/OS profile slug; use `list_emulators` to discover valid values."),
4567
+ proxy: scanProxyField,
3661
4568
  labels: z.record(z.string()).optional().describe("Arbitrary string -> string metadata attached to the scan."),
3662
4569
  campaign_id: z.string().uuid().optional().describe("Optional campaign UUID to attribute the scan to."),
3663
4570
  run_id: z.string().uuid().optional().describe("Optional run UUID inside the campaign.")
@@ -3679,6 +4586,7 @@ var createScanTool = {
3679
4586
  emulator_id: input.emulator_id,
3680
4587
  ...input.url !== void 0 ? { url: input.url } : {},
3681
4588
  ...input.ad_tag !== void 0 ? { ad_tag: input.ad_tag } : {},
4589
+ ...input.proxy !== void 0 ? { proxy: input.proxy } : {},
3682
4590
  ...input.labels !== void 0 ? { labels: input.labels } : {},
3683
4591
  ...input.campaign_id !== void 0 ? { campaign_id: input.campaign_id } : {},
3684
4592
  ...input.run_id !== void 0 ? { run_id: input.run_id } : {}
@@ -3708,22 +4616,110 @@ var getScanTool = {
3708
4616
  return ok(result.value);
3709
4617
  }
3710
4618
  };
4619
+ var GetScanCreativeScreenshotInputShape = {
4620
+ scan_id: z.string().uuid().describe("Scan UUID \u2014 must be an ad-tag scan (is_ad_tag=true)."),
4621
+ width: z.number().int().min(50).max(2e3).optional().describe("Optional resize width in pixels.")
4622
+ };
4623
+ var getScanCreativeScreenshotTool = {
4624
+ name: "get_scan_creative_screenshot",
4625
+ description: "Fetch the creative screenshot for an ad-tag scan as an inline image (base64-encoded PNG). Returns 404 if the scan is not an ad-tag scan or has no creative captured yet.",
4626
+ annotations: {
4627
+ title: "Get Scan Creative Screenshot",
4628
+ readOnlyHint: true,
4629
+ destructiveHint: false,
4630
+ idempotentHint: true,
4631
+ openWorldHint: false
4632
+ },
4633
+ inputSchema: z.object(GetScanCreativeScreenshotInputShape),
4634
+ handler: async (input, ctx) => {
4635
+ const result = await ctx.api.getScanCreativeScreenshot(input.scan_id, input.width);
4636
+ if (result.isErr()) return err(mapApiError(result.error));
4637
+ return ok(imageBlock(result.value.bytes, result.value.contentType));
4638
+ }
4639
+ };
4640
+ var GetScanLandingScreenshotInputShape = {
4641
+ scan_id: z.string().uuid().describe("Scan UUID (ad-tag scan)."),
4642
+ landing_ord: z.number().int().min(0).max(50).describe("0-indexed landing slot (see `get_scan(...).landings[*].ord`)."),
4643
+ width: z.number().int().min(50).max(2e3).optional().describe("Optional resize width in pixels.")
4644
+ };
4645
+ var getScanLandingScreenshotTool = {
4646
+ name: "get_scan_landing_screenshot",
4647
+ description: "Fetch the screenshot of one landing tab on an ad-tag scan as an inline image. Use `get_scan` first to discover available `landings[*].ord` values.",
4648
+ annotations: {
4649
+ title: "Get Scan Landing Screenshot",
4650
+ readOnlyHint: true,
4651
+ destructiveHint: false,
4652
+ idempotentHint: true,
4653
+ openWorldHint: false
4654
+ },
4655
+ inputSchema: z.object(GetScanLandingScreenshotInputShape),
4656
+ handler: async (input, ctx) => {
4657
+ const result = await ctx.api.getScanLandingScreenshot(
4658
+ input.scan_id,
4659
+ input.landing_ord,
4660
+ input.width
4661
+ );
4662
+ if (result.isErr()) return err(mapApiError(result.error));
4663
+ return ok(imageBlock(result.value.bytes, result.value.contentType));
4664
+ }
4665
+ };
4666
+ var GetScanScreenshotInputShape = {
4667
+ scan_id: z.string().uuid().describe("Scan UUID."),
4668
+ width: z.number().int().min(50).max(2e3).optional().describe(
4669
+ "Optional resize width in pixels. The API resizes server-side to keep the image small for the agent's context window."
4670
+ )
4671
+ };
4672
+ var getScanScreenshotTool = {
4673
+ name: "get_scan_screenshot",
4674
+ description: "Fetch the primary screenshot for a scan as an inline image (base64-encoded PNG). Pass `width` to request a resized version (50-2000 px). Returns one MCP image content block.",
4675
+ annotations: {
4676
+ title: "Get Scan Screenshot",
4677
+ readOnlyHint: true,
4678
+ destructiveHint: false,
4679
+ idempotentHint: true,
4680
+ openWorldHint: false
4681
+ },
4682
+ inputSchema: z.object(GetScanScreenshotInputShape),
4683
+ handler: async (input, ctx) => {
4684
+ const result = await ctx.api.getScanScreenshot(input.scan_id, input.width);
4685
+ if (result.isErr()) return err(mapApiError(result.error));
4686
+ return ok(imageBlock(result.value.bytes, result.value.contentType));
4687
+ }
4688
+ };
3711
4689
  var ListScansInputShape = {
3712
4690
  status: z.string().optional().describe(
3713
- "Comma-separated statuses to filter by. One of: pending, running, done, failed, cancelled."
4691
+ "Comma-separated statuses to filter by. Lifecycle values: pending, running, crawled, checking, checking_async, completed, partial, failed, cancelled."
3714
4692
  ),
3715
4693
  country_code: z.string().optional().describe("Comma-separated ISO 3166-1 alpha-2 country codes, e.g. US,DE,JP."),
3716
4694
  url: z.string().optional().describe("Substring match against the scanned URL."),
3717
4695
  scan_id: z.string().optional().describe("Comma-separated scan UUIDs to fetch a specific set."),
3718
4696
  date_from: z.string().date().optional().describe("ISO date (YYYY-MM-DD), inclusive lower bound on scan creation."),
3719
4697
  date_to: z.string().date().optional().describe("ISO date (YYYY-MM-DD), inclusive upper bound on scan creation."),
4698
+ timezone: z.string().optional().describe(
4699
+ "IANA timezone (e.g. 'Europe/Berlin') applied to date_from / date_to. Defaults to UTC."
4700
+ ),
4701
+ run_id: z.string().uuid().optional().describe("Filter to scans of one run."),
4702
+ campaign_id: z.string().uuid().optional().describe("Filter to scans of one campaign."),
4703
+ group_id: z.string().uuid().optional().describe("Filter to scans of one campaign group."),
3720
4704
  tag: z.string().optional().describe("Comma-separated tag slugs to filter by."),
4705
+ ai_category: z.string().optional().describe(
4706
+ "Filter by the freeform LLM-generated category prefix on the scan (slash-separated tier path, e.g. 'Gambling/Online Casinos')."
4707
+ ),
4708
+ iab_v3_category: z.string().optional().describe("Filter by canonical IAB Content Taxonomy V3 prefix (slash-separated tier path)."),
4709
+ iab_category: z.string().optional().describe("Filter by legacy IAB V2.2 category (only present on pre-P3 scans)."),
4710
+ brand: z.string().optional().describe("Filter by detected advertiser brand (case-insensitive)."),
4711
+ labels: z.record(
4712
+ z.string().min(1).max(100).regex(/^[a-z0-9_]+$/, "label keys must be snake_case ([a-z0-9_]+)"),
4713
+ z.string().min(1).max(200)
4714
+ ).optional().describe(
4715
+ "Dynamic label filters as a flat object: { brand_safety: 'high', vertical: 'gambling' }. Keys are snake_case and must exist in the org's label catalogue. Each key becomes a `label_<key>=<value>` query param. See `list_account_labels`."
4716
+ ),
3721
4717
  page: z.number().int().min(1).max(500).default(1).describe("1-indexed page number."),
3722
4718
  limit: z.number().int().min(1).max(200).default(50).describe("Page size (1-200). Default 50.")
3723
4719
  };
3724
4720
  var listScansTool = {
3725
4721
  name: "list_scans",
3726
- description: "List scans for the caller's organization with optional filters (status, country, URL substring, date range, tags). Returns a paginated envelope.",
4722
+ description: "List scans for the caller's organization with optional filters (status, country, URL substring, date range, run/campaign/group, tag, AI/IAB/brand category, dynamic labels). Returns a paginated envelope.",
3727
4723
  annotations: {
3728
4724
  title: "List Scans",
3729
4725
  readOnlyHint: true,
@@ -3733,6 +4729,12 @@ var listScansTool = {
3733
4729
  },
3734
4730
  inputSchema: z.object(ListScansInputShape),
3735
4731
  handler: async (input, ctx) => {
4732
+ const labelFilters = {};
4733
+ if (input.labels !== void 0) {
4734
+ for (const [k, v] of Object.entries(input.labels)) {
4735
+ labelFilters[`label_${k}`] = v;
4736
+ }
4737
+ }
3736
4738
  const filters = {
3737
4739
  page: input.page,
3738
4740
  limit: input.limit,
@@ -3742,7 +4744,16 @@ var listScansTool = {
3742
4744
  ...input.scan_id !== void 0 ? { scan_id: input.scan_id } : {},
3743
4745
  ...input.date_from !== void 0 ? { date_from: input.date_from } : {},
3744
4746
  ...input.date_to !== void 0 ? { date_to: input.date_to } : {},
3745
- ...input.tag !== void 0 ? { tag: input.tag } : {}
4747
+ ...input.timezone !== void 0 ? { timezone: input.timezone } : {},
4748
+ ...input.run_id !== void 0 ? { run_id: input.run_id } : {},
4749
+ ...input.campaign_id !== void 0 ? { campaign_id: input.campaign_id } : {},
4750
+ ...input.group_id !== void 0 ? { group_id: input.group_id } : {},
4751
+ ...input.tag !== void 0 ? { tag: input.tag } : {},
4752
+ ...input.ai_category !== void 0 ? { ai_category: input.ai_category } : {},
4753
+ ...input.iab_v3_category !== void 0 ? { iab_v3_category: input.iab_v3_category } : {},
4754
+ ...input.iab_category !== void 0 ? { iab_category: input.iab_category } : {},
4755
+ ...input.brand !== void 0 ? { brand: input.brand } : {},
4756
+ ...labelFilters
3746
4757
  };
3747
4758
  const result = await ctx.api.listScans(filters);
3748
4759
  if (result.isErr()) {
@@ -3839,10 +4850,14 @@ var listScanTagsTool = {
3839
4850
  return ok({ items: result.value, total: result.value.length });
3840
4851
  }
3841
4852
  };
3842
- var ListTagsInputShape = {};
4853
+ var ListTagsInputShape = {
4854
+ category: z.string().min(1).max(100).optional().describe(
4855
+ "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."
4856
+ )
4857
+ };
3843
4858
  var listTagsTool = {
3844
4859
  name: "list_tags",
3845
- description: "List every tag definition the platform knows (system tags + organization custom tags) with category, severity, and usage counters (scans + rules per tag).",
4860
+ 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.",
3846
4861
  annotations: {
3847
4862
  title: "List Tags",
3848
4863
  readOnlyHint: true,
@@ -3851,8 +4866,9 @@ var listTagsTool = {
3851
4866
  openWorldHint: false
3852
4867
  },
3853
4868
  inputSchema: z.object(ListTagsInputShape),
3854
- handler: async (_input, ctx) => {
3855
- const result = await ctx.api.listTags();
4869
+ handler: async (input, ctx) => {
4870
+ const filters = input.category !== void 0 ? { category: input.category } : void 0;
4871
+ const result = await ctx.api.listTags(filters);
3856
4872
  if (result.isErr()) return err(mapApiError(result.error));
3857
4873
  return ok({ items: result.value, total: result.value.length });
3858
4874
  }
@@ -3862,7 +4878,9 @@ var UpdateTagDefinitionInputShape = {
3862
4878
  display_name: z.string().min(1).max(200).optional().describe("New human-readable name."),
3863
4879
  description: z.string().max(2e3).optional().describe("New description."),
3864
4880
  severity: z.enum(["high", "medium", "low"]).optional().describe("New severity level."),
3865
- show_in_public_report: z.boolean().optional().describe("Whether the tag appears in the public scan-report view.")
4881
+ visibility: z.enum(["hidden", "internal", "public"]).optional().describe(
4882
+ "Tag visibility on the scan report: hidden = not surfaced; internal = visible to org members; public = appears in the public scan-share view."
4883
+ )
3866
4884
  };
3867
4885
  var updateTagDefinitionTool = {
3868
4886
  name: "update_tag_definition",
@@ -3880,8 +4898,12 @@ var updateTagDefinitionTool = {
3880
4898
  if (input.display_name !== void 0) body.display_name = input.display_name;
3881
4899
  if (input.description !== void 0) body.description = input.description;
3882
4900
  if (input.severity !== void 0) body.severity = input.severity;
3883
- if (input.show_in_public_report !== void 0) {
3884
- body.show_in_public_report = input.show_in_public_report;
4901
+ if (input.visibility !== void 0) body.visibility = input.visibility;
4902
+ if (Object.keys(body).length === 0) {
4903
+ return err({
4904
+ kind: "invalid-input",
4905
+ message: "update_tag_definition requires at least one field to change (display_name, description, severity, or visibility)."
4906
+ });
3885
4907
  }
3886
4908
  const result = await ctx.api.updateTagDefinition(input.slug, body);
3887
4909
  if (result.isErr()) return err(mapApiError(result.error));
@@ -3985,12 +5007,15 @@ var getWebhookTool = {
3985
5007
  };
3986
5008
  var ListWebhookDeliveriesInputShape = {
3987
5009
  webhook_id: z.string().uuid().describe("Webhook endpoint UUID."),
5010
+ success: z.boolean().optional().describe("Filter to delivered (true) or failed (false) attempts only."),
5011
+ from_ts: z.string().datetime({ offset: true }).optional().describe("ISO 8601 datetime (with offset), inclusive lower bound on attempt time."),
5012
+ to_ts: z.string().datetime({ offset: true }).optional().describe("ISO 8601 datetime (with offset), inclusive upper bound."),
3988
5013
  page: z.number().int().min(1).max(500).default(1).describe("1-indexed page."),
3989
5014
  limit: z.number().int().min(1).max(200).default(50).describe("Page size.")
3990
5015
  };
3991
5016
  var listWebhookDeliveriesTool = {
3992
5017
  name: "list_webhook_deliveries",
3993
- description: "List delivery attempts for one webhook endpoint with event type, status (pending / delivered / failed), HTTP response status if any, and attempt timestamp. Paginated.",
5018
+ description: "List delivery attempts for one webhook endpoint with event type, status (pending / delivered / failed), HTTP response status if any, and attempt timestamp. Filter by `success` and / or a `from_ts` / `to_ts` range. Paginated.",
3994
5019
  annotations: {
3995
5020
  title: "List Webhook Deliveries",
3996
5021
  readOnlyHint: true,
@@ -4000,10 +5025,14 @@ var listWebhookDeliveriesTool = {
4000
5025
  },
4001
5026
  inputSchema: z.object(ListWebhookDeliveriesInputShape),
4002
5027
  handler: async (input, ctx) => {
4003
- const result = await ctx.api.listWebhookDeliveries(input.webhook_id, {
5028
+ const filters = {
4004
5029
  page: input.page,
4005
- limit: input.limit
4006
- });
5030
+ limit: input.limit,
5031
+ ...input.success !== void 0 ? { success: input.success } : {},
5032
+ ...input.from_ts !== void 0 ? { from_ts: input.from_ts } : {},
5033
+ ...input.to_ts !== void 0 ? { to_ts: input.to_ts } : {}
5034
+ };
5035
+ const result = await ctx.api.listWebhookDeliveries(input.webhook_id, filters);
4007
5036
  if (result.isErr()) return err(mapApiError(result.error));
4008
5037
  return ok(result.value);
4009
5038
  }
@@ -4110,7 +5139,12 @@ var testWebhookTool = {
4110
5139
  var UpdateWebhookInputShape = {
4111
5140
  webhook_id: z.string().uuid().describe("Webhook UUID."),
4112
5141
  url: z.string().url().optional().describe("New endpoint URL."),
5142
+ description: z.string().max(256).optional().describe("New human-readable label."),
4113
5143
  event_types: z.array(z.string()).max(50).optional().describe("Replace the subscribed-event-types list."),
5144
+ campaign_ids: z.array(z.string().uuid()).optional().describe("Restrict deliveries to these campaign UUIDs (replaces the current set)."),
5145
+ clear_campaign_ids: z.boolean().optional().describe(
5146
+ "Set true to remove the campaign restriction so the webhook fires for all campaigns."
5147
+ ),
4114
5148
  is_active: z.boolean().optional().describe("Enable / disable delivery.")
4115
5149
  };
4116
5150
  var updateWebhookTool = {
@@ -4127,7 +5161,10 @@ var updateWebhookTool = {
4127
5161
  handler: async (input, ctx) => {
4128
5162
  const body = {
4129
5163
  ...input.url !== void 0 ? { url: input.url } : {},
5164
+ ...input.description !== void 0 ? { description: input.description } : {},
4130
5165
  ...input.event_types !== void 0 ? { event_types: input.event_types } : {},
5166
+ ...input.campaign_ids !== void 0 ? { campaign_ids: input.campaign_ids } : {},
5167
+ ...input.clear_campaign_ids !== void 0 ? { clear_campaign_ids: input.clear_campaign_ids } : {},
4131
5168
  ...input.is_active !== void 0 ? { is_active: input.is_active } : {}
4132
5169
  };
4133
5170
  const result = await ctx.api.updateWebhook(input.webhook_id, body);
@@ -4146,6 +5183,9 @@ function registerAllTools(register) {
4146
5183
  register(removeUserTool);
4147
5184
  register(transferOwnershipTool);
4148
5185
  register(listOrgRolesTool);
5186
+ register(createCustomRoleTool);
5187
+ register(listAccountLabelsTool);
5188
+ register(updateAccountLabelsTool);
4149
5189
  register(listApiKeysTool);
4150
5190
  register(createApiKeyTool);
4151
5191
  register(revokeApiKeyTool);
@@ -4157,6 +5197,9 @@ function registerAllTools(register) {
4157
5197
  register(createBulkScansTool);
4158
5198
  register(createScanTool);
4159
5199
  register(recheckScansTool);
5200
+ register(getScanScreenshotTool);
5201
+ register(getScanCreativeScreenshotTool);
5202
+ register(getScanLandingScreenshotTool);
4160
5203
  register(getCampaignTool);
4161
5204
  register(listCampaignsTool);
4162
5205
  register(archiveCampaignTool);
@@ -4191,6 +5234,13 @@ function registerAllTools(register) {
4191
5234
  register(updateCustomRuleTool);
4192
5235
  register(deleteCustomRuleTool);
4193
5236
  register(testCustomRuleTool);
5237
+ register(listCustomTaxonomiesTool);
5238
+ register(getCustomTaxonomyTool);
5239
+ register(createCustomTaxonomyTool);
5240
+ register(updateCustomTaxonomyTool);
5241
+ register(deleteCustomTaxonomyTool);
5242
+ register(restoreCustomTaxonomyTool);
5243
+ register(parseCustomTaxonomyTextTool);
4194
5244
  register(listPolicySetsTool);
4195
5245
  register(getPolicySetTool);
4196
5246
  register(createPolicySetTool);
@@ -4216,6 +5266,7 @@ function registerAllTools(register) {
4216
5266
  register(getUsageSummaryTool);
4217
5267
  register(listBalanceHistoryTool);
4218
5268
  register(listInvoicesTool);
5269
+ register(getInvoicePdfTool);
4219
5270
  register(listAlertDestinationsTool);
4220
5271
  register(deleteAlertDestinationTool);
4221
5272
  register(setAlertDestinationVersionTool);
@@ -4253,12 +5304,28 @@ function wireToolsIntoMcpServer(server, ctxProvider) {
4253
5304
  });
4254
5305
  }
4255
5306
  function toolOkToMcpResult(value) {
5307
+ if (isMcpContentEnvelope(value)) {
5308
+ return value;
5309
+ }
4256
5310
  const isPlainObject = value !== null && typeof value === "object" && !Array.isArray(value);
4257
5311
  return {
4258
5312
  content: [{ type: "text", text: JSON.stringify(value, null, 2) }],
4259
5313
  ...isPlainObject ? { structuredContent: value } : {}
4260
5314
  };
4261
5315
  }
5316
+ function isBinaryBlockType(type2) {
5317
+ return type2 === "image" || type2 === "audio" || type2 === "resource";
5318
+ }
5319
+ function isMcpContentEnvelope(value) {
5320
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
5321
+ const v = value;
5322
+ if (!Array.isArray(v.content) || v.content.length === 0) return false;
5323
+ return v.content.every((block) => {
5324
+ if (block === null || typeof block !== "object") return false;
5325
+ const type2 = block.type;
5326
+ return typeof type2 === "string" && isBinaryBlockType(type2);
5327
+ });
5328
+ }
4262
5329
  function toolErrorToMcpResult(error) {
4263
5330
  return {
4264
5331
  isError: true,
@@ -4285,5 +5352,5 @@ function formatToolError(error) {
4285
5352
  }
4286
5353
 
4287
5354
  export { BearerToken, createHttpApiGateway, createPinoLogger, declareEmptyResourcesAndPrompts, newRequestId, wireToolsIntoMcpServer };
4288
- //# sourceMappingURL=chunk-4MNLSWSZ.js.map
4289
- //# sourceMappingURL=chunk-4MNLSWSZ.js.map
5355
+ //# sourceMappingURL=chunk-7DSLLKEK.js.map
5356
+ //# sourceMappingURL=chunk-7DSLLKEK.js.map