@kaminari-ad/mcp 0.1.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 +383 -0
- package/LICENSE +21 -0
- package/README.md +159 -0
- package/SECURITY.md +51 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +5660 -0
- package/dist/bin.js.map +1 -0
- package/package.json +120 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@kaminari-ad/mcp` are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.1.0] - 2026-05-17
|
|
11
|
+
|
|
12
|
+
First public release of `@kaminari-ad/mcp`.
|
|
13
|
+
|
|
14
|
+
Bundles the full pre-release development arc that lived on a private
|
|
15
|
+
GitLab instance: HTTP + stdio transports, 82 MCP tools spanning scans /
|
|
16
|
+
campaigns / policies / alerts / webhooks / billing / accounts, tenant-
|
|
17
|
+
isolation suite, schema-backed parsers, openapi-fetch gateway, the
|
|
18
|
+
`prod:smoke` CI job, and the OSS bootstrap (LICENSE, SECURITY,
|
|
19
|
+
CONTRIBUTING, CODE_OF_CONDUCT, issue / PR templates).
|
|
20
|
+
|
|
21
|
+
See the GitHub release notes for the auto-generated commit summary;
|
|
22
|
+
the per-area changelog entries below capture the substantive engineering
|
|
23
|
+
work since the project was scaffolded.
|
|
24
|
+
|
|
25
|
+
### Fixed (parser-drift phase 5 — post-review hardening)
|
|
26
|
+
|
|
27
|
+
- **`list_policy_sets` regression introduced by phase 2b.** The
|
|
28
|
+
schema-strict parser required `entries`, but `GET /api/v1/policy-sets`
|
|
29
|
+
returns `PolicySetListItem` (a slim per-item shape WITHOUT entries —
|
|
30
|
+
entries are loaded on demand via `get_policy_set(id)`). Every real
|
|
31
|
+
list call after the phase 2b roll-out returned
|
|
32
|
+
`"malformed policy-sets: 0.entries: Required"`. Now:
|
|
33
|
+
- New port DTO `PolicySetListItemResponse` (no `entries`).
|
|
34
|
+
- New `PolicySetListItemSchema` in `parse-policy-set.ts` backed by
|
|
35
|
+
the generated `schemas.PolicySetListItem`.
|
|
36
|
+
- `list_policy_sets` tool now exposes the slim shape and its
|
|
37
|
+
description tells the agent to follow up with `get_policy_set`
|
|
38
|
+
when it needs the entries.
|
|
39
|
+
- HTTP gateway test stub + fake fixture updated to the slim shape.
|
|
40
|
+
- Verified against prod API via end-to-end smoke against a sandbox
|
|
41
|
+
org: parser now accepts the real response.
|
|
42
|
+
- **`set_alert_destination_version` was parsing a 204 No Content as
|
|
43
|
+
JSON.** Pre-existing on `main` but surfaced more loudly under strict
|
|
44
|
+
zod. Now uses `parseEmpty`; port returns `Result<null>`; tool emits
|
|
45
|
+
`{ updated: true }` like the other 204 mutators. HTTP gateway test
|
|
46
|
+
updated to a 204 stub (the previous 200-with-body fixture masked
|
|
47
|
+
the real contract).
|
|
48
|
+
- **`create_api_key` rejected `expires_at: null`.** Common JSON-client
|
|
49
|
+
convention is to send `null` explicitly for "no expiry". Input
|
|
50
|
+
schema now accepts both `null` and omit; the gateway sees them as
|
|
51
|
+
equivalent (no `expires_at` field on the request body).
|
|
52
|
+
- **Dist bundle ~770 KB → ~210 KB.** The `openapi-zod-client`
|
|
53
|
+
generator emits a Zodios endpoints catalogue + `axios` client at
|
|
54
|
+
the tail of `zod-schemas.ts`. We use only the `schemas` bag for
|
|
55
|
+
runtime validation; the MCP gateway is `openapi-fetch`-based, not
|
|
56
|
+
Zodios. `scripts/gen-api-types.ts` now post-processes the generated
|
|
57
|
+
output to strip the Zodios runtime (imports, `endpoints` array,
|
|
58
|
+
`api` instance, `createApiClient` helper). Axios + `form-data` (which
|
|
59
|
+
use CJS `require("util")`) no longer reach the bundle — this also
|
|
60
|
+
fixes a `"Dynamic require of util is not supported"` crash on
|
|
61
|
+
`node dist/bin.js` startup that the integration smoke test caught.
|
|
62
|
+
`check:bundle-size` reverted to the original 500 KB ceiling.
|
|
63
|
+
- **`{set_id}` path templates corrected to `{policy_set_id}`** in
|
|
64
|
+
`http-api-gateway.ts` to match the OpenAPI spec. Runtime URLs are
|
|
65
|
+
identical (openapi-fetch substitutes by key name), but the literal
|
|
66
|
+
now lines up with the spec for future-readers.
|
|
67
|
+
- **CONTRIBUTING.md "Tenant isolation" §9** added — the pinned 5-key
|
|
68
|
+
outbound header allowlist (authorization / content-type / accept /
|
|
69
|
+
user-agent / x-request-id) is now an explicit numbered rule, not
|
|
70
|
+
an implicit one referenced from inline comments. Stale §8/§11
|
|
71
|
+
references in `http-api-gateway.ts`, `pino-logger.ts`, and the
|
|
72
|
+
isolation test headers updated to point at the correct sections.
|
|
73
|
+
- **Empty-body JSDocs filled in.** `parse-empty.ts::parseEmpty` and
|
|
74
|
+
`parse-count-envelope.ts::parseIntField` now document the contract
|
|
75
|
+
(204 No Content vs single-int envelope) instead of shipping
|
|
76
|
+
placeholder `/** * */` stubs.
|
|
77
|
+
- **Tool-test success-payload assertions.** Several 204-mutator tool
|
|
78
|
+
tests (`set_alert_destination_version`, `set_campaign_alert_overrides`,
|
|
79
|
+
`request_policy_set_approval`, `update_user_role`) only asserted
|
|
80
|
+
`isOk()` and never checked the synthetic `{ updated: true }` /
|
|
81
|
+
`{ requested: true }` payload — a regression that silently changed
|
|
82
|
+
the success shape would have slipped through. Each now asserts
|
|
83
|
+
both the call body AND the success payload.
|
|
84
|
+
- **Per-DTO parser error coverage gaps closed.** `parseScanTag`,
|
|
85
|
+
`parseUsage`, `parseUsageSummary`, `parseBalanceTx`, `parseInvoice`,
|
|
86
|
+
`parseAlertDestination`, `parseBulkReplay` were happy-path only.
|
|
87
|
+
Added missing-required + wrong-type cases to each. Coverage stays
|
|
88
|
+
100% lines + 100% statements + 98.48% branches.
|
|
89
|
+
|
|
90
|
+
### Fixed (parser-drift phase 1)
|
|
91
|
+
|
|
92
|
+
Production smoke against a fresh test org found 8 of the 82 tools
|
|
93
|
+
broken on 4 distinct root-cause patterns. All fixed; the global
|
|
94
|
+
masking bug that hid the real failure mode is fixed too.
|
|
95
|
+
|
|
96
|
+
- **API returns 204 No Content, parser expected entity.** Four tools
|
|
97
|
+
hit this — `set_campaign_alert_overrides`, `request_policy_set_approval`,
|
|
98
|
+
`update_tag_definition`, `update_user_role` — with errors like
|
|
99
|
+
`malformed user` / `malformed tag detail` / `malformed policy-set`.
|
|
100
|
+
Parsers now use `parseEmpty`; port DTOs return `null`; the tool
|
|
101
|
+
output is `{ updated: true }` / `{ requested: true }` so JSON output
|
|
102
|
+
stays a plain object. Tool descriptions updated to point at the
|
|
103
|
+
follow-up GET when the caller needs the new state echoed.
|
|
104
|
+
- **Action endpoint returns `GroupActionResponse` summary, parser
|
|
105
|
+
expected the group entity.** `archive_campaign_group` and
|
|
106
|
+
`unarchive_campaign_group` both POST endpoints return
|
|
107
|
+
`{ group_id, affected_campaigns, cancelled_count, run_ids,
|
|
108
|
+
failures }` like `run_campaign_group` / `cancel_campaign_group`.
|
|
109
|
+
Parsers now use the existing `parseGroupAction`; port DTO is
|
|
110
|
+
`GroupActionResponse`.
|
|
111
|
+
- **Paginated envelope vs bare array (inverted from last week).**
|
|
112
|
+
`list_campaign_groups` — OpenAPI documents the response as a bare
|
|
113
|
+
`CampaignGroupResponse[]` but the parser was `parseCampaignGroupPage`
|
|
114
|
+
expecting `{items, total, page, limit}`. New
|
|
115
|
+
`parseCampaignGroupArray` accepts both shapes defensively (same
|
|
116
|
+
`unwrapItems` pattern used in `parsePolicySetList`). Tool DTO becomes
|
|
117
|
+
`readonly CampaignGroupResponse[]` and the bogus `page` / `limit`
|
|
118
|
+
query params (the endpoint only documents `archived?`) are dropped.
|
|
119
|
+
- **Missing required request body + ignored response.** `test_webhook`
|
|
120
|
+
sent `undefined` body, but the API requires `{event_type: string}`
|
|
121
|
+
(422 without). The rich `TestWebhookResponse` was dropped via
|
|
122
|
+
`parseEmpty`. The gateway now sends the body; new
|
|
123
|
+
`parseTestWebhookResponse` decodes
|
|
124
|
+
`{ success, response_status, elapsed_ms, error_code, response_body }`
|
|
125
|
+
so the agent can diagnose a receiver failure from one call.
|
|
126
|
+
- **`error-mapping.ts::detail()` only handled string `detail`.**
|
|
127
|
+
FastAPI 422 returns `detail: ValidationError[]`. Every 422 across
|
|
128
|
+
every tool degraded to opaque `"Upstream error"` — which masked the
|
|
129
|
+
real `test_webhook` failure mode. The detail extractor now walks
|
|
130
|
+
the array, formats each entry as `"<loc>: <msg>"`, joins with
|
|
131
|
+
`; `, surfaces field-level RCA in the tool error string.
|
|
132
|
+
|
|
133
|
+
### Added (parser-drift phase 2a — infrastructure for the rest)
|
|
134
|
+
|
|
135
|
+
- **Generated zod runtime schemas** at `src/shared/api/zod-schemas.ts`.
|
|
136
|
+
`scripts/gen-api-types.ts` now emits both
|
|
137
|
+
`src/shared/api/openapi.ts` (types, via `openapi-typescript`) and
|
|
138
|
+
`src/shared/api/zod-schemas.ts` (runtime schemas + Zodios endpoint
|
|
139
|
+
catalogue, via `openapi-zod-client`) from the SAME live OpenAPI
|
|
140
|
+
document — so the two files cannot drift relative to each other,
|
|
141
|
+
and the existing CI drift-check on the committed copies covers
|
|
142
|
+
both. Source URL changed from `https://kaminari.ad/openapi.json`
|
|
143
|
+
(returns a Next.js 404) to `https://app.kaminari.ad/openapi.json`
|
|
144
|
+
(the actual API host).
|
|
145
|
+
- **`parseWithSchema` helper** at
|
|
146
|
+
`src/infrastructure/api/parsers/parse-with-schema.ts` — wraps
|
|
147
|
+
`schema.safeParse()` with typed `ApiError` failure mapping AND
|
|
148
|
+
strips explicit-`undefined` keys from the parsed object so the
|
|
149
|
+
output matches the port DTO's `exactOptionalPropertyTypes` style.
|
|
150
|
+
Foundation for Phase 2b (converting each hand-written
|
|
151
|
+
`parse-*.ts` to a one-liner backed by `schemas.X.pick({...})`).
|
|
152
|
+
|
|
153
|
+
### Added (parser-drift phase 4 — production observability)
|
|
154
|
+
|
|
155
|
+
- **`scripts/prod-smoke.ts`** + **`npm run prod:smoke`** + a manual
|
|
156
|
+
`prod:smoke` GitLab CI job. Fires a read-only subset of MCP tools
|
|
157
|
+
at the hosted endpoint using a long-lived sandbox-org bearer
|
|
158
|
+
(`KAMINARI_AD_MCP_PROD_TOKEN` CI variable, Masked + Protected).
|
|
159
|
+
Catches drifts that escape compile-time gates — API shape changes,
|
|
160
|
+
feature-flag-gated routes flipping on/off, parser regressions.
|
|
161
|
+
Manual trigger by default; flip to a daily schedule once the
|
|
162
|
+
sandbox org + token are provisioned.
|
|
163
|
+
|
|
164
|
+
### Added (parser-drift phase 2b — full conversion)
|
|
165
|
+
|
|
166
|
+
- **Every `parse-*.ts` rewritten as a `parseWithSchema(schemas.X.pick
|
|
167
|
+
({...}).strip())` one-liner.** All 17 hand-written parsers now
|
|
168
|
+
delegate to zod schemas generated from the live OpenAPI spec. A
|
|
169
|
+
field rename / removal upstream surfaces as a `tsc` error on the
|
|
170
|
+
`.pick({…})` mask (drift fails at compile time, not at the first
|
|
171
|
+
production request). A wrong-shape runtime payload degrades to a
|
|
172
|
+
typed `upstream` MCP error with the zod issue chain — never to an
|
|
173
|
+
`undefined.x` crash.
|
|
174
|
+
- **Test fixtures hardened.** ~30 fixtures across
|
|
175
|
+
`tests/unit/infrastructure/api/**` migrated from the old loose
|
|
176
|
+
hand-parser stubs (`id: "u1"`, `created_at: "t"`,
|
|
177
|
+
`status: "done"`) to schema-valid values
|
|
178
|
+
(`id: "00000000-0000-0000-0000-…"`, ISO datetimes, enum members
|
|
179
|
+
from the OpenAPI source like `"completed"` / `"api"`). Tests now
|
|
180
|
+
assert the same contract the production API enforces.
|
|
181
|
+
- **Two exempt files**, both documented in
|
|
182
|
+
`scripts/check-no-handwritten-parsers.ts`:
|
|
183
|
+
- `parse-empty.ts` — `204 No Content`, no body to validate.
|
|
184
|
+
- `parse-count-envelope.ts::parseIntField(raw, "x")` — generic
|
|
185
|
+
one-field-int extractor used by ad-hoc envelopes like
|
|
186
|
+
`{queued_count}` / `{cancelled_count}` that have no dedicated DTO
|
|
187
|
+
in the spec.
|
|
188
|
+
|
|
189
|
+
### Added (parser-drift phase 3 — typed HTTP client)
|
|
190
|
+
|
|
191
|
+
- **`src/infrastructure/api/http-api-gateway.ts` ported to
|
|
192
|
+
`openapi-fetch`.** Every endpoint path is now a literal type
|
|
193
|
+
constrained by `paths` from `src/shared/api/openapi.ts`; path /
|
|
194
|
+
query / body shapes are validated by the same generated types,
|
|
195
|
+
with the agent-facing `Pick<S[K], …>` projections in
|
|
196
|
+
`domain/ports/api-gateway.ts` narrowing the surface. Renaming or
|
|
197
|
+
removing an endpoint on the API side fails the gateway at
|
|
198
|
+
`tsc --noEmit` immediately — no runtime drift.
|
|
199
|
+
- **Tenant-isolation contract preserved.** Pinned 5-key outbound
|
|
200
|
+
header allowlist (authorization / content-type / accept /
|
|
201
|
+
user-agent / x-request-id) — same shape the existing
|
|
202
|
+
`tests/isolation/header-injection-e2e.test.ts` AST gate already
|
|
203
|
+
enforces. Per-request `Dispatcher` injection (used by tests with
|
|
204
|
+
`MockAgent`; production uses the global agent) is forwarded through
|
|
205
|
+
a thin `fetchImpl` wrapper that splices `input.headers` /
|
|
206
|
+
`input.body` from openapi-fetch's `Request` onto the undici init
|
|
207
|
+
bag — without this the auth header silently vanishes (because
|
|
208
|
+
undici.fetch ignores `Request` and reads only `init`).
|
|
209
|
+
- **Removed `buildQuery` and the manual `${enc(id)}` interpolation.**
|
|
210
|
+
Query / path params now go through openapi-fetch's typed
|
|
211
|
+
`params.query` / `params.path` — typed key names per endpoint,
|
|
212
|
+
enforced at compile time (`scan_id` for `/scans/{scan_id}`,
|
|
213
|
+
`endpoint_id` for `/webhooks/{endpoint_id}`, etc.). A key typo
|
|
214
|
+
fails `tsc` rather than producing a silently-wrong URL.
|
|
215
|
+
|
|
216
|
+
### Added (parser-drift phase 4 — production observability)
|
|
217
|
+
|
|
218
|
+
- **`scripts/prod-smoke.ts`** + **`npm run prod:smoke`** + a manual
|
|
219
|
+
`prod:smoke` GitLab CI job (description retained from phase 2a).
|
|
220
|
+
- **`npm run check:no-handwritten-parsers`** + new
|
|
221
|
+
`check:no-handwritten-parsers` GitLab CI job in `arch_gates`.
|
|
222
|
+
Belt-and-suspenders gate: parses every `src/infrastructure/api/
|
|
223
|
+
parsers/*.ts` and fails if a parser does NOT import `{ schemas }
|
|
224
|
+
from "../../shared/api/zod-schemas"` (or one of the two documented
|
|
225
|
+
exemptions). A future contributor who adds a hand-rolled
|
|
226
|
+
`typeof raw === "object" && "field" in raw` parser hits this gate
|
|
227
|
+
immediately, with a pointer to `parse-org` / `parse-scan` as the
|
|
228
|
+
canonical schema-backed shape.
|
|
229
|
+
|
|
230
|
+
### Changed (breaking — pre-release, no API consumers yet)
|
|
231
|
+
|
|
232
|
+
- **Env vars now carry the `KAMINARI_AD_` namespace prefix.** Generic
|
|
233
|
+
names (`API_BASE_URL`, `LOG_LEVEL`, `HTTP_PORT`, `SESSION_TTL_SEC`,
|
|
234
|
+
`RATE_LIMIT_RPM`, `TRANSPORT`) were trivially poisonable by any
|
|
235
|
+
other tool in the same shell or container that set the same name.
|
|
236
|
+
All env vars are now `KAMINARI_AD_*`:
|
|
237
|
+
- `API_BASE_URL` → `KAMINARI_AD_API_URL`
|
|
238
|
+
- `LOG_LEVEL` → `KAMINARI_AD_LOG_LEVEL`
|
|
239
|
+
- `HTTP_PORT` → `KAMINARI_AD_HTTP_PORT`
|
|
240
|
+
- `SESSION_TTL_SEC` → `KAMINARI_AD_SESSION_TTL_SEC`
|
|
241
|
+
- `RATE_LIMIT_RPM` → `KAMINARI_AD_RATE_LIMIT_RPM`
|
|
242
|
+
- `TRANSPORT` → `KAMINARI_AD_TRANSPORT`
|
|
243
|
+
- `KAMINARI_AD_API_KEY` — unchanged.
|
|
244
|
+
Old unprefixed names are no longer read. `env | grep KAMINARI_AD_`
|
|
245
|
+
now enumerates every config input.
|
|
246
|
+
|
|
247
|
+
### Added
|
|
248
|
+
|
|
249
|
+
- **`KAMINARI_AD_LOG_FORMAT`** env var (`pretty` | `json`) — was
|
|
250
|
+
previously hard-coded per transport (stdio = pretty, http = json).
|
|
251
|
+
Now operator-controllable. Default remains transport-dependent.
|
|
252
|
+
MCP hosts that capture stderr (Cursor, Claude Desktop) should set
|
|
253
|
+
this to `json` for parseable structured logs.
|
|
254
|
+
- **`pino-pretty`** moved to runtime `dependencies` so the default
|
|
255
|
+
`stdio + pretty` path no longer crashes on `npx -y @kaminari-ad/mcp`
|
|
256
|
+
with "unable to determine transport target".
|
|
257
|
+
- **Pino sink hardening**: `pretty` format now uses a sync
|
|
258
|
+
`pino-pretty` write stream wired to `process.stderr` directly,
|
|
259
|
+
instead of the `transport` worker option. The worker silently
|
|
260
|
+
ignores any `destination` argument and defaults to stdout, which
|
|
261
|
+
would corrupt the MCP JSON-RPC channel in stdio mode.
|
|
262
|
+
|
|
263
|
+
### Fixed
|
|
264
|
+
|
|
265
|
+
- **Paginated list parsers (custom-rules, policy-sets) accept the
|
|
266
|
+
FastAPI envelope.** Both `parseCustomRuleArray` and
|
|
267
|
+
`parsePolicySetList` expected a bare `T[]`, but the API returns the
|
|
268
|
+
standard `{ items, total, page, limit, pages }` envelope — every
|
|
269
|
+
call to `list_custom_rules` / `list_policy_sets` returned `Upstream
|
|
270
|
+
error: expected array of …`. Parsers now accept both shapes
|
|
271
|
+
(envelope is unwrapped to the inner array; pagination metadata is
|
|
272
|
+
discarded since the tool DTOs return `readonly T[]`). Bug
|
|
273
|
+
surfaced by a real-org smoke run against a fresh
|
|
274
|
+
`/api/v1/custom-rules` instance. Other list parsers
|
|
275
|
+
(`parseScanPage`, `parseCampaignPage`, `parseAlertPage`) were
|
|
276
|
+
already envelope-aware; this brings the remaining two in line.
|
|
277
|
+
|
|
278
|
+
- **HTTP transport: Streamable HTTP session continuity.** The
|
|
279
|
+
per-request handler used to spin up a fresh `McpServer` +
|
|
280
|
+
`StreamableHTTPServerTransport` on every POST, so the SDK state
|
|
281
|
+
built by `initialize` evaporated before `initialized` arrived and
|
|
282
|
+
every subsequent request returned 400 "Server not initialized".
|
|
283
|
+
The handler now caches the SDK transport keyed by session id and
|
|
284
|
+
swaps a per-request `ApiGateway` into a `ctxRef` indirection so the
|
|
285
|
+
bearer stays request-scoped (tenant-isolation rule #9 enforced via
|
|
286
|
+
the existing `SessionStore` bearer-hash equality check). Refactored
|
|
287
|
+
into three files (`http-request-handler.ts`,
|
|
288
|
+
`session-resolver.ts`, `mcp-session-factory.ts`) to stay under the
|
|
289
|
+
200-effective-line cap.
|
|
290
|
+
- **HTTP transport: SIGTERM/SIGINT crash on startup.** Both
|
|
291
|
+
bootstraps used `import * as process from "node:process"`; the
|
|
292
|
+
namespace import does not expose `process.once` (it's on the
|
|
293
|
+
default export only), so the HTTP transport crashed immediately
|
|
294
|
+
with "process.once is not a function" — and no unit/isolation test
|
|
295
|
+
caught it because none of them spawned the real bootstrap.
|
|
296
|
+
Switched all three entrypoints (`bin.ts`, `stdio-bootstrap.ts`,
|
|
297
|
+
`http-bootstrap.ts`) to default `import process from "node:process"`
|
|
298
|
+
and added a CLI-smoke integration test that boots the built
|
|
299
|
+
`dist/bin.js` and probes `/healthz` + the full
|
|
300
|
+
`initialize → initialized → tools/list` session flow.
|
|
301
|
+
- **stdio transport: pretty logs polluted the JSON-RPC stdout channel.**
|
|
302
|
+
Pino's `transport: { target: "pino-pretty" }` option spawns a worker
|
|
303
|
+
that defaults to `process.stdout` and ignores the destination
|
|
304
|
+
argument we passed to `pino()`. In stdio mode (MCP host reads
|
|
305
|
+
JSON-RPC from stdout) this corrupted every response. The pretty
|
|
306
|
+
format now uses a synchronous `pino-pretty` write stream wired to
|
|
307
|
+
`process.stderr` directly.
|
|
308
|
+
- `pino-pretty` moved from `devDependencies` to `dependencies` so a
|
|
309
|
+
fresh `npx -y @kaminari-ad/mcp` install no longer crashes with
|
|
310
|
+
"unable to determine transport target".
|
|
311
|
+
- `zod` pinned to `3.25.76` (was `3.24.1`). MCP SDK 1.29 ships a
|
|
312
|
+
transitive `zod-to-json-schema` that imports `zod/v3`, an export
|
|
313
|
+
introduced in zod 3.25 — the old pin would crash any consumer on
|
|
314
|
+
first import with `ERR_PACKAGE_PATH_NOT_EXPORTED`.
|
|
315
|
+
|
|
316
|
+
## [0.1.0] - TBD
|
|
317
|
+
|
|
318
|
+
Initial public release. The first version that ships to npm under
|
|
319
|
+
`@kaminari-ad/mcp`.
|
|
320
|
+
|
|
321
|
+
### Added
|
|
322
|
+
|
|
323
|
+
- **MCP server** with two transports:
|
|
324
|
+
- **stdio** for local clients (Cursor, Claude Desktop, Cline, ...).
|
|
325
|
+
API key from `KAMINARI_AD_API_KEY` env var.
|
|
326
|
+
- **Streamable HTTP** for the hosted endpoint at
|
|
327
|
+
`https://mcp.kaminari.ad/mcp`. Per-request Bearer, per-request
|
|
328
|
+
`ApiGateway` closure, in-memory session store keyed by `SessionId`
|
|
329
|
+
(value: `sha256(bearer)`), leaky-bucket rate limit keyed by
|
|
330
|
+
`sha256(bearer)`.
|
|
331
|
+
- **82 tools** spanning most of the `/api/v1` surface — account,
|
|
332
|
+
scans, campaigns, runs, campaign-groups, tag-definitions,
|
|
333
|
+
custom-rules, policy-sets, alerts, alert-notifications, webhooks,
|
|
334
|
+
billing, invoicing, geos, emulators. Every tool carries the
|
|
335
|
+
Anthropic Software Directory annotations (`title`, `readOnlyHint`,
|
|
336
|
+
`destructiveHint`, `idempotentHint`, `openWorldHint`).
|
|
337
|
+
- **Tenant-isolation discipline** for the hosted HTTP mode, codified
|
|
338
|
+
as 16 rules in `CONTRIBUTING.md` and enforced by `tests/isolation/`
|
|
339
|
+
(bearer-swap, header-injection, env-fallback disabled, missing
|
|
340
|
+
auth, concurrent bearers, no-shared-state AST gate, token-not-in-
|
|
341
|
+
logs, error-path isolation).
|
|
342
|
+
- **Type-safety**: every port DTO is a `Pick<components["schemas"][X],
|
|
343
|
+
...>` projection over the generated `src/shared/api/openapi.ts`. A
|
|
344
|
+
future API field rename surfaces as a compile error in the parser.
|
|
345
|
+
- **Quality gates** (all enforced by CI):
|
|
346
|
+
- TypeScript strict + 8 ESLint plugins.
|
|
347
|
+
- 4 custom architecture gates: file size (200 LOC), import
|
|
348
|
+
boundaries (dependency-cruiser with `tsPreCompilationDeps`),
|
|
349
|
+
no-shared-state (ts-morph AST walk), tool-naming.
|
|
350
|
+
- 100% lines / 100% functions / 100% statements coverage,
|
|
351
|
+
97% branches.
|
|
352
|
+
- Bundle size cap (500 KB on `dist/bin.js`, currently ~180 KB).
|
|
353
|
+
- `npm audit` with 0 vulnerabilities.
|
|
354
|
+
- **Packaging**: ESM dist with shebang for `npx`, sourcemaps, type
|
|
355
|
+
declarations, npm provenance on release.
|
|
356
|
+
|
|
357
|
+
### Security
|
|
358
|
+
|
|
359
|
+
- `KAMINARI_AD_API_KEY` env var is **rejected on startup** in HTTP
|
|
360
|
+
mode (stdio only) — no fallback token can exist on a hosted server.
|
|
361
|
+
- Bearer tokens are **never logged**. The `BearerToken` value object
|
|
362
|
+
overrides `toString` / `toJSON` /
|
|
363
|
+
`Symbol.for("nodejs.util.inspect.custom")` to return
|
|
364
|
+
`[BearerToken redacted]`.
|
|
365
|
+
- pino is configured with redaction paths covering
|
|
366
|
+
`authorization` / `bearer` / `*.token` etc.
|
|
367
|
+
- Outbound API calls carry an **explicit 5-key header allowlist**
|
|
368
|
+
(`authorization`, `content-type`, `accept`, `user-agent`,
|
|
369
|
+
`x-request-id`). No spread of inbound request headers.
|
|
370
|
+
|
|
371
|
+
### Not yet (tracked as follow-ups)
|
|
372
|
+
|
|
373
|
+
- **OAuth 2.1 + Dynamic Client Registration + PKCE** on the hosted
|
|
374
|
+
`mcp.kaminari.ad` endpoint, required by the Anthropic Software
|
|
375
|
+
Directory (§5.D) for remote MCP servers. v0.1.0 ships with raw
|
|
376
|
+
Bearer; OAuth lands in a follow-up MR.
|
|
377
|
+
- Binary scan-screenshot fetchers (`/api/v1/scans/{id}/screenshot`
|
|
378
|
+
etc.) — agents rarely consume images directly. Reach out if you
|
|
379
|
+
need them.
|
|
380
|
+
- Invoice PDF fetcher — same reason.
|
|
381
|
+
|
|
382
|
+
[Unreleased]: https://github.com/kaminari-ad/mcp/compare/v0.1.0...HEAD
|
|
383
|
+
[0.1.0]: https://github.com/kaminari-ad/mcp/releases/tag/v0.1.0
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kaminari Ad
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# `@kaminari-ad/mcp`
|
|
2
|
+
|
|
3
|
+
Official Model Context Protocol (MCP) server for [Kaminari Ad](https://kaminari.ad) — the ad verification platform from the team behind [Kaminari.Click](https://kaminari.click).
|
|
4
|
+
|
|
5
|
+
Lets AI agents (Cursor, Claude Desktop, Cline, and any MCP-compatible client) launch scans, inspect results, manage campaigns and policies, and read alerts directly against your Kaminari Ad workspace via your API key.
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/@kaminari-ad/mcp)
|
|
8
|
+
[](https://github.com/kaminari-ad/mcp/actions/workflows/ci.yml)
|
|
9
|
+
[](LICENSE)
|
|
10
|
+
[](https://www.npmjs.com/package/@kaminari-ad/mcp)
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Quick start
|
|
15
|
+
|
|
16
|
+
### 1. Sign up & get an API key
|
|
17
|
+
|
|
18
|
+
1. Sign up at [https://app.kaminari.ad/signup](https://app.kaminari.ad/signup) (free tier, no card required).
|
|
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
|
+
3. The key is shown **once**. Copy it. The full key is hashed server-side immediately.
|
|
21
|
+
|
|
22
|
+
> 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
|
+
|
|
24
|
+
### 2a. Local install (stdio transport)
|
|
25
|
+
|
|
26
|
+
Add to your MCP client config (Cursor: `~/.cursor/mcp.json`; Claude Desktop: `~/Library/Application Support/Claude/claude_desktop_config.json`):
|
|
27
|
+
|
|
28
|
+
```jsonc
|
|
29
|
+
{
|
|
30
|
+
"mcpServers": {
|
|
31
|
+
"kaminari-ad": {
|
|
32
|
+
"command": "npx",
|
|
33
|
+
"args": ["-y", "@kaminari-ad/mcp"],
|
|
34
|
+
"env": {
|
|
35
|
+
"KAMINARI_AD_API_KEY": "kad_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Restart your client. You should see `kaminari-ad` in the MCP servers list with 82 tools exposed.
|
|
43
|
+
|
|
44
|
+
### 2b. Hosted HTTP transport (no install)
|
|
45
|
+
|
|
46
|
+
For cloud agents or clients without a local Node runtime, point at the hosted endpoint:
|
|
47
|
+
|
|
48
|
+
```jsonc
|
|
49
|
+
{
|
|
50
|
+
"mcpServers": {
|
|
51
|
+
"kaminari-ad": {
|
|
52
|
+
"url": "https://mcp.kaminari.ad/mcp",
|
|
53
|
+
"headers": {
|
|
54
|
+
"Authorization": "Bearer kad_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Tools
|
|
64
|
+
|
|
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:
|
|
66
|
+
|
|
67
|
+
- **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
|
+
- **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`
|
|
70
|
+
- **Campaign groups** (10) — list/get/create/update/run/cancel/archive/unarchive + `pause_campaign_group_schedule`, `resume_campaign_group_schedule`
|
|
71
|
+
- **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
|
+
- **Tags** (4) — `list_tags`, `get_tag_definition`, `update_tag_definition`, `delete_tag_definition`
|
|
73
|
+
- **Custom rules** (6) — `list_custom_rules`, `get_custom_rule`, `create_custom_rule`, `update_custom_rule`, `delete_custom_rule`, `test_custom_rule`
|
|
74
|
+
- **Policy sets** (6) — `list_policy_sets`, `get_policy_set`, `create_policy_set`, `update_policy_set`, `delete_policy_set`, `request_policy_set_approval`
|
|
75
|
+
- **Alerts** (3) — `list_alerts`, `update_alert_status`, `get_alert_stats`
|
|
76
|
+
- **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`
|
|
77
|
+
- **Billing** (4) — `get_billing_summary`, `list_usage`, `get_usage_summary`, `list_balance_history`
|
|
78
|
+
- **Invoicing** (1) — `list_invoices`
|
|
79
|
+
- **Alert notifications** (5) — `list_alert_destinations`, `delete_alert_destination`, `set_alert_destination_version`, `get_campaign_alert_overrides`, `set_campaign_alert_overrides`
|
|
80
|
+
- **Reference data** (2) — `list_geos`, `list_emulators`
|
|
81
|
+
|
|
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.
|
|
83
|
+
|
|
84
|
+
## Example agent prompts
|
|
85
|
+
|
|
86
|
+
These three prompts each exercise a different cross-section of tools and demonstrate the typical agent workflow:
|
|
87
|
+
|
|
88
|
+
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`.
|
|
89
|
+
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`.
|
|
90
|
+
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.
|
|
91
|
+
|
|
92
|
+
Full machine-readable tool listing is exposed by the server itself — connect with any MCP client and call `tools/list`.
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Security & tenant isolation
|
|
97
|
+
|
|
98
|
+
The hosted HTTP endpoint serves many organizations from a single process. We take cross-tenant isolation very seriously:
|
|
99
|
+
|
|
100
|
+
- The MCP server is a strict, stateless, per-request pass-through. It forwards your `Authorization` header to the Kaminari Ad API verbatim and stores no per-tenant state between requests.
|
|
101
|
+
- No caches, no in-memory data indexed by anything tenant-related.
|
|
102
|
+
- `KAMINARI_AD_API_KEY` env var is rejected on startup in HTTP mode (stdio only) — no default fallback token exists.
|
|
103
|
+
- Session IDs are bound to the SHA-256 of the Bearer that initialized them; reuse with a different Bearer is rejected.
|
|
104
|
+
- Bearers are never logged. Only their 8-character hash prefix is recorded for correlation.
|
|
105
|
+
- See [`tests/isolation/`](tests/isolation) for the regression suite that enforces every rule above on each CI run.
|
|
106
|
+
|
|
107
|
+
To report a security issue, see [SECURITY.md](SECURITY.md).
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Development
|
|
112
|
+
|
|
113
|
+
The Docker path (no local Node required for the build, but see [CONTRIBUTING](CONTRIBUTING.md) for the host-side commit hooks):
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
make check # lint + format-check + typecheck + arch-gates + test-cov
|
|
117
|
+
make test # full test suite
|
|
118
|
+
make test-unit # unit only
|
|
119
|
+
make test-isolation # tenant-isolation suite
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Or directly with `npm` if you have Node 22 LTS on the host (matches `.nvmrc` / `engines.node`):
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
npm ci --legacy-peer-deps
|
|
126
|
+
npm run lint && npm run typecheck && npm test
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for the development workflow and how to add a tool.
|
|
130
|
+
|
|
131
|
+
> The maintainers run the full development gate (integration tests, deploy automation, prod smoke) on a private GitLab instance and mirror the repo to GitHub. The public CI on GitHub Actions ([`.github/workflows/ci.yml`](.github/workflows/ci.yml)) runs lint + typecheck + unit tests + build + bundle-size check on every community PR, so contributors get fast green/red feedback without needing access to the internal infra. Tag pushes (`v*.*.*`) trigger [`.github/workflows/release.yml`](.github/workflows/release.yml), which publishes the package to npm with OIDC provenance and creates the GitHub Release.
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Stability
|
|
136
|
+
|
|
137
|
+
The **public surface** of this package is:
|
|
138
|
+
|
|
139
|
+
1. The **CLI binary** `kaminari-ad-mcp` and its `--transport stdio|http` flag, the env vars documented in `.env.example`, and the exit codes (0 / 1 fatal / 2 invalid config).
|
|
140
|
+
2. The **MCP wire protocol** as implemented by every registered tool (tool names, input schemas, output shapes, annotations). Tools deprecated in a future major version will keep working for at least one minor version with a console warning.
|
|
141
|
+
|
|
142
|
+
Everything else — the TypeScript types exported from `dist/bin.d.ts`, deep imports, internal class shapes — is **not** part of the public contract and may change in any release. Treat this package as a CLI, not a library.
|
|
143
|
+
|
|
144
|
+
We follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html) for the two items above. See [`CHANGELOG.md`](CHANGELOG.md) for the per-release record.
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Privacy
|
|
149
|
+
|
|
150
|
+
- **Data collected by the MCP server itself:** none beyond the `Authorization` header it forwards. In HTTP mode, only ephemeral per-request scoped state (session id ↔ Bearer hash, leaky-bucket rate limit by Bearer hash) is held in memory.
|
|
151
|
+
- **Data forwarded to Kaminari Ad:** every tool call is a thin pass-through to `/api/v1` over HTTPS. The Kaminari Ad privacy policy applies: [https://kaminari.ad/legal/privacy](https://kaminari.ad/legal/privacy).
|
|
152
|
+
- **Logs:** structured pino output, JSON in HTTP mode. The full Bearer token is redacted; only `bearer_hash = sha256(token).slice(0,8)` makes it into a log line, alongside `request_id`, `tool_name`, `api_status`, `elapsed_ms`. Tool inputs (which may contain customer scan IDs / URLs) are NOT logged.
|
|
153
|
+
- **Telemetry:** none. The OSS build ships a `NoopErrorReporter`. We do not bundle Sentry, OpenTelemetry exporters, or PostHog.
|
|
154
|
+
|
|
155
|
+
To report a security or privacy issue, see [SECURITY.md](SECURITY.md).
|
|
156
|
+
|
|
157
|
+
## License
|
|
158
|
+
|
|
159
|
+
MIT — see [LICENSE](LICENSE).
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Reporting a Vulnerability
|
|
4
|
+
|
|
5
|
+
If you discover a security vulnerability in `@kaminari-ad/mcp` — particularly anything related to **cross-tenant data leakage** in the hosted HTTP endpoint — please report it privately.
|
|
6
|
+
|
|
7
|
+
**Do NOT** open a public GitHub or GitLab issue, and do not discuss it in chat channels.
|
|
8
|
+
|
|
9
|
+
### How to report
|
|
10
|
+
|
|
11
|
+
Email **security@kaminari.ad** with:
|
|
12
|
+
|
|
13
|
+
- A clear description of the issue.
|
|
14
|
+
- Steps to reproduce (minimal repro preferred).
|
|
15
|
+
- Affected version(s) (`npm view @kaminari-ad/mcp version` and/or the `mcp.kaminari.ad` deployment time, if known).
|
|
16
|
+
- Your assessment of impact (data leak, auth bypass, DoS, etc.).
|
|
17
|
+
- Whether you would like to be credited in the security advisory.
|
|
18
|
+
|
|
19
|
+
You should receive an acknowledgement within 2 business days.
|
|
20
|
+
|
|
21
|
+
### Disclosure timeline
|
|
22
|
+
|
|
23
|
+
We follow a coordinated 90-day disclosure window:
|
|
24
|
+
|
|
25
|
+
- **Day 0**: report received and acknowledged.
|
|
26
|
+
- **Day 0-14**: triage and reproduction; severity assessment.
|
|
27
|
+
- **Day 14-60**: fix developed, reviewed, tested, including a regression test in `tests/isolation/`.
|
|
28
|
+
- **Day 60-90**: patched release published to npm; deployed to `mcp.kaminari.ad`; coordinated public disclosure with the reporter.
|
|
29
|
+
- After **Day 90**, we will publish a security advisory regardless of whether a fix has shipped, so users can take their own mitigations.
|
|
30
|
+
|
|
31
|
+
If the issue is being actively exploited in the wild, the timeline is compressed and we coordinate the disclosure with the reporter directly.
|
|
32
|
+
|
|
33
|
+
## Scope
|
|
34
|
+
|
|
35
|
+
Vulnerabilities of particular interest:
|
|
36
|
+
|
|
37
|
+
- **Tenant isolation breaches** in the HTTP transport: any way for one client's request to receive data from another client's session, log, cache, or in-flight request.
|
|
38
|
+
- **Token leakage**: any way a Bearer token can be reconstructed from logs, error responses, telemetry, or process memory exposed to operators.
|
|
39
|
+
- **Auth bypass**: any way to call a tool without a valid `Authorization` header or with a header that has been mutated by the MCP server.
|
|
40
|
+
- **Header injection**: any way to influence the outbound request to the Kaminari Ad API by injecting `X-*` headers, cookies, or other channels.
|
|
41
|
+
- **Supply-chain compromise**: any anomaly in the published npm package, the Docker image, or the GitLab/GitHub release pipeline.
|
|
42
|
+
|
|
43
|
+
Out of scope:
|
|
44
|
+
|
|
45
|
+
- Issues in the Kaminari Ad API itself (`/api/v1`). The API enforces its own authorization. Report API issues to the same `security@kaminari.ad` address, but note they live in the `api/` repository, not here.
|
|
46
|
+
- Issues in third-party MCP clients (Cursor, Claude Desktop, etc.).
|
|
47
|
+
- Theoretical issues with no demonstrable exploit path.
|
|
48
|
+
|
|
49
|
+
## Recognition
|
|
50
|
+
|
|
51
|
+
With your permission, we will acknowledge your contribution in the [`CHANGELOG.md`](CHANGELOG.md) and in the GitHub security advisory.
|
package/dist/bin.d.ts
ADDED