@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.
Files changed (88) hide show
  1. package/dist/index.d.ts +1 -0
  2. package/dist/index.js +7 -0
  3. package/dist/index.js.map +1 -0
  4. package/dist/src/aussetzer.d.ts +67 -0
  5. package/dist/src/aussetzer.js +82 -0
  6. package/dist/src/aussetzer.js.map +1 -0
  7. package/dist/src/autor-abzeichen.d.ts +41 -0
  8. package/dist/src/autor-abzeichen.js +56 -0
  9. package/dist/src/autor-abzeichen.js.map +1 -0
  10. package/dist/src/autor.d.ts +101 -0
  11. package/dist/src/autor.js +136 -0
  12. package/dist/src/autor.js.map +1 -0
  13. package/dist/src/bauabdruck-daten.generiert.json +5 -0
  14. package/dist/src/bauabdruck.d.ts +38 -0
  15. package/dist/src/bauabdruck.js +93 -0
  16. package/dist/src/bauabdruck.js.map +1 -0
  17. package/dist/src/bedeutung.d.ts +175 -0
  18. package/dist/src/bedeutung.js +254 -0
  19. package/dist/src/bedeutung.js.map +1 -0
  20. package/dist/src/brain-export.d.ts +24 -0
  21. package/dist/src/brain-export.js +31 -1
  22. package/dist/src/brain-export.js.map +1 -1
  23. package/dist/src/deutsch.d.ts +98 -0
  24. package/dist/src/deutsch.js +223 -0
  25. package/dist/src/deutsch.js.map +1 -0
  26. package/dist/src/eingaenge-nachruesten.d.ts +41 -0
  27. package/dist/src/eingaenge-nachruesten.js +290 -0
  28. package/dist/src/eingaenge-nachruesten.js.map +1 -0
  29. package/dist/src/eingaenge.d.ts +135 -0
  30. package/dist/src/eingaenge.js +228 -0
  31. package/dist/src/eingaenge.js.map +1 -0
  32. package/dist/src/geliefert.d.ts +63 -0
  33. package/dist/src/geliefert.js +114 -0
  34. package/dist/src/geliefert.js.map +1 -0
  35. package/dist/src/handlers/advanced.js +7 -3
  36. package/dist/src/handlers/advanced.js.map +1 -1
  37. package/dist/src/handlers/brain.d.ts +19 -0
  38. package/dist/src/handlers/brain.js +536 -48
  39. package/dist/src/handlers/brain.js.map +1 -1
  40. package/dist/src/handlers/instances.d.ts +24 -0
  41. package/dist/src/handlers/instances.js +49 -1
  42. package/dist/src/handlers/instances.js.map +1 -1
  43. package/dist/src/handlers/team.js +105 -0
  44. package/dist/src/handlers/team.js.map +1 -1
  45. package/dist/src/index.d.ts +20 -0
  46. package/dist/src/index.js +272 -26
  47. package/dist/src/index.js.map +1 -1
  48. package/dist/src/lesson-preview.d.ts +81 -0
  49. package/dist/src/lesson-preview.js +133 -0
  50. package/dist/src/lesson-preview.js.map +1 -0
  51. package/dist/src/rangfolge.d.ts +169 -0
  52. package/dist/src/rangfolge.js +214 -0
  53. package/dist/src/rangfolge.js.map +1 -0
  54. package/dist/src/raumkorrektur.d.ts +100 -0
  55. package/dist/src/raumkorrektur.js +197 -0
  56. package/dist/src/raumkorrektur.js.map +1 -0
  57. package/dist/src/recall-tiefe.d.ts +48 -0
  58. package/dist/src/recall-tiefe.js +42 -0
  59. package/dist/src/recall-tiefe.js.map +1 -0
  60. package/dist/src/rerank.d.ts +0 -14
  61. package/dist/src/rerank.js +39 -2
  62. package/dist/src/rerank.js.map +1 -1
  63. package/dist/src/search.d.ts +56 -17
  64. package/dist/src/search.js +335 -73
  65. package/dist/src/search.js.map +1 -1
  66. package/dist/src/seltenheitsbestand.d.ts +53 -0
  67. package/dist/src/seltenheitsbestand.js +92 -0
  68. package/dist/src/seltenheitsbestand.js.map +1 -0
  69. package/dist/src/spuren.d.ts +97 -0
  70. package/dist/src/spuren.js +150 -0
  71. package/dist/src/spuren.js.map +1 -0
  72. package/dist/src/tools.js +1 -1
  73. package/dist/src/tools.js.map +1 -1
  74. package/dist/src/versuch.d.ts +179 -0
  75. package/dist/src/versuch.js +250 -0
  76. package/dist/src/versuch.js.map +1 -0
  77. package/dist/src/vorspann.d.ts +73 -0
  78. package/dist/src/vorspann.js +146 -0
  79. package/dist/src/vorspann.js.map +1 -0
  80. package/dist/src/werkzeug-nutzung.d.ts +62 -0
  81. package/dist/src/werkzeug-nutzung.js +72 -0
  82. package/dist/src/werkzeug-nutzung.js.map +1 -0
  83. package/dist/src/wertbeitrag.d.ts +57 -0
  84. package/dist/src/wertbeitrag.js +73 -0
  85. package/dist/src/wertbeitrag.js.map +1 -0
  86. package/glama.json +1 -1
  87. package/package.json +12 -4
  88. 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: () => null, // fail fast, no reconnect loops in MCP context
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
- const server = new Server({ name: 'cachly-mcp', version: CURRENT_VERSION }, { capabilities: { tools: {} }, instructions: CACHLY_MCP_INSTRUCTIONS });
1355
- server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
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
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
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
- const memRes = await fetch(`${API_URL}/api/v1/instances/${instanceId}/memory`, {
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(8000),
2059
+ signal: AbortSignal.timeout(120000), // alles lesen dauert laenger als eine Zusammenfassung
1871
2060
  });
1872
- if (!memRes.ok)
1873
- throw new Error(`memory HTTP ${memRes.status}`);
1874
- const mem = await memRes.json();
1875
- const lessons = (mem.top_lessons ?? []).map((l) => ({
1876
- topic: l.topic,
1877
- outcome: l.outcome,
1878
- severity: l.severity,
1879
- tags: l.tags,
1880
- whatWorked: l.what_worked,
1881
- ts: l.ts,
1882
- author: l.author,
1883
- recallCount: l.recall_count,
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
- console.log('āœ… Export complete:');
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
- transport.handleRequest(req, res);
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);