@bbradar/mcp 0.1.7 → 0.1.8

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
@@ -29,8 +29,9 @@ const DEFAULT_FILTER_TARGET_SAMPLE_PROGRAMS = 10;
29
29
  const MAX_FILTER_TARGET_SAMPLE_PROGRAMS = 25;
30
30
  const MAX_LOCAL_EXPORT_RESOURCES = 25;
31
31
  const EXPORT_PREVIEW_LIMIT = 25;
32
- const STALE_PROGRAM_ID_RESOLVE_PAGES = 5;
33
- const STALE_PROGRAM_ID_RESOLVE_BUDGET = 5;
32
+ const STALE_PROGRAM_ID_RESOLVE_PAGES = 1;
33
+ const STALE_PROGRAM_ID_RESOLVE_BUDGET = 1;
34
+ const PROGRAM_FALLBACK_CACHE_MAX_ENTRIES = 200;
34
35
  const MAX_RESOLVE_SEARCH_QUERIES = 4;
35
36
  const SDK_VERSION = "1.29.0";
36
37
  const WEB3_TAGS = ["web3", "crypto", "blockchain", "smart-contract", "smart contract", "defi", "nft", "ethereum", "solana", "solidity"];
@@ -1488,7 +1489,19 @@ function registerResources(server, client, config, exportStore) {
1488
1489
  }));
1489
1490
  });
1490
1491
  }
1492
+ const programFallbackCache = new Map();
1493
+ const programTargetsFallbackCaches = new WeakMap();
1491
1494
  async function getProgramWithIdFallback(client, config, requestedProgramId) {
1495
+ const cachedFallback = getCachedProgramFallback(config, requestedProgramId);
1496
+ if (cachedFallback && !isProgramIndexFallback(cachedFallback)) {
1497
+ return {
1498
+ api: await client.getProgram(cachedFallback.resolvedProgramId),
1499
+ programId: cachedFallback.resolvedProgramId,
1500
+ sourceRequests: cachedFallback.sourceRequests,
1501
+ warnings: cachedFallback.warnings,
1502
+ fallback: cachedFallback
1503
+ };
1504
+ }
1492
1505
  try {
1493
1506
  return {
1494
1507
  api: await client.getProgram(requestedProgramId),
@@ -1505,6 +1518,7 @@ async function getProgramWithIdFallback(client, config, requestedProgramId) {
1505
1518
  if (!fallback) {
1506
1519
  throw error;
1507
1520
  }
1521
+ rememberProgramFallback(config, fallback);
1508
1522
  return {
1509
1523
  api: await client.getProgram(fallback.resolvedProgramId),
1510
1524
  programId: fallback.resolvedProgramId,
@@ -1515,6 +1529,30 @@ async function getProgramWithIdFallback(client, config, requestedProgramId) {
1515
1529
  }
1516
1530
  }
1517
1531
  async function runProgramIdTool(client, config, requestedProgramId, callback, indexFallbackCallback) {
1532
+ const cachedFallback = getCachedProgramFallback(config, requestedProgramId);
1533
+ if (cachedFallback) {
1534
+ if (isProgramIndexFallback(cachedFallback) && indexFallbackCallback) {
1535
+ const payload = await indexFallbackCallback(cachedFallback);
1536
+ return withProgramIndexFallbackMetadata(payload, cachedFallback);
1537
+ }
1538
+ try {
1539
+ const payload = await callback(cachedFallback.resolvedProgramId);
1540
+ return withProgramIdFallbackMetadata(payload, cachedFallback);
1541
+ }
1542
+ catch (cachedFallbackError) {
1543
+ if (!isProgramNotFoundError(cachedFallbackError) || !indexFallbackCallback) {
1544
+ throw cachedFallbackError;
1545
+ }
1546
+ const indexFallback = createProgramIndexFallbackFromStaleResolution(cachedFallback, requestedProgramId, cachedFallbackError) ??
1547
+ (await resolveIndexedProgramId(client, config, cachedFallback.resolvedProgramId, cachedFallbackError));
1548
+ if (!indexFallback) {
1549
+ throw cachedFallbackError;
1550
+ }
1551
+ rememberProgramFallback(config, indexFallback);
1552
+ const payload = await indexFallbackCallback(indexFallback);
1553
+ return withProgramIndexFallbackMetadata(payload, indexFallback);
1554
+ }
1555
+ }
1518
1556
  try {
1519
1557
  return await callback(requestedProgramId);
1520
1558
  }
@@ -1531,10 +1569,12 @@ async function runProgramIdTool(client, config, requestedProgramId, callback, in
1531
1569
  if (!indexFallback) {
1532
1570
  throw error;
1533
1571
  }
1572
+ rememberProgramFallback(config, indexFallback);
1534
1573
  const payload = await indexFallbackCallback(indexFallback);
1535
1574
  return withProgramIndexFallbackMetadata(payload, indexFallback);
1536
1575
  }
1537
1576
  try {
1577
+ rememberProgramFallback(config, fallback);
1538
1578
  const payload = await callback(fallback.resolvedProgramId);
1539
1579
  return withProgramIdFallbackMetadata(payload, fallback);
1540
1580
  }
@@ -1542,21 +1582,107 @@ async function runProgramIdTool(client, config, requestedProgramId, callback, in
1542
1582
  if (!isProgramNotFoundError(fallbackError) || !indexFallbackCallback) {
1543
1583
  throw fallbackError;
1544
1584
  }
1545
- const indexFallback = await resolveIndexedProgramId(client, config, fallback.resolvedProgramId, fallbackError);
1585
+ const indexFallback = createProgramIndexFallbackFromStaleResolution(fallback, requestedProgramId, fallbackError) ??
1586
+ (await resolveIndexedProgramId(client, config, fallback.resolvedProgramId, fallbackError));
1546
1587
  if (!indexFallback) {
1547
1588
  throw fallbackError;
1548
1589
  }
1549
- const combinedFallback = {
1550
- ...indexFallback,
1551
- requestedProgramId,
1552
- sourceRequests: [...fallback.sourceRequests, ...indexFallback.sourceRequests],
1553
- warnings: uniqueStrings([...fallback.warnings, ...indexFallback.warnings])
1554
- };
1590
+ const combinedFallback = indexFallback.requestedProgramId === requestedProgramId
1591
+ ? indexFallback
1592
+ : {
1593
+ ...indexFallback,
1594
+ requestedProgramId,
1595
+ sourceRequests: [...fallback.sourceRequests, ...indexFallback.sourceRequests],
1596
+ warnings: uniqueStrings([...fallback.warnings, ...indexFallback.warnings])
1597
+ };
1598
+ rememberProgramFallback(config, combinedFallback);
1555
1599
  const payload = await indexFallbackCallback(combinedFallback);
1556
1600
  return withProgramIndexFallbackMetadata(payload, combinedFallback);
1557
1601
  }
1558
1602
  }
1559
1603
  }
1604
+ function getCachedProgramFallback(config, requestedProgramId) {
1605
+ if (config.cacheTtlMs <= 0 || config.cacheMaxEntries <= 0) {
1606
+ return undefined;
1607
+ }
1608
+ const key = programFallbackCacheKey(config, requestedProgramId);
1609
+ const cached = programFallbackCache.get(key);
1610
+ if (!cached) {
1611
+ return undefined;
1612
+ }
1613
+ if (cached.expiresAt <= Date.now()) {
1614
+ programFallbackCache.delete(key);
1615
+ return undefined;
1616
+ }
1617
+ return cloneProgramFallbackForRequest(cached.fallback, requestedProgramId, true);
1618
+ }
1619
+ function rememberProgramFallback(config, fallback) {
1620
+ if (config.cacheTtlMs <= 0 || config.cacheMaxEntries <= 0) {
1621
+ return;
1622
+ }
1623
+ pruneProgramFallbackCache();
1624
+ const key = programFallbackCacheKey(config, fallback.requestedProgramId);
1625
+ programFallbackCache.set(key, {
1626
+ expiresAt: Date.now() + config.cacheTtlMs,
1627
+ fallback: cloneProgramFallbackForRequest(fallback, fallback.requestedProgramId, false)
1628
+ });
1629
+ if (programFallbackCache.size > PROGRAM_FALLBACK_CACHE_MAX_ENTRIES) {
1630
+ const oldestKey = programFallbackCache.keys().next().value;
1631
+ if (oldestKey) {
1632
+ programFallbackCache.delete(oldestKey);
1633
+ }
1634
+ }
1635
+ }
1636
+ function pruneProgramFallbackCache() {
1637
+ const now = Date.now();
1638
+ for (const [key, cached] of programFallbackCache) {
1639
+ if (cached.expiresAt <= now) {
1640
+ programFallbackCache.delete(key);
1641
+ }
1642
+ }
1643
+ }
1644
+ function programFallbackCacheKey(config, requestedProgramId) {
1645
+ return `${config.apiBaseUrl}\n${normalizeTag(requestedProgramId)}`;
1646
+ }
1647
+ function cloneProgramFallbackForRequest(fallback, requestedProgramId, fromCache) {
1648
+ const warnings = fromCache
1649
+ ? uniqueStrings([...fallback.warnings, `Reused cached program_id resolution for ${requestedProgramId}.`])
1650
+ : [...fallback.warnings];
1651
+ const cloned = {
1652
+ ...fallback,
1653
+ requestedProgramId,
1654
+ warnings,
1655
+ sourceRequests: [...fallback.sourceRequests],
1656
+ resolution: { ...fallback.resolution }
1657
+ };
1658
+ if (isProgramIndexFallback(fallback)) {
1659
+ const indexClone = {
1660
+ ...cloned,
1661
+ program: { ...fallback.program }
1662
+ };
1663
+ return indexClone;
1664
+ }
1665
+ return cloned;
1666
+ }
1667
+ function isProgramIndexFallback(fallback) {
1668
+ return readObject(fallback.program) !== undefined;
1669
+ }
1670
+ function createProgramIndexFallbackFromStaleResolution(fallback, requestedProgramId, detailError) {
1671
+ const match = selectProgramIndexFallbackMatch(fallback.resolvedProgramId, fallback.resolution);
1672
+ if (!match) {
1673
+ return undefined;
1674
+ }
1675
+ const detailApiError = detailError instanceof BBRadarApiError ? detailError : undefined;
1676
+ const indexWarning = `program_id ${fallback.resolvedProgramId} exists in the BBRadar program index, but the per-program detail endpoint returned 404.`;
1677
+ return {
1678
+ ...fallback,
1679
+ requestedProgramId,
1680
+ staleRequestId: fallback.staleRequestId ?? detailApiError?.requestId,
1681
+ staleUpstreamRequestId: fallback.staleUpstreamRequestId ?? detailApiError?.upstreamRequestId,
1682
+ warnings: uniqueStrings([...fallback.warnings, indexWarning]),
1683
+ program: match.program
1684
+ };
1685
+ }
1560
1686
  async function resolveIndexedProgramId(client, config, requestedProgramId, staleError) {
1561
1687
  const query = programSearchText(requestedProgramId);
1562
1688
  if (query.length < 2) {
@@ -2862,6 +2988,10 @@ async function getProgramTargetsExportFallbackPayload(client, input, programId)
2862
2988
  });
2863
2989
  }
2864
2990
  async function fetchProgramTargetsWithEndpointFallback(client, programId) {
2991
+ const cachedLookup = getCachedProgramTargetsFallback(client, programId);
2992
+ if (cachedLookup) {
2993
+ return cachedLookup;
2994
+ }
2865
2995
  try {
2866
2996
  const api = await client.getProgramTargets(programId);
2867
2997
  const data = readObject(api.data);
@@ -2894,9 +3024,86 @@ async function fetchProgramTargetsWithEndpointFallback(client, programId) {
2894
3024
  if (!isProgramNotFoundError(error)) {
2895
3025
  throw error;
2896
3026
  }
2897
- return fetchProgramTargetsFromExportFallback(client, programId);
3027
+ const lookup = await fetchProgramTargetsFromExportFallback(client, programId);
3028
+ rememberProgramTargetsFallback(client, programId, lookup);
3029
+ return lookup;
3030
+ }
3031
+ }
3032
+ function getCachedProgramTargetsFallback(client, programId) {
3033
+ const diagnostics = client.diagnostics();
3034
+ if (!diagnostics.response_cache.enabled) {
3035
+ return undefined;
3036
+ }
3037
+ const cache = programTargetsFallbackCaches.get(client);
3038
+ const key = programTargetsFallbackCacheKey(programId);
3039
+ const cached = cache?.get(key);
3040
+ if (!cached) {
3041
+ return undefined;
3042
+ }
3043
+ if (cached.expiresAt <= Date.now()) {
3044
+ cache?.delete(key);
3045
+ return undefined;
3046
+ }
3047
+ return {
3048
+ ...cached.lookup,
3049
+ cache: readObject(stripUndefined({
3050
+ ...(cached.lookup.cache ?? {}),
3051
+ hit: true,
3052
+ expires_at: new Date(cached.expiresAt).toISOString()
3053
+ })),
3054
+ sourceRequests: cached.lookup.sourceRequests.map((request) => ({
3055
+ ...request,
3056
+ cache_hit: true,
3057
+ cache_expires_at: new Date(cached.expiresAt).toISOString()
3058
+ })),
3059
+ warnings: uniqueStrings([...cached.lookup.warnings, `Reused cached target fallback for ${programId}.`])
3060
+ };
3061
+ }
3062
+ function rememberProgramTargetsFallback(client, programId, lookup) {
3063
+ const diagnostics = client.diagnostics();
3064
+ if (!diagnostics.response_cache.enabled || lookup.unavailable) {
3065
+ return;
3066
+ }
3067
+ const cache = programTargetsFallbackCacheForClient(client);
3068
+ pruneProgramTargetsFallbackCache(cache);
3069
+ const ttlMs = cacheTtlMsFromDiagnostics(diagnostics.response_cache);
3070
+ if (ttlMs <= 0) {
3071
+ return;
3072
+ }
3073
+ cache.set(programTargetsFallbackCacheKey(programId), {
3074
+ expiresAt: Date.now() + ttlMs,
3075
+ lookup
3076
+ });
3077
+ if (cache.size > PROGRAM_FALLBACK_CACHE_MAX_ENTRIES) {
3078
+ const oldestKey = cache.keys().next().value;
3079
+ if (oldestKey) {
3080
+ cache.delete(oldestKey);
3081
+ }
3082
+ }
3083
+ }
3084
+ function programTargetsFallbackCacheForClient(client) {
3085
+ const existing = programTargetsFallbackCaches.get(client);
3086
+ if (existing) {
3087
+ return existing;
3088
+ }
3089
+ const cache = new Map();
3090
+ programTargetsFallbackCaches.set(client, cache);
3091
+ return cache;
3092
+ }
3093
+ function pruneProgramTargetsFallbackCache(cache) {
3094
+ const now = Date.now();
3095
+ for (const [key, cached] of cache) {
3096
+ if (cached.expiresAt <= now) {
3097
+ cache.delete(key);
3098
+ }
2898
3099
  }
2899
3100
  }
3101
+ function programTargetsFallbackCacheKey(programId) {
3102
+ return normalizeTag(programId);
3103
+ }
3104
+ function cacheTtlMsFromDiagnostics(cache) {
3105
+ return cache.enabled ? cache.ttl_ms : 0;
3106
+ }
2900
3107
  async function fetchProgramTargetsFromExportFallback(client, programId) {
2901
3108
  try {
2902
3109
  const api = await client.exportTargets({