@cachly-dev/mcp-server 0.10.47 → 0.10.48
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 +33 -5
- package/dist/__tests__/brain-flow.test.js +2 -2
- package/dist/__tests__/brain-flow.test.js.map +1 -1
- package/dist/__tests__/handlers-unit.test.js +3 -1
- package/dist/__tests__/handlers-unit.test.js.map +1 -1
- package/dist/handlers/brain.js +1 -1
- package/dist/handlers/brain.js.map +1 -1
- package/dist/handlers/fedbrain.js +6 -5
- package/dist/handlers/fedbrain.js.map +1 -1
- package/dist/handlers/roadmap.js +1 -1
- package/dist/handlers/roadmap.js.map +1 -1
- package/dist/handlers/team.js +1 -1
- package/dist/handlers/team.js.map +1 -1
- package/dist/index.js +130 -17
- package/dist/index.js.map +1 -1
- package/dist/search.js +3 -3
- package/dist/search.js.map +1 -1
- package/package.json +2 -1
- package/smithery.yaml +0 -2
- package/src/__tests__/brain-flow.test.ts +2 -2
- package/src/__tests__/handlers-unit.test.ts +2 -1
- package/src/handlers/brain.ts +1 -1
- package/src/handlers/fedbrain.ts +6 -5
- package/src/handlers/roadmap.ts +1 -1
- package/src/handlers/team.ts +1 -1
- package/src/index.ts +82 -11
- package/src/search.ts +3 -3
package/src/index.ts
CHANGED
|
@@ -2780,6 +2780,29 @@ if (!process.env.CACHLY_NO_UPDATE_CHECK) {
|
|
|
2780
2780
|
|
|
2781
2781
|
const httpPort = process.env.PORT ? parseInt(process.env.PORT, 10) : undefined;
|
|
2782
2782
|
|
|
2783
|
+
// ── Per-request MCP Server factory (HTTP / Smithery stateless mode) ──────────
|
|
2784
|
+
// SDK ≥1.9 forbids reusing a stateless transport across requests.
|
|
2785
|
+
// Solution: create a fresh Server+Transport per /mcp request.
|
|
2786
|
+
// The module-level `server` below is used for stdio mode only.
|
|
2787
|
+
function createHttpMcpServer(): Server {
|
|
2788
|
+
const srv = new Server(
|
|
2789
|
+
{ name: 'cachly-mcp', version: CURRENT_VERSION },
|
|
2790
|
+
{ capabilities: { tools: {} } }
|
|
2791
|
+
);
|
|
2792
|
+
srv.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
|
|
2793
|
+
srv.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
2794
|
+
const { name, arguments: args } = request.params;
|
|
2795
|
+
try {
|
|
2796
|
+
const text = await handleTool(name, (args ?? {}) as Record<string, unknown>);
|
|
2797
|
+
return { content: [{ type: 'text', text }] };
|
|
2798
|
+
} catch (err) {
|
|
2799
|
+
if (err instanceof McpError) throw err;
|
|
2800
|
+
throw new McpError(ErrorCode.InternalError, (err as Error).message);
|
|
2801
|
+
}
|
|
2802
|
+
});
|
|
2803
|
+
return srv;
|
|
2804
|
+
}
|
|
2805
|
+
|
|
2783
2806
|
if (httpPort) {
|
|
2784
2807
|
// ── HTTP mode (Streamable HTTP transport) ───────────────────────────────
|
|
2785
2808
|
// Used for Smithery URL deployment: PORT=3000 node dist/index.js
|
|
@@ -2788,15 +2811,19 @@ if (httpPort) {
|
|
|
2788
2811
|
// 2. Smithery config: ?config=base64url({CACHLY_JWT, CACHLY_BRAIN_INSTANCE_ID, ...})
|
|
2789
2812
|
// Falls back to module-level env-derived values when neither is provided.
|
|
2790
2813
|
const { createServer } = await import('node:http');
|
|
2791
|
-
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
|
|
2792
|
-
await server.connect(transport);
|
|
2793
2814
|
|
|
2794
2815
|
function extractReqCtx(req: import('node:http').IncomingMessage, url: URL): ReqCtx {
|
|
2795
2816
|
let jwtVal = '';
|
|
2796
2817
|
let instId = '';
|
|
2797
2818
|
const auth = req.headers['authorization'];
|
|
2798
|
-
if (typeof auth === 'string' && auth.
|
|
2799
|
-
|
|
2819
|
+
if (typeof auth === 'string' && auth.trim()) {
|
|
2820
|
+
const a = auth.trim();
|
|
2821
|
+
jwtVal = a.toLowerCase().startsWith('bearer ') ? a.slice(7).trim() : a;
|
|
2822
|
+
}
|
|
2823
|
+
// Fallback: raw JWT in custom header (Smithery "header" transport without Bearer prefix)
|
|
2824
|
+
if (!jwtVal) {
|
|
2825
|
+
const rawJwt = req.headers['cachly_jwt'] ?? req.headers['cachly-jwt'] ?? req.headers['x-cachly-jwt'] ?? req.headers['x-api-key'];
|
|
2826
|
+
if (typeof rawJwt === 'string') jwtVal = rawJwt.trim();
|
|
2800
2827
|
}
|
|
2801
2828
|
const hdrInst = req.headers['x-cachly-instance-id'];
|
|
2802
2829
|
if (typeof hdrInst === 'string') instId = hdrInst.trim();
|
|
@@ -2811,6 +2838,15 @@ if (httpPort) {
|
|
|
2811
2838
|
if (!instId && typeof json.CACHLY_BRAIN_INSTANCE_ID === 'string') instId = json.CACHLY_BRAIN_INSTANCE_ID;
|
|
2812
2839
|
} catch { /* malformed config — ignore, fall back to globals */ }
|
|
2813
2840
|
}
|
|
2841
|
+
// Direct query params (Smithery "query" transport, manual clients, etc.)
|
|
2842
|
+
if (!jwtVal) {
|
|
2843
|
+
const q = url.searchParams.get('CACHLY_JWT') ?? url.searchParams.get('cachly_jwt') ?? url.searchParams.get('api_key');
|
|
2844
|
+
if (q) jwtVal = q.trim();
|
|
2845
|
+
}
|
|
2846
|
+
if (!instId) {
|
|
2847
|
+
const q = url.searchParams.get('CACHLY_BRAIN_INSTANCE_ID') ?? url.searchParams.get('cachly_brain_instance_id') ?? url.searchParams.get('instance_id');
|
|
2848
|
+
if (q) instId = q.trim();
|
|
2849
|
+
}
|
|
2814
2850
|
return { jwt: jwtVal, instanceId: instId };
|
|
2815
2851
|
}
|
|
2816
2852
|
|
|
@@ -2830,16 +2866,51 @@ if (httpPort) {
|
|
|
2830
2866
|
}));
|
|
2831
2867
|
return;
|
|
2832
2868
|
}
|
|
2869
|
+
// Static server card — lets Smithery skip live scanning (avoids auth complexity).
|
|
2870
|
+
if (url.pathname === '/.well-known/mcp/server-card.json') {
|
|
2871
|
+
const card = {
|
|
2872
|
+
name: 'cachly AI Brain',
|
|
2873
|
+
version: CURRENT_VERSION,
|
|
2874
|
+
description: 'Persistent AI memory for Claude Code, Cursor, Copilot, Windsurf & friends. Remembers every lesson, fix, and architecture decision forever.',
|
|
2875
|
+
transport: { type: 'streamable-http', url: 'https://mcp.cachly.dev/mcp' },
|
|
2876
|
+
capabilities: { tools: {} },
|
|
2877
|
+
};
|
|
2878
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
2879
|
+
res.end(JSON.stringify(card));
|
|
2880
|
+
return;
|
|
2881
|
+
}
|
|
2833
2882
|
if (url.pathname === '/mcp' || url.pathname === '/') {
|
|
2883
|
+
// Handle CORS preflight
|
|
2884
|
+
if (req.method === 'OPTIONS') {
|
|
2885
|
+
res.writeHead(204, {
|
|
2886
|
+
'Access-Control-Allow-Origin': '*',
|
|
2887
|
+
'Access-Control-Allow-Methods': 'GET, POST, DELETE, OPTIONS',
|
|
2888
|
+
'Access-Control-Allow-Headers': 'Authorization, Content-Type, Mcp-Session-Id, X-Cachly-Instance-Id, cachly_jwt, cachly-jwt, x-cachly-jwt, x-api-key',
|
|
2889
|
+
});
|
|
2890
|
+
res.end();
|
|
2891
|
+
return;
|
|
2892
|
+
}
|
|
2834
2893
|
const ctx = extractReqCtx(req, url);
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2894
|
+
// Create a fresh Server+Transport for each request (stateless multi-tenant mode).
|
|
2895
|
+
// SDK ≥1.9 throws if a stateless transport is reused across requests.
|
|
2896
|
+
const reqServer = createHttpMcpServer();
|
|
2897
|
+
const reqTransport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
|
|
2898
|
+
reqServer.connect(reqTransport).then(() => {
|
|
2899
|
+
reqCtx.run(ctx, () => {
|
|
2900
|
+
reqTransport.handleRequest(req, res).catch((err) => {
|
|
2901
|
+
process.stderr.write(`cachly-mcp HTTP handler error: ${(err as Error).message}\n`);
|
|
2902
|
+
if (!res.headersSent) {
|
|
2903
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
2904
|
+
res.end(JSON.stringify({ error: 'internal_error' }));
|
|
2905
|
+
}
|
|
2906
|
+
});
|
|
2842
2907
|
});
|
|
2908
|
+
}).catch((err) => {
|
|
2909
|
+
process.stderr.write(`cachly-mcp connect error: ${(err as Error).message}\n`);
|
|
2910
|
+
if (!res.headersSent) {
|
|
2911
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
2912
|
+
res.end(JSON.stringify({ error: 'internal_error' }));
|
|
2913
|
+
}
|
|
2843
2914
|
});
|
|
2844
2915
|
return;
|
|
2845
2916
|
}
|
package/src/search.ts
CHANGED
|
@@ -384,7 +384,7 @@ function hindiLightStem(word: string): string {
|
|
|
384
384
|
* настройки → настройк (genitive plural)
|
|
385
385
|
*/
|
|
386
386
|
function russianLightStem(word: string): string {
|
|
387
|
-
|
|
387
|
+
const w = word;
|
|
388
388
|
// Longest first to avoid partial stripping
|
|
389
389
|
// Verb infinitives / gerunds
|
|
390
390
|
if (w.endsWith('ывание') && w.length > 7) return w.slice(0, -6);
|
|
@@ -427,7 +427,7 @@ function russianLightStem(word: string): string {
|
|
|
427
427
|
* yüklemek → yükle (-mek/-mak infinitive)
|
|
428
428
|
*/
|
|
429
429
|
function turkishLightStem(word: string): string {
|
|
430
|
-
|
|
430
|
+
const w = word;
|
|
431
431
|
// Suffixes longest-first
|
|
432
432
|
if (w.endsWith('lardaki') && w.length > 7) return w.slice(0, -7);
|
|
433
433
|
if (w.endsWith('lerdeki') && w.length > 7) return w.slice(0, -7);
|
|
@@ -475,7 +475,7 @@ function turkishLightStem(word: string): string {
|
|
|
475
475
|
* সমস্যাটি → সমস্যা (-টি singular marker)
|
|
476
476
|
*/
|
|
477
477
|
function bengaliLightStem(word: string): string {
|
|
478
|
-
|
|
478
|
+
const w = word;
|
|
479
479
|
// Plural / collective markers (longest first)
|
|
480
480
|
if (w.endsWith('\u0997\u09c1\u09b2\u09cb') && w.length > 5) return w.slice(0, -4); // গুলো (-gulo plural)
|
|
481
481
|
if (w.endsWith('\u0997\u09c1\u09b2\u09bf') && w.length > 5) return w.slice(0, -4); // গুলি (-guli plural)
|