@kaminari-ad/mcp 0.23.0 → 0.24.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +47 -0
- package/dist/bin.js +7 -5
- package/dist/bin.js.map +1 -1
- package/dist/{chunk-6INS6YMW.js → chunk-3G2F3FOW.js} +3 -3
- package/dist/{chunk-6INS6YMW.js.map → chunk-3G2F3FOW.js.map} +1 -1
- package/dist/{chunk-XJZE6KTY.js → chunk-LPABQWHJ.js} +99 -26
- package/dist/chunk-LPABQWHJ.js.map +1 -0
- package/dist/{http-bootstrap-NSZVATPV.js → http-bootstrap-6PQSTYXB.js} +12 -4
- package/dist/http-bootstrap-6PQSTYXB.js.map +1 -0
- package/dist/{stdio-bootstrap-M7I2WD5F.js → stdio-bootstrap-VGOUNXAK.js} +4 -4
- package/dist/{stdio-bootstrap-M7I2WD5F.js.map → stdio-bootstrap-VGOUNXAK.js.map} +1 -1
- package/package.json +1 -1
- package/dist/chunk-XJZE6KTY.js.map +0 -1
- package/dist/http-bootstrap-NSZVATPV.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,53 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.24.1] - 2026-10-02
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **`GET /` on the hosted HTTP endpoint redirects to the landing page.** The
|
|
15
|
+
bare `mcp.kaminari.ad` used to answer `404 Not found`, a dead end for people
|
|
16
|
+
and crawlers arriving from MCP directories. It now returns `301` to
|
|
17
|
+
`https://kaminari.ad/mcp` (override with `KAMINARI_AD_MCP_LANDING_URL`). No
|
|
18
|
+
auth, no API call; `/mcp`, `/healthz` and the OAuth discovery document are
|
|
19
|
+
unchanged.
|
|
20
|
+
|
|
21
|
+
## [0.24.0] - 2026-09-30
|
|
22
|
+
|
|
23
|
+
> Requires the API release with campaign `notifications` and label routing.
|
|
24
|
+
> Until it lands, production ignores the new body fields and omits the new
|
|
25
|
+
> response fields. Do not tag this release ahead of the API.
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
|
|
29
|
+
- **`notifications` on `create_campaign` and `update_campaign`.** A campaign's
|
|
30
|
+
alert routing (`mode`, `destination_ids`, `routing_label_key`) is saved in
|
|
31
|
+
the same request as the campaign. The API validates the block before it
|
|
32
|
+
writes anything, so an invalid block leaves the campaign untouched. Omit it
|
|
33
|
+
to keep the current routing (update) or inherit the org defaults (create).
|
|
34
|
+
- **Label routing.** `set_campaign_alert_overrides` accepts
|
|
35
|
+
`routing_label_key` on top of `inherit` / `override`: each scan also reaches
|
|
36
|
+
the destinations whose label rule matches its value for that key (one DSP's
|
|
37
|
+
chat in a shared campaign). `get_campaign_alert_overrides` returns the key,
|
|
38
|
+
and `list_alert_destinations` returns each destination's rule as
|
|
39
|
+
`route_label_key` + `route_label_values`. The mode / destination / key
|
|
40
|
+
combination rules are checked locally before the call.
|
|
41
|
+
|
|
42
|
+
### Changed
|
|
43
|
+
|
|
44
|
+
- Regenerated `openapi.ts` / `zod-schemas.ts` from the current API, which also
|
|
45
|
+
picks up response fields added since the last regen (`ad_formats`,
|
|
46
|
+
`network_host`, `creative_redirect_chain`).
|
|
47
|
+
- `set_campaign_alert_overrides` now replaces the label routing along with the
|
|
48
|
+
mode: a call without `routing_label_key` clears a key set earlier. Read
|
|
49
|
+
`get_campaign_alert_overrides` first to keep it.
|
|
50
|
+
|
|
51
|
+
### Security
|
|
52
|
+
|
|
53
|
+
- `brace-expansion` 5.0.12, `fast-uri` 3.1.8 and `axios` 1.20.0 (transitive)
|
|
54
|
+
for GHSA-q2hr-2g5m-vwhr, GHSA-qhr7-859c-m2p7, GHSA-6j4f-fj2g-mc7p,
|
|
55
|
+
GHSA-hrr3-gc8f-f4qj and the axios 1.19 advisories (GHSA-vh66-26gq-q6x8 et al.).
|
|
56
|
+
|
|
10
57
|
## [0.23.0] - 2026-09-29
|
|
11
58
|
|
|
12
59
|
> Requires the API-side ad-format deploy. Until it lands, production
|
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-3G2F3FOW.js';
|
|
3
3
|
import process from 'process';
|
|
4
4
|
import { z } from 'zod';
|
|
5
5
|
|
|
@@ -79,7 +79,8 @@ var RawSchema = z.object({
|
|
|
79
79
|
KAMINARI_AD_OAUTH_AUTHORIZATION_SERVER_URL: z.string().url().default("https://app.kaminari.ad"),
|
|
80
80
|
// Space-separated scope list. We split on /\s+/ post-parse and drop
|
|
81
81
|
// empty tokens so accidental double-spaces don't corrupt the array.
|
|
82
|
-
KAMINARI_AD_OAUTH_SCOPES: z.string().optional()
|
|
82
|
+
KAMINARI_AD_OAUTH_SCOPES: z.string().optional(),
|
|
83
|
+
KAMINARI_AD_MCP_LANDING_URL: z.string().url().default("https://kaminari.ad/mcp")
|
|
83
84
|
});
|
|
84
85
|
function loadConfig(env) {
|
|
85
86
|
const parsed = RawSchema.safeParse(env);
|
|
@@ -102,7 +103,8 @@ function loadConfig(env) {
|
|
|
102
103
|
oauthProtectedResource: raw.KAMINARI_AD_OAUTH_PROTECTED_RESOURCE,
|
|
103
104
|
oauthProtectedResourceMetadataUrl: raw.KAMINARI_AD_OAUTH_PROTECTED_RESOURCE_METADATA_URL,
|
|
104
105
|
oauthAuthorizationServerUrl: raw.KAMINARI_AD_OAUTH_AUTHORIZATION_SERVER_URL,
|
|
105
|
-
oauthScopes
|
|
106
|
+
oauthScopes,
|
|
107
|
+
landingUrl: raw.KAMINARI_AD_MCP_LANDING_URL
|
|
106
108
|
});
|
|
107
109
|
}
|
|
108
110
|
|
|
@@ -160,10 +162,10 @@ async function main() {
|
|
|
160
162
|
}
|
|
161
163
|
const config = configResult.value;
|
|
162
164
|
if (config.transport === "stdio") {
|
|
163
|
-
const { bootstrapStdio } = await import('./stdio-bootstrap-
|
|
165
|
+
const { bootstrapStdio } = await import('./stdio-bootstrap-VGOUNXAK.js');
|
|
164
166
|
return bootstrapStdio(config);
|
|
165
167
|
}
|
|
166
|
-
const { bootstrapHttp } = await import('./http-bootstrap-
|
|
168
|
+
const { bootstrapHttp } = await import('./http-bootstrap-6PQSTYXB.js');
|
|
167
169
|
return bootstrapHttp(config);
|
|
168
170
|
}
|
|
169
171
|
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 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
|
+
{"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 * Public landing page for the MCP server. `GET /` on the hosted\n * endpoint permanently redirects here so the bare subdomain is not a\n * dead end for people and crawlers.\n */\n readonly landingUrl: 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 KAMINARI_AD_MCP_LANDING_URL: z.string().url().default(\"https://kaminari.ad/mcp\"),\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 landingUrl: raw.KAMINARI_AD_MCP_LANDING_URL,\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"]}
|
|
@@ -3,8 +3,8 @@ export { err, ok } from 'neverthrow';
|
|
|
3
3
|
|
|
4
4
|
// src/shared/version.ts
|
|
5
5
|
var NAME = "@kaminari-ad/mcp";
|
|
6
|
-
var VERSION = "0.
|
|
6
|
+
var VERSION = "0.24.1";
|
|
7
7
|
|
|
8
8
|
export { NAME, VERSION };
|
|
9
|
-
//# sourceMappingURL=chunk-
|
|
10
|
-
//# sourceMappingURL=chunk-
|
|
9
|
+
//# sourceMappingURL=chunk-3G2F3FOW.js.map
|
|
10
|
+
//# sourceMappingURL=chunk-3G2F3FOW.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/shared/version.ts"],"names":[],"mappings":";;;;AASO,IAAM,IAAA,GAAO;AACb,IAAM,OAAA,GAAU","file":"chunk-
|
|
1
|
+
{"version":3,"sources":["../src/shared/version.ts"],"names":[],"mappings":";;;;AASO,IAAM,IAAA,GAAO;AACb,IAAM,OAAA,GAAU","file":"chunk-3G2F3FOW.js","sourcesContent":["/**\n * Package version and name. Hard-coded as constants here, asserted to\n * match `package.json` by a unit test.\n *\n * Why not import `package.json`: it would force JSON-module support at\n * runtime and tsup-bundling would inline the entire manifest. Two\n * constants + one assertion test is simpler and gives the same safety.\n */\n\nexport const NAME = \"@kaminari-ad/mcp\";\nexport const VERSION = \"0.24.1\";\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { err, ok } from './chunk-
|
|
2
|
+
import { err, ok } from './chunk-3G2F3FOW.js';
|
|
3
3
|
import { createHash, randomUUID } from 'crypto';
|
|
4
4
|
import createClient from 'openapi-fetch';
|
|
5
5
|
import { fetch } from 'undici';
|
|
@@ -254,12 +254,14 @@ var ProxyTargetRequest = z.object({
|
|
|
254
254
|
city: z.string().default(""),
|
|
255
255
|
isp: z.string().default("")
|
|
256
256
|
}).partial().passthrough();
|
|
257
|
+
var AdKind = z.enum(["banner", "pop", "video"]);
|
|
257
258
|
var RepeatModeType = z.enum(["isolated", "shared"]);
|
|
258
259
|
z.object({
|
|
259
260
|
url: z.union([z.string(), z.null()]).optional(),
|
|
260
261
|
ad_tag: z.union([z.string(), z.null()]).optional(),
|
|
261
262
|
vast_tag: z.union([z.string(), z.null()]).optional(),
|
|
262
263
|
referrer: z.union([z.string(), z.null()]).optional(),
|
|
264
|
+
ignore_first_n_domains: z.number().int().gte(0).lte(5).optional().default(0),
|
|
263
265
|
country_code: z.string().min(2).max(2),
|
|
264
266
|
emulator_id: z.string().min(1).max(100),
|
|
265
267
|
proxy: ProxyTargetRequest.optional(),
|
|
@@ -267,6 +269,8 @@ z.object({
|
|
|
267
269
|
campaign_id: z.union([z.string(), z.null()]).optional(),
|
|
268
270
|
run_id: z.union([z.string(), z.null()]).optional(),
|
|
269
271
|
ad_discovery: z.boolean().optional().default(false),
|
|
272
|
+
max_discovered_ads: z.union([z.number(), z.null()]).optional(),
|
|
273
|
+
ad_formats: z.union([z.array(AdKind), z.null()]).optional(),
|
|
270
274
|
repeat_count: z.number().int().gte(1).optional().default(1),
|
|
271
275
|
repeat_mode: RepeatModeType.optional(),
|
|
272
276
|
retry_max_attempts: z.number().int().gte(0).optional().default(0)
|
|
@@ -367,9 +371,12 @@ var ScanResponse = z.object({
|
|
|
367
371
|
status: ScanStatus,
|
|
368
372
|
parent_scan_id: z.union([z.string(), z.null()]).optional(),
|
|
369
373
|
ad_discovery: z.boolean().optional().default(false),
|
|
374
|
+
max_discovered_ads: z.union([z.number(), z.null()]).optional(),
|
|
375
|
+
ad_formats: z.union([z.array(AdKind), z.null()]).optional(),
|
|
370
376
|
slot_index: z.union([z.number(), z.null()]).optional(),
|
|
371
377
|
ad_kind: z.union([z.string(), z.null()]).optional(),
|
|
372
378
|
network: z.string().optional().default(""),
|
|
379
|
+
network_host: z.string().optional().default(""),
|
|
373
380
|
offer_url: z.string(),
|
|
374
381
|
redirect_chain: z.array(RedirectHopResponse),
|
|
375
382
|
screenshot_url: z.string().optional().default(""),
|
|
@@ -378,6 +385,7 @@ var ScanResponse = z.object({
|
|
|
378
385
|
ad_tag: z.union([z.string(), z.null()]).optional(),
|
|
379
386
|
vast_tag: z.union([z.string(), z.null()]).optional(),
|
|
380
387
|
referrer: z.union([z.string(), z.null()]).optional(),
|
|
388
|
+
ignore_first_n_domains: z.number().int().optional().default(0),
|
|
381
389
|
creative_kind: z.enum(["banner", "video"]).optional().default("banner"),
|
|
382
390
|
creative_screenshot_url: z.string().optional().default(""),
|
|
383
391
|
creative_video_url: z.string().optional().default(""),
|
|
@@ -385,6 +393,7 @@ var ScanResponse = z.object({
|
|
|
385
393
|
creative_html_url: z.string().optional().default(""),
|
|
386
394
|
creative_width: z.number().int().optional().default(0),
|
|
387
395
|
creative_height: z.number().int().optional().default(0),
|
|
396
|
+
creative_redirect_chain: z.array(RedirectHopResponse).optional(),
|
|
388
397
|
video: z.union([VideoMetaResponse, z.null()]).optional(),
|
|
389
398
|
proxy: z.union([ProxyTargetResponse, z.null()]).optional(),
|
|
390
399
|
page_title: z.string(),
|
|
@@ -430,6 +439,7 @@ var ScanBriefResponse = z.object({
|
|
|
430
439
|
slot_index: z.union([z.number(), z.null()]).optional(),
|
|
431
440
|
ad_kind: z.union([z.string(), z.null()]).optional(),
|
|
432
441
|
network: z.string().optional().default(""),
|
|
442
|
+
network_host: z.string().optional().default(""),
|
|
433
443
|
emulator_display_name: z.string().optional().default(""),
|
|
434
444
|
emulator_category: z.string().optional().default(""),
|
|
435
445
|
repeat_index: z.number().int().optional().default(0),
|
|
@@ -450,6 +460,7 @@ z.object({
|
|
|
450
460
|
ad_tag: z.union([z.string(), z.null()]).optional(),
|
|
451
461
|
vast_tag: z.union([z.string(), z.null()]).optional(),
|
|
452
462
|
referrer: z.union([z.string(), z.null()]).optional(),
|
|
463
|
+
ignore_first_n_domains: z.number().int().gte(0).lte(5).optional().default(0),
|
|
453
464
|
country_codes: z.array(z.string()).min(1),
|
|
454
465
|
emulator_id: z.string().min(1).max(100),
|
|
455
466
|
proxy: ProxyTargetRequest.optional(),
|
|
@@ -510,7 +521,13 @@ var GroupActionResponse = z.object({
|
|
|
510
521
|
run_ids: z.array(z.string().uuid()).optional(),
|
|
511
522
|
failures: z.array(BulkCampaignFailure).optional()
|
|
512
523
|
}).passthrough();
|
|
524
|
+
var DiscoveryAdFormat = z.enum(["banner", "pop", "video"]);
|
|
513
525
|
var RepeatMode = z.enum(["isolated", "shared"]);
|
|
526
|
+
var CampaignNotificationsRequest = z.object({
|
|
527
|
+
mode: z.enum(["inherit", "override", "silence"]),
|
|
528
|
+
destination_ids: z.array(z.string().uuid()).optional(),
|
|
529
|
+
routing_label_key: z.union([z.string(), z.null()]).optional()
|
|
530
|
+
}).passthrough();
|
|
514
531
|
z.object({
|
|
515
532
|
name: z.string().min(1).max(200),
|
|
516
533
|
campaign_type: z.string().optional().default("url"),
|
|
@@ -518,6 +535,9 @@ z.object({
|
|
|
518
535
|
ad_tag: z.union([z.string(), z.null()]).optional(),
|
|
519
536
|
vast_tag: z.union([z.string(), z.null()]).optional(),
|
|
520
537
|
referrer: z.union([z.string(), z.null()]).optional(),
|
|
538
|
+
ignore_first_n_domains: z.number().int().gte(0).lte(5).optional().default(0),
|
|
539
|
+
max_discovered_ads: z.union([z.number(), z.null()]).optional(),
|
|
540
|
+
ad_formats: z.union([z.array(DiscoveryAdFormat), z.null()]).optional(),
|
|
521
541
|
country_codes: z.array(z.string()).min(1),
|
|
522
542
|
group_id: z.union([z.string(), z.null()]).optional(),
|
|
523
543
|
emulator_categories: z.array(z.string()).optional(),
|
|
@@ -536,7 +556,8 @@ z.object({
|
|
|
536
556
|
schedule_weekly: z.union([z.record(z.array(z.number().int())), z.null()]).optional(),
|
|
537
557
|
schedule_interval_seconds: z.union([z.number(), z.null()]).optional(),
|
|
538
558
|
schedule_enabled: z.union([z.boolean(), z.null()]).optional(),
|
|
539
|
-
schedule_timezone: z.union([z.string(), z.null()]).optional()
|
|
559
|
+
schedule_timezone: z.union([z.string(), z.null()]).optional(),
|
|
560
|
+
notifications: z.union([CampaignNotificationsRequest, z.null()]).optional()
|
|
540
561
|
}).passthrough();
|
|
541
562
|
var EmulatorSelectionResponse = z.object({
|
|
542
563
|
categories: z.array(z.string()),
|
|
@@ -551,6 +572,9 @@ var CampaignResponse = z.object({
|
|
|
551
572
|
ad_tag: z.union([z.string(), z.null()]).optional(),
|
|
552
573
|
vast_tag: z.union([z.string(), z.null()]).optional(),
|
|
553
574
|
referrer: z.union([z.string(), z.null()]).optional(),
|
|
575
|
+
ignore_first_n_domains: z.number().int().optional().default(0),
|
|
576
|
+
max_discovered_ads: z.union([z.number(), z.null()]).optional(),
|
|
577
|
+
ad_formats: z.union([z.array(DiscoveryAdFormat), z.null()]).optional(),
|
|
554
578
|
country_codes: z.array(z.string()),
|
|
555
579
|
group_id: z.string().uuid(),
|
|
556
580
|
emulator_selection: EmulatorSelectionResponse,
|
|
@@ -592,6 +616,9 @@ z.object({
|
|
|
592
616
|
ad_tag: z.union([z.string(), z.null()]),
|
|
593
617
|
vast_tag: z.union([z.string(), z.null()]),
|
|
594
618
|
referrer: z.union([z.string(), z.null()]),
|
|
619
|
+
ignore_first_n_domains: z.number().int().gte(0).lte(5).default(0),
|
|
620
|
+
max_discovered_ads: z.union([z.number(), z.null()]),
|
|
621
|
+
ad_formats: z.union([z.array(DiscoveryAdFormat), z.null()]),
|
|
595
622
|
country_codes: z.union([z.array(z.string()), z.null()]),
|
|
596
623
|
group_id: z.union([z.string(), z.null()]),
|
|
597
624
|
emulator_categories: z.union([z.array(z.string()), z.null()]),
|
|
@@ -610,7 +637,8 @@ z.object({
|
|
|
610
637
|
schedule_weekly: z.union([z.record(z.array(z.number().int())), z.null()]),
|
|
611
638
|
schedule_interval_seconds: z.union([z.number(), z.null()]),
|
|
612
639
|
schedule_enabled: z.union([z.boolean(), z.null()]),
|
|
613
|
-
schedule_timezone: z.union([z.string(), z.null()])
|
|
640
|
+
schedule_timezone: z.union([z.string(), z.null()]),
|
|
641
|
+
notifications: z.union([CampaignNotificationsRequest, z.null()])
|
|
614
642
|
}).partial().passthrough();
|
|
615
643
|
var RunSource = z.enum(["ui", "api"]);
|
|
616
644
|
var RunResponse = z.object({
|
|
@@ -1102,6 +1130,8 @@ var AlertNotificationDestinationResponse = z.object({
|
|
|
1102
1130
|
email_address: z.union([z.string(), z.null()]),
|
|
1103
1131
|
included_label_keys: z.array(z.string()),
|
|
1104
1132
|
included_statuses: z.array(z.string()),
|
|
1133
|
+
route_label_key: z.union([z.string(), z.null()]),
|
|
1134
|
+
route_label_values: z.array(z.string()),
|
|
1105
1135
|
created_at: z.string().datetime({ offset: true }),
|
|
1106
1136
|
updated_at: z.string().datetime({ offset: true })
|
|
1107
1137
|
}).passthrough();
|
|
@@ -1110,11 +1140,13 @@ var CampaignOverrideMode = z.enum(["inherit", "override", "silence"]);
|
|
|
1110
1140
|
var CampaignOverridesResponse = z.object({
|
|
1111
1141
|
campaign_id: z.string().uuid(),
|
|
1112
1142
|
mode: CampaignOverrideMode,
|
|
1113
|
-
destination_ids: z.array(z.string().uuid())
|
|
1143
|
+
destination_ids: z.array(z.string().uuid()),
|
|
1144
|
+
routing_label_key: z.union([z.string(), z.null()])
|
|
1114
1145
|
}).passthrough();
|
|
1115
1146
|
z.object({
|
|
1116
1147
|
mode: CampaignOverrideMode,
|
|
1117
|
-
destination_ids: z.array(z.string().uuid()).optional().default([])
|
|
1148
|
+
destination_ids: z.array(z.string().uuid()).optional().default([]),
|
|
1149
|
+
routing_label_key: z.union([z.string(), z.null()]).optional()
|
|
1118
1150
|
}).passthrough();
|
|
1119
1151
|
var InvoiceType = z.enum(["proforma", "final"]);
|
|
1120
1152
|
z.union([InvoiceType, z.null()]).optional();
|
|
@@ -1506,8 +1538,6 @@ var EmulatorSchema = schemas.EmulatorResponse.pick({
|
|
|
1506
1538
|
}).strip();
|
|
1507
1539
|
var EmulatorListSchema = z.array(EmulatorSchema);
|
|
1508
1540
|
var parseEmulatorList = (raw) => parseWithSchema(EmulatorListSchema, raw, "emulators");
|
|
1509
|
-
|
|
1510
|
-
// src/infrastructure/api/parsers/parse-generic.ts
|
|
1511
1541
|
function parsePageOf(parseItem) {
|
|
1512
1542
|
return (raw) => {
|
|
1513
1543
|
if (raw === null || typeof raw !== "object") {
|
|
@@ -1694,13 +1724,16 @@ var AlertDestinationSchema = schemas.AlertNotificationDestinationResponse.pick({
|
|
|
1694
1724
|
included_label_keys: true,
|
|
1695
1725
|
created_at: true,
|
|
1696
1726
|
updated_at: true
|
|
1727
|
+
}).extend({
|
|
1728
|
+
route_label_key: z.string().nullable().optional(),
|
|
1729
|
+
route_label_values: z.array(z.string()).optional()
|
|
1697
1730
|
}).strip();
|
|
1698
1731
|
var parseAlertDestination = (raw) => parseWithSchema(AlertDestinationSchema, raw, "alert-destination");
|
|
1699
1732
|
var CampaignOverridesSchema = schemas.CampaignOverridesResponse.pick({
|
|
1700
1733
|
campaign_id: true,
|
|
1701
1734
|
mode: true,
|
|
1702
1735
|
destination_ids: true
|
|
1703
|
-
}).strip();
|
|
1736
|
+
}).extend({ routing_label_key: z.string().nullable().optional() }).strip();
|
|
1704
1737
|
var parseCampaignAlertOverrides = (raw) => parseWithSchema(CampaignOverridesSchema, raw, "campaign-overrides");
|
|
1705
1738
|
var BulkReplaySchema = schemas.BulkReplayResponse.pick({
|
|
1706
1739
|
replayed: true,
|
|
@@ -3247,7 +3280,7 @@ var GetCampaignAlertOverridesInputShape = {
|
|
|
3247
3280
|
};
|
|
3248
3281
|
var getCampaignAlertOverridesTool = {
|
|
3249
3282
|
name: "get_campaign_alert_overrides",
|
|
3250
|
-
description: "Get the per-campaign override of which alert destinations receive its alerts. `mode` is one of `inherit` (no override \u2014 the campaign follows the org-wide destinations), `override` (alerts go ONLY to the returned `destination_ids`), or `silence` (the campaign sends nothing).",
|
|
3283
|
+
description: "Get the per-campaign override of which alert destinations receive its alerts. `mode` is one of `inherit` (no override \u2014 the campaign follows the org-wide destinations), `override` (alerts go ONLY to the returned `destination_ids`), or `silence` (the campaign sends nothing). `routing_label_key`, when set, adds label routing on top of inherit/override: each scan also reaches the destinations whose label rule on that key matches the scan's value.",
|
|
3251
3284
|
annotations: {
|
|
3252
3285
|
title: "Get Campaign Alert Overrides",
|
|
3253
3286
|
readOnlyHint: true,
|
|
@@ -3265,7 +3298,7 @@ var getCampaignAlertOverridesTool = {
|
|
|
3265
3298
|
var ListAlertDestinationsInputShape = {};
|
|
3266
3299
|
var listAlertDestinationsTool = {
|
|
3267
3300
|
name: "list_alert_destinations",
|
|
3268
|
-
description: "List configured alert-notification destinations (channels): Slack workspaces, Telegram chats, email lists, generic webhooks. Each entry has id, kind, display name, version, and
|
|
3301
|
+
description: "List configured alert-notification destinations (channels): Slack workspaces, Telegram chats, email lists, generic webhooks. Each entry has id, kind, display name, version, creation timestamp and, when set, its label routing rule (`route_label_key` + `route_label_values`) used by campaigns that route by that label key.",
|
|
3269
3302
|
annotations: {
|
|
3270
3303
|
title: "List Alert Destinations",
|
|
3271
3304
|
readOnlyHint: true,
|
|
@@ -3305,18 +3338,43 @@ var setAlertDestinationVersionTool = {
|
|
|
3305
3338
|
return ok({ updated: true });
|
|
3306
3339
|
}
|
|
3307
3340
|
};
|
|
3308
|
-
var
|
|
3309
|
-
campaign_id: z.string().uuid().describe("Campaign UUID."),
|
|
3341
|
+
var alertRoutingFields = {
|
|
3310
3342
|
mode: schemas.CampaignOverrideMode.describe(
|
|
3311
3343
|
"Routing mode: `inherit` (fall back to the org-wide destinations), `override` (route ONLY to `destination_ids`), `silence` (send nothing for this campaign)."
|
|
3312
3344
|
),
|
|
3313
3345
|
destination_ids: z.array(z.string().uuid()).max(50).default([]).describe(
|
|
3314
3346
|
"Destination UUIDs to route to. Accepted ONLY with `mode: override` \u2014 the API rejects it for `inherit`/`silence`. An empty list with `override` routes nowhere, which is the same outcome as `silence`."
|
|
3347
|
+
),
|
|
3348
|
+
routing_label_key: z.string().min(1).max(64).nullable().optional().describe(
|
|
3349
|
+
"Scan label key to route by on top of `inherit` or `override` (e.g. `dspName`): each scan also goes to the destinations whose label rule matches the scan's value for this key, as shown by `route_label_key` / `route_label_values` in `list_alert_destinations`. Omit or pass null for no label routing \u2014 the routing is replaced as a whole, so omitting it clears a key already set (read `get_campaign_alert_overrides` first to keep it). Not allowed with `silence`."
|
|
3315
3350
|
)
|
|
3316
3351
|
};
|
|
3352
|
+
function alertRoutingInputError(input) {
|
|
3353
|
+
if (input.mode !== "override" && input.destination_ids.length > 0) {
|
|
3354
|
+
return {
|
|
3355
|
+
kind: "invalid-input",
|
|
3356
|
+
message: `destination_ids is only accepted with mode "override", got "${input.mode}". Use mode "override" to route to specific destinations.`,
|
|
3357
|
+
fieldErrors: { destination_ids: ["only valid with mode 'override'"] }
|
|
3358
|
+
};
|
|
3359
|
+
}
|
|
3360
|
+
if (input.mode === "silence" && input.routing_label_key) {
|
|
3361
|
+
return {
|
|
3362
|
+
kind: "invalid-input",
|
|
3363
|
+
message: 'routing_label_key cannot be combined with mode "silence". Use "inherit" or "override" to route by a label.',
|
|
3364
|
+
fieldErrors: { routing_label_key: ["not valid with mode 'silence'"] }
|
|
3365
|
+
};
|
|
3366
|
+
}
|
|
3367
|
+
return void 0;
|
|
3368
|
+
}
|
|
3369
|
+
|
|
3370
|
+
// src/application/tools/alert-notifications/set-campaign-alert-overrides.tool.ts
|
|
3371
|
+
var SetCampaignAlertOverridesInputShape = {
|
|
3372
|
+
campaign_id: z.string().uuid().describe("Campaign UUID."),
|
|
3373
|
+
...alertRoutingFields
|
|
3374
|
+
};
|
|
3317
3375
|
var setCampaignAlertOverridesTool = {
|
|
3318
3376
|
name: "set_campaign_alert_overrides",
|
|
3319
|
-
description: "REPLACE the per-campaign alert-routing override. `mode=inherit` drops the override so the campaign follows the org-wide destinations; `mode=override` routes its alerts ONLY to `destination_ids`; `mode=silence` sends nothing for the campaign. There is no 'route everywhere except these' mode \u2014 to exclude one destination, pass `override` with the destinations you DO want (see `list_alert_destinations`). To read the new state, follow up with `get_campaign_alert_overrides`.",
|
|
3377
|
+
description: "REPLACE the per-campaign alert-routing override. `mode=inherit` drops the override so the campaign follows the org-wide destinations; `mode=override` routes its alerts ONLY to `destination_ids`; `mode=silence` sends nothing for the campaign. `routing_label_key` adds label routing on top of inherit/override: each scan also reaches the destinations whose label rule matches its value (e.g. one DSP's chat in a shared campaign). There is no 'route everywhere except these' mode \u2014 to exclude one destination, pass `override` with the destinations you DO want (see `list_alert_destinations`). To read the new state, follow up with `get_campaign_alert_overrides`.",
|
|
3320
3378
|
annotations: {
|
|
3321
3379
|
title: "Set Campaign Alert Overrides",
|
|
3322
3380
|
readOnlyHint: false,
|
|
@@ -3326,16 +3384,12 @@ var setCampaignAlertOverridesTool = {
|
|
|
3326
3384
|
},
|
|
3327
3385
|
inputSchema: z.object(SetCampaignAlertOverridesInputShape),
|
|
3328
3386
|
handler: async (input, ctx) => {
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
kind: "invalid-input",
|
|
3332
|
-
message: `destination_ids is only accepted with mode "override", got "${input.mode}". Use mode "override" to route to specific destinations.`,
|
|
3333
|
-
fieldErrors: { destination_ids: ["only valid with mode 'override'"] }
|
|
3334
|
-
});
|
|
3335
|
-
}
|
|
3387
|
+
const invalid = alertRoutingInputError(input);
|
|
3388
|
+
if (invalid) return err(invalid);
|
|
3336
3389
|
const result = await ctx.api.setCampaignAlertOverrides(input.campaign_id, {
|
|
3337
3390
|
mode: input.mode,
|
|
3338
|
-
destination_ids: input.destination_ids
|
|
3391
|
+
destination_ids: input.destination_ids,
|
|
3392
|
+
routing_label_key: input.routing_label_key ?? null
|
|
3339
3393
|
});
|
|
3340
3394
|
if (result.isErr()) return err(mapApiError(result.error));
|
|
3341
3395
|
return ok({ updated: true });
|
|
@@ -4003,6 +4057,17 @@ function pickCampaignConfigBody(input) {
|
|
|
4003
4057
|
if (input.schedule_timezone !== void 0) body.schedule_timezone = input.schedule_timezone;
|
|
4004
4058
|
return body;
|
|
4005
4059
|
}
|
|
4060
|
+
var notificationsBlock = z.object(alertRoutingFields);
|
|
4061
|
+
function notificationsBody(input) {
|
|
4062
|
+
return {
|
|
4063
|
+
mode: input.mode,
|
|
4064
|
+
destination_ids: input.destination_ids,
|
|
4065
|
+
routing_label_key: input.routing_label_key ?? null
|
|
4066
|
+
};
|
|
4067
|
+
}
|
|
4068
|
+
var campaignNotificationsField = notificationsBlock.optional().describe(
|
|
4069
|
+
"Alert routing saved together with the campaign: `mode`, `destination_ids`, `routing_label_key` \u2014 same meaning as in `set_campaign_alert_overrides`. The block REPLACES the whole routing: an omitted `destination_ids` means none and an omitted `routing_label_key` clears it. Omit the block to keep the current routing (update) or inherit the org defaults (create). Needs the alert_notifications.manage permission; the whole request is rejected before anything is written if the block is invalid."
|
|
4070
|
+
);
|
|
4006
4071
|
var campaignReferrerUrl = z.string().url().regex(/^https?:\/\//i, "Must be an http(s) URL.").max(2048);
|
|
4007
4072
|
var CAMPAIGN_REFERRER_DESCRIPTION = "Optional http(s) page URL every scan of this campaign is checked from. For `ad_tag` and `vast` campaigns this is the publisher page the tag is embedded in \u2014 NOT the creative and NOT its landing page; the browser commits the page document on this URL without fetching the publisher, so the creative renders as if embedded there, and its cross-origin subrequests receive the origin only (https://publisher.example/, no path). For `url` and `ad_discovery` campaigns this is where the visitor came from, sent in full as the Referer of the page request.";
|
|
4008
4073
|
var campaignReferrerField = campaignReferrerUrl.optional().describe(CAMPAIGN_REFERRER_DESCRIPTION);
|
|
@@ -4032,7 +4097,8 @@ var CreateCampaignInputShape = {
|
|
|
4032
4097
|
policy_set_id: z.union([z.string().uuid(), z.null()]).optional().describe(
|
|
4033
4098
|
"Policy set to evaluate every scan against. Omit to bind the organization's default (if any). Pass null for no policy set and no policy alerts."
|
|
4034
4099
|
),
|
|
4035
|
-
schedule_enabled: z.boolean().optional().describe("If true, the scheduler runs immediately. Default: false (manual run).")
|
|
4100
|
+
schedule_enabled: z.boolean().optional().describe("If true, the scheduler runs immediately. Default: false (manual run)."),
|
|
4101
|
+
notifications: campaignNotificationsField
|
|
4036
4102
|
};
|
|
4037
4103
|
var createCampaignTool = {
|
|
4038
4104
|
name: "create_campaign",
|
|
@@ -4046,6 +4112,8 @@ var createCampaignTool = {
|
|
|
4046
4112
|
},
|
|
4047
4113
|
inputSchema: z.object(CreateCampaignInputShape),
|
|
4048
4114
|
handler: async (input, ctx) => {
|
|
4115
|
+
const invalid = input.notifications && alertRoutingInputError(input.notifications);
|
|
4116
|
+
if (invalid) return err(invalid);
|
|
4049
4117
|
const body = {
|
|
4050
4118
|
name: input.name,
|
|
4051
4119
|
campaign_type: input.campaign_type,
|
|
@@ -4061,6 +4129,7 @@ var createCampaignTool = {
|
|
|
4061
4129
|
...input.labels !== void 0 ? { labels: input.labels } : {},
|
|
4062
4130
|
...input.policy_set_id !== void 0 ? { policy_set_id: input.policy_set_id } : {},
|
|
4063
4131
|
...input.schedule_enabled !== void 0 ? { schedule_enabled: input.schedule_enabled } : {},
|
|
4132
|
+
...input.notifications !== void 0 ? { notifications: notificationsBody(input.notifications) } : {},
|
|
4064
4133
|
...pickCampaignConfigBody(input)
|
|
4065
4134
|
};
|
|
4066
4135
|
const result = await ctx.api.createCampaign(body);
|
|
@@ -4238,11 +4307,12 @@ var UpdateCampaignInputShape = {
|
|
|
4238
4307
|
...campaignConfigFields,
|
|
4239
4308
|
labels: z.record(z.string()).optional().describe("Replace the label map."),
|
|
4240
4309
|
policy_set_id: z.string().uuid().nullable().optional().describe("New policy set UUID; pass null to clear."),
|
|
4241
|
-
schedule_enabled: z.boolean().optional().describe("Pause / resume the scheduler.")
|
|
4310
|
+
schedule_enabled: z.boolean().optional().describe("Pause / resume the scheduler."),
|
|
4311
|
+
notifications: campaignNotificationsField
|
|
4242
4312
|
};
|
|
4243
4313
|
var updateCampaignTool = {
|
|
4244
4314
|
name: "update_campaign",
|
|
4245
|
-
description: "Update one or more fields of a campaign. Fields not supplied are left unchanged. `policy_set_id` accepts null to clear the binding, `referrer` accepts null to clear the publisher page scans are checked from, `max_discovered_ads` accepts null to go back to the platform ad cap, and `ad_formats` accepts null to go back to checking every ad format. `ignore_first_n_domains` is the exception: it is not nullable, so pass 0 to go back to skipping nothing.",
|
|
4315
|
+
description: "Update one or more fields of a campaign. Fields not supplied are left unchanged. `policy_set_id` accepts null to clear the binding, `referrer` accepts null to clear the publisher page scans are checked from, `max_discovered_ads` accepts null to go back to the platform ad cap, and `ad_formats` accepts null to go back to checking every ad format. `ignore_first_n_domains` is the exception: it is not nullable, so pass 0 to go back to skipping nothing. `notifications` is not merged either: when supplied it replaces the campaign's whole alert routing.",
|
|
4246
4316
|
annotations: {
|
|
4247
4317
|
title: "Update Campaign",
|
|
4248
4318
|
readOnlyHint: false,
|
|
@@ -4252,6 +4322,8 @@ var updateCampaignTool = {
|
|
|
4252
4322
|
},
|
|
4253
4323
|
inputSchema: z.object(UpdateCampaignInputShape),
|
|
4254
4324
|
handler: async (input, ctx) => {
|
|
4325
|
+
const invalid = input.notifications && alertRoutingInputError(input.notifications);
|
|
4326
|
+
if (invalid) return err(invalid);
|
|
4255
4327
|
const body = {
|
|
4256
4328
|
...input.name !== void 0 ? { name: input.name } : {},
|
|
4257
4329
|
...input.url !== void 0 ? { url: input.url } : {},
|
|
@@ -4266,6 +4338,7 @@ var updateCampaignTool = {
|
|
|
4266
4338
|
...input.labels !== void 0 ? { labels: input.labels } : {},
|
|
4267
4339
|
...input.policy_set_id !== void 0 ? { policy_set_id: input.policy_set_id } : {},
|
|
4268
4340
|
...input.schedule_enabled !== void 0 ? { schedule_enabled: input.schedule_enabled } : {},
|
|
4341
|
+
...input.notifications !== void 0 ? { notifications: notificationsBody(input.notifications) } : {},
|
|
4269
4342
|
...pickCampaignConfigBody(input)
|
|
4270
4343
|
};
|
|
4271
4344
|
const result = await ctx.api.updateCampaign(input.campaign_id, body);
|
|
@@ -6314,5 +6387,5 @@ function formatToolError(error) {
|
|
|
6314
6387
|
}
|
|
6315
6388
|
|
|
6316
6389
|
export { BearerToken, SERVER_INSTRUCTIONS, createHttpApiGateway, createPinoLogger, declareEmptyResourcesAndPrompts, newRequestId, wireToolsIntoMcpServer };
|
|
6317
|
-
//# sourceMappingURL=chunk-
|
|
6318
|
-
//# sourceMappingURL=chunk-
|
|
6390
|
+
//# sourceMappingURL=chunk-LPABQWHJ.js.map
|
|
6391
|
+
//# sourceMappingURL=chunk-LPABQWHJ.js.map
|