@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/README.md CHANGED
@@ -112,6 +112,43 @@ Most tools that accept `program_id` also accept a human program name or handle.
112
112
 
113
113
  `find_programs_by_target` defaults to flexible target matching. It accepts bare domains, full URLs, wildcard scopes, target names, package/repo-style identifiers, or short phrases like `known api target`.
114
114
 
115
+ ## Performance Tuning
116
+
117
+ The defaults expose the complete tool surface and full text results while enabling bounded concurrency, positive response caching, in-flight request coalescing, and short negative caching for repeated 404/405 lookups.
118
+
119
+ | Environment variable | Default | Purpose |
120
+ | --- | ---: | --- |
121
+ | `BBRADAR_MCP_CACHE_TTL_SECONDS` | `30` | Successful GET response cache lifetime. Set to `0` to disable. |
122
+ | `BBRADAR_MCP_NEGATIVE_CACHE_TTL_SECONDS` | `15` | Repeated 404/405 cache lifetime. Set to `0` to disable. |
123
+ | `BBRADAR_MCP_CACHE_MAX_ENTRIES` | `500` | Maximum entries in each local response cache. |
124
+ | `BBRADAR_MCP_MAX_CONCURRENT_REQUESTS` | `6` | Maximum simultaneous upstream BBRadar API requests. |
125
+ | `BBRADAR_MCP_REQUEST_TIMEOUT_SECONDS` | `30` | Timeout for each active upstream request; queue time is excluded. |
126
+ | `BBRADAR_MCP_COMPACT_TOOL_TEXT` | `false` | Set to `true` to avoid duplicating results for clients that consume MCP `structuredContent`. |
127
+ | `BBRADAR_MCP_TOOL_PROFILE` | `full` | Complete specialized tool surface. Set to `core` only when a smaller 15-tool discovery payload is preferred. |
128
+
129
+ No performance variables are required. Caching, request coalescing, name-resolution improvements, early stopping, bounded concurrency, aggregate-call parallelism, and upstream quota protection are always active.
130
+
131
+ The client learns the API's `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset` headers per endpoint group. Once an observed group reaches zero, it blocks additional requests to that group locally until reset instead of repeatedly hitting the API, while unrelated endpoint groups remain usable. A rolling 60-second guard also enforces the documented shared key-and-IP ceiling across uncached upstream requests. A real `429` honors `Retry-After` as a global cooldown; requests are not automatically retried, so one MCP action cannot amplify a rate-limit response into more API calls. The global upstream concurrency default is six, with the heavier target-changes endpoint capped at three.
132
+
133
+ Call `get_mcp_status` to inspect cache hit rates, negative-cache hits, concurrency state, the latest upstream quota state, actual upstream request counts, per-endpoint status totals, and timing.
134
+
135
+ ### Optimized wildcard discovery
136
+
137
+ For the latest added wildcard targets from one platform with reward and report-count limits, use `get_latest_added_targets`:
138
+
139
+ ```json
140
+ {
141
+ "target_type": "wildcard",
142
+ "platforms": ["HackerOne"],
143
+ "min_reward": 5000,
144
+ "max_public_report_count": 10,
145
+ "require_known_report_count": true,
146
+ "include_full_target_list": false
147
+ }
148
+ ```
149
+
150
+ This path uses one recent-changes request. Use `find_wildcard_programs` for a ranked current-program shortlist, or `find_recently_added_wildcards` when recent wildcard activity matters. Both accept platform, minimum reward, report-count, and bounded target-sample filters. Wildcard shortlist workflows automatically send the `wildcard` target-type tag upstream before applying reward and report filters locally, so their page budget is spent only on relevant programs. `find_low_competition_high_reward` also accepts `min_wildcards`.
151
+
115
152
  ## Tools
116
153
 
117
154
  - `search_programs`
@@ -1,4 +1,3 @@
1
- import { type CacheStats } from "./cache.js";
2
1
  import type { ServerConfig } from "./config.js";
3
2
  export type QueryValue = string | number | boolean | null | undefined | readonly string[] | readonly number[] | readonly boolean[];
4
3
  export type ApiRequestOptions = {
@@ -25,6 +24,9 @@ export declare class BBRadarApiError extends Error {
25
24
  readonly status: number | undefined;
26
25
  readonly detail?: unknown;
27
26
  readonly errors?: unknown;
27
+ readonly cached: boolean;
28
+ readonly coalesced: boolean;
29
+ readonly cacheExpiresAt: string | undefined;
28
30
  constructor(params: {
29
31
  message: string;
30
32
  requestId: string;
@@ -32,19 +34,111 @@ export declare class BBRadarApiError extends Error {
32
34
  status?: number | undefined;
33
35
  detail?: unknown;
34
36
  errors?: unknown;
37
+ cached?: boolean | undefined;
38
+ coalesced?: boolean | undefined;
39
+ cacheExpiresAt?: string | undefined;
35
40
  cause?: unknown;
36
41
  });
37
42
  }
38
43
  export declare class BBRadarClient {
39
44
  private readonly config;
40
45
  private readonly cache;
46
+ private readonly negativeCache;
41
47
  private readonly inFlight;
48
+ private readonly endpointMetrics;
49
+ private readonly requestQueue;
50
+ private readonly endpointRequestQueues;
51
+ private readonly endpointActiveRequests;
52
+ private readonly upstreamRateLimits;
53
+ private readonly upstreamRequestTimestampsMs;
54
+ private activeRequests;
55
+ private globalBlockedUntilMs;
42
56
  constructor(config: ServerConfig);
43
57
  diagnostics(): {
44
- response_cache: CacheStats & {
58
+ response_cache: {
59
+ entries: number;
60
+ max_entries: number;
61
+ ttl_ms: number;
62
+ hits: number;
63
+ misses: number;
64
+ sets: number;
65
+ evictions: number;
66
+ expirations: number;
67
+ enabled: boolean;
68
+ };
69
+ negative_response_cache: {
70
+ entries: number;
71
+ max_entries: number;
72
+ ttl_ms: number;
73
+ hits: number;
74
+ misses: number;
75
+ sets: number;
76
+ evictions: number;
77
+ expirations: number;
45
78
  enabled: boolean;
46
79
  };
47
80
  in_flight_requests: number;
81
+ request_concurrency: {
82
+ limit: number;
83
+ active: number;
84
+ queued: number;
85
+ endpoint_limits: {
86
+ target_changes: {
87
+ limit: number;
88
+ active: number;
89
+ queued: number;
90
+ };
91
+ };
92
+ };
93
+ upstream_request_window: {
94
+ enforced: boolean;
95
+ limit: number;
96
+ window_ms: number;
97
+ used: number;
98
+ remaining: number | undefined;
99
+ resets_at: string | undefined;
100
+ };
101
+ upstream_rate_limit: {
102
+ observed: boolean;
103
+ bucket: string | undefined;
104
+ limit: number | undefined;
105
+ remaining: number | undefined;
106
+ reset_at: string | undefined;
107
+ blocked: boolean;
108
+ globally_blocked: boolean;
109
+ blocked_until: string | undefined;
110
+ observed_at: string | undefined;
111
+ last_status: number | undefined;
112
+ buckets: {
113
+ [k: string]: {
114
+ limit: number | undefined;
115
+ remaining: number | undefined;
116
+ reset_at: string | undefined;
117
+ blocked: boolean;
118
+ blocked_until: string | undefined;
119
+ observed_at: string | undefined;
120
+ last_status: number | undefined;
121
+ };
122
+ };
123
+ };
124
+ upstream_requests: {
125
+ actual_requests: number;
126
+ errors: number;
127
+ avg_ms: number;
128
+ max_ms: number;
129
+ by_endpoint: {
130
+ [k: string]: {
131
+ requests: number;
132
+ errors: number;
133
+ avg_ms: number;
134
+ max_ms: number;
135
+ last_ms: number;
136
+ statuses: {
137
+ [k: string]: number;
138
+ };
139
+ };
140
+ };
141
+ };
48
142
  };
49
143
  validateKey(): Promise<void>;
50
144
  listPrograms(query: Record<string, QueryValue>): Promise<ApiResult<unknown>>;
@@ -56,6 +150,18 @@ export declare class BBRadarClient {
56
150
  exportTargets(body: Record<string, unknown>): Promise<ApiResult<unknown>>;
57
151
  request<T = unknown>(options: ApiRequestOptions): Promise<ApiResult<T>>;
58
152
  private fetchFromApi;
153
+ private withRequestSlot;
154
+ private withGlobalRequestSlot;
155
+ private withEndpointRequestSlot;
156
+ private recordEndpointMetric;
157
+ private reserveUpstreamRequest;
158
+ private observeUpstreamRateLimit;
159
+ private rateLimitState;
160
+ private refreshUpstreamRequestWindow;
161
+ private upstreamRequestWindowDiagnostics;
162
+ private shouldEnforceSharedRequestWindow;
163
+ private refreshUpstreamRateLimitWindow;
164
+ private upstreamRateLimitDiagnostics;
59
165
  private buildUrl;
60
166
  private headers;
61
167
  }