@cachly-dev/mcp-server 0.10.48 → 0.10.50

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 (325) hide show
  1. package/.github/workflows/eas-build-android.yml +58 -0
  2. package/.github/workflows/eas-update.yml +55 -0
  3. package/BENCH.md +80 -0
  4. package/README.md +5 -33
  5. package/STRATEGY.md +218 -0
  6. package/apps/travel-chaos-organizer/.env.example +49 -0
  7. package/apps/travel-chaos-organizer/DEPLOY.md +328 -0
  8. package/apps/travel-chaos-organizer/Makefile +57 -0
  9. package/apps/travel-chaos-organizer/README.md +159 -0
  10. package/apps/travel-chaos-organizer/backend/.env.example +10 -0
  11. package/apps/travel-chaos-organizer/backend/Dockerfile +25 -0
  12. package/apps/travel-chaos-organizer/backend/alembic/env.py +56 -0
  13. package/apps/travel-chaos-organizer/backend/alembic/script.py.mako +24 -0
  14. package/apps/travel-chaos-organizer/backend/alembic/versions/.gitkeep +0 -0
  15. package/apps/travel-chaos-organizer/backend/alembic/versions/0001_stripe_customer_id.py +24 -0
  16. package/apps/travel-chaos-organizer/backend/alembic/versions/0002_telegram_chat_id.py +24 -0
  17. package/apps/travel-chaos-organizer/backend/alembic.ini +39 -0
  18. package/apps/travel-chaos-organizer/backend/app/__init__.py +0 -0
  19. package/apps/travel-chaos-organizer/backend/app/auth/__init__.py +0 -0
  20. package/apps/travel-chaos-organizer/backend/app/auth/keycloak.py +81 -0
  21. package/apps/travel-chaos-organizer/backend/app/config.py +66 -0
  22. package/apps/travel-chaos-organizer/backend/app/db/__init__.py +0 -0
  23. package/apps/travel-chaos-organizer/backend/app/db/database.py +20 -0
  24. package/apps/travel-chaos-organizer/backend/app/db/migrations/001_init.sql +71 -0
  25. package/apps/travel-chaos-organizer/backend/app/db/migrations/002_telemetry_freemium.sql +31 -0
  26. package/apps/travel-chaos-organizer/backend/app/db/migrations/003_telegram_chat_id.sql +4 -0
  27. package/apps/travel-chaos-organizer/backend/app/db/migrations/004_stripe_and_email.sql +5 -0
  28. package/apps/travel-chaos-organizer/backend/app/limiter.py +4 -0
  29. package/apps/travel-chaos-organizer/backend/app/logging_config.py +25 -0
  30. package/apps/travel-chaos-organizer/backend/app/main.py +77 -0
  31. package/apps/travel-chaos-organizer/backend/app/models/__init__.py +0 -0
  32. package/apps/travel-chaos-organizer/backend/app/models/schemas.py +142 -0
  33. package/apps/travel-chaos-organizer/backend/app/routers/__init__.py +0 -0
  34. package/apps/travel-chaos-organizer/backend/app/routers/admin.py +237 -0
  35. package/apps/travel-chaos-organizer/backend/app/routers/cache.py +13 -0
  36. package/apps/travel-chaos-organizer/backend/app/routers/events.py +29 -0
  37. package/apps/travel-chaos-organizer/backend/app/routers/export.py +122 -0
  38. package/apps/travel-chaos-organizer/backend/app/routers/inbox.py +122 -0
  39. package/apps/travel-chaos-organizer/backend/app/routers/items.py +130 -0
  40. package/apps/travel-chaos-organizer/backend/app/routers/mail.py +108 -0
  41. package/apps/travel-chaos-organizer/backend/app/routers/parse.py +303 -0
  42. package/apps/travel-chaos-organizer/backend/app/routers/payments.py +179 -0
  43. package/apps/travel-chaos-organizer/backend/app/routers/trips.py +134 -0
  44. package/apps/travel-chaos-organizer/backend/app/routers/users.py +169 -0
  45. package/apps/travel-chaos-organizer/backend/app/routers/waitlist.py +37 -0
  46. package/apps/travel-chaos-organizer/backend/app/services/__init__.py +0 -0
  47. package/apps/travel-chaos-organizer/backend/app/services/cache.py +131 -0
  48. package/apps/travel-chaos-organizer/backend/app/services/drip.py +128 -0
  49. package/apps/travel-chaos-organizer/backend/app/services/email.py +61 -0
  50. package/apps/travel-chaos-organizer/backend/app/services/errors.py +44 -0
  51. package/apps/travel-chaos-organizer/backend/app/services/notifier.py +10 -0
  52. package/apps/travel-chaos-organizer/backend/app/services/ollama.py +101 -0
  53. package/apps/travel-chaos-organizer/backend/app/services/parser.py +22 -0
  54. package/apps/travel-chaos-organizer/backend/app/services/quota.py +85 -0
  55. package/apps/travel-chaos-organizer/backend/app/services/sentry.py +11 -0
  56. package/apps/travel-chaos-organizer/backend/app/services/telemetry.py +35 -0
  57. package/apps/travel-chaos-organizer/backend/pytest.ini +4 -0
  58. package/apps/travel-chaos-organizer/backend/requirements-dev.txt +6 -0
  59. package/apps/travel-chaos-organizer/backend/requirements.txt +25 -0
  60. package/apps/travel-chaos-organizer/backend/tests/__init__.py +0 -0
  61. package/apps/travel-chaos-organizer/backend/tests/conftest.py +223 -0
  62. package/apps/travel-chaos-organizer/backend/tests/test_admin.py +99 -0
  63. package/apps/travel-chaos-organizer/backend/tests/test_cache.py +142 -0
  64. package/apps/travel-chaos-organizer/backend/tests/test_drip.py +32 -0
  65. package/apps/travel-chaos-organizer/backend/tests/test_e2e_chain.py +121 -0
  66. package/apps/travel-chaos-organizer/backend/tests/test_export.py +48 -0
  67. package/apps/travel-chaos-organizer/backend/tests/test_inbox.py +107 -0
  68. package/apps/travel-chaos-organizer/backend/tests/test_items.py +138 -0
  69. package/apps/travel-chaos-organizer/backend/tests/test_mail.py +113 -0
  70. package/apps/travel-chaos-organizer/backend/tests/test_ollama.py +145 -0
  71. package/apps/travel-chaos-organizer/backend/tests/test_parse_url.py +98 -0
  72. package/apps/travel-chaos-organizer/backend/tests/test_parser.py +72 -0
  73. package/apps/travel-chaos-organizer/backend/tests/test_payments.py +163 -0
  74. package/apps/travel-chaos-organizer/backend/tests/test_quota.py +46 -0
  75. package/apps/travel-chaos-organizer/backend/tests/test_search.py +67 -0
  76. package/apps/travel-chaos-organizer/backend/tests/test_trips.py +123 -0
  77. package/apps/travel-chaos-organizer/backend/tests/test_users.py +52 -0
  78. package/apps/travel-chaos-organizer/backups/.gitkeep +0 -0
  79. package/apps/travel-chaos-organizer/caddy/Caddyfile +27 -0
  80. package/apps/travel-chaos-organizer/docker-compose.node1.yml +31 -0
  81. package/apps/travel-chaos-organizer/docker-compose.override.yml +14 -0
  82. package/apps/travel-chaos-organizer/docker-compose.yml +128 -0
  83. package/apps/travel-chaos-organizer/frontend/.env.example +13 -0
  84. package/apps/travel-chaos-organizer/frontend/app/(app)/_layout.tsx +80 -0
  85. package/apps/travel-chaos-organizer/frontend/app/(app)/inbox.tsx +166 -0
  86. package/apps/travel-chaos-organizer/frontend/app/(app)/search.tsx +130 -0
  87. package/apps/travel-chaos-organizer/frontend/app/(app)/settings.tsx +381 -0
  88. package/apps/travel-chaos-organizer/frontend/app/(app)/trips/[id].tsx +346 -0
  89. package/apps/travel-chaos-organizer/frontend/app/(app)/trips.tsx +363 -0
  90. package/apps/travel-chaos-organizer/frontend/app/(app)/upgrade/cancel.tsx +34 -0
  91. package/apps/travel-chaos-organizer/frontend/app/(app)/upgrade/success.tsx +35 -0
  92. package/apps/travel-chaos-organizer/frontend/app/(auth)/login.tsx +91 -0
  93. package/apps/travel-chaos-organizer/frontend/app/_layout.tsx +71 -0
  94. package/apps/travel-chaos-organizer/frontend/app/auth.tsx +29 -0
  95. package/apps/travel-chaos-organizer/frontend/app/index.tsx +16 -0
  96. package/apps/travel-chaos-organizer/frontend/app/onboarding.tsx +119 -0
  97. package/apps/travel-chaos-organizer/frontend/app.config.ts +54 -0
  98. package/apps/travel-chaos-organizer/frontend/assets/adaptive-icon.svg +66 -0
  99. package/apps/travel-chaos-organizer/frontend/assets/icon.svg +79 -0
  100. package/apps/travel-chaos-organizer/frontend/assets/splash.svg +15 -0
  101. package/apps/travel-chaos-organizer/frontend/components/BottomSheet.tsx +102 -0
  102. package/apps/travel-chaos-organizer/frontend/components/ConfettiCannon.tsx +107 -0
  103. package/apps/travel-chaos-organizer/frontend/components/ConfirmDialog.tsx +109 -0
  104. package/apps/travel-chaos-organizer/frontend/components/ErrorBoundary.tsx +45 -0
  105. package/apps/travel-chaos-organizer/frontend/components/FileUploadButton.tsx +19 -0
  106. package/apps/travel-chaos-organizer/frontend/components/ImportSheet.tsx +155 -0
  107. package/apps/travel-chaos-organizer/frontend/components/OfflineBadge.tsx +55 -0
  108. package/apps/travel-chaos-organizer/frontend/components/QRCodeViewer.tsx +80 -0
  109. package/apps/travel-chaos-organizer/frontend/components/ScoreRing.tsx +39 -0
  110. package/apps/travel-chaos-organizer/frontend/components/Screen.tsx +39 -0
  111. package/apps/travel-chaos-organizer/frontend/components/Skeleton.tsx +70 -0
  112. package/apps/travel-chaos-organizer/frontend/components/SwipeableInboxItem.tsx +121 -0
  113. package/apps/travel-chaos-organizer/frontend/components/SwipeableTimelineItem.tsx +70 -0
  114. package/apps/travel-chaos-organizer/frontend/components/TimelineItem.tsx +73 -0
  115. package/apps/travel-chaos-organizer/frontend/components/TimelineItemDetail.tsx +130 -0
  116. package/apps/travel-chaos-organizer/frontend/components/Toast.tsx +60 -0
  117. package/apps/travel-chaos-organizer/frontend/components/ToastContext.tsx +42 -0
  118. package/apps/travel-chaos-organizer/frontend/components/TripCard.tsx +146 -0
  119. package/apps/travel-chaos-organizer/frontend/components/TripQRModal.tsx +70 -0
  120. package/apps/travel-chaos-organizer/frontend/components/UpgradeBanner.tsx +44 -0
  121. package/apps/travel-chaos-organizer/frontend/eas.json +22 -0
  122. package/apps/travel-chaos-organizer/frontend/hooks/useInbox.ts +24 -0
  123. package/apps/travel-chaos-organizer/frontend/hooks/useNetworkSync.ts +60 -0
  124. package/apps/travel-chaos-organizer/frontend/hooks/useTrips.ts +112 -0
  125. package/apps/travel-chaos-organizer/frontend/lib/api.ts +223 -0
  126. package/apps/travel-chaos-organizer/frontend/lib/auth.ts +93 -0
  127. package/apps/travel-chaos-organizer/frontend/lib/db.ts +86 -0
  128. package/apps/travel-chaos-organizer/frontend/lib/fileCache.ts +54 -0
  129. package/apps/travel-chaos-organizer/frontend/lib/haptics.ts +13 -0
  130. package/apps/travel-chaos-organizer/frontend/lib/notifications.ts +112 -0
  131. package/apps/travel-chaos-organizer/frontend/lib/offlineQueue.ts +145 -0
  132. package/apps/travel-chaos-organizer/frontend/lib/quota.tsx +43 -0
  133. package/apps/travel-chaos-organizer/frontend/lib/sentry.ts +28 -0
  134. package/apps/travel-chaos-organizer/frontend/lib/shareIntent.ts +44 -0
  135. package/apps/travel-chaos-organizer/frontend/package-lock.json +13221 -0
  136. package/apps/travel-chaos-organizer/frontend/package.json +44 -0
  137. package/apps/travel-chaos-organizer/frontend/tsconfig.json +23 -0
  138. package/apps/travel-chaos-organizer/keycloak/realm-export.json +59 -0
  139. package/apps/travel-chaos-organizer/landing/index.html +773 -0
  140. package/apps/travel-chaos-organizer/scripts/backup.sh +14 -0
  141. package/apps/travel-chaos-organizer/scripts/configure-env.sh +82 -0
  142. package/apps/travel-chaos-organizer/scripts/deploy.sh +54 -0
  143. package/apps/travel-chaos-organizer/scripts/health-check.sh +36 -0
  144. package/apps/travel-chaos-organizer/scripts/migrate.sh +7 -0
  145. package/apps/travel-chaos-organizer/scripts/server-setup.sh +85 -0
  146. package/apps/travel-chaos-organizer/scripts/smoke-test.sh +29 -0
  147. package/apps/travel-chaos-organizer/scripts/verify-tracking.sh +71 -0
  148. package/apps/travel-chaos-organizer/telegram-bot/.env.example +3 -0
  149. package/apps/travel-chaos-organizer/telegram-bot/Dockerfile +6 -0
  150. package/apps/travel-chaos-organizer/telegram-bot/bot.py +504 -0
  151. package/apps/travel-chaos-organizer/telegram-bot/requirements.txt +3 -0
  152. package/dist/packages/telegram-notify/client.d.ts +19 -0
  153. package/dist/packages/telegram-notify/client.js +75 -0
  154. package/dist/packages/telegram-notify/client.js.map +1 -0
  155. package/dist/src/__tests__/auth.test.js.map +1 -0
  156. package/dist/{__tests__ → src/__tests__}/brain-flow.test.js +141 -0
  157. package/dist/src/__tests__/brain-flow.test.js.map +1 -0
  158. package/dist/src/__tests__/confidence.test.js.map +1 -0
  159. package/dist/src/__tests__/e2e/api-auth.test.js.map +1 -0
  160. package/dist/src/__tests__/e2e/api-health.test.js.map +1 -0
  161. package/dist/src/__tests__/e2e/cli.test.js.map +1 -0
  162. package/dist/src/__tests__/e2e/config.js.map +1 -0
  163. package/dist/src/__tests__/e2e/funnel.test.js.map +1 -0
  164. package/dist/src/__tests__/e2e/mcp-tools.test.js.map +1 -0
  165. package/dist/src/__tests__/e2e/provisioning.test.js.map +1 -0
  166. package/dist/{__tests__ → src/__tests__}/handlers-unit.test.js +0 -2
  167. package/dist/src/__tests__/handlers-unit.test.js.map +1 -0
  168. package/dist/src/__tests__/namespace.test.js.map +1 -0
  169. package/dist/src/__tests__/rerank.test.js +98 -0
  170. package/dist/src/__tests__/rerank.test.js.map +1 -0
  171. package/dist/src/auth.js.map +1 -0
  172. package/dist/src/bench/cachly-bench.d.ts +35 -0
  173. package/dist/src/bench/cachly-bench.js +182 -0
  174. package/dist/src/bench/cachly-bench.js.map +1 -0
  175. package/dist/src/bench/fixtures.d.ts +34 -0
  176. package/dist/src/bench/fixtures.js +199 -0
  177. package/dist/src/bench/fixtures.js.map +1 -0
  178. package/dist/src/ckg.js.map +1 -0
  179. package/dist/src/confidence.js.map +1 -0
  180. package/dist/src/embeddings.js.map +1 -0
  181. package/dist/src/handlers/advanced.js.map +1 -0
  182. package/dist/{handlers → src/handlers}/brain.js +261 -87
  183. package/dist/src/handlers/brain.js.map +1 -0
  184. package/dist/src/handlers/cache.js.map +1 -0
  185. package/dist/{handlers → src/handlers}/context.js +9 -4
  186. package/dist/src/handlers/context.js.map +1 -0
  187. package/dist/{handlers → src/handlers}/fedbrain.d.ts +6 -0
  188. package/dist/{handlers → src/handlers}/fedbrain.js +19 -7
  189. package/dist/src/handlers/fedbrain.js.map +1 -0
  190. package/dist/src/handlers/instances.js.map +1 -0
  191. package/dist/{handlers → src/handlers}/roadmap.js +7 -3
  192. package/dist/src/handlers/roadmap.js.map +1 -0
  193. package/dist/src/handlers/syndicate.js.map +1 -0
  194. package/dist/src/handlers/tco.d.ts +16 -0
  195. package/dist/src/handlers/tco.js +303 -0
  196. package/dist/src/handlers/tco.js.map +1 -0
  197. package/dist/src/handlers/team.js.map +1 -0
  198. package/dist/{index.js → src/index.js} +153 -218
  199. package/dist/src/index.js.map +1 -0
  200. package/dist/src/namespace.js.map +1 -0
  201. package/dist/src/notifier.d.ts +5 -0
  202. package/dist/src/notifier.js +6 -0
  203. package/dist/src/notifier.js.map +1 -0
  204. package/dist/src/rerank.d.ts +50 -0
  205. package/dist/src/rerank.js +92 -0
  206. package/dist/src/rerank.js.map +1 -0
  207. package/dist/src/search.js.map +1 -0
  208. package/dist/src/search.test.d.ts +1 -0
  209. package/dist/src/search.test.js.map +1 -0
  210. package/dist/{tools.d.ts → src/tools.d.ts} +196 -0
  211. package/dist/{tools.js → src/tools.js} +178 -0
  212. package/dist/src/tools.js.map +1 -0
  213. package/dist/src/utils.js.map +1 -0
  214. package/package.json +4 -3
  215. package/packages/telegram-notify/SPLIT_PLAN.md +106 -0
  216. package/packages/telegram-notify/client.ts +88 -0
  217. package/packages/telegram-notify/emojis.json +22 -0
  218. package/packages/telegram-notify/pyproject.toml +17 -0
  219. package/packages/telegram-notify/telegram_notify/__init__.py +3 -0
  220. package/packages/telegram-notify/telegram_notify/client.py +87 -0
  221. package/packages/telegram-notify/telegram_notify/emojis.json +22 -0
  222. package/scripts/generate-icons.js +28 -0
  223. package/smithery.yaml +21 -8
  224. package/src/__tests__/brain-flow.test.ts +174 -0
  225. package/src/__tests__/handlers-unit.test.ts +1 -2
  226. package/src/__tests__/rerank.test.ts +111 -0
  227. package/src/bench/cachly-bench.ts +217 -0
  228. package/src/bench/fixtures.ts +226 -0
  229. package/src/handlers/brain.ts +276 -92
  230. package/src/handlers/context.ts +9 -4
  231. package/src/handlers/fedbrain.ts +21 -7
  232. package/src/handlers/roadmap.ts +7 -3
  233. package/src/handlers/tco.ts +347 -0
  234. package/src/index.ts +139 -220
  235. package/src/notifier.ts +5 -0
  236. package/src/rerank.ts +123 -0
  237. package/src/tools.ts +191 -0
  238. package/tsconfig.json +2 -3
  239. package/dist/__tests__/auth.test.js.map +0 -1
  240. package/dist/__tests__/brain-flow.test.js.map +0 -1
  241. package/dist/__tests__/brain-predict-failures.test.d.ts +0 -17
  242. package/dist/__tests__/brain-predict-failures.test.js +0 -339
  243. package/dist/__tests__/brain-predict-failures.test.js.map +0 -1
  244. package/dist/__tests__/confidence.test.js.map +0 -1
  245. package/dist/__tests__/e2e/api-auth.test.js.map +0 -1
  246. package/dist/__tests__/e2e/api-health.test.js.map +0 -1
  247. package/dist/__tests__/e2e/cli.test.js.map +0 -1
  248. package/dist/__tests__/e2e/config.js.map +0 -1
  249. package/dist/__tests__/e2e/funnel.test.js.map +0 -1
  250. package/dist/__tests__/e2e/mcp-tools.test.js.map +0 -1
  251. package/dist/__tests__/e2e/provisioning.test.js.map +0 -1
  252. package/dist/__tests__/handlers-unit.test.js.map +0 -1
  253. package/dist/__tests__/namespace.test.js.map +0 -1
  254. package/dist/auth.js.map +0 -1
  255. package/dist/ckg.js.map +0 -1
  256. package/dist/confidence.js.map +0 -1
  257. package/dist/embeddings.js.map +0 -1
  258. package/dist/handlers/advanced.js.map +0 -1
  259. package/dist/handlers/brain.js.map +0 -1
  260. package/dist/handlers/cache.js.map +0 -1
  261. package/dist/handlers/context.js.map +0 -1
  262. package/dist/handlers/fedbrain.js.map +0 -1
  263. package/dist/handlers/instances.js.map +0 -1
  264. package/dist/handlers/roadmap.js.map +0 -1
  265. package/dist/handlers/syndicate.js.map +0 -1
  266. package/dist/handlers/team.js.map +0 -1
  267. package/dist/index.js.map +0 -1
  268. package/dist/namespace.js.map +0 -1
  269. package/dist/search.js.map +0 -1
  270. package/dist/search.test.js.map +0 -1
  271. package/dist/tools.js.map +0 -1
  272. package/dist/utils.js.map +0 -1
  273. package/src/__tests__/brain-predict-failures.test.ts +0 -430
  274. /package/dist/{__tests__ → src/__tests__}/auth.test.d.ts +0 -0
  275. /package/dist/{__tests__ → src/__tests__}/auth.test.js +0 -0
  276. /package/dist/{__tests__ → src/__tests__}/brain-flow.test.d.ts +0 -0
  277. /package/dist/{__tests__ → src/__tests__}/confidence.test.d.ts +0 -0
  278. /package/dist/{__tests__ → src/__tests__}/confidence.test.js +0 -0
  279. /package/dist/{__tests__ → src/__tests__}/e2e/api-auth.test.d.ts +0 -0
  280. /package/dist/{__tests__ → src/__tests__}/e2e/api-auth.test.js +0 -0
  281. /package/dist/{__tests__ → src/__tests__}/e2e/api-health.test.d.ts +0 -0
  282. /package/dist/{__tests__ → src/__tests__}/e2e/api-health.test.js +0 -0
  283. /package/dist/{__tests__ → src/__tests__}/e2e/cli.test.d.ts +0 -0
  284. /package/dist/{__tests__ → src/__tests__}/e2e/cli.test.js +0 -0
  285. /package/dist/{__tests__ → src/__tests__}/e2e/config.d.ts +0 -0
  286. /package/dist/{__tests__ → src/__tests__}/e2e/config.js +0 -0
  287. /package/dist/{__tests__ → src/__tests__}/e2e/funnel.test.d.ts +0 -0
  288. /package/dist/{__tests__ → src/__tests__}/e2e/funnel.test.js +0 -0
  289. /package/dist/{__tests__ → src/__tests__}/e2e/mcp-tools.test.d.ts +0 -0
  290. /package/dist/{__tests__ → src/__tests__}/e2e/mcp-tools.test.js +0 -0
  291. /package/dist/{__tests__ → src/__tests__}/e2e/provisioning.test.d.ts +0 -0
  292. /package/dist/{__tests__ → src/__tests__}/e2e/provisioning.test.js +0 -0
  293. /package/dist/{__tests__ → src/__tests__}/handlers-unit.test.d.ts +0 -0
  294. /package/dist/{__tests__ → src/__tests__}/namespace.test.d.ts +0 -0
  295. /package/dist/{__tests__ → src/__tests__}/namespace.test.js +0 -0
  296. /package/dist/{search.test.d.ts → src/__tests__/rerank.test.d.ts} +0 -0
  297. /package/dist/{auth.d.ts → src/auth.d.ts} +0 -0
  298. /package/dist/{auth.js → src/auth.js} +0 -0
  299. /package/dist/{ckg.d.ts → src/ckg.d.ts} +0 -0
  300. /package/dist/{ckg.js → src/ckg.js} +0 -0
  301. /package/dist/{confidence.d.ts → src/confidence.d.ts} +0 -0
  302. /package/dist/{confidence.js → src/confidence.js} +0 -0
  303. /package/dist/{embeddings.d.ts → src/embeddings.d.ts} +0 -0
  304. /package/dist/{embeddings.js → src/embeddings.js} +0 -0
  305. /package/dist/{handlers → src/handlers}/advanced.d.ts +0 -0
  306. /package/dist/{handlers → src/handlers}/advanced.js +0 -0
  307. /package/dist/{handlers → src/handlers}/brain.d.ts +0 -0
  308. /package/dist/{handlers → src/handlers}/cache.d.ts +0 -0
  309. /package/dist/{handlers → src/handlers}/cache.js +0 -0
  310. /package/dist/{handlers → src/handlers}/context.d.ts +0 -0
  311. /package/dist/{handlers → src/handlers}/instances.d.ts +0 -0
  312. /package/dist/{handlers → src/handlers}/instances.js +0 -0
  313. /package/dist/{handlers → src/handlers}/roadmap.d.ts +0 -0
  314. /package/dist/{handlers → src/handlers}/syndicate.d.ts +0 -0
  315. /package/dist/{handlers → src/handlers}/syndicate.js +0 -0
  316. /package/dist/{handlers → src/handlers}/team.d.ts +0 -0
  317. /package/dist/{handlers → src/handlers}/team.js +0 -0
  318. /package/dist/{index.d.ts → src/index.d.ts} +0 -0
  319. /package/dist/{namespace.d.ts → src/namespace.d.ts} +0 -0
  320. /package/dist/{namespace.js → src/namespace.js} +0 -0
  321. /package/dist/{search.d.ts → src/search.d.ts} +0 -0
  322. /package/dist/{search.js → src/search.js} +0 -0
  323. /package/dist/{search.test.js → src/search.test.js} +0 -0
  324. /package/dist/{utils.d.ts → src/utils.d.ts} +0 -0
  325. /package/dist/{utils.js → src/utils.js} +0 -0
@@ -1,5 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { jwtExpiryMs, checkJwt, handleApiError } from './auth.js';
3
+ import { handleTcoTool } from './handlers/tco.js';
4
+ import { notify } from './notifier.js';
3
5
  import { readFile } from 'node:fs/promises';
4
6
  import { fileURLToPath } from 'node:url';
5
7
  // True only when this file is the entry point (not imported by tests or other modules).
@@ -34,26 +36,40 @@ const _isMain = process.argv[1] != null &&
34
36
  * ── Auth & Status ────────────────────────────────────────────────────────────
35
37
  * • get_api_status – check API health + JWT auth info (Keycloak)
36
38
  *
39
+ * ── Travel Chaos Organizer (TCO) ────────────────────────────────────────────
40
+ * • tco_list_trips – list all trips
41
+ * • tco_create_trip – create a new trip
42
+ * • tco_get_timeline – get timeline items for a trip
43
+ * • tco_delete_trip – delete a trip
44
+ * • tco_inbox_list – list Chaos Inbox items
45
+ * • tco_inbox_assign – assign inbox item to a trip
46
+ * • tco_inbox_reject – reject/discard an inbox item
47
+ * • tco_parse_url – fetch a URL and parse with Ollama
48
+ * • tco_import_email – import raw email text
49
+ * (Requires TCO_API_URL env var; auth forwarded from CACHLY_JWT — same Keycloak realm)
50
+ *
37
51
  * Configuration (env vars):
38
52
  * CACHLY_API_URL – default https://api.cachly.dev
39
53
  * CACHLY_JWT – your JWT (Keycloak access token)
40
54
  * CACHLY_EMBED_PROVIDER – embedding backend: openai (default), gemini, mistral, cohere, ollama, cachly (server fallback)
41
55
  * CACHLY_EMBED_MODEL – override embedding model (optional)
56
+ * TCO_API_URL – Travel Chaos Organizer backend URL (optional, enables tco_* tools)
42
57
  */
43
58
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
44
59
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
45
60
  import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
46
61
  import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from '@modelcontextprotocol/sdk/types.js';
47
62
  import { Redis } from 'ioredis';
48
- import { AsyncLocalStorage } from 'node:async_hooks';
49
63
  // ── Config ────────────────────────────────────────────────────────────────────
50
64
  const API_URL = process.env.CACHLY_API_URL ?? 'https://api.cachly.dev';
51
65
  let JWT = process.env.CACHLY_JWT ?? '';
52
66
  const EMBED_MODEL = process.env.CACHLY_EMBED_MODEL ?? '';
53
- const CURRENT_VERSION = '0.10.47';
54
- const reqCtx = new AsyncLocalStorage();
55
- function getJWT() { return reqCtx.getStore()?.jwt || JWT; }
56
- function getDefaultInstanceId() { return reqCtx.getStore()?.instanceId || _defaultInstanceId; }
67
+ const CURRENT_VERSION = '0.10.49';
68
+ // Max time to wait for a freshly-provisioned instance to become "running" before
69
+ // giving up. Free-tier provisioning in high-latency regions can take 45–90s, so the
70
+ // old hard-coded 25s caused spurious "instance_not_reachable" on first run.
71
+ // Override via CACHLY_PROVISION_TIMEOUT_MS.
72
+ const PROVISION_TIMEOUT_MS = Number(process.env.CACHLY_PROVISION_TIMEOUT_MS ?? 90_000);
57
73
  // ── Default Instance Resolution (for Smithery & single-credential setups) ────
58
74
  // When CACHLY_BRAIN_INSTANCE_ID is set, tools can omit the instance_id parameter.
59
75
  // When neither is set, we auto-fetch the first running instance once per process.
@@ -61,13 +77,9 @@ let _defaultInstanceId = process.env.CACHLY_BRAIN_INSTANCE_ID ?? '';
61
77
  // Timestamp of last failed fetch — retries after 30 s (not permanently blocked).
62
78
  let _defaultInstanceLastAttempt = 0;
63
79
  async function resolveDefaultInstanceId() {
64
- // In an HTTP request scope the instance ID is fixed by the caller's header.
65
- const scopedId = reqCtx.getStore()?.instanceId;
66
- if (scopedId)
67
- return scopedId;
68
80
  if (_defaultInstanceId)
69
81
  return _defaultInstanceId;
70
- if (!getJWT())
82
+ if (!JWT)
71
83
  return '';
72
84
  // Cooldown: don't hammer the API on every tool call after a transient failure.
73
85
  const now = Date.now();
@@ -76,7 +88,7 @@ async function resolveDefaultInstanceId() {
76
88
  _defaultInstanceLastAttempt = now;
77
89
  try {
78
90
  const res = await fetch(`${API_URL}/api/v1/instances`, {
79
- headers: { Authorization: `Bearer ${getJWT()}`, Accept: 'application/json' },
91
+ headers: { Authorization: `Bearer ${JWT}`, Accept: 'application/json' },
80
92
  signal: AbortSignal.timeout(5000),
81
93
  });
82
94
  if (!res.ok)
@@ -155,6 +167,42 @@ async function pollDeviceFlow(flow) {
155
167
  setEmbedJwt(apiKey); // keep embeddings.ts in sync
156
168
  _deviceFlow = null;
157
169
  sendFunnelEvent('device_flow_completed');
170
+ // Persist the API key to ~/.claude/mcp.json so subsequent MCP server restarts
171
+ // keep the token. Without this, JWT is lost on restart and brain_recall_count
172
+ // is never incremented (telemetry has no api_key → no tenant resolution).
173
+ void (async () => {
174
+ try {
175
+ const { writeFile, mkdir, readFile } = await import('node:fs/promises');
176
+ const { existsSync } = await import('node:fs');
177
+ const { resolve, dirname } = await import('node:path');
178
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? '';
179
+ if (!home)
180
+ return;
181
+ const configPath = resolve(home, '.claude', 'mcp.json');
182
+ let cfg = {};
183
+ if (existsSync(configPath)) {
184
+ try {
185
+ cfg = JSON.parse(await readFile(configPath, 'utf-8'));
186
+ }
187
+ catch { /* corrupt — start fresh */ }
188
+ }
189
+ cfg.mcpServers ??= {};
190
+ const existing = cfg.mcpServers['cachly'];
191
+ if (existing) {
192
+ existing.env ??= {};
193
+ existing.env['CACHLY_JWT'] = apiKey;
194
+ }
195
+ else {
196
+ cfg.mcpServers['cachly'] = {
197
+ command: 'npx', args: ['-y', '@cachly-dev/mcp-server@latest'],
198
+ env: { CACHLY_JWT: apiKey },
199
+ };
200
+ }
201
+ await mkdir(dirname(configPath), { recursive: true });
202
+ await writeFile(configPath, JSON.stringify(cfg, null, 2), 'utf-8');
203
+ }
204
+ catch { /* non-critical — don't break the flow on filesystem errors */ }
205
+ })();
158
206
  // Auto-provision: find or create the user's brain instance.
159
207
  _defaultInstanceLastAttempt = 0;
160
208
  await resolveDefaultInstanceId();
@@ -162,7 +210,7 @@ async function pollDeviceFlow(flow) {
162
210
  try {
163
211
  const autoRes = await fetch(`${API_URL}/api/v1/instances/auto`, {
164
212
  method: 'POST',
165
- headers: { Authorization: `Bearer ${getJWT()}`, 'Content-Type': 'application/json' },
213
+ headers: { Authorization: `Bearer ${JWT}`, 'Content-Type': 'application/json' },
166
214
  signal: AbortSignal.timeout(12000),
167
215
  });
168
216
  if (autoRes.ok) {
@@ -175,7 +223,7 @@ async function pollDeviceFlow(flow) {
175
223
  // Log so telemetry can detect free-tier auto-provision failures
176
224
  void fetch(`${API_URL}/api/v1/telemetry/mcp`, {
177
225
  method: 'POST',
178
- headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${getJWT()}` },
226
+ headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${JWT}` },
179
227
  body: JSON.stringify({ event: 'auto_provision_failed', status: autoRes.status, version: CURRENT_VERSION }),
180
228
  signal: AbortSignal.timeout(3000),
181
229
  }).catch(() => { });
@@ -188,7 +236,7 @@ async function pollDeviceFlow(flow) {
188
236
  if (_defaultInstanceId) {
189
237
  try {
190
238
  const checkRes = await fetch(`${API_URL}/api/v1/instances/${_defaultInstanceId}`, {
191
- headers: { Authorization: `Bearer ${getJWT()}`, Accept: 'application/json' },
239
+ headers: { Authorization: `Bearer ${JWT}`, Accept: 'application/json' },
192
240
  signal: AbortSignal.timeout(4000),
193
241
  });
194
242
  if (checkRes.ok) {
@@ -199,7 +247,7 @@ async function pollDeviceFlow(flow) {
199
247
  while (checkInst.status === 'provisioning' && Date.now() < pDeadline) {
200
248
  await new Promise(r => setTimeout(r, 3000));
201
249
  const rr = await fetch(`${API_URL}/api/v1/instances/${_defaultInstanceId}`, {
202
- headers: { Authorization: `Bearer ${getJWT()}`, Accept: 'application/json' },
250
+ headers: { Authorization: `Bearer ${JWT}`, Accept: 'application/json' },
203
251
  signal: AbortSignal.timeout(4000),
204
252
  }).catch(() => null);
205
253
  if (rr?.ok) {
@@ -215,6 +263,37 @@ async function pollDeviceFlow(flow) {
215
263
  }
216
264
  catch { /* non-fatal — getConnection will handle the wait if still provisioning */ }
217
265
  }
266
+ // Persist instance_id to ~/.claude/mcp.json after it's known.
267
+ // Without this, every restart loses CACHLY_BRAIN_INSTANCE_ID → resolveDefaultInstanceId
268
+ // makes an extra API call on every startup (with 30s cooldown on failure).
269
+ if (_defaultInstanceId) {
270
+ void (async () => {
271
+ try {
272
+ const { writeFile, readFile } = await import('node:fs/promises');
273
+ const { existsSync } = await import('node:fs');
274
+ const { resolve } = await import('node:path');
275
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? '';
276
+ if (!home)
277
+ return;
278
+ const configPath = resolve(home, '.claude', 'mcp.json');
279
+ if (!existsSync(configPath))
280
+ return;
281
+ let cfg = {};
282
+ try {
283
+ cfg = JSON.parse(await readFile(configPath, 'utf-8'));
284
+ }
285
+ catch {
286
+ return;
287
+ }
288
+ const entry = cfg.mcpServers?.['cachly'];
289
+ if (entry?.env) {
290
+ entry.env['CACHLY_BRAIN_INSTANCE_ID'] = _defaultInstanceId;
291
+ await writeFile(configPath, JSON.stringify(cfg, null, 2), 'utf-8');
292
+ }
293
+ }
294
+ catch { /* non-critical */ }
295
+ })();
296
+ }
218
297
  return 'done';
219
298
  }
220
299
  if (data.error === 'slow_down')
@@ -241,25 +320,14 @@ async function getConnection(instance_id) {
241
320
  '• Pass instance_id explicitly to the tool, or\n' +
242
321
  '• Run `list_instances` to see your available instances.');
243
322
  }
244
- if (pool.has(instance_id)) {
245
- const cached = pool.get(instance_id);
246
- // Verify the pooled connection is still alive before reusing it.
247
- // ioredis 'error' eviction is async — a connection can be in the pool
248
- // but already internally broken (status 'reconnecting' or 'end').
249
- const status = cached.status;
250
- if (status === 'ready' || status === 'connect') {
251
- return cached;
252
- }
253
- // Stale — evict and fall through to reconnect.
254
- pool.delete(instance_id);
255
- cached.disconnect();
256
- }
257
- // Fetch instance, waiting up to 25 s if it is still provisioning.
323
+ if (pool.has(instance_id))
324
+ return pool.get(instance_id);
325
+ // Fetch instance, waiting up to PROVISION_TIMEOUT_MS if it is still provisioning.
258
326
  // This covers the zero-friction path: device-flow auth → auto-provision → first tool call
259
327
  // all happen in quick succession and the instance isn't running yet.
260
328
  let inst = await apiFetch(`/api/v1/instances/${instance_id}`);
261
329
  if (inst.status === 'provisioning') {
262
- const deadline = Date.now() + 25_000;
330
+ const deadline = Date.now() + PROVISION_TIMEOUT_MS;
263
331
  while (inst.status === 'provisioning' && Date.now() < deadline) {
264
332
  await new Promise(r => setTimeout(r, 3000));
265
333
  inst = await apiFetch(`/api/v1/instances/${instance_id}`);
@@ -269,7 +337,7 @@ async function getConnection(instance_id) {
269
337
  // Telemetry: unreachable instance so the team can proactively investigate
270
338
  void fetch(`${API_URL}/api/v1/telemetry/mcp`, {
271
339
  method: 'POST',
272
- headers: { 'Content-Type': 'application/json', ...(getJWT() ? { Authorization: `Bearer ${getJWT()}` } : {}) },
340
+ headers: { 'Content-Type': 'application/json', ...(JWT ? { Authorization: `Bearer ${JWT}` } : {}) },
273
341
  body: JSON.stringify({ event: 'instance_not_reachable', instance_id, status: inst.status, version: CURRENT_VERSION }),
274
342
  signal: AbortSignal.timeout(3000),
275
343
  }).catch(() => { });
@@ -321,6 +389,7 @@ async function getConnection(instance_id) {
321
389
  host: inst.host,
322
390
  port: inst.port,
323
391
  password: password || undefined,
392
+ commandTimeout: 8000,
324
393
  ...(tlsEnabled ? { tls: {} } : {}),
325
394
  lazyConnect: true,
326
395
  enableReadyCheck: true,
@@ -330,45 +399,15 @@ async function getConnection(instance_id) {
330
399
  const evict = () => pool.delete(instance_id);
331
400
  client.on('error', evict);
332
401
  client.on('end', evict);
333
- // Retry up to 3 times with 2s backoff. Covers the window between K8s PodReady
334
- // and Valkey actually accepting connections (RDB/AOF load, ~1-3s on cold start).
335
- let connectErr;
336
- for (let attempt = 0; attempt < 3; attempt++) {
337
- try {
338
- if (attempt > 0) {
339
- await new Promise(r => setTimeout(r, 2000 * attempt));
340
- // Fresh client on retry — ioredis doesn't allow reconnect after explicit disconnect.
341
- client.disconnect();
342
- const retryClient = new Redis({
343
- host: inst.host,
344
- port: inst.port,
345
- password: password || undefined,
346
- ...(tlsEnabled ? { tls: {} } : {}),
347
- lazyConnect: true,
348
- enableReadyCheck: true,
349
- connectTimeout: 8000,
350
- retryStrategy: () => null,
351
- });
352
- retryClient.on('error', () => pool.delete(instance_id));
353
- retryClient.on('end', () => pool.delete(instance_id));
354
- await retryClient.connect();
355
- pool.set(instance_id, retryClient);
356
- return retryClient;
357
- }
358
- await client.connect();
359
- connectErr = undefined;
360
- break;
361
- }
362
- catch (err) {
363
- connectErr = err;
364
- }
402
+ try {
403
+ await client.connect();
365
404
  }
366
- if (connectErr) {
405
+ catch (err) {
367
406
  client.disconnect();
368
- const msg = connectErr instanceof Error ? connectErr.message : String(connectErr);
407
+ const msg = err instanceof Error ? err.message : String(err);
369
408
  void fetch(`${API_URL}/api/v1/telemetry/mcp`, {
370
409
  method: 'POST',
371
- headers: { 'Content-Type': 'application/json', ...(getJWT() ? { Authorization: `Bearer ${getJWT()}` } : {}) },
410
+ headers: { 'Content-Type': 'application/json', ...(JWT ? { Authorization: `Bearer ${JWT}` } : {}) },
372
411
  body: JSON.stringify({ event: 'redis_connect_failed', instance_id, host: inst.host, port: inst.port, error: msg, version: CURRENT_VERSION }),
373
412
  signal: AbortSignal.timeout(3000),
374
413
  }).catch(() => { });
@@ -382,14 +421,14 @@ async function getConnection(instance_id) {
382
421
  }
383
422
  // ── API helper ────────────────────────────────────────────────────────────────
384
423
  async function apiFetch(path, options = {}) {
385
- checkJwt(getJWT());
424
+ checkJwt(JWT);
386
425
  const timeoutMs = Number(process.env.CACHLY_API_TIMEOUT_MS ?? 15_000);
387
426
  const res = await fetch(`${API_URL}${path}`, {
388
427
  ...options,
389
428
  signal: AbortSignal.timeout(timeoutMs),
390
429
  headers: {
391
430
  'Content-Type': 'application/json',
392
- Authorization: `Bearer ${getJWT()}`,
431
+ Authorization: `Bearer ${JWT}`,
393
432
  ...options.headers,
394
433
  },
395
434
  });
@@ -410,7 +449,7 @@ import { handleTeamTool } from './handlers/team.js';
410
449
  import { handleRoadmapTool } from './handlers/roadmap.js';
411
450
  import { handleAdvancedTool } from './handlers/advanced.js';
412
451
  import { handleSyndicateTool } from './handlers/syndicate.js';
413
- import { handleFedbrainTool } from './handlers/fedbrain.js';
452
+ import { handleFedbrainTool, _lastBrainFromGitCounts } from './handlers/fedbrain.js';
414
453
  // ── Tools (imported from tools.ts) ─────────────────────────────────────────
415
454
  import { TOOLS } from './tools.js';
416
455
  // ── Handlers ──────────────────────────────────────────────────────────────────
@@ -433,7 +472,7 @@ function sendFunnelEvent(event, extra) {
433
472
  headers: { 'Content-Type': 'application/json' },
434
473
  body: JSON.stringify({
435
474
  event, version: CURRENT_VERSION, editor: detectEditor(),
436
- ...(getJWT() ? { jwt: getJWT() } : {}),
475
+ ...(JWT ? { jwt: JWT } : {}),
437
476
  ...extra,
438
477
  }),
439
478
  signal: AbortSignal.timeout(3000),
@@ -457,7 +496,7 @@ async function sendAnonymousTelemetry(toolName) {
457
496
  }
458
497
  async function handleTool(name, args) {
459
498
  // Guard: if no JWT, return actionable onboarding message instead of HTTP 401
460
- if (!getJWT()) {
499
+ if (!JWT) {
461
500
  void sendAnonymousTelemetry(name);
462
501
  // ── Zero-credential device flow ─────────────────────────────────────
463
502
  // 1st call: start device flow, return code + URL
@@ -534,14 +573,14 @@ async function handleTool(name, args) {
534
573
  // Delegate brain tools (learn, recall, session, etc.)
535
574
  const brainResult = await handleBrainTool(name, args, getConnection, apiFetch);
536
575
  if (brainResult !== null) {
537
- if (!_firstCallSuccessSent && getJWT()) {
576
+ if (!_firstCallSuccessSent && JWT) {
538
577
  _firstCallSuccessSent = true;
539
- sendFunnelEvent('first_call_success', { tool: name, instance_id: args.instance_id ?? getDefaultInstanceId() ?? '' });
578
+ sendFunnelEvent('first_call_success', { tool: name, instance_id: args.instance_id ?? _defaultInstanceId ?? '' });
540
579
  }
541
580
  // Per-tool telemetry — fires after every successful brain tool call.
542
581
  // api_key carries the cky_live_... token so the backend can resolve tenant + increment counters.
543
- const instanceId = args.instance_id ?? getDefaultInstanceId() ?? '';
544
- const telemetryExtra = getJWT() ? { api_key: getJWT(), instance_id: instanceId } : { instance_id: instanceId };
582
+ const instanceId = args.instance_id ?? _defaultInstanceId ?? '';
583
+ const telemetryExtra = JWT ? { api_key: JWT, instance_id: instanceId } : { instance_id: instanceId };
545
584
  if (name === 'recall_best_solution') {
546
585
  // recall_best_solution → increments BrainRecallCount + triggers level-up logic
547
586
  sendFunnelEvent('recall_best_solution', telemetryExtra);
@@ -569,17 +608,14 @@ async function handleTool(name, args) {
569
608
  limit: 50,
570
609
  }, getConnection, apiFetch);
571
610
  if (gitBootstrap !== null) {
572
- sendFunnelEvent('brain_from_git', telemetryExtra);
611
+ const gitCounts = _lastBrainFromGitCounts;
612
+ sendFunnelEvent('brain_from_git', { ...telemetryExtra, ...(gitCounts ?? {}) });
573
613
  sendFunnelEvent('recall_best_solution', telemetryExtra);
574
614
  // Append bootstrap summary to the session_start briefing.
575
- const bootstrapText = typeof gitBootstrap === 'object' && gitBootstrap !== null && 'content' in gitBootstrap
576
- ? gitBootstrap.content.map((c) => c.text ?? '').join('\n')
577
- : String(gitBootstrap);
578
- if (typeof brainResult === 'object' && brainResult !== null && 'content' in brainResult) {
579
- brainResult.content.push({
580
- type: 'text',
581
- text: '\n---\n' + bootstrapText,
582
- });
615
+ // brainResult is always a string here (handleBrainTool returns string for session_start).
616
+ const bootstrapText = String(gitBootstrap);
617
+ if (bootstrapText) {
618
+ return String(brainResult) + '\n---\n' + bootstrapText;
583
619
  }
584
620
  }
585
621
  }
@@ -597,7 +633,7 @@ async function handleTool(name, args) {
597
633
  const srText = typeof brainResult === 'object' && brainResult !== null && 'content' in brainResult
598
634
  ? JSON.stringify(brainResult)
599
635
  : String(brainResult ?? '');
600
- if (srText.length > 50 && !srText.includes('No lessons found') && !srText.includes('no lessons')) {
636
+ if (srText.length > 50 && !srText.includes('No lessons found') && !srText.includes('no lessons') && !srText.includes('No matches found')) {
601
637
  sendFunnelEvent('recall_best_solution', telemetryExtra);
602
638
  }
603
639
  }
@@ -606,9 +642,9 @@ async function handleTool(name, args) {
606
642
  // Delegate context tools (remember/recall/list/forget)
607
643
  const contextResult = await handleContextTool(name, args, getConnection, apiFetch);
608
644
  if (contextResult !== null) {
609
- if (!_firstCallSuccessSent && getJWT()) {
645
+ if (!_firstCallSuccessSent && JWT) {
610
646
  _firstCallSuccessSent = true;
611
- sendFunnelEvent('first_call_success', { tool: name, instance_id: args.instance_id ?? getDefaultInstanceId() ?? '' });
647
+ sendFunnelEvent('first_call_success', { tool: name, instance_id: args.instance_id ?? _defaultInstanceId ?? '' });
612
648
  }
613
649
  return contextResult;
614
650
  }
@@ -633,8 +669,8 @@ async function handleTool(name, args) {
633
669
  if (syndicateResult !== null) {
634
670
  // brain_predict is in syndicate handler — track telemetry here.
635
671
  if (name === 'brain_predict') {
636
- const instanceId = args.instance_id ?? getDefaultInstanceId() ?? '';
637
- const telemetryExtra = getJWT() ? { api_key: getJWT(), instance_id: instanceId } : { instance_id: instanceId };
672
+ const instanceId = args.instance_id ?? _defaultInstanceId ?? '';
673
+ const telemetryExtra = JWT ? { api_key: JWT, instance_id: instanceId } : { instance_id: instanceId };
638
674
  sendFunnelEvent('brain_predict', telemetryExtra);
639
675
  }
640
676
  return syndicateResult;
@@ -642,16 +678,21 @@ async function handleTool(name, args) {
642
678
  const fedbrainResult = await handleFedbrainTool(name, args, getConnection, apiFetch);
643
679
  if (fedbrainResult !== null) {
644
680
  // brain_from_git and brain_predict_failures are in fedbrain handler.
645
- const instanceId = args.instance_id ?? getDefaultInstanceId() ?? '';
646
- const telemetryExtra = getJWT() ? { api_key: getJWT(), instance_id: instanceId } : { instance_id: instanceId };
681
+ const instanceId = args.instance_id ?? _defaultInstanceId ?? '';
682
+ const telemetryExtra = JWT ? { api_key: JWT, instance_id: instanceId } : { instance_id: instanceId };
647
683
  if (name === 'brain_from_git') {
648
- sendFunnelEvent('brain_from_git', telemetryExtra);
684
+ const gitCounts = _lastBrainFromGitCounts;
685
+ sendFunnelEvent('brain_from_git', { ...telemetryExtra, ...(gitCounts ?? {}) });
649
686
  }
650
687
  else if (name === 'brain_predict_failures') {
651
688
  sendFunnelEvent('brain_predict_failures', telemetryExtra);
652
689
  }
653
690
  return fedbrainResult;
654
691
  }
692
+ // Delegate Travel Chaos Organizer tools (requires TCO_API_URL env var)
693
+ const tcoResult = await handleTcoTool(name, args, JWT ?? '');
694
+ if (tcoResult !== null)
695
+ return tcoResult;
655
696
  switch (name) {
656
697
  // ── Instance management ──────────────────────────────────────────────
657
698
  case 'get_api_status': {
@@ -671,7 +712,7 @@ async function handleTool(name, args) {
671
712
  healthStatus = `unreachable: ${e.message}`;
672
713
  }
673
714
  // Check JWT / auth
674
- if (!getJWT()) {
715
+ if (!JWT) {
675
716
  return [
676
717
  `📡 **cachly API Status**`,
677
718
  ``,
@@ -685,7 +726,7 @@ async function handleTool(name, args) {
685
726
  // Decode JWT claims (inspection only, no verification)
686
727
  let authInfo = '❌ invalid JWT format';
687
728
  try {
688
- const parts = getJWT().split('.');
729
+ const parts = JWT.split('.');
689
730
  if (parts.length === 3) {
690
731
  const payload = JSON.parse(Buffer.from(parts[1], 'base64url').toString('utf-8'));
691
732
  const sub = payload.sub ?? '(unknown)';
@@ -717,7 +758,7 @@ async function handleTool(name, args) {
717
758
  const lines = ['\n\n🧠 **Brain Instances:**'];
718
759
  for (const inst of instances) {
719
760
  const badge = inst.status === 'running' ? '🟢' : inst.status === 'provisioning' ? '🟡' : '🔴';
720
- const defaultMark = inst.id === getDefaultInstanceId() ? ' ← active' : '';
761
+ const defaultMark = inst.id === _defaultInstanceId ? ' ← active' : '';
721
762
  lines.push(` ${badge} **${inst.name}** (\`${inst.id}\`) · ${inst.status}${defaultMark}`);
722
763
  if (inst.status === 'running' && inst.host) {
723
764
  // Try a quick ping on the cached connection
@@ -861,7 +902,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
861
902
  catch (err) {
862
903
  if (err instanceof McpError)
863
904
  throw err;
864
- throw new McpError(ErrorCode.InternalError, err.message);
905
+ const msg = err.message ?? String(err);
906
+ notify('cachly', 'tool_error', { tool: name, error: msg }).catch(() => undefined);
907
+ throw new McpError(ErrorCode.InternalError, msg);
865
908
  }
866
909
  });
867
910
  // Graceful shutdown – close all Redis connections + auto-end brain session
@@ -2071,6 +2114,7 @@ if (process.argv[2] === 'setup') {
2071
2114
  const AUTH_BASE = 'https://auth.cachly.dev/realms/cachly/protocol/openid-connect';
2072
2115
  const CLIENT_ID = 'cachly-cli';
2073
2116
  console.log('Step 1: Sign in to cachly (free, no credit card)\n');
2117
+ sendFunnelEvent('setup_auth_started');
2074
2118
  // Start device flow
2075
2119
  let deviceCode = '', userCode = '', verifyUri = '', pollInterval = 5000;
2076
2120
  try {
@@ -2096,6 +2140,7 @@ if (process.argv[2] === 'setup') {
2096
2140
  rl.close();
2097
2141
  process.exit(1);
2098
2142
  }
2143
+ sendFunnelEvent('setup_auth_completed');
2099
2144
  console.log('');
2100
2145
  deviceCode = ''; // mark as fallback so we skip polling
2101
2146
  }
@@ -2128,6 +2173,7 @@ if (process.argv[2] === 'setup') {
2128
2173
  if (tokenData.access_token) {
2129
2174
  token = tokenData.access_token;
2130
2175
  console.log(' \x1b[32m✓ Authorized!\x1b[0m\n');
2176
+ sendFunnelEvent('setup_auth_completed');
2131
2177
  break;
2132
2178
  }
2133
2179
  // authorization_pending = keep polling; slow_down = increase interval
@@ -2221,9 +2267,10 @@ if (process.argv[2] === 'setup') {
2221
2267
  // Newly created — poll until running or give up after 30 s.
2222
2268
  const newId = autoBody.instance_id;
2223
2269
  console.log(` ✓ created (${newId.slice(0, 8)}…)\n`);
2270
+ // 45 attempts × 3s = 135s — generous for free-tier in slow regions.
2224
2271
  process.stdout.write('⏳ Waiting for instance to start');
2225
- for (let attempt = 0; attempt < 15; attempt++) {
2226
- await new Promise(r => setTimeout(r, 2000));
2272
+ for (let attempt = 0; attempt < 45; attempt++) {
2273
+ await new Promise(r => setTimeout(r, 3000));
2227
2274
  process.stdout.write('.');
2228
2275
  try {
2229
2276
  const checkRes = await fetch(`${API_URL}/api/v1/instances/${newId}`, { headers: { Authorization: `Bearer ${token}` } });
@@ -2262,6 +2309,7 @@ if (process.argv[2] === 'setup') {
2262
2309
  console.log(` (Run with --instance-id <id> to use a different one)\n`);
2263
2310
  }
2264
2311
  console.log(`✓ Instance: ${instance.name} (${instance.id.slice(0, 8)}…)\n`);
2312
+ sendFunnelEvent('setup_instance_ready', { instance_id: instance.id });
2265
2313
  // ── Step 3: Detect editors ────────────────────────────────────────────────
2266
2314
  const cwd = process.cwd();
2267
2315
  const detected = [];
@@ -2510,7 +2558,7 @@ if (process.argv[2] === 'index') {
2510
2558
  const instanceId = flag('instance-id') ?? process.env.CACHLY_BRAIN_INSTANCE_ID;
2511
2559
  const maxFiles = parseInt(flag('max-files') ?? '500', 10);
2512
2560
  const namespace = flag('namespace') ?? 'cachly:sem:code';
2513
- if (!instanceId || !getJWT()) {
2561
+ if (!instanceId || !JWT) {
2514
2562
  console.error('\n❌ CACHLY_BRAIN_INSTANCE_ID and CACHLY_JWT must be set\n');
2515
2563
  console.error(' export CACHLY_BRAIN_INSTANCE_ID=<uuid>');
2516
2564
  console.error(' export CACHLY_JWT=<cky_live_...>');
@@ -2547,7 +2595,7 @@ if (process.argv[2] === 'learn-git') {
2547
2595
  const repoDir = resolve(flag('dir') ?? argv.find(a => !a.startsWith('--')) ?? '.');
2548
2596
  const instanceId = flag('instance-id') ?? process.env.CACHLY_BRAIN_INSTANCE_ID;
2549
2597
  const maxCommits = parseInt(flag('max-commits') ?? '50', 10);
2550
- if (!instanceId || !getJWT()) {
2598
+ if (!instanceId || !JWT) {
2551
2599
  console.error('\n❌ CACHLY_BRAIN_INSTANCE_ID and CACHLY_JWT must be set\n');
2552
2600
  console.error(' export CACHLY_BRAIN_INSTANCE_ID=<uuid>');
2553
2601
  console.error(' export CACHLY_JWT=<cky_live_...>');
@@ -2579,8 +2627,7 @@ if (process.argv[2] === 'learn-git') {
2579
2627
  // actionable message in their editor's MCP log instead of silent failures.
2580
2628
  // Skip for CLI commands that intentionally run without credentials.
2581
2629
  const _cliNoAuthCommands = ['demo', 'share', 'health', 'setup', 'init', 'digest', 'invite', 'badge', 'join', 'upgrade'];
2582
- // In multi-tenant HTTP mode (PORT set) we never need a startup JWT — per-request auth.
2583
- if (!JWT && !process.env.PORT && !_cliNoAuthCommands.includes(process.argv[2] ?? '') && !(!process.argv[2] && process.stdout.isTTY)) {
2630
+ if (!JWT && !_cliNoAuthCommands.includes(process.argv[2] ?? '') && !(!process.argv[2] && process.stdout.isTTY)) {
2584
2631
  // No args + no JWT in a non-TTY context = running as MCP server without credentials.
2585
2632
  // Print actionable setup banner to stdout so it's captured by callers/tests.
2586
2633
  const banner = '\n' +
@@ -2644,79 +2691,12 @@ if (!process.env.CACHLY_NO_UPDATE_CHECK) {
2644
2691
  })();
2645
2692
  }
2646
2693
  const httpPort = process.env.PORT ? parseInt(process.env.PORT, 10) : undefined;
2647
- // ── Per-request MCP Server factory (HTTP / Smithery stateless mode) ──────────
2648
- // SDK ≥1.9 forbids reusing a stateless transport across requests.
2649
- // Solution: create a fresh Server+Transport per /mcp request.
2650
- // The module-level `server` below is used for stdio mode only.
2651
- function createHttpMcpServer() {
2652
- const srv = new Server({ name: 'cachly-mcp', version: CURRENT_VERSION }, { capabilities: { tools: {} } });
2653
- srv.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
2654
- srv.setRequestHandler(CallToolRequestSchema, async (request) => {
2655
- const { name, arguments: args } = request.params;
2656
- try {
2657
- const text = await handleTool(name, (args ?? {}));
2658
- return { content: [{ type: 'text', text }] };
2659
- }
2660
- catch (err) {
2661
- if (err instanceof McpError)
2662
- throw err;
2663
- throw new McpError(ErrorCode.InternalError, err.message);
2664
- }
2665
- });
2666
- return srv;
2667
- }
2668
2694
  if (httpPort) {
2669
2695
  // ── HTTP mode (Streamable HTTP transport) ───────────────────────────────
2670
2696
  // Used for Smithery URL deployment: PORT=3000 node dist/index.js
2671
- // Multi-tenant: per-request JWT + instance ID resolved via:
2672
- // 1. Authorization: Bearer <jwt> + X-Cachly-Instance-Id: <uuid>
2673
- // 2. Smithery config: ?config=base64url({CACHLY_JWT, CACHLY_BRAIN_INSTANCE_ID, ...})
2674
- // Falls back to module-level env-derived values when neither is provided.
2675
2697
  const { createServer } = await import('node:http');
2676
- function extractReqCtx(req, url) {
2677
- let jwtVal = '';
2678
- let instId = '';
2679
- const auth = req.headers['authorization'];
2680
- if (typeof auth === 'string' && auth.trim()) {
2681
- const a = auth.trim();
2682
- jwtVal = a.toLowerCase().startsWith('bearer ') ? a.slice(7).trim() : a;
2683
- }
2684
- // Fallback: raw JWT in custom header (Smithery "header" transport without Bearer prefix)
2685
- if (!jwtVal) {
2686
- const rawJwt = req.headers['cachly_jwt'] ?? req.headers['cachly-jwt'] ?? req.headers['x-cachly-jwt'] ?? req.headers['x-api-key'];
2687
- if (typeof rawJwt === 'string')
2688
- jwtVal = rawJwt.trim();
2689
- }
2690
- const hdrInst = req.headers['x-cachly-instance-id'];
2691
- if (typeof hdrInst === 'string')
2692
- instId = hdrInst.trim();
2693
- // Smithery passes user config as a base64url-encoded JSON blob in ?config=
2694
- const cfgParam = url.searchParams.get('config');
2695
- if (cfgParam && (!jwtVal || !instId)) {
2696
- try {
2697
- const b64 = cfgParam.replace(/-/g, '+').replace(/_/g, '/');
2698
- const padded = b64 + '='.repeat((4 - b64.length % 4) % 4);
2699
- const json = JSON.parse(Buffer.from(padded, 'base64').toString('utf8'));
2700
- if (!jwtVal && typeof json.CACHLY_JWT === 'string')
2701
- jwtVal = json.CACHLY_JWT;
2702
- if (!instId && typeof json.CACHLY_BRAIN_INSTANCE_ID === 'string')
2703
- instId = json.CACHLY_BRAIN_INSTANCE_ID;
2704
- }
2705
- catch { /* malformed config — ignore, fall back to globals */ }
2706
- }
2707
- // Direct query params (Smithery "query" transport, manual clients, etc.)
2708
- if (!jwtVal) {
2709
- const q = url.searchParams.get('CACHLY_JWT') ?? url.searchParams.get('cachly_jwt') ?? url.searchParams.get('api_key');
2710
- if (q)
2711
- jwtVal = q.trim();
2712
- }
2713
- if (!instId) {
2714
- const q = url.searchParams.get('CACHLY_BRAIN_INSTANCE_ID') ?? url.searchParams.get('cachly_brain_instance_id') ?? url.searchParams.get('instance_id');
2715
- if (q)
2716
- instId = q.trim();
2717
- }
2718
- return { jwt: jwtVal, instanceId: instId };
2719
- }
2698
+ const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
2699
+ await server.connect(transport);
2720
2700
  const httpServer = createServer((req, res) => {
2721
2701
  const url = new URL(req.url ?? '/', `http://localhost:${httpPort}`);
2722
2702
  if (url.pathname === '/health') {
@@ -2724,8 +2704,6 @@ if (httpPort) {
2724
2704
  res.end(JSON.stringify({
2725
2705
  status: 'ok',
2726
2706
  version: CURRENT_VERSION,
2727
- transport: 'streamable-http',
2728
- multiTenant: true,
2729
2707
  zeroResults: {
2730
2708
  total: zeroResultsTotal,
2731
2709
  last10: ZERO_RESULTS_LOG.slice(-10).map(e => ({ query: e.query, ts: new Date(e.ts).toISOString() })),
@@ -2733,52 +2711,8 @@ if (httpPort) {
2733
2711
  }));
2734
2712
  return;
2735
2713
  }
2736
- // Static server card — lets Smithery skip live scanning (avoids auth complexity).
2737
- if (url.pathname === '/.well-known/mcp/server-card.json') {
2738
- const card = {
2739
- name: 'cachly AI Brain',
2740
- version: CURRENT_VERSION,
2741
- description: 'Persistent AI memory for Claude Code, Cursor, Copilot, Windsurf & friends. Remembers every lesson, fix, and architecture decision forever.',
2742
- transport: { type: 'streamable-http', url: 'https://mcp.cachly.dev/mcp' },
2743
- capabilities: { tools: {} },
2744
- };
2745
- res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
2746
- res.end(JSON.stringify(card));
2747
- return;
2748
- }
2749
2714
  if (url.pathname === '/mcp' || url.pathname === '/') {
2750
- // Handle CORS preflight
2751
- if (req.method === 'OPTIONS') {
2752
- res.writeHead(204, {
2753
- 'Access-Control-Allow-Origin': '*',
2754
- 'Access-Control-Allow-Methods': 'GET, POST, DELETE, OPTIONS',
2755
- 'Access-Control-Allow-Headers': 'Authorization, Content-Type, Mcp-Session-Id, X-Cachly-Instance-Id, cachly_jwt, cachly-jwt, x-cachly-jwt, x-api-key',
2756
- });
2757
- res.end();
2758
- return;
2759
- }
2760
- const ctx = extractReqCtx(req, url);
2761
- // Create a fresh Server+Transport for each request (stateless multi-tenant mode).
2762
- // SDK ≥1.9 throws if a stateless transport is reused across requests.
2763
- const reqServer = createHttpMcpServer();
2764
- const reqTransport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
2765
- reqServer.connect(reqTransport).then(() => {
2766
- reqCtx.run(ctx, () => {
2767
- reqTransport.handleRequest(req, res).catch((err) => {
2768
- process.stderr.write(`cachly-mcp HTTP handler error: ${err.message}\n`);
2769
- if (!res.headersSent) {
2770
- res.writeHead(500, { 'Content-Type': 'application/json' });
2771
- res.end(JSON.stringify({ error: 'internal_error' }));
2772
- }
2773
- });
2774
- });
2775
- }).catch((err) => {
2776
- process.stderr.write(`cachly-mcp connect error: ${err.message}\n`);
2777
- if (!res.headersSent) {
2778
- res.writeHead(500, { 'Content-Type': 'application/json' });
2779
- res.end(JSON.stringify({ error: 'internal_error' }));
2780
- }
2781
- });
2715
+ transport.handleRequest(req, res);
2782
2716
  return;
2783
2717
  }
2784
2718
  res.writeHead(404);
@@ -2793,4 +2727,5 @@ else {
2793
2727
  const transport = new StdioServerTransport();
2794
2728
  await server.connect(transport);
2795
2729
  }
2730
+ notify('cachly', 'startup', { version: CURRENT_VERSION, mode: process.env.MCP_HTTP_PORT ? 'http' : 'stdio' }).catch(() => undefined);
2796
2731
  //# sourceMappingURL=index.js.map