@cachly-dev/mcp-server 0.10.82 → 0.10.84
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/src/auth.d.ts +14 -0
- package/dist/src/auth.js +35 -0
- package/dist/src/auth.js.map +1 -1
- package/dist/src/handlers/brain.js +17 -1
- package/dist/src/handlers/brain.js.map +1 -1
- package/dist/src/handlers/viz.d.ts +6 -0
- package/dist/src/handlers/viz.js +148 -0
- package/dist/src/handlers/viz.js.map +1 -0
- package/dist/src/index.js +71 -3
- package/dist/src/index.js.map +1 -1
- package/dist/src/telemetry-types.d.ts +1 -1
- package/dist/src/tools.d.ts +30 -0
- package/dist/src/tools.js +20 -0
- package/dist/src/tools.js.map +1 -1
- package/dist/src/toolspecs.d.ts +43 -0
- package/dist/src/toolspecs.js +121 -0
- package/dist/src/toolspecs.js.map +1 -0
- package/package.json +1 -1
- package/server.json +3 -3
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Redis } from 'ioredis';
|
|
2
|
+
type GetConnection = (instanceId: string) => Promise<Redis>;
|
|
3
|
+
type ApiFetch = <T>(path: string, options?: RequestInit) => Promise<T>;
|
|
4
|
+
export declare const VIZ_TOOL_NAMES: Set<string>;
|
|
5
|
+
export declare function handleVizTool(name: string, args: Record<string, unknown>, getConnection: GetConnection, _apiFetch: ApiFetch): Promise<string | null>;
|
|
6
|
+
export {};
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { safeJsonParse } from '../utils.js';
|
|
2
|
+
export const VIZ_TOOL_NAMES = new Set(['brain_graph']);
|
|
3
|
+
// Stable color groups so the 3D frontend can theme node kinds consistently.
|
|
4
|
+
const NODE_GROUP = {
|
|
5
|
+
concept: 1, person: 2, file: 3, service: 4,
|
|
6
|
+
};
|
|
7
|
+
function nodeKind(id) {
|
|
8
|
+
if (id.startsWith('person:'))
|
|
9
|
+
return 'person';
|
|
10
|
+
if (id.startsWith('file:'))
|
|
11
|
+
return 'file';
|
|
12
|
+
if (id.startsWith('service:'))
|
|
13
|
+
return 'service';
|
|
14
|
+
return 'concept';
|
|
15
|
+
}
|
|
16
|
+
function nodeLabel(n, kind) {
|
|
17
|
+
if (kind === 'person')
|
|
18
|
+
return n.handle ?? n.id.replace(/^person:/, '');
|
|
19
|
+
if (kind === 'file')
|
|
20
|
+
return (n.path ?? n.id.replace(/^file:/, '')).split('/').slice(-1)[0] ?? n.id;
|
|
21
|
+
if (kind === 'service')
|
|
22
|
+
return n.name ?? n.id.replace(/^service:/, '');
|
|
23
|
+
return n.id;
|
|
24
|
+
}
|
|
25
|
+
async function scanAll(redis, match, limit) {
|
|
26
|
+
const keys = [];
|
|
27
|
+
const stream = redis.scanStream({ match, count: 200 });
|
|
28
|
+
await new Promise((resolve, reject) => {
|
|
29
|
+
stream.on('data', (batch) => {
|
|
30
|
+
for (const k of batch) {
|
|
31
|
+
if (keys.length < limit)
|
|
32
|
+
keys.push(k);
|
|
33
|
+
}
|
|
34
|
+
if (keys.length >= limit)
|
|
35
|
+
stream.destroy();
|
|
36
|
+
});
|
|
37
|
+
stream.on('end', resolve);
|
|
38
|
+
stream.on('close', resolve);
|
|
39
|
+
stream.on('error', reject);
|
|
40
|
+
});
|
|
41
|
+
return keys;
|
|
42
|
+
}
|
|
43
|
+
export async function handleVizTool(name, args, getConnection, _apiFetch) {
|
|
44
|
+
if (name !== 'brain_graph')
|
|
45
|
+
return null;
|
|
46
|
+
const { instance_id, max_nodes = 400, domain = '', min_confidence = 0, format = 'json', } = args;
|
|
47
|
+
const redis = await getConnection(instance_id);
|
|
48
|
+
const nodeCap = Math.max(1, Math.min(2000, Number(max_nodes) || 400));
|
|
49
|
+
const minConf = Math.max(0, Math.min(1, Number(min_confidence) || 0));
|
|
50
|
+
const domainFilter = String(domain || '').toLowerCase().trim();
|
|
51
|
+
// ── Nodes ────────────────────────────────────────────────────────────────
|
|
52
|
+
const nodeKeys = await scanAll(redis, 'cachly:ckg:node:*', nodeCap);
|
|
53
|
+
const nodes = [];
|
|
54
|
+
const present = new Set();
|
|
55
|
+
for (const key of nodeKeys) {
|
|
56
|
+
const raw = await redis.get(key);
|
|
57
|
+
const stored = safeJsonParse(raw, null);
|
|
58
|
+
if (!stored || !stored.id)
|
|
59
|
+
continue;
|
|
60
|
+
const kind = nodeKind(stored.id);
|
|
61
|
+
const ndomain = String(stored.domain ?? '').toLowerCase();
|
|
62
|
+
if (domainFilter && !ndomain.includes(domainFilter) && !stored.id.toLowerCase().includes(domainFilter))
|
|
63
|
+
continue;
|
|
64
|
+
const count = Number(stored.count ?? 1) || 1;
|
|
65
|
+
nodes.push({
|
|
66
|
+
id: stored.id,
|
|
67
|
+
name: nodeLabel(stored, kind),
|
|
68
|
+
type: kind,
|
|
69
|
+
group: NODE_GROUP[kind] ?? 0,
|
|
70
|
+
domain: stored.domain ?? '',
|
|
71
|
+
val: Math.max(1, Math.round(Math.sqrt(count) * 3)),
|
|
72
|
+
count,
|
|
73
|
+
});
|
|
74
|
+
present.add(stored.id);
|
|
75
|
+
}
|
|
76
|
+
// ── Edges ──────────────────────────────────────────────────────────────────
|
|
77
|
+
// Edge keys: cachly:ckg:edge:{from}:{edgeType}:{to}. We read the stored CKGEdge
|
|
78
|
+
// (not the key) so from/to/confidence come straight from the value — robust to
|
|
79
|
+
// ids that themselves contain ':' (e.g. "docker:layer-cache").
|
|
80
|
+
const edgeKeys = await scanAll(redis, 'cachly:ckg:edge:*', nodeCap * 8);
|
|
81
|
+
const links = [];
|
|
82
|
+
let edgesSkippedConf = 0;
|
|
83
|
+
let edgesSkippedDangling = 0;
|
|
84
|
+
for (const key of edgeKeys) {
|
|
85
|
+
const raw = await redis.get(key);
|
|
86
|
+
const edge = safeJsonParse(raw, null);
|
|
87
|
+
if (!edge || !edge.from || !edge.to)
|
|
88
|
+
continue;
|
|
89
|
+
if ((edge.confidence ?? 0) < minConf) {
|
|
90
|
+
edgesSkippedConf++;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
// Drop dangling edges (endpoint filtered out) so the 3D graph stays consistent.
|
|
94
|
+
if (!present.has(edge.from) || !present.has(edge.to)) {
|
|
95
|
+
edgesSkippedDangling++;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
links.push({
|
|
99
|
+
source: edge.from,
|
|
100
|
+
target: edge.to,
|
|
101
|
+
type: edge.edgeType ?? 'related',
|
|
102
|
+
value: Number((edge.confidence ?? 0).toFixed(3)),
|
|
103
|
+
trials: edge.trials ?? 0,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
const byType = {};
|
|
107
|
+
for (const n of nodes)
|
|
108
|
+
byType[n.type] = (byType[n.type] ?? 0) + 1;
|
|
109
|
+
const graph = {
|
|
110
|
+
schema: 'cachly.brain_graph/v1',
|
|
111
|
+
nodes,
|
|
112
|
+
links,
|
|
113
|
+
meta: {
|
|
114
|
+
instance_id,
|
|
115
|
+
generated_at: new Date().toISOString(),
|
|
116
|
+
node_count: nodes.length,
|
|
117
|
+
link_count: links.length,
|
|
118
|
+
node_types: byType,
|
|
119
|
+
truncated: nodeKeys.length >= nodeCap,
|
|
120
|
+
filters: { domain: domainFilter || null, min_confidence: minConf, max_nodes: nodeCap },
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
if (format === 'summary') {
|
|
124
|
+
const lines = [
|
|
125
|
+
`## 🧠 Brain Graph — 3D Viz Export`,
|
|
126
|
+
'',
|
|
127
|
+
`**${nodes.length}** nodes · **${links.length}** links`,
|
|
128
|
+
...Object.entries(byType).map(([t, c]) => ` • ${t}: ${c}`),
|
|
129
|
+
edgesSkippedConf ? ` • ${edgesSkippedConf} edges below confidence ${minConf}` : '',
|
|
130
|
+
edgesSkippedDangling ? ` • ${edgesSkippedDangling} dangling edges dropped` : '',
|
|
131
|
+
'',
|
|
132
|
+
graph.meta.truncated ? `⚠️ Truncated at ${nodeCap} nodes — raise \`max_nodes\` for the full graph.` : '',
|
|
133
|
+
'',
|
|
134
|
+
`Call \`brain_graph(instance_id="${instance_id}", format="json")\` for the full payload the 3D frontend renders.`,
|
|
135
|
+
].filter(Boolean);
|
|
136
|
+
return lines.join('\n');
|
|
137
|
+
}
|
|
138
|
+
// Default: emit the renderable payload in a fenced block so the frontend /
|
|
139
|
+
// API can lift it verbatim into react-force-graph-3d.
|
|
140
|
+
return [
|
|
141
|
+
`🧠 **Brain Graph** — ${nodes.length} nodes, ${links.length} links (schema \`cachly.brain_graph/v1\`)`,
|
|
142
|
+
'',
|
|
143
|
+
'```json',
|
|
144
|
+
JSON.stringify(graph, null, 2),
|
|
145
|
+
'```',
|
|
146
|
+
].join('\n');
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=viz.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"viz.js","sourceRoot":"","sources":["../../../src/handlers/viz.ts"],"names":[],"mappings":"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"}
|
package/dist/src/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { jwtExpiryMs, checkJwt, handleApiError, diagnoseAuth, planAuthHeal } from './auth.js';
|
|
2
|
+
import { jwtExpiryMs, checkJwt, handleApiError, diagnoseAuth, planAuthHeal, readClientCredentialsFromEnv, buildClientCredentialsBody, clientCredentialsTokenUrl } from './auth.js';
|
|
3
3
|
import { handleTcoTool } from './handlers/tco.js';
|
|
4
4
|
import { notify } from './notifier.js';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
@@ -65,7 +65,7 @@ import { Redis } from 'ioredis';
|
|
|
65
65
|
let API_URL = process.env.CACHLY_API_URL ?? 'https://api.cachly.dev';
|
|
66
66
|
let JWT = process.env.CACHLY_JWT ?? '';
|
|
67
67
|
const _EMBED_MODEL = process.env.CACHLY_EMBED_MODEL ?? '';
|
|
68
|
-
const CURRENT_VERSION = '0.10.
|
|
68
|
+
const CURRENT_VERSION = '0.10.84';
|
|
69
69
|
// Max time to wait for a freshly-provisioned instance to become "running" before
|
|
70
70
|
// giving up. Free-tier provisioning in high-latency regions can take 45–90s, so the
|
|
71
71
|
// old hard-coded 25s caused spurious "instance_not_reachable" on first run.
|
|
@@ -610,6 +610,7 @@ import { handleAdvancedTool } from './handlers/advanced.js';
|
|
|
610
610
|
import { handleSyndicateTool } from './handlers/syndicate.js';
|
|
611
611
|
import { handleFedbrainTool, _lastBrainFromGitCounts } from './handlers/fedbrain.js';
|
|
612
612
|
import { handleShareTool } from './handlers/share.js';
|
|
613
|
+
import { handleVizTool } from './handlers/viz.js';
|
|
613
614
|
// ── Tools (imported from tools.ts) ─────────────────────────────────────────
|
|
614
615
|
import { TOOLS } from './tools.js';
|
|
615
616
|
// ── Handlers ──────────────────────────────────────────────────────────────────
|
|
@@ -882,6 +883,9 @@ async function handleTool(name, args) {
|
|
|
882
883
|
const shareResult = await handleShareTool(name, args, getConnection, apiFetch);
|
|
883
884
|
if (shareResult !== null)
|
|
884
885
|
return shareResult;
|
|
886
|
+
const vizResult = await handleVizTool(name, args, getConnection, apiFetch);
|
|
887
|
+
if (vizResult !== null)
|
|
888
|
+
return vizResult;
|
|
885
889
|
const fedbrainResult = await handleFedbrainTool(name, args, getConnection, apiFetch);
|
|
886
890
|
if (fedbrainResult !== null) {
|
|
887
891
|
// brain_from_git and brain_predict_failures are in fedbrain handler.
|
|
@@ -1973,6 +1977,36 @@ if (process.argv[2] === 'publish') {
|
|
|
1973
1977
|
}
|
|
1974
1978
|
process.exit(0);
|
|
1975
1979
|
}
|
|
1980
|
+
// ── CLI: cachly tool-specs / openapi ───────────────────────────────────────────
|
|
1981
|
+
// Emit the 114-tool surface in any agent framework's dialect, derived from the
|
|
1982
|
+
// single TOOLS source of truth. Lets OpenAI Assistants, the Anthropic Messages
|
|
1983
|
+
// API, LangChain, CrewAI and AutoGen wrap cachly without hand-written glue.
|
|
1984
|
+
// npx @cachly-dev/mcp-server tool-specs --format=openai > cachly.openai.json
|
|
1985
|
+
// npx @cachly-dev/mcp-server openapi > cachly.openapi.json
|
|
1986
|
+
if (process.argv[2] === 'tool-specs' || process.argv[2] === 'openapi') {
|
|
1987
|
+
const { renderToolSpecs } = await import('./toolspecs.js');
|
|
1988
|
+
// Accept both "--format openai" and "--format=openai".
|
|
1989
|
+
let format = 'openapi';
|
|
1990
|
+
if (process.argv[2] !== 'openapi') {
|
|
1991
|
+
const eqArg = process.argv.find(a => a.startsWith('--format='));
|
|
1992
|
+
if (eqArg) {
|
|
1993
|
+
format = eqArg.slice('--format='.length);
|
|
1994
|
+
}
|
|
1995
|
+
else {
|
|
1996
|
+
const fmtFlag = process.argv.indexOf('--format');
|
|
1997
|
+
if (fmtFlag !== -1)
|
|
1998
|
+
format = process.argv[fmtFlag + 1] ?? 'openapi';
|
|
1999
|
+
}
|
|
2000
|
+
}
|
|
2001
|
+
const valid = ['openapi', 'openai', 'anthropic', 'langchain'];
|
|
2002
|
+
if (!valid.includes(format)) {
|
|
2003
|
+
process.stderr.write(`\n❌ Unknown format "${format}". Use one of: ${valid.join(', ')}\n\n`);
|
|
2004
|
+
process.exit(1);
|
|
2005
|
+
}
|
|
2006
|
+
// Exit only after stdout has fully flushed — on a pipe, write() is async and an
|
|
2007
|
+
// immediate process.exit() truncates large payloads (~64KB) mid-flush.
|
|
2008
|
+
process.stdout.write(renderToolSpecs(TOOLS, format, CURRENT_VERSION) + '\n', () => process.exit(0));
|
|
2009
|
+
}
|
|
1976
2010
|
// ── CLI: cachly badge ─────────────────────────────────────────────────────────
|
|
1977
2011
|
// Outputs the Markdown + HTML snippet for embedding a live Brain lesson-count
|
|
1978
2012
|
// badge in any README or website. Badge SVG served by cachly API (public, no auth).
|
|
@@ -3099,6 +3133,40 @@ if (process.argv[2] === 'learn-git') {
|
|
|
3099
3133
|
process.exit(0);
|
|
3100
3134
|
}
|
|
3101
3135
|
// ── Start ─────────────────────────────────────────────────────────────────────
|
|
3136
|
+
// M2M / headless auth: if no JWT but client credentials are present, run the
|
|
3137
|
+
// OAuth2 client_credentials grant before any credential-missing handling. This
|
|
3138
|
+
// makes cachly fully non-interactive for CI runners, agent orchestrators and
|
|
3139
|
+
// AI-to-AI pipelines — no device flow, no human, no browser.
|
|
3140
|
+
if (!JWT) {
|
|
3141
|
+
const creds = readClientCredentialsFromEnv();
|
|
3142
|
+
if (creds) {
|
|
3143
|
+
try {
|
|
3144
|
+
const res = await fetch(clientCredentialsTokenUrl(creds), {
|
|
3145
|
+
method: 'POST',
|
|
3146
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
3147
|
+
body: buildClientCredentialsBody(creds),
|
|
3148
|
+
signal: AbortSignal.timeout(8000),
|
|
3149
|
+
});
|
|
3150
|
+
if (res.ok) {
|
|
3151
|
+
const tok = await res.json();
|
|
3152
|
+
if (tok.access_token) {
|
|
3153
|
+
JWT = tok.access_token;
|
|
3154
|
+
setEmbedJwt(tok.access_token);
|
|
3155
|
+
sendFunnelEvent('m2m_auth_completed', { grant: 'client_credentials', client_id: creds.clientId });
|
|
3156
|
+
process.stderr.write('🤖 cachly: authenticated via client_credentials (M2M, headless).\n');
|
|
3157
|
+
}
|
|
3158
|
+
}
|
|
3159
|
+
else {
|
|
3160
|
+
process.stderr.write(`⚠️ cachly: client_credentials auth failed (${res.status}). Check CACHLY_CLIENT_ID / CACHLY_CLIENT_SECRET.\n`);
|
|
3161
|
+
sendFunnelEvent('m2m_auth_failed', { status: res.status });
|
|
3162
|
+
}
|
|
3163
|
+
}
|
|
3164
|
+
catch (err) {
|
|
3165
|
+
process.stderr.write(`⚠️ cachly: client_credentials auth error: ${err.message}\n`);
|
|
3166
|
+
sendFunnelEvent('m2m_auth_failed', { reason: 'network' });
|
|
3167
|
+
}
|
|
3168
|
+
}
|
|
3169
|
+
}
|
|
3102
3170
|
// Credential-missing handling at startup. The critical distinction is HOW we were
|
|
3103
3171
|
// launched, because writing anything to stdout in stdio-MCP mode corrupts the
|
|
3104
3172
|
// JSON-RPC stream and exiting kills the zero-credential device-flow onboarding.
|
|
@@ -3106,7 +3174,7 @@ if (process.argv[2] === 'learn-git') {
|
|
|
3106
3174
|
// • Editor as an MCP stdio server (non-TTY) → ONE stderr hint, then KEEP RUNNING
|
|
3107
3175
|
// so tools/list works and the first tool call starts the browser sign-in.
|
|
3108
3176
|
// Skip entirely for CLI subcommands that intentionally run without credentials.
|
|
3109
|
-
const _cliNoAuthCommands = ['demo', 'share', 'publish', 'health', 'setup', 'init', 'digest', 'invite', 'badge', 'join', 'upgrade'];
|
|
3177
|
+
const _cliNoAuthCommands = ['demo', 'share', 'publish', 'health', 'setup', 'init', 'digest', 'invite', 'badge', 'join', 'upgrade', 'tool-specs', 'openapi'];
|
|
3110
3178
|
if (!JWT && !_cliNoAuthCommands.includes(process.argv[2] ?? '')) {
|
|
3111
3179
|
const runningInTerminal = !process.argv[2] && process.stdout.isTTY === true && _isMain;
|
|
3112
3180
|
if (runningInTerminal) {
|