@cachly-dev/mcp-server 0.10.70 → 0.10.72
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 +1 -1
- package/dist/src/auth.d.ts +26 -0
- package/dist/src/auth.js +77 -5
- package/dist/src/auth.js.map +1 -1
- package/dist/src/handlers/brain.js +20 -2
- package/dist/src/handlers/brain.js.map +1 -1
- package/dist/src/handlers/team.d.ts +8 -0
- package/dist/src/handlers/team.js +148 -3
- package/dist/src/handlers/team.js.map +1 -1
- package/dist/src/index.js +164 -5
- package/dist/src/index.js.map +1 -1
- package/dist/src/tools.d.ts +56 -0
- package/dist/src/tools.js +50 -0
- package/dist/src/tools.js.map +1 -1
- package/package.json +2 -2
- package/server.json +3 -3
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
<img src="https://img.shields.io/badge/GDPR-EU%20servers-green" alt="GDPR: EU servers" />
|
|
23
23
|
</a>
|
|
24
24
|
|
|
25
|
-
<img src="https://img.shields.io/badge/
|
|
25
|
+
<img src="https://img.shields.io/badge/105%20MCP%20tools-violet" alt="105 MCP tools" />
|
|
26
26
|
|
|
27
27
|
<img src="https://img.shields.io/badge/License-Apache--2.0-yellow" alt="License: Apache-2.0" />
|
|
28
28
|
</p>
|
package/dist/src/auth.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
20
|
-
|
|
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
|
|
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.`);
|
package/dist/src/auth.js.map
CHANGED
|
@@ -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,
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,12 +2,27 @@ import { execSync } from 'node:child_process';
|
|
|
2
2
|
import { calculateConfidence, confidenceBadge, STRUCTURED_TEMPLATES, CONFIDENCE_WARN_VALUE, CONFIDENCE_STALE_VALUE, CONFIDENCE_WARN_DAYS } from '../confidence.js';
|
|
3
3
|
import { ckgSlug, extractProblemConcept, ckgUpsertNode, ckgUpdateEdge, ckgUpsertPersonNode, ckgUpsertFileNode, ckgRecordCollaboration, ckgUpsertServiceNode } from '../ckg.js';
|
|
4
4
|
import { safeJsonParse, scanKeys } from '../utils.js';
|
|
5
|
+
import { getRole, ROLE_BADGE } from './team.js';
|
|
5
6
|
import { keywordSearch, tokenize, splitMultiQuery, levenshtein, indexVocab as _indexVocab } from '../search.js';
|
|
6
7
|
import { rerankByQuality } from '../rerank.js';
|
|
7
8
|
import { computeEmbedding, hasEmbedProvider } from '../embeddings.js';
|
|
8
9
|
// ── Changelog (shown once per version in session_start) ──────────────────────
|
|
9
|
-
const MCP_VERSION = '0.10.
|
|
10
|
+
const MCP_VERSION = '0.10.72';
|
|
10
11
|
const WHATS_NEW = {
|
|
12
|
+
'0.10.72': [
|
|
13
|
+
`👑 **Role model — admin · reviewer · contributor · viewer (Phase 3)**`,
|
|
14
|
+
` 🆕 \`team_assign_role\` — establish governance; first call bootstraps, admins manage the rest`,
|
|
15
|
+
` 🆕 \`team_whoami\` — see your own role and capabilities`,
|
|
16
|
+
` 🆕 \`team_roster\` — full team role table (👑🛡️✏️👁️)`,
|
|
17
|
+
` 🛡️ \`team_confirm\` is now role-aware — admin/reviewer auto-get senior weight; contributor gets peer; no self-promotion`,
|
|
18
|
+
` 👑 Setup CLI now prompts for governance bootstrap (idempotent). 105 MCP tools`,
|
|
19
|
+
],
|
|
20
|
+
'0.10.71': [
|
|
21
|
+
`🛡️ **Self-healing auth** — no more silent "0 recalls because the token quietly died"`,
|
|
22
|
+
` ♻️ Near-expiry tokens auto-refresh into a long-lived key while still valid (zero interaction)`,
|
|
23
|
+
` 🔁 A rejected (401) call self-heals once and retries before surfacing an error`,
|
|
24
|
+
` 📣 If the credential is truly dead, session_start + get_api_status say so plainly + how to fix`,
|
|
25
|
+
],
|
|
11
26
|
'0.10.70': [
|
|
12
27
|
`🛰️ **Service/System nodes in the graph (Phase 3)** — the brain now models running systems`,
|
|
13
28
|
` 🆕 \`brain_service_map(service="prometheus")\` — who owns it + every failure/fix known`,
|
|
@@ -2062,7 +2077,10 @@ export async function handleBrainTool(name, args, getConnection, apiFetch) {
|
|
|
2062
2077
|
const conf = (p.avgConfidence * 100).toFixed(0);
|
|
2063
2078
|
const daysSince = Math.floor((Date.now() - new Date(p.lastActive).getTime()) / 86400000);
|
|
2064
2079
|
const recency = daysSince < 1 ? 'today' : daysSince < 7 ? `${daysSince}d ago` : daysSince < 30 ? `${Math.floor(daysSince / 7)}w ago` : `${Math.floor(daysSince / 30)}mo ago`;
|
|
2065
|
-
|
|
2080
|
+
// Show role badge if assigned (non-blocking — role lookup is best-effort)
|
|
2081
|
+
const roleVal = await getRole(redis, instance_id, p.handle).catch(() => null);
|
|
2082
|
+
const roleBadge = roleVal ? ` ${ROLE_BADGE[roleVal]}` : '';
|
|
2083
|
+
lines.push(`${medal} **${p.handle}**${roleBadge} — ${p.lessonCount} lesson${p.lessonCount !== 1 ? 's' : ''} · ${conf}% confidence · last active ${recency}`, ` _domains: ${p.domains}_`, ``);
|
|
2066
2084
|
}
|
|
2067
2085
|
if (topCollaborators.length > 0 && topExpert) {
|
|
2068
2086
|
lines.push(`🤝 **${topExpert.handle}** frequently works with: ${topCollaborators.map(h => `**${h}**`).join(', ')}`, ` _(ask them together — they've solved structurally similar things on shared files)_`, ``);
|