@kaminari-ad/mcp 0.1.5 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,173 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.1] - 2026-05-18
11
+
12
+ Patch release — clear Node-version error message, correct `engines`
13
+ declaration, and the default API URL is now the real API host. No
14
+ tool surface change.
15
+
16
+ ### Fixed
17
+
18
+ - **Default `KAMINARI_AD_API_URL` pointed at the wrong host.**
19
+ v0.2.0 defaulted to `https://kaminari.ad`, which is the marketing
20
+ landing page — `kaminari.ad` does NOT serve `/api/v1/*` routes
21
+ (returns HTTP 404 for every tool call). The actual API host is
22
+ `https://app.kaminari.ad` (note the `app.` subdomain).
23
+ Users who set `KAMINARI_AD_API_URL` explicitly were unaffected;
24
+ users who relied on the default got 404 on every tool call. The
25
+ internal `gen-api-types` script already used the correct host
26
+ (`app.kaminari.ad`) for OpenAPI generation — only the runtime
27
+ default drifted. Now corrected here, in `.env.example`, and pinned
28
+ by a unit test against future regressions.
29
+ - **Cryptic `webidl.util.markAsUncloneable is not a function`
30
+ startup crash on Node < 22.19.** The underlying `undici@8.x`
31
+ removed feature probes in v8.0.3 and now imports `markAsUncloneable`
32
+ unconditionally — that symbol only exists on Node 22.19+. v0.2.0
33
+ declared `engines.node = ">=22.13.0"`, so npm warned but did not
34
+ block install on Node 22.13–22.18 or Node 20; users hit the
35
+ cryptic webidl error at first invocation.
36
+ - `engines.node` bumped to `>=22.19.0` to match the real floor.
37
+ Consumers with `engine-strict=true` (or `npm install
38
+ --engine-strict`) are now blocked at install time with a clear
39
+ `EBADENGINE` message.
40
+ - New runtime preflight in `bin.ts::main()` catches the case where
41
+ install slipped through (npx pulls fresh on every run; npx does
42
+ not honour `engine-strict` by default). Prints a clean message
43
+ and exits with code 2, BEFORE any dynamic import pulls undici:
44
+
45
+ ```
46
+ @kaminari-ad/mcp requires Node.js >=22.19.0 (you have v20.x.x).
47
+ The underlying undici 8.x HTTP client uses markAsUncloneable
48
+ from node:worker_threads, available only on Node 22.19+.
49
+ Older Node crashes at import time with the cryptic message
50
+ `webidl.util.markAsUncloneable is not a function`.
51
+
52
+ Please upgrade Node and re-run: https://nodejs.org/en/download
53
+ ```
54
+
55
+ ### Internal
56
+
57
+ - `tsup.config.ts` flipped to `splitting: true`. Required for the
58
+ preflight to actually run before undici loads: without splitting
59
+ esbuild inlines every dynamic `await import("./presentation/...")`
60
+ call into the top-level bundle, eagerly importing undici/MCP SDK/
61
+ pino at startup. With splitting on, transport bootstraps stay as
62
+ separate chunks loaded only after `main()` runs the Node check.
63
+ - `scripts/check-bundle-size.ts` rewritten to aggregate every
64
+ runtime `.js` chunk under `dist/` instead of just `dist/bin.js`.
65
+ With splitting on, `bin.js` is a thin ~5 KB preflight + dispatch
66
+ shim and the actual shipping cost lives in the transport / vendor
67
+ chunks. Total artifact: 173 KB across 5 chunks (smaller than
68
+ v0.2.0's 218 KB monolithic bundle thanks to tree-shaking now seeing
69
+ each chunk in isolation). Limit unchanged at 500 KB.
70
+ - `src/shared/check-node-version.ts` — pure function extracted from
71
+ `bin.ts` so the version preflight can be unit-tested without
72
+ `process.exit` side effects. Test pins all boundary cases (22.18 /
73
+ 22.19 / 22.20 / 23.x / 20.x / prerelease tags / garbage input).
74
+
75
+ ## [0.2.0] - 2026-05-17
76
+
77
+ Comprehensive parser-drift sweep across all `/api/v1/*` list
78
+ endpoints, a new tool for slim campaign selection, an MCP-client
79
+ interop fix, and small docs polish. Semver-minor because three list
80
+ tools changed output shape (breaking) and one new tool was added.
81
+
82
+ The MCP audit covered every `/api/v1/*` list endpoint. The MCP
83
+ already wrapped 21 of them — 18 were correctly wired, 3 had drift
84
+ (now fixed: `list_run_scans`, `list_custom_rules`, `list_policy_sets`).
85
+ One additional list endpoint (`/campaigns/picker`) had no MCP wrapper
86
+ and was added as the new `list_campaigns_picker` tool. The detail
87
+ endpoint `get_tag_definition` was also fixed to surface
88
+ `linked_rules`. See [MIGRATION_0_2.md](https://github.com/kaminari-ad/mcp/blob/main/MIGRATION_0_2.md)
89
+ for the agent-side migration notes.
90
+
91
+ ### Breaking
92
+
93
+ - **`list_run_scans` output element type:** `ScanBriefResponse` →
94
+ `ScanTileResponse`. The slim DTO drops `url` / `created_at` /
95
+ `labels` / `campaign_id` / `campaign_name` / `is_ad_tag` and adds
96
+ `error`. The tool no longer fails on prod data with `malformed
97
+ scans page: items.0.url: Required`; agents needing full scan
98
+ details should call `get_scan` per tile.
99
+ - **`list_custom_rules` output shape:** `readonly CustomRuleResponse[]`
100
+ → `PaginatedResponse<CustomRuleResponse>`. New optional
101
+ `page` / `limit` inputs (defaults `1` / `50`). Previously the tool
102
+ silently dropped pagination metadata; orgs with > 50 rules
103
+ appeared to have exactly 50.
104
+ - **`list_policy_sets` output shape:** `readonly PolicySetListItemResponse[]`
105
+ → `PaginatedResponse<PolicySetListItemResponse>`. New optional
106
+ `page` / `limit` inputs (defaults `1` / `50`). Same pagination
107
+ drop as `list_custom_rules`.
108
+
109
+ ### Fixed
110
+
111
+ - **`list_run_scans` failed with `malformed scans page:
112
+ items.0.url: Required`.** Parser wired the wrong DTO
113
+ (`ScanBriefResponse`) for an endpoint that returns
114
+ `ScanTileResponse`. Same drift class as `list_policy_sets` v0.1.1.
115
+ - **`list_custom_rules` / `list_policy_sets` silently dropped
116
+ pagination metadata.** Now expose `total` / `page` / `limit` so
117
+ agents iterate correctly past the default page size.
118
+ - **`get_tag_definition` now returns `linked_rules`** (the custom
119
+ rules currently producing this tag — `id` / `name` / `is_active`).
120
+ Previously silently dropped because the parser reused the list-row
121
+ schema. Agents no longer need to grep `list_custom_rules` by
122
+ `tag_slug` themselves.
123
+ - **`invalid-input` (HTTP 400 / 422) error responses now preserve
124
+ the API's machine-readable `code` field.** Forward-compat for
125
+ `delete_policy_set` growing `code: "policies.in_use"` — agents
126
+ can branch programmatically as soon as the API ships the code,
127
+ no MCP release required.
128
+
129
+ ### Added
130
+
131
+ - **`list_campaigns_picker`** (83rd tool — was 82, now 83) — slim
132
+ per-row campaign list for selection UIs (id, name, group_id,
133
+ is_archived). Cheaper than `list_campaigns` for orgs with thousands
134
+ of campaigns. Use `get_campaign(id)` after a selection.
135
+ - **Server now declares empty `resources` and `prompts` capabilities
136
+ with handlers returning `[]`.** Cursor / Claude Desktop / Cline
137
+ probe these at session start; previously the SDK responded with
138
+ `-32601 Method not found`, which Cursor's client mistranslated
139
+ as a misleading `"MCP error -32000: Connection closed"` warning.
140
+ Functional impact was zero (all tools remained callable) but
141
+ downstream agent logs filled with false positives once per session
142
+ per server. Now clean.
143
+
144
+ ### Docs
145
+
146
+ - **README:** replaced the fictional `kad_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`
147
+ API-key placeholder with `<your-kaminari-ad-api-key>` + a note
148
+ that keys are opaque (no required prefix or fixed length).
149
+ - **`create_custom_rule` / `update_custom_rule` / `test_custom_rule`:**
150
+ the `target` parameter description no longer claims a stale
151
+ `page | offer_url | html` enum (OpenAPI declares it as a freeform
152
+ string; `html` may not be currently valid). Defers to API docs.
153
+ - **`delete_policy_set`:** description now explicitly mentions the
154
+ API returns `HTTP 400` if campaigns are bound, and suggests the
155
+ unbind-via-`update_campaign` workflow before retry.
156
+
157
+ ### Internal
158
+
159
+ - Comprehensive parser-drift audit across all `/api/v1/*` list
160
+ endpoints (run via parallel `explore` subagents covering API + MCP
161
+ sides). 21 endpoints already had MCP wrappers — 18 clean, 3 with
162
+ drift (now fixed); 1 additional list endpoint had no wrapper and
163
+ was added as a new tool. No remaining drift.
164
+ - New parser modules: `parse-run-scan-page.ts`, `parse-custom-rule-page.ts`,
165
+ `parse-policy-set-page.ts`, `parse-campaign-picker.ts`. The old
166
+ defensive bare-or-envelope helpers (`parseCustomRuleArray`,
167
+ `parsePolicySetList`) are gone; their files now expose only the
168
+ per-entity parsers. `parseTagDetail` moved out of `parse-generic.ts`
169
+ into `parse-tag.ts` because it now needs the detail schema's
170
+ `linked_rules` field which the list schema doesn't have.
171
+ - New shared helper `presentation/shared/declare-empty-caps.ts`
172
+ used by both stdio and HTTP bootstraps. Unit test against an
173
+ in-memory `Client` + `InMemoryTransport.createLinkedPair()` pair,
174
+ plus an end-to-end probe in the CLI smoke that hits a real HTTP
175
+ RPC after the `initialize` handshake.
176
+
10
177
  ## [0.1.5] - 2026-05-17
11
178
 
12
179
  Re-release of v0.1.4 — the Corepack-based npm upgrade in v0.1.4
@@ -469,7 +636,9 @@ Initial public release. The first version that ships to npm under
469
636
  need them.
470
637
  - Invoice PDF fetcher — same reason.
471
638
 
472
- [Unreleased]: https://github.com/kaminari-ad/mcp/compare/v0.1.5...HEAD
639
+ [Unreleased]: https://github.com/kaminari-ad/mcp/compare/v0.2.1...HEAD
640
+ [0.2.1]: https://github.com/kaminari-ad/mcp/compare/v0.2.0...v0.2.1
641
+ [0.2.0]: https://github.com/kaminari-ad/mcp/compare/v0.1.5...v0.2.0
473
642
  [0.1.5]: https://github.com/kaminari-ad/mcp/compare/v0.1.0...v0.1.5
474
643
  [0.1.4]: https://github.com/kaminari-ad/mcp/compare/v0.1.0...v0.1.4
475
644
  [0.1.3]: https://github.com/kaminari-ad/mcp/compare/v0.1.0...v0.1.3
@@ -0,0 +1,139 @@
1
+ # Migrating from `@kaminari-ad/mcp` 0.1.x to 0.2.0
2
+
3
+ Two tools changed output shape and one tool changed its element type.
4
+ Everything else is wire-compatible.
5
+
6
+ If your agent calls **only** the unchanged tools, **no action is
7
+ required** — drop in v0.2.0 and continue.
8
+
9
+ ## Breaking changes
10
+
11
+ ### 1. `list_custom_rules` — bare array → paginated envelope
12
+
13
+ Before (≤ 0.1.5):
14
+
15
+ ```jsonc
16
+ [
17
+ { "id": "…", "name": "…", "tag_slug": "…", "rule_type": "…", "config": {...},
18
+ "target": "…", "is_active": true, "organization_id": "…", "created_at": "…" },
19
+ /* … */
20
+ ]
21
+ ```
22
+
23
+ After (≥ 0.2.0):
24
+
25
+ ```jsonc
26
+ {
27
+ "items": [
28
+ { "id": "…", "name": "…", "tag_slug": "…", "rule_type": "…", "config": {...},
29
+ "target": "…", "is_active": true, "organization_id": "…", "created_at": "…" }
30
+ ],
31
+ "total": 67,
32
+ "page": 1,
33
+ "limit": 50
34
+ }
35
+ ```
36
+
37
+ **Why:** the API has always returned a `PaginatedResponse[CustomRuleResponse]`
38
+ envelope; v0.1.x silently dropped `total` / `page` / `limit`. Orgs with
39
+
40
+ > 50 rules saw exactly 50 with no signal there was more.
41
+
42
+ **Migration:** wherever you read `result.length` on a `list_custom_rules`
43
+ output, replace with `result.items.length` and check `result.total >
44
+ result.items.length` to know if you need to call the tool again with
45
+ `page: 2`. New optional inputs:
46
+
47
+ ```jsonc
48
+ { "page": 1, "limit": 50 }
49
+
50
+
51
+ // defaults; both optional
52
+ ```
53
+
54
+ ### 2. `list_policy_sets` — bare array → paginated envelope
55
+
56
+ Same shape change as `list_custom_rules`, same migration. The items
57
+ themselves are unchanged (slim — no `entries`; call `get_policy_set`
58
+ for those).
59
+
60
+ ### 3. `list_run_scans` — `ScanBriefResponse` → `ScanTileResponse` per item
61
+
62
+ Before (≤ 0.1.5) — crashed in production with `malformed scans page:
63
+ items.0.url: Required`:
64
+
65
+ ```jsonc
66
+ { "items": [{ "id": "…", "url": "…", "country_code": "…", "status": "…",
67
+ "offer_url": "…", "screenshot_url": "…", "labels": {…},
68
+ "elapsed_ms": 1234, "campaign_id": "…", "campaign_name": "…",
69
+ "is_ad_tag": false, "created_at": "…" }], … }
70
+ ```
71
+
72
+ After (≥ 0.2.0):
73
+
74
+ ```jsonc
75
+ { "items": [{ "id": "…", "country_code": "…", "status": "…",
76
+ "offer_url": "…", "screenshot_url": "…",
77
+ "elapsed_ms": 1234, "error": "" }], … }
78
+ ```
79
+
80
+ **Why:** the API endpoint returns `ScanTileResponse` (designed for
81
+ the run-detail UI's tile grid) — `url` / `created_at` / `labels` /
82
+ `campaign_*` / `is_ad_tag` are intentionally omitted because the
83
+ caller already knows the run's campaign and the tiles don't need to
84
+ re-render input URLs. `error` (the scan-failure reason) is **added**.
85
+
86
+ **Migration:** if your agent reads any of the removed fields off
87
+ `list_run_scans` items, call `get_scan(id)` per tile to fetch the
88
+ full `ScanResponse` (which has them all).
89
+
90
+ ## Additions (non-breaking)
91
+
92
+ ### `list_campaigns_picker` — new tool
93
+
94
+ Slim per-row campaign list for selection UIs:
95
+
96
+ ```jsonc
97
+ { "id": "…", "name": "…", "group_id": "…", "is_archived": false }
98
+ ```
99
+
100
+ Cheaper than `list_campaigns` for orgs with thousands of campaigns —
101
+ the API endpoint is non-paginated and intentionally omits heavy
102
+ fields. Use `get_campaign(id)` after a selection to fetch full
103
+ details.
104
+
105
+ ### `get_tag_definition` — now returns `linked_rules`
106
+
107
+ The detail endpoint always returned `linked_rules` per OpenAPI; the
108
+ v0.1.x parser silently dropped them. v0.2.0 surfaces them:
109
+
110
+ ```jsonc
111
+ {
112
+ "slug": "malware",
113
+ "category": "security",
114
+ /* … all existing fields … */
115
+ "linked_rules": [{ "id": "…", "name": "ad-detector", "is_active": true }],
116
+ }
117
+ ```
118
+
119
+ No input change. Agents that need rules linked to a tag no longer
120
+ need to grep `list_custom_rules` by `tag_slug` themselves.
121
+
122
+ ### `resources/list` and `prompts/list` now return `[]`
123
+
124
+ Most MCP clients (Cursor, Claude Desktop, Cline) probe these methods
125
+ at session start. v0.1.x returned the JSON-RPC standard `-32601
126
+ Method not found` (spec-compliant), but Cursor's client mistranslated
127
+ that into a misleading `"Connection closed"` warning. v0.2.0 declares
128
+ empty capabilities + handlers so the probe is silent.
129
+
130
+ Functional impact for callers: zero. No tool surface changed; the
131
+ fix is purely about client-side noise.
132
+
133
+ ## Internal: forward-compat for `policies.in_use`
134
+
135
+ `invalid-input` MCP errors (HTTP 400 / 422) now preserve the API's
136
+ machine-readable `code` field when present. The API does not emit
137
+ `code` on `delete_policy_set` today, but `policies.in_use` is a known
138
+ candidate. When the API ships it, agents can branch on the code
139
+ without waiting for an MCP release.
package/README.md CHANGED
@@ -19,6 +19,8 @@ Lets AI agents (Cursor, Claude Desktop, Cline, and any MCP-compatible client) la
19
19
  2. Once signed in, go to **Settings → API Keys** and generate a new key, OR have an existing AI assistant (with a temporary login) call the [`create_api_key`](#tools) tool — both paths produce the same result.
20
20
  3. The key is shown **once**. Copy it. The full key is hashed server-side immediately.
21
21
 
22
+ > Keys are opaque random strings — no required prefix or fixed length. Treat the whole value as a raw secret and paste it verbatim into your client config.
23
+
22
24
  > Tip for evaluators / Anthropic Software Directory reviewers: ask the team at [hello@kaminari.ad](mailto:hello@kaminari.ad) for a sandboxed test account with seeded sample scans, campaigns, and alerts.
23
25
 
24
26
  ### 2a. Local install (stdio transport)
@@ -32,14 +34,14 @@ Add to your MCP client config (Cursor: `~/.cursor/mcp.json`; Claude Desktop: `~/
32
34
  "command": "npx",
33
35
  "args": ["-y", "@kaminari-ad/mcp"],
34
36
  "env": {
35
- "KAMINARI_AD_API_KEY": "kad_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
37
+ "KAMINARI_AD_API_KEY": "<your-kaminari-ad-api-key>",
36
38
  },
37
39
  },
38
40
  },
39
41
  }
40
42
  ```
41
43
 
42
- Restart your client. You should see `kaminari-ad` in the MCP servers list with 82 tools exposed.
44
+ Restart your client. You should see `kaminari-ad` in the MCP servers list with 83 tools exposed.
43
45
 
44
46
  ### 2b. Hosted HTTP transport (no install)
45
47
 
@@ -51,7 +53,7 @@ For cloud agents or clients without a local Node runtime, point at the hosted en
51
53
  "kaminari-ad": {
52
54
  "url": "https://mcp.kaminari.ad/mcp",
53
55
  "headers": {
54
- "Authorization": "Bearer kad_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
56
+ "Authorization": "Bearer <your-kaminari-ad-api-key>",
55
57
  },
56
58
  },
57
59
  },
@@ -62,11 +64,11 @@ For cloud agents or clients without a local Node runtime, point at the hosted en
62
64
 
63
65
  ## Tools
64
66
 
65
- 82 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. Highlights:
67
+ 83 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. Highlights:
66
68
 
67
69
  - **Account** (11) — `get_account`, `update_org`, `list_org_users`, `invite_user`, `update_user_role`, `remove_user`, `transfer_ownership`, `list_org_roles`, `list_api_keys`, `create_api_key`, `revoke_api_key`
68
70
  - **Scans** (7) — `list_scans`, `get_scan`, `create_scan`, `create_bulk_scans`, `recheck_scans`, `cancel_scan`, `list_scan_tags`
69
- - **Campaigns** (9) — `list_campaigns`, `get_campaign`, `create_campaign`, `update_campaign`, `archive_campaign`, `unarchive_campaign`, `cancel_campaign`, `run_campaign`, `list_campaign_runs`
71
+ - **Campaigns** (10) — `list_campaigns`, `list_campaigns_picker`, `get_campaign`, `create_campaign`, `update_campaign`, `archive_campaign`, `unarchive_campaign`, `cancel_campaign`, `run_campaign`, `list_campaign_runs`
70
72
  - **Campaign groups** (10) — list/get/create/update/run/cancel/archive/unarchive + `pause_campaign_group_schedule`, `resume_campaign_group_schedule`
71
73
  - **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)
72
74
  - **Tags** (4) — `list_tags`, `get_tag_definition`, `update_tag_definition`, `delete_tag_definition`
@@ -79,7 +81,7 @@ For cloud agents or clients without a local Node runtime, point at the hosted en
79
81
  - **Alert notifications** (5) — `list_alert_destinations`, `delete_alert_destination`, `set_alert_destination_version`, `get_campaign_alert_overrides`, `set_campaign_alert_overrides`
80
82
  - **Reference data** (2) — `list_geos`, `list_emulators`
81
83
 
82
- Not exposed (intentionally): binary scan-screenshot fetchers, invoice PDF, the UI-only campaign picker, and the public marketing forms (`/contact`, `/demo-inquiries`). Open an issue if you need one of those.
84
+ Not exposed (intentionally): binary scan-screenshot fetchers, invoice PDF, and the public marketing forms (`/contact`, `/demo-inquiries`). Open an issue if you need one of those.
83
85
 
84
86
  ## Example agent prompts
85
87
 
@@ -119,7 +121,7 @@ make test-unit # unit only
119
121
  make test-isolation # tenant-isolation suite
120
122
  ```
121
123
 
122
- Or directly with `npm` if you have Node 22 LTS on the host (matches `.nvmrc` / `engines.node`):
124
+ Or directly with `npm` if you have Node `>=22.19.0` on the host (matches `engines.node`; `.nvmrc` pins the minor for dev parity with CI). The package gates strictly at `22.19.0` because `undici@8.x` requires `markAsUncloneable` from `node:worker_threads` (Node 22.19+).
123
125
 
124
126
  ```bash
125
127
  npm ci --legacy-peer-deps