@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.
- package/.github/workflows/eas-build-android.yml +58 -0
- package/.github/workflows/eas-update.yml +55 -0
- package/BENCH.md +80 -0
- package/README.md +5 -33
- package/STRATEGY.md +218 -0
- package/apps/travel-chaos-organizer/.env.example +49 -0
- package/apps/travel-chaos-organizer/DEPLOY.md +328 -0
- package/apps/travel-chaos-organizer/Makefile +57 -0
- package/apps/travel-chaos-organizer/README.md +159 -0
- package/apps/travel-chaos-organizer/backend/.env.example +10 -0
- package/apps/travel-chaos-organizer/backend/Dockerfile +25 -0
- package/apps/travel-chaos-organizer/backend/alembic/env.py +56 -0
- package/apps/travel-chaos-organizer/backend/alembic/script.py.mako +24 -0
- package/apps/travel-chaos-organizer/backend/alembic/versions/.gitkeep +0 -0
- package/apps/travel-chaos-organizer/backend/alembic/versions/0001_stripe_customer_id.py +24 -0
- package/apps/travel-chaos-organizer/backend/alembic/versions/0002_telegram_chat_id.py +24 -0
- package/apps/travel-chaos-organizer/backend/alembic.ini +39 -0
- package/apps/travel-chaos-organizer/backend/app/__init__.py +0 -0
- package/apps/travel-chaos-organizer/backend/app/auth/__init__.py +0 -0
- package/apps/travel-chaos-organizer/backend/app/auth/keycloak.py +81 -0
- package/apps/travel-chaos-organizer/backend/app/config.py +66 -0
- package/apps/travel-chaos-organizer/backend/app/db/__init__.py +0 -0
- package/apps/travel-chaos-organizer/backend/app/db/database.py +20 -0
- package/apps/travel-chaos-organizer/backend/app/db/migrations/001_init.sql +71 -0
- package/apps/travel-chaos-organizer/backend/app/db/migrations/002_telemetry_freemium.sql +31 -0
- package/apps/travel-chaos-organizer/backend/app/db/migrations/003_telegram_chat_id.sql +4 -0
- package/apps/travel-chaos-organizer/backend/app/db/migrations/004_stripe_and_email.sql +5 -0
- package/apps/travel-chaos-organizer/backend/app/limiter.py +4 -0
- package/apps/travel-chaos-organizer/backend/app/logging_config.py +25 -0
- package/apps/travel-chaos-organizer/backend/app/main.py +77 -0
- package/apps/travel-chaos-organizer/backend/app/models/__init__.py +0 -0
- package/apps/travel-chaos-organizer/backend/app/models/schemas.py +142 -0
- package/apps/travel-chaos-organizer/backend/app/routers/__init__.py +0 -0
- package/apps/travel-chaos-organizer/backend/app/routers/admin.py +237 -0
- package/apps/travel-chaos-organizer/backend/app/routers/cache.py +13 -0
- package/apps/travel-chaos-organizer/backend/app/routers/events.py +29 -0
- package/apps/travel-chaos-organizer/backend/app/routers/export.py +122 -0
- package/apps/travel-chaos-organizer/backend/app/routers/inbox.py +122 -0
- package/apps/travel-chaos-organizer/backend/app/routers/items.py +130 -0
- package/apps/travel-chaos-organizer/backend/app/routers/mail.py +108 -0
- package/apps/travel-chaos-organizer/backend/app/routers/parse.py +303 -0
- package/apps/travel-chaos-organizer/backend/app/routers/payments.py +179 -0
- package/apps/travel-chaos-organizer/backend/app/routers/trips.py +134 -0
- package/apps/travel-chaos-organizer/backend/app/routers/users.py +169 -0
- package/apps/travel-chaos-organizer/backend/app/routers/waitlist.py +37 -0
- package/apps/travel-chaos-organizer/backend/app/services/__init__.py +0 -0
- package/apps/travel-chaos-organizer/backend/app/services/cache.py +131 -0
- package/apps/travel-chaos-organizer/backend/app/services/drip.py +128 -0
- package/apps/travel-chaos-organizer/backend/app/services/email.py +61 -0
- package/apps/travel-chaos-organizer/backend/app/services/errors.py +44 -0
- package/apps/travel-chaos-organizer/backend/app/services/notifier.py +10 -0
- package/apps/travel-chaos-organizer/backend/app/services/ollama.py +101 -0
- package/apps/travel-chaos-organizer/backend/app/services/parser.py +22 -0
- package/apps/travel-chaos-organizer/backend/app/services/quota.py +85 -0
- package/apps/travel-chaos-organizer/backend/app/services/sentry.py +11 -0
- package/apps/travel-chaos-organizer/backend/app/services/telemetry.py +35 -0
- package/apps/travel-chaos-organizer/backend/pytest.ini +4 -0
- package/apps/travel-chaos-organizer/backend/requirements-dev.txt +6 -0
- package/apps/travel-chaos-organizer/backend/requirements.txt +25 -0
- package/apps/travel-chaos-organizer/backend/tests/__init__.py +0 -0
- package/apps/travel-chaos-organizer/backend/tests/conftest.py +223 -0
- package/apps/travel-chaos-organizer/backend/tests/test_admin.py +99 -0
- package/apps/travel-chaos-organizer/backend/tests/test_cache.py +142 -0
- package/apps/travel-chaos-organizer/backend/tests/test_drip.py +32 -0
- package/apps/travel-chaos-organizer/backend/tests/test_e2e_chain.py +121 -0
- package/apps/travel-chaos-organizer/backend/tests/test_export.py +48 -0
- package/apps/travel-chaos-organizer/backend/tests/test_inbox.py +107 -0
- package/apps/travel-chaos-organizer/backend/tests/test_items.py +138 -0
- package/apps/travel-chaos-organizer/backend/tests/test_mail.py +113 -0
- package/apps/travel-chaos-organizer/backend/tests/test_ollama.py +145 -0
- package/apps/travel-chaos-organizer/backend/tests/test_parse_url.py +98 -0
- package/apps/travel-chaos-organizer/backend/tests/test_parser.py +72 -0
- package/apps/travel-chaos-organizer/backend/tests/test_payments.py +163 -0
- package/apps/travel-chaos-organizer/backend/tests/test_quota.py +46 -0
- package/apps/travel-chaos-organizer/backend/tests/test_search.py +67 -0
- package/apps/travel-chaos-organizer/backend/tests/test_trips.py +123 -0
- package/apps/travel-chaos-organizer/backend/tests/test_users.py +52 -0
- package/apps/travel-chaos-organizer/backups/.gitkeep +0 -0
- package/apps/travel-chaos-organizer/caddy/Caddyfile +27 -0
- package/apps/travel-chaos-organizer/docker-compose.node1.yml +31 -0
- package/apps/travel-chaos-organizer/docker-compose.override.yml +14 -0
- package/apps/travel-chaos-organizer/docker-compose.yml +128 -0
- package/apps/travel-chaos-organizer/frontend/.env.example +13 -0
- package/apps/travel-chaos-organizer/frontend/app/(app)/_layout.tsx +80 -0
- package/apps/travel-chaos-organizer/frontend/app/(app)/inbox.tsx +166 -0
- package/apps/travel-chaos-organizer/frontend/app/(app)/search.tsx +130 -0
- package/apps/travel-chaos-organizer/frontend/app/(app)/settings.tsx +381 -0
- package/apps/travel-chaos-organizer/frontend/app/(app)/trips/[id].tsx +346 -0
- package/apps/travel-chaos-organizer/frontend/app/(app)/trips.tsx +363 -0
- package/apps/travel-chaos-organizer/frontend/app/(app)/upgrade/cancel.tsx +34 -0
- package/apps/travel-chaos-organizer/frontend/app/(app)/upgrade/success.tsx +35 -0
- package/apps/travel-chaos-organizer/frontend/app/(auth)/login.tsx +91 -0
- package/apps/travel-chaos-organizer/frontend/app/_layout.tsx +71 -0
- package/apps/travel-chaos-organizer/frontend/app/auth.tsx +29 -0
- package/apps/travel-chaos-organizer/frontend/app/index.tsx +16 -0
- package/apps/travel-chaos-organizer/frontend/app/onboarding.tsx +119 -0
- package/apps/travel-chaos-organizer/frontend/app.config.ts +54 -0
- package/apps/travel-chaos-organizer/frontend/assets/adaptive-icon.svg +66 -0
- package/apps/travel-chaos-organizer/frontend/assets/icon.svg +79 -0
- package/apps/travel-chaos-organizer/frontend/assets/splash.svg +15 -0
- package/apps/travel-chaos-organizer/frontend/components/BottomSheet.tsx +102 -0
- package/apps/travel-chaos-organizer/frontend/components/ConfettiCannon.tsx +107 -0
- package/apps/travel-chaos-organizer/frontend/components/ConfirmDialog.tsx +109 -0
- package/apps/travel-chaos-organizer/frontend/components/ErrorBoundary.tsx +45 -0
- package/apps/travel-chaos-organizer/frontend/components/FileUploadButton.tsx +19 -0
- package/apps/travel-chaos-organizer/frontend/components/ImportSheet.tsx +155 -0
- package/apps/travel-chaos-organizer/frontend/components/OfflineBadge.tsx +55 -0
- package/apps/travel-chaos-organizer/frontend/components/QRCodeViewer.tsx +80 -0
- package/apps/travel-chaos-organizer/frontend/components/ScoreRing.tsx +39 -0
- package/apps/travel-chaos-organizer/frontend/components/Screen.tsx +39 -0
- package/apps/travel-chaos-organizer/frontend/components/Skeleton.tsx +70 -0
- package/apps/travel-chaos-organizer/frontend/components/SwipeableInboxItem.tsx +121 -0
- package/apps/travel-chaos-organizer/frontend/components/SwipeableTimelineItem.tsx +70 -0
- package/apps/travel-chaos-organizer/frontend/components/TimelineItem.tsx +73 -0
- package/apps/travel-chaos-organizer/frontend/components/TimelineItemDetail.tsx +130 -0
- package/apps/travel-chaos-organizer/frontend/components/Toast.tsx +60 -0
- package/apps/travel-chaos-organizer/frontend/components/ToastContext.tsx +42 -0
- package/apps/travel-chaos-organizer/frontend/components/TripCard.tsx +146 -0
- package/apps/travel-chaos-organizer/frontend/components/TripQRModal.tsx +70 -0
- package/apps/travel-chaos-organizer/frontend/components/UpgradeBanner.tsx +44 -0
- package/apps/travel-chaos-organizer/frontend/eas.json +22 -0
- package/apps/travel-chaos-organizer/frontend/hooks/useInbox.ts +24 -0
- package/apps/travel-chaos-organizer/frontend/hooks/useNetworkSync.ts +60 -0
- package/apps/travel-chaos-organizer/frontend/hooks/useTrips.ts +112 -0
- package/apps/travel-chaos-organizer/frontend/lib/api.ts +223 -0
- package/apps/travel-chaos-organizer/frontend/lib/auth.ts +93 -0
- package/apps/travel-chaos-organizer/frontend/lib/db.ts +86 -0
- package/apps/travel-chaos-organizer/frontend/lib/fileCache.ts +54 -0
- package/apps/travel-chaos-organizer/frontend/lib/haptics.ts +13 -0
- package/apps/travel-chaos-organizer/frontend/lib/notifications.ts +112 -0
- package/apps/travel-chaos-organizer/frontend/lib/offlineQueue.ts +145 -0
- package/apps/travel-chaos-organizer/frontend/lib/quota.tsx +43 -0
- package/apps/travel-chaos-organizer/frontend/lib/sentry.ts +28 -0
- package/apps/travel-chaos-organizer/frontend/lib/shareIntent.ts +44 -0
- package/apps/travel-chaos-organizer/frontend/package-lock.json +13221 -0
- package/apps/travel-chaos-organizer/frontend/package.json +44 -0
- package/apps/travel-chaos-organizer/frontend/tsconfig.json +23 -0
- package/apps/travel-chaos-organizer/keycloak/realm-export.json +59 -0
- package/apps/travel-chaos-organizer/landing/index.html +773 -0
- package/apps/travel-chaos-organizer/scripts/backup.sh +14 -0
- package/apps/travel-chaos-organizer/scripts/configure-env.sh +82 -0
- package/apps/travel-chaos-organizer/scripts/deploy.sh +54 -0
- package/apps/travel-chaos-organizer/scripts/health-check.sh +36 -0
- package/apps/travel-chaos-organizer/scripts/migrate.sh +7 -0
- package/apps/travel-chaos-organizer/scripts/server-setup.sh +85 -0
- package/apps/travel-chaos-organizer/scripts/smoke-test.sh +29 -0
- package/apps/travel-chaos-organizer/scripts/verify-tracking.sh +71 -0
- package/apps/travel-chaos-organizer/telegram-bot/.env.example +3 -0
- package/apps/travel-chaos-organizer/telegram-bot/Dockerfile +6 -0
- package/apps/travel-chaos-organizer/telegram-bot/bot.py +504 -0
- package/apps/travel-chaos-organizer/telegram-bot/requirements.txt +3 -0
- package/dist/packages/telegram-notify/client.d.ts +19 -0
- package/dist/packages/telegram-notify/client.js +75 -0
- package/dist/packages/telegram-notify/client.js.map +1 -0
- package/dist/src/__tests__/auth.test.js.map +1 -0
- package/dist/{__tests__ → src/__tests__}/brain-flow.test.js +141 -0
- package/dist/src/__tests__/brain-flow.test.js.map +1 -0
- package/dist/src/__tests__/confidence.test.js.map +1 -0
- package/dist/src/__tests__/e2e/api-auth.test.js.map +1 -0
- package/dist/src/__tests__/e2e/api-health.test.js.map +1 -0
- package/dist/src/__tests__/e2e/cli.test.js.map +1 -0
- package/dist/src/__tests__/e2e/config.js.map +1 -0
- package/dist/src/__tests__/e2e/funnel.test.js.map +1 -0
- package/dist/src/__tests__/e2e/mcp-tools.test.js.map +1 -0
- package/dist/src/__tests__/e2e/provisioning.test.js.map +1 -0
- package/dist/{__tests__ → src/__tests__}/handlers-unit.test.js +0 -2
- package/dist/src/__tests__/handlers-unit.test.js.map +1 -0
- package/dist/src/__tests__/namespace.test.js.map +1 -0
- package/dist/src/__tests__/rerank.test.js +98 -0
- package/dist/src/__tests__/rerank.test.js.map +1 -0
- package/dist/src/auth.js.map +1 -0
- package/dist/src/bench/cachly-bench.d.ts +35 -0
- package/dist/src/bench/cachly-bench.js +182 -0
- package/dist/src/bench/cachly-bench.js.map +1 -0
- package/dist/src/bench/fixtures.d.ts +34 -0
- package/dist/src/bench/fixtures.js +199 -0
- package/dist/src/bench/fixtures.js.map +1 -0
- package/dist/src/ckg.js.map +1 -0
- package/dist/src/confidence.js.map +1 -0
- package/dist/src/embeddings.js.map +1 -0
- package/dist/src/handlers/advanced.js.map +1 -0
- package/dist/{handlers → src/handlers}/brain.js +261 -87
- package/dist/src/handlers/brain.js.map +1 -0
- package/dist/src/handlers/cache.js.map +1 -0
- package/dist/{handlers → src/handlers}/context.js +9 -4
- package/dist/src/handlers/context.js.map +1 -0
- package/dist/{handlers → src/handlers}/fedbrain.d.ts +6 -0
- package/dist/{handlers → src/handlers}/fedbrain.js +19 -7
- package/dist/src/handlers/fedbrain.js.map +1 -0
- package/dist/src/handlers/instances.js.map +1 -0
- package/dist/{handlers → src/handlers}/roadmap.js +7 -3
- package/dist/src/handlers/roadmap.js.map +1 -0
- package/dist/src/handlers/syndicate.js.map +1 -0
- package/dist/src/handlers/tco.d.ts +16 -0
- package/dist/src/handlers/tco.js +303 -0
- package/dist/src/handlers/tco.js.map +1 -0
- package/dist/src/handlers/team.js.map +1 -0
- package/dist/{index.js → src/index.js} +153 -218
- package/dist/src/index.js.map +1 -0
- package/dist/src/namespace.js.map +1 -0
- package/dist/src/notifier.d.ts +5 -0
- package/dist/src/notifier.js +6 -0
- package/dist/src/notifier.js.map +1 -0
- package/dist/src/rerank.d.ts +50 -0
- package/dist/src/rerank.js +92 -0
- package/dist/src/rerank.js.map +1 -0
- package/dist/src/search.js.map +1 -0
- package/dist/src/search.test.d.ts +1 -0
- package/dist/src/search.test.js.map +1 -0
- package/dist/{tools.d.ts → src/tools.d.ts} +196 -0
- package/dist/{tools.js → src/tools.js} +178 -0
- package/dist/src/tools.js.map +1 -0
- package/dist/src/utils.js.map +1 -0
- package/package.json +4 -3
- package/packages/telegram-notify/SPLIT_PLAN.md +106 -0
- package/packages/telegram-notify/client.ts +88 -0
- package/packages/telegram-notify/emojis.json +22 -0
- package/packages/telegram-notify/pyproject.toml +17 -0
- package/packages/telegram-notify/telegram_notify/__init__.py +3 -0
- package/packages/telegram-notify/telegram_notify/client.py +87 -0
- package/packages/telegram-notify/telegram_notify/emojis.json +22 -0
- package/scripts/generate-icons.js +28 -0
- package/smithery.yaml +21 -8
- package/src/__tests__/brain-flow.test.ts +174 -0
- package/src/__tests__/handlers-unit.test.ts +1 -2
- package/src/__tests__/rerank.test.ts +111 -0
- package/src/bench/cachly-bench.ts +217 -0
- package/src/bench/fixtures.ts +226 -0
- package/src/handlers/brain.ts +276 -92
- package/src/handlers/context.ts +9 -4
- package/src/handlers/fedbrain.ts +21 -7
- package/src/handlers/roadmap.ts +7 -3
- package/src/handlers/tco.ts +347 -0
- package/src/index.ts +139 -220
- package/src/notifier.ts +5 -0
- package/src/rerank.ts +123 -0
- package/src/tools.ts +191 -0
- package/tsconfig.json +2 -3
- package/dist/__tests__/auth.test.js.map +0 -1
- package/dist/__tests__/brain-flow.test.js.map +0 -1
- package/dist/__tests__/brain-predict-failures.test.d.ts +0 -17
- package/dist/__tests__/brain-predict-failures.test.js +0 -339
- package/dist/__tests__/brain-predict-failures.test.js.map +0 -1
- package/dist/__tests__/confidence.test.js.map +0 -1
- package/dist/__tests__/e2e/api-auth.test.js.map +0 -1
- package/dist/__tests__/e2e/api-health.test.js.map +0 -1
- package/dist/__tests__/e2e/cli.test.js.map +0 -1
- package/dist/__tests__/e2e/config.js.map +0 -1
- package/dist/__tests__/e2e/funnel.test.js.map +0 -1
- package/dist/__tests__/e2e/mcp-tools.test.js.map +0 -1
- package/dist/__tests__/e2e/provisioning.test.js.map +0 -1
- package/dist/__tests__/handlers-unit.test.js.map +0 -1
- package/dist/__tests__/namespace.test.js.map +0 -1
- package/dist/auth.js.map +0 -1
- package/dist/ckg.js.map +0 -1
- package/dist/confidence.js.map +0 -1
- package/dist/embeddings.js.map +0 -1
- package/dist/handlers/advanced.js.map +0 -1
- package/dist/handlers/brain.js.map +0 -1
- package/dist/handlers/cache.js.map +0 -1
- package/dist/handlers/context.js.map +0 -1
- package/dist/handlers/fedbrain.js.map +0 -1
- package/dist/handlers/instances.js.map +0 -1
- package/dist/handlers/roadmap.js.map +0 -1
- package/dist/handlers/syndicate.js.map +0 -1
- package/dist/handlers/team.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/namespace.js.map +0 -1
- package/dist/search.js.map +0 -1
- package/dist/search.test.js.map +0 -1
- package/dist/tools.js.map +0 -1
- package/dist/utils.js.map +0 -1
- package/src/__tests__/brain-predict-failures.test.ts +0 -430
- /package/dist/{__tests__ → src/__tests__}/auth.test.d.ts +0 -0
- /package/dist/{__tests__ → src/__tests__}/auth.test.js +0 -0
- /package/dist/{__tests__ → src/__tests__}/brain-flow.test.d.ts +0 -0
- /package/dist/{__tests__ → src/__tests__}/confidence.test.d.ts +0 -0
- /package/dist/{__tests__ → src/__tests__}/confidence.test.js +0 -0
- /package/dist/{__tests__ → src/__tests__}/e2e/api-auth.test.d.ts +0 -0
- /package/dist/{__tests__ → src/__tests__}/e2e/api-auth.test.js +0 -0
- /package/dist/{__tests__ → src/__tests__}/e2e/api-health.test.d.ts +0 -0
- /package/dist/{__tests__ → src/__tests__}/e2e/api-health.test.js +0 -0
- /package/dist/{__tests__ → src/__tests__}/e2e/cli.test.d.ts +0 -0
- /package/dist/{__tests__ → src/__tests__}/e2e/cli.test.js +0 -0
- /package/dist/{__tests__ → src/__tests__}/e2e/config.d.ts +0 -0
- /package/dist/{__tests__ → src/__tests__}/e2e/config.js +0 -0
- /package/dist/{__tests__ → src/__tests__}/e2e/funnel.test.d.ts +0 -0
- /package/dist/{__tests__ → src/__tests__}/e2e/funnel.test.js +0 -0
- /package/dist/{__tests__ → src/__tests__}/e2e/mcp-tools.test.d.ts +0 -0
- /package/dist/{__tests__ → src/__tests__}/e2e/mcp-tools.test.js +0 -0
- /package/dist/{__tests__ → src/__tests__}/e2e/provisioning.test.d.ts +0 -0
- /package/dist/{__tests__ → src/__tests__}/e2e/provisioning.test.js +0 -0
- /package/dist/{__tests__ → src/__tests__}/handlers-unit.test.d.ts +0 -0
- /package/dist/{__tests__ → src/__tests__}/namespace.test.d.ts +0 -0
- /package/dist/{__tests__ → src/__tests__}/namespace.test.js +0 -0
- /package/dist/{search.test.d.ts → src/__tests__/rerank.test.d.ts} +0 -0
- /package/dist/{auth.d.ts → src/auth.d.ts} +0 -0
- /package/dist/{auth.js → src/auth.js} +0 -0
- /package/dist/{ckg.d.ts → src/ckg.d.ts} +0 -0
- /package/dist/{ckg.js → src/ckg.js} +0 -0
- /package/dist/{confidence.d.ts → src/confidence.d.ts} +0 -0
- /package/dist/{confidence.js → src/confidence.js} +0 -0
- /package/dist/{embeddings.d.ts → src/embeddings.d.ts} +0 -0
- /package/dist/{embeddings.js → src/embeddings.js} +0 -0
- /package/dist/{handlers → src/handlers}/advanced.d.ts +0 -0
- /package/dist/{handlers → src/handlers}/advanced.js +0 -0
- /package/dist/{handlers → src/handlers}/brain.d.ts +0 -0
- /package/dist/{handlers → src/handlers}/cache.d.ts +0 -0
- /package/dist/{handlers → src/handlers}/cache.js +0 -0
- /package/dist/{handlers → src/handlers}/context.d.ts +0 -0
- /package/dist/{handlers → src/handlers}/instances.d.ts +0 -0
- /package/dist/{handlers → src/handlers}/instances.js +0 -0
- /package/dist/{handlers → src/handlers}/roadmap.d.ts +0 -0
- /package/dist/{handlers → src/handlers}/syndicate.d.ts +0 -0
- /package/dist/{handlers → src/handlers}/syndicate.js +0 -0
- /package/dist/{handlers → src/handlers}/team.d.ts +0 -0
- /package/dist/{handlers → src/handlers}/team.js +0 -0
- /package/dist/{index.d.ts → src/index.d.ts} +0 -0
- /package/dist/{namespace.d.ts → src/namespace.d.ts} +0 -0
- /package/dist/{namespace.js → src/namespace.js} +0 -0
- /package/dist/{search.d.ts → src/search.d.ts} +0 -0
- /package/dist/{search.js → src/search.js} +0 -0
- /package/dist/{search.test.js → src/search.test.js} +0 -0
- /package/dist/{utils.d.ts → src/utils.d.ts} +0 -0
- /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.
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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 (!
|
|
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 ${
|
|
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 ${
|
|
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 ${
|
|
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 ${
|
|
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 ${
|
|
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
|
-
|
|
246
|
-
|
|
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() +
|
|
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', ...(
|
|
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
|
-
|
|
334
|
-
|
|
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
|
-
|
|
405
|
+
catch (err) {
|
|
367
406
|
client.disconnect();
|
|
368
|
-
const msg =
|
|
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', ...(
|
|
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(
|
|
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 ${
|
|
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
|
-
...(
|
|
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 (!
|
|
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 &&
|
|
576
|
+
if (!_firstCallSuccessSent && JWT) {
|
|
538
577
|
_firstCallSuccessSent = true;
|
|
539
|
-
sendFunnelEvent('first_call_success', { tool: name, instance_id: args.instance_id ??
|
|
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 ??
|
|
544
|
-
const telemetryExtra =
|
|
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
|
-
|
|
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
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
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 &&
|
|
645
|
+
if (!_firstCallSuccessSent && JWT) {
|
|
610
646
|
_firstCallSuccessSent = true;
|
|
611
|
-
sendFunnelEvent('first_call_success', { tool: name, instance_id: args.instance_id ??
|
|
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 ??
|
|
637
|
-
const telemetryExtra =
|
|
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 ??
|
|
646
|
-
const telemetryExtra =
|
|
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
|
-
|
|
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 (!
|
|
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 =
|
|
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 ===
|
|
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
|
-
|
|
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 <
|
|
2226
|
-
await new Promise(r => setTimeout(r,
|
|
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 || !
|
|
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 || !
|
|
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
|
-
|
|
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
|
-
|
|
2677
|
-
|
|
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
|
-
|
|
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
|