@cachly-dev/mcp-server 0.9.7 → 0.9.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/index.js CHANGED
@@ -47,7 +47,7 @@ import { Redis } from 'ioredis';
47
47
  const API_URL = process.env.CACHLY_API_URL ?? 'https://api.cachly.dev';
48
48
  let JWT = process.env.CACHLY_JWT ?? '';
49
49
  const EMBED_MODEL = process.env.CACHLY_EMBED_MODEL ?? '';
50
- const CURRENT_VERSION = '0.9.7';
50
+ const CURRENT_VERSION = '0.9.8';
51
51
  // ── Default Instance Resolution (for Smithery & single-credential setups) ────
52
52
  // When CACHLY_BRAIN_INSTANCE_ID is set, tools can omit the instance_id parameter.
53
53
  // When neither is set, we auto-fetch the first running instance once per process.
@@ -2005,18 +2005,20 @@ function recencyBoost(timestampMs) {
2005
2005
  * └─────────────────────────────────────────────────────────────────┘
2006
2006
  */
2007
2007
  async function keywordSearch(redis, patterns, query, topK = 10) {
2008
- // ── Step 1: Collect and tokenize all documents ──
2009
- const allKeys = [];
2010
- for (const pattern of patterns) {
2011
- const stream = redis.scanStream({ match: pattern, count: 200 });
2012
- await new Promise((resolve, reject) => {
2008
+ // ── Step 1: Collect all matching keys — patterns scanned in parallel ──
2009
+ const keyBatches = await Promise.all(patterns.map(pattern => {
2010
+ const keys = [];
2011
+ const stream = redis.scanStream({ match: pattern, count: 500 });
2012
+ return new Promise((resolve, reject) => {
2013
2013
  stream.on('data', (batch) => {
2014
- allKeys.push(...batch.filter((k) => !k.endsWith(':meta')));
2014
+ keys.push(...batch.filter((k) => !k.endsWith(':meta')));
2015
2015
  });
2016
- stream.on('end', resolve);
2016
+ stream.on('end', () => resolve(keys));
2017
2017
  stream.on('error', reject);
2018
2018
  });
2019
- }
2019
+ }));
2020
+ // Deduplicate across patterns (a key may match multiple globs)
2021
+ const allKeys = [...new Set(keyBatches.flat())];
2020
2022
  if (allKeys.length === 0)
2021
2023
  return [];
2022
2024
  // Pipeline GET for speed
@@ -2269,12 +2271,32 @@ async function getConnection(instance_id) {
2269
2271
  return client;
2270
2272
  }
2271
2273
  // ── API helper ────────────────────────────────────────────────────────────────
2274
+ function jwtExpiryMs(token) {
2275
+ try {
2276
+ const parts = token.split('.');
2277
+ if (parts.length !== 3)
2278
+ return null;
2279
+ const payload = JSON.parse(Buffer.from(parts[1], 'base64url').toString('utf-8'));
2280
+ return payload.exp ? payload.exp * 1000 : null;
2281
+ }
2282
+ catch {
2283
+ return null;
2284
+ }
2285
+ }
2272
2286
  async function apiFetch(path, options = {}) {
2273
2287
  if (!JWT) {
2274
- throw new McpError(ErrorCode.InvalidRequest, 'CACHLY_JWT env var not set. Get your API token from https://cachly.dev/settings');
2288
+ throw new McpError(ErrorCode.InvalidRequest, 'CACHLY_JWT env var not set.\n\nGet your token at https://cachly.dev/setup-ai and add it to your MCP config:\n CACHLY_JWT=<your-token>');
2275
2289
  }
2290
+ // Detect token expiry before making the network call — gives a clearer error
2291
+ const expMs = jwtExpiryMs(JWT);
2292
+ if (expMs !== null && expMs < Date.now()) {
2293
+ const expiredAt = new Date(expMs).toISOString();
2294
+ throw new McpError(ErrorCode.InvalidRequest, `CACHLY_JWT expired at ${expiredAt}.\n\nGet a fresh token at https://cachly.dev/setup-ai and update CACHLY_JWT in your MCP config.`);
2295
+ }
2296
+ const timeoutMs = Number(process.env.CACHLY_API_TIMEOUT_MS ?? 15_000);
2276
2297
  const res = await fetch(`${API_URL}${path}`, {
2277
2298
  ...options,
2299
+ signal: AbortSignal.timeout(timeoutMs),
2278
2300
  headers: {
2279
2301
  'Content-Type': 'application/json',
2280
2302
  Authorization: `Bearer ${JWT}`,
@@ -2283,7 +2305,14 @@ async function apiFetch(path, options = {}) {
2283
2305
  });
2284
2306
  if (!res.ok) {
2285
2307
  const body = await res.json().catch(() => ({ error: res.statusText }));
2286
- throw new McpError(ErrorCode.InternalError, `cachly API error ${res.status}: ${body.error ?? res.statusText}`);
2308
+ const detail = body.error ?? res.statusText;
2309
+ if (res.status === 401) {
2310
+ throw new McpError(ErrorCode.InvalidRequest, `Authentication failed (401): ${detail}\n\nYour CACHLY_JWT may be expired or invalid. Get a fresh token at https://cachly.dev/setup-ai`);
2311
+ }
2312
+ if (res.status === 403) {
2313
+ throw new McpError(ErrorCode.InvalidRequest, `Access denied (403): ${detail}\n\nCheck that your CACHLY_JWT belongs to an account with access to this resource.`);
2314
+ }
2315
+ throw new McpError(ErrorCode.InternalError, `cachly API error ${res.status}: ${detail}`);
2287
2316
  }
2288
2317
  if (res.status === 204)
2289
2318
  return undefined;
@@ -9523,6 +9552,21 @@ if (!JWT) {
9523
9552
  '╚══════════════════════════════════════════════════════════════════╝\n' +
9524
9553
  '\n');
9525
9554
  }
9555
+ else {
9556
+ // Warn if the JWT is already expired or expiring within the hour.
9557
+ const expMs = jwtExpiryMs(JWT);
9558
+ if (expMs !== null) {
9559
+ const minsLeft = Math.floor((expMs - Date.now()) / 60_000);
9560
+ if (minsLeft <= 0) {
9561
+ process.stderr.write(`\n⚠️ cachly: CACHLY_JWT expired ${Math.abs(minsLeft)} minute(s) ago.\n` +
9562
+ ` All tool calls will fail. Get a fresh token at https://cachly.dev/setup-ai\n\n`);
9563
+ }
9564
+ else if (minsLeft < 60) {
9565
+ process.stderr.write(`\n⚠️ cachly: CACHLY_JWT expires in ${minsLeft} minute(s).\n` +
9566
+ ` Refresh it soon at https://cachly.dev/setup-ai to avoid interruptions.\n\n`);
9567
+ }
9568
+ }
9569
+ }
9526
9570
  // ── Update nudge (non-blocking, fire-and-forget) ─────────────────────────────
9527
9571
  // Check npm registry once per process start; if outdated, log to stderr so
9528
9572
  // the editor's MCP log shows an actionable one-liner. Skipped if opted out.