@kaminari-ad/mcp 0.2.2 → 0.3.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-KTQBHJUB.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,
@@ -1143,6 +1290,49 @@ var CustomRuleSchema2 = schemas.CustomRuleResponse.pick({
1143
1290
  created_at: true
1144
1291
  }).strip();
1145
1292
  var parseCustomRulePage = (raw) => parsePagedWithItemSchema(CustomRuleSchema2, raw, "custom-rules");
1293
+ var CustomTaxonomyListItemSchema = schemas.CustomTaxonomyListItem.pick({
1294
+ id: true,
1295
+ name: true,
1296
+ slug: true,
1297
+ description: true,
1298
+ is_active: true,
1299
+ version: true,
1300
+ node_count: true,
1301
+ created_at: true,
1302
+ updated_at: true
1303
+ }).strip();
1304
+ var CustomTaxonomyListSchema = z.array(CustomTaxonomyListItemSchema);
1305
+ var TaxonomyNodeSchema = schemas.TaxonomyNodeResponse.pick({
1306
+ id: true,
1307
+ parent_id: true,
1308
+ level: true,
1309
+ position: true,
1310
+ name: true,
1311
+ description: true,
1312
+ is_default: true
1313
+ }).strip();
1314
+ var CustomTaxonomySchema = schemas.CustomTaxonomyResponse.pick({
1315
+ id: true,
1316
+ organization_id: true,
1317
+ name: true,
1318
+ slug: true,
1319
+ description: true,
1320
+ is_active: true,
1321
+ version: true,
1322
+ created_at: true,
1323
+ updated_at: true
1324
+ }).extend({ nodes: z.array(TaxonomyNodeSchema) }).strip();
1325
+ var ParsedTaxonomyNodeSchema = schemas.ParsedTaxonomyNode.pick({
1326
+ level: true,
1327
+ name: true,
1328
+ description: true
1329
+ }).strip();
1330
+ var ParseTaxonomyTextResponseSchema = schemas.ParseTaxonomyTextResponse.pick({
1331
+ warnings: true
1332
+ }).extend({ nodes: z.array(ParsedTaxonomyNodeSchema) }).strip();
1333
+ var parseCustomTaxonomyList = (raw) => parseWithSchema(CustomTaxonomyListSchema, raw, "custom-taxonomies");
1334
+ var parseCustomTaxonomy = (raw) => parseWithSchema(CustomTaxonomySchema, raw, "custom-taxonomy");
1335
+ var parseTaxonomyTextPreview = (raw) => parseWithSchema(ParseTaxonomyTextResponseSchema, raw, "custom-taxonomy-parse-text");
1146
1336
 
1147
1337
  // src/infrastructure/api/parsers/parse-empty.ts
1148
1338
  function parseEmpty(_raw) {
@@ -1213,6 +1403,13 @@ var RoleSchema = schemas.RoleResponse.pick({
1213
1403
  permissions: true
1214
1404
  }).strip();
1215
1405
  var parseRole = (raw) => parseWithSchema(RoleSchema, raw, "role");
1406
+ var LabelDefinitionSchema = schemas.LabelDefinitionResponse.pick({
1407
+ key: true,
1408
+ display_name: true,
1409
+ position: true,
1410
+ auto_extract: true
1411
+ }).strip();
1412
+ var parseLabelDefinition = (raw) => parseWithSchema(LabelDefinitionSchema, raw, "label-definition");
1216
1413
  var ApiKeyCreatedSchema = schemas.ApiKeyCreatedResponse.pick({
1217
1414
  id: true,
1218
1415
  key_prefix: true,
@@ -1354,7 +1551,12 @@ var GroupActionSchema = schemas.GroupActionResponse.pick({
1354
1551
  var parseGroupAction = (raw) => parseWithSchema(GroupActionSchema, raw, "group-action");
1355
1552
  schemas.PolicyEntryResponse.pick({
1356
1553
  id: true,
1554
+ rule_type: true,
1357
1555
  tag_slug: true,
1556
+ iab_v3: true,
1557
+ brand: true,
1558
+ ai_category: true,
1559
+ custom_taxonomy: true,
1358
1560
  country_codes: true
1359
1561
  }).strip();
1360
1562
  var GeoSchema = schemas.GeoResponse.pick({
@@ -1465,7 +1667,7 @@ var TagDefinitionSchema = schemas.TagDefinitionWithStatsResponse.pick({
1465
1667
  severity: true,
1466
1668
  is_system: true,
1467
1669
  organization_id: true,
1468
- show_in_public_report: true,
1670
+ visibility: true,
1469
1671
  scans_count: true,
1470
1672
  rules_count: true
1471
1673
  }).strip();
@@ -1484,7 +1686,7 @@ var TagDetailSchema = schemas.TagDefinitionDetailResponse.pick({
1484
1686
  severity: true,
1485
1687
  is_system: true,
1486
1688
  organization_id: true,
1487
- show_in_public_report: true,
1689
+ visibility: true,
1488
1690
  scans_count: true,
1489
1691
  rules_count: true
1490
1692
  }).extend({ linked_rules: z.array(LinkedRuleSchema).optional() }).strip();
@@ -1592,6 +1794,56 @@ function createHttpApiGateway(config) {
1592
1794
  }
1593
1795
  return err(toApiError(status2, error ?? data, response.headers.get("retry-after") ?? void 0));
1594
1796
  }
1797
+ async function binaryGet(path, query) {
1798
+ const startedAtMs = Date.now();
1799
+ const qs = query && Object.keys(query).length > 0 ? `?${new URLSearchParams(
1800
+ Object.fromEntries(Object.entries(query).map(([k, v]) => [k, String(v)]))
1801
+ ).toString()}` : "";
1802
+ const url = `${baseUrl}${path}${qs}`;
1803
+ let response;
1804
+ try {
1805
+ const init = {
1806
+ method: "GET",
1807
+ // Header allowlist: see binaryGet docstring above. Keep this
1808
+ // map minimal; any future addition needs a CONTRIBUTING.md
1809
+ // update + a tenant-isolation review.
1810
+ headers: {
1811
+ authorization: bearer.toAuthorizationHeader(),
1812
+ accept: "image/*, application/pdf, application/octet-stream",
1813
+ "user-agent": "kaminari-ad-mcp",
1814
+ "x-request-id": requestId
1815
+ }
1816
+ };
1817
+ if (dispatcher !== void 0) init["dispatcher"] = dispatcher;
1818
+ response = await fetch(
1819
+ url,
1820
+ init
1821
+ );
1822
+ } catch (cause) {
1823
+ logger.warn({ api_path: path, elapsed_ms: Date.now() - startedAtMs }, "api.network_error");
1824
+ const inner = cause instanceof Error && cause.cause instanceof Error ? cause.cause : cause instanceof Error ? cause : null;
1825
+ return err({ kind: "upstream", detail: inner !== null ? inner.message : "network error" });
1826
+ }
1827
+ const status2 = response.status;
1828
+ logger.info(
1829
+ { api_path: path, api_status: status2, elapsed_ms: Date.now() - startedAtMs },
1830
+ "api.done"
1831
+ );
1832
+ if (status2 >= 200 && status2 < 300) {
1833
+ const buf = await response.arrayBuffer();
1834
+ return ok({
1835
+ bytes: new Uint8Array(buf),
1836
+ contentType: response.headers.get("content-type") ?? "application/octet-stream"
1837
+ });
1838
+ }
1839
+ let errBody;
1840
+ try {
1841
+ errBody = await response.json();
1842
+ } catch {
1843
+ errBody = void 0;
1844
+ }
1845
+ return err(toApiError(status2, errBody, response.headers.get("retry-after") ?? void 0));
1846
+ }
1595
1847
  return {
1596
1848
  // ── Account ───────────────────────────────────────────────────
1597
1849
  async getAccount() {
@@ -1633,6 +1885,33 @@ function createHttpApiGateway(config) {
1633
1885
  async listOrgRoles() {
1634
1886
  return call("GET", "/api/v1/account/roles", {}, parseArrayOf(parseRole));
1635
1887
  },
1888
+ async createCustomRole(body) {
1889
+ return call("POST", "/api/v1/account/roles", { body }, parseRole);
1890
+ },
1891
+ async listAccountLabels() {
1892
+ return call("GET", "/api/v1/account/labels", {}, parseArrayOf(parseLabelDefinition));
1893
+ },
1894
+ async updateAccountLabels(body) {
1895
+ return call("PUT", "/api/v1/account/labels", { body }, parseArrayOf(parseLabelDefinition));
1896
+ },
1897
+ async getScanScreenshot(scanId, w2) {
1898
+ return binaryGet(`/api/v1/scans/${scanId}/screenshot`, w2 !== void 0 ? { w: w2 } : void 0);
1899
+ },
1900
+ async getScanCreativeScreenshot(scanId, w2) {
1901
+ return binaryGet(
1902
+ `/api/v1/scans/${scanId}/creative-screenshot`,
1903
+ w2 !== void 0 ? { w: w2 } : void 0
1904
+ );
1905
+ },
1906
+ async getScanLandingScreenshot(scanId, landingOrd, w2) {
1907
+ return binaryGet(
1908
+ `/api/v1/scans/${scanId}/landings/${String(landingOrd)}/screenshot`,
1909
+ w2 !== void 0 ? { w: w2 } : void 0
1910
+ );
1911
+ },
1912
+ async getInvoicePdf(invoiceId) {
1913
+ return binaryGet(`/api/v1/invoices/${invoiceId}/pdf`);
1914
+ },
1636
1915
  async listApiKeys() {
1637
1916
  return call("GET", "/api/v1/account/api-keys", {}, parseApiKeyList);
1638
1917
  },
@@ -1759,8 +2038,13 @@ function createHttpApiGateway(config) {
1759
2038
  parsePageOf(parseRun)
1760
2039
  );
1761
2040
  },
1762
- async listCampaignsPicker() {
1763
- return call("GET", "/api/v1/campaigns/picker", {}, parseCampaignPickerArray);
2041
+ async listCampaignsPicker(filters) {
2042
+ return call(
2043
+ "GET",
2044
+ "/api/v1/campaigns/picker",
2045
+ filters ? { params: { query: filters } } : {},
2046
+ parseCampaignPickerArray
2047
+ );
1764
2048
  },
1765
2049
  // ── Runs ──────────────────────────────────────────────────────
1766
2050
  async getRun(id) {
@@ -1859,8 +2143,13 @@ function createHttpApiGateway(config) {
1859
2143
  );
1860
2144
  },
1861
2145
  // ── Tag definitions ───────────────────────────────────────────
1862
- async listTags() {
1863
- return call("GET", "/api/v1/tag-definitions", {}, parseTagDefinitionArray);
2146
+ async listTags(filters) {
2147
+ return call(
2148
+ "GET",
2149
+ "/api/v1/tag-definitions",
2150
+ filters ? { params: { query: filters } } : {},
2151
+ parseTagDefinitionArray
2152
+ );
1864
2153
  },
1865
2154
  async getTagDefinition(slug) {
1866
2155
  return call(
@@ -1964,6 +2253,53 @@ function createHttpApiGateway(config) {
1964
2253
  parseEmpty
1965
2254
  );
1966
2255
  },
2256
+ // ── Custom taxonomies ─────────────────────────────────────────
2257
+ async listCustomTaxonomies() {
2258
+ return call("GET", "/api/v1/custom-taxonomies", {}, parseCustomTaxonomyList);
2259
+ },
2260
+ async getCustomTaxonomy(id) {
2261
+ return call(
2262
+ "GET",
2263
+ "/api/v1/custom-taxonomies/{taxonomy_id}",
2264
+ { params: { path: { taxonomy_id: id } } },
2265
+ parseCustomTaxonomy
2266
+ );
2267
+ },
2268
+ async createCustomTaxonomy(body) {
2269
+ return call("POST", "/api/v1/custom-taxonomies", { body }, parseCustomTaxonomy);
2270
+ },
2271
+ async updateCustomTaxonomy(id, body) {
2272
+ return call(
2273
+ "PUT",
2274
+ "/api/v1/custom-taxonomies/{taxonomy_id}",
2275
+ { params: { path: { taxonomy_id: id } }, body },
2276
+ parseCustomTaxonomy
2277
+ );
2278
+ },
2279
+ async deleteCustomTaxonomy(id) {
2280
+ return call(
2281
+ "DELETE",
2282
+ "/api/v1/custom-taxonomies/{taxonomy_id}",
2283
+ { params: { path: { taxonomy_id: id } } },
2284
+ parseEmpty
2285
+ );
2286
+ },
2287
+ async restoreCustomTaxonomy(id) {
2288
+ return call(
2289
+ "POST",
2290
+ "/api/v1/custom-taxonomies/{taxonomy_id}/restore",
2291
+ { params: { path: { taxonomy_id: id } } },
2292
+ parseCustomTaxonomy
2293
+ );
2294
+ },
2295
+ async parseCustomTaxonomyText(body) {
2296
+ return call(
2297
+ "POST",
2298
+ "/api/v1/custom-taxonomies/parse-text",
2299
+ { body },
2300
+ parseTaxonomyTextPreview
2301
+ );
2302
+ },
1967
2303
  // ── Alerts ────────────────────────────────────────────────────
1968
2304
  async listAlerts(filters) {
1969
2305
  return call("GET", "/api/v1/alerts", { params: { query: filters } }, parseAlertPage);
@@ -2254,6 +2590,32 @@ var createApiKeyTool = {
2254
2590
  return ok(result.value);
2255
2591
  }
2256
2592
  };
2593
+ var CreateCustomRoleInputShape = {
2594
+ name: z.string().min(1).max(100).describe("Display name for the role."),
2595
+ permissions: z.array(z.string().min(1).max(100)).min(1).max(200).describe(
2596
+ "Permission slugs the role grants (e.g. 'scans.read', 'campaigns.write'). Use `list_org_roles` to see permissions on existing system roles."
2597
+ )
2598
+ };
2599
+ var createCustomRoleTool = {
2600
+ name: "create_custom_role",
2601
+ description: "Create a custom organization role with the supplied permission set. Returns the persisted role; assign it to users via `update_user_role`.",
2602
+ annotations: {
2603
+ title: "Create Custom Role",
2604
+ readOnlyHint: false,
2605
+ destructiveHint: false,
2606
+ idempotentHint: false,
2607
+ openWorldHint: false
2608
+ },
2609
+ inputSchema: z.object(CreateCustomRoleInputShape),
2610
+ handler: async (input, ctx) => {
2611
+ const result = await ctx.api.createCustomRole({
2612
+ name: input.name,
2613
+ permissions: [...input.permissions]
2614
+ });
2615
+ if (result.isErr()) return err(mapApiError(result.error));
2616
+ return ok(result.value);
2617
+ }
2618
+ };
2257
2619
  var GetAccountInputShape = {};
2258
2620
  var getAccountTool = {
2259
2621
  name: "get_account",
@@ -2299,6 +2661,24 @@ var inviteUserTool = {
2299
2661
  return ok(result.value);
2300
2662
  }
2301
2663
  };
2664
+ var ListAccountLabelsInputShape = {};
2665
+ var listAccountLabelsTool = {
2666
+ name: "list_account_labels",
2667
+ 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`.",
2668
+ annotations: {
2669
+ title: "List Account Labels",
2670
+ readOnlyHint: true,
2671
+ destructiveHint: false,
2672
+ idempotentHint: true,
2673
+ openWorldHint: false
2674
+ },
2675
+ inputSchema: z.object(ListAccountLabelsInputShape),
2676
+ handler: async (_input, ctx) => {
2677
+ const result = await ctx.api.listAccountLabels();
2678
+ if (result.isErr()) return err(mapApiError(result.error));
2679
+ return ok({ items: result.value });
2680
+ }
2681
+ };
2302
2682
  var ListApiKeysInputShape = {};
2303
2683
  var listApiKeysTool = {
2304
2684
  name: "list_api_keys",
@@ -2413,6 +2793,45 @@ var transferOwnershipTool = {
2413
2793
  return ok({ transferred: true });
2414
2794
  }
2415
2795
  };
2796
+ var LabelItem = z.object({
2797
+ 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."),
2798
+ display_name: z.string().min(1).max(200).describe("Human-readable name shown in the UI."),
2799
+ auto_extract: z.boolean().optional().describe(
2800
+ "When true, the LLM auto-populates this label's value during scan classification. Defaults to false."
2801
+ )
2802
+ });
2803
+ var UpdateAccountLabelsInputShape = {
2804
+ labels: z.array(LabelItem).max(20).describe(
2805
+ "REPLACEMENT list of all labels \u2014 pass every label you want to keep. Order in the array becomes `position`. Empty array removes all labels."
2806
+ )
2807
+ };
2808
+ var updateAccountLabelsTool = {
2809
+ name: "update_account_labels",
2810
+ 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.",
2811
+ annotations: {
2812
+ title: "Update Account Labels",
2813
+ readOnlyHint: false,
2814
+ // REPLACE semantics: passing `labels: []` wipes every label and
2815
+ // detaches the value on every scan that referenced one. Hint as
2816
+ // destructive so MCP clients that gate dangerous tools warn the
2817
+ // user before this call runs.
2818
+ destructiveHint: true,
2819
+ idempotentHint: true,
2820
+ openWorldHint: false
2821
+ },
2822
+ inputSchema: z.object(UpdateAccountLabelsInputShape),
2823
+ handler: async (input, ctx) => {
2824
+ const result = await ctx.api.updateAccountLabels({
2825
+ labels: input.labels.map((l) => ({
2826
+ key: l.key,
2827
+ display_name: l.display_name,
2828
+ auto_extract: l.auto_extract ?? false
2829
+ }))
2830
+ });
2831
+ if (result.isErr()) return err(mapApiError(result.error));
2832
+ return ok({ items: result.value });
2833
+ }
2834
+ };
2416
2835
  var UpdateOrgInputShape = {
2417
2836
  name: z.string().min(1).max(200).optional().describe("New organization display name."),
2418
2837
  settings: z.record(z.unknown()).optional().describe("Replacement settings object. Fields not supplied are left unchanged.")
@@ -2574,7 +2993,7 @@ var setCampaignAlertOverridesTool = {
2574
2993
  var GetAlertStatsInputShape = {};
2575
2994
  var getAlertStatsTool = {
2576
2995
  name: "get_alert_stats",
2577
- description: "Get alert counts grouped by status: open, ack, resolved, ignored, total.",
2996
+ 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
2997
  annotations: {
2579
2998
  title: "Get Alert Stats",
2580
2999
  readOnlyHint: true,
@@ -2591,13 +3010,15 @@ var getAlertStatsTool = {
2591
3010
  };
2592
3011
  var ListAlertsInputShape = {
2593
3012
  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."),
3013
+ status: z.enum(["open", "acknowledged", "resolved", "dismissed"]).optional().describe(
3014
+ "Filter by alert status. Open = unhandled; acknowledged = seen by an operator; resolved = closed; dismissed = closed without action."
3015
+ ),
2595
3016
  page: z.number().int().min(1).max(500).default(1).describe("1-indexed page number."),
2596
3017
  limit: z.number().int().min(1).max(200).default(50).describe("Page size.")
2597
3018
  };
2598
3019
  var listAlertsTool = {
2599
3020
  name: "list_alerts",
2600
- description: "List violation alerts (one per scan + violating-tag combo) with offer URL, tag, country, status, scan back-reference.",
3021
+ 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
3022
  annotations: {
2602
3023
  title: "List Alerts",
2603
3024
  readOnlyHint: true,
@@ -2675,15 +3096,29 @@ var getUsageSummaryTool = {
2675
3096
  return ok(result.value);
2676
3097
  }
2677
3098
  };
3099
+ var TransactionTypeEnum = z.enum([
3100
+ "initial_balance",
3101
+ "top_up_manual",
3102
+ "usage_charge",
3103
+ "subscription_renewal",
3104
+ "subscription_upgrade",
3105
+ "admin_adjustment",
3106
+ "refund",
3107
+ "invoice_settlement",
3108
+ "crypto_top_up"
3109
+ ]);
2678
3110
  var ListBalanceHistoryInputShape = {
2679
3111
  date_from: z.string().date().optional().describe("ISO date, inclusive."),
2680
3112
  date_to: z.string().date().optional().describe("ISO date, inclusive."),
3113
+ type: z.array(TransactionTypeEnum).max(9).optional().describe(
3114
+ "Filter by transaction kind. Pass several values to OR them (e.g. ['top_up_manual','crypto_top_up'] for credits-only)."
3115
+ ),
2681
3116
  page: z.number().int().min(1).max(500).default(1).describe("1-indexed page."),
2682
3117
  limit: z.number().int().min(1).max(200).default(50).describe("Page size.")
2683
3118
  };
2684
3119
  var listBalanceHistoryTool = {
2685
3120
  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.",
3121
+ 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
3122
  annotations: {
2688
3123
  title: "List Balance History",
2689
3124
  readOnlyHint: true,
@@ -2697,7 +3132,8 @@ var listBalanceHistoryTool = {
2697
3132
  page: input.page,
2698
3133
  limit: input.limit,
2699
3134
  ...input.date_from !== void 0 ? { date_from: input.date_from } : {},
2700
- ...input.date_to !== void 0 ? { date_to: input.date_to } : {}
3135
+ ...input.date_to !== void 0 ? { date_to: input.date_to } : {},
3136
+ ...input.type !== void 0 ? { type: input.type } : {}
2701
3137
  };
2702
3138
  const result = await ctx.api.listBalanceHistory(filters);
2703
3139
  if (result.isErr()) return err(mapApiError(result.error));
@@ -2705,8 +3141,10 @@ var listBalanceHistoryTool = {
2705
3141
  }
2706
3142
  };
2707
3143
  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."),
3144
+ date_from: z.string().datetime({ offset: true }).optional().describe(
3145
+ "ISO 8601 datetime (with timezone offset), inclusive lower bound on the usage timestamp. Plain dates also work \u2014 the API normalises to UTC midnight."
3146
+ ),
3147
+ date_to: z.string().datetime({ offset: true }).optional().describe("ISO 8601 datetime (with offset), inclusive upper bound."),
2710
3148
  scan_id: z.string().uuid().optional().describe("Filter to one scan's cost rows."),
2711
3149
  page: z.number().int().min(1).max(500).default(1).describe("1-indexed page."),
2712
3150
  limit: z.number().int().min(1).max(200).default(50).describe("Page size.")
@@ -3070,12 +3508,16 @@ var listCampaignRunsTool = {
3070
3508
  };
3071
3509
  var ListCampaignsInputShape = {
3072
3510
  group_id: z.string().uuid().optional().describe("Optional campaign-group UUID to filter by."),
3511
+ archived: z.boolean().optional().describe(
3512
+ "When omitted, the API returns active campaigns only. Pass `true` to see archived campaigns, `false` for active-only (explicit)."
3513
+ ),
3514
+ q: z.string().min(1).max(200).optional().describe("Substring search against campaign name (case-insensitive)."),
3073
3515
  page: z.number().int().min(1).max(500).default(1).describe("1-indexed page number."),
3074
3516
  limit: z.number().int().min(1).max(200).default(50).describe("Page size (1-200).")
3075
3517
  };
3076
3518
  var listCampaignsTool = {
3077
3519
  name: "list_campaigns",
3078
- description: "List campaigns for the caller's organization, optionally filtered by group, paginated.",
3520
+ description: "List campaigns for the caller's organization, optionally filtered by group, archived flag, or name substring. Paginated.",
3079
3521
  annotations: {
3080
3522
  title: "List Campaigns",
3081
3523
  readOnlyHint: true,
@@ -3088,14 +3530,21 @@ var listCampaignsTool = {
3088
3530
  const filters = {
3089
3531
  page: input.page,
3090
3532
  limit: input.limit,
3091
- ...input.group_id !== void 0 ? { group_id: input.group_id } : {}
3533
+ ...input.group_id !== void 0 ? { group_id: input.group_id } : {},
3534
+ ...input.archived !== void 0 ? { archived: input.archived } : {},
3535
+ ...input.q !== void 0 ? { q: input.q } : {}
3092
3536
  };
3093
3537
  const result = await ctx.api.listCampaigns(filters);
3094
3538
  if (result.isErr()) return err(mapApiError(result.error));
3095
3539
  return ok(result.value);
3096
3540
  }
3097
3541
  };
3098
- var ListCampaignsPickerInputShape = {};
3542
+ var ListCampaignsPickerInputShape = {
3543
+ archived: z.boolean().optional().describe("Pass true to include archived campaigns. Defaults to active-only."),
3544
+ group_id: z.string().uuid().optional().describe("Filter to one campaign group."),
3545
+ q: z.string().min(1).max(200).optional().describe("Substring search against campaign name (case-insensitive)."),
3546
+ limit: z.number().int().min(1).max(500).optional().describe("Maximum rows to return (default per API, typically 200).")
3547
+ };
3099
3548
  var listCampaignsPickerTool = {
3100
3549
  name: "list_campaigns_picker",
3101
3550
  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 +3556,16 @@ var listCampaignsPickerTool = {
3107
3556
  openWorldHint: false
3108
3557
  },
3109
3558
  inputSchema: z.object(ListCampaignsPickerInputShape),
3110
- handler: async (_input, ctx) => {
3111
- const result = await ctx.api.listCampaignsPicker();
3559
+ handler: async (input, ctx) => {
3560
+ const filters = {
3561
+ ...input.archived !== void 0 ? { archived: input.archived } : {},
3562
+ ...input.group_id !== void 0 ? { group_id: input.group_id } : {},
3563
+ ...input.q !== void 0 ? { q: input.q } : {},
3564
+ ...input.limit !== void 0 ? { limit: input.limit } : {}
3565
+ };
3566
+ const result = await ctx.api.listCampaignsPicker(
3567
+ Object.keys(filters).length > 0 ? filters : void 0
3568
+ );
3112
3569
  if (result.isErr()) return err(mapApiError(result.error));
3113
3570
  return ok(result.value);
3114
3571
  }
@@ -3340,6 +3797,196 @@ var updateCustomRuleTool = {
3340
3797
  return ok(result.value);
3341
3798
  }
3342
3799
  };
3800
+ var TaxonomyNodeShape = z.object({
3801
+ client_id: z.string().min(1).max(100).describe(
3802
+ "Caller-chosen string id used to wire parent/child links inside this request only. The API allocates real UUIDs on response."
3803
+ ),
3804
+ parent_client_id: z.string().min(1).max(100).nullable().optional().describe("client_id of this node's parent. null/omitted = top-level node."),
3805
+ name: z.string().min(1).max(100).describe("Display name of the node."),
3806
+ description: z.string().max(200).optional().describe(
3807
+ "Free-form description shown to the LLM during classification \u2014 keep it concrete (one sentence)."
3808
+ ),
3809
+ is_default: z.boolean().optional().describe(
3810
+ "Mark this node as the fallback bucket. Exactly ONE node per taxonomy must be is_default=true."
3811
+ )
3812
+ });
3813
+
3814
+ // src/application/tools/custom-taxonomies/create-custom-taxonomy.tool.ts
3815
+ var CreateCustomTaxonomyInputShape = {
3816
+ name: z.string().min(1).max(100).describe("Display name (1-100 chars)."),
3817
+ description: z.string().max(500).optional().describe("Free-form description (defaults to empty string)."),
3818
+ nodes: z.array(TaxonomyNodeShape).max(2e3).optional().describe(
3819
+ "Initial tree as a flat array (parents before children, linked via client_id / parent_client_id). Empty / omitted = empty taxonomy."
3820
+ )
3821
+ };
3822
+ var createCustomTaxonomyTool = {
3823
+ name: "create_custom_taxonomy",
3824
+ 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.",
3825
+ annotations: {
3826
+ title: "Create Custom Taxonomy",
3827
+ readOnlyHint: false,
3828
+ destructiveHint: false,
3829
+ idempotentHint: false,
3830
+ openWorldHint: false
3831
+ },
3832
+ inputSchema: z.object(CreateCustomTaxonomyInputShape),
3833
+ handler: async (input, ctx) => {
3834
+ const result = await ctx.api.createCustomTaxonomy({
3835
+ name: input.name,
3836
+ description: input.description ?? "",
3837
+ nodes: (input.nodes ?? []).map(toRequestNode)
3838
+ });
3839
+ if (result.isErr()) return err(mapApiError(result.error));
3840
+ return ok(result.value);
3841
+ }
3842
+ };
3843
+ function toRequestNode(n) {
3844
+ return {
3845
+ client_id: n.client_id,
3846
+ parent_client_id: n.parent_client_id ?? null,
3847
+ name: n.name,
3848
+ description: n.description ?? "",
3849
+ is_default: n.is_default ?? false
3850
+ };
3851
+ }
3852
+ var DeleteCustomTaxonomyInputShape = {
3853
+ taxonomy_id: z.string().uuid().describe("Custom taxonomy UUID.")
3854
+ };
3855
+ var deleteCustomTaxonomyTool = {
3856
+ name: "delete_custom_taxonomy",
3857
+ 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.",
3858
+ annotations: {
3859
+ title: "Delete Custom Taxonomy",
3860
+ readOnlyHint: false,
3861
+ destructiveHint: true,
3862
+ // Soft-delete: a second call returns 404 for the same id but
3863
+ // does not change observable state — same convention used by
3864
+ // `delete_policy_set` and `delete_tag_definition`.
3865
+ idempotentHint: true,
3866
+ openWorldHint: false
3867
+ },
3868
+ inputSchema: z.object(DeleteCustomTaxonomyInputShape),
3869
+ handler: async (input, ctx) => {
3870
+ const result = await ctx.api.deleteCustomTaxonomy(input.taxonomy_id);
3871
+ if (result.isErr()) return err(mapApiError(result.error));
3872
+ return ok({ deleted: true });
3873
+ }
3874
+ };
3875
+ var GetCustomTaxonomyInputShape = {
3876
+ taxonomy_id: z.string().uuid().describe("Custom taxonomy UUID.")
3877
+ };
3878
+ var getCustomTaxonomyTool = {
3879
+ name: "get_custom_taxonomy",
3880
+ description: "Get one custom taxonomy by UUID with its full node tree (each node has id, parent_id, level, position, name, description, is_default).",
3881
+ annotations: {
3882
+ title: "Get Custom Taxonomy",
3883
+ readOnlyHint: true,
3884
+ destructiveHint: false,
3885
+ idempotentHint: true,
3886
+ openWorldHint: false
3887
+ },
3888
+ inputSchema: z.object(GetCustomTaxonomyInputShape),
3889
+ handler: async (input, ctx) => {
3890
+ const result = await ctx.api.getCustomTaxonomy(input.taxonomy_id);
3891
+ if (result.isErr()) return err(mapApiError(result.error));
3892
+ return ok(result.value);
3893
+ }
3894
+ };
3895
+ var ListCustomTaxonomiesInputShape = {};
3896
+ var listCustomTaxonomiesTool = {
3897
+ name: "list_custom_taxonomies",
3898
+ 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`.",
3899
+ annotations: {
3900
+ title: "List Custom Taxonomies",
3901
+ readOnlyHint: true,
3902
+ destructiveHint: false,
3903
+ idempotentHint: true,
3904
+ openWorldHint: false
3905
+ },
3906
+ inputSchema: z.object(ListCustomTaxonomiesInputShape),
3907
+ handler: async (_input, ctx) => {
3908
+ const result = await ctx.api.listCustomTaxonomies();
3909
+ if (result.isErr()) return err(mapApiError(result.error));
3910
+ return ok({ items: result.value });
3911
+ }
3912
+ };
3913
+ var ParseCustomTaxonomyTextInputShape = {
3914
+ text: z.string().min(1).max(5e4).describe(
3915
+ "Free-form text describing the tree (one node per line; indentation expresses depth). Up to 50 000 chars."
3916
+ )
3917
+ };
3918
+ var parseCustomTaxonomyTextTool = {
3919
+ name: "parse_custom_taxonomy_text",
3920
+ 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).",
3921
+ annotations: {
3922
+ title: "Parse Custom Taxonomy Text",
3923
+ readOnlyHint: true,
3924
+ destructiveHint: false,
3925
+ idempotentHint: true,
3926
+ openWorldHint: false
3927
+ },
3928
+ inputSchema: z.object(ParseCustomTaxonomyTextInputShape),
3929
+ handler: async (input, ctx) => {
3930
+ const result = await ctx.api.parseCustomTaxonomyText({ text: input.text });
3931
+ if (result.isErr()) return err(mapApiError(result.error));
3932
+ return ok(result.value);
3933
+ }
3934
+ };
3935
+ var RestoreCustomTaxonomyInputShape = {
3936
+ taxonomy_id: z.string().uuid().describe("Custom taxonomy UUID (must currently be is_active=false).")
3937
+ };
3938
+ var restoreCustomTaxonomyTool = {
3939
+ name: "restore_custom_taxonomy",
3940
+ description: "Re-activate a previously soft-deleted custom taxonomy. Returns the restored taxonomy with its full tree (is_active=true).",
3941
+ annotations: {
3942
+ title: "Restore Custom Taxonomy",
3943
+ readOnlyHint: false,
3944
+ destructiveHint: false,
3945
+ idempotentHint: true,
3946
+ openWorldHint: false
3947
+ },
3948
+ inputSchema: z.object(RestoreCustomTaxonomyInputShape),
3949
+ handler: async (input, ctx) => {
3950
+ const result = await ctx.api.restoreCustomTaxonomy(input.taxonomy_id);
3951
+ if (result.isErr()) return err(mapApiError(result.error));
3952
+ return ok(result.value);
3953
+ }
3954
+ };
3955
+ var UpdateCustomTaxonomyInputShape = {
3956
+ taxonomy_id: z.string().uuid().describe("Custom taxonomy UUID."),
3957
+ name: z.string().min(1).max(100).describe("New display name."),
3958
+ description: z.string().max(500).optional().describe("New description (defaults to empty string)."),
3959
+ nodes: z.array(TaxonomyNodeShape).max(2e3).optional().describe(
3960
+ "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."
3961
+ )
3962
+ };
3963
+ var updateCustomTaxonomyTool = {
3964
+ name: "update_custom_taxonomy",
3965
+ 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.",
3966
+ annotations: {
3967
+ title: "Update Custom Taxonomy",
3968
+ readOnlyHint: false,
3969
+ destructiveHint: false,
3970
+ idempotentHint: true,
3971
+ openWorldHint: false
3972
+ },
3973
+ inputSchema: z.object(UpdateCustomTaxonomyInputShape),
3974
+ handler: async (input, ctx) => {
3975
+ const result = await ctx.api.updateCustomTaxonomy(input.taxonomy_id, {
3976
+ name: input.name,
3977
+ description: input.description ?? "",
3978
+ nodes: (input.nodes ?? []).map((n) => ({
3979
+ client_id: n.client_id,
3980
+ parent_client_id: n.parent_client_id ?? null,
3981
+ name: n.name,
3982
+ description: n.description ?? "",
3983
+ is_default: n.is_default ?? false
3984
+ }))
3985
+ });
3986
+ if (result.isErr()) return err(mapApiError(result.error));
3987
+ return ok(result.value);
3988
+ }
3989
+ };
3343
3990
  var ListEmulatorsInputShape = {};
3344
3991
  var listEmulatorsTool = {
3345
3992
  name: "list_emulators",
@@ -3378,13 +4025,72 @@ var listGeosTool = {
3378
4025
  return ok({ items: result.value, total: result.value.length });
3379
4026
  }
3380
4027
  };
4028
+
4029
+ // src/application/tools/_shared/binary-content.ts
4030
+ function imageBlock(bytes, mimeType) {
4031
+ return {
4032
+ content: [
4033
+ {
4034
+ type: "image",
4035
+ data: Buffer.from(bytes).toString("base64"),
4036
+ mimeType
4037
+ }
4038
+ ]
4039
+ };
4040
+ }
4041
+ function resourceBlock(bytes, mimeType, uri) {
4042
+ return {
4043
+ content: [
4044
+ {
4045
+ type: "resource",
4046
+ resource: {
4047
+ uri,
4048
+ mimeType,
4049
+ blob: Buffer.from(bytes).toString("base64")
4050
+ }
4051
+ }
4052
+ ]
4053
+ };
4054
+ }
4055
+
4056
+ // src/application/tools/invoicing/get-invoice-pdf.tool.ts
4057
+ var GetInvoicePdfInputShape = {
4058
+ invoice_id: z.string().uuid().describe("Invoice UUID \u2014 see `list_invoices`.")
4059
+ };
4060
+ var getInvoicePdfTool = {
4061
+ name: "get_invoice_pdf",
4062
+ 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.",
4063
+ annotations: {
4064
+ title: "Get Invoice PDF",
4065
+ readOnlyHint: true,
4066
+ destructiveHint: false,
4067
+ idempotentHint: true,
4068
+ openWorldHint: false
4069
+ },
4070
+ inputSchema: z.object(GetInvoicePdfInputShape),
4071
+ handler: async (input, ctx) => {
4072
+ const result = await ctx.api.getInvoicePdf(input.invoice_id);
4073
+ if (result.isErr()) return err(mapApiError(result.error));
4074
+ return ok(
4075
+ resourceBlock(
4076
+ result.value.bytes,
4077
+ result.value.contentType,
4078
+ `kaminari-ad://invoices/${input.invoice_id}.pdf`
4079
+ )
4080
+ );
4081
+ }
4082
+ };
3381
4083
  var ListInvoicesInputShape = {
4084
+ type: z.enum(["proforma", "final"]).optional().describe(
4085
+ "Filter by invoice kind: proforma (advance bill) or final (issued after the period)."
4086
+ ),
4087
+ status: z.enum(["draft", "issued", "paid", "voided", "overdue"]).optional().describe("Filter by lifecycle status."),
3382
4088
  page: z.number().int().min(1).max(500).default(1).describe("1-indexed page."),
3383
4089
  limit: z.number().int().min(1).max(200).default(50).describe("Page size.")
3384
4090
  };
3385
4091
  var listInvoicesTool = {
3386
4092
  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).",
4093
+ 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
4094
  annotations: {
3389
4095
  title: "List Invoices",
3390
4096
  readOnlyHint: true,
@@ -3394,23 +4100,123 @@ var listInvoicesTool = {
3394
4100
  },
3395
4101
  inputSchema: z.object(ListInvoicesInputShape),
3396
4102
  handler: async (input, ctx) => {
3397
- const result = await ctx.api.listInvoices({ page: input.page, limit: input.limit });
4103
+ const filters = {
4104
+ page: input.page,
4105
+ limit: input.limit,
4106
+ ...input.type !== void 0 ? { type: input.type } : {},
4107
+ ...input.status !== void 0 ? { status: input.status } : {}
4108
+ };
4109
+ const result = await ctx.api.listInvoices(filters);
3398
4110
  if (result.isErr()) return err(mapApiError(result.error));
3399
4111
  return ok(result.value);
3400
4112
  }
3401
4113
  };
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.")
4114
+ var TIER1 = z.string().min(1).max(200);
4115
+ var TIER_OPT = z.string().max(200).nullable().optional();
4116
+ var IabV3Block = z.object({
4117
+ tier1: TIER1.describe("Top-level IAB Content Taxonomy V3 category (e.g. 'Sensitive Topics')."),
4118
+ tier2: TIER_OPT.describe("Sub-category (optional). Empty/null = subtree match."),
4119
+ tier3: TIER_OPT.describe("Sub-sub-category (optional)."),
4120
+ tier4: TIER_OPT.describe(
4121
+ "Leaf-level category (optional). When all 4 tiers set, leaf-only match."
4122
+ )
4123
+ });
4124
+ var AiCategoryBlock = z.object({
4125
+ tier1: TIER1.describe("Top-level freeform AI-generated category (e.g. 'Gambling')."),
4126
+ tier2: TIER_OPT.describe("Sub-category (optional)."),
4127
+ tier3: TIER_OPT.describe("Sub-sub-category (optional)."),
4128
+ tier4: TIER_OPT.describe("Leaf-level category (optional).")
4129
+ });
4130
+ var CustomTaxonomyBlock = z.object({
4131
+ taxonomy_id: z.string().uuid().describe("ID of the per-org custom taxonomy (see `list_custom_taxonomies`)."),
4132
+ tier1: TIER1.describe("Top-level node name in the taxonomy."),
4133
+ tier2: TIER_OPT.describe("Tier 2 node name (optional)."),
4134
+ tier3: TIER_OPT.describe("Tier 3 node name (optional)."),
4135
+ tier4: TIER_OPT.describe("Leaf node name (optional).")
3405
4136
  });
4137
+ 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.");
4138
+ var PolicyEntryInput = z.discriminatedUnion("rule_type", [
4139
+ z.object({
4140
+ rule_type: z.literal("tag").describe("Match a specific tag slug emitted by the scan pipeline."),
4141
+ tag_slug: z.string().min(1).max(100).describe("Tag slug (see `list_tags` for the catalogue)."),
4142
+ country_codes: COUNTRY_CODES
4143
+ }),
4144
+ z.object({
4145
+ rule_type: z.literal("iab_v3").describe("Match the scan's canonical IAB V3 category prefix."),
4146
+ iab_v3: IabV3Block,
4147
+ country_codes: COUNTRY_CODES
4148
+ }),
4149
+ z.object({
4150
+ rule_type: z.literal("brand").describe("Match the scan's advertiser brand (case-insensitive)."),
4151
+ brand: z.string().min(1).max(200).describe("Brand string."),
4152
+ country_codes: COUNTRY_CODES
4153
+ }),
4154
+ z.object({
4155
+ rule_type: z.literal("ai_category").describe("Match the freeform LLM-generated category prefix on the scan."),
4156
+ ai_category: AiCategoryBlock,
4157
+ country_codes: COUNTRY_CODES
4158
+ }),
4159
+ z.object({
4160
+ rule_type: z.literal("custom_taxonomy").describe("Match a per-org custom taxonomy node prefix on the scan."),
4161
+ custom_taxonomy: CustomTaxonomyBlock,
4162
+ country_codes: COUNTRY_CODES
4163
+ })
4164
+ ]).describe(
4165
+ "One policy rule. Discriminated by `rule_type`; populate the matching value-block (tag_slug / iab_v3 / brand / ai_category / custom_taxonomy)."
4166
+ );
4167
+ function policyEntryToRequest(entry) {
4168
+ const base = {
4169
+ rule_type: entry.rule_type,
4170
+ tag_slug: null,
4171
+ iab_v3: null,
4172
+ brand: null,
4173
+ ai_category: null,
4174
+ custom_taxonomy: null,
4175
+ country_codes: [...entry.country_codes]
4176
+ };
4177
+ if (entry.rule_type === "tag") {
4178
+ return { ...base, tag_slug: entry.tag_slug };
4179
+ }
4180
+ if (entry.rule_type === "iab_v3") {
4181
+ return { ...base, iab_v3: normaliseTiers(entry.iab_v3) };
4182
+ }
4183
+ if (entry.rule_type === "brand") {
4184
+ return { ...base, brand: entry.brand };
4185
+ }
4186
+ if (entry.rule_type === "ai_category") {
4187
+ return { ...base, ai_category: normaliseTiers(entry.ai_category) };
4188
+ }
4189
+ return { ...base, custom_taxonomy: normaliseCustomTaxonomy(entry.custom_taxonomy) };
4190
+ }
4191
+ function normaliseTiers(t) {
4192
+ return {
4193
+ tier1: t.tier1,
4194
+ tier2: t.tier2 ?? null,
4195
+ tier3: t.tier3 ?? null,
4196
+ tier4: t.tier4 ?? null
4197
+ };
4198
+ }
4199
+ function normaliseCustomTaxonomy(t) {
4200
+ return {
4201
+ taxonomy_id: t.taxonomy_id,
4202
+ tier1: t.tier1,
4203
+ tier2: t.tier2 ?? null,
4204
+ tier3: t.tier3 ?? null,
4205
+ tier4: t.tier4 ?? null
4206
+ };
4207
+ }
4208
+
4209
+ // src/application/tools/policy-sets/create-policy-set.tool.ts
3406
4210
  var CreatePolicySetInputShape = {
3407
4211
  name: z.string().min(1).max(200).describe("Display name."),
3408
4212
  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.")
4213
+ entries: z.array(PolicyEntryInput).min(1).max(500).describe(
4214
+ "At least one entry. Each entry is a discriminated union over five rule kinds: tag / iab_v3 / brand / ai_category / custom_taxonomy."
4215
+ )
3410
4216
  };
3411
4217
  var createPolicySetTool = {
3412
4218
  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`.",
4219
+ 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
4220
  annotations: {
3415
4221
  title: "Create Policy Set",
3416
4222
  readOnlyHint: false,
@@ -3423,7 +4229,7 @@ var createPolicySetTool = {
3423
4229
  const result = await ctx.api.createPolicySet({
3424
4230
  name: input.name,
3425
4231
  description: input.description,
3426
- entries: input.entries.map((e) => ({ tag_slug: e.tag_slug, country_codes: e.country_codes }))
4232
+ entries: input.entries.map(policyEntryToRequest)
3427
4233
  });
3428
4234
  if (result.isErr()) return err(mapApiError(result.error));
3429
4235
  return ok(result.value);
@@ -3454,7 +4260,7 @@ var GetPolicySetInputShape = {
3454
4260
  };
3455
4261
  var getPolicySetTool = {
3456
4262
  name: "get_policy_set",
3457
- description: "Get one policy set by UUID with its complete list of entries (tag-slug + applicable country codes).",
4263
+ 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
4264
  annotations: {
3459
4265
  title: "Get Policy Set",
3460
4266
  readOnlyHint: true,
@@ -3471,11 +4277,14 @@ var getPolicySetTool = {
3471
4277
  };
3472
4278
  var ListPolicySetsInputShape = {
3473
4279
  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.")
4280
+ limit: z.number().int().min(1).max(200).default(50).describe("Page size."),
4281
+ visibility: z.enum(["private", "public"]).optional().describe(
4282
+ "Narrow to one scope. `private` = org-owned only, `public` = Kaminari Ad-curated sets visible to every org. Omit the filter to see BOTH combined."
4283
+ )
3475
4284
  };
3476
4285
  var listPolicySetsTool = {
3477
4286
  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.",
4287
+ 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
4288
  annotations: {
3480
4289
  title: "List Policy Sets",
3481
4290
  readOnlyHint: true,
@@ -3485,7 +4294,11 @@ var listPolicySetsTool = {
3485
4294
  },
3486
4295
  inputSchema: z.object(ListPolicySetsInputShape),
3487
4296
  handler: async (input, ctx) => {
3488
- const result = await ctx.api.listPolicySets({ page: input.page, limit: input.limit });
4297
+ const result = await ctx.api.listPolicySets({
4298
+ page: input.page,
4299
+ limit: input.limit,
4300
+ ...input.visibility !== void 0 ? { visibility: input.visibility } : {}
4301
+ });
3489
4302
  if (result.isErr()) return err(mapApiError(result.error));
3490
4303
  return ok(result.value);
3491
4304
  }
@@ -3510,19 +4323,17 @@ var requestPolicySetApprovalTool = {
3510
4323
  return ok({ requested: true });
3511
4324
  }
3512
4325
  };
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
4326
  var UpdatePolicySetInputShape = {
3518
4327
  policy_set_id: z.string().uuid().describe("Policy set UUID."),
3519
4328
  name: z.string().min(1).max(200).describe("New name (always required by the API on update)."),
3520
4329
  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.")
4330
+ entries: z.array(PolicyEntryInput).min(1).max(500).describe(
4331
+ "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)."
4332
+ )
3522
4333
  };
3523
4334
  var updatePolicySetTool = {
3524
4335
  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.",
4336
+ 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
4337
  annotations: {
3527
4338
  title: "Update Policy Set",
3528
4339
  readOnlyHint: false,
@@ -3535,7 +4346,7 @@ var updatePolicySetTool = {
3535
4346
  const result = await ctx.api.updatePolicySet(input.policy_set_id, {
3536
4347
  name: input.name,
3537
4348
  description: input.description,
3538
- entries: input.entries.map((e) => ({ tag_slug: e.tag_slug, country_codes: e.country_codes }))
4349
+ entries: input.entries.map(policyEntryToRequest)
3539
4350
  });
3540
4351
  if (result.isErr()) return err(mapApiError(result.error));
3541
4352
  return ok(result.value);
@@ -3708,22 +4519,110 @@ var getScanTool = {
3708
4519
  return ok(result.value);
3709
4520
  }
3710
4521
  };
4522
+ var GetScanCreativeScreenshotInputShape = {
4523
+ scan_id: z.string().uuid().describe("Scan UUID \u2014 must be an ad-tag scan (is_ad_tag=true)."),
4524
+ width: z.number().int().min(50).max(2e3).optional().describe("Optional resize width in pixels.")
4525
+ };
4526
+ var getScanCreativeScreenshotTool = {
4527
+ name: "get_scan_creative_screenshot",
4528
+ 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.",
4529
+ annotations: {
4530
+ title: "Get Scan Creative Screenshot",
4531
+ readOnlyHint: true,
4532
+ destructiveHint: false,
4533
+ idempotentHint: true,
4534
+ openWorldHint: false
4535
+ },
4536
+ inputSchema: z.object(GetScanCreativeScreenshotInputShape),
4537
+ handler: async (input, ctx) => {
4538
+ const result = await ctx.api.getScanCreativeScreenshot(input.scan_id, input.width);
4539
+ if (result.isErr()) return err(mapApiError(result.error));
4540
+ return ok(imageBlock(result.value.bytes, result.value.contentType));
4541
+ }
4542
+ };
4543
+ var GetScanLandingScreenshotInputShape = {
4544
+ scan_id: z.string().uuid().describe("Scan UUID (ad-tag scan)."),
4545
+ landing_ord: z.number().int().min(0).max(50).describe("0-indexed landing slot (see `get_scan(...).landings[*].ord`)."),
4546
+ width: z.number().int().min(50).max(2e3).optional().describe("Optional resize width in pixels.")
4547
+ };
4548
+ var getScanLandingScreenshotTool = {
4549
+ name: "get_scan_landing_screenshot",
4550
+ 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.",
4551
+ annotations: {
4552
+ title: "Get Scan Landing Screenshot",
4553
+ readOnlyHint: true,
4554
+ destructiveHint: false,
4555
+ idempotentHint: true,
4556
+ openWorldHint: false
4557
+ },
4558
+ inputSchema: z.object(GetScanLandingScreenshotInputShape),
4559
+ handler: async (input, ctx) => {
4560
+ const result = await ctx.api.getScanLandingScreenshot(
4561
+ input.scan_id,
4562
+ input.landing_ord,
4563
+ input.width
4564
+ );
4565
+ if (result.isErr()) return err(mapApiError(result.error));
4566
+ return ok(imageBlock(result.value.bytes, result.value.contentType));
4567
+ }
4568
+ };
4569
+ var GetScanScreenshotInputShape = {
4570
+ scan_id: z.string().uuid().describe("Scan UUID."),
4571
+ width: z.number().int().min(50).max(2e3).optional().describe(
4572
+ "Optional resize width in pixels. The API resizes server-side to keep the image small for the agent's context window."
4573
+ )
4574
+ };
4575
+ var getScanScreenshotTool = {
4576
+ name: "get_scan_screenshot",
4577
+ 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.",
4578
+ annotations: {
4579
+ title: "Get Scan Screenshot",
4580
+ readOnlyHint: true,
4581
+ destructiveHint: false,
4582
+ idempotentHint: true,
4583
+ openWorldHint: false
4584
+ },
4585
+ inputSchema: z.object(GetScanScreenshotInputShape),
4586
+ handler: async (input, ctx) => {
4587
+ const result = await ctx.api.getScanScreenshot(input.scan_id, input.width);
4588
+ if (result.isErr()) return err(mapApiError(result.error));
4589
+ return ok(imageBlock(result.value.bytes, result.value.contentType));
4590
+ }
4591
+ };
3711
4592
  var ListScansInputShape = {
3712
4593
  status: z.string().optional().describe(
3713
- "Comma-separated statuses to filter by. One of: pending, running, done, failed, cancelled."
4594
+ "Comma-separated statuses to filter by. Lifecycle values: pending, running, crawled, checking, checking_async, completed, partial, failed, cancelled."
3714
4595
  ),
3715
4596
  country_code: z.string().optional().describe("Comma-separated ISO 3166-1 alpha-2 country codes, e.g. US,DE,JP."),
3716
4597
  url: z.string().optional().describe("Substring match against the scanned URL."),
3717
4598
  scan_id: z.string().optional().describe("Comma-separated scan UUIDs to fetch a specific set."),
3718
4599
  date_from: z.string().date().optional().describe("ISO date (YYYY-MM-DD), inclusive lower bound on scan creation."),
3719
4600
  date_to: z.string().date().optional().describe("ISO date (YYYY-MM-DD), inclusive upper bound on scan creation."),
4601
+ timezone: z.string().optional().describe(
4602
+ "IANA timezone (e.g. 'Europe/Berlin') applied to date_from / date_to. Defaults to UTC."
4603
+ ),
4604
+ run_id: z.string().uuid().optional().describe("Filter to scans of one run."),
4605
+ campaign_id: z.string().uuid().optional().describe("Filter to scans of one campaign."),
4606
+ group_id: z.string().uuid().optional().describe("Filter to scans of one campaign group."),
3720
4607
  tag: z.string().optional().describe("Comma-separated tag slugs to filter by."),
4608
+ ai_category: z.string().optional().describe(
4609
+ "Filter by the freeform LLM-generated category prefix on the scan (slash-separated tier path, e.g. 'Gambling/Online Casinos')."
4610
+ ),
4611
+ iab_v3_category: z.string().optional().describe("Filter by canonical IAB Content Taxonomy V3 prefix (slash-separated tier path)."),
4612
+ iab_category: z.string().optional().describe("Filter by legacy IAB V2.2 category (only present on pre-P3 scans)."),
4613
+ brand: z.string().optional().describe("Filter by detected advertiser brand (case-insensitive)."),
4614
+ labels: z.record(
4615
+ z.string().min(1).max(100).regex(/^[a-z0-9_]+$/, "label keys must be snake_case ([a-z0-9_]+)"),
4616
+ z.string().min(1).max(200)
4617
+ ).optional().describe(
4618
+ "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`."
4619
+ ),
3721
4620
  page: z.number().int().min(1).max(500).default(1).describe("1-indexed page number."),
3722
4621
  limit: z.number().int().min(1).max(200).default(50).describe("Page size (1-200). Default 50.")
3723
4622
  };
3724
4623
  var listScansTool = {
3725
4624
  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.",
4625
+ description: "List scans for the caller's organization with optional filters (status, country, URL substring, date range, run/campaign/group, tag, AI/IAB/brand category, dynamic labels). Returns a paginated envelope.",
3727
4626
  annotations: {
3728
4627
  title: "List Scans",
3729
4628
  readOnlyHint: true,
@@ -3733,6 +4632,12 @@ var listScansTool = {
3733
4632
  },
3734
4633
  inputSchema: z.object(ListScansInputShape),
3735
4634
  handler: async (input, ctx) => {
4635
+ const labelFilters = {};
4636
+ if (input.labels !== void 0) {
4637
+ for (const [k, v] of Object.entries(input.labels)) {
4638
+ labelFilters[`label_${k}`] = v;
4639
+ }
4640
+ }
3736
4641
  const filters = {
3737
4642
  page: input.page,
3738
4643
  limit: input.limit,
@@ -3742,7 +4647,16 @@ var listScansTool = {
3742
4647
  ...input.scan_id !== void 0 ? { scan_id: input.scan_id } : {},
3743
4648
  ...input.date_from !== void 0 ? { date_from: input.date_from } : {},
3744
4649
  ...input.date_to !== void 0 ? { date_to: input.date_to } : {},
3745
- ...input.tag !== void 0 ? { tag: input.tag } : {}
4650
+ ...input.timezone !== void 0 ? { timezone: input.timezone } : {},
4651
+ ...input.run_id !== void 0 ? { run_id: input.run_id } : {},
4652
+ ...input.campaign_id !== void 0 ? { campaign_id: input.campaign_id } : {},
4653
+ ...input.group_id !== void 0 ? { group_id: input.group_id } : {},
4654
+ ...input.tag !== void 0 ? { tag: input.tag } : {},
4655
+ ...input.ai_category !== void 0 ? { ai_category: input.ai_category } : {},
4656
+ ...input.iab_v3_category !== void 0 ? { iab_v3_category: input.iab_v3_category } : {},
4657
+ ...input.iab_category !== void 0 ? { iab_category: input.iab_category } : {},
4658
+ ...input.brand !== void 0 ? { brand: input.brand } : {},
4659
+ ...labelFilters
3746
4660
  };
3747
4661
  const result = await ctx.api.listScans(filters);
3748
4662
  if (result.isErr()) {
@@ -3839,10 +4753,14 @@ var listScanTagsTool = {
3839
4753
  return ok({ items: result.value, total: result.value.length });
3840
4754
  }
3841
4755
  };
3842
- var ListTagsInputShape = {};
4756
+ var ListTagsInputShape = {
4757
+ category: z.string().min(1).max(100).optional().describe(
4758
+ "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."
4759
+ )
4760
+ };
3843
4761
  var listTagsTool = {
3844
4762
  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).",
4763
+ 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
4764
  annotations: {
3847
4765
  title: "List Tags",
3848
4766
  readOnlyHint: true,
@@ -3851,8 +4769,9 @@ var listTagsTool = {
3851
4769
  openWorldHint: false
3852
4770
  },
3853
4771
  inputSchema: z.object(ListTagsInputShape),
3854
- handler: async (_input, ctx) => {
3855
- const result = await ctx.api.listTags();
4772
+ handler: async (input, ctx) => {
4773
+ const filters = input.category !== void 0 ? { category: input.category } : void 0;
4774
+ const result = await ctx.api.listTags(filters);
3856
4775
  if (result.isErr()) return err(mapApiError(result.error));
3857
4776
  return ok({ items: result.value, total: result.value.length });
3858
4777
  }
@@ -3862,7 +4781,9 @@ var UpdateTagDefinitionInputShape = {
3862
4781
  display_name: z.string().min(1).max(200).optional().describe("New human-readable name."),
3863
4782
  description: z.string().max(2e3).optional().describe("New description."),
3864
4783
  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.")
4784
+ visibility: z.enum(["hidden", "internal", "public"]).optional().describe(
4785
+ "Tag visibility on the scan report: hidden = not surfaced; internal = visible to org members; public = appears in the public scan-share view."
4786
+ )
3866
4787
  };
3867
4788
  var updateTagDefinitionTool = {
3868
4789
  name: "update_tag_definition",
@@ -3880,8 +4801,12 @@ var updateTagDefinitionTool = {
3880
4801
  if (input.display_name !== void 0) body.display_name = input.display_name;
3881
4802
  if (input.description !== void 0) body.description = input.description;
3882
4803
  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;
4804
+ if (input.visibility !== void 0) body.visibility = input.visibility;
4805
+ if (Object.keys(body).length === 0) {
4806
+ return err({
4807
+ kind: "invalid-input",
4808
+ message: "update_tag_definition requires at least one field to change (display_name, description, severity, or visibility)."
4809
+ });
3885
4810
  }
3886
4811
  const result = await ctx.api.updateTagDefinition(input.slug, body);
3887
4812
  if (result.isErr()) return err(mapApiError(result.error));
@@ -3985,12 +4910,15 @@ var getWebhookTool = {
3985
4910
  };
3986
4911
  var ListWebhookDeliveriesInputShape = {
3987
4912
  webhook_id: z.string().uuid().describe("Webhook endpoint UUID."),
4913
+ success: z.boolean().optional().describe("Filter to delivered (true) or failed (false) attempts only."),
4914
+ from_ts: z.string().datetime({ offset: true }).optional().describe("ISO 8601 datetime (with offset), inclusive lower bound on attempt time."),
4915
+ to_ts: z.string().datetime({ offset: true }).optional().describe("ISO 8601 datetime (with offset), inclusive upper bound."),
3988
4916
  page: z.number().int().min(1).max(500).default(1).describe("1-indexed page."),
3989
4917
  limit: z.number().int().min(1).max(200).default(50).describe("Page size.")
3990
4918
  };
3991
4919
  var listWebhookDeliveriesTool = {
3992
4920
  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.",
4921
+ 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
4922
  annotations: {
3995
4923
  title: "List Webhook Deliveries",
3996
4924
  readOnlyHint: true,
@@ -4000,10 +4928,14 @@ var listWebhookDeliveriesTool = {
4000
4928
  },
4001
4929
  inputSchema: z.object(ListWebhookDeliveriesInputShape),
4002
4930
  handler: async (input, ctx) => {
4003
- const result = await ctx.api.listWebhookDeliveries(input.webhook_id, {
4931
+ const filters = {
4004
4932
  page: input.page,
4005
- limit: input.limit
4006
- });
4933
+ limit: input.limit,
4934
+ ...input.success !== void 0 ? { success: input.success } : {},
4935
+ ...input.from_ts !== void 0 ? { from_ts: input.from_ts } : {},
4936
+ ...input.to_ts !== void 0 ? { to_ts: input.to_ts } : {}
4937
+ };
4938
+ const result = await ctx.api.listWebhookDeliveries(input.webhook_id, filters);
4007
4939
  if (result.isErr()) return err(mapApiError(result.error));
4008
4940
  return ok(result.value);
4009
4941
  }
@@ -4146,6 +5078,9 @@ function registerAllTools(register) {
4146
5078
  register(removeUserTool);
4147
5079
  register(transferOwnershipTool);
4148
5080
  register(listOrgRolesTool);
5081
+ register(createCustomRoleTool);
5082
+ register(listAccountLabelsTool);
5083
+ register(updateAccountLabelsTool);
4149
5084
  register(listApiKeysTool);
4150
5085
  register(createApiKeyTool);
4151
5086
  register(revokeApiKeyTool);
@@ -4157,6 +5092,9 @@ function registerAllTools(register) {
4157
5092
  register(createBulkScansTool);
4158
5093
  register(createScanTool);
4159
5094
  register(recheckScansTool);
5095
+ register(getScanScreenshotTool);
5096
+ register(getScanCreativeScreenshotTool);
5097
+ register(getScanLandingScreenshotTool);
4160
5098
  register(getCampaignTool);
4161
5099
  register(listCampaignsTool);
4162
5100
  register(archiveCampaignTool);
@@ -4191,6 +5129,13 @@ function registerAllTools(register) {
4191
5129
  register(updateCustomRuleTool);
4192
5130
  register(deleteCustomRuleTool);
4193
5131
  register(testCustomRuleTool);
5132
+ register(listCustomTaxonomiesTool);
5133
+ register(getCustomTaxonomyTool);
5134
+ register(createCustomTaxonomyTool);
5135
+ register(updateCustomTaxonomyTool);
5136
+ register(deleteCustomTaxonomyTool);
5137
+ register(restoreCustomTaxonomyTool);
5138
+ register(parseCustomTaxonomyTextTool);
4194
5139
  register(listPolicySetsTool);
4195
5140
  register(getPolicySetTool);
4196
5141
  register(createPolicySetTool);
@@ -4216,6 +5161,7 @@ function registerAllTools(register) {
4216
5161
  register(getUsageSummaryTool);
4217
5162
  register(listBalanceHistoryTool);
4218
5163
  register(listInvoicesTool);
5164
+ register(getInvoicePdfTool);
4219
5165
  register(listAlertDestinationsTool);
4220
5166
  register(deleteAlertDestinationTool);
4221
5167
  register(setAlertDestinationVersionTool);
@@ -4253,12 +5199,28 @@ function wireToolsIntoMcpServer(server, ctxProvider) {
4253
5199
  });
4254
5200
  }
4255
5201
  function toolOkToMcpResult(value) {
5202
+ if (isMcpContentEnvelope(value)) {
5203
+ return value;
5204
+ }
4256
5205
  const isPlainObject = value !== null && typeof value === "object" && !Array.isArray(value);
4257
5206
  return {
4258
5207
  content: [{ type: "text", text: JSON.stringify(value, null, 2) }],
4259
5208
  ...isPlainObject ? { structuredContent: value } : {}
4260
5209
  };
4261
5210
  }
5211
+ function isBinaryBlockType(type2) {
5212
+ return type2 === "image" || type2 === "audio" || type2 === "resource";
5213
+ }
5214
+ function isMcpContentEnvelope(value) {
5215
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
5216
+ const v = value;
5217
+ if (!Array.isArray(v.content) || v.content.length === 0) return false;
5218
+ return v.content.every((block) => {
5219
+ if (block === null || typeof block !== "object") return false;
5220
+ const type2 = block.type;
5221
+ return typeof type2 === "string" && isBinaryBlockType(type2);
5222
+ });
5223
+ }
4262
5224
  function toolErrorToMcpResult(error) {
4263
5225
  return {
4264
5226
  isError: true,
@@ -4285,5 +5247,5 @@ function formatToolError(error) {
4285
5247
  }
4286
5248
 
4287
5249
  export { BearerToken, createHttpApiGateway, createPinoLogger, declareEmptyResourcesAndPrompts, newRequestId, wireToolsIntoMcpServer };
4288
- //# sourceMappingURL=chunk-4MNLSWSZ.js.map
4289
- //# sourceMappingURL=chunk-4MNLSWSZ.js.map
5250
+ //# sourceMappingURL=chunk-W7TPFRJB.js.map
5251
+ //# sourceMappingURL=chunk-W7TPFRJB.js.map