@kaminari-ad/mcp 0.5.2 → 0.7.2
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 +21 -0
- package/README.md +1 -1
- package/dist/bin.js +3 -5
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-3CY2WEZF.js → chunk-NRDA7DQQ.js} +92 -36
- package/dist/chunk-NRDA7DQQ.js.map +1 -0
- package/dist/{chunk-SQVFFWOB.js → chunk-VTKVXW4G.js} +3 -3
- package/dist/{chunk-SQVFFWOB.js.map → chunk-VTKVXW4G.js.map} +1 -1
- package/dist/{http-bootstrap-X2Y2VGBO.js → http-bootstrap-7VZLKAM4.js} +32 -159
- package/dist/http-bootstrap-7VZLKAM4.js.map +1 -0
- package/dist/{stdio-bootstrap-JBLNH46R.js → stdio-bootstrap-CE2Y76DX.js} +4 -4
- package/dist/{stdio-bootstrap-JBLNH46R.js.map → stdio-bootstrap-CE2Y76DX.js.map} +1 -1
- package/package.json +1 -1
- package/dist/chunk-3CY2WEZF.js.map +0 -1
- package/dist/http-bootstrap-X2Y2VGBO.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.6.0] - 2026-06-30
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- **HTTP transport is now stateless** (`StreamableHTTPServerTransport` with
|
|
15
|
+
`sessionIdGenerator: undefined`). The server no longer issues an
|
|
16
|
+
`Mcp-Session-Id`; it builds a fresh, single-use MCP server + transport per
|
|
17
|
+
request and authenticates each request independently by its own Bearer. This
|
|
18
|
+
lets `mcp.kaminari.ad` run multiple replicas behind a round-robin load
|
|
19
|
+
balancer with no sticky sessions and no shared session store — fixing the
|
|
20
|
+
mid-session failures that appeared after the Traefik edge cutover. It aligns
|
|
21
|
+
with the MCP stateless direction (SEP-2575 / SEP-2567) and improves
|
|
22
|
+
ChatGPT-connector compatibility (the connector opens a new session per tool
|
|
23
|
+
call, which a stateful server handles poorly).
|
|
24
|
+
|
|
25
|
+
### Removed
|
|
26
|
+
|
|
27
|
+
- In-memory session store, the session-id <-> bearer binding, and the
|
|
28
|
+
`KAMINARI_AD_SESSION_TTL_SEC` env var — all obsolete in stateless mode. The
|
|
29
|
+
only remaining mutable store is the per-bearer rate limiter.
|
|
30
|
+
|
|
10
31
|
## [0.5.2] - 2026-06-20
|
|
11
32
|
|
|
12
33
|
### Security
|
package/README.md
CHANGED
|
@@ -198,7 +198,7 @@ We follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html) for the two
|
|
|
198
198
|
|
|
199
199
|
## Privacy
|
|
200
200
|
|
|
201
|
-
- **Data collected by the MCP server itself:** none beyond the `Authorization` header it forwards.
|
|
201
|
+
- **Data collected by the MCP server itself:** none beyond the `Authorization` header it forwards. The HTTP transport is stateless — no sessions are persisted; each request is authenticated independently by its own Bearer. The only in-memory state is the leaky-bucket rate limiter keyed by `sha256(bearer)`.
|
|
202
202
|
- **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).
|
|
203
203
|
- **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.
|
|
204
204
|
- **Telemetry:** none. The OSS build ships a `NoopErrorReporter`. We do not bundle Sentry, OpenTelemetry exporters, or PostHog.
|
package/dist/bin.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { NAME, VERSION, err, ok } from './chunk-
|
|
2
|
+
import { NAME, VERSION, err, ok } from './chunk-VTKVXW4G.js';
|
|
3
3
|
import process from 'process';
|
|
4
4
|
import { z } from 'zod';
|
|
5
5
|
|
|
@@ -69,7 +69,6 @@ var RawSchema = z.object({
|
|
|
69
69
|
KAMINARI_AD_LOG_LEVEL: LogLevelSchema.default("info"),
|
|
70
70
|
KAMINARI_AD_LOG_FORMAT: LogFormatSchema.optional(),
|
|
71
71
|
KAMINARI_AD_HTTP_PORT: z.coerce.number().int().min(0).max(65535).default(8080),
|
|
72
|
-
KAMINARI_AD_SESSION_TTL_SEC: z.coerce.number().int().min(60).max(86400).default(1800),
|
|
73
72
|
KAMINARI_AD_RATE_LIMIT_RPM: z.coerce.number().int().min(1).max(1e4).default(120),
|
|
74
73
|
KAMINARI_AD_API_KEY: z.string().min(8).optional(),
|
|
75
74
|
// OAuth discovery (RFC 9728 protected-resource + RFC 8414 AS
|
|
@@ -98,7 +97,6 @@ function loadConfig(env) {
|
|
|
98
97
|
logLevel: raw.KAMINARI_AD_LOG_LEVEL,
|
|
99
98
|
logFormat,
|
|
100
99
|
httpPort: raw.KAMINARI_AD_HTTP_PORT,
|
|
101
|
-
sessionTtlSec: raw.KAMINARI_AD_SESSION_TTL_SEC,
|
|
102
100
|
rateLimitRpm: raw.KAMINARI_AD_RATE_LIMIT_RPM,
|
|
103
101
|
stdioApiKey: raw.KAMINARI_AD_API_KEY,
|
|
104
102
|
oauthProtectedResource: raw.KAMINARI_AD_OAUTH_PROTECTED_RESOURCE,
|
|
@@ -162,10 +160,10 @@ async function main() {
|
|
|
162
160
|
}
|
|
163
161
|
const config = configResult.value;
|
|
164
162
|
if (config.transport === "stdio") {
|
|
165
|
-
const { bootstrapStdio } = await import('./stdio-bootstrap-
|
|
163
|
+
const { bootstrapStdio } = await import('./stdio-bootstrap-CE2Y76DX.js');
|
|
166
164
|
return bootstrapStdio(config);
|
|
167
165
|
}
|
|
168
|
-
const { bootstrapHttp } = await import('./http-bootstrap-
|
|
166
|
+
const { bootstrapHttp } = await import('./http-bootstrap-7VZLKAM4.js');
|
|
169
167
|
return bootstrapHttp(config);
|
|
170
168
|
}
|
|
171
169
|
main().then(
|
package/dist/bin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/shared/check-node-version.ts","../src/shared/oauth-scope-catalogue.ts","../src/shared/config.ts","../src/bin.ts"],"names":[],"mappings":"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","file":"bin.js","sourcesContent":["/**\n * Pure Node-version preflight, extracted from {@link bin.ts} so it\n * can be unit-tested without `process.exit` side effects.\n *\n * Why this exists: `undici@8.x` removed feature probes in v8.0.3 and\n * imports `markAsUncloneable` from `node:worker_threads`\n * unconditionally — that symbol only exists on Node 22.19+. Older\n * Node versions crash at import time with the cryptic message\n * `webidl.util.markAsUncloneable is not a function`. We surface a\n * clean upgrade message instead, BEFORE any dynamic import pulls\n * undici.\n *\n * Keep {@link REQUIRED_NODE_MAJOR} / {@link REQUIRED_NODE_MINOR} in\n * sync with `engines.node` in `package.json` (currently\n * `>=22.19.0`).\n */\n\nimport { err, ok, type Result } from \"./result.js\";\nimport { NAME } from \"./version.js\";\n\nexport const REQUIRED_NODE_MAJOR = 22;\nexport const REQUIRED_NODE_MINOR = 19;\n\n/**\n * Check whether `nodeVersion` (e.g. `\"22.19.0\"`, the value of\n * `process.versions.node`) satisfies the package's runtime floor.\n *\n * Returns `Ok(undefined)` on pass, `Err(message)` with a multi-line\n * upgrade message on fail. The message is plain text ready to be\n * written to stderr verbatim.\n */\nexport function checkNodeVersion(nodeVersion: string): Result<undefined, string> {\n const parts = nodeVersion.split(\".\");\n const major = Number(parts[0]);\n const minor = Number(parts[1]);\n if (\n Number.isNaN(major) ||\n Number.isNaN(minor) ||\n major < REQUIRED_NODE_MAJOR ||\n (major === REQUIRED_NODE_MAJOR && minor < REQUIRED_NODE_MINOR)\n ) {\n const required = `>=${String(REQUIRED_NODE_MAJOR)}.${String(REQUIRED_NODE_MINOR)}.0`;\n return err(\n [\n `${NAME} requires Node.js ${required} (you have v${nodeVersion}).`,\n \"\",\n \"The underlying undici 8.x HTTP client uses markAsUncloneable\",\n \"from node:worker_threads, available only on Node 22.19+.\",\n \"Older Node crashes at import time with the cryptic message\",\n \"`webidl.util.markAsUncloneable is not a function`.\",\n \"\",\n \"Please upgrade Node and re-run: https://nodejs.org/en/download\",\n ].join(\"\\n\")\n );\n }\n return ok(undefined);\n}\n","/**\n * Canonical catalogue of OAuth 2.0 scopes the Kaminari Ad MCP server\n * advertises in its protected-resource metadata (RFC 9728) and\n * `WWW-Authenticate: Bearer scope=\"...\"` challenge.\n *\n * The catalogue is intentionally static and lives in `shared/` because\n * (a) it has no runtime behaviour — it is data — and (b) it is read by\n * both the `protected-resource-metadata-handler` and the\n * `www-authenticate` builder. If a new scope is added on the\n * Authorization Server side, mirror it here in the same release so the\n * Resource Server (this process) advertises it.\n *\n * `offline_access` is part of the catalogue per RFC 6749 §6 because\n * the Authorization Server issues refresh tokens only when the client\n * requests this scope. Anthropic's Claude clients append it\n * automatically when the server advertises it in\n * `scopes_supported`/the `WWW-Authenticate` `scope=` parameter.\n */\n\n/**\n * Default scope catalogue, frozen at build time.\n *\n * MUST stay byte-identical to the Authorization Server's advertised\n * catalogue (`app.oauth_authorization.domain.entities.scope.advertised_scopes()`\n * — i.e. `sorted(KNOWN_SCOPES)`). The order is lexicographic, which\n * happens to yield the desired semantics for free: `:read` sorts before\n * `:write` within a resource, and `offline_access` sorts after every\n * `mcp:` scope.\n *\n * Read scopes grant the resource's view permission; write scopes grant\n * read + the resource's mutating permissions. billing/invoicing/account\n * are read-only over OAuth by design (money-spend / privilege-escalation\n * tools stay API-key only). When the AS catalogue changes, mirror it\n * here in the same release — `tests/unit/shared/oauth-scope-catalogue.test.ts`\n * pins the exact list so drift fails CI.\n */\nexport const DEFAULT_OAUTH_SCOPES: readonly string[] = Object.freeze([\n \"mcp:account:read\",\n \"mcp:alert_notifications:read\",\n \"mcp:alert_notifications:write\",\n \"mcp:alerts:read\",\n \"mcp:alerts:write\",\n \"mcp:billing:read\",\n \"mcp:campaigns:read\",\n \"mcp:campaigns:write\",\n \"mcp:custom_rules:read\",\n \"mcp:custom_rules:write\",\n \"mcp:invoicing:read\",\n \"mcp:policies:read\",\n \"mcp:policies:write\",\n \"mcp:scans:read\",\n \"mcp:scans:write\",\n \"mcp:tags:read\",\n \"mcp:tags:write\",\n \"mcp:taxonomies:read\",\n \"mcp:taxonomies:write\",\n \"mcp:webhooks:read\",\n \"mcp:webhooks:write\",\n \"offline_access\",\n]);\n","/**\n * Runtime configuration for `@kaminari-ad/mcp`.\n *\n * The single source of truth for all env-var reads. `process.env.X`\n * outside this file is a code-review blocker — every config value flows\n * through the zod-parsed {@link Config} object.\n */\n\nimport { z } from \"zod\";\n\nimport { DEFAULT_OAUTH_SCOPES } from \"./oauth-scope-catalogue.js\";\nimport { err, ok, type Result } from \"./result.js\";\n\n/**\n * MCP transport. `stdio` is for local install (one process per user);\n * `http` is for the hosted multi-tenant endpoint.\n */\nexport const TransportSchema = z.enum([\"stdio\", \"http\"]);\nexport type Transport = z.infer<typeof TransportSchema>;\n\n/**\n * pino log level.\n */\nexport const LogLevelSchema = z.enum([\"trace\", \"debug\", \"info\", \"warn\", \"error\", \"fatal\"]);\nexport type LogLevel = z.infer<typeof LogLevelSchema>;\n\n/**\n * pino log output format.\n *\n * - `pretty` — colorised single-line ANSI output for terminals. Default\n * in stdio mode (operator reads stderr in their shell).\n * - `json` — newline-delimited JSON, one record per line. Default in\n * HTTP mode (Docker/k8s log aggregators expect JSON). MCP hosts that\n * capture stderr from the stdio process (Cursor, Claude Desktop)\n * parse much better with `json` — flip this when the operator is a\n * machine, not a human.\n */\nexport const LogFormatSchema = z.enum([\"pretty\", \"json\"]);\nexport type LogFormat = z.infer<typeof LogFormatSchema>;\n\n/**\n * Parsed configuration. All fields are `readonly` — mutation post-load\n * is a bug.\n */\nexport interface Config {\n readonly transport: Transport;\n readonly apiBaseUrl: string;\n readonly logLevel: LogLevel;\n readonly logFormat: LogFormat;\n readonly httpPort: number;\n readonly sessionTtlSec: number;\n readonly rateLimitRpm: number;\n /**\n * Set only in stdio mode. In HTTP mode the bootstrap MUST assert\n * this is `undefined` (see CONTRIBUTING.md \"Tenant isolation\" §5).\n */\n readonly stdioApiKey: string | undefined;\n /**\n * Canonical MCP endpoint URL advertised as the `resource` field of\n * the RFC 9728 protected-resource metadata document. Anthropic's\n * Claude clients require this to match the URL the user enters in\n * Claude exactly (including the path component).\n */\n readonly oauthProtectedResource: string;\n /**\n * Absolute URL where this server serves the protected-resource\n * metadata JSON. Advertised back to clients via\n * `WWW-Authenticate: Bearer resource_metadata=\"…\"` on 401.\n */\n readonly oauthProtectedResourceMetadataUrl: string;\n /**\n * Issuer URL of the Authorization Server that mints access tokens\n * accepted by this Resource Server. Advertised as the first (and\n * only) entry of the metadata's `authorization_servers` array.\n */\n readonly oauthAuthorizationServerUrl: string;\n /**\n * Scopes the Resource Server advertises in both the metadata's\n * `scopes_supported` field and the 401 `WWW-Authenticate scope=…`\n * parameter. Defaults to {@link DEFAULT_OAUTH_SCOPES}; operators\n * may override via `KAMINARI_AD_OAUTH_SCOPES` (space-separated).\n */\n readonly oauthScopes: readonly string[];\n}\n\n/**\n * Every env var carries the `KAMINARI_AD_` namespace prefix.\n *\n * Why: this process inherits the host's full environment (Cursor /\n * Claude Desktop / Docker compose / k8s pod), where `API_BASE_URL` /\n * `LOG_LEVEL` / `HTTP_PORT` are common generic names. Without a\n * namespace, another tool in the same shell trivially poisons our\n * config — e.g. `LOG_LEVEL=debug` set for some app library suddenly\n * makes the MCP server log every secret-redacted-but-still-noisy\n * request to stderr, and the user has no idea why. With the prefix\n * the boundary is explicit and `env | grep KAMINARI_AD_` enumerates\n * exactly what we read.\n */\nconst RawSchema = z.object({\n KAMINARI_AD_TRANSPORT: TransportSchema.default(\"stdio\"),\n // `app.kaminari.ad` is the API host; `kaminari.ad` (no subdomain)\n // is the marketing landing page and does NOT serve /api/v1/*.\n // Setting this wrong (e.g. the root domain) makes every tool call\n // fail with HTTP 404 — the symptom that surfaced this default bug\n // in v0.2.0.\n KAMINARI_AD_API_URL: z.string().url().default(\"https://app.kaminari.ad\"),\n KAMINARI_AD_LOG_LEVEL: LogLevelSchema.default(\"info\"),\n KAMINARI_AD_LOG_FORMAT: LogFormatSchema.optional(),\n KAMINARI_AD_HTTP_PORT: z.coerce.number().int().min(0).max(65535).default(8080),\n KAMINARI_AD_SESSION_TTL_SEC: z.coerce.number().int().min(60).max(86_400).default(1800),\n KAMINARI_AD_RATE_LIMIT_RPM: z.coerce.number().int().min(1).max(10_000).default(120),\n KAMINARI_AD_API_KEY: z.string().min(8).optional(),\n // OAuth discovery (RFC 9728 protected-resource + RFC 8414 AS\n // metadata). All three URLs default to the production deployment;\n // local/staging operators override per-environment.\n KAMINARI_AD_OAUTH_PROTECTED_RESOURCE: z.string().url().default(\"https://mcp.kaminari.ad/mcp\"),\n KAMINARI_AD_OAUTH_PROTECTED_RESOURCE_METADATA_URL: z\n .string()\n .url()\n .default(\"https://mcp.kaminari.ad/.well-known/oauth-protected-resource\"),\n KAMINARI_AD_OAUTH_AUTHORIZATION_SERVER_URL: z.string().url().default(\"https://app.kaminari.ad\"),\n // Space-separated scope list. We split on /\\s+/ post-parse and drop\n // empty tokens so accidental double-spaces don't corrupt the array.\n KAMINARI_AD_OAUTH_SCOPES: z.string().optional(),\n});\n\n/**\n * Parse and validate environment variables into a {@link Config}.\n *\n * Returns `Err<ConfigError>` on validation failure. Caller (typically\n * `bin.ts`) prints the error and exits non-zero.\n *\n * `KAMINARI_AD_LOG_FORMAT` defaults are transport-dependent: `pretty`\n * for stdio (terminal-facing) and `json` for http (aggregator-facing).\n * If the user sets the env var explicitly, that wins.\n */\nexport function loadConfig(env: NodeJS.ProcessEnv): Result<Config, ConfigError> {\n const parsed = RawSchema.safeParse(env);\n if (!parsed.success) {\n return err({ kind: \"invalid\", issues: parsed.error.flatten().fieldErrors });\n }\n const raw = parsed.data;\n const transport = raw.KAMINARI_AD_TRANSPORT;\n const logFormat = raw.KAMINARI_AD_LOG_FORMAT ?? (transport === \"stdio\" ? \"pretty\" : \"json\");\n const oauthScopesRaw = raw.KAMINARI_AD_OAUTH_SCOPES;\n const oauthScopes: readonly string[] =\n oauthScopesRaw === undefined\n ? DEFAULT_OAUTH_SCOPES\n : Object.freeze(oauthScopesRaw.split(/\\s+/).filter((s) => s.length > 0));\n return ok({\n transport,\n apiBaseUrl: raw.KAMINARI_AD_API_URL,\n logLevel: raw.KAMINARI_AD_LOG_LEVEL,\n logFormat,\n httpPort: raw.KAMINARI_AD_HTTP_PORT,\n sessionTtlSec: raw.KAMINARI_AD_SESSION_TTL_SEC,\n rateLimitRpm: raw.KAMINARI_AD_RATE_LIMIT_RPM,\n stdioApiKey: raw.KAMINARI_AD_API_KEY,\n oauthProtectedResource: raw.KAMINARI_AD_OAUTH_PROTECTED_RESOURCE,\n oauthProtectedResourceMetadataUrl: raw.KAMINARI_AD_OAUTH_PROTECTED_RESOURCE_METADATA_URL,\n oauthAuthorizationServerUrl: raw.KAMINARI_AD_OAUTH_AUTHORIZATION_SERVER_URL,\n oauthScopes,\n });\n}\n\n/**\n * Errors from {@link loadConfig}.\n */\nexport interface ConfigError {\n readonly kind: \"invalid\";\n readonly issues: Readonly<Record<string, readonly string[] | undefined>>;\n}\n","/**\n * CLI entrypoint for `@kaminari-ad/mcp`.\n *\n * Dispatches to a transport-specific composition root (stdio or HTTP)\n * based on the `--transport` flag (or `TRANSPORT` env var, with stdio\n * as the default).\n *\n * Wire-up is intentionally minimal — every dependency is constructed\n * inside the chosen bootstrap, never here. This file just parses argv\n * and config, then hands off.\n *\n * Imports policy: the STATIC imports below MUST stay free of any\n * runtime dependency that requires Node >= 22.19 (undici, MCP SDK,\n * pino). Otherwise the Node-version preflight in `main()` runs too\n * late — those modules crash at import time on older Node. The\n * transport bootstraps pull undici / MCP SDK / pino via dynamic\n * `await import()` so the preflight gets to fire first.\n */\n\nimport process from \"node:process\";\n\nimport { checkNodeVersion } from \"./shared/check-node-version.js\";\nimport { loadConfig, type Transport } from \"./shared/config.js\";\nimport { NAME, VERSION } from \"./shared/version.js\";\n\nfunction parseTransportFlag(argv: readonly string[]): Transport | undefined {\n for (const arg of argv) {\n if (arg === \"--transport=stdio\" || arg === \"--transport=http\") {\n return arg === \"--transport=stdio\" ? \"stdio\" : \"http\";\n }\n if (arg.startsWith(\"--transport=\")) {\n const value = arg.slice(\"--transport=\".length);\n if (value === \"stdio\" || value === \"http\") return value;\n }\n }\n return undefined;\n}\n\nasync function main(): Promise<number> {\n const argv = process.argv.slice(2);\n\n // Informational flags handled BEFORE the Node preflight so users on\n // any Node can confirm the binary resolves and discover the help\n // text — the only paths that touch zero runtime deps.\n if (argv.includes(\"--version\") || argv.includes(\"-v\")) {\n process.stdout.write(`${NAME} ${VERSION}\\n`);\n return 0;\n }\n if (argv.includes(\"--help\") || argv.includes(\"-h\")) {\n process.stdout.write(\n [\n `${NAME} ${VERSION}`,\n ``,\n `Usage:`,\n ` kaminari-ad-mcp [--transport=stdio|http]`,\n ``,\n `Transports:`,\n ` stdio (default) Local install — auth via KAMINARI_AD_API_KEY env var.`,\n ` http Hosted multi-tenant server — auth via incoming Authorization header.`,\n ``,\n `Configuration is documented in .env.example.`,\n ``,\n ].join(\"\\n\")\n );\n return 0;\n }\n\n // Preflight: bail with a clean upgrade message BEFORE any dynamic\n // import pulls undici / MCP SDK / pino — all three require\n // Node 22.19+ and crash at import time on older Node. Without this\n // gate users see undici's cryptic\n // `webidl.util.markAsUncloneable is not a function` instead.\n const check = checkNodeVersion(process.versions.node);\n if (check.isErr()) {\n process.stderr.write(`${check.error}\\n`);\n return 2;\n }\n\n const cliTransport = parseTransportFlag(argv);\n const env: NodeJS.ProcessEnv = cliTransport\n ? { ...process.env, KAMINARI_AD_TRANSPORT: cliTransport }\n : process.env;\n\n const configResult = loadConfig(env);\n if (configResult.isErr()) {\n process.stderr.write(`Invalid configuration: ${JSON.stringify(configResult.error.issues)}\\n`);\n return 2;\n }\n const config = configResult.value;\n\n if (config.transport === \"stdio\") {\n const { bootstrapStdio } = await import(\"./presentation/stdio/stdio-bootstrap.js\");\n return bootstrapStdio(config);\n }\n const { bootstrapHttp } = await import(\"./presentation/http/http-bootstrap.js\");\n return bootstrapHttp(config);\n}\n\nmain().then(\n (code) => {\n process.exit(code);\n },\n (error: unknown) => {\n process.stderr.write(`Fatal: ${error instanceof Error ? error.message : String(error)}\\n`);\n process.exit(1);\n }\n);\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/shared/check-node-version.ts","../src/shared/oauth-scope-catalogue.ts","../src/shared/config.ts","../src/bin.ts"],"names":[],"mappings":"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","file":"bin.js","sourcesContent":["/**\n * Pure Node-version preflight, extracted from {@link bin.ts} so it\n * can be unit-tested without `process.exit` side effects.\n *\n * Why this exists: `undici@8.x` removed feature probes in v8.0.3 and\n * imports `markAsUncloneable` from `node:worker_threads`\n * unconditionally — that symbol only exists on Node 22.19+. Older\n * Node versions crash at import time with the cryptic message\n * `webidl.util.markAsUncloneable is not a function`. We surface a\n * clean upgrade message instead, BEFORE any dynamic import pulls\n * undici.\n *\n * Keep {@link REQUIRED_NODE_MAJOR} / {@link REQUIRED_NODE_MINOR} in\n * sync with `engines.node` in `package.json` (currently\n * `>=22.19.0`).\n */\n\nimport { err, ok, type Result } from \"./result.js\";\nimport { NAME } from \"./version.js\";\n\nexport const REQUIRED_NODE_MAJOR = 22;\nexport const REQUIRED_NODE_MINOR = 19;\n\n/**\n * Check whether `nodeVersion` (e.g. `\"22.19.0\"`, the value of\n * `process.versions.node`) satisfies the package's runtime floor.\n *\n * Returns `Ok(undefined)` on pass, `Err(message)` with a multi-line\n * upgrade message on fail. The message is plain text ready to be\n * written to stderr verbatim.\n */\nexport function checkNodeVersion(nodeVersion: string): Result<undefined, string> {\n const parts = nodeVersion.split(\".\");\n const major = Number(parts[0]);\n const minor = Number(parts[1]);\n if (\n Number.isNaN(major) ||\n Number.isNaN(minor) ||\n major < REQUIRED_NODE_MAJOR ||\n (major === REQUIRED_NODE_MAJOR && minor < REQUIRED_NODE_MINOR)\n ) {\n const required = `>=${String(REQUIRED_NODE_MAJOR)}.${String(REQUIRED_NODE_MINOR)}.0`;\n return err(\n [\n `${NAME} requires Node.js ${required} (you have v${nodeVersion}).`,\n \"\",\n \"The underlying undici 8.x HTTP client uses markAsUncloneable\",\n \"from node:worker_threads, available only on Node 22.19+.\",\n \"Older Node crashes at import time with the cryptic message\",\n \"`webidl.util.markAsUncloneable is not a function`.\",\n \"\",\n \"Please upgrade Node and re-run: https://nodejs.org/en/download\",\n ].join(\"\\n\")\n );\n }\n return ok(undefined);\n}\n","/**\n * Canonical catalogue of OAuth 2.0 scopes the Kaminari Ad MCP server\n * advertises in its protected-resource metadata (RFC 9728) and\n * `WWW-Authenticate: Bearer scope=\"...\"` challenge.\n *\n * The catalogue is intentionally static and lives in `shared/` because\n * (a) it has no runtime behaviour — it is data — and (b) it is read by\n * both the `protected-resource-metadata-handler` and the\n * `www-authenticate` builder. If a new scope is added on the\n * Authorization Server side, mirror it here in the same release so the\n * Resource Server (this process) advertises it.\n *\n * `offline_access` is part of the catalogue per RFC 6749 §6 because\n * the Authorization Server issues refresh tokens only when the client\n * requests this scope. Anthropic's Claude clients append it\n * automatically when the server advertises it in\n * `scopes_supported`/the `WWW-Authenticate` `scope=` parameter.\n */\n\n/**\n * Default scope catalogue, frozen at build time.\n *\n * MUST stay byte-identical to the Authorization Server's advertised\n * catalogue (`app.oauth_authorization.domain.entities.scope.advertised_scopes()`\n * — i.e. `sorted(KNOWN_SCOPES)`). The order is lexicographic, which\n * happens to yield the desired semantics for free: `:read` sorts before\n * `:write` within a resource, and `offline_access` sorts after every\n * `mcp:` scope.\n *\n * Read scopes grant the resource's view permission; write scopes grant\n * read + the resource's mutating permissions. billing/invoicing/account\n * are read-only over OAuth by design (money-spend / privilege-escalation\n * tools stay API-key only). When the AS catalogue changes, mirror it\n * here in the same release — `tests/unit/shared/oauth-scope-catalogue.test.ts`\n * pins the exact list so drift fails CI.\n */\nexport const DEFAULT_OAUTH_SCOPES: readonly string[] = Object.freeze([\n \"mcp:account:read\",\n \"mcp:alert_notifications:read\",\n \"mcp:alert_notifications:write\",\n \"mcp:alerts:read\",\n \"mcp:alerts:write\",\n \"mcp:billing:read\",\n \"mcp:campaigns:read\",\n \"mcp:campaigns:write\",\n \"mcp:custom_rules:read\",\n \"mcp:custom_rules:write\",\n \"mcp:invoicing:read\",\n \"mcp:policies:read\",\n \"mcp:policies:write\",\n \"mcp:scans:read\",\n \"mcp:scans:write\",\n \"mcp:tags:read\",\n \"mcp:tags:write\",\n \"mcp:taxonomies:read\",\n \"mcp:taxonomies:write\",\n \"mcp:webhooks:read\",\n \"mcp:webhooks:write\",\n \"offline_access\",\n]);\n","/**\n * Runtime configuration for `@kaminari-ad/mcp`.\n *\n * The single source of truth for all env-var reads. `process.env.X`\n * outside this file is a code-review blocker — every config value flows\n * through the zod-parsed {@link Config} object.\n */\n\nimport { z } from \"zod\";\n\nimport { DEFAULT_OAUTH_SCOPES } from \"./oauth-scope-catalogue.js\";\nimport { err, ok, type Result } from \"./result.js\";\n\n/**\n * MCP transport. `stdio` is for local install (one process per user);\n * `http` is for the hosted multi-tenant endpoint.\n */\nexport const TransportSchema = z.enum([\"stdio\", \"http\"]);\nexport type Transport = z.infer<typeof TransportSchema>;\n\n/**\n * pino log level.\n */\nexport const LogLevelSchema = z.enum([\"trace\", \"debug\", \"info\", \"warn\", \"error\", \"fatal\"]);\nexport type LogLevel = z.infer<typeof LogLevelSchema>;\n\n/**\n * pino log output format.\n *\n * - `pretty` — colorised single-line ANSI output for terminals. Default\n * in stdio mode (operator reads stderr in their shell).\n * - `json` — newline-delimited JSON, one record per line. Default in\n * HTTP mode (Docker/k8s log aggregators expect JSON). MCP hosts that\n * capture stderr from the stdio process (Cursor, Claude Desktop)\n * parse much better with `json` — flip this when the operator is a\n * machine, not a human.\n */\nexport const LogFormatSchema = z.enum([\"pretty\", \"json\"]);\nexport type LogFormat = z.infer<typeof LogFormatSchema>;\n\n/**\n * Parsed configuration. All fields are `readonly` — mutation post-load\n * is a bug.\n */\nexport interface Config {\n readonly transport: Transport;\n readonly apiBaseUrl: string;\n readonly logLevel: LogLevel;\n readonly logFormat: LogFormat;\n readonly httpPort: number;\n readonly rateLimitRpm: number;\n /**\n * Set only in stdio mode. In HTTP mode the bootstrap MUST assert\n * this is `undefined` (see CONTRIBUTING.md \"Tenant isolation\" §5).\n */\n readonly stdioApiKey: string | undefined;\n /**\n * Canonical MCP endpoint URL advertised as the `resource` field of\n * the RFC 9728 protected-resource metadata document. Anthropic's\n * Claude clients require this to match the URL the user enters in\n * Claude exactly (including the path component).\n */\n readonly oauthProtectedResource: string;\n /**\n * Absolute URL where this server serves the protected-resource\n * metadata JSON. Advertised back to clients via\n * `WWW-Authenticate: Bearer resource_metadata=\"…\"` on 401.\n */\n readonly oauthProtectedResourceMetadataUrl: string;\n /**\n * Issuer URL of the Authorization Server that mints access tokens\n * accepted by this Resource Server. Advertised as the first (and\n * only) entry of the metadata's `authorization_servers` array.\n */\n readonly oauthAuthorizationServerUrl: string;\n /**\n * Scopes the Resource Server advertises in both the metadata's\n * `scopes_supported` field and the 401 `WWW-Authenticate scope=…`\n * parameter. Defaults to {@link DEFAULT_OAUTH_SCOPES}; operators\n * may override via `KAMINARI_AD_OAUTH_SCOPES` (space-separated).\n */\n readonly oauthScopes: readonly string[];\n}\n\n/**\n * Every env var carries the `KAMINARI_AD_` namespace prefix.\n *\n * Why: this process inherits the host's full environment (Cursor /\n * Claude Desktop / Docker compose / k8s pod), where `API_BASE_URL` /\n * `LOG_LEVEL` / `HTTP_PORT` are common generic names. Without a\n * namespace, another tool in the same shell trivially poisons our\n * config — e.g. `LOG_LEVEL=debug` set for some app library suddenly\n * makes the MCP server log every secret-redacted-but-still-noisy\n * request to stderr, and the user has no idea why. With the prefix\n * the boundary is explicit and `env | grep KAMINARI_AD_` enumerates\n * exactly what we read.\n */\nconst RawSchema = z.object({\n KAMINARI_AD_TRANSPORT: TransportSchema.default(\"stdio\"),\n // `app.kaminari.ad` is the API host; `kaminari.ad` (no subdomain)\n // is the marketing landing page and does NOT serve /api/v1/*.\n // Setting this wrong (e.g. the root domain) makes every tool call\n // fail with HTTP 404 — the symptom that surfaced this default bug\n // in v0.2.0.\n KAMINARI_AD_API_URL: z.string().url().default(\"https://app.kaminari.ad\"),\n KAMINARI_AD_LOG_LEVEL: LogLevelSchema.default(\"info\"),\n KAMINARI_AD_LOG_FORMAT: LogFormatSchema.optional(),\n KAMINARI_AD_HTTP_PORT: z.coerce.number().int().min(0).max(65535).default(8080),\n KAMINARI_AD_RATE_LIMIT_RPM: z.coerce.number().int().min(1).max(10_000).default(120),\n KAMINARI_AD_API_KEY: z.string().min(8).optional(),\n // OAuth discovery (RFC 9728 protected-resource + RFC 8414 AS\n // metadata). All three URLs default to the production deployment;\n // local/staging operators override per-environment.\n KAMINARI_AD_OAUTH_PROTECTED_RESOURCE: z.string().url().default(\"https://mcp.kaminari.ad/mcp\"),\n KAMINARI_AD_OAUTH_PROTECTED_RESOURCE_METADATA_URL: z\n .string()\n .url()\n .default(\"https://mcp.kaminari.ad/.well-known/oauth-protected-resource\"),\n KAMINARI_AD_OAUTH_AUTHORIZATION_SERVER_URL: z.string().url().default(\"https://app.kaminari.ad\"),\n // Space-separated scope list. We split on /\\s+/ post-parse and drop\n // empty tokens so accidental double-spaces don't corrupt the array.\n KAMINARI_AD_OAUTH_SCOPES: z.string().optional(),\n});\n\n/**\n * Parse and validate environment variables into a {@link Config}.\n *\n * Returns `Err<ConfigError>` on validation failure. Caller (typically\n * `bin.ts`) prints the error and exits non-zero.\n *\n * `KAMINARI_AD_LOG_FORMAT` defaults are transport-dependent: `pretty`\n * for stdio (terminal-facing) and `json` for http (aggregator-facing).\n * If the user sets the env var explicitly, that wins.\n */\nexport function loadConfig(env: NodeJS.ProcessEnv): Result<Config, ConfigError> {\n const parsed = RawSchema.safeParse(env);\n if (!parsed.success) {\n return err({ kind: \"invalid\", issues: parsed.error.flatten().fieldErrors });\n }\n const raw = parsed.data;\n const transport = raw.KAMINARI_AD_TRANSPORT;\n const logFormat = raw.KAMINARI_AD_LOG_FORMAT ?? (transport === \"stdio\" ? \"pretty\" : \"json\");\n const oauthScopesRaw = raw.KAMINARI_AD_OAUTH_SCOPES;\n const oauthScopes: readonly string[] =\n oauthScopesRaw === undefined\n ? DEFAULT_OAUTH_SCOPES\n : Object.freeze(oauthScopesRaw.split(/\\s+/).filter((s) => s.length > 0));\n return ok({\n transport,\n apiBaseUrl: raw.KAMINARI_AD_API_URL,\n logLevel: raw.KAMINARI_AD_LOG_LEVEL,\n logFormat,\n httpPort: raw.KAMINARI_AD_HTTP_PORT,\n rateLimitRpm: raw.KAMINARI_AD_RATE_LIMIT_RPM,\n stdioApiKey: raw.KAMINARI_AD_API_KEY,\n oauthProtectedResource: raw.KAMINARI_AD_OAUTH_PROTECTED_RESOURCE,\n oauthProtectedResourceMetadataUrl: raw.KAMINARI_AD_OAUTH_PROTECTED_RESOURCE_METADATA_URL,\n oauthAuthorizationServerUrl: raw.KAMINARI_AD_OAUTH_AUTHORIZATION_SERVER_URL,\n oauthScopes,\n });\n}\n\n/**\n * Errors from {@link loadConfig}.\n */\nexport interface ConfigError {\n readonly kind: \"invalid\";\n readonly issues: Readonly<Record<string, readonly string[] | undefined>>;\n}\n","/**\n * CLI entrypoint for `@kaminari-ad/mcp`.\n *\n * Dispatches to a transport-specific composition root (stdio or HTTP)\n * based on the `--transport` flag (or `TRANSPORT` env var, with stdio\n * as the default).\n *\n * Wire-up is intentionally minimal — every dependency is constructed\n * inside the chosen bootstrap, never here. This file just parses argv\n * and config, then hands off.\n *\n * Imports policy: the STATIC imports below MUST stay free of any\n * runtime dependency that requires Node >= 22.19 (undici, MCP SDK,\n * pino). Otherwise the Node-version preflight in `main()` runs too\n * late — those modules crash at import time on older Node. The\n * transport bootstraps pull undici / MCP SDK / pino via dynamic\n * `await import()` so the preflight gets to fire first.\n */\n\nimport process from \"node:process\";\n\nimport { checkNodeVersion } from \"./shared/check-node-version.js\";\nimport { loadConfig, type Transport } from \"./shared/config.js\";\nimport { NAME, VERSION } from \"./shared/version.js\";\n\nfunction parseTransportFlag(argv: readonly string[]): Transport | undefined {\n for (const arg of argv) {\n if (arg === \"--transport=stdio\" || arg === \"--transport=http\") {\n return arg === \"--transport=stdio\" ? \"stdio\" : \"http\";\n }\n if (arg.startsWith(\"--transport=\")) {\n const value = arg.slice(\"--transport=\".length);\n if (value === \"stdio\" || value === \"http\") return value;\n }\n }\n return undefined;\n}\n\nasync function main(): Promise<number> {\n const argv = process.argv.slice(2);\n\n // Informational flags handled BEFORE the Node preflight so users on\n // any Node can confirm the binary resolves and discover the help\n // text — the only paths that touch zero runtime deps.\n if (argv.includes(\"--version\") || argv.includes(\"-v\")) {\n process.stdout.write(`${NAME} ${VERSION}\\n`);\n return 0;\n }\n if (argv.includes(\"--help\") || argv.includes(\"-h\")) {\n process.stdout.write(\n [\n `${NAME} ${VERSION}`,\n ``,\n `Usage:`,\n ` kaminari-ad-mcp [--transport=stdio|http]`,\n ``,\n `Transports:`,\n ` stdio (default) Local install — auth via KAMINARI_AD_API_KEY env var.`,\n ` http Hosted multi-tenant server — auth via incoming Authorization header.`,\n ``,\n `Configuration is documented in .env.example.`,\n ``,\n ].join(\"\\n\")\n );\n return 0;\n }\n\n // Preflight: bail with a clean upgrade message BEFORE any dynamic\n // import pulls undici / MCP SDK / pino — all three require\n // Node 22.19+ and crash at import time on older Node. Without this\n // gate users see undici's cryptic\n // `webidl.util.markAsUncloneable is not a function` instead.\n const check = checkNodeVersion(process.versions.node);\n if (check.isErr()) {\n process.stderr.write(`${check.error}\\n`);\n return 2;\n }\n\n const cliTransport = parseTransportFlag(argv);\n const env: NodeJS.ProcessEnv = cliTransport\n ? { ...process.env, KAMINARI_AD_TRANSPORT: cliTransport }\n : process.env;\n\n const configResult = loadConfig(env);\n if (configResult.isErr()) {\n process.stderr.write(`Invalid configuration: ${JSON.stringify(configResult.error.issues)}\\n`);\n return 2;\n }\n const config = configResult.value;\n\n if (config.transport === \"stdio\") {\n const { bootstrapStdio } = await import(\"./presentation/stdio/stdio-bootstrap.js\");\n return bootstrapStdio(config);\n }\n const { bootstrapHttp } = await import(\"./presentation/http/http-bootstrap.js\");\n return bootstrapHttp(config);\n}\n\nmain().then(\n (code) => {\n process.exit(code);\n },\n (error: unknown) => {\n process.stderr.write(`Fatal: ${error instanceof Error ? error.message : String(error)}\\n`);\n process.exit(1);\n }\n);\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { err, ok } from './chunk-
|
|
2
|
+
import { err, ok } from './chunk-VTKVXW4G.js';
|
|
3
3
|
import { createHash, randomUUID } from 'crypto';
|
|
4
4
|
import createClient from 'openapi-fetch';
|
|
5
5
|
import { fetch } from 'undici';
|
|
@@ -63,8 +63,8 @@ var BearerToken = class _BearerToken {
|
|
|
63
63
|
return createHash("sha256").update(this.#raw).digest("hex").slice(0, BEARER_HASH_PREFIX_LEN);
|
|
64
64
|
}
|
|
65
65
|
/**
|
|
66
|
-
* Returns the full SHA-256 hex digest. Used as the
|
|
67
|
-
*
|
|
66
|
+
* Returns the full SHA-256 hex digest. Used as the per-bearer key for
|
|
67
|
+
* the rate limiter. Not for logs.
|
|
68
68
|
*/
|
|
69
69
|
fullHash() {
|
|
70
70
|
return createHash("sha256").update(this.#raw).digest("hex");
|
|
@@ -155,12 +155,16 @@ var OrganizationStatus = z.enum([
|
|
|
155
155
|
"suspended",
|
|
156
156
|
"rejected"
|
|
157
157
|
]);
|
|
158
|
+
var OrgTransitionAction = z.enum(["approve", "reject", "suspend", "reactivate"]);
|
|
159
|
+
var OrgTransitionOptionResponse = z.object({ target_status: OrganizationStatus, action: OrgTransitionAction }).passthrough();
|
|
158
160
|
var OrgResponse = z.object({
|
|
159
161
|
id: z.string().uuid(),
|
|
160
162
|
name: z.string(),
|
|
161
|
-
owner_id: z.string().
|
|
163
|
+
owner_id: z.union([z.string(), z.null()]),
|
|
162
164
|
status: OrganizationStatus,
|
|
165
|
+
domain: z.union([z.string(), z.null()]).optional(),
|
|
163
166
|
created_at: z.string().datetime({ offset: true }),
|
|
167
|
+
available_transitions: z.array(OrgTransitionOptionResponse).optional().default([]),
|
|
164
168
|
is_active: z.boolean()
|
|
165
169
|
}).passthrough();
|
|
166
170
|
z.object({ name: z.union([z.string(), z.null()]) }).partial().passthrough();
|
|
@@ -190,6 +194,7 @@ var UserResponse = z.object({
|
|
|
190
194
|
name: z.string(),
|
|
191
195
|
role_id: z.string().uuid(),
|
|
192
196
|
role_name: z.string(),
|
|
197
|
+
role_scope: z.string().optional().default("org"),
|
|
193
198
|
organization_id: z.string().uuid(),
|
|
194
199
|
organization_name: z.string(),
|
|
195
200
|
is_active: z.boolean(),
|
|
@@ -251,6 +256,7 @@ var ScanStatus = z.enum([
|
|
|
251
256
|
"crawled",
|
|
252
257
|
"checking",
|
|
253
258
|
"checking_async",
|
|
259
|
+
"rechecking",
|
|
254
260
|
"completed",
|
|
255
261
|
"partial",
|
|
256
262
|
"failed",
|
|
@@ -346,6 +352,8 @@ var ScanResponse = z.object({
|
|
|
346
352
|
classification: z.union([ScanClassificationResponse, z.null()]).optional(),
|
|
347
353
|
campaign_id: z.union([z.string(), z.null()]).optional(),
|
|
348
354
|
campaign_name: z.union([z.string(), z.null()]).optional(),
|
|
355
|
+
campaign_group_id: z.union([z.string(), z.null()]).optional(),
|
|
356
|
+
campaign_group_name: z.union([z.string(), z.null()]).optional(),
|
|
349
357
|
created_at: z.string().datetime({ offset: true }),
|
|
350
358
|
completed_at: z.union([z.string(), z.null()]),
|
|
351
359
|
landings: z.array(LandingResponse).optional()
|
|
@@ -367,7 +375,9 @@ var ScanBriefResponse = z.object({
|
|
|
367
375
|
created_at: z.string().datetime({ offset: true }),
|
|
368
376
|
campaign_id: z.union([z.string(), z.null()]).optional(),
|
|
369
377
|
campaign_name: z.union([z.string(), z.null()]).optional(),
|
|
370
|
-
is_ad_tag: z.boolean().optional().default(false)
|
|
378
|
+
is_ad_tag: z.boolean().optional().default(false),
|
|
379
|
+
emulator_display_name: z.string().optional().default(""),
|
|
380
|
+
emulator_category: z.string().optional().default("")
|
|
371
381
|
}).passthrough();
|
|
372
382
|
z.object({
|
|
373
383
|
items: z.array(ScanBriefResponse),
|
|
@@ -488,7 +498,8 @@ var CampaignPickerItem = z.object({
|
|
|
488
498
|
id: z.string().uuid(),
|
|
489
499
|
name: z.string(),
|
|
490
500
|
group_id: z.string().uuid(),
|
|
491
|
-
is_archived: z.boolean()
|
|
501
|
+
is_archived: z.boolean(),
|
|
502
|
+
policy_set_id: z.union([z.string(), z.null()]).optional()
|
|
492
503
|
}).passthrough();
|
|
493
504
|
z.object({
|
|
494
505
|
name: z.union([z.string(), z.null()]),
|
|
@@ -555,6 +566,7 @@ var ScanTagResponse = z.object({
|
|
|
555
566
|
tag_slug: z.string(),
|
|
556
567
|
detail: z.string(),
|
|
557
568
|
url: z.string().optional().default(""),
|
|
569
|
+
surface: z.string().optional().default("page"),
|
|
558
570
|
display_name: z.string().optional().default(""),
|
|
559
571
|
category: z.string().optional().default(""),
|
|
560
572
|
severity: z.string().optional().default(""),
|
|
@@ -566,12 +578,13 @@ var TagDefinitionWithStatsResponse = z.object({
|
|
|
566
578
|
source: z.string(),
|
|
567
579
|
display_name: z.string(),
|
|
568
580
|
description: z.string(),
|
|
569
|
-
|
|
581
|
+
scope: z.string(),
|
|
570
582
|
organization_id: z.union([z.string(), z.null()]),
|
|
571
583
|
visibility: z.string(),
|
|
572
584
|
severity: z.string(),
|
|
573
585
|
scans_count: z.number().int(),
|
|
574
|
-
rules_count: z.number().int()
|
|
586
|
+
rules_count: z.number().int(),
|
|
587
|
+
archived_at: z.union([z.string(), z.null()]).optional()
|
|
575
588
|
}).passthrough();
|
|
576
589
|
var LinkedRuleResponse = z.object({
|
|
577
590
|
id: z.string().uuid(),
|
|
@@ -586,12 +599,13 @@ var TagDefinitionDetailResponse = z.object({
|
|
|
586
599
|
source: z.string(),
|
|
587
600
|
display_name: z.string(),
|
|
588
601
|
description: z.string(),
|
|
589
|
-
|
|
602
|
+
scope: z.string(),
|
|
590
603
|
organization_id: z.union([z.string(), z.null()]),
|
|
591
604
|
visibility: z.string(),
|
|
592
605
|
severity: z.string(),
|
|
593
606
|
scans_count: z.number().int(),
|
|
594
607
|
rules_count: z.number().int(),
|
|
608
|
+
archived_at: z.union([z.string(), z.null()]).optional(),
|
|
595
609
|
linked_rules: z.array(LinkedRuleResponse).optional()
|
|
596
610
|
}).passthrough();
|
|
597
611
|
var TagVisibility = z.enum(["hidden", "internal", "public"]);
|
|
@@ -611,14 +625,15 @@ z.object({
|
|
|
611
625
|
}).passthrough();
|
|
612
626
|
var CustomRuleResponse = z.object({
|
|
613
627
|
id: z.string().uuid(),
|
|
614
|
-
organization_id: z.string().
|
|
628
|
+
organization_id: z.union([z.string(), z.null()]),
|
|
615
629
|
name: z.string(),
|
|
616
630
|
tag_slug: z.string(),
|
|
617
631
|
rule_type: z.string(),
|
|
618
632
|
config: z.object({}).partial().passthrough(),
|
|
619
633
|
target: z.string(),
|
|
620
634
|
is_active: z.boolean(),
|
|
621
|
-
created_at: z.string().datetime({ offset: true })
|
|
635
|
+
created_at: z.string().datetime({ offset: true }),
|
|
636
|
+
scope: z.string().optional().default("personal")
|
|
622
637
|
}).passthrough();
|
|
623
638
|
z.object({
|
|
624
639
|
items: z.array(CustomRuleResponse),
|
|
@@ -638,7 +653,10 @@ z.object({
|
|
|
638
653
|
scan_id: z.string().uuid(),
|
|
639
654
|
rule_type: z.string(),
|
|
640
655
|
config: z.object({}).partial().passthrough(),
|
|
641
|
-
target: z.string().optional().default("page")
|
|
656
|
+
target: z.string().optional().default("page"),
|
|
657
|
+
name: z.string().optional().default("Test Rule"),
|
|
658
|
+
context: z.enum(["isolated", "production"]).optional().default("isolated"),
|
|
659
|
+
rule_id: z.union([z.string(), z.null()]).optional()
|
|
642
660
|
}).passthrough();
|
|
643
661
|
var RuleTestTagResult = z.object({ tag_slug: z.string(), detail: z.string().optional().default("") }).passthrough();
|
|
644
662
|
var RuleTestResponse = z.object({
|
|
@@ -681,7 +699,8 @@ var PolicyEntryRequest = z.object({
|
|
|
681
699
|
z.object({
|
|
682
700
|
name: z.string().min(1).max(200),
|
|
683
701
|
description: z.string().max(2e3).optional().default(""),
|
|
684
|
-
entries: z.array(PolicyEntryRequest).min(1).max(500)
|
|
702
|
+
entries: z.array(PolicyEntryRequest).min(1).max(500),
|
|
703
|
+
campaign_ids: z.array(z.string().uuid()).max(2e3).optional()
|
|
685
704
|
}).passthrough();
|
|
686
705
|
var PolicyEntryIabV3Response = z.object({
|
|
687
706
|
tier1: z.string(),
|
|
@@ -712,6 +731,7 @@ var PolicyEntryResponse = z.object({
|
|
|
712
731
|
custom_taxonomy: z.union([PolicyEntryCustomTaxonomyResponse, z.null()]).optional(),
|
|
713
732
|
country_codes: z.array(z.string())
|
|
714
733
|
}).passthrough();
|
|
734
|
+
var LinkedCampaignResponse = z.object({ id: z.string().uuid(), name: z.string(), is_archived: z.boolean() }).passthrough();
|
|
715
735
|
var PolicySetResponse = z.object({
|
|
716
736
|
id: z.string().uuid(),
|
|
717
737
|
name: z.string(),
|
|
@@ -720,6 +740,8 @@ var PolicySetResponse = z.object({
|
|
|
720
740
|
visibility: z.string(),
|
|
721
741
|
is_approved: z.boolean(),
|
|
722
742
|
entries: z.array(PolicyEntryResponse),
|
|
743
|
+
campaigns: z.array(LinkedCampaignResponse).optional(),
|
|
744
|
+
campaigns_total: z.number().int().optional().default(0),
|
|
723
745
|
created_at: z.string().datetime({ offset: true })
|
|
724
746
|
}).passthrough();
|
|
725
747
|
var VisibilityType = z.enum(["private", "public"]);
|
|
@@ -743,9 +765,22 @@ z.object({
|
|
|
743
765
|
z.object({
|
|
744
766
|
name: z.string().min(1).max(200),
|
|
745
767
|
description: z.string().max(2e3).optional().default(""),
|
|
746
|
-
entries: z.array(PolicyEntryRequest).min(1).max(500)
|
|
768
|
+
entries: z.array(PolicyEntryRequest).min(1).max(500),
|
|
769
|
+
campaign_ids: z.array(z.string().uuid()).max(2e3).optional()
|
|
747
770
|
}).passthrough();
|
|
748
|
-
|
|
771
|
+
z.object({
|
|
772
|
+
items: z.array(LinkedCampaignResponse),
|
|
773
|
+
total: z.number().int(),
|
|
774
|
+
page: z.number().int(),
|
|
775
|
+
limit: z.number().int(),
|
|
776
|
+
pages: z.number().int()
|
|
777
|
+
}).passthrough();
|
|
778
|
+
z.object({ campaign_ids: z.array(z.string().uuid()).min(1).max(500) }).passthrough();
|
|
779
|
+
z.object({
|
|
780
|
+
campaign_ids: z.array(z.string().uuid()).max(500),
|
|
781
|
+
detach_all: z.boolean().default(false)
|
|
782
|
+
}).partial().passthrough();
|
|
783
|
+
var AlertStatus = z.enum(["open", "escalated", "resolved", "dismissed"]);
|
|
749
784
|
z.union([AlertStatus, z.null()]).optional();
|
|
750
785
|
var AlertResponse = z.object({
|
|
751
786
|
id: z.string().uuid(),
|
|
@@ -756,7 +791,7 @@ var AlertResponse = z.object({
|
|
|
756
791
|
organization_id: z.string().uuid(),
|
|
757
792
|
tag_slug: z.string(),
|
|
758
793
|
country_code: z.string(),
|
|
759
|
-
status:
|
|
794
|
+
status: AlertStatus,
|
|
760
795
|
closed_by: z.union([z.string(), z.null()]),
|
|
761
796
|
created_at: z.string().datetime({ offset: true }),
|
|
762
797
|
updated_at: z.union([z.string(), z.null()]),
|
|
@@ -774,9 +809,17 @@ z.object({
|
|
|
774
809
|
pages: z.number().int()
|
|
775
810
|
}).passthrough();
|
|
776
811
|
z.object({ status: AlertStatus }).passthrough();
|
|
812
|
+
z.object({
|
|
813
|
+
status: AlertStatus,
|
|
814
|
+
ids: z.union([z.array(z.string().uuid()), z.null()]).optional(),
|
|
815
|
+
all_matching: z.boolean().optional().default(false),
|
|
816
|
+
filter_status: z.union([AlertStatus, z.null()]).optional(),
|
|
817
|
+
filter_campaign_id: z.union([z.string(), z.null()]).optional()
|
|
818
|
+
}).passthrough();
|
|
819
|
+
z.object({ updated: z.number().int(), skipped: z.number().int() }).passthrough();
|
|
777
820
|
var AlertStatsResponse = z.object({
|
|
778
821
|
open: z.number().int(),
|
|
779
|
-
|
|
822
|
+
escalated: z.number().int(),
|
|
780
823
|
resolved: z.number().int(),
|
|
781
824
|
dismissed: z.number().int()
|
|
782
825
|
}).passthrough();
|
|
@@ -846,6 +889,7 @@ var BalanceTransactionResponse = z.object({
|
|
|
846
889
|
description: z.string(),
|
|
847
890
|
reference_kind: z.union([z.string(), z.null()]),
|
|
848
891
|
reference_id: z.union([z.string(), z.null()]),
|
|
892
|
+
document_number: z.union([z.string(), z.null()]).optional(),
|
|
849
893
|
actor_user_id: z.union([z.string(), z.null()]),
|
|
850
894
|
created_at: z.string().datetime({ offset: true })
|
|
851
895
|
}).passthrough();
|
|
@@ -950,6 +994,7 @@ var AlertNotificationDestinationResponse = z.object({
|
|
|
950
994
|
telegram_chat_type: z.union([z.string(), z.null()]),
|
|
951
995
|
email_address: z.union([z.string(), z.null()]),
|
|
952
996
|
included_label_keys: z.array(z.string()),
|
|
997
|
+
included_statuses: z.array(z.string()),
|
|
953
998
|
created_at: z.string().datetime({ offset: true }),
|
|
954
999
|
updated_at: z.string().datetime({ offset: true })
|
|
955
1000
|
}).passthrough();
|
|
@@ -974,6 +1019,10 @@ var InvoiceResponse = z.object({
|
|
|
974
1019
|
type: z.string(),
|
|
975
1020
|
status: z.string(),
|
|
976
1021
|
total_micros: z.number().int(),
|
|
1022
|
+
net_micros: z.number().int(),
|
|
1023
|
+
vat_micros: z.number().int(),
|
|
1024
|
+
vat_rate: z.string(),
|
|
1025
|
+
vat_reason: z.string(),
|
|
977
1026
|
currency: z.string(),
|
|
978
1027
|
period_start: z.union([z.string(), z.null()]),
|
|
979
1028
|
period_end: z.union([z.string(), z.null()]),
|
|
@@ -992,7 +1041,6 @@ z.object({
|
|
|
992
1041
|
limit: z.number().int(),
|
|
993
1042
|
pages: z.number().int()
|
|
994
1043
|
}).passthrough();
|
|
995
|
-
z.object({ url: z.union([z.string(), z.null()]), ready: z.boolean() }).passthrough();
|
|
996
1044
|
z.object({
|
|
997
1045
|
name: z.string().min(2).max(100),
|
|
998
1046
|
email: z.string().email(),
|
|
@@ -1460,7 +1508,7 @@ var RuleTestSchema = schemas.RuleTestResponse.pick({
|
|
|
1460
1508
|
var parseRuleTest = (raw) => parseWithSchema(RuleTestSchema, raw, "rule-test");
|
|
1461
1509
|
var AlertStatsSchema = schemas.AlertStatsResponse.pick({
|
|
1462
1510
|
open: true,
|
|
1463
|
-
|
|
1511
|
+
escalated: true,
|
|
1464
1512
|
resolved: true,
|
|
1465
1513
|
dismissed: true
|
|
1466
1514
|
}).strip();
|
|
@@ -1692,7 +1740,7 @@ var TagDefinitionSchema = schemas.TagDefinitionWithStatsResponse.pick({
|
|
|
1692
1740
|
display_name: true,
|
|
1693
1741
|
description: true,
|
|
1694
1742
|
severity: true,
|
|
1695
|
-
|
|
1743
|
+
scope: true,
|
|
1696
1744
|
organization_id: true,
|
|
1697
1745
|
visibility: true,
|
|
1698
1746
|
scans_count: true,
|
|
@@ -1711,7 +1759,7 @@ var TagDetailSchema = schemas.TagDefinitionDetailResponse.pick({
|
|
|
1711
1759
|
display_name: true,
|
|
1712
1760
|
description: true,
|
|
1713
1761
|
severity: true,
|
|
1714
|
-
|
|
1762
|
+
scope: true,
|
|
1715
1763
|
organization_id: true,
|
|
1716
1764
|
visibility: true,
|
|
1717
1765
|
scans_count: true,
|
|
@@ -3032,7 +3080,7 @@ var setCampaignAlertOverridesTool = {
|
|
|
3032
3080
|
var GetAlertStatsInputShape = {};
|
|
3033
3081
|
var getAlertStatsTool = {
|
|
3034
3082
|
name: "get_alert_stats",
|
|
3035
|
-
description: "Get alert counts grouped by status: open,
|
|
3083
|
+
description: "Get alert counts grouped by status: open, escalated, resolved, dismissed (total is implicit in the sum). Status names match the canonical AlertStatus enum used by `list_alerts` and `update_alert_status`.",
|
|
3036
3084
|
annotations: {
|
|
3037
3085
|
title: "Get Alert Stats",
|
|
3038
3086
|
readOnlyHint: true,
|
|
@@ -3049,8 +3097,8 @@ var getAlertStatsTool = {
|
|
|
3049
3097
|
};
|
|
3050
3098
|
var ListAlertsInputShape = {
|
|
3051
3099
|
campaign_id: z.string().uuid().optional().describe("Filter to one campaign's alerts."),
|
|
3052
|
-
status: z.enum(["open", "
|
|
3053
|
-
"Filter by alert status. Open = unhandled;
|
|
3100
|
+
status: z.enum(["open", "escalated", "resolved", "dismissed"]).optional().describe(
|
|
3101
|
+
"Filter by alert status. Open = unhandled; escalated = flagged for attention; resolved = closed; dismissed = closed without action."
|
|
3054
3102
|
),
|
|
3055
3103
|
page: z.number().int().min(1).max(500).default(1).describe("1-indexed page number."),
|
|
3056
3104
|
limit: z.number().int().min(1).max(200).default(50).describe("Page size.")
|
|
@@ -3080,11 +3128,11 @@ var listAlertsTool = {
|
|
|
3080
3128
|
};
|
|
3081
3129
|
var UpdateAlertStatusInputShape = {
|
|
3082
3130
|
alert_id: z.string().uuid().describe("Alert UUID."),
|
|
3083
|
-
status: z.enum(["open", "
|
|
3131
|
+
status: z.enum(["open", "escalated", "resolved", "dismissed"]).describe("New status: open | escalated | resolved | dismissed.")
|
|
3084
3132
|
};
|
|
3085
3133
|
var updateAlertStatusTool = {
|
|
3086
3134
|
name: "update_alert_status",
|
|
3087
|
-
description: "Update an alert's status in its lifecycle: open \u2192
|
|
3135
|
+
description: "Update an alert's status in its lifecycle: open \u2192 escalated \u2192 resolved | dismissed. The API enforces valid transitions; an invalid one returns 422.",
|
|
3088
3136
|
annotations: {
|
|
3089
3137
|
title: "Update Alert Status",
|
|
3090
3138
|
readOnlyHint: false,
|
|
@@ -3507,7 +3555,9 @@ var CreateCampaignInputShape = {
|
|
|
3507
3555
|
name: z.string().min(1).max(200).describe("Display name (1-200 chars)."),
|
|
3508
3556
|
campaign_type: z.enum(["url", "ad_tag"]).describe("`url` or `ad_tag` \u2014 must match the target field below."),
|
|
3509
3557
|
url: z.string().url().optional().describe("Target URL (required if campaign_type=url)."),
|
|
3510
|
-
ad_tag: z.string().optional().describe(
|
|
3558
|
+
ad_tag: z.string().optional().describe(
|
|
3559
|
+
"Ad-tag HTML/JS or an http(s) URL of a page with the rendered creative (required if campaign_type=ad_tag)."
|
|
3560
|
+
),
|
|
3511
3561
|
country_codes: z.array(z.string().length(2)).min(1).describe("ISO 3166-1 alpha-2 codes \u2014 one scan per country per run."),
|
|
3512
3562
|
group_id: z.string().uuid().optional().describe("Parent group UUID; defaults to the org's default group."),
|
|
3513
3563
|
...campaignConfigFields,
|
|
@@ -3697,7 +3747,9 @@ var UpdateCampaignInputShape = {
|
|
|
3697
3747
|
campaign_id: z.string().uuid().describe("Campaign UUID to update."),
|
|
3698
3748
|
name: z.string().min(1).max(200).optional().describe("New display name."),
|
|
3699
3749
|
url: z.string().url().optional().describe("New target URL (url-type campaigns)."),
|
|
3700
|
-
ad_tag: z.string().optional().describe(
|
|
3750
|
+
ad_tag: z.string().optional().describe(
|
|
3751
|
+
"New ad-tag HTML/JS or an http(s) URL of a page with the rendered creative (ad_tag-type campaigns)."
|
|
3752
|
+
),
|
|
3701
3753
|
country_codes: z.array(z.string().length(2)).optional().describe("Replace the country list."),
|
|
3702
3754
|
group_id: z.string().uuid().optional().describe("Move the campaign to another group."),
|
|
3703
3755
|
...campaignConfigFields,
|
|
@@ -3738,7 +3790,7 @@ var CreateCustomRuleInputShape = {
|
|
|
3738
3790
|
"Display name. For non-LLM rules this also becomes the `display_name` of the auto-registered tag definition (see `tag_slug`)."
|
|
3739
3791
|
),
|
|
3740
3792
|
tag_slug: z.string().max(100).optional().describe(
|
|
3741
|
-
"Tag slug to assign on match. Empty = create-only (advanced). The API auto-registers a custom tag definition for this slug with `display_name = name`. **MUST NOT collide with a built-in system tag slug** (see `list_tags` where `
|
|
3793
|
+
"Tag slug to assign on match. Empty = create-only (advanced). The API auto-registers a custom tag definition for this slug with `display_name = name`. **MUST NOT collide with a built-in system tag slug** (see `list_tags` where `scope=system`); colliding requests return 422 with code `checking.system_slug_reserved`. For `rule_type='llm'` use `config.tags` keys instead and leave `tag_slug` empty."
|
|
3742
3794
|
),
|
|
3743
3795
|
rule_type: z.string().max(50).describe(
|
|
3744
3796
|
"Rule engine. One of: `stopword_content`, `stopword_url`, `regexp_content`, `regexp_url`, `blacklist_domain`, `combo`, `llm`. The API validates."
|
|
@@ -3838,7 +3890,7 @@ var TestCustomRuleInputShape = {
|
|
|
3838
3890
|
"Rule engine type. One of: `stopword_content`, `stopword_url`, `regexp_content`, `regexp_url`, `blacklist_domain`, `combo`, `llm`."
|
|
3839
3891
|
),
|
|
3840
3892
|
config: z.record(z.unknown()).describe(
|
|
3841
|
-
"Rule-type-specific config to test. Same shape as `create_custom_rule`'s `config`. NOTE: `test_custom_rule` evaluates the rule against a scan WITHOUT persisting it, so slug-collision validation does NOT run here \u2014 verify slugs against `list_tags` (`
|
|
3893
|
+
"Rule-type-specific config to test. Same shape as `create_custom_rule`'s `config`. NOTE: `test_custom_rule` evaluates the rule against a scan WITHOUT persisting it, so slug-collision validation does NOT run here \u2014 verify slugs against `list_tags` (`scope=system`) before promoting to `create_custom_rule`."
|
|
3842
3894
|
),
|
|
3843
3895
|
target: z.string().max(30).describe(
|
|
3844
3896
|
"Where to apply the rule (e.g. 'page' for landing HTML). See API docs for the full set of valid values."
|
|
@@ -3847,7 +3899,7 @@ var TestCustomRuleInputShape = {
|
|
|
3847
3899
|
};
|
|
3848
3900
|
var testCustomRuleTool = {
|
|
3849
3901
|
name: "test_custom_rule",
|
|
3850
|
-
description: "Preview-test a rule definition against an existing scan WITHOUT persisting the rule. Returns `matched: bool`, evaluation time, and per-tag-slug details. Use to validate config before `create_custom_rule`. NOTE: slug-collision validation does NOT run in preview mode \u2014 verify any `tag_slug` (or LLM `config.tags` keys) against `list_tags` (`
|
|
3902
|
+
description: "Preview-test a rule definition against an existing scan WITHOUT persisting the rule. Returns `matched: bool`, evaluation time, and per-tag-slug details. Use to validate config before `create_custom_rule`. NOTE: slug-collision validation does NOT run in preview mode \u2014 verify any `tag_slug` (or LLM `config.tags` keys) against `list_tags` (`scope=system`) before promoting to `create_custom_rule`, which would otherwise reject the same payload with HTTP 422 / code `checking.system_slug_reserved`.",
|
|
3851
3903
|
annotations: {
|
|
3852
3904
|
title: "Test Custom Rule",
|
|
3853
3905
|
readOnlyHint: true,
|
|
@@ -3873,7 +3925,7 @@ var UpdateCustomRuleInputShape = {
|
|
|
3873
3925
|
"New display name. For non-LLM rules this also becomes the new `display_name` of the auto-registered tag definition."
|
|
3874
3926
|
),
|
|
3875
3927
|
tag_slug: z.string().max(100).optional().describe(
|
|
3876
|
-
"New tag slug to assign on match. **MUST NOT collide with a built-in system tag slug** (see `list_tags` where `
|
|
3928
|
+
"New tag slug to assign on match. **MUST NOT collide with a built-in system tag slug** (see `list_tags` where `scope=system`); colliding requests return 422 with code `checking.system_slug_reserved`. Re-registering an existing custom slug refreshes its tag definition's `display_name` / `description`."
|
|
3877
3929
|
),
|
|
3878
3930
|
config: z.record(z.unknown()).optional().describe(
|
|
3879
3931
|
"New rule-type-specific config object. For `rule_type='llm'` the keys of `config.tags` are auto-registered as tag definitions; any key that collides with a system slug returns the same 422 contract."
|
|
@@ -4552,7 +4604,9 @@ var scanProxyField = z.object({
|
|
|
4552
4604
|
// src/application/tools/scans/create-bulk-scans.tool.ts
|
|
4553
4605
|
var CreateBulkScansInputShape = {
|
|
4554
4606
|
url: z.string().url().optional().describe("Direct URL. EITHER `url` OR `ad_tag` is required."),
|
|
4555
|
-
ad_tag: z.string().optional().describe(
|
|
4607
|
+
ad_tag: z.string().optional().describe(
|
|
4608
|
+
"Raw ad-tag HTML/JS OR an http(s) URL of a page with the creative already rendered. EITHER `url` OR `ad_tag` is required."
|
|
4609
|
+
),
|
|
4556
4610
|
country_codes: z.array(z.string().length(2)).min(1).max(50).describe("List of ISO 3166-1 alpha-2 country codes; one scan per country is created."),
|
|
4557
4611
|
emulator_id: z.string().min(1).max(100).describe("Device/OS profile slug; same for every country in the batch."),
|
|
4558
4612
|
proxy: scanProxyField,
|
|
@@ -4585,7 +4639,9 @@ var createBulkScansTool = {
|
|
|
4585
4639
|
};
|
|
4586
4640
|
var CreateScanInputShape = {
|
|
4587
4641
|
url: z.string().url().optional().describe("Direct URL of the ad / landing page. EITHER `url` OR `ad_tag` is required."),
|
|
4588
|
-
ad_tag: z.string().optional().describe(
|
|
4642
|
+
ad_tag: z.string().optional().describe(
|
|
4643
|
+
"Raw HTML/JS ad tag (script, iframe, image) OR an http(s) URL of a page with the creative already rendered. EITHER `url` OR `ad_tag` is required."
|
|
4644
|
+
),
|
|
4589
4645
|
country_code: z.string().length(2).describe("ISO 3166-1 alpha-2 country code, e.g. US, DE, JP. Determines proxy geo."),
|
|
4590
4646
|
emulator_id: z.string().min(1).max(100).describe("Device/OS profile slug; use `list_emulators` to discover valid values."),
|
|
4591
4647
|
proxy: scanProxyField,
|
|
@@ -5376,5 +5432,5 @@ function formatToolError(error) {
|
|
|
5376
5432
|
}
|
|
5377
5433
|
|
|
5378
5434
|
export { BearerToken, SERVER_INSTRUCTIONS, createHttpApiGateway, createPinoLogger, declareEmptyResourcesAndPrompts, newRequestId, wireToolsIntoMcpServer };
|
|
5379
|
-
//# sourceMappingURL=chunk-
|
|
5380
|
-
//# sourceMappingURL=chunk-
|
|
5435
|
+
//# sourceMappingURL=chunk-NRDA7DQQ.js.map
|
|
5436
|
+
//# sourceMappingURL=chunk-NRDA7DQQ.js.map
|