@cachly-dev/mcp-server 0.10.47 → 0.10.49
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/STRATEGY.md +209 -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 +13037 -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 +143 -2
- 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 +1 -1
- package/dist/src/__tests__/handlers-unit.test.js.map +1 -0
- package/dist/src/__tests__/namespace.test.js.map +1 -0
- package/dist/src/auth.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 +135 -31
- 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 +15 -2
- 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 +8 -4
- 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/{handlers → src/handlers}/team.js +1 -1
- package/dist/src/handlers/team.js.map +1 -0
- package/dist/{index.js → src/index.js} +145 -97
- 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/{search.js → src/search.js} +3 -3
- package/dist/src/search.js.map +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 +3 -2
- 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 -10
- package/src/__tests__/brain-flow.test.ts +176 -2
- package/src/__tests__/handlers-unit.test.ts +1 -1
- package/src/handlers/brain.ts +139 -32
- package/src/handlers/context.ts +9 -4
- package/src/handlers/fedbrain.ts +17 -2
- package/src/handlers/roadmap.ts +8 -4
- package/src/handlers/tco.ts +347 -0
- package/src/handlers/team.ts +1 -1
- package/src/index.ts +137 -147
- package/src/notifier.ts +5 -0
- package/src/search.ts +3 -3
- 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/{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/{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.test.d.ts → src/search.test.d.ts} +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')
|
|
@@ -243,12 +322,12 @@ async function getConnection(instance_id) {
|
|
|
243
322
|
}
|
|
244
323
|
if (pool.has(instance_id))
|
|
245
324
|
return pool.get(instance_id);
|
|
246
|
-
// Fetch instance, waiting up to
|
|
325
|
+
// Fetch instance, waiting up to PROVISION_TIMEOUT_MS if it is still provisioning.
|
|
247
326
|
// This covers the zero-friction path: device-flow auth → auto-provision → first tool call
|
|
248
327
|
// all happen in quick succession and the instance isn't running yet.
|
|
249
328
|
let inst = await apiFetch(`/api/v1/instances/${instance_id}`);
|
|
250
329
|
if (inst.status === 'provisioning') {
|
|
251
|
-
const deadline = Date.now() +
|
|
330
|
+
const deadline = Date.now() + PROVISION_TIMEOUT_MS;
|
|
252
331
|
while (inst.status === 'provisioning' && Date.now() < deadline) {
|
|
253
332
|
await new Promise(r => setTimeout(r, 3000));
|
|
254
333
|
inst = await apiFetch(`/api/v1/instances/${instance_id}`);
|
|
@@ -258,7 +337,7 @@ async function getConnection(instance_id) {
|
|
|
258
337
|
// Telemetry: unreachable instance so the team can proactively investigate
|
|
259
338
|
void fetch(`${API_URL}/api/v1/telemetry/mcp`, {
|
|
260
339
|
method: 'POST',
|
|
261
|
-
headers: { 'Content-Type': 'application/json', ...(
|
|
340
|
+
headers: { 'Content-Type': 'application/json', ...(JWT ? { Authorization: `Bearer ${JWT}` } : {}) },
|
|
262
341
|
body: JSON.stringify({ event: 'instance_not_reachable', instance_id, status: inst.status, version: CURRENT_VERSION }),
|
|
263
342
|
signal: AbortSignal.timeout(3000),
|
|
264
343
|
}).catch(() => { });
|
|
@@ -310,6 +389,7 @@ async function getConnection(instance_id) {
|
|
|
310
389
|
host: inst.host,
|
|
311
390
|
port: inst.port,
|
|
312
391
|
password: password || undefined,
|
|
392
|
+
commandTimeout: 8000,
|
|
313
393
|
...(tlsEnabled ? { tls: {} } : {}),
|
|
314
394
|
lazyConnect: true,
|
|
315
395
|
enableReadyCheck: true,
|
|
@@ -327,7 +407,7 @@ async function getConnection(instance_id) {
|
|
|
327
407
|
const msg = err instanceof Error ? err.message : String(err);
|
|
328
408
|
void fetch(`${API_URL}/api/v1/telemetry/mcp`, {
|
|
329
409
|
method: 'POST',
|
|
330
|
-
headers: { 'Content-Type': 'application/json', ...(
|
|
410
|
+
headers: { 'Content-Type': 'application/json', ...(JWT ? { Authorization: `Bearer ${JWT}` } : {}) },
|
|
331
411
|
body: JSON.stringify({ event: 'redis_connect_failed', instance_id, host: inst.host, port: inst.port, error: msg, version: CURRENT_VERSION }),
|
|
332
412
|
signal: AbortSignal.timeout(3000),
|
|
333
413
|
}).catch(() => { });
|
|
@@ -341,14 +421,14 @@ async function getConnection(instance_id) {
|
|
|
341
421
|
}
|
|
342
422
|
// ── API helper ────────────────────────────────────────────────────────────────
|
|
343
423
|
async function apiFetch(path, options = {}) {
|
|
344
|
-
checkJwt(
|
|
424
|
+
checkJwt(JWT);
|
|
345
425
|
const timeoutMs = Number(process.env.CACHLY_API_TIMEOUT_MS ?? 15_000);
|
|
346
426
|
const res = await fetch(`${API_URL}${path}`, {
|
|
347
427
|
...options,
|
|
348
428
|
signal: AbortSignal.timeout(timeoutMs),
|
|
349
429
|
headers: {
|
|
350
430
|
'Content-Type': 'application/json',
|
|
351
|
-
Authorization: `Bearer ${
|
|
431
|
+
Authorization: `Bearer ${JWT}`,
|
|
352
432
|
...options.headers,
|
|
353
433
|
},
|
|
354
434
|
});
|
|
@@ -369,7 +449,7 @@ import { handleTeamTool } from './handlers/team.js';
|
|
|
369
449
|
import { handleRoadmapTool } from './handlers/roadmap.js';
|
|
370
450
|
import { handleAdvancedTool } from './handlers/advanced.js';
|
|
371
451
|
import { handleSyndicateTool } from './handlers/syndicate.js';
|
|
372
|
-
import { handleFedbrainTool } from './handlers/fedbrain.js';
|
|
452
|
+
import { handleFedbrainTool, _lastBrainFromGitCounts } from './handlers/fedbrain.js';
|
|
373
453
|
// ── Tools (imported from tools.ts) ─────────────────────────────────────────
|
|
374
454
|
import { TOOLS } from './tools.js';
|
|
375
455
|
// ── Handlers ──────────────────────────────────────────────────────────────────
|
|
@@ -392,7 +472,7 @@ function sendFunnelEvent(event, extra) {
|
|
|
392
472
|
headers: { 'Content-Type': 'application/json' },
|
|
393
473
|
body: JSON.stringify({
|
|
394
474
|
event, version: CURRENT_VERSION, editor: detectEditor(),
|
|
395
|
-
...(
|
|
475
|
+
...(JWT ? { jwt: JWT } : {}),
|
|
396
476
|
...extra,
|
|
397
477
|
}),
|
|
398
478
|
signal: AbortSignal.timeout(3000),
|
|
@@ -416,7 +496,7 @@ async function sendAnonymousTelemetry(toolName) {
|
|
|
416
496
|
}
|
|
417
497
|
async function handleTool(name, args) {
|
|
418
498
|
// Guard: if no JWT, return actionable onboarding message instead of HTTP 401
|
|
419
|
-
if (!
|
|
499
|
+
if (!JWT) {
|
|
420
500
|
void sendAnonymousTelemetry(name);
|
|
421
501
|
// ── Zero-credential device flow ─────────────────────────────────────
|
|
422
502
|
// 1st call: start device flow, return code + URL
|
|
@@ -493,14 +573,14 @@ async function handleTool(name, args) {
|
|
|
493
573
|
// Delegate brain tools (learn, recall, session, etc.)
|
|
494
574
|
const brainResult = await handleBrainTool(name, args, getConnection, apiFetch);
|
|
495
575
|
if (brainResult !== null) {
|
|
496
|
-
if (!_firstCallSuccessSent &&
|
|
576
|
+
if (!_firstCallSuccessSent && JWT) {
|
|
497
577
|
_firstCallSuccessSent = true;
|
|
498
|
-
sendFunnelEvent('first_call_success', { tool: name, instance_id: args.instance_id ??
|
|
578
|
+
sendFunnelEvent('first_call_success', { tool: name, instance_id: args.instance_id ?? _defaultInstanceId ?? '' });
|
|
499
579
|
}
|
|
500
580
|
// Per-tool telemetry — fires after every successful brain tool call.
|
|
501
581
|
// api_key carries the cky_live_... token so the backend can resolve tenant + increment counters.
|
|
502
|
-
const instanceId = args.instance_id ??
|
|
503
|
-
const telemetryExtra =
|
|
582
|
+
const instanceId = args.instance_id ?? _defaultInstanceId ?? '';
|
|
583
|
+
const telemetryExtra = JWT ? { api_key: JWT, instance_id: instanceId } : { instance_id: instanceId };
|
|
504
584
|
if (name === 'recall_best_solution') {
|
|
505
585
|
// recall_best_solution → increments BrainRecallCount + triggers level-up logic
|
|
506
586
|
sendFunnelEvent('recall_best_solution', telemetryExtra);
|
|
@@ -528,17 +608,14 @@ async function handleTool(name, args) {
|
|
|
528
608
|
limit: 50,
|
|
529
609
|
}, getConnection, apiFetch);
|
|
530
610
|
if (gitBootstrap !== null) {
|
|
531
|
-
|
|
611
|
+
const gitCounts = _lastBrainFromGitCounts;
|
|
612
|
+
sendFunnelEvent('brain_from_git', { ...telemetryExtra, ...(gitCounts ?? {}) });
|
|
532
613
|
sendFunnelEvent('recall_best_solution', telemetryExtra);
|
|
533
614
|
// Append bootstrap summary to the session_start briefing.
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
brainResult.content.push({
|
|
539
|
-
type: 'text',
|
|
540
|
-
text: '\n---\n' + bootstrapText,
|
|
541
|
-
});
|
|
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;
|
|
542
619
|
}
|
|
543
620
|
}
|
|
544
621
|
}
|
|
@@ -556,7 +633,7 @@ async function handleTool(name, args) {
|
|
|
556
633
|
const srText = typeof brainResult === 'object' && brainResult !== null && 'content' in brainResult
|
|
557
634
|
? JSON.stringify(brainResult)
|
|
558
635
|
: String(brainResult ?? '');
|
|
559
|
-
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')) {
|
|
560
637
|
sendFunnelEvent('recall_best_solution', telemetryExtra);
|
|
561
638
|
}
|
|
562
639
|
}
|
|
@@ -565,9 +642,9 @@ async function handleTool(name, args) {
|
|
|
565
642
|
// Delegate context tools (remember/recall/list/forget)
|
|
566
643
|
const contextResult = await handleContextTool(name, args, getConnection, apiFetch);
|
|
567
644
|
if (contextResult !== null) {
|
|
568
|
-
if (!_firstCallSuccessSent &&
|
|
645
|
+
if (!_firstCallSuccessSent && JWT) {
|
|
569
646
|
_firstCallSuccessSent = true;
|
|
570
|
-
sendFunnelEvent('first_call_success', { tool: name, instance_id: args.instance_id ??
|
|
647
|
+
sendFunnelEvent('first_call_success', { tool: name, instance_id: args.instance_id ?? _defaultInstanceId ?? '' });
|
|
571
648
|
}
|
|
572
649
|
return contextResult;
|
|
573
650
|
}
|
|
@@ -592,8 +669,8 @@ async function handleTool(name, args) {
|
|
|
592
669
|
if (syndicateResult !== null) {
|
|
593
670
|
// brain_predict is in syndicate handler — track telemetry here.
|
|
594
671
|
if (name === 'brain_predict') {
|
|
595
|
-
const instanceId = args.instance_id ??
|
|
596
|
-
const telemetryExtra =
|
|
672
|
+
const instanceId = args.instance_id ?? _defaultInstanceId ?? '';
|
|
673
|
+
const telemetryExtra = JWT ? { api_key: JWT, instance_id: instanceId } : { instance_id: instanceId };
|
|
597
674
|
sendFunnelEvent('brain_predict', telemetryExtra);
|
|
598
675
|
}
|
|
599
676
|
return syndicateResult;
|
|
@@ -601,16 +678,21 @@ async function handleTool(name, args) {
|
|
|
601
678
|
const fedbrainResult = await handleFedbrainTool(name, args, getConnection, apiFetch);
|
|
602
679
|
if (fedbrainResult !== null) {
|
|
603
680
|
// brain_from_git and brain_predict_failures are in fedbrain handler.
|
|
604
|
-
const instanceId = args.instance_id ??
|
|
605
|
-
const telemetryExtra =
|
|
681
|
+
const instanceId = args.instance_id ?? _defaultInstanceId ?? '';
|
|
682
|
+
const telemetryExtra = JWT ? { api_key: JWT, instance_id: instanceId } : { instance_id: instanceId };
|
|
606
683
|
if (name === 'brain_from_git') {
|
|
607
|
-
|
|
684
|
+
const gitCounts = _lastBrainFromGitCounts;
|
|
685
|
+
sendFunnelEvent('brain_from_git', { ...telemetryExtra, ...(gitCounts ?? {}) });
|
|
608
686
|
}
|
|
609
687
|
else if (name === 'brain_predict_failures') {
|
|
610
688
|
sendFunnelEvent('brain_predict_failures', telemetryExtra);
|
|
611
689
|
}
|
|
612
690
|
return fedbrainResult;
|
|
613
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;
|
|
614
696
|
switch (name) {
|
|
615
697
|
// ── Instance management ──────────────────────────────────────────────
|
|
616
698
|
case 'get_api_status': {
|
|
@@ -630,7 +712,7 @@ async function handleTool(name, args) {
|
|
|
630
712
|
healthStatus = `unreachable: ${e.message}`;
|
|
631
713
|
}
|
|
632
714
|
// Check JWT / auth
|
|
633
|
-
if (!
|
|
715
|
+
if (!JWT) {
|
|
634
716
|
return [
|
|
635
717
|
`📡 **cachly API Status**`,
|
|
636
718
|
``,
|
|
@@ -644,7 +726,7 @@ async function handleTool(name, args) {
|
|
|
644
726
|
// Decode JWT claims (inspection only, no verification)
|
|
645
727
|
let authInfo = '❌ invalid JWT format';
|
|
646
728
|
try {
|
|
647
|
-
const parts =
|
|
729
|
+
const parts = JWT.split('.');
|
|
648
730
|
if (parts.length === 3) {
|
|
649
731
|
const payload = JSON.parse(Buffer.from(parts[1], 'base64url').toString('utf-8'));
|
|
650
732
|
const sub = payload.sub ?? '(unknown)';
|
|
@@ -676,7 +758,7 @@ async function handleTool(name, args) {
|
|
|
676
758
|
const lines = ['\n\n🧠 **Brain Instances:**'];
|
|
677
759
|
for (const inst of instances) {
|
|
678
760
|
const badge = inst.status === 'running' ? '🟢' : inst.status === 'provisioning' ? '🟡' : '🔴';
|
|
679
|
-
const defaultMark = inst.id ===
|
|
761
|
+
const defaultMark = inst.id === _defaultInstanceId ? ' ← active' : '';
|
|
680
762
|
lines.push(` ${badge} **${inst.name}** (\`${inst.id}\`) · ${inst.status}${defaultMark}`);
|
|
681
763
|
if (inst.status === 'running' && inst.host) {
|
|
682
764
|
// Try a quick ping on the cached connection
|
|
@@ -820,7 +902,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
820
902
|
catch (err) {
|
|
821
903
|
if (err instanceof McpError)
|
|
822
904
|
throw err;
|
|
823
|
-
|
|
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);
|
|
824
908
|
}
|
|
825
909
|
});
|
|
826
910
|
// Graceful shutdown – close all Redis connections + auto-end brain session
|
|
@@ -2030,6 +2114,7 @@ if (process.argv[2] === 'setup') {
|
|
|
2030
2114
|
const AUTH_BASE = 'https://auth.cachly.dev/realms/cachly/protocol/openid-connect';
|
|
2031
2115
|
const CLIENT_ID = 'cachly-cli';
|
|
2032
2116
|
console.log('Step 1: Sign in to cachly (free, no credit card)\n');
|
|
2117
|
+
sendFunnelEvent('setup_auth_started');
|
|
2033
2118
|
// Start device flow
|
|
2034
2119
|
let deviceCode = '', userCode = '', verifyUri = '', pollInterval = 5000;
|
|
2035
2120
|
try {
|
|
@@ -2055,6 +2140,7 @@ if (process.argv[2] === 'setup') {
|
|
|
2055
2140
|
rl.close();
|
|
2056
2141
|
process.exit(1);
|
|
2057
2142
|
}
|
|
2143
|
+
sendFunnelEvent('setup_auth_completed');
|
|
2058
2144
|
console.log('');
|
|
2059
2145
|
deviceCode = ''; // mark as fallback so we skip polling
|
|
2060
2146
|
}
|
|
@@ -2087,6 +2173,7 @@ if (process.argv[2] === 'setup') {
|
|
|
2087
2173
|
if (tokenData.access_token) {
|
|
2088
2174
|
token = tokenData.access_token;
|
|
2089
2175
|
console.log(' \x1b[32m✓ Authorized!\x1b[0m\n');
|
|
2176
|
+
sendFunnelEvent('setup_auth_completed');
|
|
2090
2177
|
break;
|
|
2091
2178
|
}
|
|
2092
2179
|
// authorization_pending = keep polling; slow_down = increase interval
|
|
@@ -2180,9 +2267,10 @@ if (process.argv[2] === 'setup') {
|
|
|
2180
2267
|
// Newly created — poll until running or give up after 30 s.
|
|
2181
2268
|
const newId = autoBody.instance_id;
|
|
2182
2269
|
console.log(` ✓ created (${newId.slice(0, 8)}…)\n`);
|
|
2270
|
+
// 45 attempts × 3s = 135s — generous for free-tier in slow regions.
|
|
2183
2271
|
process.stdout.write('⏳ Waiting for instance to start');
|
|
2184
|
-
for (let attempt = 0; attempt <
|
|
2185
|
-
await new Promise(r => setTimeout(r,
|
|
2272
|
+
for (let attempt = 0; attempt < 45; attempt++) {
|
|
2273
|
+
await new Promise(r => setTimeout(r, 3000));
|
|
2186
2274
|
process.stdout.write('.');
|
|
2187
2275
|
try {
|
|
2188
2276
|
const checkRes = await fetch(`${API_URL}/api/v1/instances/${newId}`, { headers: { Authorization: `Bearer ${token}` } });
|
|
@@ -2221,6 +2309,7 @@ if (process.argv[2] === 'setup') {
|
|
|
2221
2309
|
console.log(` (Run with --instance-id <id> to use a different one)\n`);
|
|
2222
2310
|
}
|
|
2223
2311
|
console.log(`✓ Instance: ${instance.name} (${instance.id.slice(0, 8)}…)\n`);
|
|
2312
|
+
sendFunnelEvent('setup_instance_ready', { instance_id: instance.id });
|
|
2224
2313
|
// ── Step 3: Detect editors ────────────────────────────────────────────────
|
|
2225
2314
|
const cwd = process.cwd();
|
|
2226
2315
|
const detected = [];
|
|
@@ -2469,7 +2558,7 @@ if (process.argv[2] === 'index') {
|
|
|
2469
2558
|
const instanceId = flag('instance-id') ?? process.env.CACHLY_BRAIN_INSTANCE_ID;
|
|
2470
2559
|
const maxFiles = parseInt(flag('max-files') ?? '500', 10);
|
|
2471
2560
|
const namespace = flag('namespace') ?? 'cachly:sem:code';
|
|
2472
|
-
if (!instanceId || !
|
|
2561
|
+
if (!instanceId || !JWT) {
|
|
2473
2562
|
console.error('\n❌ CACHLY_BRAIN_INSTANCE_ID and CACHLY_JWT must be set\n');
|
|
2474
2563
|
console.error(' export CACHLY_BRAIN_INSTANCE_ID=<uuid>');
|
|
2475
2564
|
console.error(' export CACHLY_JWT=<cky_live_...>');
|
|
@@ -2506,7 +2595,7 @@ if (process.argv[2] === 'learn-git') {
|
|
|
2506
2595
|
const repoDir = resolve(flag('dir') ?? argv.find(a => !a.startsWith('--')) ?? '.');
|
|
2507
2596
|
const instanceId = flag('instance-id') ?? process.env.CACHLY_BRAIN_INSTANCE_ID;
|
|
2508
2597
|
const maxCommits = parseInt(flag('max-commits') ?? '50', 10);
|
|
2509
|
-
if (!instanceId || !
|
|
2598
|
+
if (!instanceId || !JWT) {
|
|
2510
2599
|
console.error('\n❌ CACHLY_BRAIN_INSTANCE_ID and CACHLY_JWT must be set\n');
|
|
2511
2600
|
console.error(' export CACHLY_BRAIN_INSTANCE_ID=<uuid>');
|
|
2512
2601
|
console.error(' export CACHLY_JWT=<cky_live_...>');
|
|
@@ -2538,8 +2627,7 @@ if (process.argv[2] === 'learn-git') {
|
|
|
2538
2627
|
// actionable message in their editor's MCP log instead of silent failures.
|
|
2539
2628
|
// Skip for CLI commands that intentionally run without credentials.
|
|
2540
2629
|
const _cliNoAuthCommands = ['demo', 'share', 'health', 'setup', 'init', 'digest', 'invite', 'badge', 'join', 'upgrade'];
|
|
2541
|
-
|
|
2542
|
-
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)) {
|
|
2543
2631
|
// No args + no JWT in a non-TTY context = running as MCP server without credentials.
|
|
2544
2632
|
// Print actionable setup banner to stdout so it's captured by callers/tests.
|
|
2545
2633
|
const banner = '\n' +
|
|
@@ -2606,39 +2694,9 @@ const httpPort = process.env.PORT ? parseInt(process.env.PORT, 10) : undefined;
|
|
|
2606
2694
|
if (httpPort) {
|
|
2607
2695
|
// ── HTTP mode (Streamable HTTP transport) ───────────────────────────────
|
|
2608
2696
|
// Used for Smithery URL deployment: PORT=3000 node dist/index.js
|
|
2609
|
-
// Multi-tenant: per-request JWT + instance ID resolved via:
|
|
2610
|
-
// 1. Authorization: Bearer <jwt> + X-Cachly-Instance-Id: <uuid>
|
|
2611
|
-
// 2. Smithery config: ?config=base64url({CACHLY_JWT, CACHLY_BRAIN_INSTANCE_ID, ...})
|
|
2612
|
-
// Falls back to module-level env-derived values when neither is provided.
|
|
2613
2697
|
const { createServer } = await import('node:http');
|
|
2614
2698
|
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
|
|
2615
2699
|
await server.connect(transport);
|
|
2616
|
-
function extractReqCtx(req, url) {
|
|
2617
|
-
let jwtVal = '';
|
|
2618
|
-
let instId = '';
|
|
2619
|
-
const auth = req.headers['authorization'];
|
|
2620
|
-
if (typeof auth === 'string' && auth.toLowerCase().startsWith('bearer ')) {
|
|
2621
|
-
jwtVal = auth.slice(7).trim();
|
|
2622
|
-
}
|
|
2623
|
-
const hdrInst = req.headers['x-cachly-instance-id'];
|
|
2624
|
-
if (typeof hdrInst === 'string')
|
|
2625
|
-
instId = hdrInst.trim();
|
|
2626
|
-
// Smithery passes user config as a base64url-encoded JSON blob in ?config=
|
|
2627
|
-
const cfgParam = url.searchParams.get('config');
|
|
2628
|
-
if (cfgParam && (!jwtVal || !instId)) {
|
|
2629
|
-
try {
|
|
2630
|
-
const b64 = cfgParam.replace(/-/g, '+').replace(/_/g, '/');
|
|
2631
|
-
const padded = b64 + '='.repeat((4 - b64.length % 4) % 4);
|
|
2632
|
-
const json = JSON.parse(Buffer.from(padded, 'base64').toString('utf8'));
|
|
2633
|
-
if (!jwtVal && typeof json.CACHLY_JWT === 'string')
|
|
2634
|
-
jwtVal = json.CACHLY_JWT;
|
|
2635
|
-
if (!instId && typeof json.CACHLY_BRAIN_INSTANCE_ID === 'string')
|
|
2636
|
-
instId = json.CACHLY_BRAIN_INSTANCE_ID;
|
|
2637
|
-
}
|
|
2638
|
-
catch { /* malformed config — ignore, fall back to globals */ }
|
|
2639
|
-
}
|
|
2640
|
-
return { jwt: jwtVal, instanceId: instId };
|
|
2641
|
-
}
|
|
2642
2700
|
const httpServer = createServer((req, res) => {
|
|
2643
2701
|
const url = new URL(req.url ?? '/', `http://localhost:${httpPort}`);
|
|
2644
2702
|
if (url.pathname === '/health') {
|
|
@@ -2646,8 +2704,6 @@ if (httpPort) {
|
|
|
2646
2704
|
res.end(JSON.stringify({
|
|
2647
2705
|
status: 'ok',
|
|
2648
2706
|
version: CURRENT_VERSION,
|
|
2649
|
-
transport: 'streamable-http',
|
|
2650
|
-
multiTenant: true,
|
|
2651
2707
|
zeroResults: {
|
|
2652
2708
|
total: zeroResultsTotal,
|
|
2653
2709
|
last10: ZERO_RESULTS_LOG.slice(-10).map(e => ({ query: e.query, ts: new Date(e.ts).toISOString() })),
|
|
@@ -2656,16 +2712,7 @@ if (httpPort) {
|
|
|
2656
2712
|
return;
|
|
2657
2713
|
}
|
|
2658
2714
|
if (url.pathname === '/mcp' || url.pathname === '/') {
|
|
2659
|
-
|
|
2660
|
-
reqCtx.run(ctx, () => {
|
|
2661
|
-
transport.handleRequest(req, res).catch((err) => {
|
|
2662
|
-
process.stderr.write(`cachly-mcp HTTP handler error: ${err.message}\n`);
|
|
2663
|
-
if (!res.headersSent) {
|
|
2664
|
-
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
2665
|
-
res.end(JSON.stringify({ error: 'internal_error' }));
|
|
2666
|
-
}
|
|
2667
|
-
});
|
|
2668
|
-
});
|
|
2715
|
+
transport.handleRequest(req, res);
|
|
2669
2716
|
return;
|
|
2670
2717
|
}
|
|
2671
2718
|
res.writeHead(404);
|
|
@@ -2680,4 +2727,5 @@ else {
|
|
|
2680
2727
|
const transport = new StdioServerTransport();
|
|
2681
2728
|
await server.connect(transport);
|
|
2682
2729
|
}
|
|
2730
|
+
notify('cachly', 'startup', { version: CURRENT_VERSION, mode: process.env.MCP_HTTP_PORT ? 'http' : 'stdio' }).catch(() => undefined);
|
|
2683
2731
|
//# sourceMappingURL=index.js.map
|