@bbradar/mcp 0.1.19 → 0.1.20

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
@@ -5,7 +5,7 @@ import { BBRadarApiError } from "./bbradarClient.js";
5
5
  import { stableStringify, stripUndefined } from "./json.js";
6
6
  import { FixedWindowRateLimiter } from "./rateLimit.js";
7
7
  import { readArray, readBoolean, readNumber, readObject, readString, sanitizeJson, sanitizeChange, sanitizeProgram, sanitizeTarget, sanitizeTargetsForExport } from "./sanitize.js";
8
- const SERVER_VERSION = "0.1.0";
8
+ const SERVER_VERSION = "0.1.19";
9
9
  const MAX_PROGRAMS_PER_SEARCH = 100;
10
10
  const MAX_TARGET_EXPORT = 500;
11
11
  const MAX_RECENT_CHANGES = 100;
@@ -20,7 +20,7 @@ const MAX_FIND_TARGET_SAMPLE_PROGRAMS = 10;
20
20
  const MAX_FIND_TARGET_SAMPLES = 20;
21
21
  const MAX_FIND_UPSTREAM_REQUEST_BUDGET = 50;
22
22
  const DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET = 20;
23
- const DEFAULT_RESOLVE_UPSTREAM_REQUEST_BUDGET = 4;
23
+ const DEFAULT_RESOLVE_UPSTREAM_REQUEST_BUDGET = 12;
24
24
  const MAX_ACCEPTED_NUMERIC_LIMIT = 1_000;
25
25
  const PROGRAM_COLLECTION_CONCURRENCY = 4;
26
26
  const TARGET_SAMPLE_CONCURRENCY = 3;
@@ -31,11 +31,29 @@ const MAX_FILTER_TARGET_SAMPLE_PROGRAMS = 25;
31
31
  const MAX_LOCAL_EXPORT_RESOURCES = 25;
32
32
  const EXPORT_PREVIEW_LIMIT = 25;
33
33
  const STALE_PROGRAM_ID_RESOLVE_PAGES = 1;
34
- const STALE_PROGRAM_ID_RESOLVE_BUDGET = 8;
34
+ const STALE_PROGRAM_ID_RESOLVE_BUDGET = 12;
35
35
  const PROGRAM_FALLBACK_CACHE_MAX_ENTRIES = 200;
36
36
  const MAX_RESOLVE_SEARCH_QUERIES = 4;
37
+ const FAST_RESOLVE_SEARCH_QUERIES = 2;
37
38
  const MAX_DIRECT_HANDLE_RESOLUTION_REQUESTS = 96;
38
39
  const SDK_VERSION = "1.29.0";
40
+ const CORE_TOOL_NAMES = new Set([
41
+ "search_programs",
42
+ "get_program",
43
+ "resolve_program",
44
+ "run_program_name_action",
45
+ "get_program_eligible_targets_by_name",
46
+ "get_program_targets",
47
+ "get_recent_changes",
48
+ "get_latest_added_targets",
49
+ "get_opportunities",
50
+ "find_programs",
51
+ "find_programs_by_target",
52
+ "list_filters",
53
+ "get_bbradar_guide",
54
+ "get_mcp_status",
55
+ "export_targets"
56
+ ]);
39
57
  const PROGRAM_ID_PLATFORM_GUESSES = [
40
58
  "HackerOne",
41
59
  "BugCrowd",
@@ -346,12 +364,26 @@ const programCandidatePresetInputSchema = {
346
364
  output_mode: programListModeSchema.default("compact"),
347
365
  upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(30)
348
366
  };
367
+ function createProfiledToolRegistrar(server, profile) {
368
+ return (name, toolConfig, callback) => {
369
+ const registeredTool = server.registerTool(name, toolConfig, callback);
370
+ if (profile === "core" && !CORE_TOOL_NAMES.has(name)) {
371
+ registeredTool.disable();
372
+ }
373
+ return registeredTool;
374
+ };
375
+ }
349
376
  export function createBbradarServer(client, config) {
350
- const server = new McpServer({
351
- name: "bbradar.io",
352
- version: SERVER_VERSION
353
- }, {
354
- instructions: [
377
+ const instructions = config.toolProfile === "core"
378
+ ? [
379
+ "Use BBRadar only for passive bug bounty intelligence; never scan or contact targets.",
380
+ "This server uses the compact core tool profile. Start with get_bbradar_guide when unsure.",
381
+ "Use resolve_program or run_program_name_action for a named program.",
382
+ "Use find_programs for general discovery and find_programs_by_target for reverse target lookup.",
383
+ "Default to bounty-eligible in-scope data unless the user explicitly requests broader results.",
384
+ "Prefer compact/identifier outputs and disclose sampled ranking scope when present."
385
+ ]
386
+ : [
355
387
  "Use BBRadar only for passive bug bounty intelligence; never scan or contact targets.",
356
388
  "When unsure which BBRadar tool to use, call get_bbradar_guide first. It is local-only and cheap.",
357
389
  "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.",
@@ -360,14 +392,20 @@ export function createBbradarServer(client, config) {
360
392
  "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.",
361
393
  "Recently updated programs across platforms: find_recently_updated_programs. Do not infer a platform such as Immunefi unless the user explicitly names it.",
362
394
  "Freshness: find_recently_updated_programs, get_latest_added_targets, check_program_new_targets, check_watchlist_new_targets, find_recent_by_type, get_program_scope_delta.",
363
- "Discovery: find_program_candidates, find_stack_matches, find_language_programs, find_target_type_programs, find_reward_programs, find_paid_programs, find_vdp_programs, find_web3_contests, find_low_noise_programs.",
395
+ "Discovery: find_program_candidates, find_wildcard_programs, find_recently_added_wildcards, find_low_competition_high_reward, find_stack_matches, find_language_programs, find_target_type_programs, find_reward_programs, find_paid_programs, find_vdp_programs, find_web3_contests, find_low_noise_programs.",
364
396
  "Details: get_program_brief, get_program_scope_summary, get_program_target_breakdown, find_programs_by_target, compare_programs_compact.",
365
397
  "Prefer compact/identifier outputs and disclose sampled ranking scope when present."
366
- ].join("\n")
398
+ ];
399
+ const server = new McpServer({
400
+ name: "bbradar.io",
401
+ version: SERVER_VERSION
402
+ }, {
403
+ instructions: instructions.join("\n")
367
404
  });
405
+ const registerTool = createProfiledToolRegistrar(server, config.toolProfile);
368
406
  const rateLimiter = new FixedWindowRateLimiter();
369
407
  const exportStore = new Map();
370
- server.registerTool("search_programs", {
408
+ registerTool("search_programs", {
371
409
  title: "Search BBRadar Programs",
372
410
  description: `List active programs. page_size max ${MAX_PROGRAMS_PER_SEARCH}.`,
373
411
  inputSchema: {
@@ -393,7 +431,7 @@ export function createBbradarServer(client, config) {
393
431
  meta: sanitizeJson(data?.meta)
394
432
  });
395
433
  }));
396
- server.registerTool("get_program", {
434
+ registerTool("get_program", {
397
435
  title: "Get BBRadar Program",
398
436
  description: "Get one program by platform:handle program_id, handle, or human program name. Names are resolved automatically when unambiguous.",
399
437
  inputSchema: {
@@ -429,7 +467,7 @@ export function createBbradarServer(client, config) {
429
467
  }
430
468
  }));
431
469
  }));
432
- server.registerTool("resolve_program", {
470
+ registerTool("resolve_program", {
433
471
  title: "Resolve Program",
434
472
  description: "Resolve partial name/handle text to likely program_ids. Use this when the user gives a program name fragment like Nado.",
435
473
  inputSchema: {
@@ -453,7 +491,7 @@ export function createBbradarServer(client, config) {
453
491
  },
454
492
  annotations: readOnlyAnnotations
455
493
  }, (args) => runTool("resolve_program", config, rateLimiter, async () => resolveProgram(client, config, args)));
456
- server.registerTool("run_program_name_action", {
494
+ registerTool("run_program_name_action", {
457
495
  title: "Run Program Name Action",
458
496
  description: "Start here for one named program. Resolves a partial program name, then runs targets, new_targets, scope_delta, brief, or target_breakdown.",
459
497
  inputSchema: {
@@ -487,7 +525,7 @@ export function createBbradarServer(client, config) {
487
525
  },
488
526
  annotations: readOnlyAnnotations
489
527
  }, (args) => runTool("run_program_name_action", config, rateLimiter, async () => runProgramNameAction(client, config, args)));
490
- server.registerTool("get_program_eligible_targets_by_name", {
528
+ registerTool("get_program_eligible_targets_by_name", {
491
529
  title: "Get Eligible Targets By Program Name",
492
530
  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.",
493
531
  inputSchema: {
@@ -518,7 +556,7 @@ export function createBbradarServer(client, config) {
518
556
  },
519
557
  annotations: readOnlyAnnotations
520
558
  }, (args) => runTool("get_program_eligible_targets_by_name", config, rateLimiter, async () => getProgramEligibleTargetsByName(client, config, args)));
521
- server.registerTool("get_program_targets", {
559
+ registerTool("get_program_targets", {
522
560
  title: "Get Program Targets",
523
561
  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}.`,
524
562
  inputSchema: {
@@ -540,7 +578,7 @@ export function createBbradarServer(client, config) {
540
578
  }, (args) => runTool("get_program_targets", config, rateLimiter, async () => {
541
579
  return runProgramIdTool(client, config, args.program_id, async (programId) => getProgramTargetsPayload(client, args, programId), async (indexFallback) => getProgramTargetsExportFallbackPayload(client, args, indexFallback.resolvedProgramId));
542
580
  }));
543
- server.registerTool("get_recent_changes", {
581
+ registerTool("get_recent_changes", {
544
582
  title: "Get Recent Target Changes",
545
583
  description: `List recent target changes. page_size max ${MAX_RECENT_CHANGES}.`,
546
584
  inputSchema: {
@@ -579,15 +617,22 @@ export function createBbradarServer(client, config) {
579
617
  facets: sanitizeJson(data?.facets)
580
618
  });
581
619
  }));
582
- server.registerTool("get_latest_added_targets", {
620
+ registerTool("get_latest_added_targets", {
583
621
  title: "Get Latest Added Targets",
584
- description: "Latest added targets by type, optionally with that program's target list.",
622
+ description: "Latest added targets by type with optional platform, reward, and report-count filters. For fast wildcard discovery use target_type=wildcard and include_full_target_list=false.",
585
623
  inputSchema: {
586
624
  target_type: filterValueSchema().default("domain"),
625
+ platforms: platformListSchema(),
626
+ min_reward: z.number().int().min(0).optional(),
627
+ reward_threshold_mode: rewardThresholdModeSchema.default("max_at_least"),
628
+ max_public_report_count: z.number().int().min(0).optional(),
629
+ require_known_report_count: z.boolean().default(false),
630
+ include_unknown_report_count: z.boolean().default(true),
587
631
  include_out_of_scope: z.boolean().default(false).describe("Include out-of-scope."),
588
632
  include_ineligible: z.boolean().default(false).describe("Include ineligible."),
589
633
  recent_changes_page_size: recentChangesPageSizeSchema.default(MAX_RECENT_CHANGES),
590
- include_full_target_list: z.boolean().default(true),
634
+ max_results: z.number().int().min(1).max(MAX_RECENT_CHANGES).default(10),
635
+ include_full_target_list: z.boolean().default(true).describe("Fetch the newest matching program's complete target list. Set false for the fastest cross-program discovery."),
591
636
  full_target_list_mode: targetListModeSchema.default("identifiers"),
592
637
  full_target_limit: z.number().int().min(1).max(MAX_TARGETS_PER_PROGRAM).default(MAX_TARGETS_PER_PROGRAM),
593
638
  full_list_include_out_of_scope: z.boolean().default(false),
@@ -599,13 +644,14 @@ export function createBbradarServer(client, config) {
599
644
  query: jsonRecordSchema().optional(),
600
645
  latest_added_at: z.string().optional(),
601
646
  program: programOutputSchema().optional(),
647
+ matches: z.array(jsonRecordSchema()).optional(),
602
648
  new_targets: z.array(z.unknown()).optional(),
603
649
  full_targets: z.array(z.unknown()).optional(),
604
650
  meta: jsonRecordSchema().optional()
605
651
  },
606
652
  annotations: readOnlyAnnotations
607
653
  }, (args) => runTool("get_latest_added_targets", config, rateLimiter, async () => getLatestAddedTargets(client, config, args)));
608
- server.registerTool("get_program_scope_summary", {
654
+ registerTool("get_program_scope_summary", {
609
655
  title: "Get Program Scope Summary",
610
656
  description: "Grouped compact scope for one program.",
611
657
  inputSchema: {
@@ -624,7 +670,7 @@ export function createBbradarServer(client, config) {
624
670
  },
625
671
  annotations: readOnlyAnnotations
626
672
  }, (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 }))));
627
- server.registerTool("get_program_target_breakdown", {
673
+ registerTool("get_program_target_breakdown", {
628
674
  title: "Get Program Target Breakdown",
629
675
  description: "Target type, scope tag, language tag, and bucket counts for one program.",
630
676
  inputSchema: {
@@ -643,7 +689,7 @@ export function createBbradarServer(client, config) {
643
689
  },
644
690
  annotations: readOnlyAnnotations
645
691
  }, (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 }))));
646
- server.registerTool("get_program_scope_delta", {
692
+ registerTool("get_program_scope_delta", {
647
693
  title: "Get Program Scope Delta",
648
694
  description: "Compact recent scope changes for one program.",
649
695
  inputSchema: {
@@ -669,7 +715,7 @@ export function createBbradarServer(client, config) {
669
715
  },
670
716
  annotations: readOnlyAnnotations
671
717
  }, (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))));
672
- server.registerTool("get_recent_target_activity", {
718
+ registerTool("get_recent_target_activity", {
673
719
  title: "Get Recent Target Activity",
674
720
  description: "Recent target changes grouped by program.",
675
721
  inputSchema: {
@@ -695,7 +741,7 @@ export function createBbradarServer(client, config) {
695
741
  },
696
742
  annotations: readOnlyAnnotations
697
743
  }, (args) => runTool("get_recent_target_activity", config, rateLimiter, async () => getRecentTargetActivity(client, config, args)));
698
- server.registerTool("check_program_new_targets", {
744
+ registerTool("check_program_new_targets", {
699
745
  title: "Check Program New Targets",
700
746
  description: "Yes/no new in-scope targets for one program.",
701
747
  inputSchema: {
@@ -720,7 +766,7 @@ export function createBbradarServer(client, config) {
720
766
  },
721
767
  annotations: readOnlyAnnotations
722
768
  }, (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))));
723
- server.registerTool("check_watchlist_new_targets", {
769
+ registerTool("check_watchlist_new_targets", {
724
770
  title: "Check Watchlist New Targets",
725
771
  description: "New in-scope targets across many known program_ids.",
726
772
  inputSchema: {
@@ -741,7 +787,7 @@ export function createBbradarServer(client, config) {
741
787
  },
742
788
  annotations: readOnlyAnnotations
743
789
  }, (args) => runTool("check_watchlist_new_targets", config, rateLimiter, async () => checkWatchlistNewTargets(client, config, args)));
744
- server.registerTool("get_opportunities", {
790
+ registerTool("get_opportunities", {
745
791
  title: "Get BBRadar Opportunities",
746
792
  description: `List programs in one opportunity tier. page_size max ${MAX_PROGRAMS_PER_SEARCH}.`,
747
793
  inputSchema: {
@@ -772,7 +818,7 @@ export function createBbradarServer(client, config) {
772
818
  meta: sanitizeJson(data?.meta)
773
819
  });
774
820
  }));
775
- server.registerTool("find_programs", {
821
+ registerTool("find_programs", {
776
822
  title: "Find BBRadar Programs",
777
823
  description: "Ranked program discovery with filters and compact output.",
778
824
  inputSchema: {
@@ -820,24 +866,30 @@ export function createBbradarServer(client, config) {
820
866
  ...args,
821
867
  target_samples_only_wildcards: args.min_wildcards > 0
822
868
  })));
823
- server.registerTool("find_wildcard_programs", {
869
+ registerTool("find_wildcard_programs", {
824
870
  title: "Find Wildcard Programs",
825
- description: "Wildcard program shortlist.",
871
+ description: "Wildcard program shortlist with platform, minimum reward, freshness, and report-count filters.",
826
872
  inputSchema: {
827
873
  platforms: platformListSchema(),
828
874
  tags: tagListSchema(),
829
875
  updated_since: isoDateTimeSchema.optional(),
876
+ min_reward: z.number().int().min(0).optional(),
877
+ reward_threshold_mode: rewardThresholdModeSchema.default("max_at_least"),
830
878
  max_public_report_count: z.number().int().min(0).default(25),
831
879
  require_known_report_count: z.boolean().default(true),
832
880
  include_unknown_report_count: z.boolean().default(false),
833
881
  exclude_ctf: z.boolean().default(true),
834
882
  require_bounty: z.boolean().default(true),
835
883
  min_wildcards: z.number().int().min(1).max(100).default(1),
884
+ min_added_24h: z.number().int().min(0).optional(),
885
+ min_added_7d: z.number().int().min(0).optional(),
886
+ sort_by: programSortSchema.default("best_hunt_value"),
836
887
  max_pages: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(3),
837
888
  max_results: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(10),
838
889
  include_target_samples: z.boolean().default(false),
839
890
  target_sample_programs: z.number().int().min(0).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(3),
840
891
  target_sample_size: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(5),
892
+ output_mode: programListModeSchema.default("compact"),
841
893
  upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET)
842
894
  },
843
895
  outputSchema: findProgramsOutputShape(),
@@ -851,8 +903,8 @@ export function createBbradarServer(client, config) {
851
903
  web3: false,
852
904
  opportunity_levels: [],
853
905
  updated_since: args.updated_since,
854
- min_bounty_min: undefined,
855
- min_bounty_max: undefined,
906
+ min_bounty_min: args.min_reward !== undefined && args.reward_threshold_mode === "min_at_least" ? args.min_reward : undefined,
907
+ min_bounty_max: args.min_reward !== undefined && args.reward_threshold_mode === "max_at_least" ? args.min_reward : undefined,
856
908
  max_bounty_max: undefined,
857
909
  require_bounty: args.require_bounty,
858
910
  max_public_report_count: args.max_public_report_count,
@@ -867,20 +919,21 @@ export function createBbradarServer(client, config) {
867
919
  min_wildcards: args.min_wildcards,
868
920
  min_eligible_targets: undefined,
869
921
  min_total_targets: undefined,
870
- min_added_24h: undefined,
871
- min_added_7d: undefined,
922
+ min_added_24h: args.min_added_24h,
923
+ min_added_7d: args.min_added_7d,
872
924
  max_pages: args.max_pages,
873
925
  max_results: args.max_results,
874
- sort_by: "best_hunt_value",
926
+ sort_by: args.sort_by,
875
927
  include_target_samples: args.include_target_samples,
876
928
  target_sample_programs: args.target_sample_programs,
877
929
  target_sample_size: args.target_sample_size,
878
930
  only_in_scope_targets: true,
879
931
  only_bounty_eligible_targets: true,
880
932
  target_samples_only_wildcards: true,
933
+ output_mode: args.output_mode,
881
934
  upstream_request_budget: args.upstream_request_budget
882
935
  })));
883
- server.registerTool("find_web3_programs", {
936
+ registerTool("find_web3_programs", {
884
937
  title: "Find Web3 Programs",
885
938
  description: "Web3/blockchain/smart-contract program shortlist.",
886
939
  inputSchema: {
@@ -941,7 +994,7 @@ export function createBbradarServer(client, config) {
941
994
  target_samples_only_wildcards: args.min_wildcards > 0,
942
995
  upstream_request_budget: args.upstream_request_budget
943
996
  })));
944
- server.registerTool("find_reward_programs", {
997
+ registerTool("find_reward_programs", {
945
998
  title: "Find Reward Programs",
946
999
  description: "Programs matching reward thresholds.",
947
1000
  inputSchema: {
@@ -970,7 +1023,7 @@ export function createBbradarServer(client, config) {
970
1023
  outputSchema: findProgramsOutputShape(),
971
1024
  annotations: readOnlyAnnotations
972
1025
  }, (args) => runTool("find_reward_programs", config, rateLimiter, async () => findRewardPrograms(client, config, args)));
973
- server.registerTool("find_web3_contests", {
1026
+ registerTool("find_web3_contests", {
974
1027
  title: "Find Web3 Contests",
975
1028
  description: "Web3 contest or competitive audit shortlist.",
976
1029
  inputSchema: {
@@ -993,7 +1046,7 @@ export function createBbradarServer(client, config) {
993
1046
  outputSchema: findProgramsOutputShape(),
994
1047
  annotations: readOnlyAnnotations
995
1048
  }, (args) => runTool("find_web3_contests", config, rateLimiter, async () => findWeb3Contests(client, config, args)));
996
- server.registerTool("find_language_programs", {
1049
+ registerTool("find_language_programs", {
997
1050
  title: "Find Language Programs",
998
1051
  description: "Language-specific program shortlist.",
999
1052
  inputSchema: {
@@ -1020,7 +1073,7 @@ export function createBbradarServer(client, config) {
1020
1073
  outputSchema: findProgramsOutputShape(),
1021
1074
  annotations: readOnlyAnnotations
1022
1075
  }, (args) => runTool("find_language_programs", config, rateLimiter, async () => findLanguagePrograms(client, config, args)));
1023
- server.registerTool("find_target_type_programs", {
1076
+ registerTool("find_target_type_programs", {
1024
1077
  title: "Find Target Type Programs",
1025
1078
  description: "Target-type program shortlist.",
1026
1079
  inputSchema: {
@@ -1046,7 +1099,7 @@ export function createBbradarServer(client, config) {
1046
1099
  outputSchema: findProgramsOutputShape(),
1047
1100
  annotations: readOnlyAnnotations
1048
1101
  }, (args) => runTool("find_target_type_programs", config, rateLimiter, async () => findTargetTypePrograms(client, config, args)));
1049
- server.registerTool("find_vdp_programs", {
1102
+ registerTool("find_vdp_programs", {
1050
1103
  title: "Find VDP Programs",
1051
1104
  description: "VDP/disclosure-only/no-bounty program shortlist.",
1052
1105
  inputSchema: {
@@ -1066,7 +1119,7 @@ export function createBbradarServer(client, config) {
1066
1119
  outputSchema: findProgramsOutputShape(),
1067
1120
  annotations: readOnlyAnnotations
1068
1121
  }, (args) => runTool("find_vdp_programs", config, rateLimiter, async () => findVdpPrograms(client, config, args)));
1069
- server.registerTool("find_paid_programs", {
1122
+ registerTool("find_paid_programs", {
1070
1123
  title: "Find Paid Programs",
1071
1124
  description: "Paid bounty program shortlist.",
1072
1125
  inputSchema: {
@@ -1089,7 +1142,7 @@ export function createBbradarServer(client, config) {
1089
1142
  outputSchema: findProgramsOutputShape(),
1090
1143
  annotations: readOnlyAnnotations
1091
1144
  }, (args) => runTool("find_paid_programs", config, rateLimiter, async () => findPaidPrograms(client, config, args)));
1092
- server.registerTool("find_stack_matches", {
1145
+ registerTool("find_stack_matches", {
1093
1146
  title: "Find Stack Matches",
1094
1147
  description: "Combined stack/language/type/reward shortlist.",
1095
1148
  inputSchema: {
@@ -1119,7 +1172,7 @@ export function createBbradarServer(client, config) {
1119
1172
  outputSchema: findProgramsOutputShape(),
1120
1173
  annotations: readOnlyAnnotations
1121
1174
  }, (args) => runTool("find_stack_matches", config, rateLimiter, async () => findStackMatches(client, config, args)));
1122
- server.registerTool("find_low_noise_programs", {
1175
+ registerTool("find_low_noise_programs", {
1123
1176
  title: "Find Low Noise Programs",
1124
1177
  description: "Low-public-report program shortlist.",
1125
1178
  inputSchema: {
@@ -1145,7 +1198,7 @@ export function createBbradarServer(client, config) {
1145
1198
  outputSchema: findProgramsOutputShape(),
1146
1199
  annotations: readOnlyAnnotations
1147
1200
  }, (args) => runTool("find_low_noise_programs", config, rateLimiter, async () => findLowNoisePrograms(client, config, args)));
1148
- server.registerTool("find_recent_by_type", {
1201
+ registerTool("find_recent_by_type", {
1149
1202
  title: "Find Recent By Type",
1150
1203
  description: "Recent changes by target type grouped by program.",
1151
1204
  inputSchema: {
@@ -1171,7 +1224,7 @@ export function createBbradarServer(client, config) {
1171
1224
  },
1172
1225
  annotations: readOnlyAnnotations
1173
1226
  }, (args) => runTool("find_recent_by_type", config, rateLimiter, async () => findRecentByType(client, config, args)));
1174
- server.registerTool("find_programs_by_target", {
1227
+ registerTool("find_programs_by_target", {
1175
1228
  title: "Find Programs By Target",
1176
1229
  description: "Reverse lookup target/domain ownership. Accepts domains, URLs, wildcards, target names, and partial target text.",
1177
1230
  inputSchema: {
@@ -1194,21 +1247,21 @@ export function createBbradarServer(client, config) {
1194
1247
  },
1195
1248
  annotations: readOnlyAnnotations
1196
1249
  }, (args) => runTool("find_programs_by_target", config, rateLimiter, async () => findProgramsByTarget(client, config, args)));
1197
- server.registerTool("find_program_candidates", {
1250
+ registerTool("find_program_candidates", {
1198
1251
  title: "Find BBRadar Program Candidates",
1199
1252
  description: "Start here for broad program discovery. Default ranked hunt-candidate preset with safe filters and compact output.",
1200
1253
  inputSchema: programCandidatePresetInputSchema,
1201
1254
  outputSchema: findProgramsOutputShape(),
1202
1255
  annotations: readOnlyAnnotations
1203
1256
  }, (args) => runTool("find_program_candidates", config, rateLimiter, async () => findProgramCandidates(client, config, args)));
1204
- server.registerTool("find_hunt_candidates", {
1257
+ registerTool("find_hunt_candidates", {
1205
1258
  title: "Find BBRadar Hunt Candidates",
1206
1259
  description: "Legacy alias for find_program_candidates. Prefer find_program_candidates for new calls.",
1207
1260
  inputSchema: programCandidatePresetInputSchema,
1208
1261
  outputSchema: findProgramsOutputShape(),
1209
1262
  annotations: readOnlyAnnotations
1210
1263
  }, (args) => runTool("find_hunt_candidates", config, rateLimiter, async () => findProgramCandidates(client, config, args)));
1211
- server.registerTool("find_fresh_hunt_candidates", {
1264
+ registerTool("find_fresh_hunt_candidates", {
1212
1265
  title: "Find Fresh Hunt Candidates",
1213
1266
  description: "Freshness-first hunt shortlist.",
1214
1267
  inputSchema: {
@@ -1277,7 +1330,7 @@ export function createBbradarServer(client, config) {
1277
1330
  output_mode: args.output_mode,
1278
1331
  upstream_request_budget: args.upstream_request_budget
1279
1332
  })));
1280
- server.registerTool("find_recently_updated_programs", {
1333
+ registerTool("find_recently_updated_programs", {
1281
1334
  title: "Find Recently Updated Programs",
1282
1335
  description: "Recently updated program shortlist across all platforms by default. Platform filters are applied only when explicitly provided.",
1283
1336
  inputSchema: {
@@ -1302,7 +1355,7 @@ export function createBbradarServer(client, config) {
1302
1355
  outputSchema: findProgramsOutputShape(),
1303
1356
  annotations: readOnlyAnnotations
1304
1357
  }, (args) => runTool("find_recently_updated_programs", config, rateLimiter, async () => findRecentlyUpdatedPrograms(client, config, args)));
1305
- server.registerTool("list_filters", {
1358
+ registerTool("list_filters", {
1306
1359
  title: "List BBRadar Filters",
1307
1360
  description: "Discover observed filter values.",
1308
1361
  inputSchema: {
@@ -1321,7 +1374,7 @@ export function createBbradarServer(client, config) {
1321
1374
  },
1322
1375
  annotations: readOnlyAnnotations
1323
1376
  }, (args) => runTool("list_filters", config, rateLimiter, async () => listFilters(client, args)));
1324
- server.registerTool("get_bbradar_guide", {
1377
+ registerTool("get_bbradar_guide", {
1325
1378
  title: "Get BBRadar Tool Guide",
1326
1379
  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.",
1327
1380
  inputSchema: {
@@ -1334,9 +1387,9 @@ export function createBbradarServer(client, config) {
1334
1387
  annotations: readOnlyAnnotations
1335
1388
  }, (args) => runTool("get_bbradar_guide", config, rateLimiter, async () => ({
1336
1389
  request_id: randomUUID(),
1337
- guide: bbradarGuide(args.topic)
1390
+ guide: bbradarGuide(args.topic, config.toolProfile)
1338
1391
  })));
1339
- server.registerTool("get_mcp_status", {
1392
+ registerTool("get_mcp_status", {
1340
1393
  title: "Get BBRadar MCP Status",
1341
1394
  description: "Local MCP status and timing metrics.",
1342
1395
  inputSchema: {},
@@ -1355,10 +1408,14 @@ export function createBbradarServer(client, config) {
1355
1408
  web_base_url: config.webBaseUrl,
1356
1409
  response_cache_enabled: config.cacheTtlMs > 0 && config.cacheMaxEntries > 0,
1357
1410
  cache_ttl_ms: config.cacheTtlMs,
1411
+ negative_cache_ttl_ms: config.negativeCacheTtlMs,
1358
1412
  cache_max_entries: config.cacheMaxEntries,
1359
1413
  local_program_index_enabled: false,
1360
1414
  default_rate_limit_per_minute: config.defaultRateLimitPerMinute,
1361
1415
  export_rate_limit_per_minute: config.exportRateLimitPerMinute,
1416
+ max_concurrent_upstream_requests: config.maxConcurrentRequests,
1417
+ compact_tool_text: config.compactToolText,
1418
+ tool_profile: config.toolProfile,
1362
1419
  request_timeout_ms: config.requestTimeoutMs,
1363
1420
  validate_on_startup: config.validateOnStartup,
1364
1421
  transport: "stdio",
@@ -1367,7 +1424,7 @@ export function createBbradarServer(client, config) {
1367
1424
  client: client.diagnostics()
1368
1425
  }
1369
1426
  })));
1370
- server.registerTool("compare_programs", {
1427
+ registerTool("compare_programs", {
1371
1428
  title: "Compare BBRadar Programs",
1372
1429
  description: "Compare programs.",
1373
1430
  inputSchema: {
@@ -1381,7 +1438,7 @@ export function createBbradarServer(client, config) {
1381
1438
  outputSchema: decisionOutputShape(),
1382
1439
  annotations: readOnlyAnnotations
1383
1440
  }, (args) => runTool("compare_programs", config, rateLimiter, async () => comparePrograms(client, config, args)));
1384
- server.registerTool("compare_programs_compact", {
1441
+ registerTool("compare_programs_compact", {
1385
1442
  title: "Compare Programs Compact",
1386
1443
  description: "Compact program comparison.",
1387
1444
  inputSchema: {
@@ -1394,7 +1451,7 @@ export function createBbradarServer(client, config) {
1394
1451
  outputSchema: decisionOutputShape(),
1395
1452
  annotations: readOnlyAnnotations
1396
1453
  }, (args) => runTool("compare_programs_compact", config, rateLimiter, async () => comparePrograms(client, config, { ...args, output_mode: "compact" })));
1397
- server.registerTool("summarize_program_activity", {
1454
+ registerTool("summarize_program_activity", {
1398
1455
  title: "Summarize Program Activity",
1399
1456
  description: "Summarize one program's activity.",
1400
1457
  inputSchema: {
@@ -1409,7 +1466,7 @@ export function createBbradarServer(client, config) {
1409
1466
  },
1410
1467
  annotations: readOnlyAnnotations
1411
1468
  }, (args) => runTool("summarize_program_activity", config, rateLimiter, async () => runProgramIdTool(client, config, args.program_id, (programId) => summarizeProgramActivity(client, config, programId, args.recent_changes_limit))));
1412
- server.registerTool("get_program_brief", {
1469
+ registerTool("get_program_brief", {
1413
1470
  title: "Get Program Brief",
1414
1471
  description: "Compact worth-hunting brief for one program.",
1415
1472
  inputSchema: {
@@ -1432,17 +1489,28 @@ export function createBbradarServer(client, config) {
1432
1489
  },
1433
1490
  annotations: readOnlyAnnotations
1434
1491
  }, (args) => runTool("get_program_brief", config, rateLimiter, async () => runProgramIdTool(client, config, args.program_id, (programId) => getProgramBrief(client, config, { ...args, program_id: programId }))));
1435
- server.registerTool("find_recently_added_wildcards", {
1492
+ registerTool("find_recently_added_wildcards", {
1436
1493
  title: "Find Recently Added Wildcards",
1437
- description: "Recently added wildcard programs.",
1494
+ description: "Recently added wildcard programs with platform, minimum reward, and report-count filters. Returns bounded wildcard target samples.",
1438
1495
  inputSchema: {
1439
1496
  platforms: platformListSchema(),
1440
1497
  tags: tagListSchema(),
1498
+ min_added_24h: z.number().int().min(0).optional(),
1441
1499
  min_added_7d: z.number().int().min(1).default(1),
1500
+ min_reward: z.number().int().min(0).optional(),
1501
+ reward_threshold_mode: rewardThresholdModeSchema.default("max_at_least"),
1442
1502
  max_public_report_count: z.number().int().min(0).optional(),
1503
+ require_known_report_count: z.boolean().default(false),
1504
+ include_unknown_report_count: z.boolean().default(false),
1443
1505
  require_bounty: z.boolean().default(true),
1506
+ min_wildcards: z.number().int().min(1).max(100).default(1),
1507
+ sort_by: programSortSchema.default("most_new_targets"),
1444
1508
  max_pages: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(3),
1445
1509
  max_results: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(10),
1510
+ include_target_samples: z.boolean().default(true),
1511
+ target_sample_programs: z.number().int().min(0).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(3),
1512
+ target_sample_size: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(5),
1513
+ output_mode: programListModeSchema.default("compact"),
1446
1514
  upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET)
1447
1515
  },
1448
1516
  outputSchema: findProgramsOutputShape(),
@@ -1456,37 +1524,38 @@ export function createBbradarServer(client, config) {
1456
1524
  web3: false,
1457
1525
  opportunity_levels: [],
1458
1526
  updated_since: undefined,
1459
- min_bounty_min: undefined,
1460
- min_bounty_max: undefined,
1527
+ min_bounty_min: args.min_reward !== undefined && args.reward_threshold_mode === "min_at_least" ? args.min_reward : undefined,
1528
+ min_bounty_max: args.min_reward !== undefined && args.reward_threshold_mode === "max_at_least" ? args.min_reward : undefined,
1461
1529
  max_bounty_max: undefined,
1462
1530
  require_bounty: args.require_bounty,
1463
1531
  max_public_report_count: args.max_public_report_count,
1464
- require_known_report_count: false,
1465
- include_unknown_report_count: false,
1532
+ require_known_report_count: args.require_known_report_count,
1533
+ include_unknown_report_count: args.include_unknown_report_count,
1466
1534
  exclude_ctf: true,
1467
1535
  exclude_mobile_only: false,
1468
1536
  exclude_non_web: false,
1469
1537
  has_api_scope: false,
1470
1538
  fresh_launch_days: undefined,
1471
- min_wildcards: 1,
1539
+ min_wildcards: args.min_wildcards,
1472
1540
  min_eligible_targets: undefined,
1473
1541
  min_total_targets: undefined,
1474
- min_added_24h: undefined,
1542
+ min_added_24h: args.min_added_24h,
1475
1543
  min_added_7d: args.min_added_7d,
1476
1544
  max_pages: args.max_pages,
1477
1545
  max_results: args.max_results,
1478
- sort_by: "best_hunt_value",
1479
- include_target_samples: true,
1480
- target_sample_programs: Math.min(args.max_results, MAX_FIND_TARGET_SAMPLE_PROGRAMS),
1481
- target_sample_size: 5,
1546
+ sort_by: args.sort_by,
1547
+ include_target_samples: args.include_target_samples,
1548
+ target_sample_programs: args.target_sample_programs,
1549
+ target_sample_size: args.target_sample_size,
1482
1550
  only_in_scope_targets: true,
1483
1551
  only_bounty_eligible_targets: true,
1484
1552
  target_samples_only_wildcards: true,
1553
+ output_mode: args.output_mode,
1485
1554
  upstream_request_budget: args.upstream_request_budget
1486
1555
  })));
1487
- server.registerTool("find_low_competition_high_reward", {
1556
+ registerTool("find_low_competition_high_reward", {
1488
1557
  title: "Find Low Competition High Reward",
1489
- description: "Legacy low-report high-reward shortlist.",
1558
+ description: "Low-report, high-reward shortlist with optional wildcard and technology filters.",
1490
1559
  inputSchema: {
1491
1560
  platforms: platformListSchema(),
1492
1561
  tags: tagListSchema(),
@@ -1495,6 +1564,7 @@ export function createBbradarServer(client, config) {
1495
1564
  max_public_report_count: z.number().int().min(0).default(10),
1496
1565
  min_bounty_max: z.number().int().min(0).default(5_000),
1497
1566
  min_eligible_targets: z.number().int().min(0).default(1),
1567
+ min_wildcards: z.number().int().min(0).max(100).default(0),
1498
1568
  exclude_ctf: z.boolean().default(true),
1499
1569
  exclude_mobile_only: z.boolean().default(false),
1500
1570
  exclude_non_web: z.boolean().default(false),
@@ -1502,6 +1572,10 @@ export function createBbradarServer(client, config) {
1502
1572
  fresh_launch_days: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).optional(),
1503
1573
  max_pages: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(3),
1504
1574
  max_results: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(10),
1575
+ include_target_samples: z.boolean().default(true),
1576
+ target_sample_programs: z.number().int().min(0).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(3),
1577
+ target_sample_size: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(5),
1578
+ output_mode: programListModeSchema.default("compact"),
1505
1579
  upstream_request_budget: z.number().int().min(1).max(MAX_ACCEPTED_NUMERIC_LIMIT).default(DEFAULT_FIND_UPSTREAM_REQUEST_BUDGET)
1506
1580
  },
1507
1581
  outputSchema: findProgramsOutputShape(),
@@ -1527,7 +1601,7 @@ export function createBbradarServer(client, config) {
1527
1601
  exclude_non_web: args.exclude_non_web,
1528
1602
  has_api_scope: args.has_api_scope,
1529
1603
  fresh_launch_days: args.fresh_launch_days,
1530
- min_wildcards: 0,
1604
+ min_wildcards: args.min_wildcards,
1531
1605
  min_eligible_targets: args.min_eligible_targets,
1532
1606
  min_total_targets: undefined,
1533
1607
  min_added_24h: undefined,
@@ -1535,15 +1609,16 @@ export function createBbradarServer(client, config) {
1535
1609
  max_pages: args.max_pages,
1536
1610
  max_results: args.max_results,
1537
1611
  sort_by: "best_hunt_value",
1538
- include_target_samples: true,
1539
- target_sample_programs: Math.min(args.max_results, MAX_FIND_TARGET_SAMPLE_PROGRAMS),
1540
- target_sample_size: 5,
1612
+ include_target_samples: args.include_target_samples,
1613
+ target_sample_programs: args.target_sample_programs,
1614
+ target_sample_size: args.target_sample_size,
1541
1615
  only_in_scope_targets: true,
1542
1616
  only_bounty_eligible_targets: true,
1543
- target_samples_only_wildcards: false,
1617
+ target_samples_only_wildcards: args.min_wildcards > 0,
1618
+ output_mode: args.output_mode,
1544
1619
  upstream_request_budget: args.upstream_request_budget
1545
1620
  })));
1546
- server.registerTool("get_program_delta", {
1621
+ registerTool("get_program_delta", {
1547
1622
  title: "Get Program Delta",
1548
1623
  description: "Recent target changes for one program.",
1549
1624
  inputSchema: {
@@ -1561,7 +1636,7 @@ export function createBbradarServer(client, config) {
1561
1636
  },
1562
1637
  annotations: readOnlyAnnotations
1563
1638
  }, (args) => runTool("get_program_delta", config, rateLimiter, async () => runProgramIdTool(client, config, args.program_id, (programId) => getProgramDelta(client, config, { ...args, program_id: programId }))));
1564
- server.registerTool("export_targets", {
1639
+ registerTool("export_targets", {
1565
1640
  title: "Export BBRadar Targets",
1566
1641
  description: `Read-only target export. limit max ${MAX_TARGET_EXPORT}.`,
1567
1642
  inputSchema: {
@@ -1618,11 +1693,14 @@ export function createBbradarServer(client, config) {
1618
1693
  });
1619
1694
  }
1620
1695
  }));
1621
- registerPrompts(server);
1696
+ registerPrompts(server, config.toolProfile);
1622
1697
  registerResources(server, client, config, exportStore);
1623
1698
  return server;
1624
1699
  }
1625
- function bbradarGuide(topic) {
1700
+ function bbradarGuide(topic, profile) {
1701
+ if (profile === "core") {
1702
+ return coreBbradarGuide(topic);
1703
+ }
1626
1704
  const primaryEntryPoints = [
1627
1705
  {
1628
1706
  tool: "find_program_candidates",
@@ -1674,6 +1752,27 @@ function bbradarGuide(topic) {
1674
1752
  first_tool: "find_recently_updated_programs",
1675
1753
  then: "Use get_latest_added_targets for global added targets, or check_program_new_targets when the user names one program."
1676
1754
  },
1755
+ {
1756
+ user_goal: "latest added wildcard targets with platform, reward, or report-count filters",
1757
+ first_tool: "get_latest_added_targets",
1758
+ arguments: {
1759
+ target_type: "wildcard",
1760
+ platforms: ["requested platform"],
1761
+ min_reward: 5000,
1762
+ max_public_report_count: 10,
1763
+ include_full_target_list: false
1764
+ }
1765
+ },
1766
+ {
1767
+ user_goal: "wildcard programs with low report count and good rewards",
1768
+ first_tool: "find_wildcard_programs",
1769
+ arguments: {
1770
+ platforms: ["requested platform"],
1771
+ min_reward: 5000,
1772
+ max_public_report_count: 10,
1773
+ include_target_samples: true
1774
+ }
1775
+ },
1677
1776
  {
1678
1777
  user_goal: "compare a short list",
1679
1778
  first_tool: "compare_programs_compact",
@@ -1691,8 +1790,8 @@ function bbradarGuide(topic) {
1691
1790
  }
1692
1791
  ];
1693
1792
  const advancedTools = {
1694
- discovery: ["find_stack_matches", "find_language_programs", "find_target_type_programs", "find_reward_programs", "find_paid_programs", "find_web3_contests", "find_low_noise_programs"],
1695
- 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"],
1793
+ discovery: ["find_wildcard_programs", "find_low_competition_high_reward", "find_stack_matches", "find_language_programs", "find_target_type_programs", "find_reward_programs", "find_paid_programs", "find_web3_contests", "find_low_noise_programs"],
1794
+ freshness: ["find_recently_added_wildcards", "find_recently_updated_programs", "find_fresh_hunt_candidates", "get_latest_added_targets", "get_recent_target_activity", "get_program_scope_delta", "check_watchlist_new_targets"],
1696
1795
  details: ["get_program_brief", "get_program_scope_summary", "get_program_target_breakdown", "find_programs_by_target"],
1697
1796
  exports: ["export_targets"]
1698
1797
  };
@@ -1724,6 +1823,20 @@ function bbradarGuide(topic) {
1724
1823
  {
1725
1824
  user_says: "who owns https://api.example.com/v1/users",
1726
1825
  call: { tool: "find_programs_by_target", arguments: { target_query: "https://api.example.com/v1/users" } }
1826
+ },
1827
+ {
1828
+ user_says: "latest HackerOne wildcard targets with rewards over 5000",
1829
+ call: {
1830
+ tool: "get_latest_added_targets",
1831
+ arguments: { target_type: "wildcard", platforms: ["HackerOne"], min_reward: 5000, include_full_target_list: false }
1832
+ }
1833
+ },
1834
+ {
1835
+ user_says: "wildcard programs with under 10 reports and good rewards",
1836
+ call: {
1837
+ tool: "find_wildcard_programs",
1838
+ arguments: { max_public_report_count: 10, min_reward: 5000, include_target_samples: true }
1839
+ }
1727
1840
  }
1728
1841
  ];
1729
1842
  return {
@@ -1744,6 +1857,67 @@ function bbradarGuide(topic) {
1744
1857
  examples
1745
1858
  };
1746
1859
  }
1860
+ function coreBbradarGuide(topic) {
1861
+ const decisionTree = [
1862
+ {
1863
+ user_goal: "find programs to hunt",
1864
+ first_tool: "find_programs",
1865
+ arguments: { max_results: 10, output_mode: "compact" }
1866
+ },
1867
+ {
1868
+ user_goal: "named program summary or action",
1869
+ first_tool: "run_program_name_action",
1870
+ arguments: { query: "program name", action: "brief" }
1871
+ },
1872
+ {
1873
+ user_goal: "named program targets",
1874
+ first_tool: "get_program_eligible_targets_by_name",
1875
+ arguments: { query: "program name", target_list_mode: "identifiers" }
1876
+ },
1877
+ {
1878
+ user_goal: "find which program owns a target",
1879
+ first_tool: "find_programs_by_target",
1880
+ arguments: { target_query: "domain, URL, wildcard, package/repo name, or short target phrase", match_mode: "smart" }
1881
+ },
1882
+ {
1883
+ user_goal: "fresh or recently changed scope",
1884
+ first_tool: "get_recent_changes",
1885
+ then: "Use get_latest_added_targets when only newly added targets are needed."
1886
+ },
1887
+ {
1888
+ user_goal: "export targets",
1889
+ first_tool: "export_targets",
1890
+ then: "Use the returned bbradar://exports resource_uri for the full local export payload."
1891
+ },
1892
+ {
1893
+ user_goal: "unknown filter values",
1894
+ first_tool: "list_filters",
1895
+ then: "Reuse observed platforms, tags, target types, and language tags in find_programs."
1896
+ }
1897
+ ];
1898
+ return {
1899
+ topic,
1900
+ tool_profile: "core",
1901
+ rule_zero: "BBRadar is passive intelligence only. Never scan or contact targets.",
1902
+ defaults: {
1903
+ output_mode: "compact",
1904
+ target_list_mode: "identifiers",
1905
+ target_match_mode: "smart",
1906
+ include_out_of_scope: false,
1907
+ include_ineligible: false,
1908
+ require_bounty: true
1909
+ },
1910
+ primary_entry_points: [
1911
+ "find_programs",
1912
+ "run_program_name_action",
1913
+ "get_program_eligible_targets_by_name",
1914
+ "find_programs_by_target",
1915
+ "get_bbradar_guide"
1916
+ ],
1917
+ decision_tree: decisionTree.filter((entry) => topic === "start" || guideEntryMatchesTopic(topic, entry)),
1918
+ available_tools: [...CORE_TOOL_NAMES]
1919
+ };
1920
+ }
1747
1921
  function guideEntryMatchesTopic(topic, entry) {
1748
1922
  const goal = typeof entry.user_goal === "string" ? entry.user_goal : "";
1749
1923
  const firstTool = typeof entry.first_tool === "string" ? entry.first_tool : "";
@@ -3178,13 +3352,26 @@ async function findProgramsByTarget(client, config, input) {
3178
3352
  };
3179
3353
  const sourceRequests = [];
3180
3354
  const matches = [];
3355
+ const directHandleFirst = !queryLooksLikeTargetIdentifier(input.target_query) &&
3356
+ !/\s/.test(input.target_query.trim()) &&
3357
+ looksLikeDirectProgramHandleQuery(input.target_query);
3358
+ if (input.use_api_search && !directHandleFirst) {
3359
+ const directResult = await searchProgramsByTargetApi(client, config, input, budget, warnings, sourceRequests);
3360
+ if (directResult) {
3361
+ return directResult;
3362
+ }
3363
+ }
3181
3364
  const directHandles = await collectDirectHandlePrograms(client, {
3182
3365
  query: input.target_query,
3183
3366
  platforms: input.platforms,
3184
3367
  budget,
3185
3368
  warnings,
3186
3369
  source: "target_query_program_id_guess",
3187
- maxProgramIds: queryLooksLikeTargetIdentifier(input.target_query) || looksLikeDirectProgramHandleQuery(input.target_query) ? MAX_DIRECT_HANDLE_RESOLUTION_REQUESTS : 4,
3370
+ maxProgramIds: input.use_api_search && directHandleFirst
3371
+ ? 4
3372
+ : queryLooksLikeTargetIdentifier(input.target_query) || looksLikeDirectProgramHandleQuery(input.target_query)
3373
+ ? MAX_DIRECT_HANDLE_RESOLUTION_REQUESTS
3374
+ : 4,
3188
3375
  maxHandleVariants: 1,
3189
3376
  stopOnFirstMatch: true
3190
3377
  });
@@ -3204,8 +3391,8 @@ async function findProgramsByTarget(client, config, input) {
3204
3391
  }
3205
3392
  });
3206
3393
  }
3207
- if (input.use_api_search) {
3208
- const directResult = await searchProgramsByTargetApi(client, config, input, warnings);
3394
+ if (input.use_api_search && directHandleFirst) {
3395
+ const directResult = await searchProgramsByTargetApi(client, config, input, budget, warnings, sourceRequests);
3209
3396
  if (directResult) {
3210
3397
  return directResult;
3211
3398
  }
@@ -3310,13 +3497,18 @@ function programsByTargetResult(input, warnings, sourceRequests, matches, budget
3310
3497
  meta: options.meta
3311
3498
  });
3312
3499
  }
3313
- async function searchProgramsByTargetApi(client, config, input, warnings) {
3500
+ async function searchProgramsByTargetApi(client, config, input, budget, warnings, sourceRequests) {
3314
3501
  const searchQueries = targetApiSearchQueries(input.target_query);
3315
3502
  const grouped = new Map();
3316
- const sourceRequests = [];
3317
3503
  let rawMatchesScanned = 0;
3504
+ let searchQueriesChecked = 0;
3318
3505
  let lastMeta;
3319
3506
  for (const searchQuery of searchQueries) {
3507
+ if (!tryConsumeBudget(budget)) {
3508
+ addUniqueWarning(warnings, "Upstream request budget was exhausted before all target search variants could be checked.");
3509
+ break;
3510
+ }
3511
+ searchQueriesChecked += 1;
3320
3512
  let api;
3321
3513
  try {
3322
3514
  api = await client.searchTargets({
@@ -3329,8 +3521,10 @@ async function searchProgramsByTargetApi(client, config, input, warnings) {
3329
3521
  page: 1,
3330
3522
  page_size: Math.max(input.max_results * input.max_targets_per_program, input.max_results)
3331
3523
  });
3524
+ refundBudgetIfNoUpstreamRequest(budget, api);
3332
3525
  }
3333
3526
  catch (error) {
3527
+ refundBudgetIfNoUpstreamError(budget, error);
3334
3528
  if (error instanceof BBRadarApiError && (error.status === 404 || error.status === 405)) {
3335
3529
  warnings.push("Direct target search API is unavailable; fell back to sampled program target lists.");
3336
3530
  return undefined;
@@ -3371,15 +3565,93 @@ async function searchProgramsByTargetApi(client, config, input, warnings) {
3371
3565
  if (!targetMatchesQuery(target, input.target_query, input.match_mode)) {
3372
3566
  continue;
3373
3567
  }
3374
- const existing = grouped.get(programId) ?? { program: addProgramResourceLinks({ ...program, id: programId }), targets: [] };
3375
- existing.targets.push(target);
3568
+ const existing = grouped.get(programId) ?? {
3569
+ program: addProgramResourceLinks({ ...program, id: programId }),
3570
+ targets: [],
3571
+ targetKeys: new Set()
3572
+ };
3573
+ const targetKey = stableStringify({
3574
+ identifier: targetIdentifier(target),
3575
+ target_type: stringField(target, "target_type")
3576
+ });
3577
+ if (!existing.targetKeys.has(targetKey)) {
3578
+ existing.targetKeys.add(targetKey);
3579
+ existing.targets.push(target);
3580
+ }
3376
3581
  grouped.set(programId, existing);
3377
3582
  }
3378
3583
  }
3379
- if (grouped.size >= input.max_results) {
3584
+ if (grouped.size >= input.max_results ||
3585
+ (grouped.size > 0 && input.match_mode === "smart")) {
3380
3586
  break;
3381
3587
  }
3382
3588
  }
3589
+ if (grouped.size > 0 && input.match_mode === "smart") {
3590
+ await mapWithConcurrency([...grouped.entries()].slice(0, input.max_results), TARGET_SAMPLE_CONCURRENCY, async ([programId, entry]) => {
3591
+ if (!tryConsumeBudget(budget)) {
3592
+ addUniqueWarning(warnings, "Upstream request budget was exhausted before matched program target lists could be hydrated.");
3593
+ return;
3594
+ }
3595
+ try {
3596
+ const targetsApi = await client.getProgramTargets(programId);
3597
+ refundBudgetIfNoUpstreamRequest(budget, targetsApi);
3598
+ sourceRequests.push({
3599
+ source: "program_targets",
3600
+ program_id: programId,
3601
+ request_id: targetsApi.requestId,
3602
+ upstream_request_id: targetsApi.upstreamRequestId,
3603
+ ...apiSourceMetadata(targetsApi)
3604
+ });
3605
+ const targetsData = readObject(targetsApi.data);
3606
+ const hydratedTargets = readArray(targetsData?.targets).map(sanitizeTarget);
3607
+ const hydratedTargetOrder = new Map(hydratedTargets.map((target, index) => [
3608
+ stableStringify({
3609
+ identifier: targetIdentifier(target),
3610
+ target_type: stringField(target, "target_type")
3611
+ }),
3612
+ index
3613
+ ]));
3614
+ for (const target of hydratedTargets) {
3615
+ if (!targetIdentifier(target) ||
3616
+ !targetHasAllowedScope(target, {
3617
+ include_out_of_scope: input.include_out_of_scope,
3618
+ include_ineligible: input.include_ineligible,
3619
+ strict_scope_filter: false
3620
+ }) ||
3621
+ !targetMatchesQuery(target, input.target_query, input.match_mode)) {
3622
+ continue;
3623
+ }
3624
+ const targetKey = stableStringify({
3625
+ identifier: targetIdentifier(target),
3626
+ target_type: stringField(target, "target_type")
3627
+ });
3628
+ if (!entry.targetKeys.has(targetKey)) {
3629
+ entry.targetKeys.add(targetKey);
3630
+ entry.targets.push(target);
3631
+ }
3632
+ }
3633
+ entry.targets.sort((left, right) => {
3634
+ const leftKey = stableStringify({
3635
+ identifier: targetIdentifier(left),
3636
+ target_type: stringField(left, "target_type")
3637
+ });
3638
+ const rightKey = stableStringify({
3639
+ identifier: targetIdentifier(right),
3640
+ target_type: stringField(right, "target_type")
3641
+ });
3642
+ return ((hydratedTargetOrder.get(leftKey) ?? Number.MAX_SAFE_INTEGER) -
3643
+ (hydratedTargetOrder.get(rightKey) ?? Number.MAX_SAFE_INTEGER));
3644
+ });
3645
+ }
3646
+ catch (error) {
3647
+ refundBudgetIfNoUpstreamError(budget, error);
3648
+ if (!isProgramNotFoundError(error)) {
3649
+ throw error;
3650
+ }
3651
+ addUniqueWarning(warnings, `Could not hydrate target matches for ${programId}; returned direct target-search matches only.`);
3652
+ }
3653
+ });
3654
+ }
3383
3655
  const matches = [...grouped.values()].slice(0, input.max_results).map((entry) => stripUndefined({
3384
3656
  program: formatProgram(entry.program, "compact"),
3385
3657
  match_count: entry.targets.length,
@@ -3407,6 +3679,14 @@ async function searchProgramsByTargetApi(client, config, input, warnings) {
3407
3679
  },
3408
3680
  matches,
3409
3681
  programs: matches.map((match) => readObject(match.program)).filter((program) => program !== undefined),
3682
+ upstream_budget: {
3683
+ used: budget.initial - budget.remaining,
3684
+ remaining: budget.remaining
3685
+ },
3686
+ ranking_scope: {
3687
+ mode: "target_search_api",
3688
+ possibly_incomplete: grouped.size < input.max_results && searchQueriesChecked < searchQueries.length
3689
+ },
3410
3690
  meta: stripUndefined({
3411
3691
  ...(lastMeta ?? {}),
3412
3692
  returned: matches.length,
@@ -3503,7 +3783,8 @@ async function listFilters(client, input) {
3503
3783
  }
3504
3784
  }
3505
3785
  }
3506
- catch {
3786
+ catch (error) {
3787
+ refundBudgetIfNoUpstreamError(budget, error);
3507
3788
  // Facets are best-effort; list_filters should still return observed program filters.
3508
3789
  }
3509
3790
  if (input.include_target_facets && input.target_sample_programs > 0) {
@@ -3639,11 +3920,15 @@ async function comparePrograms(client, config, input) {
3639
3920
  });
3640
3921
  }
3641
3922
  async function summarizeProgramActivity(client, config, programId, recentChangesLimit) {
3642
- const programApi = await client.getProgram(programId);
3923
+ const [programApi, changes] = await Promise.all([
3924
+ client.getProgram(programId),
3925
+ recentChangesLimit > 0
3926
+ ? fetchProgramChanges(client, config, programId, recentChangesLimit, true, true, true)
3927
+ : Promise.resolve(undefined)
3928
+ ]);
3643
3929
  const program = addProgramResourceLinks(sanitizeProgram(programApi.data, config.webBaseUrl));
3644
3930
  const scopeSummary = readObject(program.scope_summary);
3645
3931
  const targetActivity = readObject(scopeSummary?.target_activity);
3646
- const changes = recentChangesLimit > 0 ? await fetchProgramChanges(client, config, programId, recentChangesLimit, true, true, true) : undefined;
3647
3932
  return {
3648
3933
  request_id: randomUUID(),
3649
3934
  source_requests: [
@@ -3670,12 +3955,14 @@ async function getLatestAddedTargets(client, config, input) {
3670
3955
  include_removed: false,
3671
3956
  include_ineligible: input.include_ineligible,
3672
3957
  include_out_of_scope: input.include_out_of_scope,
3958
+ platforms: input.platforms,
3673
3959
  tags: [input.target_type],
3674
3960
  page: 1,
3675
3961
  page_size: input.recent_changes_page_size
3676
3962
  });
3677
3963
  const recentData = readObject(recentApi.data);
3678
3964
  const changes = readArray(recentData?.results).map((change) => sanitizeChange(change, config.webBaseUrl));
3965
+ const platformFilters = new Set(input.platforms.map(normalizeTag));
3679
3966
  const matchingChanges = changes
3680
3967
  .filter((change) => {
3681
3968
  const target = readObject(change.target);
@@ -3684,6 +3971,7 @@ async function getLatestAddedTargets(client, config, input) {
3684
3971
  programId !== undefined &&
3685
3972
  target !== undefined &&
3686
3973
  targetMatchesRequestedType(target, input.target_type) &&
3974
+ latestAddedProgramMatches(change, input, platformFilters) &&
3687
3975
  targetHasAllowedScope(target, {
3688
3976
  include_out_of_scope: input.include_out_of_scope,
3689
3977
  include_ineligible: input.include_ineligible,
@@ -3691,6 +3979,11 @@ async function getLatestAddedTargets(client, config, input) {
3691
3979
  }));
3692
3980
  })
3693
3981
  .sort((left, right) => timestampField(right, "changed_at") - timestampField(left, "changed_at"));
3982
+ const limitedMatches = matchingChanges.slice(0, input.max_results);
3983
+ const matches = limitedMatches.map((change, index) => ({
3984
+ rank: index + 1,
3985
+ ...formatChange(change, "compact")
3986
+ }));
3694
3987
  const latest = matchingChanges[0];
3695
3988
  const sourceRequests = [
3696
3989
  {
@@ -3705,9 +3998,12 @@ async function getLatestAddedTargets(client, config, input) {
3705
3998
  request_id: randomUUID(),
3706
3999
  source_requests: sourceRequests,
3707
4000
  query: latestAddedTargetsQuery(input),
4001
+ matches: [],
3708
4002
  new_targets: [],
3709
4003
  meta: {
3710
4004
  recent_changes_scanned: changes.length,
4005
+ matching_changes_scanned: 0,
4006
+ matches_returned: 0,
3711
4007
  no_match: true
3712
4008
  }
3713
4009
  };
@@ -3750,11 +4046,14 @@ async function getLatestAddedTargets(client, config, input) {
3750
4046
  query: latestAddedTargetsQuery(input),
3751
4047
  latest_added_at: latestAddedAt,
3752
4048
  program,
4049
+ matches,
3753
4050
  new_targets: formatTargetList(latestNewTargets, input.full_target_list_mode),
3754
4051
  full_targets: input.include_full_target_list ? formatTargetList(limitedFullTargets, input.full_target_list_mode) : undefined,
3755
4052
  meta: stripUndefined({
3756
4053
  recent_changes_scanned: changes.length,
3757
4054
  matching_changes_scanned: matchingChanges.length,
4055
+ matches_returned: matches.length,
4056
+ matches_has_more: matches.length < matchingChanges.length,
3758
4057
  new_targets_returned: formatTargetList(latestNewTargets, input.full_target_list_mode).length,
3759
4058
  full_targets_total_active: totalActiveTargets,
3760
4059
  full_targets_total_after_filters: totalAfterFilters,
@@ -3763,6 +4062,39 @@ async function getLatestAddedTargets(client, config, input) {
3763
4062
  })
3764
4063
  });
3765
4064
  }
4065
+ function latestAddedProgramMatches(change, input, platformFilters) {
4066
+ const rawProgram = readObject(change.program);
4067
+ if (!rawProgram) {
4068
+ return false;
4069
+ }
4070
+ const program = addProgramResourceLinks(rawProgram);
4071
+ const rewardRange = readObject(program.reward_range);
4072
+ const platform = normalizeTag(stringField(program, "platform") ?? "");
4073
+ const bountyMin = numberField(rewardRange, "min");
4074
+ const bountyMax = numberField(rewardRange, "max");
4075
+ const publicReportCount = numberField(program, "public_report_count");
4076
+ if (platformFilters.size > 0 && !platformFilters.has(platform)) {
4077
+ return false;
4078
+ }
4079
+ if (input.min_reward !== undefined) {
4080
+ const reward = input.reward_threshold_mode === "min_at_least" ? bountyMin : bountyMax;
4081
+ if ((reward ?? 0) < input.min_reward) {
4082
+ return false;
4083
+ }
4084
+ }
4085
+ if (input.require_known_report_count && publicReportCount === undefined) {
4086
+ return false;
4087
+ }
4088
+ if (input.max_public_report_count !== undefined) {
4089
+ if (publicReportCount === undefined) {
4090
+ return input.include_unknown_report_count;
4091
+ }
4092
+ if (publicReportCount > input.max_public_report_count) {
4093
+ return false;
4094
+ }
4095
+ }
4096
+ return true;
4097
+ }
3766
4098
  async function getProgramTargetsPayload(client, input, programId) {
3767
4099
  const api = await client.getProgramTargets(programId);
3768
4100
  const data = readObject(api.data);
@@ -4319,9 +4651,11 @@ async function getProgramTargetBreakdownFromIndexFallback(client, indexFallback,
4319
4651
  });
4320
4652
  }
4321
4653
  async function getProgramScopeDelta(client, config, input) {
4322
- const programApi = await client.getProgram(input.program_id);
4654
+ const [programApi, changesPayload] = await Promise.all([
4655
+ client.getProgram(input.program_id),
4656
+ fetchProgramChanges(client, config, input.program_id, input.page_size, input.include_removed || input.change_type === "removed", input.include_ineligible, input.include_out_of_scope, input.max_targets)
4657
+ ]);
4323
4658
  const program = addProgramResourceLinks(sanitizeProgram(programApi.data, config.webBaseUrl));
4324
- const changesPayload = await fetchProgramChanges(client, config, input.program_id, input.page_size, input.include_removed || input.change_type === "removed", input.include_ineligible, input.include_out_of_scope, input.max_targets);
4325
4659
  const filteredChanges = filterProgramScopeDeltaChanges(changesPayload.changes, input);
4326
4660
  const limitedChanges = filteredChanges.slice(0, input.max_targets);
4327
4661
  return stripUndefined({
@@ -4776,9 +5110,11 @@ async function getProgramBrief(client, config, input) {
4776
5110
  });
4777
5111
  }
4778
5112
  async function getProgramDelta(client, config, input) {
4779
- const programApi = await client.getProgram(input.program_id);
5113
+ const [programApi, changes] = await Promise.all([
5114
+ client.getProgram(input.program_id),
5115
+ fetchProgramChanges(client, config, input.program_id, input.page_size, input.include_removed, input.include_ineligible, input.include_out_of_scope)
5116
+ ]);
4780
5117
  const program = addProgramResourceLinks(sanitizeProgram(programApi.data, config.webBaseUrl));
4781
- const changes = await fetchProgramChanges(client, config, input.program_id, input.page_size, input.include_removed, input.include_ineligible, input.include_out_of_scope);
4782
5118
  return {
4783
5119
  request_id: randomUUID(),
4784
5120
  source_requests: [
@@ -5372,6 +5708,9 @@ function hasHighConfidenceProgramResolutionMatch(programs, webBaseUrl, query) {
5372
5708
  const threshold = looksLikeDirectProgramHandleQuery(query) ? 95 : 85;
5373
5709
  return programs.some((program) => programResolutionMatch(toProgramCandidate(program, webBaseUrl), query).score >= threshold);
5374
5710
  }
5711
+ function hasViableProgramResolutionMatch(programs, scorePrograms) {
5712
+ return scorePrograms ? scorePrograms(programs) >= 80 : programs.length > 0;
5713
+ }
5375
5714
  function bestProgramResolutionScore(programs, webBaseUrl, query) {
5376
5715
  return programs.reduce((best, program) => Math.max(best, programResolutionMatch(toProgramCandidate(program, webBaseUrl), query).score), 0);
5377
5716
  }
@@ -5511,7 +5850,13 @@ async function findPrograms(client, config, input) {
5511
5850
  initial: input.upstream_request_budget,
5512
5851
  remaining: input.upstream_request_budget
5513
5852
  };
5514
- const upstreamTags = uniqueStrings([...input.tags, ...input.scope_tags, ...input.target_types, ...input.language_tags]);
5853
+ const upstreamTags = uniqueStrings([
5854
+ ...input.tags,
5855
+ ...input.scope_tags,
5856
+ ...input.target_types,
5857
+ ...input.language_tags,
5858
+ ...(input.min_wildcards > 0 ? ["wildcard"] : [])
5859
+ ]);
5515
5860
  const collected = await collectPrograms(client, {
5516
5861
  platforms: input.platforms,
5517
5862
  tags: upstreamTags,
@@ -5623,8 +5968,33 @@ async function collectProgramsForResolution(client, options) {
5623
5968
  const collections = [];
5624
5969
  const queryCollections = [];
5625
5970
  const queries = options.searchQueries.length > 0 ? options.searchQueries : [undefined];
5971
+ const fastQueries = queries.slice(0, FAST_RESOLVE_SEARCH_QUERIES);
5972
+ const remainingQueries = queries.slice(FAST_RESOLVE_SEARCH_QUERIES);
5626
5973
  const primaryDirectPlatforms = options.platforms.length > 0 ? options.platforms : PRIMARY_PROGRAM_ID_PLATFORM_GUESSES;
5627
5974
  const primaryDirectProgramIds = new Set(directProgramIdCandidates(options.query, primaryDirectPlatforms, { maxHandleVariants: 1 }));
5975
+ for (const search of fastQueries) {
5976
+ if (options.budget.remaining <= 0) {
5977
+ addUniqueWarning(options.warnings, "Program name search stopped at the request budget; pass a higher upstream_request_budget for a deeper resolution attempt.");
5978
+ break;
5979
+ }
5980
+ const collection = await collectPrograms(client, {
5981
+ search,
5982
+ platforms: options.platforms,
5983
+ tags: options.tags,
5984
+ updated_since: undefined,
5985
+ opportunity_levels: options.opportunity_levels,
5986
+ max_pages: 1,
5987
+ budget: options.budget,
5988
+ warnings: options.warnings
5989
+ });
5990
+ collections.push(collection);
5991
+ queryCollections.push({ search, collection });
5992
+ const merged = mergeProgramCollections(collections);
5993
+ if (options.isHighConfidenceMatch?.(merged.programs) ||
5994
+ hasViableProgramResolutionMatch(merged.programs, options.scorePrograms)) {
5995
+ return merged;
5996
+ }
5997
+ }
5628
5998
  if (primaryDirectProgramIds.size > 0 && options.budget.remaining > 0) {
5629
5999
  const primaryDirectHandles = await collectDirectHandlePrograms(client, {
5630
6000
  query: options.query,
@@ -5644,7 +6014,7 @@ async function collectProgramsForResolution(client, options) {
5644
6014
  return mergeProgramCollections(collections);
5645
6015
  }
5646
6016
  }
5647
- for (const search of queries) {
6017
+ for (const search of remainingQueries) {
5648
6018
  if (options.budget.remaining <= 0) {
5649
6019
  addUniqueWarning(options.warnings, "Program name search stopped at the request budget; pass a higher upstream_request_budget for a deeper resolution attempt.");
5650
6020
  break;
@@ -5665,7 +6035,9 @@ async function collectProgramsForResolution(client, options) {
5665
6035
  return mergeProgramCollections(collections);
5666
6036
  }
5667
6037
  }
5668
- if (options.budget.remaining > 0) {
6038
+ const searchedCollection = collections.length > 0 ? mergeProgramCollections(collections) : undefined;
6039
+ if (options.budget.remaining > 0 &&
6040
+ (!searchedCollection || !hasViableProgramResolutionMatch(searchedCollection.programs, options.scorePrograms))) {
5669
6041
  const directHandles = await collectDirectHandlePrograms(client, {
5670
6042
  query: options.query,
5671
6043
  platforms: options.platforms,
@@ -5752,9 +6124,14 @@ async function collectDirectHandlePrograms(client, options) {
5752
6124
  const sourceRequests = [];
5753
6125
  let requestsScanned = 0;
5754
6126
  for (const programId of programIds) {
6127
+ if (!tryConsumeBudget(options.budget)) {
6128
+ addUniqueWarning(options.warnings, "Upstream request budget was exhausted before all direct program handle guesses could be checked.");
6129
+ break;
6130
+ }
5755
6131
  requestsScanned += 1;
5756
6132
  try {
5757
6133
  const targetsApi = await client.getProgramTargets(programId);
6134
+ refundBudgetIfNoUpstreamRequest(options.budget, targetsApi);
5758
6135
  const targetsData = readObject(targetsApi.data);
5759
6136
  const hasTargetsArray = Array.isArray(targetsData?.targets);
5760
6137
  const targets = readArray(targetsData?.targets).map(sanitizeTarget);
@@ -5775,6 +6152,7 @@ async function collectDirectHandlePrograms(client, options) {
5775
6152
  }
5776
6153
  }
5777
6154
  catch (error) {
6155
+ refundBudgetIfNoUpstreamError(options.budget, error);
5778
6156
  if (error instanceof BBRadarApiError && error.status === 404) {
5779
6157
  sourceRequests.push({
5780
6158
  source: options.source,
@@ -5782,7 +6160,9 @@ async function collectDirectHandlePrograms(client, options) {
5782
6160
  program_id: programId,
5783
6161
  request_id: error.requestId,
5784
6162
  upstream_request_id: error.upstreamRequestId,
5785
- status: error.status
6163
+ status: error.status,
6164
+ cache_hit: error.cached,
6165
+ cache_expires_at: error.cacheExpiresAt
5786
6166
  });
5787
6167
  continue;
5788
6168
  }
@@ -6195,7 +6575,8 @@ async function collectTargetSamples(client, candidates, input, budget) {
6195
6575
  .slice(0, input.target_sample_size);
6196
6576
  samples[programId] = targets;
6197
6577
  }
6198
- catch {
6578
+ catch (error) {
6579
+ refundBudgetIfNoUpstreamError(budget, error);
6199
6580
  samples[programId] = [];
6200
6581
  }
6201
6582
  });
@@ -6258,6 +6639,11 @@ function refundBudgetIfNoUpstreamRequest(budget, api, cost = 1) {
6258
6639
  budget.remaining = Math.min(budget.initial, budget.remaining + cost);
6259
6640
  }
6260
6641
  }
6642
+ function refundBudgetIfNoUpstreamError(budget, error, cost = 1) {
6643
+ if (error instanceof BBRadarApiError && (error.cached || error.coalesced)) {
6644
+ budget.remaining = Math.min(budget.initial, budget.remaining + cost);
6645
+ }
6646
+ }
6261
6647
  function addUniqueWarning(warnings, warning) {
6262
6648
  if (warnings && !warnings.includes(warning)) {
6263
6649
  warnings.push(warning);
@@ -6360,7 +6746,12 @@ function readStringArrayField(value, field) {
6360
6746
  const fieldValue = value[field];
6361
6747
  return Array.isArray(fieldValue) ? fieldValue.filter((entry) => typeof entry === "string") : [];
6362
6748
  }
6363
- function registerPrompts(server) {
6749
+ function registerPrompts(server, profile) {
6750
+ const discoveryToolInstructions = profile === "core"
6751
+ ? `- Start with find_programs and pass opportunity_levels from the requested value below unless the user asked for a broader search.
6752
+ - Express language, target type, reward, Web3, wildcard, VDP, and platform needs through find_programs filters.`
6753
+ : `- Start with find_program_candidates unless the user needs custom filters; pass opportunity_levels from the requested value below.
6754
+ - Use find_language_programs for language asks, find_target_type_programs for API/mobile/source-code/domain asks, find_reward_programs or find_paid_programs for reward asks, find_vdp_programs only for explicit VDP/no-bounty asks, find_web3_contests for Web3 contest/audit asks, find_web3_programs for general Web3 requests, and find_wildcard_programs for wildcard-specific requests.`;
6364
6755
  server.registerPrompt("find_best_programs_to_hunt", {
6365
6756
  title: "Find Best Programs To Hunt",
6366
6757
  description: "Rank fresh BBRadar program data only. Do not invoke external bug bounty skills unless the user explicitly asks for methodology.",
@@ -6373,8 +6764,8 @@ function registerPrompts(server) {
6373
6764
  }, (args) => promptResult(`Use the BBRadar MCP tools only. Do not invoke local bug bounty skills, methodology files, worklogs, or non-BBRadar tools unless the user explicitly asks for methodology. Do not perform active scanning or contact third-party targets. Treat results as fresh API reads; do not describe them as cached.
6374
6765
 
6375
6766
  Find the best BBRadar program candidates.
6376
- - Start with find_program_candidates unless the user needs custom filters; pass opportunity_levels from this JSON array unless the user asked for a broader search: ${promptJson([args.opportunity_level ?? "elite"])}.
6377
- - Use find_language_programs for language asks, find_target_type_programs for API/mobile/source-code/domain asks, find_reward_programs or find_paid_programs for reward asks, find_vdp_programs only for explicit VDP/no-bounty asks, find_web3_contests for Web3 contest/audit asks, find_web3_programs for general Web3 requests, and find_wildcard_programs for wildcard-specific requests.
6767
+ ${discoveryToolInstructions}
6768
+ - Use this opportunity level JSON array: ${promptJson([args.opportunity_level ?? "elite"])}.
6378
6769
  - Apply platform filters from this JSON string if provided: ${promptJson(args.platforms ?? "")}.
6379
6770
  - Apply tag filters from this JSON string if provided: ${promptJson(args.tags ?? "")}.
6380
6771
  - Keep the shortlist to this JSON value: ${promptJson(args.max_programs ?? "10")}.
@@ -6434,7 +6825,7 @@ async function runTool(toolName, config, rateLimiter, callback) {
6434
6825
  },
6435
6826
  mcp_rate_limit: toRateLimitPayload(decision),
6436
6827
  mcp_timing: toolTimingPayload(toolName, durationMs)
6437
- }, true);
6828
+ }, true, config.compactToolText);
6438
6829
  }
6439
6830
  try {
6440
6831
  const payload = await callback();
@@ -6444,21 +6835,21 @@ async function runTool(toolName, config, rateLimiter, callback) {
6444
6835
  ...payload,
6445
6836
  mcp_rate_limit: toRateLimitPayload(decision),
6446
6837
  mcp_timing: toolTimingPayload(toolName, durationMs)
6447
- });
6838
+ }, false, config.compactToolText);
6448
6839
  }
6449
6840
  catch (error) {
6450
6841
  const durationMs = Date.now() - startedAt;
6451
6842
  recordToolMetric(toolName, durationMs, "error");
6452
- return toolResult(errorPayload(error, decision, toolName, durationMs), true);
6843
+ return toolResult(errorPayload(error, decision, toolName, durationMs), true, config.compactToolText);
6453
6844
  }
6454
6845
  }
6455
- function toolResult(payload, isError = false) {
6846
+ function toolResult(payload, isError = false, compactText = false) {
6456
6847
  const resourceLinks = collectResourceLinks(payload);
6457
6848
  return {
6458
6849
  content: [
6459
6850
  {
6460
6851
  type: "text",
6461
- text: JSON.stringify(payload)
6852
+ text: compactText ? compactToolText(payload, isError) : JSON.stringify(payload)
6462
6853
  },
6463
6854
  ...resourceLinks
6464
6855
  ],
@@ -6466,6 +6857,28 @@ function toolResult(payload, isError = false) {
6466
6857
  ...(isError ? { isError: true } : {})
6467
6858
  };
6468
6859
  }
6860
+ function compactToolText(payload, isError) {
6861
+ if (isError) {
6862
+ return JSON.stringify(stripUndefined({
6863
+ request_id: payload.request_id,
6864
+ error: payload.error,
6865
+ mcp_timing: payload.mcp_timing
6866
+ }));
6867
+ }
6868
+ const meta = readObject(payload.meta);
6869
+ const returned = numberField(meta, "returned") ??
6870
+ [
6871
+ readArray(payload.programs).length,
6872
+ readArray(payload.targets).length,
6873
+ readArray(payload.changes).length
6874
+ ].find((count) => count > 0);
6875
+ return JSON.stringify(stripUndefined({
6876
+ request_id: payload.request_id,
6877
+ summary: "Full tool result is available in structuredContent.",
6878
+ returned,
6879
+ mcp_timing: payload.mcp_timing
6880
+ }));
6881
+ }
6469
6882
  function promptResult(text) {
6470
6883
  return {
6471
6884
  messages: [
@@ -6605,18 +7018,25 @@ function compactRecord(values) {
6605
7018
  }));
6606
7019
  }
6607
7020
  function latestAddedTargetsQuery(input) {
6608
- return {
7021
+ return stripUndefined({
6609
7022
  change_type: "added",
6610
7023
  target_type: input.target_type,
7024
+ platforms: input.platforms.length > 0 ? input.platforms : undefined,
7025
+ min_reward: input.min_reward,
7026
+ reward_threshold_mode: input.reward_threshold_mode,
7027
+ max_public_report_count: input.max_public_report_count,
7028
+ require_known_report_count: input.require_known_report_count,
7029
+ include_unknown_report_count: input.include_unknown_report_count,
6611
7030
  include_out_of_scope: input.include_out_of_scope,
6612
7031
  include_ineligible: input.include_ineligible,
6613
7032
  recent_changes_page_size: input.recent_changes_page_size,
7033
+ max_results: input.max_results,
6614
7034
  include_full_target_list: input.include_full_target_list,
6615
7035
  full_target_list_mode: input.full_target_list_mode,
6616
7036
  full_target_limit: input.full_target_limit,
6617
7037
  full_list_include_out_of_scope: input.full_list_include_out_of_scope,
6618
7038
  full_list_include_ineligible: input.full_list_include_ineligible
6619
- };
7039
+ });
6620
7040
  }
6621
7041
  function formatTargetList(targets, mode) {
6622
7042
  if (mode === "identifiers") {