@kaminari-ad/mcp 0.14.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,110 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.16.0] - 2026-08-26
11
+
12
+ ### Added
13
+
14
+ - **`unpublish_policy_set` — publication is no longer a one-way door
15
+ (KAMIAD-167).** `request_policy_set_approval` had no inverse, so an
16
+ agent could put a set into the shared catalog but never take it out;
17
+ the only escape was asking a human to reject it, and that worked only
18
+ while the request was still pending. The new tool returns a set to
19
+ private from either state — approved or awaiting review — and is
20
+ idempotent on a set that is already private. It is annotated
21
+ `destructiveHint: true` because other organizations lose access to a
22
+ set they may be browsing. Campaigns already bound to the set keep
23
+ running against it; going private blocks new attachments instead.
24
+
25
+ Needs the API side deployed first: it wraps
26
+ `POST /api/v1/policy-sets/{id}/unpublish`, added in `adverif/api!378`.
27
+
28
+ ### Changed
29
+
30
+ - **`get_campaign_alert_overrides` validates `mode` strictly — this
31
+ actually shipped in 0.15.0, undocumented.** That release's regen
32
+ narrowed `CampaignOverridesResponse.mode` from a bare `string` to the
33
+ generated `CampaignOverrideMode` enum (`inherit | override |
34
+ silence`), and the response parser enforces it, so a fourth value
35
+ from the API now surfaces as an `upstream` error instead of passing
36
+ through. That is the intent — the api types the field, so drift
37
+ should fail loudly — but it is a behaviour change and 0.15.0 recorded
38
+ only "regenerated openapi.ts / zod-schemas.ts".
39
+ - `set_campaign_alert_overrides` now takes its `mode` input from that
40
+ same generated enum instead of a hand-written `z.enum` with identical
41
+ values. Behaviour is unchanged; the point is that `check-tool-enum-drift`
42
+ can finally catch future value changes on this field, so its
43
+ exemption is gone.
44
+
45
+ ### Removed
46
+
47
+ - The two `/api/forms/*` entries in `check-api-coverage`'s
48
+ `EXEMPT_OPERATIONS`. Those routes are `include_in_schema=False` and
49
+ dropped out of the spec in the 0.15.0 regen, so the exemptions had
50
+ become dead config — exactly as their own comment predicted.
51
+
52
+ ## [0.15.0] - 2026-08-24
53
+
54
+ ### Added
55
+
56
+ - **`get_proxy_targeting` — the accepted proxy values, instead of
57
+ guesswork.** A scan's `proxy` block takes a region, city, and ISP whose
58
+ valid values come from our upstream network provider's catalogue, so
59
+ they cannot be an enum in the schema: they differ per country and per
60
+ connection type. Until now an agent had to guess, and a wrong guess
61
+ came back as a 422 with a prose message. The new tool wraps
62
+ `GET /api/v1/proxy/targeting` and returns the accepted regions,
63
+ cities, and ISPs for a country, ordered by pool size so the agent can
64
+ prefer values likely to yield an exit node. Anything it lists is
65
+ accepted by `create_scan`.
66
+
67
+ Two things the tool's description makes explicit, because both are
68
+ easy to get wrong: pass `proxy_type: "mobile"` when the scan is mobile
69
+ (the two networks are separate pools — in the US that is 1500+ ISPs
70
+ against roughly a dozen carriers), and take a city from the same
71
+ response that produced the region, since a region and a city that do
72
+ not belong together leave the provider nothing to route through.
73
+
74
+ - **The write tools now point at it.** A tool nobody is told about is a
75
+ tool nobody calls, and an agent starts from `create_scan`, not from the
76
+ catalogue. The `proxy` fields on `create_scan` / `create_bulk_scans`
77
+ and the `proxy_*` fields on `create_campaign` / `update_campaign` now
78
+ name `get_proxy_targeting` and carry the same two warnings. The
79
+ campaign `proxy_region` description previously said "free-text", which
80
+ told the agent to invent a value — precisely the behaviour that
81
+ produces the 422 this release exists to remove.
82
+
83
+ ### Changed
84
+
85
+ - **Regenerated `openapi.ts` / `zod-schemas.ts`** against the API
86
+ release that adds `/api/v1/proxy/targeting`.
87
+
88
+ - **A rejected field value now arrives as a `detail` array.** The API
89
+ changed 422 responses for values checked against a runtime vocabulary
90
+ (`proxy.region`, `proxy.city`, `proxy.isp`, `country_code`,
91
+ `emulator_id`) from a prose string to the `HTTPValidationError`
92
+ array its OpenAPI schema always declared. No change was needed here —
93
+ `toApiError` has handled both shapes since the parser-drift work — and
94
+ the agent-visible message is now `body.proxy.region: Unsupported proxy
95
+ region …` instead of an undifferentiated sentence.
96
+
97
+ ## [0.14.1] - 2026-08-24
98
+
99
+ ### Changed
100
+
101
+ - **Three tag tools described archived tags as still reachable
102
+ (KAMIAD-120).** The API now retires an archived tag everywhere: it is
103
+ no longer assigned to scans, no longer raises alerts, and an archived
104
+ platform tag is gone from the catalogue entirely. `list_tags` told the
105
+ agent the opposite — to pass `include_archived` "when resolving a slug
106
+ seen on an older scan", which now returns nothing for exactly those
107
+ slugs. `get_tag_definition` now documents the 404, and
108
+ `list_scan_tags` that a scan can report fewer tags than when it ran.
109
+ Descriptions only; no schema or shape change, so
110
+ `src/shared/api/{openapi,zod-schemas}.ts` are unaffected — the two
111
+ stale JSDoc lines about `include_archived` are regenerated from the
112
+ live spec once the API side is deployed.
113
+
10
114
  ## [0.14.0] - 2026-08-23
11
115
 
12
116
  Resyncs the tool surface with `/api/v1` (KAMIAD-158). The generated
@@ -1128,7 +1232,10 @@ Initial public release. The first version that ships to npm under
1128
1232
  need them.
1129
1233
  - Invoice PDF fetcher — same reason.
1130
1234
 
1131
- [Unreleased]: https://github.com/kaminari-ad/mcp/compare/v0.13.0...HEAD
1235
+ [Unreleased]: https://github.com/kaminari-ad/mcp/compare/v0.15.0...HEAD
1236
+ [0.15.0]: https://github.com/kaminari-ad/mcp/compare/v0.14.1...v0.15.0
1237
+ [0.14.1]: https://github.com/kaminari-ad/mcp/compare/v0.14.0...v0.14.1
1238
+ [0.14.0]: https://github.com/kaminari-ad/mcp/compare/v0.13.0...v0.14.0
1132
1239
  [0.13.0]: https://github.com/kaminari-ad/mcp/compare/v0.12.0...v0.13.0
1133
1240
  [0.12.0]: https://github.com/kaminari-ad/mcp/compare/v0.11.0...v0.12.0
1134
1241
  [0.11.0]: https://github.com/kaminari-ad/mcp/compare/v0.10.0...v0.11.0
package/README.md CHANGED
@@ -63,7 +63,7 @@ Add to your MCP client config (Cursor: `~/.cursor/mcp.json`; Claude Desktop: `~/
63
63
  }
64
64
  ```
65
65
 
66
- Restart your client. You should see `kaminari-ad` in the MCP servers list with 105 tools exposed.
66
+ Restart your client. You should see `kaminari-ad` in the MCP servers list with 107 tools exposed.
67
67
 
68
68
  ### 2b. Hosted HTTP transport (no install)
69
69
 
@@ -113,7 +113,7 @@ which decides which credential type minted it.
113
113
 
114
114
  ## Tools
115
115
 
116
- 105 tools covering the public `/api/v1` surface of Kaminari Ad. Every tool carries MCP behaviour annotations (`title`, `readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`) so MCP clients can warn before destructive actions. The complete list, by domain:
116
+ 107 tools covering the public `/api/v1` surface of Kaminari Ad. Every tool carries MCP behaviour annotations (`title`, `readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`) so MCP clients can warn before destructive actions. The complete list, by domain:
117
117
 
118
118
  - **Account** (14) — `get_account`, `update_org`, `list_org_users`, `invite_user`, `update_user_role`, `remove_user`, `transfer_ownership`, `list_org_roles`, `create_custom_role`, `list_account_labels`, `update_account_labels`, `list_api_keys`, `create_api_key`, `revoke_api_key`
119
119
  - **Scans** (13) — `list_scans`, `get_scan`, `list_scan_children`, `create_scan`, `create_bulk_scans`, `recheck_scans`, `cancel_scan`, `get_scan_screenshot`, `get_scan_creative_screenshot`, `get_scan_landing_screenshot`, `get_scan_creative_html`, `get_scan_creative_video`, `get_scan_vast_xml`
@@ -123,13 +123,13 @@ which decides which credential type minted it.
123
123
  - **Tags** (5) — `list_tags`, `get_tag_definition`, `update_tag_definition`, `delete_tag_definition`, `list_scan_tags`
124
124
  - **Custom rules** (6) — `list_custom_rules`, `get_custom_rule`, `create_custom_rule`, `update_custom_rule`, `delete_custom_rule`, `test_custom_rule`
125
125
  - **Custom taxonomies** (7) — `list_custom_taxonomies`, `get_custom_taxonomy`, `create_custom_taxonomy`, `update_custom_taxonomy`, `delete_custom_taxonomy`, `restore_custom_taxonomy`, `parse_custom_taxonomy_text`
126
- - **Policy sets** (9) — `list_policy_sets`, `get_policy_set`, `create_policy_set`, `update_policy_set`, `delete_policy_set`, `request_policy_set_approval`, `list_policy_set_campaigns`, `attach_policy_set_campaigns`, `detach_policy_set_campaigns`
126
+ - **Policy sets** (10) — `list_policy_sets`, `get_policy_set`, `create_policy_set`, `update_policy_set`, `delete_policy_set`, `request_policy_set_approval`, `unpublish_policy_set`, `list_policy_set_campaigns`, `attach_policy_set_campaigns`, `detach_policy_set_campaigns`
127
127
  - **Alerts** (4) — `list_alerts`, `update_alert_status`, `bulk_update_alert_status`, `get_alert_stats`
128
128
  - **Webhooks** (11) — `list_webhooks`, `get_webhook`, `create_webhook`, `update_webhook`, `delete_webhook`, `list_webhook_event_types`, `list_webhook_deliveries`, `test_webhook`, `rotate_webhook_secret`, `replay_webhook_delivery`, `bulk_replay_webhook`
129
129
  - **Billing** (4) — `get_billing_summary`, `list_usage`, `get_usage_summary`, `list_balance_history`
130
130
  - **Invoicing** (2) — `list_invoices`, `get_invoice_pdf`
131
131
  - **Alert notifications** (5) — `list_alert_destinations`, `delete_alert_destination`, `set_alert_destination_version`, `get_campaign_alert_overrides`, `set_campaign_alert_overrides`
132
- - **Reference data** (2) — `list_geos`, `list_emulators`
132
+ - **Reference data** (3) — `list_geos`, `list_emulators`, `get_proxy_targeting`
133
133
 
134
134
  Screenshots (`get_scan_screenshot`, `get_scan_creative_screenshot`, `get_scan_landing_screenshot`) come back as inline MCP `image` blocks; `get_invoice_pdf` and `get_scan_creative_video` as inline resource blocks — no second fetch, no presigned URL. The two text artifacts (`get_scan_creative_html`, `get_scan_vast_xml`) come back as strings the model can read directly. Every artifact download is size-capped in the gateway — 256 KiB for the text artifacts, 8 MiB for the binary ones — and refused while reading rather than buffered and then rejected.
135
135
 
package/dist/bin.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { NAME, VERSION, err, ok } from './chunk-FQTVDC5D.js';
2
+ import { NAME, VERSION, err, ok } from './chunk-LTUO4SP6.js';
3
3
  import process from 'process';
4
4
  import { z } from 'zod';
5
5
 
@@ -160,10 +160,10 @@ async function main() {
160
160
  }
161
161
  const config = configResult.value;
162
162
  if (config.transport === "stdio") {
163
- const { bootstrapStdio } = await import('./stdio-bootstrap-XZIU2X6Z.js');
163
+ const { bootstrapStdio } = await import('./stdio-bootstrap-LLCHIZ4X.js');
164
164
  return bootstrapStdio(config);
165
165
  }
166
- const { bootstrapHttp } = await import('./http-bootstrap-AYQ47NTT.js');
166
+ const { bootstrapHttp } = await import('./http-bootstrap-PDCTS4IS.js');
167
167
  return bootstrapHttp(config);
168
168
  }
169
169
  main().then(
@@ -3,8 +3,8 @@ export { err, ok } from 'neverthrow';
3
3
 
4
4
  // src/shared/version.ts
5
5
  var NAME = "@kaminari-ad/mcp";
6
- var VERSION = "0.14.0";
6
+ var VERSION = "0.16.0";
7
7
 
8
8
  export { NAME, VERSION };
9
- //# sourceMappingURL=chunk-FQTVDC5D.js.map
10
- //# sourceMappingURL=chunk-FQTVDC5D.js.map
9
+ //# sourceMappingURL=chunk-LTUO4SP6.js.map
10
+ //# sourceMappingURL=chunk-LTUO4SP6.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/shared/version.ts"],"names":[],"mappings":";;;;AASO,IAAM,IAAA,GAAO;AACb,IAAM,OAAA,GAAU","file":"chunk-FQTVDC5D.js","sourcesContent":["/**\n * Package version and name. Hard-coded as constants here, asserted to\n * match `package.json` by a unit test.\n *\n * Why not import `package.json`: it would force JSON-module support at\n * runtime and tsup-bundling would inline the entire manifest. Two\n * constants + one assertion test is simpler and gives the same safety.\n */\n\nexport const NAME = \"@kaminari-ad/mcp\";\nexport const VERSION = \"0.14.0\";\n"]}
1
+ {"version":3,"sources":["../src/shared/version.ts"],"names":[],"mappings":";;;;AASO,IAAM,IAAA,GAAO;AACb,IAAM,OAAA,GAAU","file":"chunk-LTUO4SP6.js","sourcesContent":["/**\n * Package version and name. Hard-coded as constants here, asserted to\n * match `package.json` by a unit test.\n *\n * Why not import `package.json`: it would force JSON-module support at\n * runtime and tsup-bundling would inline the entire manifest. Two\n * constants + one assertion test is simpler and gives the same safety.\n */\n\nexport const NAME = \"@kaminari-ad/mcp\";\nexport const VERSION = \"0.16.0\";\n"]}
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { err, ok } from './chunk-FQTVDC5D.js';
2
+ import { err, ok } from './chunk-LTUO4SP6.js';
3
3
  import { createHash, randomUUID } from 'crypto';
4
4
  import createClient from 'openapi-fetch';
5
5
  import { fetch } from 'undici';
@@ -471,6 +471,15 @@ var GeoResponse = z.object({
471
471
  region: z.string(),
472
472
  tier: z.string()
473
473
  }).passthrough();
474
+ var ProxyTargetingResponse = z.object({
475
+ country_code: z.string(),
476
+ proxy_type: z.string(),
477
+ regions: z.array(z.string()),
478
+ cities: z.array(z.string()),
479
+ isps: z.array(z.string()),
480
+ refreshed_at: z.union([z.string(), z.null()]),
481
+ ttl_seconds: z.number().int()
482
+ }).passthrough();
474
483
  var EmulatorResponse = z.object({
475
484
  id: z.string(),
476
485
  display_name: z.string(),
@@ -1093,13 +1102,14 @@ var AlertNotificationDestinationResponse = z.object({
1093
1102
  updated_at: z.string().datetime({ offset: true })
1094
1103
  }).passthrough();
1095
1104
  z.object({ version: AlertNotificationVersion }).passthrough();
1105
+ var CampaignOverrideMode = z.enum(["inherit", "override", "silence"]);
1096
1106
  var CampaignOverridesResponse = z.object({
1097
1107
  campaign_id: z.string().uuid(),
1098
- mode: z.string(),
1108
+ mode: CampaignOverrideMode,
1099
1109
  destination_ids: z.array(z.string().uuid())
1100
1110
  }).passthrough();
1101
1111
  z.object({
1102
- mode: z.string().min(1).max(16),
1112
+ mode: CampaignOverrideMode,
1103
1113
  destination_ids: z.array(z.string().uuid()).optional().default([])
1104
1114
  }).passthrough();
1105
1115
  var InvoiceType = z.enum(["proforma", "final"]);
@@ -1135,32 +1145,6 @@ z.object({
1135
1145
  limit: z.number().int(),
1136
1146
  pages: z.number().int()
1137
1147
  }).passthrough();
1138
- z.object({
1139
- name: z.string().min(2).max(100),
1140
- email: z.string().email(),
1141
- message: z.string().min(10).max(2e3),
1142
- source: z.string().max(512).optional().default("")
1143
- }).passthrough();
1144
- z.object({
1145
- id: z.string().uuid(),
1146
- received_at: z.string().datetime({ offset: true })
1147
- }).passthrough();
1148
- var PreferredContactChannel = z.enum(["telegram", "whatsapp", "email"]);
1149
- z.object({
1150
- first_name: z.string().min(2).max(60),
1151
- last_name: z.string().min(2).max(60),
1152
- company_email: z.string().email(),
1153
- company_name: z.string().min(2).max(120),
1154
- preferred_channel: PreferredContactChannel,
1155
- contact_handle: z.string().max(120).optional().default(""),
1156
- comment: z.string().max(2e3).optional().default(""),
1157
- privacy_accepted: z.boolean(),
1158
- source: z.string().max(512).optional().default("")
1159
- }).passthrough();
1160
- z.object({
1161
- id: z.string().uuid(),
1162
- received_at: z.string().datetime({ offset: true })
1163
- }).passthrough();
1164
1148
  var CustomTaxonomyListItem = z.object({
1165
1149
  id: z.string().uuid(),
1166
1150
  name: z.string(),
@@ -1230,6 +1214,7 @@ var schemas = {
1230
1214
  ScanResponse,
1231
1215
  ScanBriefResponse,
1232
1216
  GeoResponse,
1217
+ ProxyTargetingResponse,
1233
1218
  EmulatorResponse,
1234
1219
  CampaignGroupResponse,
1235
1220
  GroupActionResponse,
@@ -1261,6 +1246,7 @@ var schemas = {
1261
1246
  DeliveryAttemptResponse,
1262
1247
  BulkReplayResponse,
1263
1248
  AlertNotificationDestinationResponse,
1249
+ CampaignOverrideMode,
1264
1250
  CampaignOverridesResponse,
1265
1251
  InvoiceResponse,
1266
1252
  CustomTaxonomyListItem,
@@ -1779,6 +1765,18 @@ var PolicySetListItemSchema = schemas.PolicySetListItem.pick({
1779
1765
  }).strip();
1780
1766
  var parsePolicySetPage = (raw) => parsePagedWithItemSchema(PolicySetListItemSchema, raw, "policy-sets");
1781
1767
 
1768
+ // src/infrastructure/api/parsers/parse-proxy-targeting.ts
1769
+ var ProxyTargetingSchema = schemas.ProxyTargetingResponse.pick({
1770
+ country_code: true,
1771
+ proxy_type: true,
1772
+ regions: true,
1773
+ cities: true,
1774
+ isps: true,
1775
+ refreshed_at: true,
1776
+ ttl_seconds: true
1777
+ }).strip();
1778
+ var parseProxyTargeting = (raw) => parseWithSchema(ProxyTargetingSchema, raw, "proxy targeting");
1779
+
1782
1780
  // src/infrastructure/api/parsers/parse-run.ts
1783
1781
  var RunSchema = schemas.RunResponse.pick({
1784
1782
  id: true,
@@ -2254,6 +2252,9 @@ function createHttpApiGateway(config) {
2254
2252
  async listEmulators() {
2255
2253
  return call("GET", "/api/v1/emulators", {}, parseEmulatorList);
2256
2254
  },
2255
+ async getProxyTargeting(query) {
2256
+ return call("GET", "/api/v1/proxy/targeting", { params: { query } }, parseProxyTargeting);
2257
+ },
2257
2258
  // ── Campaigns ─────────────────────────────────────────────────
2258
2259
  async listCampaigns(filters) {
2259
2260
  return call("GET", "/api/v1/campaigns", { params: { query: filters } }, parseCampaignPage);
@@ -2556,6 +2557,14 @@ function createHttpApiGateway(config) {
2556
2557
  parseEmpty
2557
2558
  );
2558
2559
  },
2560
+ async unpublishPolicySet(id) {
2561
+ return call(
2562
+ "POST",
2563
+ "/api/v1/policy-sets/{policy_set_id}/unpublish",
2564
+ { params: { path: { policy_set_id: id } } },
2565
+ parseEmpty
2566
+ );
2567
+ },
2559
2568
  // ── Custom taxonomies ─────────────────────────────────────────
2560
2569
  async listCustomTaxonomies(filters) {
2561
2570
  return call(
@@ -3288,12 +3297,7 @@ var setAlertDestinationVersionTool = {
3288
3297
  };
3289
3298
  var SetCampaignAlertOverridesInputShape = {
3290
3299
  campaign_id: z.string().uuid().describe("Campaign UUID."),
3291
- // enum-drift: allow — the API validates these three values but types
3292
- // the field as a plain string, so no generated schema constrains it.
3293
- // Swap to `schemas.CampaignOverrideMode` after the api enum change
3294
- // (`fix/type-override-mode-and-unlist-forms`) reaches prod and the
3295
- // next `make gen-api-types` picks it up.
3296
- mode: z.enum(["inherit", "override", "silence"]).describe(
3300
+ mode: schemas.CampaignOverrideMode.describe(
3297
3301
  "Routing mode: `inherit` (fall back to the org-wide destinations), `override` (route ONLY to `destination_ids`), `silence` (send nothing for this campaign)."
3298
3302
  ),
3299
3303
  destination_ids: z.array(z.string().uuid()).max(50).default([]).describe(
@@ -3920,10 +3924,18 @@ var campaignConfigFields = {
3920
3924
  emulator_mode: z.enum(["random", "all"]).optional().describe(
3921
3925
  "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."
3922
3926
  ),
3923
- proxy_type: z.enum(["residential", "mobile"]).optional().describe("Proxy network type. Default: residential."),
3924
- proxy_region: z.string().optional().describe("Proxy region/state targeting (free-text; only honoured for a single country)."),
3925
- proxy_city: z.string().optional().describe("Proxy city targeting."),
3926
- proxy_isp: z.string().optional().describe("Proxy ISP targeting."),
3927
+ proxy_type: z.enum(["residential", "mobile"]).optional().describe(
3928
+ "Proxy network type. Default: residential. Residential and mobile are separate pools with separate catalogues, so pass the same value to `get_proxy_targeting` that you send here."
3929
+ ),
3930
+ proxy_region: z.string().optional().describe(
3931
+ "Proxy region/state; use a value from `get_proxy_targeting` for one of this campaign's countries. Only honoured when the campaign targets a single country."
3932
+ ),
3933
+ proxy_city: z.string().optional().describe(
3934
+ "Proxy city from `get_proxy_targeting`. If you also set `proxy_region`, take the city from a call made with that same region \u2014 a city from a different region passes validation but leaves the provider no exit node."
3935
+ ),
3936
+ proxy_isp: z.string().optional().describe(
3937
+ "Proxy ISP, or mobile carrier when `proxy_type` is mobile. Use a value from `get_proxy_targeting`."
3938
+ ),
3927
3939
  ...repeatRetryFields,
3928
3940
  schedule_type: z.enum(["weekly", "interval"]).optional().describe(
3929
3941
  "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."
@@ -5055,6 +5067,26 @@ var requestPolicySetApprovalTool = {
5055
5067
  return ok({ requested: true });
5056
5068
  }
5057
5069
  };
5070
+ var UnpublishPolicySetInputShape = {
5071
+ policy_set_id: z.string().uuid().describe("Policy set UUID.")
5072
+ };
5073
+ var unpublishPolicySetTool = {
5074
+ name: "unpublish_policy_set",
5075
+ description: "Return one of your organization's policy sets to PRIVATE. Inverse of request_policy_set_approval: removes an approved set from the shared catalog other organizations browse, or withdraws a publication request that is still awaiting review. The set stays fully usable inside your own organization. Campaigns in other organizations that already reference the set keep their binding \u2014 going private blocks new attachments rather than breaking existing ones. Safe to call on a set that is already private.",
5076
+ annotations: {
5077
+ title: "Unpublish Policy Set",
5078
+ readOnlyHint: false,
5079
+ destructiveHint: true,
5080
+ idempotentHint: true,
5081
+ openWorldHint: false
5082
+ },
5083
+ inputSchema: z.object(UnpublishPolicySetInputShape),
5084
+ handler: async (input, ctx) => {
5085
+ const result = await ctx.api.unpublishPolicySet(input.policy_set_id);
5086
+ if (result.isErr()) return err(mapApiError(result.error));
5087
+ return ok({ unpublished: true });
5088
+ }
5089
+ };
5058
5090
  var UpdatePolicySetInputShape = {
5059
5091
  policy_set_id: z.string().uuid().describe("Policy set UUID."),
5060
5092
  name: z.string().min(1).max(200).describe("New name (always required by the API on update)."),
@@ -5084,6 +5116,38 @@ var updatePolicySetTool = {
5084
5116
  return ok(result.value);
5085
5117
  }
5086
5118
  };
5119
+ var GetProxyTargetingInputShape = {
5120
+ country_code: z.string().length(2).describe("ISO 3166-1 alpha-2 country code, e.g. 'US'. Case-insensitive."),
5121
+ proxy_type: z.enum(["residential", "mobile"]).optional().describe(
5122
+ "Which network to describe. Defaults to residential. Pass 'mobile' when the scan will use mobile: the two are separate pools with different catalogues, so a residential ISP is normally rejected for a mobile scan."
5123
+ ),
5124
+ region: z.string().optional().describe(
5125
+ "Narrow the returned cities to one region. Pass a value from this tool's own 'regions' array. Omit it for the country-wide city list."
5126
+ )
5127
+ };
5128
+ var getProxyTargetingTool = {
5129
+ name: "get_proxy_targeting",
5130
+ description: "List the proxy regions, cities, and ISPs accepted for a country. Call this before setting proxy targeting on create_scan, create_bulk_scans, create_campaign, or update_campaign \u2014 the accepted values come from the upstream network provider and cannot be listed in those tools' schemas, so a guess is a 422. Anything listed here is accepted. Values are ordered by pool size, largest first; prefer values near the front. Two traps: residential and mobile are separate pools, so pass the proxy_type the scan will use; and without a region the cities are the whole country's, so if you target a region, call again with that region and take the city from that narrower list \u2014 a region and a city that do not belong together are accepted but leave the provider no exit node.",
5131
+ annotations: {
5132
+ title: "Get Proxy Targeting",
5133
+ readOnlyHint: true,
5134
+ destructiveHint: false,
5135
+ idempotentHint: true,
5136
+ openWorldHint: false
5137
+ },
5138
+ inputSchema: z.object(GetProxyTargetingInputShape),
5139
+ handler: async (input, ctx) => {
5140
+ const result = await ctx.api.getProxyTargeting({
5141
+ country_code: input.country_code,
5142
+ ...input.proxy_type !== void 0 && { proxy_type: input.proxy_type },
5143
+ ...input.region !== void 0 && { region: input.region }
5144
+ });
5145
+ if (result.isErr()) {
5146
+ return err(mapApiError(result.error));
5147
+ }
5148
+ return ok(result.value);
5149
+ }
5150
+ };
5087
5151
  var CancelRunInputShape = { run_id: z.string().uuid().describe("Run UUID.") };
5088
5152
  var cancelRunTool = {
5089
5153
  name: "cancel_run",
@@ -5166,11 +5230,21 @@ var cancelScanTool = {
5166
5230
  }
5167
5231
  };
5168
5232
  var scanProxyField = z.object({
5169
- proxy_type: z.enum(["residential", "mobile"]).optional().describe("Proxy network type. Default: residential."),
5170
- region: z.string().optional().describe("Proxy region/state targeting."),
5171
- city: z.string().optional().describe("Proxy city targeting."),
5172
- isp: z.string().optional().describe("Proxy ISP targeting.")
5173
- }).optional().describe("Optional proxy geo targeting for the crawl. Omit to use the org default.");
5233
+ proxy_type: z.enum(["residential", "mobile"]).optional().describe(
5234
+ "Proxy network type. Default: residential. Residential and mobile are separate pools with separate catalogues, so pass the same value to `get_proxy_targeting` that you send here."
5235
+ ),
5236
+ region: z.string().optional().describe(
5237
+ "Proxy region/state. Use a value from `get_proxy_targeting`; anything else is rejected with 422."
5238
+ ),
5239
+ city: z.string().optional().describe(
5240
+ "Proxy city. Use a value from `get_proxy_targeting`. If you also set `region`, take the city from a `get_proxy_targeting` call made with that same region \u2014 a city from a different region passes validation but leaves the provider no exit node."
5241
+ ),
5242
+ isp: z.string().optional().describe(
5243
+ "Proxy ISP, or mobile carrier when `proxy_type` is mobile. Use a value from `get_proxy_targeting`."
5244
+ )
5245
+ }).optional().describe(
5246
+ "Optional proxy geo targeting for the crawl. Omit to use the org default. Call `get_proxy_targeting` for the country first: the accepted values come from the upstream network provider and cannot be listed in this schema."
5247
+ );
5174
5248
  var scanReferrerField = z.string().url().regex(/^https?:\/\//i, "Must be an http(s) URL.").max(2048).optional().describe(
5175
5249
  "Optional http(s) page URL the check is performed from. With `ad_tag` or `vast_tag` this is the publisher page the tag is embedded in \u2014 NOT the creative and NOT its landing page; the browser commits the page document on this URL without fetching the publisher, so the creative renders as if embedded there, and its cross-origin subrequests receive the origin only (https://publisher.example/, no path). With a `url` target this is where the visitor came from, sent in full as the Referer of the page request."
5176
5250
  );
@@ -5599,7 +5673,7 @@ var GetTagDefinitionInputShape = {
5599
5673
  };
5600
5674
  var getTagDefinitionTool = {
5601
5675
  name: "get_tag_definition",
5602
- description: "Get full definition of one tag: display name, description, severity, category, source (system vs custom), public-report visibility, usage counts, plus `linked_rules` \u2014 the custom rules currently producing this tag (id, name, active flag). Fetch a specific rule's full config via `get_custom_rule`.",
5676
+ description: "Get full definition of one tag: display name, description, severity, category, source (system vs custom), public-report visibility, usage counts, plus `linked_rules` \u2014 the custom rules currently producing this tag (id, name, active flag). Fetch a specific rule's full config via `get_custom_rule`. Returns 404 for a retired (archived) tag this organization does not own, including every archived system tag.",
5603
5677
  annotations: {
5604
5678
  title: "Get Tag Definition",
5605
5679
  readOnlyHint: true,
@@ -5619,7 +5693,7 @@ var ListScanTagsInputShape = {
5619
5693
  };
5620
5694
  var listScanTagsTool = {
5621
5695
  name: "list_scan_tags",
5622
- description: "List every tag (system + custom) attached to one scan by the checker pipeline, with display name, category, and severity.",
5696
+ description: "List every tag (system + custom) attached to one scan by the checker pipeline, with display name, category, and severity. Tags whose definition has since been archived are omitted \u2014 the scan can therefore report fewer tags than when it ran.",
5623
5697
  annotations: {
5624
5698
  title: "List Scan Tags",
5625
5699
  readOnlyHint: true,
@@ -5639,12 +5713,12 @@ var ListTagsInputShape = {
5639
5713
  "Filter to tags in one category slug (e.g. 'security', 'malware', 'compliance'). Use the slug exactly as it appears in `category` of an existing tag."
5640
5714
  ),
5641
5715
  include_archived: z.boolean().optional().describe(
5642
- "Include archived tag definitions, which are excluded by default. Archived tags still appear on the scans that carry them, so pass `true` when resolving a slug seen on an older scan."
5716
+ "Also return the archived tag definitions this organization owns, which are excluded by default. An archived tag is retired: it is no longer assigned to scans and no longer raises alerts, and archived platform (system) tags are never returned at all \u2014 so this flag cannot resolve a slug seen on an older scan."
5643
5717
  )
5644
5718
  };
5645
5719
  var listTagsTool = {
5646
5720
  name: "list_tags",
5647
- 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, and pass `include_archived` to also see retired definitions.",
5721
+ description: "List every tag definition in service for this organization (system tags + organization custom tags) with category, severity, visibility, and usage counters (scans + rules per tag). Optionally filter by category, and pass `include_archived` to also see the retired definitions this organization owns.",
5648
5722
  annotations: {
5649
5723
  title: "List Tags",
5650
5724
  readOnlyHint: true,
@@ -5980,6 +6054,7 @@ function registerAllTools(register) {
5980
6054
  register(createApiKeyTool);
5981
6055
  register(revokeApiKeyTool);
5982
6056
  register(listGeosTool);
6057
+ register(getProxyTargetingTool);
5983
6058
  register(listEmulatorsTool);
5984
6059
  register(getScanTool);
5985
6060
  register(listScanChildrenTool);
@@ -6041,6 +6116,7 @@ function registerAllTools(register) {
6041
6116
  register(updatePolicySetTool);
6042
6117
  register(deletePolicySetTool);
6043
6118
  register(requestPolicySetApprovalTool);
6119
+ register(unpublishPolicySetTool);
6044
6120
  register(listPolicySetCampaignsTool);
6045
6121
  register(attachPolicySetCampaignsTool);
6046
6122
  register(detachPolicySetCampaignsTool);
@@ -6150,5 +6226,5 @@ function formatToolError(error) {
6150
6226
  }
6151
6227
 
6152
6228
  export { BearerToken, SERVER_INSTRUCTIONS, createHttpApiGateway, createPinoLogger, declareEmptyResourcesAndPrompts, newRequestId, wireToolsIntoMcpServer };
6153
- //# sourceMappingURL=chunk-CUJWJSNJ.js.map
6154
- //# sourceMappingURL=chunk-CUJWJSNJ.js.map
6229
+ //# sourceMappingURL=chunk-YYS2HD6K.js.map
6230
+ //# sourceMappingURL=chunk-YYS2HD6K.js.map