@cachly-dev/mcp-server 0.10.70 → 0.10.71

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.
@@ -1,3 +1,29 @@
1
1
  export declare function jwtExpiryMs(token: string): number | null;
2
+ /** Long-lived API keys cannot expire silently; raw access tokens can. */
3
+ export declare const API_KEY_PREFIX = "cky_";
4
+ export type AuthState = 'healthy' | 'no_jwt' | 'expired' | 'near_expiry' | 'long_lived';
5
+ export interface AuthDiagnosis {
6
+ state: AuthState;
7
+ /** Safe to make API calls right now? (healthy, near_expiry and long_lived all are.) */
8
+ usable: boolean;
9
+ /** ms until expiry, or null for tokens with no exp claim (long-lived keys). */
10
+ expiresInMs: number | null;
11
+ /** Human-facing message with the single fix, when action is needed. */
12
+ message: string;
13
+ }
14
+ /** A long-lived API key (cky_live_… / cky_…) has no exp and never expires silently. */
15
+ export declare function isLongLivedApiKey(token: string): boolean;
16
+ export declare const NEAR_EXPIRY_MS: number;
17
+ /**
18
+ * Diagnose the current credential without making any network call. Pure +
19
+ * deterministic (pass `now` in tests).
20
+ */
21
+ export declare function diagnoseAuth(jwt: string, now?: number): AuthDiagnosis;
22
+ export type AuthHealAction = 'none' | 'refresh' | 'reauth';
23
+ /**
24
+ * Decide what self-healing step (if any) to take for a diagnosis. Pure so the
25
+ * decision is unit-testable independently of the network-bound executor.
26
+ */
27
+ export declare function planAuthHeal(d: AuthDiagnosis): AuthHealAction;
2
28
  export declare function checkJwt(jwt: string): void;
3
29
  export declare function handleApiError(status: number, detail: string): never;
package/dist/src/auth.js CHANGED
@@ -11,18 +11,90 @@ export function jwtExpiryMs(token) {
11
11
  return null;
12
12
  }
13
13
  }
14
- export function checkJwt(jwt) {
14
+ // ── Self-healing auth ─────────────────────────────────────────────────────────
15
+ // The silent-failure mode we guard against: a token that is missing, expired, or
16
+ // about to expire degrades the brain into "0 recalls" without the user ever being
17
+ // told why. Instead of failing silently (or only at the moment a network call
18
+ // finally 401s), we diagnose the credential up front and either heal it
19
+ // automatically (mint a fresh long-lived key while the current one is still valid)
20
+ // or surface a single, actionable instruction.
21
+ /** Long-lived API keys cannot expire silently; raw access tokens can. */
22
+ export const API_KEY_PREFIX = 'cky_';
23
+ /** A long-lived API key (cky_live_… / cky_…) has no exp and never expires silently. */
24
+ export function isLongLivedApiKey(token) {
25
+ return token.startsWith(API_KEY_PREFIX);
26
+ }
27
+ // Refresh a token this close to expiry (or already expired) the moment we touch it.
28
+ export const NEAR_EXPIRY_MS = 10 * 60 * 1000; // 10 minutes
29
+ const SETUP_URL = 'https://cachly.dev/setup-ai';
30
+ /**
31
+ * Diagnose the current credential without making any network call. Pure +
32
+ * deterministic (pass `now` in tests).
33
+ */
34
+ export function diagnoseAuth(jwt, now = Date.now()) {
15
35
  if (!jwt) {
16
- 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>');
36
+ return {
37
+ state: 'no_jwt',
38
+ usable: false,
39
+ expiresInMs: null,
40
+ message: `No CACHLY_JWT set — the brain can't persist or recall anything (you'd silently get 0 recalls).\n\nGet your token at ${SETUP_URL} and add it to your MCP config, or just call any tool to start the one-time device-flow sign-in.`,
41
+ };
17
42
  }
18
43
  const expMs = jwtExpiryMs(jwt);
19
- if (expMs !== null && expMs < Date.now()) {
20
- throw new McpError(ErrorCode.InvalidRequest, `CACHLY_JWT expired at ${new Date(expMs).toISOString()}.\n\nGet a fresh token at https://cachly.dev/setup-ai and update CACHLY_JWT in your MCP config.`);
44
+ // No exp claim → long-lived API key. These are the healthy steady state.
45
+ if (expMs === null) {
46
+ return {
47
+ state: isLongLivedApiKey(jwt) ? 'long_lived' : 'healthy',
48
+ usable: true,
49
+ expiresInMs: null,
50
+ message: '',
51
+ };
52
+ }
53
+ const remaining = expMs - now;
54
+ if (remaining <= 0) {
55
+ return {
56
+ state: 'expired',
57
+ usable: false,
58
+ expiresInMs: remaining,
59
+ message: `CACHLY_JWT expired at ${new Date(expMs).toISOString()} — recalls will silently return nothing until you re-authenticate.\n\nGet a fresh token at ${SETUP_URL}, or call any tool to re-run the device-flow sign-in.`,
60
+ };
61
+ }
62
+ if (remaining <= NEAR_EXPIRY_MS) {
63
+ const mins = Math.max(1, Math.round(remaining / 60_000));
64
+ return {
65
+ state: 'near_expiry',
66
+ usable: true,
67
+ expiresInMs: remaining,
68
+ message: `CACHLY_JWT expires in ~${mins} min. cachly will refresh it automatically into a long-lived key while it's still valid.`,
69
+ };
70
+ }
71
+ return { state: 'healthy', usable: true, expiresInMs: remaining, message: '' };
72
+ }
73
+ /**
74
+ * Decide what self-healing step (if any) to take for a diagnosis. Pure so the
75
+ * decision is unit-testable independently of the network-bound executor.
76
+ */
77
+ export function planAuthHeal(d) {
78
+ switch (d.state) {
79
+ case 'no_jwt':
80
+ case 'expired':
81
+ return 'reauth';
82
+ case 'near_expiry':
83
+ return 'refresh';
84
+ case 'healthy':
85
+ case 'long_lived':
86
+ return 'none';
87
+ }
88
+ }
89
+ export function checkJwt(jwt) {
90
+ const d = diagnoseAuth(jwt);
91
+ if (!d.usable) {
92
+ throw new McpError(ErrorCode.InvalidRequest, d.message);
21
93
  }
22
94
  }
23
95
  export function handleApiError(status, detail) {
24
96
  if (status === 401) {
25
- 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`);
97
+ throw new McpError(ErrorCode.InvalidRequest, `Authentication failed (401): ${detail}\n\nYour CACHLY_JWT may be expired or invalid. Get a fresh token at ${SETUP_URL}`);
26
98
  }
27
99
  if (status === 403) {
28
100
  throw new McpError(ErrorCode.InvalidRequest, `Access denied (403): ${detail}\n\nCheck that your CACHLY_JWT belongs to an account with access to this resource.`);
@@ -1 +1 @@
1
- {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,oCAAoC,CAAC;AAEzE,MAAM,UAAU,WAAW,CAAC,KAAa;IACvC,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC/B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QACpC,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CACxB,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CACjC,CAAC;QACtB,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;IACjD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,GAAW;IAClC,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,QAAQ,CAChB,SAAS,CAAC,cAAc,EACxB,wIAAwI,CACzI,CAAC;IACJ,CAAC;IACD,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;IAC/B,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;QACzC,MAAM,IAAI,QAAQ,CAChB,SAAS,CAAC,cAAc,EACxB,yBAAyB,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,WAAW,EAAE,iGAAiG,CACxJ,CAAC;IACJ,CAAC;AACH,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,MAAc,EAAE,MAAc;IAC3D,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;QACnB,MAAM,IAAI,QAAQ,CAChB,SAAS,CAAC,cAAc,EACxB,gCAAgC,MAAM,iGAAiG,CACxI,CAAC;IACJ,CAAC;IACD,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;QACnB,MAAM,IAAI,QAAQ,CAChB,SAAS,CAAC,cAAc,EACxB,wBAAwB,MAAM,oFAAoF,CACnH,CAAC;IACJ,CAAC;IACD,MAAM,IAAI,QAAQ,CAAC,SAAS,CAAC,aAAa,EAAE,oBAAoB,MAAM,KAAK,MAAM,EAAE,CAAC,CAAC;AACvF,CAAC"}
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/auth.ts"],"names":[],"mappings":"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"}
@@ -6,8 +6,14 @@ import { keywordSearch, tokenize, splitMultiQuery, levenshtein, indexVocab as _i
6
6
  import { rerankByQuality } from '../rerank.js';
7
7
  import { computeEmbedding, hasEmbedProvider } from '../embeddings.js';
8
8
  // ── Changelog (shown once per version in session_start) ──────────────────────
9
- const MCP_VERSION = '0.10.70';
9
+ const MCP_VERSION = '0.10.71';
10
10
  const WHATS_NEW = {
11
+ '0.10.71': [
12
+ `🛡️ **Self-healing auth** — no more silent "0 recalls because the token quietly died"`,
13
+ ` ♻️ Near-expiry tokens auto-refresh into a long-lived key while still valid (zero interaction)`,
14
+ ` 🔁 A rejected (401) call self-heals once and retries before surfacing an error`,
15
+ ` 📣 If the credential is truly dead, session_start + get_api_status say so plainly + how to fix`,
16
+ ],
11
17
  '0.10.70': [
12
18
  `🛰️ **Service/System nodes in the graph (Phase 3)** — the brain now models running systems`,
13
19
  ` 🆕 \`brain_service_map(service="prometheus")\` — who owns it + every failure/fix known`,