@bbradar/mcp 0.1.12 → 0.1.13

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/dist/server.js CHANGED
@@ -70,13 +70,51 @@ const PROGRAM_RESOLUTION_STOP_WORDS = new Set([
70
70
  "have",
71
71
  "any"
72
72
  ]);
73
+ const TARGET_QUERY_STOP_WORDS = new Set([
74
+ "program",
75
+ "programs",
76
+ "target",
77
+ "targets",
78
+ "scope",
79
+ "scopes",
80
+ "asset",
81
+ "assets",
82
+ "endpoint",
83
+ "endpoints",
84
+ "url",
85
+ "urls",
86
+ "http",
87
+ "https",
88
+ "www",
89
+ "the",
90
+ "a",
91
+ "an",
92
+ "of",
93
+ "on",
94
+ "in",
95
+ "with",
96
+ "to",
97
+ "for",
98
+ "find",
99
+ "show",
100
+ "lookup",
101
+ "look",
102
+ "up",
103
+ "com",
104
+ "org",
105
+ "net",
106
+ "io"
107
+ ]);
73
108
  const targetListModeSchema = z.enum(["identifiers", "compact", "full"]);
74
109
  const changeListModeSchema = z.enum(["compact", "full"]);
75
110
  const programListModeSchema = z.enum(["compact", "full"]);
76
111
  const rewardThresholdModeSchema = z.enum(["max_at_least", "min_at_least"]);
77
112
  const vdpModeSchema = z.enum(["include", "exclude", "only_likely", "only_known_no_bounty"]);
78
- const targetMatchModeSchema = z.enum(["contains", "exact", "suffix", "wildcard"]);
113
+ const targetMatchModeSchema = z
114
+ .enum(["smart", "contains", "exact", "suffix", "wildcard"])
115
+ .describe("How to match target_query. smart is forgiving for URLs, wildcards, target names, and partial words.");
79
116
  const programNameActionSchema = z.enum(["new_targets", "targets", "scope_delta", "brief", "target_breakdown"]);
117
+ const guideTopicSchema = z.enum(["start", "program", "discovery", "freshness", "targets", "compare", "export", "filters"]).default("start");
80
118
  const readOnlyAnnotations = {
81
119
  readOnlyHint: true,
82
120
  destructiveHint: false,
@@ -84,17 +122,36 @@ const readOnlyAnnotations = {
84
122
  openWorldHint: true
85
123
  };
86
124
  const TOOL_METRICS = new Map();
87
- const filterValueSchema = z
88
- .string()
89
- .trim()
90
- .min(1)
91
- .max(128)
92
- .refine((value) => !/[,\u0000-\u001F\u007F]/.test(value), {
93
- message: "Filter values cannot contain commas or control characters."
94
- });
95
- const stringListSchema = z
96
- .preprocess(normalizeStringListInput, z.array(filterValueSchema).max(50).default([]))
97
- .describe("Optional filter values. Empty or omitted means all values; do not infer a platform filter.");
125
+ function filterValueSchema() {
126
+ return z
127
+ .string()
128
+ .trim()
129
+ .min(1)
130
+ .max(128)
131
+ .refine((value) => !/[,\u0000-\u001F\u007F]/.test(value), {
132
+ message: "Filter values cannot contain commas or control characters."
133
+ });
134
+ }
135
+ function stringListSchema(description = "Optional filter values. Empty or omitted means all values; do not infer a platform filter.") {
136
+ return z
137
+ .preprocess(normalizeStringListInput, z.array(filterValueSchema()).max(50).default([]))
138
+ .describe(description);
139
+ }
140
+ function platformListSchema() {
141
+ return stringListSchema("Optional platform filters such as HackerOne, Bugcrowd, Intigriti, or HackenProof. Empty or omitted means all platforms.");
142
+ }
143
+ function tagListSchema() {
144
+ return stringListSchema("Optional combined scope/language/target-type tags. Empty or omitted means all tags.");
145
+ }
146
+ function scopeTagListSchema() {
147
+ return stringListSchema("Optional scope tags such as domain, api, mobile, smart-contract, or wildcard. Empty or omitted means all scope tags.");
148
+ }
149
+ function targetTypeListSchema() {
150
+ return stringListSchema("Optional target-type tags such as domain, api, mobile, smart-contract, url, or wildcard. Empty or omitted means all target types.");
151
+ }
152
+ function languageTagListSchema() {
153
+ return stringListSchema("Optional language or technology tags such as javascript, solidity, web3, php, or python. Empty or omitted means all languages.");
154
+ }
98
155
  const acceptedTargetLimitSchema = z.number().int().min(1).max(MAX_TARGETS_PER_PROGRAM);
99
156
  const searchTextSchema = z
100
157
  .string()
@@ -104,6 +161,15 @@ const searchTextSchema = z
104
161
  .refine((value) => !/[\u0000-\u001F\u007F]/.test(value), {
105
162
  message: "Search text cannot contain control characters."
106
163
  });
164
+ const targetQuerySchema = z
165
+ .string()
166
+ .trim()
167
+ .min(2)
168
+ .max(512)
169
+ .refine((value) => !/[\u0000-\u001F\u007F]/.test(value), {
170
+ message: "Target query cannot contain control characters."
171
+ })
172
+ .describe("Target identifier, domain, URL, wildcard, app/package/repo name, or partial target text. URLs and wildcards are normalized automatically.");
107
173
  const isoDateTimeSchema = z.string().datetime({ offset: true });
108
174
  const pageSchema = z.number().int().min(1).default(1);
109
175
  const programsPageSizeSchema = z.number().int().min(1).max(MAX_PROGRAMS_PER_SEARCH).default(20);
@@ -133,69 +199,97 @@ const programIdSchema = z
133
199
  message: "program_id cannot contain control characters."
134
200
  })
135
201
  .describe("platform:handle id.");
136
- const jsonRecordSchema = z.record(z.unknown());
137
- const rateLimitOutputSchema = z.object({
138
- remaining: z.number().int().min(0),
139
- reset_at: z.string()
140
- });
141
- const toolTimingOutputSchema = z.object({
142
- tool_name: z.string(),
143
- duration_ms: z.number().int().min(0)
144
- });
145
- const cacheOutputSchema = z.object({
146
- hit: z.boolean(),
147
- coalesced_live_request: z.boolean().optional(),
148
- expires_at: z.string().optional()
149
- });
150
- const errorOutputSchema = z.object({
151
- message: z.string(),
152
- status: z.number().int().optional(),
153
- detail: z.unknown().optional(),
154
- errors: z.unknown().optional()
155
- });
156
- const programOutputSchema = jsonRecordSchema;
157
- const targetOutputSchema = jsonRecordSchema;
158
- const apiEnvelopeOutputShape = {
159
- request_id: z.string(),
160
- upstream_request_id: z.string().optional(),
161
- fetched_at: z.string().optional(),
162
- cache: cacheOutputSchema.optional(),
163
- source_requests: z.array(jsonRecordSchema).optional(),
164
- mcp_rate_limit: rateLimitOutputSchema.optional(),
165
- mcp_timing: toolTimingOutputSchema.optional(),
166
- warnings: z.array(z.string()).optional(),
167
- program_id_resolution: jsonRecordSchema.optional(),
168
- error: errorOutputSchema.optional()
169
- };
170
- const programListOutputShape = {
171
- ...apiEnvelopeOutputShape,
172
- programs: z.array(programOutputSchema).optional(),
173
- meta: jsonRecordSchema.optional()
174
- };
175
- const findProgramsOutputShape = {
176
- ...programListOutputShape,
177
- source_requests: z.array(jsonRecordSchema).optional(),
178
- filters: jsonRecordSchema.optional(),
179
- warnings: z.array(z.string()).optional(),
180
- errors: z.array(jsonRecordSchema).optional(),
181
- upstream_budget: jsonRecordSchema.optional(),
182
- ranking_scope: jsonRecordSchema.optional()
183
- };
184
- const decisionOutputShape = {
185
- ...findProgramsOutputShape,
186
- partial_success: z.boolean().optional(),
187
- program_ids_requested: z.array(z.string()).optional(),
188
- failed_program_ids: z.array(z.string()).optional(),
189
- compared_programs: z.array(programOutputSchema).optional(),
190
- activity: jsonRecordSchema.optional(),
191
- changes: z.array(jsonRecordSchema).optional()
192
- };
202
+ const programIdentifierSchema = z
203
+ .string()
204
+ .trim()
205
+ .min(2)
206
+ .max(256)
207
+ .refine((value) => !/[\u0000-\u001F\u007F]/.test(value), {
208
+ message: "Program identifiers cannot contain control characters."
209
+ })
210
+ .describe("BBRadar platform:handle program_id or a human program name/handle. Names are resolved automatically.");
211
+ function jsonRecordSchema() {
212
+ return z.record(z.unknown());
213
+ }
214
+ function rateLimitOutputSchema() {
215
+ return z.object({
216
+ remaining: z.number().int().min(0),
217
+ reset_at: z.string()
218
+ });
219
+ }
220
+ function toolTimingOutputSchema() {
221
+ return z.object({
222
+ tool_name: z.string(),
223
+ duration_ms: z.number().int().min(0)
224
+ });
225
+ }
226
+ function cacheOutputSchema() {
227
+ return z.object({
228
+ hit: z.boolean(),
229
+ coalesced_live_request: z.boolean().optional(),
230
+ expires_at: z.string().optional()
231
+ });
232
+ }
233
+ function errorOutputSchema() {
234
+ return z.object({
235
+ message: z.string(),
236
+ status: z.number().int().optional(),
237
+ detail: z.unknown().optional(),
238
+ errors: z.unknown().optional()
239
+ });
240
+ }
241
+ function programOutputSchema() {
242
+ return jsonRecordSchema();
243
+ }
244
+ function apiEnvelopeOutputShape() {
245
+ return {
246
+ request_id: z.string(),
247
+ upstream_request_id: z.string().optional(),
248
+ fetched_at: z.string().optional(),
249
+ cache: cacheOutputSchema().optional(),
250
+ source_requests: z.array(jsonRecordSchema()).optional(),
251
+ mcp_rate_limit: rateLimitOutputSchema().optional(),
252
+ mcp_timing: toolTimingOutputSchema().optional(),
253
+ warnings: z.array(z.string()).optional(),
254
+ program_id_resolution: jsonRecordSchema().optional(),
255
+ error: errorOutputSchema().optional()
256
+ };
257
+ }
258
+ function programListOutputShape() {
259
+ return {
260
+ ...apiEnvelopeOutputShape(),
261
+ programs: z.array(programOutputSchema()).optional(),
262
+ meta: jsonRecordSchema().optional()
263
+ };
264
+ }
265
+ function findProgramsOutputShape() {
266
+ return {
267
+ ...programListOutputShape(),
268
+ source_requests: z.array(jsonRecordSchema()).optional(),
269
+ filters: jsonRecordSchema().optional(),
270
+ warnings: z.array(z.string()).optional(),
271
+ errors: z.array(jsonRecordSchema()).optional(),
272
+ upstream_budget: jsonRecordSchema().optional(),
273
+ ranking_scope: jsonRecordSchema().optional()
274
+ };
275
+ }
276
+ function decisionOutputShape() {
277
+ return {
278
+ ...findProgramsOutputShape(),
279
+ partial_success: z.boolean().optional(),
280
+ program_ids_requested: z.array(z.string()).optional(),
281
+ failed_program_ids: z.array(z.string()).optional(),
282
+ compared_programs: z.array(programOutputSchema()).optional(),
283
+ activity: jsonRecordSchema().optional(),
284
+ changes: z.array(jsonRecordSchema()).optional()
285
+ };
286
+ }
193
287
  const programCandidatePresetInputSchema = {
194
- platforms: stringListSchema,
195
- tags: stringListSchema,
196
- scope_tags: stringListSchema,
197
- target_types: stringListSchema,
198
- language_tags: stringListSchema,
288
+ platforms: platformListSchema(),
289
+ tags: tagListSchema(),
290
+ scope_tags: scopeTagListSchema(),
291
+ target_types: targetTypeListSchema(),
292
+ language_tags: languageTagListSchema(),
199
293
  opportunity_levels: z.array(opportunityLevelSchema).max(4).default(["elite", "hot", "strong", "potential"]),
200
294
  web3: z.boolean().default(false),
201
295
  wildcard: z.boolean().default(false),
@@ -225,7 +319,9 @@ export function createBbradarServer(client, config) {
225
319
  }, {
226
320
  instructions: [
227
321
  "Use BBRadar only for passive bug bounty intelligence; never scan or contact targets.",
228
- "For BBRadar asks, use these tools first and avoid external skills unless methodology is explicitly requested.",
322
+ "When unsure which BBRadar tool to use, call get_bbradar_guide first. It is local-only and cheap.",
323
+ "Simple default path: find_program_candidates for discovery, run_program_name_action for one named program, get_program_eligible_targets_by_name for target lists by program name.",
324
+ "Use advanced tools only after the user's goal is clear or the guide points to them.",
229
325
  "Partial program name + action: run_program_name_action. Partial program name only: resolve_program.",
230
326
  "Default to bounty-eligible in-scope data. Include out-of-scope, ineligible, VDP, disclosure-only, or no-bounty data only when the user explicitly asks.",
231
327
  "Recently updated programs across platforms: find_recently_updated_programs. Do not infer a platform such as Immunefi unless the user explicitly names it.",
@@ -241,14 +337,14 @@ export function createBbradarServer(client, config) {
241
337
  title: "Search BBRadar Programs",
242
338
  description: `List active programs. page_size max ${MAX_PROGRAMS_PER_SEARCH}.`,
243
339
  inputSchema: {
244
- platforms: stringListSchema,
245
- tags: stringListSchema,
340
+ platforms: platformListSchema(),
341
+ tags: tagListSchema(),
246
342
  updated_since: isoDateTimeSchema.optional().describe("ISO datetime."),
247
343
  vdp_mode: vdpModeSchema.default("exclude").describe("VDP/no-bounty filter mode."),
248
344
  page: pageSchema,
249
345
  page_size: programsPageSizeSchema
250
346
  },
251
- outputSchema: programListOutputShape,
347
+ outputSchema: programListOutputShape(),
252
348
  annotations: readOnlyAnnotations
253
349
  }, (args) => runTool("search_programs", config, rateLimiter, async () => {
254
350
  const { vdp_mode, ...filters } = args;
@@ -265,14 +361,14 @@ export function createBbradarServer(client, config) {
265
361
  }));
266
362
  server.registerTool("get_program", {
267
363
  title: "Get BBRadar Program",
268
- description: "Get one program by id.",
364
+ description: "Get one program by platform:handle program_id, handle, or human program name. Names are resolved automatically when unambiguous.",
269
365
  inputSchema: {
270
- program_id: programIdSchema
366
+ program_id: programIdentifierSchema
271
367
  },
272
368
  outputSchema: {
273
- ...apiEnvelopeOutputShape,
274
- program: programOutputSchema.optional(),
275
- meta: jsonRecordSchema.optional()
369
+ ...apiEnvelopeOutputShape(),
370
+ program: programOutputSchema().optional(),
371
+ meta: jsonRecordSchema().optional()
276
372
  },
277
373
  annotations: readOnlyAnnotations
278
374
  }, (args) => runTool("get_program", config, rateLimiter, async () => {
@@ -304,8 +400,8 @@ export function createBbradarServer(client, config) {
304
400
  description: "Resolve partial name/handle text to likely program_ids. Use this when the user gives a program name fragment like Nado.",
305
401
  inputSchema: {
306
402
  query: searchTextSchema,
307
- platforms: stringListSchema,
308
- tags: stringListSchema,
403
+ platforms: platformListSchema(),
404
+ tags: tagListSchema(),
309
405
  opportunity_levels: z.array(opportunityLevelSchema).max(4).default([]),
310
406
  max_pages: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(3),
311
407
  max_results: z.number().int().min(1).max(25).default(10),
@@ -313,28 +409,28 @@ export function createBbradarServer(client, config) {
313
409
  upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET)
314
410
  },
315
411
  outputSchema: {
316
- ...programListOutputShape,
317
- source_requests: z.array(jsonRecordSchema).optional(),
318
- query: jsonRecordSchema.optional(),
319
- matches: z.array(jsonRecordSchema).optional(),
412
+ ...programListOutputShape(),
413
+ source_requests: z.array(jsonRecordSchema()).optional(),
414
+ query: jsonRecordSchema().optional(),
415
+ matches: z.array(jsonRecordSchema()).optional(),
320
416
  warnings: z.array(z.string()).optional(),
321
- upstream_budget: jsonRecordSchema.optional(),
322
- ranking_scope: jsonRecordSchema.optional()
417
+ upstream_budget: jsonRecordSchema().optional(),
418
+ ranking_scope: jsonRecordSchema().optional()
323
419
  },
324
420
  annotations: readOnlyAnnotations
325
421
  }, (args) => runTool("resolve_program", config, rateLimiter, async () => resolveProgram(client, config, args)));
326
422
  server.registerTool("run_program_name_action", {
327
423
  title: "Run Program Name Action",
328
- description: "Resolve a partial program name, then run targets, new_targets, scope_delta, brief, or target_breakdown.",
424
+ description: "Start here for one named program. Resolves a partial program name, then runs targets, new_targets, scope_delta, brief, or target_breakdown.",
329
425
  inputSchema: {
330
426
  query: searchTextSchema,
331
427
  action: programNameActionSchema.default("new_targets"),
332
- platforms: stringListSchema,
333
- tags: stringListSchema,
428
+ platforms: platformListSchema(),
429
+ tags: tagListSchema(),
334
430
  opportunity_levels: z.array(opportunityLevelSchema).max(4).default([]),
335
431
  since: isoDateTimeSchema.optional(),
336
- target_type: filterValueSchema.optional(),
337
- language_tags: stringListSchema,
432
+ target_type: filterValueSchema().optional(),
433
+ language_tags: languageTagListSchema(),
338
434
  include_out_of_scope: z.boolean().default(false),
339
435
  include_ineligible: z.boolean().default(false),
340
436
  page_size: recentChangesPageSizeSchema.default(MAX_RECENT_CHANGES),
@@ -346,14 +442,14 @@ export function createBbradarServer(client, config) {
346
442
  upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET)
347
443
  },
348
444
  outputSchema: {
349
- ...apiEnvelopeOutputShape,
350
- source_requests: z.array(jsonRecordSchema).optional(),
351
- resolved_program: jsonRecordSchema.optional(),
445
+ ...apiEnvelopeOutputShape(),
446
+ source_requests: z.array(jsonRecordSchema()).optional(),
447
+ resolved_program: jsonRecordSchema().optional(),
352
448
  action: z.string().optional(),
353
- result: jsonRecordSchema.optional(),
449
+ result: jsonRecordSchema().optional(),
354
450
  warnings: z.array(z.string()).optional(),
355
- upstream_budget: jsonRecordSchema.optional(),
356
- ranking_scope: jsonRecordSchema.optional()
451
+ upstream_budget: jsonRecordSchema().optional(),
452
+ ranking_scope: jsonRecordSchema().optional()
357
453
  },
358
454
  annotations: readOnlyAnnotations
359
455
  }, (args) => runTool("run_program_name_action", config, rateLimiter, async () => runProgramNameAction(client, config, args)));
@@ -362,8 +458,8 @@ export function createBbradarServer(client, config) {
362
458
  description: "Fast one-call path for user asks like 'find in-scope eligible targets for Aztec': resolve the program name, fetch targets, and return filtered in-scope bounty-eligible targets. Prefer this over separate resolve_program + get_program_targets calls when the user asks for a target list by name.",
363
459
  inputSchema: {
364
460
  query: searchTextSchema,
365
- platforms: stringListSchema,
366
- tags: stringListSchema,
461
+ platforms: platformListSchema(),
462
+ tags: tagListSchema(),
367
463
  opportunity_levels: z.array(opportunityLevelSchema).max(4).default([]),
368
464
  max_resolve_pages: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(3),
369
465
  upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET),
@@ -376,23 +472,23 @@ export function createBbradarServer(client, config) {
376
472
  output_mode: programListModeSchema.default("compact")
377
473
  },
378
474
  outputSchema: {
379
- ...apiEnvelopeOutputShape,
380
- source_requests: z.array(jsonRecordSchema).optional(),
381
- resolved_program: jsonRecordSchema.optional(),
475
+ ...apiEnvelopeOutputShape(),
476
+ source_requests: z.array(jsonRecordSchema()).optional(),
477
+ resolved_program: jsonRecordSchema().optional(),
382
478
  program_id: z.string().optional(),
383
479
  targets: z.array(z.unknown()).optional(),
384
480
  warnings: z.array(z.string()).optional(),
385
- upstream_budget: jsonRecordSchema.optional(),
386
- ranking_scope: jsonRecordSchema.optional(),
387
- meta: jsonRecordSchema.optional()
481
+ upstream_budget: jsonRecordSchema().optional(),
482
+ ranking_scope: jsonRecordSchema().optional(),
483
+ meta: jsonRecordSchema().optional()
388
484
  },
389
485
  annotations: readOnlyAnnotations
390
486
  }, (args) => runTool("get_program_eligible_targets_by_name", config, rateLimiter, async () => getProgramEligibleTargetsByName(client, config, args)));
391
487
  server.registerTool("get_program_targets", {
392
488
  title: "Get Program Targets",
393
- description: `Get active targets for one known program_id. This is a final source for eligible target lists; do not call summary/breakdown unless grouped counts are needed. For a program name query, prefer get_program_eligible_targets_by_name. limit max ${MAX_TARGETS_PER_PROGRAM}.`,
489
+ description: `Get active targets for one program_id, handle, or human program name. This is a final source for eligible target lists; do not call summary/breakdown unless grouped counts are needed. For a program name query, get_program_eligible_targets_by_name is the simplest path. limit max ${MAX_TARGETS_PER_PROGRAM}.`,
394
490
  inputSchema: {
395
- program_id: programIdSchema,
491
+ program_id: programIdentifierSchema,
396
492
  include_out_of_scope: z.boolean().default(false),
397
493
  include_ineligible: z.boolean().default(false),
398
494
  strict_scope_filter: z.boolean().default(true),
@@ -401,10 +497,10 @@ export function createBbradarServer(client, config) {
401
497
  output_mode: targetListModeSchema.default("compact")
402
498
  },
403
499
  outputSchema: {
404
- ...apiEnvelopeOutputShape,
500
+ ...apiEnvelopeOutputShape(),
405
501
  program_id: z.string().optional(),
406
502
  targets: z.array(z.unknown()).optional(),
407
- meta: jsonRecordSchema.optional()
503
+ meta: jsonRecordSchema().optional()
408
504
  },
409
505
  annotations: readOnlyAnnotations
410
506
  }, (args) => runTool("get_program_targets", config, rateLimiter, async () => {
@@ -419,17 +515,17 @@ export function createBbradarServer(client, config) {
419
515
  include_ineligible: z.boolean().default(false),
420
516
  include_out_of_scope: z.boolean().default(false),
421
517
  search: searchTextSchema.optional(),
422
- platforms: stringListSchema,
423
- tags: stringListSchema,
518
+ platforms: platformListSchema(),
519
+ tags: tagListSchema(),
424
520
  page: pageSchema,
425
521
  page_size: recentChangesPageSizeSchema,
426
522
  output_mode: changeListModeSchema.default("compact")
427
523
  },
428
524
  outputSchema: {
429
- ...apiEnvelopeOutputShape,
430
- changes: z.array(jsonRecordSchema).optional(),
431
- meta: jsonRecordSchema.optional(),
432
- facets: jsonRecordSchema.optional()
525
+ ...apiEnvelopeOutputShape(),
526
+ changes: z.array(jsonRecordSchema()).optional(),
527
+ meta: jsonRecordSchema().optional(),
528
+ facets: jsonRecordSchema().optional()
433
529
  },
434
530
  annotations: readOnlyAnnotations
435
531
  }, (args) => runTool("get_recent_changes", config, rateLimiter, async () => {
@@ -453,7 +549,7 @@ export function createBbradarServer(client, config) {
453
549
  title: "Get Latest Added Targets",
454
550
  description: "Latest added targets by type, optionally with that program's target list.",
455
551
  inputSchema: {
456
- target_type: filterValueSchema.default("domain"),
552
+ target_type: filterValueSchema().default("domain"),
457
553
  include_out_of_scope: z.boolean().default(false).describe("Include out-of-scope."),
458
554
  include_ineligible: z.boolean().default(false).describe("Include ineligible."),
459
555
  recent_changes_page_size: recentChangesPageSizeSchema.default(MAX_RECENT_CHANGES),
@@ -464,14 +560,14 @@ export function createBbradarServer(client, config) {
464
560
  full_list_include_ineligible: z.boolean().default(false)
465
561
  },
466
562
  outputSchema: {
467
- ...apiEnvelopeOutputShape,
468
- source_requests: z.array(jsonRecordSchema).optional(),
469
- query: jsonRecordSchema.optional(),
563
+ ...apiEnvelopeOutputShape(),
564
+ source_requests: z.array(jsonRecordSchema()).optional(),
565
+ query: jsonRecordSchema().optional(),
470
566
  latest_added_at: z.string().optional(),
471
- program: programOutputSchema.optional(),
567
+ program: programOutputSchema().optional(),
472
568
  new_targets: z.array(z.unknown()).optional(),
473
569
  full_targets: z.array(z.unknown()).optional(),
474
- meta: jsonRecordSchema.optional()
570
+ meta: jsonRecordSchema().optional()
475
571
  },
476
572
  annotations: readOnlyAnnotations
477
573
  }, (args) => runTool("get_latest_added_targets", config, rateLimiter, async () => getLatestAddedTargets(client, config, args)));
@@ -479,18 +575,18 @@ export function createBbradarServer(client, config) {
479
575
  title: "Get Program Scope Summary",
480
576
  description: "Grouped compact scope for one program.",
481
577
  inputSchema: {
482
- program_id: programIdSchema,
578
+ program_id: programIdentifierSchema,
483
579
  target_list_mode: targetListModeSchema.default("identifiers"),
484
580
  group_limit: z.number().int().min(1).max(MAX_TARGETS_PER_PROGRAM).default(MAX_TARGETS_PER_PROGRAM),
485
581
  include_out_of_scope: z.boolean().default(false),
486
582
  include_ineligible: z.boolean().default(false)
487
583
  },
488
584
  outputSchema: {
489
- ...apiEnvelopeOutputShape,
490
- source_requests: z.array(jsonRecordSchema).optional(),
491
- program: programOutputSchema.optional(),
492
- scope: jsonRecordSchema.optional(),
493
- meta: jsonRecordSchema.optional()
585
+ ...apiEnvelopeOutputShape(),
586
+ source_requests: z.array(jsonRecordSchema()).optional(),
587
+ program: programOutputSchema().optional(),
588
+ scope: jsonRecordSchema().optional(),
589
+ meta: jsonRecordSchema().optional()
494
590
  },
495
591
  annotations: readOnlyAnnotations
496
592
  }, (args) => runTool("get_program_scope_summary", config, rateLimiter, async () => runProgramIdTool(client, config, args.program_id, (programId) => getProgramScopeSummary(client, config, { ...args, program_id: programId }), (indexFallback) => getProgramScopeSummaryFromIndexFallback(client, indexFallback, { ...args, program_id: indexFallback.resolvedProgramId }))));
@@ -498,18 +594,18 @@ export function createBbradarServer(client, config) {
498
594
  title: "Get Program Target Breakdown",
499
595
  description: "Target type, scope tag, language tag, and bucket counts for one program.",
500
596
  inputSchema: {
501
- program_id: programIdSchema,
597
+ program_id: programIdentifierSchema,
502
598
  target_list_mode: targetListModeSchema.default("identifiers"),
503
599
  group_limit: z.number().int().min(1).max(MAX_TARGETS_PER_PROGRAM).default(100),
504
600
  include_out_of_scope: z.boolean().default(false),
505
601
  include_ineligible: z.boolean().default(false)
506
602
  },
507
603
  outputSchema: {
508
- ...apiEnvelopeOutputShape,
509
- source_requests: z.array(jsonRecordSchema).optional(),
510
- program: programOutputSchema.optional(),
511
- breakdown: jsonRecordSchema.optional(),
512
- meta: jsonRecordSchema.optional()
604
+ ...apiEnvelopeOutputShape(),
605
+ source_requests: z.array(jsonRecordSchema()).optional(),
606
+ program: programOutputSchema().optional(),
607
+ breakdown: jsonRecordSchema().optional(),
608
+ meta: jsonRecordSchema().optional()
513
609
  },
514
610
  annotations: readOnlyAnnotations
515
611
  }, (args) => runTool("get_program_target_breakdown", config, rateLimiter, async () => runProgramIdTool(client, config, args.program_id, (programId) => getProgramTargetBreakdown(client, config, { ...args, program_id: programId }), (indexFallback) => getProgramTargetBreakdownFromIndexFallback(client, indexFallback, { ...args, program_id: indexFallback.resolvedProgramId }))));
@@ -517,11 +613,11 @@ export function createBbradarServer(client, config) {
517
613
  title: "Get Program Scope Delta",
518
614
  description: "Compact recent scope changes for one program.",
519
615
  inputSchema: {
520
- program_id: programIdSchema,
616
+ program_id: programIdentifierSchema,
521
617
  since: isoDateTimeSchema.optional(),
522
618
  change_type: z.enum(["added", "updated", "removed"]).optional(),
523
- target_type: filterValueSchema.optional(),
524
- language_tags: stringListSchema,
619
+ target_type: filterValueSchema().optional(),
620
+ language_tags: languageTagListSchema(),
525
621
  include_removed: z.boolean().default(true),
526
622
  include_out_of_scope: z.boolean().default(false),
527
623
  include_ineligible: z.boolean().default(false),
@@ -530,12 +626,12 @@ export function createBbradarServer(client, config) {
530
626
  target_list_mode: targetListModeSchema.default("identifiers")
531
627
  },
532
628
  outputSchema: {
533
- ...apiEnvelopeOutputShape,
534
- source_requests: z.array(jsonRecordSchema).optional(),
535
- program: programOutputSchema.optional(),
536
- delta: jsonRecordSchema.optional(),
537
- changes: z.array(jsonRecordSchema).optional(),
538
- meta: jsonRecordSchema.optional()
629
+ ...apiEnvelopeOutputShape(),
630
+ source_requests: z.array(jsonRecordSchema()).optional(),
631
+ program: programOutputSchema().optional(),
632
+ delta: jsonRecordSchema().optional(),
633
+ changes: z.array(jsonRecordSchema()).optional(),
634
+ meta: jsonRecordSchema().optional()
539
635
  },
540
636
  annotations: readOnlyAnnotations
541
637
  }, (args) => runTool("get_program_scope_delta", config, rateLimiter, async () => runProgramIdTool(client, config, args.program_id, (programId) => getProgramScopeDelta(client, config, { ...args, program_id: programId }), (indexFallback) => getProgramScopeDeltaFromIndexedProgram(client, config, { ...args, program_id: indexFallback.resolvedProgramId }, indexFallback.program))));
@@ -544,24 +640,24 @@ export function createBbradarServer(client, config) {
544
640
  description: "Recent target changes grouped by program.",
545
641
  inputSchema: {
546
642
  change_type: z.enum(["added", "updated", "removed"]).optional(),
547
- target_type: filterValueSchema.optional(),
643
+ target_type: filterValueSchema().optional(),
548
644
  include_removed: z.boolean().default(true),
549
645
  include_out_of_scope: z.boolean().default(false),
550
646
  include_ineligible: z.boolean().default(false),
551
647
  search: searchTextSchema.optional(),
552
- platforms: stringListSchema,
553
- tags: stringListSchema,
554
- language_tags: stringListSchema,
648
+ platforms: platformListSchema(),
649
+ tags: tagListSchema(),
650
+ language_tags: languageTagListSchema(),
555
651
  page_size: recentChangesPageSizeSchema.default(MAX_RECENT_CHANGES),
556
652
  max_programs: z.number().int().min(1).max(50).default(10),
557
653
  sample_size: z.number().int().min(1).max(20).default(5),
558
654
  target_list_mode: targetListModeSchema.default("identifiers")
559
655
  },
560
656
  outputSchema: {
561
- ...apiEnvelopeOutputShape,
562
- source_requests: z.array(jsonRecordSchema).optional(),
563
- activity: z.array(jsonRecordSchema).optional(),
564
- meta: jsonRecordSchema.optional()
657
+ ...apiEnvelopeOutputShape(),
658
+ source_requests: z.array(jsonRecordSchema()).optional(),
659
+ activity: z.array(jsonRecordSchema()).optional(),
660
+ meta: jsonRecordSchema().optional()
565
661
  },
566
662
  annotations: readOnlyAnnotations
567
663
  }, (args) => runTool("get_recent_target_activity", config, rateLimiter, async () => getRecentTargetActivity(client, config, args)));
@@ -569,24 +665,24 @@ export function createBbradarServer(client, config) {
569
665
  title: "Check Program New Targets",
570
666
  description: "Yes/no new in-scope targets for one program.",
571
667
  inputSchema: {
572
- program_id: programIdSchema,
668
+ program_id: programIdentifierSchema,
573
669
  since: isoDateTimeSchema.optional().describe("ISO datetime."),
574
- target_type: filterValueSchema.optional(),
670
+ target_type: filterValueSchema().optional(),
575
671
  include_ineligible: z.boolean().default(false).describe("Include ineligible."),
576
672
  page_size: recentChangesPageSizeSchema.default(MAX_RECENT_CHANGES),
577
673
  max_targets: acceptedTargetLimitSchema.default(25),
578
674
  target_list_mode: targetListModeSchema.default("identifiers")
579
675
  },
580
676
  outputSchema: {
581
- ...apiEnvelopeOutputShape,
582
- source_requests: z.array(jsonRecordSchema).optional(),
677
+ ...apiEnvelopeOutputShape(),
678
+ source_requests: z.array(jsonRecordSchema()).optional(),
583
679
  program_id: z.string().optional(),
584
- program: programOutputSchema.optional(),
680
+ program: programOutputSchema().optional(),
585
681
  has_new_targets: z.boolean().optional(),
586
682
  new_target_count: z.number().int().min(0).optional(),
587
683
  latest_added_at: z.string().optional(),
588
684
  new_targets: z.array(z.unknown()).optional(),
589
- meta: jsonRecordSchema.optional()
685
+ meta: jsonRecordSchema().optional()
590
686
  },
591
687
  annotations: readOnlyAnnotations
592
688
  }, (args) => runTool("check_program_new_targets", config, rateLimiter, async () => runProgramIdTool(client, config, args.program_id, (programId) => checkProgramNewTargets(client, config, { ...args, program_id: programId }), (indexFallback) => checkProgramNewTargetsFromIndexedProgram(client, config, { ...args, program_id: indexFallback.resolvedProgramId }, indexFallback.program))));
@@ -594,20 +690,20 @@ export function createBbradarServer(client, config) {
594
690
  title: "Check Watchlist New Targets",
595
691
  description: "New in-scope targets across many known program_ids.",
596
692
  inputSchema: {
597
- program_ids: z.array(programIdSchema).min(1).max(50),
693
+ program_ids: z.array(programIdentifierSchema).min(1).max(50),
598
694
  since: isoDateTimeSchema.optional(),
599
- target_type: filterValueSchema.optional(),
695
+ target_type: filterValueSchema().optional(),
600
696
  include_ineligible: z.boolean().default(false),
601
697
  page_size: recentChangesPageSizeSchema.default(MAX_RECENT_CHANGES),
602
698
  max_targets_per_program: z.number().int().min(1).max(50).default(10),
603
699
  target_list_mode: targetListModeSchema.default("identifiers")
604
700
  },
605
701
  outputSchema: {
606
- ...apiEnvelopeOutputShape,
607
- source_requests: z.array(jsonRecordSchema).optional(),
608
- programs: z.array(jsonRecordSchema).optional(),
702
+ ...apiEnvelopeOutputShape(),
703
+ source_requests: z.array(jsonRecordSchema()).optional(),
704
+ programs: z.array(jsonRecordSchema()).optional(),
609
705
  any_new_targets: z.boolean().optional(),
610
- meta: jsonRecordSchema.optional()
706
+ meta: jsonRecordSchema().optional()
611
707
  },
612
708
  annotations: readOnlyAnnotations
613
709
  }, (args) => runTool("check_watchlist_new_targets", config, rateLimiter, async () => checkWatchlistNewTargets(client, config, args)));
@@ -616,15 +712,15 @@ export function createBbradarServer(client, config) {
616
712
  description: `List programs in one opportunity tier. page_size max ${MAX_PROGRAMS_PER_SEARCH}.`,
617
713
  inputSchema: {
618
714
  level: opportunityLevelSchema.default("elite"),
619
- platforms: stringListSchema,
620
- tags: stringListSchema,
715
+ platforms: platformListSchema(),
716
+ tags: tagListSchema(),
621
717
  updated_since: isoDateTimeSchema.optional(),
622
718
  vdp_mode: vdpModeSchema.default("exclude").describe("VDP/no-bounty filter mode."),
623
719
  page: pageSchema,
624
720
  page_size: programsPageSizeSchema
625
721
  },
626
722
  outputSchema: {
627
- ...programListOutputShape,
723
+ ...programListOutputShape(),
628
724
  level: opportunityLevelSchema.optional()
629
725
  },
630
726
  annotations: readOnlyAnnotations
@@ -646,11 +742,11 @@ export function createBbradarServer(client, config) {
646
742
  title: "Find BBRadar Programs",
647
743
  description: "Ranked program discovery with filters and compact output.",
648
744
  inputSchema: {
649
- platforms: stringListSchema,
650
- tags: stringListSchema,
651
- scope_tags: stringListSchema.describe("Scope tags."),
652
- target_types: stringListSchema.describe("Target-type tags."),
653
- language_tags: stringListSchema.describe("Language tags."),
745
+ platforms: platformListSchema(),
746
+ tags: tagListSchema(),
747
+ scope_tags: scopeTagListSchema(),
748
+ target_types: targetTypeListSchema(),
749
+ language_tags: languageTagListSchema(),
654
750
  web3: z.boolean().default(false).describe("Require Web3 signals."),
655
751
  opportunity_levels: z.array(opportunityLevelSchema).max(4).default([]),
656
752
  updated_since: isoDateTimeSchema.optional(),
@@ -684,7 +780,7 @@ export function createBbradarServer(client, config) {
684
780
  output_mode: programListModeSchema.default("compact"),
685
781
  upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET)
686
782
  },
687
- outputSchema: findProgramsOutputShape,
783
+ outputSchema: findProgramsOutputShape(),
688
784
  annotations: readOnlyAnnotations
689
785
  }, (args) => runTool("find_programs", config, rateLimiter, async () => findPrograms(client, config, {
690
786
  ...args,
@@ -694,8 +790,8 @@ export function createBbradarServer(client, config) {
694
790
  title: "Find Wildcard Programs",
695
791
  description: "Wildcard program shortlist.",
696
792
  inputSchema: {
697
- platforms: stringListSchema,
698
- tags: stringListSchema,
793
+ platforms: platformListSchema(),
794
+ tags: tagListSchema(),
699
795
  updated_since: isoDateTimeSchema.optional(),
700
796
  max_public_report_count: z.number().int().min(0).default(25),
701
797
  require_known_report_count: z.boolean().default(true),
@@ -710,7 +806,7 @@ export function createBbradarServer(client, config) {
710
806
  target_sample_size: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(5),
711
807
  upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET)
712
808
  },
713
- outputSchema: findProgramsOutputShape,
809
+ outputSchema: findProgramsOutputShape(),
714
810
  annotations: readOnlyAnnotations
715
811
  }, (args) => runTool("find_wildcard_programs", config, rateLimiter, async () => findPrograms(client, config, {
716
812
  platforms: args.platforms,
@@ -754,8 +850,8 @@ export function createBbradarServer(client, config) {
754
850
  title: "Find Web3 Programs",
755
851
  description: "Web3/blockchain/smart-contract program shortlist.",
756
852
  inputSchema: {
757
- platforms: stringListSchema,
758
- tags: stringListSchema,
853
+ platforms: platformListSchema(),
854
+ tags: tagListSchema(),
759
855
  updated_since: isoDateTimeSchema.optional(),
760
856
  max_public_report_count: z.number().int().min(0).optional(),
761
857
  require_known_report_count: z.boolean().default(false),
@@ -771,7 +867,7 @@ export function createBbradarServer(client, config) {
771
867
  target_sample_size: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(5),
772
868
  upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET)
773
869
  },
774
- outputSchema: findProgramsOutputShape,
870
+ outputSchema: findProgramsOutputShape(),
775
871
  annotations: readOnlyAnnotations
776
872
  }, (args) => runTool("find_web3_programs", config, rateLimiter, async () => findPrograms(client, config, {
777
873
  platforms: args.platforms,
@@ -815,10 +911,10 @@ export function createBbradarServer(client, config) {
815
911
  title: "Find Reward Programs",
816
912
  description: "Programs matching reward thresholds.",
817
913
  inputSchema: {
818
- platforms: stringListSchema,
819
- tags: stringListSchema,
820
- target_types: stringListSchema,
821
- language_tags: stringListSchema,
914
+ platforms: platformListSchema(),
915
+ tags: tagListSchema(),
916
+ target_types: targetTypeListSchema(),
917
+ language_tags: languageTagListSchema(),
822
918
  min_reward: z.number().int().min(0).default(5_000),
823
919
  reward_threshold_mode: rewardThresholdModeSchema.default("max_at_least"),
824
920
  web3: z.boolean().default(false),
@@ -837,15 +933,15 @@ export function createBbradarServer(client, config) {
837
933
  output_mode: programListModeSchema.default("compact"),
838
934
  upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET)
839
935
  },
840
- outputSchema: findProgramsOutputShape,
936
+ outputSchema: findProgramsOutputShape(),
841
937
  annotations: readOnlyAnnotations
842
938
  }, (args) => runTool("find_reward_programs", config, rateLimiter, async () => findRewardPrograms(client, config, args)));
843
939
  server.registerTool("find_web3_contests", {
844
940
  title: "Find Web3 Contests",
845
941
  description: "Web3 contest or competitive audit shortlist.",
846
942
  inputSchema: {
847
- platforms: stringListSchema,
848
- tags: stringListSchema,
943
+ platforms: platformListSchema(),
944
+ tags: tagListSchema(),
849
945
  min_reward: z.number().int().min(0).optional(),
850
946
  reward_threshold_mode: rewardThresholdModeSchema.default("max_at_least"),
851
947
  require_contest_signal: z.boolean().default(true),
@@ -860,17 +956,17 @@ export function createBbradarServer(client, config) {
860
956
  output_mode: programListModeSchema.default("compact"),
861
957
  upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(30)
862
958
  },
863
- outputSchema: findProgramsOutputShape,
959
+ outputSchema: findProgramsOutputShape(),
864
960
  annotations: readOnlyAnnotations
865
961
  }, (args) => runTool("find_web3_contests", config, rateLimiter, async () => findWeb3Contests(client, config, args)));
866
962
  server.registerTool("find_language_programs", {
867
963
  title: "Find Language Programs",
868
964
  description: "Language-specific program shortlist.",
869
965
  inputSchema: {
870
- platforms: stringListSchema,
871
- tags: stringListSchema,
872
- language_tags: z.array(filterValueSchema).min(1).max(50),
873
- target_types: stringListSchema,
966
+ platforms: platformListSchema(),
967
+ tags: tagListSchema(),
968
+ language_tags: z.array(filterValueSchema()).min(1).max(50),
969
+ target_types: targetTypeListSchema(),
874
970
  min_reward: z.number().int().min(0).optional(),
875
971
  reward_threshold_mode: rewardThresholdModeSchema.default("max_at_least"),
876
972
  require_bounty: z.boolean().default(false),
@@ -887,18 +983,18 @@ export function createBbradarServer(client, config) {
887
983
  output_mode: programListModeSchema.default("compact"),
888
984
  upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET)
889
985
  },
890
- outputSchema: findProgramsOutputShape,
986
+ outputSchema: findProgramsOutputShape(),
891
987
  annotations: readOnlyAnnotations
892
988
  }, (args) => runTool("find_language_programs", config, rateLimiter, async () => findLanguagePrograms(client, config, args)));
893
989
  server.registerTool("find_target_type_programs", {
894
990
  title: "Find Target Type Programs",
895
991
  description: "Target-type program shortlist.",
896
992
  inputSchema: {
897
- platforms: stringListSchema,
898
- tags: stringListSchema,
899
- target_types: z.array(filterValueSchema).min(1).max(50),
900
- scope_tags: stringListSchema,
901
- language_tags: stringListSchema,
993
+ platforms: platformListSchema(),
994
+ tags: tagListSchema(),
995
+ target_types: z.array(filterValueSchema()).min(1).max(50),
996
+ scope_tags: scopeTagListSchema(),
997
+ language_tags: languageTagListSchema(),
902
998
  min_reward: z.number().int().min(0).optional(),
903
999
  reward_threshold_mode: rewardThresholdModeSchema.default("max_at_least"),
904
1000
  require_bounty: z.boolean().default(false),
@@ -913,17 +1009,17 @@ export function createBbradarServer(client, config) {
913
1009
  output_mode: programListModeSchema.default("compact"),
914
1010
  upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET)
915
1011
  },
916
- outputSchema: findProgramsOutputShape,
1012
+ outputSchema: findProgramsOutputShape(),
917
1013
  annotations: readOnlyAnnotations
918
1014
  }, (args) => runTool("find_target_type_programs", config, rateLimiter, async () => findTargetTypePrograms(client, config, args)));
919
1015
  server.registerTool("find_vdp_programs", {
920
1016
  title: "Find VDP Programs",
921
1017
  description: "VDP/disclosure-only/no-bounty program shortlist.",
922
1018
  inputSchema: {
923
- platforms: stringListSchema,
924
- tags: stringListSchema,
925
- target_types: stringListSchema,
926
- language_tags: stringListSchema,
1019
+ platforms: platformListSchema(),
1020
+ tags: tagListSchema(),
1021
+ target_types: targetTypeListSchema(),
1022
+ language_tags: languageTagListSchema(),
927
1023
  vdp_mode: z.enum(["only_likely", "only_known_no_bounty"]).default("only_likely"),
928
1024
  max_pages: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(3),
929
1025
  max_results: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(10),
@@ -933,17 +1029,17 @@ export function createBbradarServer(client, config) {
933
1029
  output_mode: programListModeSchema.default("compact"),
934
1030
  upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET)
935
1031
  },
936
- outputSchema: findProgramsOutputShape,
1032
+ outputSchema: findProgramsOutputShape(),
937
1033
  annotations: readOnlyAnnotations
938
1034
  }, (args) => runTool("find_vdp_programs", config, rateLimiter, async () => findVdpPrograms(client, config, args)));
939
1035
  server.registerTool("find_paid_programs", {
940
1036
  title: "Find Paid Programs",
941
1037
  description: "Paid bounty program shortlist.",
942
1038
  inputSchema: {
943
- platforms: stringListSchema,
944
- tags: stringListSchema,
945
- target_types: stringListSchema,
946
- language_tags: stringListSchema,
1039
+ platforms: platformListSchema(),
1040
+ tags: tagListSchema(),
1041
+ target_types: targetTypeListSchema(),
1042
+ language_tags: languageTagListSchema(),
947
1043
  min_reward: z.number().int().min(0).optional(),
948
1044
  reward_threshold_mode: rewardThresholdModeSchema.default("max_at_least"),
949
1045
  min_eligible_targets: z.number().int().min(0).default(1),
@@ -956,18 +1052,18 @@ export function createBbradarServer(client, config) {
956
1052
  output_mode: programListModeSchema.default("compact"),
957
1053
  upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET)
958
1054
  },
959
- outputSchema: findProgramsOutputShape,
1055
+ outputSchema: findProgramsOutputShape(),
960
1056
  annotations: readOnlyAnnotations
961
1057
  }, (args) => runTool("find_paid_programs", config, rateLimiter, async () => findPaidPrograms(client, config, args)));
962
1058
  server.registerTool("find_stack_matches", {
963
1059
  title: "Find Stack Matches",
964
1060
  description: "Combined stack/language/type/reward shortlist.",
965
1061
  inputSchema: {
966
- platforms: stringListSchema,
967
- tags: stringListSchema,
968
- language_tags: stringListSchema,
969
- target_types: stringListSchema,
970
- scope_tags: stringListSchema,
1062
+ platforms: platformListSchema(),
1063
+ tags: tagListSchema(),
1064
+ language_tags: languageTagListSchema(),
1065
+ target_types: targetTypeListSchema(),
1066
+ scope_tags: scopeTagListSchema(),
971
1067
  min_reward: z.number().int().min(0).optional(),
972
1068
  reward_threshold_mode: rewardThresholdModeSchema.default("max_at_least"),
973
1069
  web3: z.boolean().default(false),
@@ -986,17 +1082,17 @@ export function createBbradarServer(client, config) {
986
1082
  output_mode: programListModeSchema.default("compact"),
987
1083
  upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(30)
988
1084
  },
989
- outputSchema: findProgramsOutputShape,
1085
+ outputSchema: findProgramsOutputShape(),
990
1086
  annotations: readOnlyAnnotations
991
1087
  }, (args) => runTool("find_stack_matches", config, rateLimiter, async () => findStackMatches(client, config, args)));
992
1088
  server.registerTool("find_low_noise_programs", {
993
1089
  title: "Find Low Noise Programs",
994
1090
  description: "Low-public-report program shortlist.",
995
1091
  inputSchema: {
996
- platforms: stringListSchema,
997
- tags: stringListSchema,
998
- target_types: stringListSchema,
999
- language_tags: stringListSchema,
1092
+ platforms: platformListSchema(),
1093
+ tags: tagListSchema(),
1094
+ target_types: targetTypeListSchema(),
1095
+ language_tags: languageTagListSchema(),
1000
1096
  min_reward: z.number().int().min(0).optional(),
1001
1097
  reward_threshold_mode: rewardThresholdModeSchema.default("max_at_least"),
1002
1098
  max_public_report_count: z.number().int().min(0).default(5),
@@ -1012,43 +1108,43 @@ export function createBbradarServer(client, config) {
1012
1108
  output_mode: programListModeSchema.default("compact"),
1013
1109
  upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET)
1014
1110
  },
1015
- outputSchema: findProgramsOutputShape,
1111
+ outputSchema: findProgramsOutputShape(),
1016
1112
  annotations: readOnlyAnnotations
1017
1113
  }, (args) => runTool("find_low_noise_programs", config, rateLimiter, async () => findLowNoisePrograms(client, config, args)));
1018
1114
  server.registerTool("find_recent_by_type", {
1019
1115
  title: "Find Recent By Type",
1020
1116
  description: "Recent changes by target type grouped by program.",
1021
1117
  inputSchema: {
1022
- target_type: filterValueSchema,
1118
+ target_type: filterValueSchema(),
1023
1119
  change_type: z.enum(["added", "updated", "removed"]).default("added"),
1024
1120
  include_removed: z.boolean().default(false),
1025
1121
  include_out_of_scope: z.boolean().default(false),
1026
1122
  include_ineligible: z.boolean().default(false),
1027
1123
  search: searchTextSchema.optional(),
1028
- platforms: stringListSchema,
1029
- tags: stringListSchema,
1030
- language_tags: stringListSchema,
1124
+ platforms: platformListSchema(),
1125
+ tags: tagListSchema(),
1126
+ language_tags: languageTagListSchema(),
1031
1127
  page_size: recentChangesPageSizeSchema.default(MAX_RECENT_CHANGES),
1032
1128
  max_programs: z.number().int().min(1).max(50).default(10),
1033
1129
  sample_size: z.number().int().min(1).max(20).default(5),
1034
1130
  target_list_mode: targetListModeSchema.default("identifiers")
1035
1131
  },
1036
1132
  outputSchema: {
1037
- ...apiEnvelopeOutputShape,
1038
- source_requests: z.array(jsonRecordSchema).optional(),
1039
- activity: z.array(jsonRecordSchema).optional(),
1040
- meta: jsonRecordSchema.optional()
1133
+ ...apiEnvelopeOutputShape(),
1134
+ source_requests: z.array(jsonRecordSchema()).optional(),
1135
+ activity: z.array(jsonRecordSchema()).optional(),
1136
+ meta: jsonRecordSchema().optional()
1041
1137
  },
1042
1138
  annotations: readOnlyAnnotations
1043
1139
  }, (args) => runTool("find_recent_by_type", config, rateLimiter, async () => findRecentByType(client, config, args)));
1044
1140
  server.registerTool("find_programs_by_target", {
1045
1141
  title: "Find Programs By Target",
1046
- description: "Reverse lookup target/domain ownership.",
1142
+ description: "Reverse lookup target/domain ownership. Accepts domains, URLs, wildcards, target names, and partial target text.",
1047
1143
  inputSchema: {
1048
- target_query: searchTextSchema,
1049
- match_mode: targetMatchModeSchema.default("contains"),
1050
- platforms: stringListSchema,
1051
- tags: stringListSchema,
1144
+ target_query: targetQuerySchema,
1145
+ match_mode: targetMatchModeSchema.default("smart"),
1146
+ platforms: platformListSchema(),
1147
+ tags: tagListSchema(),
1052
1148
  use_api_search: z.boolean().default(true),
1053
1149
  max_pages: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(2),
1054
1150
  max_results: z.number().int().min(1).max(25).default(10),
@@ -1059,33 +1155,33 @@ export function createBbradarServer(client, config) {
1059
1155
  upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(30)
1060
1156
  },
1061
1157
  outputSchema: {
1062
- ...findProgramsOutputShape,
1063
- matches: z.array(jsonRecordSchema).optional()
1158
+ ...findProgramsOutputShape(),
1159
+ matches: z.array(jsonRecordSchema()).optional()
1064
1160
  },
1065
1161
  annotations: readOnlyAnnotations
1066
1162
  }, (args) => runTool("find_programs_by_target", config, rateLimiter, async () => findProgramsByTarget(client, config, args)));
1067
1163
  server.registerTool("find_program_candidates", {
1068
1164
  title: "Find BBRadar Program Candidates",
1069
- description: "Default ranked BBRadar discovery preset.",
1165
+ description: "Start here for broad program discovery. Default ranked hunt-candidate preset with safe filters and compact output.",
1070
1166
  inputSchema: programCandidatePresetInputSchema,
1071
- outputSchema: findProgramsOutputShape,
1167
+ outputSchema: findProgramsOutputShape(),
1072
1168
  annotations: readOnlyAnnotations
1073
1169
  }, (args) => runTool("find_program_candidates", config, rateLimiter, async () => findProgramCandidates(client, config, args)));
1074
1170
  server.registerTool("find_hunt_candidates", {
1075
1171
  title: "Find BBRadar Hunt Candidates",
1076
- description: "Legacy alias for find_program_candidates.",
1172
+ description: "Legacy alias for find_program_candidates. Prefer find_program_candidates for new calls.",
1077
1173
  inputSchema: programCandidatePresetInputSchema,
1078
- outputSchema: findProgramsOutputShape,
1174
+ outputSchema: findProgramsOutputShape(),
1079
1175
  annotations: readOnlyAnnotations
1080
1176
  }, (args) => runTool("find_hunt_candidates", config, rateLimiter, async () => findProgramCandidates(client, config, args)));
1081
1177
  server.registerTool("find_fresh_hunt_candidates", {
1082
1178
  title: "Find Fresh Hunt Candidates",
1083
1179
  description: "Freshness-first hunt shortlist.",
1084
1180
  inputSchema: {
1085
- platforms: stringListSchema,
1086
- tags: stringListSchema,
1087
- target_types: z.array(filterValueSchema).max(50).default(["domain"]),
1088
- language_tags: stringListSchema,
1181
+ platforms: platformListSchema(),
1182
+ tags: tagListSchema(),
1183
+ target_types: z.array(filterValueSchema()).max(50).default(["domain"]),
1184
+ language_tags: languageTagListSchema(),
1089
1185
  opportunity_levels: z.array(opportunityLevelSchema).max(4).default(["elite", "hot", "strong", "potential"]),
1090
1186
  min_added_7d: z.number().int().min(0).default(1),
1091
1187
  max_public_report_count: z.number().int().min(0).default(25),
@@ -1107,7 +1203,7 @@ export function createBbradarServer(client, config) {
1107
1203
  output_mode: programListModeSchema.default("compact"),
1108
1204
  upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(30)
1109
1205
  },
1110
- outputSchema: findProgramsOutputShape,
1206
+ outputSchema: findProgramsOutputShape(),
1111
1207
  annotations: readOnlyAnnotations
1112
1208
  }, (args) => runTool("find_fresh_hunt_candidates", config, rateLimiter, async () => findPrograms(client, config, {
1113
1209
  platforms: args.platforms,
@@ -1151,10 +1247,10 @@ export function createBbradarServer(client, config) {
1151
1247
  title: "Find Recently Updated Programs",
1152
1248
  description: "Recently updated program shortlist across all platforms by default. Platform filters are applied only when explicitly provided.",
1153
1249
  inputSchema: {
1154
- platforms: stringListSchema,
1155
- tags: stringListSchema,
1156
- target_types: stringListSchema,
1157
- language_tags: stringListSchema,
1250
+ platforms: platformListSchema(),
1251
+ tags: tagListSchema(),
1252
+ target_types: targetTypeListSchema(),
1253
+ language_tags: languageTagListSchema(),
1158
1254
  web3: z.boolean().default(false),
1159
1255
  updated_since: isoDateTimeSchema.optional(),
1160
1256
  require_bounty: z.boolean().default(false),
@@ -1169,35 +1265,50 @@ export function createBbradarServer(client, config) {
1169
1265
  output_mode: programListModeSchema.default("compact"),
1170
1266
  upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET)
1171
1267
  },
1172
- outputSchema: findProgramsOutputShape,
1268
+ outputSchema: findProgramsOutputShape(),
1173
1269
  annotations: readOnlyAnnotations
1174
1270
  }, (args) => runTool("find_recently_updated_programs", config, rateLimiter, async () => findRecentlyUpdatedPrograms(client, config, args)));
1175
1271
  server.registerTool("list_filters", {
1176
1272
  title: "List BBRadar Filters",
1177
1273
  description: "Discover observed filter values.",
1178
1274
  inputSchema: {
1179
- platforms: stringListSchema,
1180
- tags: stringListSchema,
1275
+ platforms: platformListSchema(),
1276
+ tags: tagListSchema(),
1181
1277
  max_pages: z.number().int().min(1).max(MAX_FILTER_DISCOVERY_PAGES).default(DEFAULT_FILTER_DISCOVERY_PAGES),
1182
1278
  include_target_facets: z.boolean().default(true),
1183
1279
  target_sample_programs: z.number().int().min(0).max(MAX_FILTER_TARGET_SAMPLE_PROGRAMS).default(DEFAULT_FILTER_TARGET_SAMPLE_PROGRAMS),
1184
1280
  upstream_request_budget: z.number().int().min(1).max(MAX_FIND_UPSTREAM_REQUEST_BUDGET).default(DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET)
1185
1281
  },
1186
1282
  outputSchema: {
1187
- ...apiEnvelopeOutputShape,
1188
- filters: jsonRecordSchema.optional(),
1189
- observed_from: jsonRecordSchema.optional(),
1190
- upstream_budget: jsonRecordSchema.optional()
1283
+ ...apiEnvelopeOutputShape(),
1284
+ filters: jsonRecordSchema().optional(),
1285
+ observed_from: jsonRecordSchema().optional(),
1286
+ upstream_budget: jsonRecordSchema().optional()
1191
1287
  },
1192
1288
  annotations: readOnlyAnnotations
1193
1289
  }, (args) => runTool("list_filters", config, rateLimiter, async () => listFilters(client, args)));
1290
+ server.registerTool("get_bbradar_guide", {
1291
+ title: "Get BBRadar Tool Guide",
1292
+ description: "Local quick-start guide for choosing BBRadar tools. Call this when the user's request is ambiguous or when a smaller model may need a decision tree.",
1293
+ inputSchema: {
1294
+ topic: guideTopicSchema.describe("Guide focus area. Use start for the compact default decision tree.")
1295
+ },
1296
+ outputSchema: {
1297
+ ...apiEnvelopeOutputShape(),
1298
+ guide: jsonRecordSchema().optional()
1299
+ },
1300
+ annotations: readOnlyAnnotations
1301
+ }, (args) => runTool("get_bbradar_guide", config, rateLimiter, async () => ({
1302
+ request_id: randomUUID(),
1303
+ guide: bbradarGuide(args.topic)
1304
+ })));
1194
1305
  server.registerTool("get_mcp_status", {
1195
1306
  title: "Get BBRadar MCP Status",
1196
1307
  description: "Local MCP status and timing metrics.",
1197
1308
  inputSchema: {},
1198
1309
  outputSchema: {
1199
- ...apiEnvelopeOutputShape,
1200
- status: jsonRecordSchema.optional()
1310
+ ...apiEnvelopeOutputShape(),
1311
+ status: jsonRecordSchema().optional()
1201
1312
  },
1202
1313
  annotations: readOnlyAnnotations
1203
1314
  }, () => runTool("get_mcp_status", config, rateLimiter, async () => ({
@@ -1226,41 +1337,41 @@ export function createBbradarServer(client, config) {
1226
1337
  title: "Compare BBRadar Programs",
1227
1338
  description: "Compare programs.",
1228
1339
  inputSchema: {
1229
- program_ids: z.array(programIdSchema).min(2).max(10),
1340
+ program_ids: z.array(programIdentifierSchema).min(2).max(10),
1230
1341
  include_target_samples: z.boolean().default(false),
1231
1342
  target_sample_size: z.number().int().min(1).max(MAX_FIND_TARGET_SAMPLES).default(5),
1232
1343
  only_in_scope_targets: z.boolean().default(true),
1233
1344
  only_bounty_eligible_targets: z.boolean().default(true),
1234
1345
  output_mode: programListModeSchema.default("compact")
1235
1346
  },
1236
- outputSchema: decisionOutputShape,
1347
+ outputSchema: decisionOutputShape(),
1237
1348
  annotations: readOnlyAnnotations
1238
1349
  }, (args) => runTool("compare_programs", config, rateLimiter, async () => comparePrograms(client, config, args)));
1239
1350
  server.registerTool("compare_programs_compact", {
1240
1351
  title: "Compare Programs Compact",
1241
1352
  description: "Compact program comparison.",
1242
1353
  inputSchema: {
1243
- program_ids: z.array(programIdSchema).min(2).max(10),
1354
+ program_ids: z.array(programIdentifierSchema).min(2).max(10),
1244
1355
  include_target_samples: z.boolean().default(false),
1245
1356
  target_sample_size: z.number().int().min(1).max(5).default(3),
1246
1357
  only_in_scope_targets: z.boolean().default(true),
1247
1358
  only_bounty_eligible_targets: z.boolean().default(true)
1248
1359
  },
1249
- outputSchema: decisionOutputShape,
1360
+ outputSchema: decisionOutputShape(),
1250
1361
  annotations: readOnlyAnnotations
1251
1362
  }, (args) => runTool("compare_programs_compact", config, rateLimiter, async () => comparePrograms(client, config, { ...args, output_mode: "compact" })));
1252
1363
  server.registerTool("summarize_program_activity", {
1253
1364
  title: "Summarize Program Activity",
1254
1365
  description: "Summarize one program's activity.",
1255
1366
  inputSchema: {
1256
- program_id: programIdSchema,
1367
+ program_id: programIdentifierSchema,
1257
1368
  recent_changes_limit: z.number().int().min(0).max(MAX_RECENT_CHANGES).default(25)
1258
1369
  },
1259
1370
  outputSchema: {
1260
- ...apiEnvelopeOutputShape,
1261
- program: programOutputSchema.optional(),
1262
- activity: jsonRecordSchema.optional(),
1263
- changes: z.array(jsonRecordSchema).optional()
1371
+ ...apiEnvelopeOutputShape(),
1372
+ program: programOutputSchema().optional(),
1373
+ activity: jsonRecordSchema().optional(),
1374
+ changes: z.array(jsonRecordSchema()).optional()
1264
1375
  },
1265
1376
  annotations: readOnlyAnnotations
1266
1377
  }, (args) => runTool("summarize_program_activity", config, rateLimiter, async () => runProgramIdTool(client, config, args.program_id, (programId) => summarizeProgramActivity(client, config, programId, args.recent_changes_limit))));
@@ -1268,7 +1379,7 @@ export function createBbradarServer(client, config) {
1268
1379
  title: "Get Program Brief",
1269
1380
  description: "Compact worth-hunting brief for one program.",
1270
1381
  inputSchema: {
1271
- program_id: programIdSchema,
1382
+ program_id: programIdentifierSchema,
1272
1383
  target_sample_size: z.number().int().min(1).max(50).default(10),
1273
1384
  target_list_mode: targetListModeSchema.default("compact"),
1274
1385
  include_recent_changes: z.boolean().default(true),
@@ -1277,13 +1388,13 @@ export function createBbradarServer(client, config) {
1277
1388
  include_ineligible: z.boolean().default(false)
1278
1389
  },
1279
1390
  outputSchema: {
1280
- ...apiEnvelopeOutputShape,
1281
- source_requests: z.array(jsonRecordSchema).optional(),
1282
- program: programOutputSchema.optional(),
1283
- brief: jsonRecordSchema.optional(),
1284
- target_samples: jsonRecordSchema.optional(),
1285
- recent_changes: z.array(jsonRecordSchema).optional(),
1286
- meta: jsonRecordSchema.optional()
1391
+ ...apiEnvelopeOutputShape(),
1392
+ source_requests: z.array(jsonRecordSchema()).optional(),
1393
+ program: programOutputSchema().optional(),
1394
+ brief: jsonRecordSchema().optional(),
1395
+ target_samples: jsonRecordSchema().optional(),
1396
+ recent_changes: z.array(jsonRecordSchema()).optional(),
1397
+ meta: jsonRecordSchema().optional()
1287
1398
  },
1288
1399
  annotations: readOnlyAnnotations
1289
1400
  }, (args) => runTool("get_program_brief", config, rateLimiter, async () => runProgramIdTool(client, config, args.program_id, (programId) => getProgramBrief(client, config, { ...args, program_id: programId }))));
@@ -1291,8 +1402,8 @@ export function createBbradarServer(client, config) {
1291
1402
  title: "Find Recently Added Wildcards",
1292
1403
  description: "Recently added wildcard programs.",
1293
1404
  inputSchema: {
1294
- platforms: stringListSchema,
1295
- tags: stringListSchema,
1405
+ platforms: platformListSchema(),
1406
+ tags: tagListSchema(),
1296
1407
  min_added_7d: z.number().int().min(1).default(1),
1297
1408
  max_public_report_count: z.number().int().min(0).optional(),
1298
1409
  require_bounty: z.boolean().default(true),
@@ -1300,7 +1411,7 @@ export function createBbradarServer(client, config) {
1300
1411
  max_results: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(10),
1301
1412
  upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET)
1302
1413
  },
1303
- outputSchema: findProgramsOutputShape,
1414
+ outputSchema: findProgramsOutputShape(),
1304
1415
  annotations: readOnlyAnnotations
1305
1416
  }, (args) => runTool("find_recently_added_wildcards", config, rateLimiter, async () => findPrograms(client, config, {
1306
1417
  platforms: args.platforms,
@@ -1343,10 +1454,10 @@ export function createBbradarServer(client, config) {
1343
1454
  title: "Find Low Competition High Reward",
1344
1455
  description: "Legacy low-report high-reward shortlist.",
1345
1456
  inputSchema: {
1346
- platforms: stringListSchema,
1347
- tags: stringListSchema,
1348
- target_types: stringListSchema,
1349
- language_tags: stringListSchema,
1457
+ platforms: platformListSchema(),
1458
+ tags: tagListSchema(),
1459
+ target_types: targetTypeListSchema(),
1460
+ language_tags: languageTagListSchema(),
1350
1461
  max_public_report_count: z.number().int().min(0).default(10),
1351
1462
  min_bounty_max: z.number().int().min(0).default(5_000),
1352
1463
  min_eligible_targets: z.number().int().min(0).default(1),
@@ -1359,7 +1470,7 @@ export function createBbradarServer(client, config) {
1359
1470
  max_results: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(10),
1360
1471
  upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET)
1361
1472
  },
1362
- outputSchema: findProgramsOutputShape,
1473
+ outputSchema: findProgramsOutputShape(),
1363
1474
  annotations: readOnlyAnnotations
1364
1475
  }, (args) => runTool("find_low_competition_high_reward", config, rateLimiter, async () => findPrograms(client, config, {
1365
1476
  platforms: args.platforms,
@@ -1402,17 +1513,17 @@ export function createBbradarServer(client, config) {
1402
1513
  title: "Get Program Delta",
1403
1514
  description: "Recent target changes for one program.",
1404
1515
  inputSchema: {
1405
- program_id: programIdSchema,
1516
+ program_id: programIdentifierSchema,
1406
1517
  include_removed: z.boolean().default(true),
1407
1518
  include_ineligible: z.boolean().default(false),
1408
1519
  include_out_of_scope: z.boolean().default(false),
1409
1520
  page_size: recentChangesPageSizeSchema.default(50)
1410
1521
  },
1411
1522
  outputSchema: {
1412
- ...apiEnvelopeOutputShape,
1413
- program: programOutputSchema.optional(),
1414
- changes: z.array(jsonRecordSchema).optional(),
1415
- meta: jsonRecordSchema.optional()
1523
+ ...apiEnvelopeOutputShape(),
1524
+ program: programOutputSchema().optional(),
1525
+ changes: z.array(jsonRecordSchema()).optional(),
1526
+ meta: jsonRecordSchema().optional()
1416
1527
  },
1417
1528
  annotations: readOnlyAnnotations
1418
1529
  }, (args) => runTool("get_program_delta", config, rateLimiter, async () => runProgramIdTool(client, config, args.program_id, (programId) => getProgramDelta(client, config, { ...args, program_id: programId }))));
@@ -1420,9 +1531,9 @@ export function createBbradarServer(client, config) {
1420
1531
  title: "Export BBRadar Targets",
1421
1532
  description: `Read-only target export. limit max ${MAX_TARGET_EXPORT}.`,
1422
1533
  inputSchema: {
1423
- program_ids: z.array(programIdSchema).max(50).default([]),
1424
- platforms: stringListSchema,
1425
- tags: stringListSchema,
1534
+ program_ids: z.array(programIdentifierSchema).max(50).default([]),
1535
+ platforms: platformListSchema(),
1536
+ tags: tagListSchema(),
1426
1537
  opportunity_levels: z.array(z.enum(["elite", "hot", "strong", "potential"])).max(4).default([]),
1427
1538
  include_out_of_scope: z.boolean().default(false),
1428
1539
  include_ineligible: z.boolean().default(false),
@@ -1430,19 +1541,26 @@ export function createBbradarServer(client, config) {
1430
1541
  limit: z.number().int().min(1).max(MAX_TARGET_EXPORT).default(MAX_TARGET_EXPORT)
1431
1542
  },
1432
1543
  outputSchema: {
1433
- ...apiEnvelopeOutputShape,
1544
+ ...apiEnvelopeOutputShape(),
1434
1545
  export: z.unknown().optional(),
1435
- meta: jsonRecordSchema.optional()
1546
+ meta: jsonRecordSchema().optional()
1436
1547
  },
1437
1548
  annotations: readOnlyAnnotations
1438
1549
  }, (args) => runTool("export_targets", config, rateLimiter, async () => {
1550
+ const resolvedProgramIds = await resolveProgramIdentifiers(client, config, args.program_ids);
1551
+ const exportArgs = {
1552
+ ...args,
1553
+ program_ids: resolvedProgramIds.entries.map((entry) => entry.resolvedProgramId)
1554
+ };
1439
1555
  try {
1440
- const api = await client.exportTargets(compactRecord(args));
1556
+ const api = await client.exportTargets(compactRecord(exportArgs));
1441
1557
  const exportId = randomUUID();
1442
1558
  const sanitizedExport = sanitizeTargetsForExport(api.data, args.limit);
1443
1559
  const resourceUri = exportResourceUri(exportId);
1444
1560
  rememberExport(exportStore, exportId, sanitizedExport);
1445
1561
  return withApiMetadata(api, {
1562
+ source_requests: resolvedProgramIds.sourceRequests.length > 0 ? resolvedProgramIds.sourceRequests : undefined,
1563
+ warnings: resolvedProgramIds.warnings.length > 0 ? resolvedProgramIds.warnings : undefined,
1446
1564
  export: {
1447
1565
  export_id: exportId,
1448
1566
  resource_uri: resourceUri,
@@ -1456,13 +1574,168 @@ export function createBbradarServer(client, config) {
1456
1574
  if (!(error instanceof BBRadarApiError) || (error.status !== 404 && error.status !== 405)) {
1457
1575
  throw error;
1458
1576
  }
1459
- return exportTargetsFromProgramTargetsFallback(client, config, exportStore, args, error);
1577
+ const fallbackPayload = await exportTargetsFromProgramTargetsFallback(client, config, exportStore, exportArgs, error);
1578
+ const fallbackSourceRequests = readArray(fallbackPayload.source_requests).filter((request) => readObject(request) !== undefined);
1579
+ const fallbackWarnings = readArray(fallbackPayload.warnings).filter((warning) => typeof warning === "string");
1580
+ return stripUndefined({
1581
+ ...fallbackPayload,
1582
+ source_requests: [...resolvedProgramIds.sourceRequests, ...fallbackSourceRequests],
1583
+ warnings: uniqueStrings([...resolvedProgramIds.warnings, ...fallbackWarnings])
1584
+ });
1460
1585
  }
1461
1586
  }));
1462
1587
  registerPrompts(server);
1463
1588
  registerResources(server, client, config, exportStore);
1464
1589
  return server;
1465
1590
  }
1591
+ function bbradarGuide(topic) {
1592
+ const primaryEntryPoints = [
1593
+ {
1594
+ tool: "find_program_candidates",
1595
+ use_when: "The user wants good programs to hunt, broad discovery, or does not name one exact program.",
1596
+ minimum_arguments: {},
1597
+ useful_arguments: ["platforms", "target_types", "language_tags", "min_bounty_max", "max_public_report_count", "fresh_launch_days", "has_api_scope"]
1598
+ },
1599
+ {
1600
+ tool: "run_program_name_action",
1601
+ use_when: "The user names one program and asks for a brief, targets, new targets, scope delta, or target breakdown.",
1602
+ minimum_arguments: { query: "program name or handle", action: "brief" },
1603
+ action_values: ["brief", "targets", "new_targets", "scope_delta", "target_breakdown"]
1604
+ },
1605
+ {
1606
+ tool: "get_program_eligible_targets_by_name",
1607
+ use_when: "The user names one program and asks for in-scope bounty-eligible targets.",
1608
+ minimum_arguments: { query: "program name or handle" },
1609
+ useful_arguments: ["target_list_mode", "limit", "strict_scope_filter"]
1610
+ },
1611
+ {
1612
+ tool: "get_bbradar_guide",
1613
+ use_when: "The tool choice is unclear.",
1614
+ minimum_arguments: { topic: "start" }
1615
+ }
1616
+ ];
1617
+ const decisionTree = [
1618
+ {
1619
+ user_goal: "find programs to hunt",
1620
+ first_tool: "find_program_candidates",
1621
+ then: "Use get_program_brief on the best program_id if the user wants details."
1622
+ },
1623
+ {
1624
+ user_goal: "named program summary",
1625
+ first_tool: "run_program_name_action",
1626
+ arguments: { query: "program name", action: "brief" }
1627
+ },
1628
+ {
1629
+ user_goal: "named program targets",
1630
+ first_tool: "get_program_eligible_targets_by_name",
1631
+ arguments: { query: "program name", target_list_mode: "identifiers" }
1632
+ },
1633
+ {
1634
+ user_goal: "find which program owns a target",
1635
+ first_tool: "find_programs_by_target",
1636
+ arguments: { target_query: "domain, URL, wildcard, package/repo name, or short target phrase", match_mode: "smart" }
1637
+ },
1638
+ {
1639
+ user_goal: "fresh or recently changed scope",
1640
+ first_tool: "find_recently_updated_programs",
1641
+ then: "Use get_latest_added_targets for global added targets, or check_program_new_targets when the user names one program."
1642
+ },
1643
+ {
1644
+ user_goal: "compare a short list",
1645
+ first_tool: "compare_programs_compact",
1646
+ arguments: { program_ids: ["Platform:handle", "Platform:handle"] }
1647
+ },
1648
+ {
1649
+ user_goal: "export targets",
1650
+ first_tool: "export_targets",
1651
+ then: "Use the returned bbradar://exports resource_uri for the full local export payload."
1652
+ },
1653
+ {
1654
+ user_goal: "unknown filter values",
1655
+ first_tool: "list_filters",
1656
+ then: "Reuse observed platforms, scope tags, target types, and language tags in discovery tools."
1657
+ }
1658
+ ];
1659
+ const advancedTools = {
1660
+ discovery: ["find_stack_matches", "find_language_programs", "find_target_type_programs", "find_reward_programs", "find_paid_programs", "find_web3_contests", "find_low_noise_programs"],
1661
+ freshness: ["find_recently_updated_programs", "find_fresh_hunt_candidates", "get_latest_added_targets", "get_recent_target_activity", "get_program_scope_delta", "check_watchlist_new_targets"],
1662
+ details: ["get_program_brief", "get_program_scope_summary", "get_program_target_breakdown", "find_programs_by_target"],
1663
+ exports: ["export_targets"]
1664
+ };
1665
+ const filters = {
1666
+ platforms: "Only set when the user explicitly names a platform. Do not infer Immunefi, HackerOne, etc.",
1667
+ tags: "Broad combined tag filter across scope, language, and target-type signals.",
1668
+ scope_tags: "Scope category signals such as domain, api, mobile, smart-contract, wildcard.",
1669
+ target_types: "Target-kind signals such as domain, api, url, mobile, smart-contract.",
1670
+ language_tags: "Technology/language signals such as javascript, solidity, web3, php, python.",
1671
+ vdp_mode: "Default exclude. Include VDP/no-bounty data only when asked."
1672
+ };
1673
+ const examples = [
1674
+ {
1675
+ user_says: "best low-noise API programs",
1676
+ call: { tool: "find_program_candidates", arguments: { has_api_scope: true, max_public_report_count: 5, target_types: ["api"] } }
1677
+ },
1678
+ {
1679
+ user_says: "targets for Nado",
1680
+ call: { tool: "get_program_eligible_targets_by_name", arguments: { query: "Nado", target_list_mode: "identifiers" } }
1681
+ },
1682
+ {
1683
+ user_says: "what changed on Aztec",
1684
+ call: { tool: "run_program_name_action", arguments: { query: "Aztec", action: "scope_delta" } }
1685
+ },
1686
+ {
1687
+ user_says: "fresh solidity programs",
1688
+ call: { tool: "find_fresh_hunt_candidates", arguments: { language_tags: ["solidity"], target_types: ["smart-contract"] } }
1689
+ },
1690
+ {
1691
+ user_says: "who owns https://api.example.com/v1/users",
1692
+ call: { tool: "find_programs_by_target", arguments: { target_query: "https://api.example.com/v1/users" } }
1693
+ }
1694
+ ];
1695
+ return {
1696
+ topic,
1697
+ rule_zero: "BBRadar is passive intelligence only. Never scan or contact targets.",
1698
+ defaults: {
1699
+ output_mode: "compact",
1700
+ target_list_mode: "identifiers",
1701
+ target_match_mode: "smart",
1702
+ include_out_of_scope: false,
1703
+ include_ineligible: false,
1704
+ require_bounty: true
1705
+ },
1706
+ primary_entry_points: topic === "start" ? primaryEntryPoints : primaryEntryPoints.filter((entry) => entry.tool !== "get_bbradar_guide"),
1707
+ decision_tree: decisionTree.filter((entry) => topic === "start" || guideEntryMatchesTopic(topic, entry)),
1708
+ filters,
1709
+ advanced_tools: advancedTools,
1710
+ examples
1711
+ };
1712
+ }
1713
+ function guideEntryMatchesTopic(topic, entry) {
1714
+ const goal = typeof entry.user_goal === "string" ? entry.user_goal : "";
1715
+ const firstTool = typeof entry.first_tool === "string" ? entry.first_tool : "";
1716
+ if (topic === "program") {
1717
+ return goal.includes("named program") || firstTool === "get_program_brief";
1718
+ }
1719
+ if (topic === "discovery") {
1720
+ return goal.includes("find programs");
1721
+ }
1722
+ if (topic === "freshness") {
1723
+ return goal.includes("fresh") || goal.includes("changed");
1724
+ }
1725
+ if (topic === "targets") {
1726
+ return goal.includes("target") || firstTool === "find_programs_by_target";
1727
+ }
1728
+ if (topic === "compare") {
1729
+ return goal.includes("compare");
1730
+ }
1731
+ if (topic === "export") {
1732
+ return goal.includes("export");
1733
+ }
1734
+ if (topic === "filters") {
1735
+ return goal.includes("filter");
1736
+ }
1737
+ return true;
1738
+ }
1466
1739
  function registerResources(server, client, config, exportStore) {
1467
1740
  server.registerResource("target-export", new ResourceTemplate("bbradar://exports{?export_id}", { list: undefined }), {
1468
1741
  title: "BBRadar Target Export",
@@ -1568,6 +1841,20 @@ async function getProgramWithIdFallback(client, config, requestedProgramId) {
1568
1841
  fallback: cachedFallback
1569
1842
  };
1570
1843
  }
1844
+ if (!isStrictProgramId(requestedProgramId)) {
1845
+ const fallback = await resolveProgramNameIdentifier(client, config, requestedProgramId, "compact");
1846
+ if (fallback) {
1847
+ rememberProgramFallback(config, fallback);
1848
+ return {
1849
+ api: await client.getProgram(fallback.resolvedProgramId),
1850
+ programId: fallback.resolvedProgramId,
1851
+ sourceRequests: fallback.sourceRequests,
1852
+ warnings: fallback.warnings,
1853
+ fallback
1854
+ };
1855
+ }
1856
+ throw unresolvedProgramNameError(requestedProgramId);
1857
+ }
1571
1858
  try {
1572
1859
  return {
1573
1860
  api: await client.getProgram(requestedProgramId),
@@ -1619,6 +1906,39 @@ async function runProgramIdTool(client, config, requestedProgramId, callback, in
1619
1906
  return withProgramIndexFallbackMetadata(payload, indexFallback);
1620
1907
  }
1621
1908
  }
1909
+ if (!isStrictProgramId(requestedProgramId)) {
1910
+ const fallback = await resolveProgramNameIdentifier(client, config, requestedProgramId, indexFallbackCallback ? "full" : "compact");
1911
+ if (fallback) {
1912
+ try {
1913
+ rememberProgramFallback(config, fallback);
1914
+ const payload = await callback(fallback.resolvedProgramId);
1915
+ return withProgramIdFallbackMetadata(payload, fallback);
1916
+ }
1917
+ catch (fallbackError) {
1918
+ if (!isProgramNotFoundError(fallbackError) || !indexFallbackCallback) {
1919
+ throw fallbackError;
1920
+ }
1921
+ const indexFallback = createProgramIndexFallbackFromStaleResolution(fallback, requestedProgramId, fallbackError) ??
1922
+ (await resolveIndexedProgramId(client, config, fallback.resolvedProgramId, fallbackError));
1923
+ if (!indexFallback) {
1924
+ throw fallbackError;
1925
+ }
1926
+ const combinedFallback = indexFallback.requestedProgramId === requestedProgramId
1927
+ ? indexFallback
1928
+ : {
1929
+ ...indexFallback,
1930
+ requestedProgramId,
1931
+ reason: fallback.reason,
1932
+ sourceRequests: [...fallback.sourceRequests, ...indexFallback.sourceRequests],
1933
+ warnings: uniqueStrings([...fallback.warnings, ...indexFallback.warnings])
1934
+ };
1935
+ rememberProgramFallback(config, combinedFallback);
1936
+ const payload = await indexFallbackCallback(combinedFallback);
1937
+ return withProgramIndexFallbackMetadata(payload, combinedFallback);
1938
+ }
1939
+ }
1940
+ throw unresolvedProgramNameError(requestedProgramId);
1941
+ }
1622
1942
  try {
1623
1943
  return await callback(requestedProgramId);
1624
1944
  }
@@ -1776,6 +2096,7 @@ async function resolveIndexedProgramId(client, config, requestedProgramId, stale
1776
2096
  requestedProgramId,
1777
2097
  resolvedProgramId: match.resolvedProgramId,
1778
2098
  query,
2099
+ reason: "stale_program_id",
1779
2100
  staleRequestId: staleApiError?.requestId,
1780
2101
  staleUpstreamRequestId: staleApiError?.upstreamRequestId,
1781
2102
  sourceRequests,
@@ -1784,6 +2105,38 @@ async function resolveIndexedProgramId(client, config, requestedProgramId, stale
1784
2105
  program: match.program
1785
2106
  };
1786
2107
  }
2108
+ async function resolveProgramNameIdentifier(client, config, requestedProgramName, outputMode) {
2109
+ const query = programSearchText(requestedProgramName);
2110
+ if (query.length < 2) {
2111
+ return undefined;
2112
+ }
2113
+ const resolution = await resolveProgram(client, config, {
2114
+ query,
2115
+ platforms: [],
2116
+ tags: [],
2117
+ opportunity_levels: [],
2118
+ max_pages: STALE_PROGRAM_ID_RESOLVE_PAGES,
2119
+ max_results: 5,
2120
+ output_mode: outputMode,
2121
+ upstream_request_budget: STALE_PROGRAM_ID_RESOLVE_BUDGET
2122
+ });
2123
+ const match = selectProgramIdFallbackMatch(requestedProgramName, resolution);
2124
+ if (!match) {
2125
+ return undefined;
2126
+ }
2127
+ const sourceRequests = readArray(resolution.source_requests).filter((request) => readObject(request) !== undefined);
2128
+ const warnings = readArray(resolution.warnings).filter((warning) => typeof warning === "string");
2129
+ const fallbackWarning = `Resolved program name ${requestedProgramName} to program_id ${match.resolvedProgramId}.`;
2130
+ return {
2131
+ requestedProgramId: requestedProgramName,
2132
+ resolvedProgramId: match.resolvedProgramId,
2133
+ query,
2134
+ reason: "program_name",
2135
+ sourceRequests,
2136
+ warnings: uniqueStrings([...warnings, fallbackWarning]),
2137
+ resolution
2138
+ };
2139
+ }
1787
2140
  async function resolveStaleProgramId(client, config, requestedProgramId, staleError) {
1788
2141
  const query = programSearchText(requestedProgramId);
1789
2142
  if (query.length < 2) {
@@ -1811,6 +2164,7 @@ async function resolveStaleProgramId(client, config, requestedProgramId, staleEr
1811
2164
  requestedProgramId,
1812
2165
  resolvedProgramId: match.resolvedProgramId,
1813
2166
  query,
2167
+ reason: "stale_program_id",
1814
2168
  staleRequestId: staleApiError?.requestId,
1815
2169
  staleUpstreamRequestId: staleApiError?.upstreamRequestId,
1816
2170
  sourceRequests,
@@ -1818,6 +2172,32 @@ async function resolveStaleProgramId(client, config, requestedProgramId, staleEr
1818
2172
  resolution
1819
2173
  };
1820
2174
  }
2175
+ async function resolveProgramIdentifiers(client, config, identifiers) {
2176
+ const entries = [];
2177
+ const sourceRequests = [];
2178
+ const warnings = [];
2179
+ for (const identifier of identifiers) {
2180
+ if (isStrictProgramId(identifier)) {
2181
+ entries.push({ requestedProgramId: identifier, resolvedProgramId: identifier, resolved: false });
2182
+ continue;
2183
+ }
2184
+ const fallback = await resolveProgramNameIdentifier(client, config, identifier, "compact");
2185
+ if (!fallback) {
2186
+ entries.push({ requestedProgramId: identifier, resolvedProgramId: identifier, resolved: false });
2187
+ warnings.push(`Could not resolve program name ${identifier}; used it as provided.`);
2188
+ continue;
2189
+ }
2190
+ entries.push({ requestedProgramId: identifier, resolvedProgramId: fallback.resolvedProgramId, resolved: true });
2191
+ sourceRequests.push(...fallback.sourceRequests);
2192
+ warnings.push(...fallback.warnings);
2193
+ rememberProgramFallback(config, fallback);
2194
+ }
2195
+ return {
2196
+ entries,
2197
+ sourceRequests,
2198
+ warnings: uniqueStrings(warnings)
2199
+ };
2200
+ }
1821
2201
  function selectProgramIdFallbackMatch(requestedProgramId, resolution) {
1822
2202
  const requestedHandle = normalizeTag(programHandleText(requestedProgramId));
1823
2203
  const requestedSearchText = normalizeTag(programSearchText(requestedProgramId));
@@ -1829,6 +2209,7 @@ function selectProgramIdFallbackMatch(requestedProgramId, resolution) {
1829
2209
  const program = readObject(entry.program);
1830
2210
  const resolvedProgramId = stringField(entry, "program_id") ?? stringField(program, "id");
1831
2211
  const candidateHandle = normalizeTag(stringField(program, "handle") ?? "");
2212
+ const candidateName = normalizeTag(stringField(program, "name") ?? "");
1832
2213
  const candidateSearchText = resolvedProgramId ? normalizeTag(programSearchText(resolvedProgramId)) : "";
1833
2214
  const candidatePlatform = normalizeTag(stringField(program, "platform") ?? (resolvedProgramId ? programPlatformText(resolvedProgramId) : ""));
1834
2215
  const score = readNumber(entry.score) ?? 0;
@@ -1842,16 +2223,24 @@ function selectProgramIdFallbackMatch(requestedProgramId, resolution) {
1842
2223
  candidateHandle.startsWith(`${requestedHandle}-`) ||
1843
2224
  candidateSearchText.startsWith(`${requestedSearchText}_`) ||
1844
2225
  candidateSearchText.startsWith(`${requestedSearchText}-`));
2226
+ const looseNameMatch = requestedPlatform.length === 0 &&
2227
+ score >= 85 &&
2228
+ (candidateHandle === requestedHandle ||
2229
+ candidateSearchText === requestedSearchText ||
2230
+ candidateName === requestedSearchText ||
2231
+ candidateName.includes(requestedSearchText) ||
2232
+ (candidateHandle.length > 0 && requestedSearchText.includes(candidateHandle)));
1845
2233
  return {
1846
2234
  resolvedProgramId,
1847
2235
  score,
1848
2236
  exactHandleMatch,
1849
- partialHandleMatch
2237
+ partialHandleMatch,
2238
+ looseNameMatch
1850
2239
  };
1851
2240
  })
1852
2241
  .filter((entry) => entry.resolvedProgramId !== undefined &&
1853
2242
  normalizeTag(entry.resolvedProgramId) !== normalizeTag(requestedProgramId) &&
1854
- (entry.exactHandleMatch || entry.partialHandleMatch) &&
2243
+ (entry.exactHandleMatch || entry.partialHandleMatch || entry.looseNameMatch) &&
1855
2244
  entry.score >= 80)
1856
2245
  .sort((left, right) => right.score - left.score || left.resolvedProgramId.localeCompare(right.resolvedProgramId));
1857
2246
  if (candidates.length === 0) {
@@ -1876,6 +2265,7 @@ function selectProgramIndexFallbackMatch(requestedProgramId, resolution) {
1876
2265
  const program = readObject(entry.program);
1877
2266
  const resolvedProgramId = stringField(entry, "program_id") ?? stringField(program, "id");
1878
2267
  const candidateHandle = normalizeTag(stringField(program, "handle") ?? (resolvedProgramId ? programHandleText(resolvedProgramId) : ""));
2268
+ const candidateName = normalizeTag(stringField(program, "name") ?? "");
1879
2269
  const candidatePlatform = normalizeTag(stringField(program, "platform") ?? (resolvedProgramId ? programPlatformText(resolvedProgramId) : ""));
1880
2270
  const score = readNumber(entry.score) ?? 0;
1881
2271
  const idMatch = resolvedProgramId !== undefined && normalizeTag(resolvedProgramId) === requestedId;
@@ -1886,11 +2276,17 @@ function selectProgramIndexFallbackMatch(requestedProgramId, resolution) {
1886
2276
  requestedPlatform.length > 0 &&
1887
2277
  candidatePlatform === requestedPlatform &&
1888
2278
  (candidateHandle.startsWith(`${requestedHandle}_`) || candidateHandle.startsWith(`${requestedHandle}-`));
2279
+ const looseNameMatch = requestedPlatform.length === 0 &&
2280
+ score >= 85 &&
2281
+ (candidateHandle === requestedHandle ||
2282
+ candidateName === requestedHandle ||
2283
+ candidateName.includes(requestedHandle) ||
2284
+ (candidateHandle.length > 0 && requestedHandle.includes(candidateHandle)));
1889
2285
  return {
1890
2286
  resolvedProgramId,
1891
2287
  program,
1892
2288
  score,
1893
- exactMatch: idMatch || platformHandleMatch || partialPlatformHandleMatch
2289
+ exactMatch: idMatch || platformHandleMatch || partialPlatformHandleMatch || looseNameMatch
1894
2290
  };
1895
2291
  })
1896
2292
  .filter((entry) => entry.resolvedProgramId !== undefined && entry.program !== undefined && entry.exactMatch && entry.score >= 80)
@@ -1914,8 +2310,8 @@ function withProgramIdFallbackMetadata(payload, fallback) {
1914
2310
  requested_program_id: fallback.requestedProgramId,
1915
2311
  resolved_program_id: fallback.resolvedProgramId,
1916
2312
  query: fallback.query,
1917
- reason: "stale_program_id",
1918
- stale_status: 404,
2313
+ reason: fallback.reason ?? "stale_program_id",
2314
+ stale_status: fallback.staleRequestId || fallback.staleUpstreamRequestId ? 404 : undefined,
1919
2315
  stale_request_id: fallback.staleRequestId,
1920
2316
  stale_upstream_request_id: fallback.staleUpstreamRequestId,
1921
2317
  resolution_request_id: stringField(fallback.resolution, "request_id")
@@ -2799,7 +3195,7 @@ async function searchProgramsByTargetApi(client, config, input, warnings) {
2799
3195
  try {
2800
3196
  api = await client.searchTargets({
2801
3197
  q: input.target_query,
2802
- match_mode: input.match_mode,
3198
+ match_mode: upstreamTargetMatchMode(input.match_mode),
2803
3199
  platforms: input.platforms,
2804
3200
  tags: input.tags,
2805
3201
  include_out_of_scope: input.include_out_of_scope,
@@ -2845,12 +3241,17 @@ async function searchProgramsByTargetApi(client, config, input, warnings) {
2845
3241
  matching_targets: formatTargetList(entry.targets.slice(0, input.max_targets_per_program), input.target_list_mode),
2846
3242
  matching_targets_has_more: entry.targets.length > input.max_targets_per_program
2847
3243
  }));
3244
+ if (matches.length === 0 && input.match_mode === "smart") {
3245
+ warnings.push("Direct target search returned no smart matches; fell back to sampled program target lists.");
3246
+ return undefined;
3247
+ }
2848
3248
  const sanitizedMeta = readObject(sanitizeJson(data?.meta));
2849
3249
  return stripUndefined({
2850
3250
  request_id: randomUUID(),
2851
3251
  source_requests: [
2852
3252
  {
2853
3253
  source: "target_search",
3254
+ api_match_mode: upstreamTargetMatchMode(input.match_mode),
2854
3255
  request_id: api.requestId,
2855
3256
  upstream_request_id: api.upstreamRequestId,
2856
3257
  ...apiSourceMetadata(api)
@@ -4064,7 +4465,8 @@ async function checkProgramNewTargetsFromIndexedProgram(client, config, input, i
4064
4465
  });
4065
4466
  }
4066
4467
  async function checkWatchlistNewTargets(client, config, input) {
4067
- const watchlist = new Set(input.program_ids);
4468
+ const resolvedProgramIds = await resolveProgramIdentifiers(client, config, input.program_ids);
4469
+ const watchlist = new Set(resolvedProgramIds.entries.map((entry) => entry.resolvedProgramId));
4068
4470
  const api = await client.getRecentChanges({
4069
4471
  change_type: "added",
4070
4472
  include_removed: false,
@@ -4079,8 +4481,8 @@ async function checkWatchlistNewTargets(client, config, input) {
4079
4481
  const sinceTimestamp = input.since ? Date.parse(input.since) : undefined;
4080
4482
  const changes = readArray(data?.results).map((change) => sanitizeChange(change, config.webBaseUrl));
4081
4483
  const grouped = new Map();
4082
- for (const programId of input.program_ids) {
4083
- grouped.set(programId, {
4484
+ for (const entry of resolvedProgramIds.entries) {
4485
+ grouped.set(entry.resolvedProgramId, {
4084
4486
  program: undefined,
4085
4487
  changes: [],
4086
4488
  targets: [],
@@ -4118,12 +4520,13 @@ async function checkWatchlistNewTargets(client, config, input) {
4118
4520
  existing.latestAddedAt = stringField(change, "changed_at");
4119
4521
  }
4120
4522
  }
4121
- const programs = input.program_ids.map((programId) => {
4122
- const entry = grouped.get(programId);
4523
+ const programs = resolvedProgramIds.entries.map((programEntry) => {
4524
+ const entry = grouped.get(programEntry.resolvedProgramId);
4123
4525
  const targets = entry?.targets ?? [];
4124
4526
  const limitedTargets = targets.slice(0, input.max_targets_per_program);
4125
4527
  return stripUndefined({
4126
- program_id: programId,
4528
+ program_id: programEntry.resolvedProgramId,
4529
+ requested_program_id: programEntry.resolved ? programEntry.requestedProgramId : undefined,
4127
4530
  program: entry?.program ? formatProgram(entry.program, "compact") : undefined,
4128
4531
  has_new_targets: targets.length > 0,
4129
4532
  new_target_count: targets.length,
@@ -4135,6 +4538,7 @@ async function checkWatchlistNewTargets(client, config, input) {
4135
4538
  return stripUndefined({
4136
4539
  request_id: randomUUID(),
4137
4540
  source_requests: [
4541
+ ...resolvedProgramIds.sourceRequests,
4138
4542
  {
4139
4543
  source: "recent_changes",
4140
4544
  request_id: api.requestId,
@@ -4144,6 +4548,7 @@ async function checkWatchlistNewTargets(client, config, input) {
4144
4548
  ],
4145
4549
  any_new_targets: programs.some((program) => program.has_new_targets === true),
4146
4550
  programs,
4551
+ warnings: resolvedProgramIds.warnings.length > 0 ? resolvedProgramIds.warnings : undefined,
4147
4552
  meta: stripUndefined({
4148
4553
  recent_changes_scanned: changes.length,
4149
4554
  watchlist_size: input.program_ids.length,
@@ -4274,7 +4679,7 @@ function addProgramResourceLinks(program) {
4274
4679
  };
4275
4680
  }
4276
4681
  function parseProgramIdFromResourceUri(uri) {
4277
- return programIdSchema.parse(uri.searchParams.get("program_id") ?? "");
4682
+ return programIdentifierSchema.parse(uri.searchParams.get("program_id") ?? "");
4278
4683
  }
4279
4684
  function programResourceUri(programId) {
4280
4685
  return `bbradar://program?program_id=${encodeURIComponent(programId)}`;
@@ -4858,7 +5263,7 @@ function programFetchError(programId, error) {
4858
5263
  message: error.message,
4859
5264
  detail: sanitizeJson(error.detail),
4860
5265
  errors: sanitizeJson(error.errors),
4861
- suggested_fix: "Check that the program_id uses BBRadar platform:handle format. Spaces and slashes are supported and are encoded by the MCP server."
5266
+ suggested_fix: "Use a BBRadar platform:handle program_id or call resolve_program with the human program name and retry with one returned program_id."
4862
5267
  });
4863
5268
  }
4864
5269
  return {
@@ -4884,6 +5289,12 @@ function apiOperationError(operation, error) {
4884
5289
  message: error instanceof Error ? error.message : String(error)
4885
5290
  };
4886
5291
  }
5292
+ function unresolvedProgramNameError(programName) {
5293
+ return new Error(`Could not resolve program name "${programName}" to one unambiguous BBRadar program_id. Call resolve_program with the name, then retry with a returned platform:handle program_id.`);
5294
+ }
5295
+ function isStrictProgramId(value) {
5296
+ return programIdSchema.safeParse(value).success;
5297
+ }
4887
5298
  function programSearchText(programId) {
4888
5299
  const colonIndex = programId.indexOf(":");
4889
5300
  const handle = colonIndex >= 0 ? programId.slice(colonIndex + 1) : programId;
@@ -5613,7 +6024,7 @@ Find the best BBRadar program candidates.
5613
6024
  title: "Summarize Program Scope",
5614
6025
  description: "Summarize BBRadar target data for one program.",
5615
6026
  argsSchema: {
5616
- program_id: programIdSchema
6027
+ program_id: programIdentifierSchema
5617
6028
  }
5618
6029
  }, (args) => promptResult(`Use get_program_targets for the program_id in this JSON string: ${promptJson(args.program_id)} with include_out_of_scope=false, include_ineligible=false, and limit=${MAX_TARGETS_PER_PROGRAM}. Do not call get_program first unless program metadata is explicitly needed. Only request out-of-scope or ineligible targets if the user explicitly asks for them.
5619
6030
 
@@ -5628,7 +6039,7 @@ Summarize the program scope:
5628
6039
  title: "Prepare Passive Recon Plan",
5629
6040
  description: "Create a passive-only plan from BBRadar program and target data. Use only when planning is explicitly requested.",
5630
6041
  argsSchema: {
5631
- program_id: programIdSchema.optional(),
6042
+ program_id: programIdentifierSchema.optional(),
5632
6043
  focus: z.string().optional().describe("Optional focus area such as APIs, domains, mobile, or cloud.")
5633
6044
  }
5634
6045
  }, (args) => promptResult(`Prepare a passive-only plan from BBRadar data. Program id JSON string, if present: ${promptJson(args.program_id ?? "")}.
@@ -5871,32 +6282,150 @@ function targetMatchesRequestedType(target, targetType) {
5871
6282
  return actualTargetType === normalizedTargetType || readStringArrayField(target, "scope_tags").map(normalizeTag).includes(normalizedTargetType);
5872
6283
  }
5873
6284
  function targetMatchesQuery(target, query, mode) {
5874
- const identifier = targetIdentifier(target);
5875
- if (!identifier) {
6285
+ const profile = targetSearchProfile(target);
6286
+ if (profile.values.length === 0) {
6287
+ return false;
6288
+ }
6289
+ const queryValues = targetQueryVariants(query);
6290
+ if (queryValues.length === 0) {
5876
6291
  return false;
5877
6292
  }
5878
- const normalizedIdentifier = normalizeTargetIdentifierForMatch(identifier);
5879
- const normalizedQuery = normalizeTargetIdentifierForMatch(query);
5880
6293
  if (mode === "exact") {
5881
- return normalizedIdentifier === normalizedQuery;
6294
+ return profile.values.some((value) => queryValues.some((queryValue) => value === queryValue));
5882
6295
  }
5883
6296
  if (mode === "suffix") {
5884
- return (normalizedIdentifier === normalizedQuery ||
5885
- normalizedIdentifier.endsWith(`.${normalizedQuery}`) ||
5886
- normalizedQuery.endsWith(`.${normalizedIdentifier}`));
6297
+ return profile.values.some((value) => queryValues.some((queryValue) => targetSuffixMatch(value, queryValue)));
5887
6298
  }
5888
6299
  if (mode === "wildcard") {
5889
- return wildcardTargetMatch(normalizedQuery, normalizedIdentifier) || wildcardTargetMatch(normalizedIdentifier, normalizedQuery);
6300
+ return profile.values.some((value) => queryValues.some((queryValue) => targetWildcardMatch(value, queryValue)));
6301
+ }
6302
+ const containsMatch = profile.values.some((value) => queryValues.some((queryValue) => value.includes(queryValue) || queryValue.includes(value)));
6303
+ if (mode === "contains") {
6304
+ return containsMatch;
6305
+ }
6306
+ return (containsMatch ||
6307
+ profile.values.some((value) => queryValues.some((queryValue) => targetSuffixMatch(value, queryValue) || targetWildcardMatch(value, queryValue))) ||
6308
+ targetTokensMatch(profile.tokens, query));
6309
+ }
6310
+ function upstreamTargetMatchMode(mode) {
6311
+ return mode === "smart" ? "contains" : mode;
6312
+ }
6313
+ function targetSearchProfile(target) {
6314
+ const scalarFields = [
6315
+ "identifier",
6316
+ "display_name",
6317
+ "normalized_identifier",
6318
+ "target_type",
6319
+ "name",
6320
+ "url",
6321
+ "uri",
6322
+ "link",
6323
+ "repository",
6324
+ "repo",
6325
+ "package",
6326
+ "package_name",
6327
+ "bundle_id",
6328
+ "app_id",
6329
+ "asset",
6330
+ "asset_identifier",
6331
+ "technology"
6332
+ ];
6333
+ const rawValues = [
6334
+ ...scalarFields.map((field) => stringField(target, field)),
6335
+ ...readStringArrayField(target, "scope_tags"),
6336
+ ...readStringArrayField(target, "language_tags"),
6337
+ ...readStringArrayField(target, "tags")
6338
+ ].filter((value) => value !== undefined);
6339
+ const values = uniqueStrings(rawValues.flatMap(targetStringVariants));
6340
+ return {
6341
+ values,
6342
+ tokens: targetTokenSet([...rawValues, ...values].join(" "))
6343
+ };
6344
+ }
6345
+ function targetQueryVariants(query) {
6346
+ return targetStringVariants(query);
6347
+ }
6348
+ function targetStringVariants(value) {
6349
+ const normalized = normalizeTargetIdentifierForMatch(value);
6350
+ if (!normalized) {
6351
+ return [];
5890
6352
  }
5891
- return normalizedIdentifier.includes(normalizedQuery) || normalizedQuery.includes(normalizedIdentifier);
6353
+ const withoutWildcard = stripLeadingTargetWildcard(normalized);
6354
+ const host = targetHostVariant(normalized);
6355
+ const pathVariants = targetPathVariants(normalized);
6356
+ const tokenPhrase = [...targetTokenSet(normalized)].join(" ");
6357
+ return uniqueStrings([
6358
+ normalized,
6359
+ withoutWildcard,
6360
+ host,
6361
+ host ? stripLeadingTargetWildcard(host) : undefined,
6362
+ ...pathVariants,
6363
+ tokenPhrase.length >= 2 ? tokenPhrase : undefined
6364
+ ].filter((entry) => entry !== undefined && entry.length >= 2));
6365
+ }
6366
+ function targetSuffixMatch(value, query) {
6367
+ const normalizedValue = stripLeadingTargetWildcard(value);
6368
+ const normalizedQuery = stripLeadingTargetWildcard(query);
6369
+ return (normalizedValue === normalizedQuery ||
6370
+ normalizedValue.endsWith(`.${normalizedQuery}`) ||
6371
+ normalizedQuery.endsWith(`.${normalizedValue}`));
6372
+ }
6373
+ function targetWildcardMatch(value, query) {
6374
+ return wildcardTargetMatch(query, value) || wildcardTargetMatch(value, query);
6375
+ }
6376
+ function targetTokensMatch(targetTokens, query) {
6377
+ const queryTokens = [...targetTokenSet(query)];
6378
+ if (queryTokens.length === 0) {
6379
+ return false;
6380
+ }
6381
+ const overlap = queryTokens.filter((token) => targetTokens.has(token)).length;
6382
+ if (queryTokens.length <= 2) {
6383
+ return overlap === queryTokens.length;
6384
+ }
6385
+ return overlap >= Math.ceil(queryTokens.length * 0.67);
6386
+ }
6387
+ function targetTokenSet(value) {
6388
+ const tokens = [...tokenSet(normalizeTargetIdentifierForMatch(value))];
6389
+ const meaningfulTokens = tokens.filter((token) => !TARGET_QUERY_STOP_WORDS.has(token));
6390
+ return new Set(meaningfulTokens.length > 0 ? meaningfulTokens : tokens);
5892
6391
  }
5893
6392
  function normalizeTargetIdentifierForMatch(value) {
5894
- return value
6393
+ const normalized = value
5895
6394
  .trim()
5896
6395
  .toLowerCase()
5897
- .replace(/^https?:\/\//, "")
5898
- .replace(/^www\./, "")
5899
- .replace(/\/+$/, "");
6396
+ .replace(/\\/g, "/")
6397
+ .replace(/^['"`<]+|['"`>]+$/g, "")
6398
+ .replace(/^[a-z][a-z0-9+.-]*:\/\//, "")
6399
+ .replace(/^www\./, "");
6400
+ const queryStart = normalized.search(/[?#]/);
6401
+ return (queryStart >= 0 ? normalized.slice(0, queryStart) : normalized).replace(/\/+$/, "");
6402
+ }
6403
+ function stripLeadingTargetWildcard(value) {
6404
+ return value.replace(/^\*\./, "").replace(/^\*\/?/, "");
6405
+ }
6406
+ function targetHostVariant(value) {
6407
+ const withoutWildcard = stripLeadingTargetWildcard(value);
6408
+ const slashIndex = withoutWildcard.indexOf("/");
6409
+ if (slashIndex <= 0) {
6410
+ return undefined;
6411
+ }
6412
+ const host = withoutWildcard.slice(0, slashIndex).replace(/^www\./, "");
6413
+ return host.includes(".") || host === "localhost" ? host : undefined;
6414
+ }
6415
+ function targetPathVariants(value) {
6416
+ const withoutWildcard = stripLeadingTargetWildcard(value);
6417
+ const segments = withoutWildcard.split("/").filter(Boolean);
6418
+ if (segments.length <= 1) {
6419
+ return [];
6420
+ }
6421
+ const pathSegments = targetHostVariant(value) ? segments.slice(1) : segments;
6422
+ const lastSegment = pathSegments.at(-1);
6423
+ const lastWithoutExtension = lastSegment?.replace(/\.[a-z0-9]{1,12}$/i, "");
6424
+ return uniqueStrings([
6425
+ pathSegments.join("/"),
6426
+ lastSegment,
6427
+ lastWithoutExtension && lastWithoutExtension !== lastSegment ? lastWithoutExtension : undefined
6428
+ ].filter((entry) => entry !== undefined && entry.length >= 2));
5900
6429
  }
5901
6430
  function wildcardTargetMatch(pattern, value) {
5902
6431
  if (!pattern.includes("*")) {