@cachly-dev/mcp-server 0.10.124 ā 0.10.126
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/index.d.ts +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/src/aussetzer.d.ts +67 -0
- package/dist/src/aussetzer.js +82 -0
- package/dist/src/aussetzer.js.map +1 -0
- package/dist/src/autor-abzeichen.d.ts +41 -0
- package/dist/src/autor-abzeichen.js +56 -0
- package/dist/src/autor-abzeichen.js.map +1 -0
- package/dist/src/autor.d.ts +101 -0
- package/dist/src/autor.js +136 -0
- package/dist/src/autor.js.map +1 -0
- package/dist/src/bauabdruck-daten.generiert.json +5 -0
- package/dist/src/bauabdruck.d.ts +38 -0
- package/dist/src/bauabdruck.js +93 -0
- package/dist/src/bauabdruck.js.map +1 -0
- package/dist/src/bedeutung.d.ts +175 -0
- package/dist/src/bedeutung.js +254 -0
- package/dist/src/bedeutung.js.map +1 -0
- package/dist/src/brain-export.d.ts +24 -0
- package/dist/src/brain-export.js +31 -1
- package/dist/src/brain-export.js.map +1 -1
- package/dist/src/deutsch.d.ts +98 -0
- package/dist/src/deutsch.js +223 -0
- package/dist/src/deutsch.js.map +1 -0
- package/dist/src/eingaenge-nachruesten.d.ts +41 -0
- package/dist/src/eingaenge-nachruesten.js +290 -0
- package/dist/src/eingaenge-nachruesten.js.map +1 -0
- package/dist/src/eingaenge.d.ts +135 -0
- package/dist/src/eingaenge.js +228 -0
- package/dist/src/eingaenge.js.map +1 -0
- package/dist/src/geliefert.d.ts +63 -0
- package/dist/src/geliefert.js +114 -0
- package/dist/src/geliefert.js.map +1 -0
- package/dist/src/handlers/advanced.js +7 -3
- package/dist/src/handlers/advanced.js.map +1 -1
- package/dist/src/handlers/brain.d.ts +19 -0
- package/dist/src/handlers/brain.js +536 -48
- package/dist/src/handlers/brain.js.map +1 -1
- package/dist/src/handlers/instances.d.ts +24 -0
- package/dist/src/handlers/instances.js +49 -1
- package/dist/src/handlers/instances.js.map +1 -1
- package/dist/src/handlers/team.js +105 -0
- package/dist/src/handlers/team.js.map +1 -1
- package/dist/src/index.d.ts +20 -0
- package/dist/src/index.js +272 -26
- package/dist/src/index.js.map +1 -1
- package/dist/src/lesson-preview.d.ts +81 -0
- package/dist/src/lesson-preview.js +133 -0
- package/dist/src/lesson-preview.js.map +1 -0
- package/dist/src/rangfolge.d.ts +169 -0
- package/dist/src/rangfolge.js +214 -0
- package/dist/src/rangfolge.js.map +1 -0
- package/dist/src/raumkorrektur.d.ts +100 -0
- package/dist/src/raumkorrektur.js +197 -0
- package/dist/src/raumkorrektur.js.map +1 -0
- package/dist/src/recall-tiefe.d.ts +48 -0
- package/dist/src/recall-tiefe.js +42 -0
- package/dist/src/recall-tiefe.js.map +1 -0
- package/dist/src/rerank.d.ts +0 -14
- package/dist/src/rerank.js +39 -2
- package/dist/src/rerank.js.map +1 -1
- package/dist/src/search.d.ts +56 -17
- package/dist/src/search.js +335 -73
- package/dist/src/search.js.map +1 -1
- package/dist/src/seltenheitsbestand.d.ts +53 -0
- package/dist/src/seltenheitsbestand.js +92 -0
- package/dist/src/seltenheitsbestand.js.map +1 -0
- package/dist/src/spuren.d.ts +97 -0
- package/dist/src/spuren.js +150 -0
- package/dist/src/spuren.js.map +1 -0
- package/dist/src/tools.js +1 -1
- package/dist/src/tools.js.map +1 -1
- package/dist/src/versuch.d.ts +179 -0
- package/dist/src/versuch.js +250 -0
- package/dist/src/versuch.js.map +1 -0
- package/dist/src/vorspann.d.ts +73 -0
- package/dist/src/vorspann.js +146 -0
- package/dist/src/vorspann.js.map +1 -0
- package/dist/src/werkzeug-nutzung.d.ts +62 -0
- package/dist/src/werkzeug-nutzung.js +72 -0
- package/dist/src/werkzeug-nutzung.js.map +1 -0
- package/dist/src/wertbeitrag.d.ts +57 -0
- package/dist/src/wertbeitrag.js +73 -0
- package/dist/src/wertbeitrag.js.map +1 -0
- package/glama.json +1 -1
- package/package.json +12 -4
- package/server.json +2 -2
package/dist/src/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { jwtExpiryMs, checkJwt, handleApiError, diagnoseAuth, planAuthHeal, readClientCredentialsFromEnv, buildClientCredentialsBody, clientCredentialsTokenUrl } from './auth.js';
|
|
3
3
|
import { cachlyUrl } from './cachly-url.js';
|
|
4
|
+
import { nutzungsSchluessel } from './werkzeug-nutzung.js';
|
|
5
|
+
import { istLieferung, lieferSchluessel, tokenDerAntwort, hatTreffer, trefferSchluessel } from './geliefert.js';
|
|
4
6
|
import { notify } from './notifier.js';
|
|
5
7
|
import { fileURLToPath } from 'node:url';
|
|
6
8
|
import { createRequire } from 'node:module';
|
|
@@ -47,7 +49,7 @@ const _isMain = process.argv[1] != null &&
|
|
|
47
49
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
48
50
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
49
51
|
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
50
|
-
import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from '@modelcontextprotocol/sdk/types.js';
|
|
52
|
+
import { CallToolRequestSchema, ErrorCode, ListPromptsRequestSchema, ListResourceTemplatesRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, McpError, } from '@modelcontextprotocol/sdk/types.js';
|
|
51
53
|
import { Redis } from 'ioredis';
|
|
52
54
|
// āā Config āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
53
55
|
// Mutable so the `setup`/`init` CLI can honor a `--api-url` flag for self-hosting.
|
|
@@ -593,6 +595,28 @@ export { tokenize, splitMultiQuery, levenshtein, recencyBoost, extractTimestamp,
|
|
|
593
595
|
// āā Connection pool āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
594
596
|
/** Reuse Redis connections across tool calls (keyed by instance_id). */
|
|
595
597
|
const pool = new Map();
|
|
598
|
+
/**
|
|
599
|
+
* Reconnect exactly ONCE, then give up.
|
|
600
|
+
*
|
|
601
|
+
* The old policy was `retryStrategy: () => null` ā fail fast, never reconnect.
|
|
602
|
+
* Measured cost of that policy (2026-08-16, one working session): the FIRST
|
|
603
|
+
* brain call after every idle stretch died with "Connection is closed", the
|
|
604
|
+
* retyped second call worked. Five times in one day, every time the same
|
|
605
|
+
* shape. Cause: the pooled socket goes half-open while idle (NAT/LB drops the
|
|
606
|
+
* TCP session silently); the next command writes into a dead pipe, and with
|
|
607
|
+
* reconnects disabled the error goes straight to the user instead of into one
|
|
608
|
+
* quick reconnect.
|
|
609
|
+
*
|
|
610
|
+
* One retry after 200 ms heals exactly that case. Anything beyond one attempt
|
|
611
|
+
* still fails fast ā the "no reconnect loops in MCP context" concern the old
|
|
612
|
+
* policy protected stays protected: a genuinely down instance costs one extra
|
|
613
|
+
* 200 ms attempt, not a loop.
|
|
614
|
+
*
|
|
615
|
+
* Exported for the test that pins this behaviour down.
|
|
616
|
+
*/
|
|
617
|
+
export function reconnectOnce(times) {
|
|
618
|
+
return times <= 1 ? 200 : null;
|
|
619
|
+
}
|
|
596
620
|
async function getConnection(instance_id) {
|
|
597
621
|
if (!instance_id) {
|
|
598
622
|
// With auto-provisioning, this only happens when the API was briefly
|
|
@@ -680,7 +704,11 @@ async function getConnection(instance_id) {
|
|
|
680
704
|
lazyConnect: true,
|
|
681
705
|
enableReadyCheck: true,
|
|
682
706
|
connectTimeout: 8000,
|
|
683
|
-
retryStrategy:
|
|
707
|
+
retryStrategy: reconnectOnce,
|
|
708
|
+
// One in-flight command survives exactly one reconnect (see reconnectOnce).
|
|
709
|
+
maxRetriesPerRequest: 1,
|
|
710
|
+
// Detect half-open sockets while idle instead of at the next command.
|
|
711
|
+
keepAlive: 15000,
|
|
684
712
|
});
|
|
685
713
|
// Drop the client from the pool AND close its socket on error/end, so a
|
|
686
714
|
// mid-session Redis drop doesn't leak sockets across reconnects. disconnect()
|
|
@@ -768,7 +796,7 @@ import { appendLedgerEntry, readLedger, defaultLedgerPath } from './ambient-ledg
|
|
|
768
796
|
import { loadAmbientMemory, saveAmbientMemory } from './ambient-memory.js';
|
|
769
797
|
import { buildAmbientDeps } from './ambient-deps.js';
|
|
770
798
|
import { resolveApiKey, saveApiKey } from './credentials.js';
|
|
771
|
-
import { buildLessonsMarkdown, buildLessonsJsonl } from './brain-export.js';
|
|
799
|
+
import { buildLessonsMarkdown, buildLessonsJsonl, vonRohLektion } from './brain-export.js';
|
|
772
800
|
import { detectEditor as detectEditorImpl } from './editor.js';
|
|
773
801
|
import { milestoneSent, markMilestoneSent } from './funnel-milestones.js';
|
|
774
802
|
import { kostprobeUebrig, kostprobeVerbrauchen, kostprobeHinweis, schrankeNachKostproben, } from './kostprobe.js';
|
|
@@ -949,6 +977,49 @@ async function featureGate(name, instanceId) {
|
|
|
949
977
|
}
|
|
950
978
|
return { sperre: schrankeNachKostproben(name, pitch, UPGRADE_URL_FG) };
|
|
951
979
|
}
|
|
980
|
+
/**
|
|
981
|
+
* Einen Werkzeug-Aufruf zaehlen (GROW-046).
|
|
982
|
+
*
|
|
983
|
+
* Ein Hash je Brain, ein Feld je Werkzeugname. Gelesen wird das in
|
|
984
|
+
* `brain_metrics` ā bewusst KEIN eigenes Werkzeug dafuer: ein 123. Werkzeug,
|
|
985
|
+
* um zu messen, dass es zu viele Werkzeuge gibt, waere die Pointe, die
|
|
986
|
+
* niemand will.
|
|
987
|
+
*
|
|
988
|
+
* Jeder Fehler wird geschluckt. Eine Messung, die den gemessenen Vorgang
|
|
989
|
+
* stoert, misst am Ende sich selbst.
|
|
990
|
+
*/
|
|
991
|
+
async function zaehleWerkzeug(instanceId, name) {
|
|
992
|
+
try {
|
|
993
|
+
const redis = await getConnection(instanceId);
|
|
994
|
+
await redis.hincrby(nutzungsSchluessel(instanceId), name, 1);
|
|
995
|
+
}
|
|
996
|
+
catch {
|
|
997
|
+
// still ā siehe oben
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
/**
|
|
1001
|
+
* Wie viel kam aus dem Gedaechtnis? ā gezaehlt an der EINEN Stelle, durch die
|
|
1002
|
+
* jede Antwort geht.
|
|
1003
|
+
*
|
|
1004
|
+
* Drei Zahlen, alle drei Summen: gelieferte Token, Abrufe, Abrufe mit Treffer.
|
|
1005
|
+
* Keine davon braucht eine Annahme. Fire-and-forget wie die Werkzeugzaehlung ā
|
|
1006
|
+
* eine Statistik darf eine Antwort nie verzoegern und nie scheitern lassen.
|
|
1007
|
+
*/
|
|
1008
|
+
async function zaehleLieferung(instanceId, name, antwort) {
|
|
1009
|
+
if (!istLieferung(name))
|
|
1010
|
+
return;
|
|
1011
|
+
try {
|
|
1012
|
+
const redis = await getConnection(instanceId);
|
|
1013
|
+
const treffer = hatTreffer(antwort);
|
|
1014
|
+
await redis.incrby(lieferSchluessel(instanceId), treffer ? tokenDerAntwort(antwort) : 0);
|
|
1015
|
+
await redis.hincrby(trefferSchluessel(instanceId), 'abrufe', 1);
|
|
1016
|
+
if (treffer)
|
|
1017
|
+
await redis.hincrby(trefferSchluessel(instanceId), 'treffer', 1);
|
|
1018
|
+
}
|
|
1019
|
+
catch {
|
|
1020
|
+
// still ā siehe oben
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
952
1023
|
async function handleTool(name, args) {
|
|
953
1024
|
// Guard: if no JWT, return actionable onboarding message instead of HTTP 401
|
|
954
1025
|
if (!JWT) {
|
|
@@ -1034,9 +1105,32 @@ async function handleTool(name, args) {
|
|
|
1034
1105
|
kostprobeHinweisText = gate.kostprobe;
|
|
1035
1106
|
sendFunnelEvent('premium_taste_used', { tool: name, instance_id: args.instance_id ?? '' });
|
|
1036
1107
|
}
|
|
1108
|
+
/*
|
|
1109
|
+
* GROW-046: Erst messen, dann zusammenlegen.
|
|
1110
|
+
*
|
|
1111
|
+
* Die Glama-Bewertung sagt, cachly habe zu viele Werkzeuge. Der Einwand ist
|
|
1112
|
+
* plausibel ā nur konnte ihm niemand mit Zahlen begegnen, weil nirgends
|
|
1113
|
+
* gezaehlt wurde, welches Werkzeug wie oft laeuft. Ohne diese Zahl streicht
|
|
1114
|
+
* man das, was man SELBST selten benutzt, und das ist selten dasselbe wie
|
|
1115
|
+
* das, was die Nutzer selten benutzen.
|
|
1116
|
+
*
|
|
1117
|
+
* Gezaehlt wird NACH dem Tarif-Gate: ein abgewiesener Aufruf ist kein
|
|
1118
|
+
* Gebrauch, sondern ein verhinderter. Wer beides zusammenzaehlt, haelt
|
|
1119
|
+
* gesperrte Werkzeuge faelschlich fuer beliebt.
|
|
1120
|
+
*
|
|
1121
|
+
* Fire-and-forget: eine Zaehlung darf einen Aufruf nie verzoegern und nie
|
|
1122
|
+
* scheitern lassen.
|
|
1123
|
+
*/
|
|
1124
|
+
if (typeof args.instance_id === 'string' && args.instance_id) {
|
|
1125
|
+
void zaehleWerkzeug(args.instance_id, name);
|
|
1126
|
+
}
|
|
1037
1127
|
// Delegate brain tools (learn, recall, session, etc.)
|
|
1038
1128
|
const brainResult = await handleBrainTool(name, args, getConnection, apiFetch);
|
|
1039
1129
|
if (brainResult !== null) {
|
|
1130
|
+
// Erst hier steht die Antwort fest ā vorher gibt es nichts zu zaehlen.
|
|
1131
|
+
if (typeof args.instance_id === 'string' && args.instance_id) {
|
|
1132
|
+
void zaehleLieferung(args.instance_id, name, String(brainResult ?? ''));
|
|
1133
|
+
}
|
|
1040
1134
|
if (!_firstCallSuccessSent && JWT) {
|
|
1041
1135
|
_firstCallSuccessSent = true;
|
|
1042
1136
|
// Persistent guard: ambient hooks spawn a fresh process per prompt, so the
|
|
@@ -1351,8 +1445,90 @@ const CACHLY_MCP_INSTRUCTIONS = `This project has a Cachly AI Brain ā persiste
|
|
|
1351
1445
|
- BEFORE a risky op (deploy, migration, dependency bump): call brain_predict.
|
|
1352
1446
|
|
|
1353
1447
|
The Brain instance id comes from the CACHLY_BRAIN_INSTANCE_ID environment variable.`;
|
|
1354
|
-
|
|
1355
|
-
|
|
1448
|
+
/**
|
|
1449
|
+
* Baut einen frischen MCP-Server mit allen Handlern.
|
|
1450
|
+
*
|
|
1451
|
+
* āāā WARUM DAS EINE FABRIK IST UND KEINE KONSTANTE āāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
1452
|
+
*
|
|
1453
|
+
* Im HTTP-Betrieb braucht JEDE Anfrage ihren eigenen Server samt Transport.
|
|
1454
|
+
* Bis zum 17.08.2026 gab es genau einen, beim Start erzeugt und fuer alles
|
|
1455
|
+
* wiederverwendet ā und ein MCP-Server laesst sich nur EINMAL initialisieren.
|
|
1456
|
+
*
|
|
1457
|
+
* Die Folge war unauffaellig und teuer: Der erste initialize nach dem Start
|
|
1458
|
+
* antwortete mit 200, jeder weitere mit 500 und LEEREM Rumpf. Nachgestellt:
|
|
1459
|
+
*
|
|
1460
|
+
* Aufruf 1: HTTP 200
|
|
1461
|
+
* Aufruf 2: HTTP 500
|
|
1462
|
+
* Aufruf 3: HTTP 500
|
|
1463
|
+
*
|
|
1464
|
+
* Nach jedem Ausrollen war der Endpunkt also fuer genau eine Anfrage gesund.
|
|
1465
|
+
* Glama und Smithery haben ihren einen Treffer gelandet und die Antwort
|
|
1466
|
+
* eingefroren ā beide zeigen bis heute 80 Werkzeuge, obwohl es 122 sind. Kein
|
|
1467
|
+
* Log, keine Fehlermeldung, kein roter Alarm. Der Endpunkt galt als "up",
|
|
1468
|
+
* weil /health antwortete.
|
|
1469
|
+
*
|
|
1470
|
+
* Der Aufbau ist billig: ein "new Server" und zwei Handler. TOOLS ist eine
|
|
1471
|
+
* Konstante des Moduls, es wird nichts kopiert.
|
|
1472
|
+
*/
|
|
1473
|
+
/**
|
|
1474
|
+
* Was dieser Server kann. Werkzeuge sind der Kern; Ressourcen und Prompts
|
|
1475
|
+
* fuehren wir mit LEEREN Listen.
|
|
1476
|
+
*
|
|
1477
|
+
* āāā WARUM LEER UND NICHT WEGGELASSEN (18.08.2026) āāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
1478
|
+
*
|
|
1479
|
+
* Genau genommen war das Weglassen richtig: Wer eine Faehigkeit nicht
|
|
1480
|
+
* ankuendigt, darf auf `resources/list` mit -32601 "Method not found"
|
|
1481
|
+
* antworten, und das ist die vorgesehene Antwort.
|
|
1482
|
+
*
|
|
1483
|
+
* Nur liest sie niemand so. Im Scan-Protokoll von Smithery stand:
|
|
1484
|
+
*
|
|
1485
|
+
* [scan] Capabilities found: 122 tools. Warning: Failed to list resources:
|
|
1486
|
+
* MCP error -32601: Method not found; Failed to list prompts: ...
|
|
1487
|
+
*
|
|
1488
|
+
* Ein Pruefer, der "Failed" protokolliert, faellt nicht auf die Feinheit
|
|
1489
|
+
* herein, dass unser Nein das korrekte Nein war. Aus einer sauberen Antwort
|
|
1490
|
+
* wird eine Warnzeile im Bericht ā und Warnzeilen kosten Noten in den
|
|
1491
|
+
* Verzeichnissen, ohne dass irgendwo etwas kaputt ist.
|
|
1492
|
+
*
|
|
1493
|
+
* Eine leere Liste sagt dasselbe wie der Fehler ("hier gibt es nichts"), aber
|
|
1494
|
+
* in einer Form, die jeder Aufrufer richtig versteht. Das ist keine
|
|
1495
|
+
* Beschoenigung: Wir behaupten nicht, Ressourcen zu haben, wir liefern
|
|
1496
|
+
* nachpruefbar null Stueck.
|
|
1497
|
+
*/
|
|
1498
|
+
const CAPABILITIES = { tools: {}, resources: {}, prompts: {} };
|
|
1499
|
+
const listToolsHandler = async () => ({ tools: TOOLS });
|
|
1500
|
+
// Bewusst Funktionsdeklarationen statt const: makeServer() steht weiter oben
|
|
1501
|
+
// als callToolHandler, und Deklarationen werden hochgezogen. So gibt es EINE
|
|
1502
|
+
// Stelle je Handler, nicht eine pro Server-Aufbau.
|
|
1503
|
+
async function listResourcesHandler() {
|
|
1504
|
+
return { resources: [] };
|
|
1505
|
+
}
|
|
1506
|
+
async function listResourceTemplatesHandler() {
|
|
1507
|
+
return { resourceTemplates: [] };
|
|
1508
|
+
}
|
|
1509
|
+
async function listPromptsHandler() {
|
|
1510
|
+
return { prompts: [] };
|
|
1511
|
+
}
|
|
1512
|
+
/** Haengt alle Handler an einen Server. Beide Betriebsarten teilen sich diese
|
|
1513
|
+
* eine Liste ā sonst kennt der HTTP-Server bald etwas, das der stdio-Server
|
|
1514
|
+
* nicht kennt, und niemand merkt es. */
|
|
1515
|
+
function registriereHandler(s) {
|
|
1516
|
+
s.setRequestHandler(ListToolsRequestSchema, listToolsHandler);
|
|
1517
|
+
s.setRequestHandler(CallToolRequestSchema, callToolHandler);
|
|
1518
|
+
s.setRequestHandler(ListResourcesRequestSchema, listResourcesHandler);
|
|
1519
|
+
s.setRequestHandler(ListResourceTemplatesRequestSchema, listResourceTemplatesHandler);
|
|
1520
|
+
s.setRequestHandler(ListPromptsRequestSchema, listPromptsHandler);
|
|
1521
|
+
}
|
|
1522
|
+
function makeServer() {
|
|
1523
|
+
const s = new Server({ name: 'cachly-mcp', version: CURRENT_VERSION }, { capabilities: CAPABILITIES, instructions: CACHLY_MCP_INSTRUCTIONS });
|
|
1524
|
+
registriereHandler(s);
|
|
1525
|
+
return s;
|
|
1526
|
+
}
|
|
1527
|
+
const server = new Server({ name: 'cachly-mcp', version: CURRENT_VERSION }, { capabilities: CAPABILITIES, instructions: CACHLY_MCP_INSTRUCTIONS });
|
|
1528
|
+
server.setRequestHandler(ListToolsRequestSchema, listToolsHandler);
|
|
1529
|
+
server.setRequestHandler(ListResourcesRequestSchema, listResourcesHandler);
|
|
1530
|
+
server.setRequestHandler(ListResourceTemplatesRequestSchema, listResourceTemplatesHandler);
|
|
1531
|
+
server.setRequestHandler(ListPromptsRequestSchema, listPromptsHandler);
|
|
1356
1532
|
// āā Auto-session management āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
1357
1533
|
// Transparently starts a Brain session on the first tool call of a process
|
|
1358
1534
|
// and saves a session_end summary on SIGTERM/exit so users never have to call
|
|
@@ -1423,7 +1599,7 @@ async function autoEndSession() {
|
|
|
1423
1599
|
}
|
|
1424
1600
|
catch { /* non-fatal */ }
|
|
1425
1601
|
}
|
|
1426
|
-
|
|
1602
|
+
const callToolHandler = async (request) => {
|
|
1427
1603
|
const { name, arguments: args } = request.params;
|
|
1428
1604
|
// Auto-start brain session on first tool call that has an instance_id.
|
|
1429
1605
|
// Skip session management tools to avoid recursion.
|
|
@@ -1456,7 +1632,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1456
1632
|
notify('cachly', 'tool_error', { tool: name, error: msg }).catch(() => undefined);
|
|
1457
1633
|
throw new McpError(ErrorCode.InternalError, msg);
|
|
1458
1634
|
}
|
|
1459
|
-
}
|
|
1635
|
+
};
|
|
1636
|
+
server.setRequestHandler(CallToolRequestSchema, callToolHandler);
|
|
1460
1637
|
// Graceful shutdown ā close all Redis connections + auto-end brain session
|
|
1461
1638
|
process.on('SIGTERM', async () => {
|
|
1462
1639
|
await autoEndSession().catch(() => undefined);
|
|
@@ -1865,23 +2042,60 @@ if (process.argv[2] === 'export') {
|
|
|
1865
2042
|
}
|
|
1866
2043
|
process.stdout.write('\nš¤ Exporting your Brain...\n\n');
|
|
1867
2044
|
try {
|
|
1868
|
-
|
|
2045
|
+
// āā Warum /export und nicht /memory āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
2046
|
+
//
|
|
2047
|
+
// Bis zum 19.08.2026 stand hier /memory. Der Endpunkt ist die
|
|
2048
|
+
// Dashboard-Zusammenfassung: oberste 50 Lektionen, what_worked auf 120
|
|
2049
|
+
// Zeichen gekuerzt. Gemessen am 19.08.2026 an einem Brain mit 493
|
|
2050
|
+
// Lektionen: der Export lieferte 50 Stueck mit mitten im Wort
|
|
2051
|
+
// abgeschnittenen Saetzen ā 10 Prozent, unlesbar.
|
|
2052
|
+
//
|
|
2053
|
+
// Bei einem Produkt, das "your memory, not the vendor's" verspricht, ist
|
|
2054
|
+
// das kein Schoenheitsfehler. /export liefert alles, ungekuerzt.
|
|
2055
|
+
let lessons = [];
|
|
2056
|
+
let unvollstaendig = '';
|
|
2057
|
+
const expRes = await fetch(`${API_URL}/api/v1/instances/${instanceId}/export`, {
|
|
1869
2058
|
headers: { Authorization: `Bearer ${apiKey}` },
|
|
1870
|
-
signal: AbortSignal.timeout(
|
|
2059
|
+
signal: AbortSignal.timeout(120000), // alles lesen dauert laenger als eine Zusammenfassung
|
|
1871
2060
|
});
|
|
1872
|
-
if (
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
2061
|
+
if (expRes.ok) {
|
|
2062
|
+
const dump = await expRes.json();
|
|
2063
|
+
lessons = (dump.lessons ?? [])
|
|
2064
|
+
.map(vonRohLektion)
|
|
2065
|
+
.filter((l) => l !== null);
|
|
2066
|
+
// Der Server sagt selbst, ob er alles hatte. Diese Antwort wird
|
|
2067
|
+
// weitergereicht statt geschluckt ā ein abgeschnittener Export, der wie
|
|
2068
|
+
// ein vollstaendiger aussieht, ist schlimmer als ein Fehler.
|
|
2069
|
+
if (dump.complete === false)
|
|
2070
|
+
unvollstaendig = dump.note ?? 'the server reported an incomplete read';
|
|
2071
|
+
}
|
|
2072
|
+
else if (expRes.status === 404) {
|
|
2073
|
+
// Aeltere Server kennen /export noch nicht. Dann gibt es die
|
|
2074
|
+
// Zusammenfassung ā aber der Nutzer erfaehrt, dass er sie bekommt.
|
|
2075
|
+
const memRes = await fetch(`${API_URL}/api/v1/instances/${instanceId}/memory`, {
|
|
2076
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
2077
|
+
signal: AbortSignal.timeout(8000),
|
|
2078
|
+
});
|
|
2079
|
+
if (!memRes.ok)
|
|
2080
|
+
throw new Error(`memory HTTP ${memRes.status}`);
|
|
2081
|
+
const mem = await memRes.json();
|
|
2082
|
+
lessons = (mem.top_lessons ?? []).map((l) => ({
|
|
2083
|
+
topic: l.topic,
|
|
2084
|
+
outcome: l.outcome,
|
|
2085
|
+
severity: l.severity,
|
|
2086
|
+
tags: l.tags,
|
|
2087
|
+
whatWorked: l.what_worked,
|
|
2088
|
+
ts: l.ts,
|
|
2089
|
+
author: l.author,
|
|
2090
|
+
recallCount: l.recall_count,
|
|
2091
|
+
}));
|
|
2092
|
+
unvollstaendig =
|
|
2093
|
+
'this server has no /export endpoint yet, so this is the DASHBOARD SUMMARY: ' +
|
|
2094
|
+
'at most 50 lessons, each shortened to 120 characters. Update the server for a full export.';
|
|
2095
|
+
}
|
|
2096
|
+
else {
|
|
2097
|
+
throw new Error(`export HTTP ${expRes.status}`);
|
|
2098
|
+
}
|
|
1885
2099
|
if (lessons.length === 0) {
|
|
1886
2100
|
console.log(' No lessons in this Brain yet ā nothing to export.\n');
|
|
1887
2101
|
process.exit(0);
|
|
@@ -1898,7 +2112,14 @@ if (process.argv[2] === 'export') {
|
|
|
1898
2112
|
await writeFile(jsonlPath, buildLessonsJsonl(lessons), 'utf-8');
|
|
1899
2113
|
written.push(`${lessons.length} lesson${lessons.length === 1 ? '' : 's'} ā ${jsonlPath}`);
|
|
1900
2114
|
}
|
|
1901
|
-
|
|
2115
|
+
if (unvollstaendig) {
|
|
2116
|
+
console.log('ā ļø Export INCOMPLETE ā this is not your whole Brain:');
|
|
2117
|
+
console.log(` ${unvollstaendig}`);
|
|
2118
|
+
console.log('');
|
|
2119
|
+
}
|
|
2120
|
+
else {
|
|
2121
|
+
console.log('ā
Export complete:');
|
|
2122
|
+
}
|
|
1902
2123
|
for (const line of written)
|
|
1903
2124
|
console.log(` ${line}`);
|
|
1904
2125
|
console.log('');
|
|
@@ -4045,8 +4266,6 @@ if (httpPort) {
|
|
|
4045
4266
|
// āā HTTP mode (Streamable HTTP transport) āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
4046
4267
|
// Used for Smithery URL deployment: PORT=3000 node dist/src/index.js
|
|
4047
4268
|
const { createServer } = await import('node:http');
|
|
4048
|
-
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
|
|
4049
|
-
await server.connect(transport);
|
|
4050
4269
|
const httpServer = createServer((req, res) => {
|
|
4051
4270
|
const url = new URL(req.url ?? '/', `http://localhost:${httpPort}`);
|
|
4052
4271
|
if (url.pathname === '/health') {
|
|
@@ -4062,7 +4281,34 @@ if (httpPort) {
|
|
|
4062
4281
|
return;
|
|
4063
4282
|
}
|
|
4064
4283
|
if (url.pathname === '/mcp' || url.pathname === '/') {
|
|
4065
|
-
|
|
4284
|
+
// JEDE Anfrage bekommt einen eigenen Server samt Transport.
|
|
4285
|
+
//
|
|
4286
|
+
// Vorher gab es genau einen, beim Start erzeugt. Ein MCP-Server laesst
|
|
4287
|
+
// sich aber nur EINMAL initialisieren ā der erste Aufruf nach dem Start
|
|
4288
|
+
// ging durch, jeder weitere endete mit 500 und leerem Rumpf. Siehe die
|
|
4289
|
+
// Begruendung bei makeServer().
|
|
4290
|
+
void (async () => {
|
|
4291
|
+
const s = makeServer();
|
|
4292
|
+
const t = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
|
|
4293
|
+
// Aufraeumen, sobald die Antwort durch ist ā sonst haelt jede Anfrage
|
|
4294
|
+
// ihren Server fest und der Speicher waechst mit jedem Aufruf.
|
|
4295
|
+
res.on('close', () => {
|
|
4296
|
+
t.close().catch(() => undefined);
|
|
4297
|
+
s.close().catch(() => undefined);
|
|
4298
|
+
});
|
|
4299
|
+
try {
|
|
4300
|
+
await s.connect(t);
|
|
4301
|
+
await t.handleRequest(req, res);
|
|
4302
|
+
}
|
|
4303
|
+
catch (err) {
|
|
4304
|
+
// Ein Fehler ohne Text war genau das Problem, das hier behoben wird.
|
|
4305
|
+
process.stderr.write(`cachly-mcp HTTP: ${err.message}\n`);
|
|
4306
|
+
if (!res.headersSent) {
|
|
4307
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
4308
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
4309
|
+
}
|
|
4310
|
+
}
|
|
4311
|
+
})();
|
|
4066
4312
|
return;
|
|
4067
4313
|
}
|
|
4068
4314
|
res.writeHead(404);
|