@kaminari-ad/mcp 0.13.0 → 0.14.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 +111 -1
- package/README.md +8 -8
- package/dist/bin.js +3 -3
- package/dist/{chunk-HOKDLKJN.js → chunk-IEOPWWSC.js} +499 -40
- package/dist/chunk-IEOPWWSC.js.map +1 -0
- package/dist/{chunk-VFDW3UL2.js → chunk-YS7IDW24.js} +3 -3
- package/dist/{chunk-VFDW3UL2.js.map → chunk-YS7IDW24.js.map} +1 -1
- package/dist/{http-bootstrap-ROIMZVK2.js → http-bootstrap-XAA75RA3.js} +4 -4
- package/dist/{http-bootstrap-ROIMZVK2.js.map → http-bootstrap-XAA75RA3.js.map} +1 -1
- package/dist/{stdio-bootstrap-VM6OOG2O.js → stdio-bootstrap-II6IVJWD.js} +4 -4
- package/dist/{stdio-bootstrap-VM6OOG2O.js.map → stdio-bootstrap-II6IVJWD.js.map} +1 -1
- package/package.json +3 -1
- package/dist/chunk-HOKDLKJN.js.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { err, ok } from './chunk-
|
|
2
|
+
import { err, ok } from './chunk-YS7IDW24.js';
|
|
3
3
|
import { createHash, randomUUID } from 'crypto';
|
|
4
4
|
import createClient from 'openapi-fetch';
|
|
5
5
|
import { fetch } from 'undici';
|
|
@@ -103,6 +103,20 @@ function newRequestId() {
|
|
|
103
103
|
return randomUUID();
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
+
// src/shared/artifact-limits.ts
|
|
107
|
+
var MAX_TEXT_ARTIFACT_BYTES = 256 * 1024;
|
|
108
|
+
var MAX_BINARY_ARTIFACT_BYTES = 8 * 1024 * 1024;
|
|
109
|
+
function formatBytes(bytes) {
|
|
110
|
+
const kib = 1024;
|
|
111
|
+
if (bytes < kib) return `${String(bytes)} B`;
|
|
112
|
+
const mib = kib * kib;
|
|
113
|
+
if (bytes < mib) return `${(bytes / kib).toFixed(1)} KiB`;
|
|
114
|
+
return `${(bytes / mib).toFixed(1)} MiB`;
|
|
115
|
+
}
|
|
116
|
+
function decodeUtf8(bytes) {
|
|
117
|
+
return new TextDecoder("utf-8").decode(bytes);
|
|
118
|
+
}
|
|
119
|
+
|
|
106
120
|
// src/infrastructure/api/error-mapping.ts
|
|
107
121
|
function detail(parsed) {
|
|
108
122
|
if (parsed === null || typeof parsed !== "object") return "Upstream error";
|
|
@@ -893,7 +907,7 @@ z.object({
|
|
|
893
907
|
filter_date_to: z.union([z.string(), z.null()]).optional(),
|
|
894
908
|
filter_timezone: z.union([z.string(), z.null()]).optional()
|
|
895
909
|
}).passthrough();
|
|
896
|
-
z.object({ updated: z.number().int(), skipped: z.number().int() }).passthrough();
|
|
910
|
+
var BulkUpdateAlertStatusResponse = z.object({ updated: z.number().int(), skipped: z.number().int() }).passthrough();
|
|
897
911
|
var AlertStatsResponse = z.object({
|
|
898
912
|
open: z.number().int(),
|
|
899
913
|
escalated: z.number().int(),
|
|
@@ -1231,9 +1245,11 @@ var schemas = {
|
|
|
1231
1245
|
CustomRuleResponse,
|
|
1232
1246
|
RuleTestResponse,
|
|
1233
1247
|
PolicyEntryResponse,
|
|
1248
|
+
LinkedCampaignResponse,
|
|
1234
1249
|
PolicySetResponse,
|
|
1235
1250
|
PolicySetListItem,
|
|
1236
1251
|
AlertResponse,
|
|
1252
|
+
BulkUpdateAlertStatusResponse,
|
|
1237
1253
|
AlertStatsResponse,
|
|
1238
1254
|
BillingSummaryResponse,
|
|
1239
1255
|
UsageResponse,
|
|
@@ -1388,7 +1404,8 @@ var CampaignGroupSchema = schemas.CampaignGroupResponse.pick({
|
|
|
1388
1404
|
is_archived: true,
|
|
1389
1405
|
schedule_paused: true,
|
|
1390
1406
|
campaign_count: true,
|
|
1391
|
-
created_at: true
|
|
1407
|
+
created_at: true,
|
|
1408
|
+
last_run_at: true
|
|
1392
1409
|
}).strip();
|
|
1393
1410
|
var parseCampaignGroup = (raw) => parseWithSchema(CampaignGroupSchema, raw, "campaign-group");
|
|
1394
1411
|
var parseCampaignGroupArray = (raw) => parseArrayOrItemsWithSchema(CampaignGroupSchema, raw, "campaign-groups");
|
|
@@ -1600,6 +1617,11 @@ var AlertStatsSchema = schemas.AlertStatsResponse.pick({
|
|
|
1600
1617
|
dismissed: true
|
|
1601
1618
|
}).strip();
|
|
1602
1619
|
var parseAlertStats = (raw) => parseWithSchema(AlertStatsSchema, raw, "alert-stats");
|
|
1620
|
+
var BulkUpdateAlertStatusSchema = schemas.BulkUpdateAlertStatusResponse.pick({
|
|
1621
|
+
updated: true,
|
|
1622
|
+
skipped: true
|
|
1623
|
+
}).strip();
|
|
1624
|
+
var parseBulkUpdateAlertStatus = (raw) => parseWithSchema(BulkUpdateAlertStatusSchema, raw, "bulk-update-alert-status");
|
|
1603
1625
|
var UsageSchema = schemas.UsageResponse.pick({
|
|
1604
1626
|
id: true,
|
|
1605
1627
|
scan_id: true,
|
|
@@ -1724,6 +1746,14 @@ var GeoSchema = schemas.GeoResponse.pick({
|
|
|
1724
1746
|
var GeoListSchema = z.array(GeoSchema);
|
|
1725
1747
|
var parseGeoList = (raw) => parseWithSchema(GeoListSchema, raw, "geos");
|
|
1726
1748
|
|
|
1749
|
+
// src/infrastructure/api/parsers/parse-linked-campaign-page.ts
|
|
1750
|
+
var LinkedCampaignSchema = schemas.LinkedCampaignResponse.pick({
|
|
1751
|
+
id: true,
|
|
1752
|
+
name: true,
|
|
1753
|
+
is_archived: true
|
|
1754
|
+
}).strip();
|
|
1755
|
+
var parseLinkedCampaignPage = (raw) => parsePagedWithItemSchema(LinkedCampaignSchema, raw, "policy-set-campaigns");
|
|
1756
|
+
|
|
1727
1757
|
// src/infrastructure/api/parsers/parse-policy-set.ts
|
|
1728
1758
|
var PolicySetSchema = schemas.PolicySetResponse.pick({
|
|
1729
1759
|
id: true,
|
|
@@ -1979,7 +2009,50 @@ function createHttpApiGateway(config) {
|
|
|
1979
2009
|
}
|
|
1980
2010
|
return err(toApiError(status2, error ?? data, response.headers.get("retry-after") ?? void 0));
|
|
1981
2011
|
}
|
|
1982
|
-
async function
|
|
2012
|
+
async function readCapped(response, maxBytes) {
|
|
2013
|
+
const tooLarge = err({
|
|
2014
|
+
kind: "upstream",
|
|
2015
|
+
detail: `Artifact is larger than the ${formatBytes(maxBytes)} this tool will transfer. Open the report URL from \`get_scan\` to view it instead.`
|
|
2016
|
+
});
|
|
2017
|
+
const body = response.body;
|
|
2018
|
+
if (body === null) {
|
|
2019
|
+
return err({ kind: "upstream", detail: "Artifact response had no body." });
|
|
2020
|
+
}
|
|
2021
|
+
const declared = Number(response.headers.get("content-length") ?? Number.NaN);
|
|
2022
|
+
if (Number.isFinite(declared) && declared > maxBytes) {
|
|
2023
|
+
await body.cancel().catch(() => void 0);
|
|
2024
|
+
return tooLarge;
|
|
2025
|
+
}
|
|
2026
|
+
const reader = body.getReader();
|
|
2027
|
+
const chunks = [];
|
|
2028
|
+
let total = 0;
|
|
2029
|
+
try {
|
|
2030
|
+
for (; ; ) {
|
|
2031
|
+
const { done, value } = await reader.read();
|
|
2032
|
+
if (done) break;
|
|
2033
|
+
total += value.byteLength;
|
|
2034
|
+
if (total > maxBytes) {
|
|
2035
|
+
await reader.cancel().catch(() => void 0);
|
|
2036
|
+
return tooLarge;
|
|
2037
|
+
}
|
|
2038
|
+
chunks.push(value);
|
|
2039
|
+
}
|
|
2040
|
+
} catch (cause) {
|
|
2041
|
+
await reader.cancel().catch(() => void 0);
|
|
2042
|
+
return err({
|
|
2043
|
+
kind: "upstream",
|
|
2044
|
+
detail: cause instanceof Error ? cause.message : "artifact stream failed"
|
|
2045
|
+
});
|
|
2046
|
+
}
|
|
2047
|
+
const out = new Uint8Array(total);
|
|
2048
|
+
let offset = 0;
|
|
2049
|
+
for (const chunk of chunks) {
|
|
2050
|
+
out.set(chunk, offset);
|
|
2051
|
+
offset += chunk.byteLength;
|
|
2052
|
+
}
|
|
2053
|
+
return ok(out);
|
|
2054
|
+
}
|
|
2055
|
+
async function binaryGet(path, maxBytes, query) {
|
|
1983
2056
|
const startedAtMs = Date.now();
|
|
1984
2057
|
const qs = query && Object.keys(query).length > 0 ? `?${new URLSearchParams(
|
|
1985
2058
|
Object.fromEntries(Object.entries(query).map(([k, v]) => [k, String(v)]))
|
|
@@ -1994,7 +2067,7 @@ function createHttpApiGateway(config) {
|
|
|
1994
2067
|
// update + a tenant-isolation review.
|
|
1995
2068
|
headers: {
|
|
1996
2069
|
authorization: bearer.toAuthorizationHeader(),
|
|
1997
|
-
accept: "image/*, application/pdf, application/octet-stream",
|
|
2070
|
+
accept: "image/*, application/pdf, video/mp4, text/plain, application/xml, text/xml, application/octet-stream",
|
|
1998
2071
|
"user-agent": "kaminari-ad-mcp",
|
|
1999
2072
|
"x-request-id": requestId
|
|
2000
2073
|
}
|
|
@@ -2012,9 +2085,10 @@ function createHttpApiGateway(config) {
|
|
|
2012
2085
|
"api.done"
|
|
2013
2086
|
);
|
|
2014
2087
|
if (status2 >= 200 && status2 < 300) {
|
|
2015
|
-
const
|
|
2088
|
+
const read = await readCapped(response, maxBytes);
|
|
2089
|
+
if (read.isErr()) return err(read.error);
|
|
2016
2090
|
return ok({
|
|
2017
|
-
bytes:
|
|
2091
|
+
bytes: read.value,
|
|
2018
2092
|
contentType: response.headers.get("content-type") ?? "application/octet-stream"
|
|
2019
2093
|
});
|
|
2020
2094
|
}
|
|
@@ -2077,22 +2151,37 @@ function createHttpApiGateway(config) {
|
|
|
2077
2151
|
return call("PUT", "/api/v1/account/labels", { body }, parseArrayOf(parseLabelDefinition));
|
|
2078
2152
|
},
|
|
2079
2153
|
async getScanScreenshot(scanId, w2) {
|
|
2080
|
-
return binaryGet(
|
|
2154
|
+
return binaryGet(
|
|
2155
|
+
`/api/v1/scans/${scanId}/screenshot`,
|
|
2156
|
+
MAX_BINARY_ARTIFACT_BYTES,
|
|
2157
|
+
w2 !== void 0 ? { w: w2 } : void 0
|
|
2158
|
+
);
|
|
2081
2159
|
},
|
|
2082
2160
|
async getScanCreativeScreenshot(scanId, w2) {
|
|
2083
2161
|
return binaryGet(
|
|
2084
2162
|
`/api/v1/scans/${scanId}/creative-screenshot`,
|
|
2163
|
+
MAX_BINARY_ARTIFACT_BYTES,
|
|
2085
2164
|
w2 !== void 0 ? { w: w2 } : void 0
|
|
2086
2165
|
);
|
|
2087
2166
|
},
|
|
2088
2167
|
async getScanLandingScreenshot(scanId, landingOrd, w2) {
|
|
2089
2168
|
return binaryGet(
|
|
2090
2169
|
`/api/v1/scans/${scanId}/landings/${String(landingOrd)}/screenshot`,
|
|
2170
|
+
MAX_BINARY_ARTIFACT_BYTES,
|
|
2091
2171
|
w2 !== void 0 ? { w: w2 } : void 0
|
|
2092
2172
|
);
|
|
2093
2173
|
},
|
|
2174
|
+
async getScanCreativeHtml(scanId) {
|
|
2175
|
+
return binaryGet(`/api/v1/scans/${scanId}/creative-html`, MAX_TEXT_ARTIFACT_BYTES);
|
|
2176
|
+
},
|
|
2177
|
+
async getScanCreativeVideo(scanId) {
|
|
2178
|
+
return binaryGet(`/api/v1/scans/${scanId}/creative-video`, MAX_BINARY_ARTIFACT_BYTES);
|
|
2179
|
+
},
|
|
2180
|
+
async getScanVastXml(scanId) {
|
|
2181
|
+
return binaryGet(`/api/v1/scans/${scanId}/vast-xml`, MAX_TEXT_ARTIFACT_BYTES);
|
|
2182
|
+
},
|
|
2094
2183
|
async getInvoicePdf(invoiceId) {
|
|
2095
|
-
return binaryGet(`/api/v1/invoices/${invoiceId}/pdf
|
|
2184
|
+
return binaryGet(`/api/v1/invoices/${invoiceId}/pdf`, MAX_BINARY_ARTIFACT_BYTES);
|
|
2096
2185
|
},
|
|
2097
2186
|
async listApiKeys() {
|
|
2098
2187
|
return call("GET", "/api/v1/account/api-keys", {}, parseApiKeyList);
|
|
@@ -2435,6 +2524,30 @@ function createHttpApiGateway(config) {
|
|
|
2435
2524
|
parseEmpty
|
|
2436
2525
|
);
|
|
2437
2526
|
},
|
|
2527
|
+
async listPolicySetCampaigns(id, filters) {
|
|
2528
|
+
return call(
|
|
2529
|
+
"GET",
|
|
2530
|
+
"/api/v1/policy-sets/{policy_set_id}/campaigns",
|
|
2531
|
+
{ params: { path: { policy_set_id: id }, query: filters } },
|
|
2532
|
+
parseLinkedCampaignPage
|
|
2533
|
+
);
|
|
2534
|
+
},
|
|
2535
|
+
async attachPolicySetCampaigns(id, body) {
|
|
2536
|
+
return call(
|
|
2537
|
+
"POST",
|
|
2538
|
+
"/api/v1/policy-sets/{policy_set_id}/campaigns/attach",
|
|
2539
|
+
{ params: { path: { policy_set_id: id } }, body },
|
|
2540
|
+
parseEmpty
|
|
2541
|
+
);
|
|
2542
|
+
},
|
|
2543
|
+
async detachPolicySetCampaigns(id, body) {
|
|
2544
|
+
return call(
|
|
2545
|
+
"POST",
|
|
2546
|
+
"/api/v1/policy-sets/{policy_set_id}/campaigns/detach",
|
|
2547
|
+
{ params: { path: { policy_set_id: id } }, body },
|
|
2548
|
+
parseEmpty
|
|
2549
|
+
);
|
|
2550
|
+
},
|
|
2438
2551
|
async requestPolicySetApproval(id) {
|
|
2439
2552
|
return call(
|
|
2440
2553
|
"POST",
|
|
@@ -2444,8 +2557,13 @@ function createHttpApiGateway(config) {
|
|
|
2444
2557
|
);
|
|
2445
2558
|
},
|
|
2446
2559
|
// ── Custom taxonomies ─────────────────────────────────────────
|
|
2447
|
-
async listCustomTaxonomies() {
|
|
2448
|
-
return call(
|
|
2560
|
+
async listCustomTaxonomies(filters) {
|
|
2561
|
+
return call(
|
|
2562
|
+
"GET",
|
|
2563
|
+
"/api/v1/custom-taxonomies",
|
|
2564
|
+
{ params: { query: filters ?? {} } },
|
|
2565
|
+
parseCustomTaxonomyList
|
|
2566
|
+
);
|
|
2449
2567
|
},
|
|
2450
2568
|
async getCustomTaxonomy(id) {
|
|
2451
2569
|
return call(
|
|
@@ -2502,8 +2620,11 @@ function createHttpApiGateway(config) {
|
|
|
2502
2620
|
parseEmpty
|
|
2503
2621
|
);
|
|
2504
2622
|
},
|
|
2505
|
-
async
|
|
2506
|
-
return call("
|
|
2623
|
+
async bulkUpdateAlertStatus(body) {
|
|
2624
|
+
return call("POST", "/api/v1/alerts/bulk-status", { body }, parseBulkUpdateAlertStatus);
|
|
2625
|
+
},
|
|
2626
|
+
async getAlertStats(filters) {
|
|
2627
|
+
return call("GET", "/api/v1/alerts/stats", { params: { query: filters } }, parseAlertStats);
|
|
2507
2628
|
},
|
|
2508
2629
|
// ── Webhooks ──────────────────────────────────────────────────
|
|
2509
2630
|
async listWebhooks() {
|
|
@@ -3107,7 +3228,7 @@ var GetCampaignAlertOverridesInputShape = {
|
|
|
3107
3228
|
};
|
|
3108
3229
|
var getCampaignAlertOverridesTool = {
|
|
3109
3230
|
name: "get_campaign_alert_overrides",
|
|
3110
|
-
description: "Get the per-campaign override of which alert destinations receive its alerts. `mode` is one of `inherit` (
|
|
3231
|
+
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).",
|
|
3111
3232
|
annotations: {
|
|
3112
3233
|
title: "Get Campaign Alert Overrides",
|
|
3113
3234
|
readOnlyHint: true,
|
|
@@ -3167,16 +3288,21 @@ var setAlertDestinationVersionTool = {
|
|
|
3167
3288
|
};
|
|
3168
3289
|
var SetCampaignAlertOverridesInputShape = {
|
|
3169
3290
|
campaign_id: z.string().uuid().describe("Campaign UUID."),
|
|
3170
|
-
|
|
3171
|
-
|
|
3291
|
+
// enum-drift: allow — the API validates these three values but types
|
|
3292
|
+
// the field as a plain string, so no generated schema constrains it.
|
|
3293
|
+
// Swap to `schemas.CampaignOverrideMode` after the api enum change
|
|
3294
|
+
// (`fix/type-override-mode-and-unlist-forms`) reaches prod and the
|
|
3295
|
+
// next `make gen-api-types` picks it up.
|
|
3296
|
+
mode: z.enum(["inherit", "override", "silence"]).describe(
|
|
3297
|
+
"Routing mode: `inherit` (fall back to the org-wide destinations), `override` (route ONLY to `destination_ids`), `silence` (send nothing for this campaign)."
|
|
3172
3298
|
),
|
|
3173
3299
|
destination_ids: z.array(z.string().uuid()).max(50).default([]).describe(
|
|
3174
|
-
"Destination UUIDs
|
|
3300
|
+
"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`."
|
|
3175
3301
|
)
|
|
3176
3302
|
};
|
|
3177
3303
|
var setCampaignAlertOverridesTool = {
|
|
3178
3304
|
name: "set_campaign_alert_overrides",
|
|
3179
|
-
description: "REPLACE the per-campaign alert-routing override. `mode=inherit`
|
|
3305
|
+
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`.",
|
|
3180
3306
|
annotations: {
|
|
3181
3307
|
title: "Set Campaign Alert Overrides",
|
|
3182
3308
|
readOnlyHint: false,
|
|
@@ -3186,6 +3312,13 @@ var setCampaignAlertOverridesTool = {
|
|
|
3186
3312
|
},
|
|
3187
3313
|
inputSchema: z.object(SetCampaignAlertOverridesInputShape),
|
|
3188
3314
|
handler: async (input, ctx) => {
|
|
3315
|
+
if (input.mode !== "override" && input.destination_ids.length > 0) {
|
|
3316
|
+
return err({
|
|
3317
|
+
kind: "invalid-input",
|
|
3318
|
+
message: `destination_ids is only accepted with mode "override", got "${input.mode}". Use mode "override" to route to specific destinations.`,
|
|
3319
|
+
fieldErrors: { destination_ids: ["only valid with mode 'override'"] }
|
|
3320
|
+
});
|
|
3321
|
+
}
|
|
3189
3322
|
const result = await ctx.api.setCampaignAlertOverrides(input.campaign_id, {
|
|
3190
3323
|
mode: input.mode,
|
|
3191
3324
|
destination_ids: input.destination_ids
|
|
@@ -3194,10 +3327,116 @@ var setCampaignAlertOverridesTool = {
|
|
|
3194
3327
|
return ok({ updated: true });
|
|
3195
3328
|
}
|
|
3196
3329
|
};
|
|
3197
|
-
var
|
|
3330
|
+
var MAX_IDS = 1e3;
|
|
3331
|
+
var FILTER_FIELDS = [
|
|
3332
|
+
"filter_status",
|
|
3333
|
+
"filter_campaign_id",
|
|
3334
|
+
"filter_policy_set_ids",
|
|
3335
|
+
"filter_tag_slugs",
|
|
3336
|
+
"filter_country_codes",
|
|
3337
|
+
"filter_date_from",
|
|
3338
|
+
"filter_date_to",
|
|
3339
|
+
"filter_timezone"
|
|
3340
|
+
];
|
|
3341
|
+
var BulkUpdateAlertStatusInputShape = {
|
|
3342
|
+
status: z.enum(["open", "escalated", "resolved", "dismissed"]).describe("Status to move the selected alerts to."),
|
|
3343
|
+
ids: z.array(z.string().uuid()).min(1).max(MAX_IDS).optional().describe(
|
|
3344
|
+
`Explicit alert UUIDs, at most ${String(MAX_IDS)} per call. Mutually exclusive with \`all_matching\`.`
|
|
3345
|
+
),
|
|
3346
|
+
all_matching: z.boolean().optional().describe(
|
|
3347
|
+
"Apply to every alert matching the `filter_*` fields instead of an id list. With no filters this means EVERY alert in the organization, so filter first and confirm the scope with `get_alert_stats` using the same filters."
|
|
3348
|
+
),
|
|
3349
|
+
filter_status: z.enum(["open", "escalated", "resolved", "dismissed"]).optional().describe("Only with `all_matching`: restrict to alerts currently in this status."),
|
|
3350
|
+
filter_campaign_id: z.string().uuid().optional().describe("Only with `all_matching`: restrict to one campaign."),
|
|
3351
|
+
filter_policy_set_ids: z.array(z.string().uuid()).min(1).optional().describe("Only with `all_matching`: restrict to alerts raised by any of these policy sets."),
|
|
3352
|
+
filter_tag_slugs: z.array(z.string().min(1)).min(1).optional().describe("Only with `all_matching`: restrict to alerts carrying any of these tag slugs."),
|
|
3353
|
+
filter_country_codes: z.array(z.string().min(2).max(2)).min(1).optional().describe("Only with `all_matching`: restrict to these ISO 3166-1 alpha-2 countries."),
|
|
3354
|
+
filter_date_from: z.string().date().optional().describe("Only with `all_matching`: inclusive lower bound (YYYY-MM-DD) on alert creation."),
|
|
3355
|
+
filter_date_to: z.string().date().optional().describe("Only with `all_matching`: inclusive upper bound (YYYY-MM-DD) on alert creation."),
|
|
3356
|
+
filter_timezone: z.string().optional().describe(
|
|
3357
|
+
"IANA timezone for the filter date bounds. REQUIRED by the API whenever either date is set."
|
|
3358
|
+
)
|
|
3359
|
+
};
|
|
3360
|
+
var bulkUpdateAlertStatusTool = {
|
|
3361
|
+
name: "bulk_update_alert_status",
|
|
3362
|
+
description: "Change the status of many alerts at once. Select EITHER an explicit `ids` list (max 1000) OR `all_matching: true` with the `filter_*` fields, which mirror `list_alerts`. Alerts already in a state the transition cannot leave are skipped rather than failing the call, so the response reports `updated` and `skipped` counts \u2014 compare `updated + skipped` against the selection size to see how many were no-ops. Prefer `update_alert_status` for a single alert.",
|
|
3363
|
+
annotations: {
|
|
3364
|
+
title: "Bulk Update Alert Status",
|
|
3365
|
+
readOnlyHint: false,
|
|
3366
|
+
destructiveHint: false,
|
|
3367
|
+
idempotentHint: true,
|
|
3368
|
+
openWorldHint: false
|
|
3369
|
+
},
|
|
3370
|
+
inputSchema: z.object(BulkUpdateAlertStatusInputShape),
|
|
3371
|
+
handler: async (input, ctx) => {
|
|
3372
|
+
const matchesAll = input.all_matching === true;
|
|
3373
|
+
if (input.ids !== void 0 === matchesAll) {
|
|
3374
|
+
return err({
|
|
3375
|
+
kind: "invalid-input",
|
|
3376
|
+
message: "Select alerts with either `ids` or `all_matching: true` \u2014 not both, and not neither."
|
|
3377
|
+
});
|
|
3378
|
+
}
|
|
3379
|
+
const suppliedFilters = FILTER_FIELDS.filter((field) => input[field] !== void 0);
|
|
3380
|
+
if (!matchesAll && suppliedFilters.length > 0) {
|
|
3381
|
+
return err({
|
|
3382
|
+
kind: "invalid-input",
|
|
3383
|
+
message: `\`${suppliedFilters.join("`, `")}\` applies only with \`all_matching: true\`. An explicit \`ids\` list is already the selection, so the filters would be ignored.`
|
|
3384
|
+
});
|
|
3385
|
+
}
|
|
3386
|
+
if ((input.filter_date_from !== void 0 || input.filter_date_to !== void 0) && input.filter_timezone === void 0) {
|
|
3387
|
+
return err({
|
|
3388
|
+
kind: "invalid-input",
|
|
3389
|
+
message: "filter_timezone is required whenever filter_date_from or filter_date_to is set.",
|
|
3390
|
+
fieldErrors: { filter_timezone: ["required alongside a filter date bound"] }
|
|
3391
|
+
});
|
|
3392
|
+
}
|
|
3393
|
+
const result = await ctx.api.bulkUpdateAlertStatus({
|
|
3394
|
+
status: input.status,
|
|
3395
|
+
all_matching: matchesAll,
|
|
3396
|
+
...input.ids !== void 0 ? { ids: [...input.ids] } : {},
|
|
3397
|
+
...input.filter_status !== void 0 ? { filter_status: input.filter_status } : {},
|
|
3398
|
+
...input.filter_campaign_id !== void 0 ? { filter_campaign_id: input.filter_campaign_id } : {},
|
|
3399
|
+
...input.filter_policy_set_ids !== void 0 ? { filter_policy_set_ids: [...input.filter_policy_set_ids] } : {},
|
|
3400
|
+
...input.filter_tag_slugs !== void 0 ? { filter_tag_slugs: [...input.filter_tag_slugs] } : {},
|
|
3401
|
+
...input.filter_country_codes !== void 0 ? { filter_country_codes: [...input.filter_country_codes] } : {},
|
|
3402
|
+
...input.filter_date_from !== void 0 ? { filter_date_from: input.filter_date_from } : {},
|
|
3403
|
+
...input.filter_date_to !== void 0 ? { filter_date_to: input.filter_date_to } : {},
|
|
3404
|
+
...input.filter_timezone !== void 0 ? { filter_timezone: input.filter_timezone } : {}
|
|
3405
|
+
});
|
|
3406
|
+
if (result.isErr()) return err(mapApiError(result.error));
|
|
3407
|
+
return ok(result.value);
|
|
3408
|
+
}
|
|
3409
|
+
};
|
|
3410
|
+
var alertFilterFields = {
|
|
3411
|
+
campaign_id: z.string().uuid().optional().describe("Filter to one campaign's alerts."),
|
|
3412
|
+
policy_set_id: z.string().optional().describe(
|
|
3413
|
+
"Comma-separated policy-set UUIDs. Matches alerts raised by any of them; see `list_policy_sets`."
|
|
3414
|
+
),
|
|
3415
|
+
tag: z.string().optional().describe("Comma-separated tag slugs. Matches alerts carrying any of them; see `list_tags`."),
|
|
3416
|
+
country_code: z.string().optional().describe("Comma-separated ISO 3166-1 alpha-2 country codes, e.g. US,DE,JP."),
|
|
3417
|
+
date_from: z.string().date().optional().describe("ISO date (YYYY-MM-DD), inclusive lower bound on alert creation."),
|
|
3418
|
+
date_to: z.string().date().optional().describe("ISO date (YYYY-MM-DD), inclusive upper bound on alert creation."),
|
|
3419
|
+
timezone: z.string().optional().describe(
|
|
3420
|
+
"IANA timezone (e.g. 'Europe/Berlin') the date bounds are read in. Defaults to UTC. Omitting both dates spans all time."
|
|
3421
|
+
)
|
|
3422
|
+
};
|
|
3423
|
+
function toAlertFilterQuery(input) {
|
|
3424
|
+
return {
|
|
3425
|
+
...input.campaign_id !== void 0 ? { campaign_id: input.campaign_id } : {},
|
|
3426
|
+
...input.policy_set_id !== void 0 ? { policy_set_id: input.policy_set_id } : {},
|
|
3427
|
+
...input.tag !== void 0 ? { tag: input.tag } : {},
|
|
3428
|
+
...input.country_code !== void 0 ? { country_code: input.country_code } : {},
|
|
3429
|
+
...input.date_from !== void 0 ? { date_from: input.date_from } : {},
|
|
3430
|
+
...input.date_to !== void 0 ? { date_to: input.date_to } : {},
|
|
3431
|
+
...input.timezone !== void 0 ? { timezone: input.timezone } : {}
|
|
3432
|
+
};
|
|
3433
|
+
}
|
|
3434
|
+
|
|
3435
|
+
// src/application/tools/alerts/get-alert-stats.tool.ts
|
|
3436
|
+
var GetAlertStatsInputShape = alertFilterFields;
|
|
3198
3437
|
var getAlertStatsTool = {
|
|
3199
3438
|
name: "get_alert_stats",
|
|
3200
|
-
description: "Get alert counts grouped by status: open, escalated, resolved, dismissed.
|
|
3439
|
+
description: "Get alert counts grouped by status: open, escalated, resolved, dismissed. Accepts every filter `list_alerts` does except `status` \u2014 the response buckets BY status \u2014 so passing the same filters to both makes the four counts sum to the `total` that `list_alerts` reports. Unfiltered, the counts cover the whole organization over all time. Use this to size a selection before calling `bulk_update_alert_status` with `all_matching`.",
|
|
3201
3440
|
annotations: {
|
|
3202
3441
|
title: "Get Alert Stats",
|
|
3203
3442
|
readOnlyHint: true,
|
|
@@ -3206,14 +3445,14 @@ var getAlertStatsTool = {
|
|
|
3206
3445
|
openWorldHint: false
|
|
3207
3446
|
},
|
|
3208
3447
|
inputSchema: z.object(GetAlertStatsInputShape),
|
|
3209
|
-
handler: async (
|
|
3210
|
-
const result = await ctx.api.getAlertStats();
|
|
3448
|
+
handler: async (input, ctx) => {
|
|
3449
|
+
const result = await ctx.api.getAlertStats(toAlertFilterQuery(input));
|
|
3211
3450
|
if (result.isErr()) return err(mapApiError(result.error));
|
|
3212
3451
|
return ok(result.value);
|
|
3213
3452
|
}
|
|
3214
3453
|
};
|
|
3215
3454
|
var ListAlertsInputShape = {
|
|
3216
|
-
|
|
3455
|
+
...alertFilterFields,
|
|
3217
3456
|
status: z.enum(["open", "escalated", "resolved", "dismissed"]).optional().describe(
|
|
3218
3457
|
"Filter by alert status. Open = unhandled; escalated = flagged for attention; resolved = closed; dismissed = closed without action."
|
|
3219
3458
|
),
|
|
@@ -3222,7 +3461,7 @@ var ListAlertsInputShape = {
|
|
|
3222
3461
|
};
|
|
3223
3462
|
var listAlertsTool = {
|
|
3224
3463
|
name: "list_alerts",
|
|
3225
|
-
description: "List violation alerts (one per scan + violating rule combo) with offer URL, country, status, scan back-reference, and the kind-aware fields `rule_type` (tag / iab_v3 / brand / ai_category / custom_taxonomy) + `matched_value` (the canonical text the scan matched against).",
|
|
3464
|
+
description: "List violation alerts (one per scan + violating rule combo) with offer URL, country, status, scan back-reference, and the kind-aware fields `rule_type` (tag / iab_v3 / brand / ai_category / custom_taxonomy) + `matched_value` (the canonical text the scan matched against). Filter by campaign, status, policy set, tag, country and creation-date range; `policy_set_id`, `tag` and `country_code` take comma-separated values and match any of them. Pass the same filters to `get_alert_stats` to get counts that agree with this list.",
|
|
3226
3465
|
annotations: {
|
|
3227
3466
|
title: "List Alerts",
|
|
3228
3467
|
readOnlyHint: true,
|
|
@@ -3235,7 +3474,7 @@ var listAlertsTool = {
|
|
|
3235
3474
|
const filters = {
|
|
3236
3475
|
page: input.page,
|
|
3237
3476
|
limit: input.limit,
|
|
3238
|
-
...input
|
|
3477
|
+
...toAlertFilterQuery(input),
|
|
3239
3478
|
...input.status !== void 0 ? { status: input.status } : {}
|
|
3240
3479
|
};
|
|
3241
3480
|
const result = await ctx.api.listAlerts(filters);
|
|
@@ -3458,12 +3697,34 @@ var getCampaignGroupTool = {
|
|
|
3458
3697
|
return ok(result.value);
|
|
3459
3698
|
}
|
|
3460
3699
|
};
|
|
3700
|
+
var campaignDateFilterFields = {
|
|
3701
|
+
created_from: z.string().date().optional().describe("ISO date (YYYY-MM-DD), inclusive lower bound on the creation day."),
|
|
3702
|
+
created_to: z.string().date().optional().describe("ISO date (YYYY-MM-DD), inclusive upper bound on the creation day."),
|
|
3703
|
+
last_run_from: z.string().date().optional().describe(
|
|
3704
|
+
"ISO date (YYYY-MM-DD), inclusive lower bound on the most recent run. Setting either last-run bound excludes anything that has never run."
|
|
3705
|
+
),
|
|
3706
|
+
last_run_to: z.string().date().optional().describe("ISO date (YYYY-MM-DD), inclusive upper bound on the most recent run."),
|
|
3707
|
+
timezone: z.string().optional().describe("IANA timezone (e.g. 'Europe/Berlin') the date bounds are read in. Defaults to UTC.")
|
|
3708
|
+
};
|
|
3709
|
+
function toCampaignDateFilterQuery(input) {
|
|
3710
|
+
return {
|
|
3711
|
+
...input.created_from !== void 0 ? { created_from: input.created_from } : {},
|
|
3712
|
+
...input.created_to !== void 0 ? { created_to: input.created_to } : {},
|
|
3713
|
+
...input.last_run_from !== void 0 ? { last_run_from: input.last_run_from } : {},
|
|
3714
|
+
...input.last_run_to !== void 0 ? { last_run_to: input.last_run_to } : {},
|
|
3715
|
+
...input.timezone !== void 0 ? { timezone: input.timezone } : {}
|
|
3716
|
+
};
|
|
3717
|
+
}
|
|
3718
|
+
|
|
3719
|
+
// src/application/tools/campaign-groups/list-campaign-groups.tool.ts
|
|
3461
3720
|
var ListCampaignGroupsInputShape = {
|
|
3462
|
-
archived: z.boolean().optional().describe("If true, list ONLY archived groups. Default: only active groups.")
|
|
3721
|
+
archived: z.boolean().optional().describe("If true, list ONLY archived groups. Default: only active groups."),
|
|
3722
|
+
q: z.string().min(1).max(200).optional().describe("Substring search against group name (case-insensitive)."),
|
|
3723
|
+
...campaignDateFilterFields
|
|
3463
3724
|
};
|
|
3464
3725
|
var listCampaignGroupsTool = {
|
|
3465
3726
|
name: "list_campaign_groups",
|
|
3466
|
-
description: "List campaign groups \u2014 folders that group related campaigns
|
|
3727
|
+
description: "List campaign groups \u2014 folders that group related campaigns \u2014 with per-group campaign count and `last_run_at`. Filter by archived flag, name substring, or creation / last-run date range; a group's last run is the newest run across its campaigns. Not paginated; the org-scoped list is typically small (a few dozen groups max).",
|
|
3467
3728
|
annotations: {
|
|
3468
3729
|
title: "List Campaign Groups",
|
|
3469
3730
|
readOnlyHint: true,
|
|
@@ -3473,7 +3734,11 @@ var listCampaignGroupsTool = {
|
|
|
3473
3734
|
},
|
|
3474
3735
|
inputSchema: z.object(ListCampaignGroupsInputShape),
|
|
3475
3736
|
handler: async (input, ctx) => {
|
|
3476
|
-
const filters =
|
|
3737
|
+
const filters = {
|
|
3738
|
+
...toCampaignDateFilterQuery(input),
|
|
3739
|
+
...input.archived !== void 0 ? { archived: input.archived } : {},
|
|
3740
|
+
...input.q !== void 0 ? { q: input.q } : {}
|
|
3741
|
+
};
|
|
3477
3742
|
const result = await ctx.api.listCampaignGroups(filters);
|
|
3478
3743
|
if (result.isErr()) return err(mapApiError(result.error));
|
|
3479
3744
|
return ok({ items: result.value });
|
|
@@ -3798,12 +4063,13 @@ var ListCampaignsInputShape = {
|
|
|
3798
4063
|
"When omitted, the API returns active campaigns only. Pass `true` to see archived campaigns, `false` for active-only (explicit)."
|
|
3799
4064
|
),
|
|
3800
4065
|
q: z.string().min(1).max(200).optional().describe("Substring search against campaign name (case-insensitive)."),
|
|
4066
|
+
...campaignDateFilterFields,
|
|
3801
4067
|
page: z.number().int().min(1).max(500).default(1).describe("1-indexed page number."),
|
|
3802
4068
|
limit: z.number().int().min(1).max(200).default(50).describe("Page size (1-200).")
|
|
3803
4069
|
};
|
|
3804
4070
|
var listCampaignsTool = {
|
|
3805
4071
|
name: "list_campaigns",
|
|
3806
|
-
description: "List campaigns for the caller's organization, optionally filtered by group, archived flag, or
|
|
4072
|
+
description: "List campaigns for the caller's organization, optionally filtered by group, archived flag, name substring, or creation / last-run date range. Paginated. Each row carries the same fields as `get_campaign`, including the repeat / retry settings (`repeat_count`, `repeat_mode`, `retry_max_attempts`) \u2014 use them to spot the campaigns that multiply their per-run scan count.",
|
|
3807
4073
|
annotations: {
|
|
3808
4074
|
title: "List Campaigns",
|
|
3809
4075
|
readOnlyHint: true,
|
|
@@ -3816,6 +4082,7 @@ var listCampaignsTool = {
|
|
|
3816
4082
|
const filters = {
|
|
3817
4083
|
page: input.page,
|
|
3818
4084
|
limit: input.limit,
|
|
4085
|
+
...toCampaignDateFilterQuery(input),
|
|
3819
4086
|
...input.group_id !== void 0 ? { group_id: input.group_id } : {},
|
|
3820
4087
|
...input.archived !== void 0 ? { archived: input.archived } : {},
|
|
3821
4088
|
...input.q !== void 0 ? { q: input.q } : {}
|
|
@@ -4259,10 +4526,14 @@ var getCustomTaxonomyTool = {
|
|
|
4259
4526
|
return ok(result.value);
|
|
4260
4527
|
}
|
|
4261
4528
|
};
|
|
4262
|
-
var ListCustomTaxonomiesInputShape = {
|
|
4529
|
+
var ListCustomTaxonomiesInputShape = {
|
|
4530
|
+
include_inactive: z.boolean().optional().describe(
|
|
4531
|
+
"Include soft-deleted taxonomies, which are excluded by default. Pass `true` to find one to `restore_custom_taxonomy`, or to resolve a taxonomy id seen on an older scan."
|
|
4532
|
+
)
|
|
4533
|
+
};
|
|
4263
4534
|
var listCustomTaxonomiesTool = {
|
|
4264
4535
|
name: "list_custom_taxonomies",
|
|
4265
|
-
description: "List the calling org's custom classification taxonomies. Returns slim summaries (id, name, slug, version, node_count, is_active).
|
|
4536
|
+
description: "List the calling org's custom classification taxonomies. Returns slim summaries (id, name, slug, version, node_count, is_active). Only active taxonomies are listed unless you pass `include_inactive: true`; soft-deleted ones then appear with `is_active=false`. Fetch the full tree via `get_custom_taxonomy`.",
|
|
4266
4537
|
annotations: {
|
|
4267
4538
|
title: "List Custom Taxonomies",
|
|
4268
4539
|
readOnlyHint: true,
|
|
@@ -4271,8 +4542,10 @@ var listCustomTaxonomiesTool = {
|
|
|
4271
4542
|
openWorldHint: false
|
|
4272
4543
|
},
|
|
4273
4544
|
inputSchema: z.object(ListCustomTaxonomiesInputShape),
|
|
4274
|
-
handler: async (
|
|
4275
|
-
const result = await ctx.api.listCustomTaxonomies(
|
|
4545
|
+
handler: async (input, ctx) => {
|
|
4546
|
+
const result = await ctx.api.listCustomTaxonomies(
|
|
4547
|
+
input.include_inactive !== void 0 ? { include_inactive: input.include_inactive } : {}
|
|
4548
|
+
);
|
|
4276
4549
|
if (result.isErr()) return err(mapApiError(result.error));
|
|
4277
4550
|
return ok({ items: result.value });
|
|
4278
4551
|
}
|
|
@@ -4478,6 +4751,34 @@ var listInvoicesTool = {
|
|
|
4478
4751
|
return ok(result.value);
|
|
4479
4752
|
}
|
|
4480
4753
|
};
|
|
4754
|
+
var MAX_CAMPAIGNS = 500;
|
|
4755
|
+
var AttachPolicySetCampaignsInputShape = {
|
|
4756
|
+
policy_set_id: z.string().uuid().describe("Policy set UUID."),
|
|
4757
|
+
campaign_ids: z.array(z.string().uuid()).min(1).max(MAX_CAMPAIGNS).describe(
|
|
4758
|
+
`Campaign UUIDs to bind, at most ${String(MAX_CAMPAIGNS)} per call. A campaign already on THIS set is a no-op; a campaign on a DIFFERENT set is moved to this one.`
|
|
4759
|
+
)
|
|
4760
|
+
};
|
|
4761
|
+
var attachPolicySetCampaignsTool = {
|
|
4762
|
+
name: "attach_policy_set_campaigns",
|
|
4763
|
+
description: "Bind campaigns to a policy set. This and `detach_policy_set_campaigns` are the only way to change membership \u2014 `update_policy_set` edits the set's name, description and rules, never its campaigns. Membership changes here are incremental: this set's other members are left alone. IMPORTANT: a campaign can belong to only one policy set, so naming a campaign currently on a DIFFERENT set MOVES it here \u2014 it stops being evaluated against its old set's rules, and no warning is returned. Check `list_campaigns` (`policy_set_id`) first when the campaign may already be bound elsewhere. Split lists longer than 500 across calls; verify with `list_policy_set_campaigns`.",
|
|
4764
|
+
annotations: {
|
|
4765
|
+
title: "Attach Policy Set Campaigns",
|
|
4766
|
+
readOnlyHint: false,
|
|
4767
|
+
// Reassigns a campaign away from whatever set it was on, and the
|
|
4768
|
+
// previous binding is not recoverable from this tool's output.
|
|
4769
|
+
destructiveHint: true,
|
|
4770
|
+
idempotentHint: true,
|
|
4771
|
+
openWorldHint: false
|
|
4772
|
+
},
|
|
4773
|
+
inputSchema: z.object(AttachPolicySetCampaignsInputShape),
|
|
4774
|
+
handler: async (input, ctx) => {
|
|
4775
|
+
const result = await ctx.api.attachPolicySetCampaigns(input.policy_set_id, {
|
|
4776
|
+
campaign_ids: [...input.campaign_ids]
|
|
4777
|
+
});
|
|
4778
|
+
if (result.isErr()) return err(mapApiError(result.error));
|
|
4779
|
+
return ok({ attached: input.campaign_ids.length });
|
|
4780
|
+
}
|
|
4781
|
+
};
|
|
4481
4782
|
var TIER1 = z.string().min(1).max(200);
|
|
4482
4783
|
var TIER_OPT = z.string().max(200).nullable().optional();
|
|
4483
4784
|
var IabV3Block = z.object({
|
|
@@ -4622,6 +4923,43 @@ var deletePolicySetTool = {
|
|
|
4622
4923
|
return ok({ deleted: true });
|
|
4623
4924
|
}
|
|
4624
4925
|
};
|
|
4926
|
+
var MAX_CAMPAIGNS2 = 500;
|
|
4927
|
+
var DetachPolicySetCampaignsInputShape = {
|
|
4928
|
+
policy_set_id: z.string().uuid().describe("Policy set UUID."),
|
|
4929
|
+
campaign_ids: z.array(z.string().uuid()).min(1).max(MAX_CAMPAIGNS2).optional().describe(
|
|
4930
|
+
`Campaign UUIDs to unbind, at most ${String(MAX_CAMPAIGNS2)} per call. Omit when using \`detach_all\`.`
|
|
4931
|
+
),
|
|
4932
|
+
detach_all: z.boolean().optional().describe(
|
|
4933
|
+
"Unbind EVERY campaign from this policy set. Check the current membership with `list_policy_set_campaigns` first \u2014 the campaigns stop being evaluated against these rules, so alerts they would have raised simply stop."
|
|
4934
|
+
)
|
|
4935
|
+
};
|
|
4936
|
+
var detachPolicySetCampaignsTool = {
|
|
4937
|
+
name: "detach_policy_set_campaigns",
|
|
4938
|
+
description: "Unbind campaigns from a policy set INCREMENTALLY \u2014 the bindings you don't name survive. Pass `campaign_ids` for specific campaigns or `detach_all: true` to clear the whole membership. This and `attach_policy_set_campaigns` are the only way to change membership; `update_policy_set` edits the set's name, description and rules, never its campaigns. Detaching leaves the policy set and its rules intact, and alerts already raised under it are kept.",
|
|
4939
|
+
annotations: {
|
|
4940
|
+
title: "Detach Policy Set Campaigns",
|
|
4941
|
+
readOnlyHint: false,
|
|
4942
|
+
destructiveHint: true,
|
|
4943
|
+
idempotentHint: true,
|
|
4944
|
+
openWorldHint: false
|
|
4945
|
+
},
|
|
4946
|
+
inputSchema: z.object(DetachPolicySetCampaignsInputShape),
|
|
4947
|
+
handler: async (input, ctx) => {
|
|
4948
|
+
const detachAll = input.detach_all === true;
|
|
4949
|
+
if (input.campaign_ids !== void 0 === detachAll) {
|
|
4950
|
+
return err({
|
|
4951
|
+
kind: "invalid-input",
|
|
4952
|
+
message: "Choose either `campaign_ids` or `detach_all: true` \u2014 not both, and not neither. Passing neither would be a silent no-op."
|
|
4953
|
+
});
|
|
4954
|
+
}
|
|
4955
|
+
const result = await ctx.api.detachPolicySetCampaigns(input.policy_set_id, {
|
|
4956
|
+
detach_all: detachAll,
|
|
4957
|
+
...input.campaign_ids !== void 0 ? { campaign_ids: [...input.campaign_ids] } : {}
|
|
4958
|
+
});
|
|
4959
|
+
if (result.isErr()) return err(mapApiError(result.error));
|
|
4960
|
+
return ok({ detached: detachAll ? "all" : input.campaign_ids?.length ?? 0 });
|
|
4961
|
+
}
|
|
4962
|
+
};
|
|
4625
4963
|
var GetPolicySetInputShape = {
|
|
4626
4964
|
policy_set_id: z.string().uuid().describe("Policy set UUID.")
|
|
4627
4965
|
};
|
|
@@ -4642,6 +4980,33 @@ var getPolicySetTool = {
|
|
|
4642
4980
|
return ok(result.value);
|
|
4643
4981
|
}
|
|
4644
4982
|
};
|
|
4983
|
+
var ListPolicySetCampaignsInputShape = {
|
|
4984
|
+
policy_set_id: z.string().uuid().describe("Policy set UUID."),
|
|
4985
|
+
q: z.string().min(1).max(200).optional().describe("Substring search against campaign name (case-insensitive)."),
|
|
4986
|
+
page: z.number().int().min(1).max(500).default(1).describe("1-indexed page number."),
|
|
4987
|
+
limit: z.number().int().min(1).max(200).default(50).describe("Page size (1-200).")
|
|
4988
|
+
};
|
|
4989
|
+
var listPolicySetCampaignsTool = {
|
|
4990
|
+
name: "list_policy_set_campaigns",
|
|
4991
|
+
description: "List the campaigns bound to a policy set, paginated, each with `id`, `name` and `is_archived`. `get_policy_set` returns only the first page of bindings plus a `campaigns_total`, so use this tool when a set has more campaigns than that page holds, or to search them by name. Read the membership here before `detach_policy_set_campaigns` \u2014 and before `delete_policy_set`, which the API refuses while any active campaign is still bound.",
|
|
4992
|
+
annotations: {
|
|
4993
|
+
title: "List Policy Set Campaigns",
|
|
4994
|
+
readOnlyHint: true,
|
|
4995
|
+
destructiveHint: false,
|
|
4996
|
+
idempotentHint: true,
|
|
4997
|
+
openWorldHint: false
|
|
4998
|
+
},
|
|
4999
|
+
inputSchema: z.object(ListPolicySetCampaignsInputShape),
|
|
5000
|
+
handler: async (input, ctx) => {
|
|
5001
|
+
const result = await ctx.api.listPolicySetCampaigns(input.policy_set_id, {
|
|
5002
|
+
page: input.page,
|
|
5003
|
+
limit: input.limit,
|
|
5004
|
+
...input.q !== void 0 ? { q: input.q } : {}
|
|
5005
|
+
});
|
|
5006
|
+
if (result.isErr()) return err(mapApiError(result.error));
|
|
5007
|
+
return ok(result.value);
|
|
5008
|
+
}
|
|
5009
|
+
};
|
|
4645
5010
|
var ListPolicySetsInputShape = {
|
|
4646
5011
|
page: z.number().int().min(1).max(500).default(1).describe("1-indexed page number."),
|
|
4647
5012
|
limit: z.number().int().min(1).max(200).default(50).describe("Page size."),
|
|
@@ -4927,6 +5292,32 @@ var getScanTool = {
|
|
|
4927
5292
|
return ok(result.value);
|
|
4928
5293
|
}
|
|
4929
5294
|
};
|
|
5295
|
+
var GetScanCreativeHtmlInputShape = {
|
|
5296
|
+
scan_id: z.string().uuid().describe("Scan UUID.")
|
|
5297
|
+
};
|
|
5298
|
+
var getScanCreativeHtmlTool = {
|
|
5299
|
+
name: "get_scan_creative_html",
|
|
5300
|
+
description: "Fetch the generated creative markup for an ad-tag scan as text \u2014 the HTML the ad tag produced, with the scripts, iframes and click-through URLs it embedded. Use it to explain WHY a creative was tagged when a screenshot cannot show it (obfuscated redirectors, hidden trackers, injected handlers). Only ad-tag scans have this artifact; a URL or VAST scan returns not-found. The markup is returned inert as text and is never executed. Markup over 256 KiB is refused \u2014 open the report URL from `get_scan` for those.",
|
|
5301
|
+
annotations: {
|
|
5302
|
+
title: "Get Scan Creative HTML",
|
|
5303
|
+
readOnlyHint: true,
|
|
5304
|
+
destructiveHint: false,
|
|
5305
|
+
idempotentHint: true,
|
|
5306
|
+
openWorldHint: false
|
|
5307
|
+
},
|
|
5308
|
+
inputSchema: z.object(GetScanCreativeHtmlInputShape),
|
|
5309
|
+
handler: async (input, ctx) => {
|
|
5310
|
+
const result = await ctx.api.getScanCreativeHtml(input.scan_id);
|
|
5311
|
+
if (result.isErr()) return err(mapApiError(result.error));
|
|
5312
|
+
const { bytes, contentType } = result.value;
|
|
5313
|
+
return ok({
|
|
5314
|
+
scan_id: input.scan_id,
|
|
5315
|
+
content_type: contentType,
|
|
5316
|
+
byte_size: bytes.byteLength,
|
|
5317
|
+
html: decodeUtf8(bytes)
|
|
5318
|
+
});
|
|
5319
|
+
}
|
|
5320
|
+
};
|
|
4930
5321
|
var GetScanCreativeScreenshotInputShape = {
|
|
4931
5322
|
scan_id: z.string().uuid().describe("Scan UUID \u2014 must be an ad-tag scan (is_ad_tag=true)."),
|
|
4932
5323
|
width: z.number().int().min(50).max(2e3).optional().describe("Optional resize width in pixels.")
|
|
@@ -4948,6 +5339,27 @@ var getScanCreativeScreenshotTool = {
|
|
|
4948
5339
|
return ok(imageBlock(result.value.bytes, result.value.contentType));
|
|
4949
5340
|
}
|
|
4950
5341
|
};
|
|
5342
|
+
var GetScanCreativeVideoInputShape = {
|
|
5343
|
+
scan_id: z.string().uuid().describe("Scan UUID.")
|
|
5344
|
+
};
|
|
5345
|
+
var getScanCreativeVideoTool = {
|
|
5346
|
+
name: "get_scan_creative_video",
|
|
5347
|
+
description: "Fetch the MP4 MediaFile a VAST scan downloaded, as one inline resource block. Only VAST scans have it; others return not-found. Prefer `get_scan_creative_screenshot` for a still frame and `get_scan_vast_xml` for the declared metadata \u2014 both are far cheaper. Reach for the video itself only when the moving image is the evidence. Files over 8 MiB are refused rather than inlined; fall back to `get_scan_creative_screenshot` or the report URL from `get_scan` for those.",
|
|
5348
|
+
annotations: {
|
|
5349
|
+
title: "Get Scan Creative Video",
|
|
5350
|
+
readOnlyHint: true,
|
|
5351
|
+
destructiveHint: false,
|
|
5352
|
+
idempotentHint: true,
|
|
5353
|
+
openWorldHint: false
|
|
5354
|
+
},
|
|
5355
|
+
inputSchema: z.object(GetScanCreativeVideoInputShape),
|
|
5356
|
+
handler: async (input, ctx) => {
|
|
5357
|
+
const result = await ctx.api.getScanCreativeVideo(input.scan_id);
|
|
5358
|
+
if (result.isErr()) return err(mapApiError(result.error));
|
|
5359
|
+
const { bytes, contentType } = result.value;
|
|
5360
|
+
return ok(resourceBlock(bytes, contentType, `/api/v1/scans/${input.scan_id}/creative-video`));
|
|
5361
|
+
}
|
|
5362
|
+
};
|
|
4951
5363
|
var GetScanLandingScreenshotInputShape = {
|
|
4952
5364
|
scan_id: z.string().uuid().describe("Scan UUID (ad-tag scan)."),
|
|
4953
5365
|
landing_ord: z.number().int().min(0).max(50).describe("0-indexed landing slot (see `get_scan(...).landings[*].ord`)."),
|
|
@@ -4999,6 +5411,32 @@ var getScanScreenshotTool = {
|
|
|
4999
5411
|
return ok(imageBlock(result.value.bytes, result.value.contentType));
|
|
5000
5412
|
}
|
|
5001
5413
|
};
|
|
5414
|
+
var GetScanVastXmlInputShape = {
|
|
5415
|
+
scan_id: z.string().uuid().describe("Scan UUID.")
|
|
5416
|
+
};
|
|
5417
|
+
var getScanVastXmlTool = {
|
|
5418
|
+
name: "get_scan_vast_xml",
|
|
5419
|
+
description: "Fetch the VAST document for a VAST scan as text, with wrappers already resolved so the whole chain is visible in one payload. Use it to inspect the tracking pixels, verification vendors, companion ads and click-through targets a video ad declared \u2014 the parsed summary on `get_scan` (`video` block) covers duration, media file and ad system, but not the full element tree. Only VAST scans have this artifact; others return not-found. Documents over 256 KiB are refused.",
|
|
5420
|
+
annotations: {
|
|
5421
|
+
title: "Get Scan VAST XML",
|
|
5422
|
+
readOnlyHint: true,
|
|
5423
|
+
destructiveHint: false,
|
|
5424
|
+
idempotentHint: true,
|
|
5425
|
+
openWorldHint: false
|
|
5426
|
+
},
|
|
5427
|
+
inputSchema: z.object(GetScanVastXmlInputShape),
|
|
5428
|
+
handler: async (input, ctx) => {
|
|
5429
|
+
const result = await ctx.api.getScanVastXml(input.scan_id);
|
|
5430
|
+
if (result.isErr()) return err(mapApiError(result.error));
|
|
5431
|
+
const { bytes, contentType } = result.value;
|
|
5432
|
+
return ok({
|
|
5433
|
+
scan_id: input.scan_id,
|
|
5434
|
+
content_type: contentType,
|
|
5435
|
+
byte_size: bytes.byteLength,
|
|
5436
|
+
xml: decodeUtf8(bytes)
|
|
5437
|
+
});
|
|
5438
|
+
}
|
|
5439
|
+
};
|
|
5002
5440
|
var ListScanChildrenInputShape = {
|
|
5003
5441
|
scan_id: z.string().uuid().describe("The publisher scan's UUID (an ad-discovery scan created with ad_discovery=true)."),
|
|
5004
5442
|
page: z.number().int().min(1).max(500).default(1).describe("1-indexed page number."),
|
|
@@ -5026,7 +5464,7 @@ var listScanChildrenTool = {
|
|
|
5026
5464
|
};
|
|
5027
5465
|
var ListScansInputShape = {
|
|
5028
5466
|
status: z.string().optional().describe(
|
|
5029
|
-
"Comma-separated statuses to filter by. Lifecycle values: pending, running, crawled, checking, checking_async, completed, partial, failed, cancelled."
|
|
5467
|
+
"Comma-separated statuses to filter by. Lifecycle values: pending, running, crawled, checking, checking_async, rechecking, completed, partial, failed, cancelled."
|
|
5030
5468
|
),
|
|
5031
5469
|
country_code: z.string().optional().describe("Comma-separated ISO 3166-1 alpha-2 country codes, e.g. US,DE,JP."),
|
|
5032
5470
|
url: z.string().optional().describe("Substring match against the scanned URL."),
|
|
@@ -5039,7 +5477,13 @@ var ListScansInputShape = {
|
|
|
5039
5477
|
run_id: z.string().uuid().optional().describe("Filter to scans of one run."),
|
|
5040
5478
|
campaign_id: z.string().uuid().optional().describe("Filter to scans of one campaign."),
|
|
5041
5479
|
group_id: z.string().uuid().optional().describe("Filter to scans of one campaign group."),
|
|
5480
|
+
parent_scan_id: z.string().uuid().optional().describe(
|
|
5481
|
+
"Filter to the ad-discovery child scans of one parent scan. Setting this lifts the default 7-day window, so old children stay reachable. `list_scan_children` is the paginated equivalent."
|
|
5482
|
+
),
|
|
5042
5483
|
tag: z.string().optional().describe("Comma-separated tag slugs to filter by."),
|
|
5484
|
+
tag_match: z.enum(["any", "all"]).optional().describe(
|
|
5485
|
+
"How multiple `tag` slugs combine: `any` (default) returns scans carrying at least one, `all` requires every one of them."
|
|
5486
|
+
),
|
|
5043
5487
|
ai_category: z.string().optional().describe(
|
|
5044
5488
|
"Filter by the freeform LLM-generated category prefix on the scan (slash-separated tier path, e.g. 'Gambling/Online Casinos')."
|
|
5045
5489
|
),
|
|
@@ -5086,7 +5530,9 @@ var listScansTool = {
|
|
|
5086
5530
|
...input.run_id !== void 0 ? { run_id: input.run_id } : {},
|
|
5087
5531
|
...input.campaign_id !== void 0 ? { campaign_id: input.campaign_id } : {},
|
|
5088
5532
|
...input.group_id !== void 0 ? { group_id: input.group_id } : {},
|
|
5533
|
+
...input.parent_scan_id !== void 0 ? { parent_scan_id: input.parent_scan_id } : {},
|
|
5089
5534
|
...input.tag !== void 0 ? { tag: input.tag } : {},
|
|
5535
|
+
...input.tag_match !== void 0 ? { tag_match: input.tag_match } : {},
|
|
5090
5536
|
...input.ai_category !== void 0 ? { ai_category: input.ai_category } : {},
|
|
5091
5537
|
...input.iab_v3_category !== void 0 ? { iab_v3_category: input.iab_v3_category } : {},
|
|
5092
5538
|
...input.iab_category !== void 0 ? { iab_category: input.iab_category } : {},
|
|
@@ -5153,7 +5599,7 @@ var GetTagDefinitionInputShape = {
|
|
|
5153
5599
|
};
|
|
5154
5600
|
var getTagDefinitionTool = {
|
|
5155
5601
|
name: "get_tag_definition",
|
|
5156
|
-
description: "Get full definition of one tag: display name, description, severity, category, source (system vs custom), public-report visibility, usage counts, plus `linked_rules` \u2014 the custom rules currently producing this tag (id, name, active flag). Fetch a specific rule's full config via `get_custom_rule`.",
|
|
5602
|
+
description: "Get full definition of one tag: display name, description, severity, category, source (system vs custom), public-report visibility, usage counts, plus `linked_rules` \u2014 the custom rules currently producing this tag (id, name, active flag). Fetch a specific rule's full config via `get_custom_rule`. Returns 404 for a retired (archived) tag this organization does not own, including every archived system tag.",
|
|
5157
5603
|
annotations: {
|
|
5158
5604
|
title: "Get Tag Definition",
|
|
5159
5605
|
readOnlyHint: true,
|
|
@@ -5173,7 +5619,7 @@ var ListScanTagsInputShape = {
|
|
|
5173
5619
|
};
|
|
5174
5620
|
var listScanTagsTool = {
|
|
5175
5621
|
name: "list_scan_tags",
|
|
5176
|
-
description: "List every tag (system + custom) attached to one scan by the checker pipeline, with display name, category, and severity.",
|
|
5622
|
+
description: "List every tag (system + custom) attached to one scan by the checker pipeline, with display name, category, and severity. Tags whose definition has since been archived are omitted \u2014 the scan can therefore report fewer tags than when it ran.",
|
|
5177
5623
|
annotations: {
|
|
5178
5624
|
title: "List Scan Tags",
|
|
5179
5625
|
readOnlyHint: true,
|
|
@@ -5191,11 +5637,14 @@ var listScanTagsTool = {
|
|
|
5191
5637
|
var ListTagsInputShape = {
|
|
5192
5638
|
category: z.string().min(1).max(100).optional().describe(
|
|
5193
5639
|
"Filter to tags in one category slug (e.g. 'security', 'malware', 'compliance'). Use the slug exactly as it appears in `category` of an existing tag."
|
|
5640
|
+
),
|
|
5641
|
+
include_archived: z.boolean().optional().describe(
|
|
5642
|
+
"Also return the archived tag definitions this organization owns, which are excluded by default. An archived tag is retired: it is no longer assigned to scans and no longer raises alerts, and archived platform (system) tags are never returned at all \u2014 so this flag cannot resolve a slug seen on an older scan."
|
|
5194
5643
|
)
|
|
5195
5644
|
};
|
|
5196
5645
|
var listTagsTool = {
|
|
5197
5646
|
name: "list_tags",
|
|
5198
|
-
description: "List every tag definition
|
|
5647
|
+
description: "List every tag definition in service for this organization (system tags + organization custom tags) with category, severity, visibility, and usage counters (scans + rules per tag). Optionally filter by category, and pass `include_archived` to also see the retired definitions this organization owns.",
|
|
5199
5648
|
annotations: {
|
|
5200
5649
|
title: "List Tags",
|
|
5201
5650
|
readOnlyHint: true,
|
|
@@ -5205,7 +5654,10 @@ var listTagsTool = {
|
|
|
5205
5654
|
},
|
|
5206
5655
|
inputSchema: z.object(ListTagsInputShape),
|
|
5207
5656
|
handler: async (input, ctx) => {
|
|
5208
|
-
const filters =
|
|
5657
|
+
const filters = {
|
|
5658
|
+
...input.category !== void 0 ? { category: input.category } : {},
|
|
5659
|
+
...input.include_archived !== void 0 ? { include_archived: input.include_archived } : {}
|
|
5660
|
+
};
|
|
5209
5661
|
const result = await ctx.api.listTags(filters);
|
|
5210
5662
|
if (result.isErr()) return err(mapApiError(result.error));
|
|
5211
5663
|
return ok({ items: result.value, total: result.value.length });
|
|
@@ -5539,6 +5991,9 @@ function registerAllTools(register) {
|
|
|
5539
5991
|
register(getScanScreenshotTool);
|
|
5540
5992
|
register(getScanCreativeScreenshotTool);
|
|
5541
5993
|
register(getScanLandingScreenshotTool);
|
|
5994
|
+
register(getScanCreativeHtmlTool);
|
|
5995
|
+
register(getScanCreativeVideoTool);
|
|
5996
|
+
register(getScanVastXmlTool);
|
|
5542
5997
|
register(getCampaignTool);
|
|
5543
5998
|
register(listCampaignsTool);
|
|
5544
5999
|
register(archiveCampaignTool);
|
|
@@ -5586,8 +6041,12 @@ function registerAllTools(register) {
|
|
|
5586
6041
|
register(updatePolicySetTool);
|
|
5587
6042
|
register(deletePolicySetTool);
|
|
5588
6043
|
register(requestPolicySetApprovalTool);
|
|
6044
|
+
register(listPolicySetCampaignsTool);
|
|
6045
|
+
register(attachPolicySetCampaignsTool);
|
|
6046
|
+
register(detachPolicySetCampaignsTool);
|
|
5589
6047
|
register(listAlertsTool);
|
|
5590
6048
|
register(updateAlertStatusTool);
|
|
6049
|
+
register(bulkUpdateAlertStatusTool);
|
|
5591
6050
|
register(getAlertStatsTool);
|
|
5592
6051
|
register(listWebhooksTool);
|
|
5593
6052
|
register(getWebhookTool);
|
|
@@ -5691,5 +6150,5 @@ function formatToolError(error) {
|
|
|
5691
6150
|
}
|
|
5692
6151
|
|
|
5693
6152
|
export { BearerToken, SERVER_INSTRUCTIONS, createHttpApiGateway, createPinoLogger, declareEmptyResourcesAndPrompts, newRequestId, wireToolsIntoMcpServer };
|
|
5694
|
-
//# sourceMappingURL=chunk-
|
|
5695
|
-
//# sourceMappingURL=chunk-
|
|
6153
|
+
//# sourceMappingURL=chunk-IEOPWWSC.js.map
|
|
6154
|
+
//# sourceMappingURL=chunk-IEOPWWSC.js.map
|