@kaminari-ad/mcp 0.13.0 → 0.14.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,97 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.14.0] - 2026-08-23
11
+
12
+ Resyncs the tool surface with `/api/v1` (KAMIAD-158). The generated
13
+ types were already current; the hand-written gateway and tool schemas
14
+ were not, so this release is mostly about the drift between them — plus
15
+ two gates so the next one fails CI instead of reaching a user.
16
+
17
+ ### Fixed
18
+
19
+ - **Artifact downloads were unbounded.** `binaryGet` called
20
+ `arrayBuffer()` with no ceiling, so one oversized MediaFile decided
21
+ how much memory the hosted server used — the API caps none of these
22
+ endpoints. It now refuses on an oversized `content-length` and
23
+ counts bytes while reading the stream, cancelling past the limit
24
+ (256 KiB text, 8 MiB binary). The cap covers the screenshot and
25
+ invoice-PDF tools too, which had the same exposure.
26
+ - **`attach_policy_set_campaigns` described a side effect it does not
27
+ have.** A campaign belongs to exactly one policy set, so attaching
28
+ one that is bound elsewhere MOVES it; the tool said existing
29
+ bindings survive. Now flagged `destructiveHint: true` and the
30
+ description says so.
31
+ - **`bulk_update_alert_status` forwarded `filter_*` alongside `ids`**,
32
+ which the API rejects. Refused locally with the offending field
33
+ names.
34
+ - **`list_campaign_groups` advertised `last_run_at`** while the
35
+ response projection stripped it, so the new last-run filters could
36
+ not be interpreted. Projected now.
37
+ - **`set_campaign_alert_overrides` accepted values the API rejects.**
38
+ `mode` was `inherit | include | exclude`; the API has only ever
39
+ accepted `inherit | override | silence`. Every value the schema
40
+ allowed was refused upstream and every value the API wanted was
41
+ refused locally, so an agent could not change campaign alert routing
42
+ at all. `mode` now matches the API, `destination_ids` is refused
43
+ locally unless `mode` is `override` (mirroring the API's
44
+ `notifications.invalid_override_combination`), and the setter's
45
+ description explains that "route everywhere except these" does not
46
+ exist — invert the list under `override` instead.
47
+ - **`list_custom_taxonomies` claimed soft-deleted taxonomies were
48
+ listed.** They are excluded unless the new `include_inactive` is
49
+ `true`, so an agent looking for one to restore concluded it was gone.
50
+ - `list_scans` now documents `rechecking` as a filterable status.
51
+
52
+ ### Added
53
+
54
+ - **`bulk_update_alert_status`** — `POST /api/v1/alerts/bulk-status`.
55
+ Select with `ids` (max 1000) or `all_matching: true` plus `filter_*`
56
+ fields; returns `updated` / `skipped`.
57
+ - **`list_policy_set_campaigns`**, **`attach_policy_set_campaigns`**,
58
+ **`detach_policy_set_campaigns`** — policy-set membership, which was
59
+ previously not editable through MCP at all: `update_policy_set` sends
60
+ only name, description and rules. Attach warns that a campaign bound
61
+ to another set is moved rather than rejected, since a campaign can
62
+ belong to only one set.
63
+ - **`get_scan_creative_html`**, **`get_scan_vast_xml`** — creative
64
+ markup and the resolved VAST document, returned as text the model
65
+ reads directly. **`get_scan_creative_video`** — the MP4 as a resource
66
+ block. All three refuse oversized payloads (256 KiB text, 8 MiB
67
+ binary) rather than inlining them.
68
+ - Filters the API had grown and the tools never picked up:
69
+ `list_alerts` and `get_alert_stats` take `policy_set_id`, `tag`,
70
+ `country_code`, `date_from`, `date_to`, `timezone` (`get_alert_stats`
71
+ previously took no arguments at all, so its counts could not be
72
+ reconciled with a filtered list); `list_scans` takes `tag_match` and
73
+ `parent_scan_id`; `list_campaigns` and `list_campaign_groups` take
74
+ `created_from`/`created_to`/`last_run_from`/`last_run_to`/`timezone`,
75
+ and `list_campaign_groups` also takes `q`; `list_tags` takes
76
+ `include_archived`.
77
+ - **`check:api-coverage`** — asserts every `/api/v1` path + method in
78
+ the generated `openapi.ts` is reachable from the gateway, and that
79
+ each of their query parameters is representable by the gateway
80
+ method. The parameter half is the point: a path-only gate would have
81
+ passed on the state this release fixes.
82
+ - **`check:tool-enum-drift`** — every `z.enum` in a tool must have the
83
+ same value set as a generated enum, or carry a justified exemption.
84
+ Reintroducing the `mode` bug now fails CI. Equality rather than
85
+ subset: with 68 values across the generated enums, "subset of any"
86
+ accepted almost anything (`z.enum(["open"])` passed as a subset of
87
+ `AlertStatus`). A `z.enum` whose argument is not an inline array of
88
+ literals is reported rather than skipped, since an unreadable enum
89
+ is how a wrong value set would slip past.
90
+
91
+ ### Changed
92
+
93
+ - `npm run gen:api-types` formats its output with Prettier. The raw
94
+ generator emits 4-space TypeScript while `format:check` covers
95
+ `src/**`, so a no-op regen used to produce a ~17k-line indentation
96
+ diff and a failing gate.
97
+ - `.prettierignore` no longer lists `src/infrastructure/api/openapi.ts`
98
+ — the generated files moved to `src/shared/api/` long ago, so the
99
+ entry was dead and pointed at the wrong policy.
100
+
10
101
  ## [0.13.0] - 2026-08-20
11
102
 
12
103
  ### Added
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 98 tools exposed.
66
+ Restart your client. You should see `kaminari-ad` in the MCP servers list with 105 tools exposed.
67
67
 
68
68
  ### 2b. Hosted HTTP transport (no install)
69
69
 
@@ -113,34 +113,34 @@ which decides which credential type minted it.
113
113
 
114
114
  ## Tools
115
115
 
116
- 98 tools mirroring most of 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
+ 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:
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
- - **Scans** (10) — `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`
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`
120
120
  - **Campaigns** (10) — `list_campaigns`, `list_campaigns_picker`, `get_campaign`, `create_campaign`, `update_campaign`, `archive_campaign`, `unarchive_campaign`, `cancel_campaign`, `run_campaign`, `list_campaign_runs`
121
121
  - **Campaign groups** (10) — list/get/create/update/run/cancel/archive/unarchive + `pause_campaign_group_schedule`, `resume_campaign_group_schedule`
122
122
  - **Runs** (3) — `get_run`, `list_run_scans`, `cancel_run` (use `list_campaign_runs` to enumerate runs of a campaign — the API has no standalone `/runs` index)
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** (6) — `list_policy_sets`, `get_policy_set`, `create_policy_set`, `update_policy_set`, `delete_policy_set`, `request_policy_set_approval`
127
- - **Alerts** (3) — `list_alerts`, `update_alert_status`, `get_alert_stats`
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`
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
132
  - **Reference data** (2) — `list_geos`, `list_emulators`
133
133
 
134
- Screenshots (`get_scan_screenshot`, `get_scan_creative_screenshot`, `get_scan_landing_screenshot`) come back as inline MCP `image` blocks and `get_invoice_pdf` as an inline resource block — no second fetch, no presigned URL.
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
 
136
- Not exposed (intentionally): the raw creative artifacts (`creative-html`, `creative-video`, `vast-xml`) and the public marketing forms (`/contact`, `/demo-inquiries`). Open an issue if you need one of those.
136
+ Not exposed (intentionally): the public marketing forms, which are anonymous intake for kaminari.ad itself rather than an agent capability.
137
137
 
138
138
  ## Example agent prompts
139
139
 
140
140
  These three prompts each exercise a different cross-section of tools and demonstrate the typical agent workflow:
141
141
 
142
142
  1. **"Scan https://news.example.com/article-promo across US, UK, DE on mobile profiles, flag anything that redirects to a paywall."** Touches `list_emulators` → `create_bulk_scans` → wait → `list_scans` (status=completed) → `get_scan` → `list_scan_tags`.
143
- 2. **"Create a campaign that re-checks the homepage of brand-x.com every hour from JP and US; alert me on Slack if it ever shows a malware tag."** Touches `list_emulators` → `list_policy_sets` (find one with `malware`) → `create_campaign` (schedule_enabled=true) → `list_alert_destinations` → `set_campaign_alert_overrides`.
143
+ 2. **"Create a campaign that re-checks the homepage of brand-x.com every hour from JP and US; alert me on Slack if it ever shows a malware tag."** Touches `list_emulators` → `list_policy_sets` (find one with `malware`) → `create_campaign` (schedule_enabled=true) → `attach_policy_set_campaigns` → `list_alert_destinations` → `set_campaign_alert_overrides` (`mode: "override"` with the Slack destination).
144
144
  3. **"What did I spend on ad verification last month, and which campaigns drove the cost?"** Touches `get_usage_summary` → `list_usage` (with date_from/date_to) → group by `scan_id` → `get_scan` → `get_campaign` for attribution.
145
145
 
146
146
  Full machine-readable tool listing is exposed by the server itself — connect with any MCP client and call `tools/list`.
package/dist/bin.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { NAME, VERSION, err, ok } from './chunk-VFDW3UL2.js';
2
+ import { NAME, VERSION, err, ok } from './chunk-FQTVDC5D.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-VM6OOG2O.js');
163
+ const { bootstrapStdio } = await import('./stdio-bootstrap-XZIU2X6Z.js');
164
164
  return bootstrapStdio(config);
165
165
  }
166
- const { bootstrapHttp } = await import('./http-bootstrap-ROIMZVK2.js');
166
+ const { bootstrapHttp } = await import('./http-bootstrap-AYQ47NTT.js');
167
167
  return bootstrapHttp(config);
168
168
  }
169
169
  main().then(