@bbradar/mcp 0.1.20 → 0.2.0

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
@@ -1,6 +1,6 @@
1
1
  # @bbradar/mcp
2
2
 
3
- Local STDIO MCP server for [bbradar.io](https://bbradar.io) Pro subscribers. It lets MCP clients query [bbradar.io](https://bbradar.io) Pro program, target, opportunity, and recent-change data through a local npm package.
3
+ Local STDIO MCP server for [bbradar.io](https://bbradar.io) Pro subscribers. It lets MCP clients query [bbradar.io](https://bbradar.io) platform and Self-Hosted program profiles, active targets, target-level opportunity intelligence, recent target changes, and repository-change events through a local npm package.
4
4
 
5
5
  ## Requirements
6
6
 
@@ -106,15 +106,32 @@ claude mcp add bbradar_io --scope user --env BBRADAR_API_KEY="your_pro_mcp_api_k
106
106
 
107
107
  After adding the MCP config, restart your client and call `get_mcp_status`. A working setup returns server status, timing, and rate-limit metadata.
108
108
 
109
+ Every server process negotiates once against `GET /api/v1/pro/capabilities` and retains the contract only in memory. `get_mcp_status` reports the negotiated version and endpoint set. Negotiation is best-effort by default; when startup validation is enabled, authentication or entitlement failures stop startup. Older API deployments without the capability route fall back to the program-list validation probe.
110
+
109
111
  For simple tool selection, call `get_bbradar_guide`. It returns the recommended entry points, common filter meanings, and example calls without contacting the BBRadar API.
110
112
 
111
113
  Most tools that accept `program_id` also accept a human program name or handle. The server resolves names automatically when there is one clear match; for ambiguous names, call `resolve_program` first and retry with the returned `platform:handle` id.
112
114
 
115
+ Self-Hosted programs use IDs such as `Self-Hosted:program-slug`. They are available through program search, exact program detail, and program-target tools. Their `data_capabilities` field identifies unavailable source signals; the MCP server does not invent opportunity scores, public report counts, target-change history, submission costs, or repository changes for that catalog.
116
+
117
+ `search_programs` accepts a `search` value and sends it to the API for indexed server-side filtering. Name resolution uses the same path automatically, avoiding broad page scans for exact or partial program names.
118
+
113
119
  `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`.
120
+ It uses the Pro API's direct reverse-target-search route first, including for Self-Hosted targets, and only falls back to sampled target lists when an older API deployment does not expose that route.
121
+
122
+ `export_targets` uses the bounded bearer `POST /api/v1/pro/targets/export` route directly. JSON exports preserve `program_id` on every target; CSV previews are capped in the tool result while the complete payload remains available for ten minutes through the returned `bbradar://exports` resource. The local resource store is capped at 25 entries and 64 MiB in aggregate, and CSV formula/control-byte neutralization is applied independently of the API.
123
+
124
+ `get_program_targets` forwards text, target type, eligibility, minimum Target Opportunity Score, Target Opportunity Label, and competition-risk filters to the API. Compact output retains the score, label, competition risk, and Why This Target; full output also includes the score breakdown and reason fields.
125
+
126
+ `get_repository_changes` exposes the read-only repository event feed. It defaults to `include_targets=false` and compact output, never contains source code or diffs, and supports `after_id` cursor polling. Both change-feed tools keep at most 500 cursors keyed separately by endpoint and normalized filters. Seed a stream once with `after_id`, then use `resume_cursor=true`; the server stores `next_after_id` even for an empty page. Cursor reads continue sequentially while `has_more=true`, bounded by `max_cursor_pages` (default `2`) and `cursor_time_budget_ms` (default `3000`). Cursor requests bypass the response cache so an empty page cannot hide newly arriving changes.
127
+
128
+ Cursors are memory-only by default. Set `BBRADAR_MCP_CURSOR_STATE_PATH` to opt into restart-safe cursor persistence. The state file contains only endpoint-prefixed SHA-256 filter hashes and non-negative numeric IDs—never raw filters, API responses, program or target records, credentials, or authorization headers.
129
+
130
+ Large target and change collections return at most 50 inline records by default. The remainder is held in a bounded, ten-minute in-memory continuation and exposed through a `bbradar://continuations` resource link. This keeps model context small without losing access to the complete bounded result.
114
131
 
115
132
  ## Performance Tuning
116
133
 
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.
134
+ The defaults expose the 16-tool core surface and compact text results while enabling bounded concurrency, positive response caching, in-flight request coalescing, stale-while-revalidate program metadata, endpoint circuit breakers, and short negative caching for repeated 404/405 lookups. Set the tool profile to `full` when all specialized tools are needed.
118
135
 
119
136
  | Environment variable | Default | Purpose |
120
137
  | --- | ---: | --- |
@@ -123,14 +140,31 @@ The defaults expose the complete tool surface and full text results while enabli
123
140
  | `BBRADAR_MCP_CACHE_MAX_ENTRIES` | `500` | Maximum entries in each local response cache. |
124
141
  | `BBRADAR_MCP_MAX_CONCURRENT_REQUESTS` | `6` | Maximum simultaneous upstream BBRadar API requests. |
125
142
  | `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. |
143
+ | `BBRADAR_MCP_COMPACT_TOOL_TEXT` | `true` | Avoid duplicating full results for clients that consume MCP `structuredContent`. Set to `false` for legacy text-only clients. |
144
+ | `BBRADAR_MCP_TOOL_PROFILE` | `core` | Compact 16-tool discovery surface. Set to `full` for every specialized tool. |
145
+ | `BBRADAR_MCP_PROGRAM_STALE_WHILE_REVALIDATE_SECONDS` | `300` | Maximum stale window for exact program metadata while one background refresh runs. |
146
+ | `BBRADAR_MCP_CIRCUIT_BREAKER_FAILURE_THRESHOLD` | `3` | Consecutive retryable endpoint failures before its circuit opens. |
147
+ | `BBRADAR_MCP_CIRCUIT_BREAKER_COOLDOWN_SECONDS` | `30` | Endpoint circuit cooldown before a new request is admitted. |
148
+ | `BBRADAR_MCP_CURSOR_STATE_PATH` | unset | Optional path for numeric cursor-only restart persistence. API responses are never written there. |
149
+ | `BBRADAR_MCP_CONTINUATION_INLINE_LIMIT` | `50` | Maximum records kept inline before a local continuation resource is returned. |
150
+ | `BBRADAR_MCP_CONTINUATION_TTL_SECONDS` | `600` | Lifetime of in-memory continuation results. |
151
+ | `BBRADAR_MCP_ALLOW_INSECURE_HTTP` | `false` | Explicitly allow an HTTP API base URL outside loopback for a trusted private development network. Never enable for internet traffic. |
152
+
153
+ No performance variables are required. Capability-derived page limits, caching, request coalescing, server-side name resolution, reverse-target search, early stopping, bounded concurrency, aggregate-call parallelism, a 64 KiB request-body ceiling, a 16 MiB upstream response ceiling, automatic HTTP compression handling, connection reuse, and upstream quota protection are always active. Unsupported capability-gated tools are removed from discovery. Target responses expose upstream pagination state so an AI client does not mistake a capped page for a complete inventory.
154
+
155
+ 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. Target and repository changes share one quota state and one conservative two-request concurrency pool. A rolling 60-second guard also enforces the documented shared key-and-IP ceiling across uncached upstream requests. Real `423`, `429`, and `503` responses honor `Retry-After` as a local cooldown, capped at ten minutes so a malformed upstream header cannot lock the MCP process indefinitely. Requests are not automatically retried, so one MCP action cannot amplify an admission or rate-limit response into more API calls. The global upstream concurrency default is six and bulk exports are capped at two; cursor page walking is strictly sequential.
156
+
157
+ The bearer API destination must use HTTPS. Loopback HTTP remains available for local development; non-loopback HTTP requires the explicit `BBRADAR_MCP_ALLOW_INSECURE_HTTP=true` opt-in. API base URLs containing embedded credentials, query strings, fragments, or non-HTTP schemes are rejected before the key can be sent.
158
+
159
+ Call `get_mcp_status` to inspect cache and stale-hit rates, circuit states, cursor persistence, concurrency, negotiated capabilities, upstream quotas, actual request counts, per-endpoint status totals, and timing.
160
+
161
+ Run `npm run benchmark` for the deterministic local performance benchmark. It reports request and byte reductions for reverse target discovery, exact-program cache efficiency, and context reduction from bounded continuations.
128
162
 
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.
163
+ ### Flexible multi-signal discovery
130
164
 
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.
165
+ Use `find_programs` as the generic ranked path for combinations of platform, reward, opportunity, scope breadth, technology, competition, and freshness signals. It supports both inclusion and exclusion through `platforms`, `exclude_platforms`, `web3`, `exclude_web3`, and the remaining scope/reward/activity filters. When a user describes an exclusion without naming exact platform values, call `list_filters` first and reuse the observed values. If an intentionally strict combination returns no candidates, relax one local threshold at a time; the upstream program pages are cached, so these fallback passes normally add no API requests.
132
166
 
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.
167
+ For repository-aware hunting, call `get_repository_changes` and `find_programs` in parallel with the same requested platform set. The repository feed supplies recent events and scored targets while `find_programs` supplies reward and ranking data. Intersect their program ids, discard non-requested programs that merely share the same repository, and treat targets as event-level associations when the feed omits `program_id`. This remains generic across platforms and technology stacks and avoids a specialized tool for every prompt variation.
134
168
 
135
169
  ### Optimized wildcard discovery
136
170
 
@@ -149,7 +183,7 @@ For the latest added wildcard targets from one platform with reward and report-c
149
183
 
150
184
  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
185
 
152
- ## Tools
186
+ ## Full-profile tools
153
187
 
154
188
  - `search_programs`
155
189
  - `get_program`
@@ -157,6 +191,7 @@ This path uses one recent-changes request. Use `find_wildcard_programs` for a ra
157
191
  - `run_program_name_action`
158
192
  - `get_program_targets`
159
193
  - `get_recent_changes`
194
+ - `get_repository_changes`
160
195
  - `get_opportunities`
161
196
  - `find_programs`
162
197
  - `find_wildcard_programs`
@@ -0,0 +1,167 @@
1
+ import http from "node:http";
2
+ import { performance } from "node:perf_hooks";
3
+
4
+ import { BBRadarClient } from "../dist/bbradarClient.js";
5
+
6
+ const programCount = 100;
7
+ const targetsPerProgram = 20;
8
+ const programs = Array.from({ length: programCount }, (_, index) => ({
9
+ id: `Platform:program-${index}`,
10
+ name: `Program ${index}`,
11
+ platform: "Platform"
12
+ }));
13
+ const targets = new Map(programs.map((program, programIndex) => [
14
+ program.id,
15
+ Array.from({ length: targetsPerProgram }, (_, targetIndex) => ({
16
+ identifier: `api-${targetIndex}.program-${programIndex}.example`,
17
+ target_type: "domain",
18
+ in_scope: true,
19
+ eligible_for_bounty: true
20
+ }))
21
+ ]));
22
+ const counters = { requests: 0, responseBytes: 0 };
23
+
24
+ const api = http.createServer((req, res) => {
25
+ counters.requests += 1;
26
+ const url = new URL(req.url ?? "/", "http://127.0.0.1");
27
+ let payload;
28
+
29
+ if (url.pathname === "/api/v1/pro/programs") {
30
+ payload = { programs, meta: { total: programs.length, total_pages: 1 } };
31
+ } else if (url.pathname === "/api/v1/pro/targets/search") {
32
+ payload = {
33
+ results: [{ program: programs[42], target: targets.get(programs[42].id)[0] }],
34
+ meta: { total: 1, returned: 1 }
35
+ };
36
+ } else if (url.pathname === "/api/v1/pro/programs/Platform:program-42") {
37
+ payload = programs[42];
38
+ } else {
39
+ const match = url.pathname.match(/^\/api\/v1\/pro\/programs\/(.+)\/targets$/);
40
+ payload = match
41
+ ? { targets: targets.get(decodeURIComponent(match[1])) ?? [], meta: { total: targetsPerProgram } }
42
+ : { detail: "not found" };
43
+ if (!match) {
44
+ res.statusCode = 404;
45
+ }
46
+ }
47
+
48
+ const body = JSON.stringify(payload);
49
+ counters.responseBytes += Buffer.byteLength(body);
50
+ res.setHeader("content-type", "application/json");
51
+ res.end(body);
52
+ });
53
+
54
+ await new Promise((resolve) => api.listen(0, "127.0.0.1", resolve));
55
+ const { port } = api.address();
56
+ const baseUrl = `http://127.0.0.1:${port}/api/v1`;
57
+ const headers = { authorization: "Bearer benchmark" };
58
+
59
+ try {
60
+ resetCounters();
61
+ const baselineStarted = performance.now();
62
+ const listed = await getJson(`${baseUrl}/pro/programs`, headers);
63
+ let baselineMatch;
64
+ for (const program of listed.programs) {
65
+ const response = await getJson(`${baseUrl}/pro/programs/${encodeURIComponent(program.id)}/targets`, headers);
66
+ baselineMatch = response.targets.find((target) => target.identifier === "api-0.program-42.example") ?? baselineMatch;
67
+ }
68
+ const baseline = snapshot(performance.now() - baselineStarted);
69
+
70
+ resetCounters();
71
+ const optimizedStarted = performance.now();
72
+ const optimizedMatch = await getJson(
73
+ `${baseUrl}/pro/targets/search?q=api-0.program-42.example&match_mode=exact&page=1&page_size=1`,
74
+ headers
75
+ );
76
+ const optimized = snapshot(performance.now() - optimizedStarted);
77
+
78
+ const client = new BBRadarClient({
79
+ apiKey: "benchmark",
80
+ apiBaseUrl: baseUrl,
81
+ webBaseUrl: "https://bbradar.io",
82
+ cacheTtlMs: 30_000,
83
+ negativeCacheTtlMs: 15_000,
84
+ cacheMaxEntries: 500,
85
+ defaultRateLimitPerMinute: 60,
86
+ exportRateLimitPerMinute: 15,
87
+ maxConcurrentRequests: 6,
88
+ compactToolText: true,
89
+ toolProfile: "core",
90
+ requestTimeoutMs: 5_000,
91
+ validateOnStartup: false
92
+ });
93
+ resetCounters();
94
+ const cacheStarted = performance.now();
95
+ for (let index = 0; index < 50; index += 1) {
96
+ await client.getProgram("Platform:program-42");
97
+ }
98
+ const cachedMetadata = snapshot(performance.now() - cacheStarted);
99
+
100
+ const largeResult = Array.from({ length: 500 }, (_, index) => ({
101
+ identifier: `target-${index}.example`,
102
+ target_opportunity_score: 70 + (index % 20),
103
+ why_this_target: "Compact benchmark fixture"
104
+ }));
105
+ const fullContextBytes = Buffer.byteLength(JSON.stringify(largeResult));
106
+ const inlineContextBytes = Buffer.byteLength(JSON.stringify(largeResult.slice(0, 50)));
107
+ const requestReduction = percentReduction(baseline.requests, optimized.requests);
108
+ const byteReduction = percentReduction(baseline.response_bytes, optimized.response_bytes);
109
+ const contextReduction = percentReduction(fullContextBytes, inlineContextBytes);
110
+
111
+ if (
112
+ requestReduction < 90 ||
113
+ byteReduction < 90 ||
114
+ cachedMetadata.requests !== 1 ||
115
+ contextReduction < 80
116
+ ) {
117
+ throw new Error("Performance regression threshold failed.");
118
+ }
119
+
120
+ process.stdout.write(`${JSON.stringify({
121
+ fixture: { program_count: programCount, targets_per_program: targetsPerProgram },
122
+ reverse_target_discovery: {
123
+ baseline,
124
+ optimized,
125
+ request_reduction_percent: requestReduction,
126
+ byte_reduction_percent: byteReduction,
127
+ match_found: Boolean(baselineMatch && optimizedMatch.results?.length === 1)
128
+ },
129
+ repeated_exact_program_metadata: {
130
+ calls: 50,
131
+ upstream: cachedMetadata,
132
+ cache_hit_ratio: client.diagnostics().response_cache.hits / 50
133
+ },
134
+ bounded_model_context: {
135
+ full_500_record_bytes: fullContextBytes,
136
+ inline_50_record_bytes: inlineContextBytes,
137
+ byte_reduction_percent: contextReduction
138
+ }
139
+ }, null, 2)}\n`);
140
+ } finally {
141
+ api.close();
142
+ }
143
+
144
+ async function getJson(url, requestHeaders) {
145
+ const response = await fetch(url, { headers: requestHeaders });
146
+ if (!response.ok) {
147
+ throw new Error(`Benchmark request failed: ${response.status}`);
148
+ }
149
+ return response.json();
150
+ }
151
+
152
+ function resetCounters() {
153
+ counters.requests = 0;
154
+ counters.responseBytes = 0;
155
+ }
156
+
157
+ function snapshot(durationMs) {
158
+ return {
159
+ requests: counters.requests,
160
+ response_bytes: counters.responseBytes,
161
+ duration_ms: Math.round(durationMs * 100) / 100
162
+ };
163
+ }
164
+
165
+ function percentReduction(before, after) {
166
+ return before === 0 ? 0 : Math.round((1 - after / before) * 10_000) / 100;
167
+ }
@@ -7,6 +7,7 @@ export type ApiRequestOptions = {
7
7
  body?: unknown;
8
8
  cache?: boolean;
9
9
  coalesce?: boolean;
10
+ staleWhileRevalidateMs?: number;
10
11
  };
11
12
  export type ApiResult<T> = {
12
13
  data: T;
@@ -17,6 +18,13 @@ export type ApiResult<T> = {
17
18
  status: number;
18
19
  fetchedAt: string;
19
20
  cacheExpiresAt?: string | undefined;
21
+ stale?: boolean | undefined;
22
+ };
23
+ type CapabilityNegotiationState = {
24
+ status: "not_started" | "negotiated" | "legacy" | "failed";
25
+ negotiatedAt?: string | undefined;
26
+ contract?: unknown;
27
+ error?: string | undefined;
20
28
  };
21
29
  export declare class BBRadarApiError extends Error {
22
30
  readonly requestId: string;
@@ -50,9 +58,12 @@ export declare class BBRadarClient {
50
58
  private readonly endpointRequestQueues;
51
59
  private readonly endpointActiveRequests;
52
60
  private readonly upstreamRateLimits;
61
+ private readonly circuitBreakers;
53
62
  private readonly upstreamRequestTimestampsMs;
54
63
  private activeRequests;
55
64
  private globalBlockedUntilMs;
65
+ private capabilityNegotiation;
66
+ private capabilityState;
56
67
  constructor(config: ServerConfig);
57
68
  diagnostics(): {
58
69
  response_cache: {
@@ -64,6 +75,7 @@ export declare class BBRadarClient {
64
75
  sets: number;
65
76
  evictions: number;
66
77
  expirations: number;
78
+ stale_hits: number;
67
79
  enabled: boolean;
68
80
  };
69
81
  negative_response_cache: {
@@ -75,15 +87,36 @@ export declare class BBRadarClient {
75
87
  sets: number;
76
88
  evictions: number;
77
89
  expirations: number;
90
+ stale_hits: number;
78
91
  enabled: boolean;
79
92
  };
93
+ capability_negotiation: CapabilityNegotiationState;
94
+ circuit_breakers: {
95
+ failure_threshold: number;
96
+ cooldown_ms: number;
97
+ by_endpoint: {
98
+ [k: string]: {
99
+ state: string;
100
+ consecutive_failures: number;
101
+ open_until: string | undefined;
102
+ last_failure_at: string | undefined;
103
+ last_success_at: string | undefined;
104
+ last_status: number | undefined;
105
+ };
106
+ };
107
+ };
80
108
  in_flight_requests: number;
81
109
  request_concurrency: {
82
110
  limit: number;
83
111
  active: number;
84
112
  queued: number;
85
113
  endpoint_limits: {
86
- target_changes: {
114
+ change_feeds: {
115
+ limit: number;
116
+ active: number;
117
+ queued: number;
118
+ };
119
+ target_export: {
87
120
  limit: number;
88
121
  active: number;
89
122
  queued: number;
@@ -141,27 +174,43 @@ export declare class BBRadarClient {
141
174
  };
142
175
  };
143
176
  validateKey(): Promise<void>;
177
+ negotiateCapabilities(): Promise<void>;
178
+ capabilitySnapshot(): CapabilityNegotiationState;
179
+ supportsEndpoint(endpoint: string): boolean;
180
+ endpointCapability(endpoint: string): Record<string, unknown> | undefined;
181
+ endpointNumber(endpoint: string, field: string, fallback: number): number;
182
+ endpointStringArray(endpoint: string, field: string, fallback: readonly string[]): string[];
183
+ private fetchCapabilityContract;
144
184
  listPrograms(query: Record<string, QueryValue>): Promise<ApiResult<unknown>>;
185
+ getCapabilities(options?: Pick<ApiRequestOptions, "cache" | "coalesce">): Promise<ApiResult<unknown>>;
145
186
  getProgram(programId: string): Promise<ApiResult<unknown>>;
146
- getProgramTargets(programId: string): Promise<ApiResult<unknown>>;
187
+ getProgramTargets(programId: string, query?: Record<string, QueryValue>): Promise<ApiResult<unknown>>;
147
188
  getRecentChanges(query: Record<string, QueryValue>): Promise<ApiResult<unknown>>;
189
+ getRepositoryChanges(query: Record<string, QueryValue>): Promise<ApiResult<unknown>>;
148
190
  searchTargets(query: Record<string, QueryValue>): Promise<ApiResult<unknown>>;
149
191
  getOpportunities(level: string, query: Record<string, QueryValue>): Promise<ApiResult<unknown>>;
150
192
  exportTargets(body: Record<string, unknown>): Promise<ApiResult<unknown>>;
151
193
  request<T = unknown>(options: ApiRequestOptions): Promise<ApiResult<T>>;
152
194
  private fetchFromApi;
195
+ private refreshInBackground;
153
196
  private withRequestSlot;
154
197
  private withGlobalRequestSlot;
155
198
  private withEndpointRequestSlot;
156
199
  private recordEndpointMetric;
157
200
  private reserveUpstreamRequest;
158
201
  private observeUpstreamRateLimit;
202
+ private observeUpstreamErrorBody;
159
203
  private rateLimitState;
160
204
  private refreshUpstreamRequestWindow;
161
205
  private upstreamRequestWindowDiagnostics;
162
206
  private shouldEnforceSharedRequestWindow;
163
207
  private refreshUpstreamRateLimitWindow;
164
208
  private upstreamRateLimitDiagnostics;
209
+ private assertCircuitClosed;
210
+ private observeCircuitResponse;
211
+ private observeCircuitFailure;
212
+ private circuitBreakerDiagnostics;
165
213
  private buildUrl;
166
214
  private headers;
167
215
  }
216
+ export {};