@cachly-dev/mcp-server 0.10.71 → 0.10.73
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/handlers/brain.js +40 -5
- package/dist/src/handlers/brain.js.map +1 -1
- package/dist/src/handlers/team.d.ts +18 -0
- package/dist/src/handlers/team.js +248 -3
- package/dist/src/handlers/team.js.map +1 -1
- package/dist/src/index.js +67 -11
- package/dist/src/index.js.map +1 -1
- package/dist/src/tools.d.ts +107 -0
- package/dist/src/tools.js +87 -0
- package/dist/src/tools.js.map +1 -1
- package/package.json +3 -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/107%20MCP%20tools-violet" alt="107 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>
|
|
@@ -2,12 +2,29 @@ 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, getScopes, lessonVisibleToScope } 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.73';
|
|
10
11
|
const WHATS_NEW = {
|
|
12
|
+
'0.10.73': [
|
|
13
|
+
`🔐 **Team visibility scopes + one-command init + external bench**`,
|
|
14
|
+
` 🆕 \`team_grant_scope\` / \`team_scopes\` — scope lessons to a sub-team (group="security"); only members + admins recall them`,
|
|
15
|
+
` 🏷️ \`learn_from_attempts(group="...")\` — orthogonal to private; smart_recall enforces it per requester`,
|
|
16
|
+
` ⚡ \`init\` is now zero-arg + idempotent (reuses saved creds, only writes what changed, <60s)`,
|
|
17
|
+
` 📦 \`npm run bench:external\` — prove recall-lift on a third-party-labeled corpus (portable JSON format)`,
|
|
18
|
+
` 📊 107 MCP tools`,
|
|
19
|
+
],
|
|
20
|
+
'0.10.72': [
|
|
21
|
+
`👑 **Role model — admin · reviewer · contributor · viewer (Phase 3)**`,
|
|
22
|
+
` 🆕 \`team_assign_role\` — establish governance; first call bootstraps, admins manage the rest`,
|
|
23
|
+
` 🆕 \`team_whoami\` — see your own role and capabilities`,
|
|
24
|
+
` 🆕 \`team_roster\` — full team role table (👑🛡️✏️👁️)`,
|
|
25
|
+
` 🛡️ \`team_confirm\` is now role-aware — admin/reviewer auto-get senior weight; contributor gets peer; no self-promotion`,
|
|
26
|
+
` 👑 Setup CLI now prompts for governance bootstrap (idempotent). 105 MCP tools`,
|
|
27
|
+
],
|
|
11
28
|
'0.10.71': [
|
|
12
29
|
`🛡️ **Self-healing auth** — no more silent "0 recalls because the token quietly died"`,
|
|
13
30
|
` ♻️ Near-expiry tokens auto-refresh into a long-lived key while still valid (zero interaction)`,
|
|
@@ -105,7 +122,7 @@ export const BRAIN_TOOL_NAMES = new Set([
|
|
|
105
122
|
export async function handleBrainTool(name, args, getConnection, apiFetch) {
|
|
106
123
|
switch (name) {
|
|
107
124
|
case 'learn_from_attempts': {
|
|
108
|
-
const { instance_id, topic, outcome, what_worked, what_failed = '', context: ctx = '', severity = 'major', file_paths = [], commands = [], tags = [], depends_on = [], author = '', visibility = 'team', service = '', service_kind = 'service', } = args;
|
|
125
|
+
const { instance_id, topic, outcome, what_worked, what_failed = '', context: ctx = '', severity = 'major', file_paths = [], commands = [], tags = [], depends_on = [], author = '', visibility = 'team', service = '', service_kind = 'service', group = '', } = args;
|
|
109
126
|
const redis = await getConnection(instance_id);
|
|
110
127
|
const ts = new Date().toISOString();
|
|
111
128
|
// ── Structured template hints ──────────────────────────────────────────
|
|
@@ -224,6 +241,7 @@ export async function handleBrainTool(name, args, getConnection, apiFetch) {
|
|
|
224
241
|
depends_on,
|
|
225
242
|
...(author ? { author } : {}),
|
|
226
243
|
...(service ? { service } : {}),
|
|
244
|
+
...(group ? { group: String(group).toLowerCase().trim() } : {}),
|
|
227
245
|
visibility,
|
|
228
246
|
recall_count: recallCount,
|
|
229
247
|
ts,
|
|
@@ -576,6 +594,15 @@ export async function handleBrainTool(name, args, getConnection, apiFetch) {
|
|
|
576
594
|
? rawContextFiles.filter((f) => typeof f === 'string')
|
|
577
595
|
: [];
|
|
578
596
|
const redis = await getConnection(instance_id);
|
|
597
|
+
// Team-level visibility scopes: resolve the requester's group memberships +
|
|
598
|
+
// admin status once, so group-scoped lessons only surface for members/admins.
|
|
599
|
+
let requesterScopes = new Set();
|
|
600
|
+
let requesterIsAdmin = false;
|
|
601
|
+
if (requester) {
|
|
602
|
+
requesterScopes = await getScopes(redis, instance_id, requester).catch(() => new Set());
|
|
603
|
+
const reqRole = await getRole(redis, instance_id, requester).catch(() => null);
|
|
604
|
+
requesterIsAdmin = reqRole === 'admin';
|
|
605
|
+
}
|
|
579
606
|
// ── Layer 1: Keyword search across ALL brain data (always works, no embedding) ──
|
|
580
607
|
const rawMatches = await keywordSearch(redis, ['cachly:ctx:*', 'cachly:lesson:best:*', 'cachly:idx:*'], query, 10);
|
|
581
608
|
// ── Layer 1.5: Quality-aware rerank — proven success lessons outrank
|
|
@@ -749,13 +776,18 @@ export async function handleBrainTool(name, args, getConnection, apiFetch) {
|
|
|
749
776
|
}
|
|
750
777
|
}
|
|
751
778
|
}
|
|
752
|
-
// Filter private lessons
|
|
779
|
+
// Filter private lessons (recall_best_solution only) + enforce team scopes:
|
|
780
|
+
// group-scoped lessons surface only for members of that group (admins see all).
|
|
753
781
|
const hybridResults = [...hybridMap.values()]
|
|
754
782
|
.filter(r => {
|
|
755
783
|
if (!r.key.startsWith('cachly:lesson:best:'))
|
|
756
784
|
return true;
|
|
757
785
|
const ld = safeJsonParse(r.content, {});
|
|
758
|
-
|
|
786
|
+
if (ld.visibility === 'private')
|
|
787
|
+
return false;
|
|
788
|
+
if (!lessonVisibleToScope(ld.group, requesterScopes, requesterIsAdmin))
|
|
789
|
+
return false;
|
|
790
|
+
return true;
|
|
759
791
|
})
|
|
760
792
|
.sort((a, b) => b.hybridScore - a.hybridScore);
|
|
761
793
|
// ── Build output ──────────────────────────────────────────────────────────
|
|
@@ -2068,7 +2100,10 @@ export async function handleBrainTool(name, args, getConnection, apiFetch) {
|
|
|
2068
2100
|
const conf = (p.avgConfidence * 100).toFixed(0);
|
|
2069
2101
|
const daysSince = Math.floor((Date.now() - new Date(p.lastActive).getTime()) / 86400000);
|
|
2070
2102
|
const recency = daysSince < 1 ? 'today' : daysSince < 7 ? `${daysSince}d ago` : daysSince < 30 ? `${Math.floor(daysSince / 7)}w ago` : `${Math.floor(daysSince / 30)}mo ago`;
|
|
2071
|
-
|
|
2103
|
+
// Show role badge if assigned (non-blocking — role lookup is best-effort)
|
|
2104
|
+
const roleVal = await getRole(redis, instance_id, p.handle).catch(() => null);
|
|
2105
|
+
const roleBadge = roleVal ? ` ${ROLE_BADGE[roleVal]}` : '';
|
|
2106
|
+
lines.push(`${medal} **${p.handle}**${roleBadge} — ${p.lessonCount} lesson${p.lessonCount !== 1 ? 's' : ''} · ${conf}% confidence · last active ${recency}`, ` _domains: ${p.domains}_`, ``);
|
|
2072
2107
|
}
|
|
2073
2108
|
if (topCollaborators.length > 0 && topExpert) {
|
|
2074
2109
|
lines.push(`🤝 **${topExpert.handle}** frequently works with: ${topCollaborators.map(h => `**${h}**`).join(', ')}`, ` _(ask them together — they've solved structurally similar things on shared files)_`, ``);
|