@deadragdoll/tellymcp 0.0.6 → 0.0.8
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/.env.example.client +2 -0
- package/.env.example.gateway +2 -1
- package/README-ru.md +106 -0
- package/README.md +91 -48
- package/VERSION.md +29 -0
- package/dist/cli.js +1 -1
- package/dist/lib/mixins/session.errors.js +15 -11
- package/dist/lib/pinoTargets.js +29 -0
- package/dist/moleculer.config.js +20 -66
- package/dist/services/features/telegram-mcp/src/app/config/env.js +12 -1
- package/dist/services/features/telegram-mcp/src/app/http.js +1 -1
- package/dist/services/features/telegram-mcp/src/app/providers/mcp/server.js +2 -2
- package/dist/services/features/telegram-mcp/src/features/distributed-gateway/model/gatewayHttpService.js +1 -1
- package/dist/services/features/telegram-mcp/src/shared/integrations/telegram/transport.js +103 -3
- package/dist/services/features/telegram-mcp/src/shared/integrations/tmux/client.js +75 -0
- package/dist/services/features/telegram-mcp/src/shared/lib/logger/logger.js +7 -26
- package/package.json +2 -4
- package/dist/lib/middlewares/tracer.js +0 -172
- package/dist/lib/trace.js +0 -147
- package/dist/lib/traceContext.js +0 -116
package/.env.example.client
CHANGED
package/.env.example.gateway
CHANGED
package/README-ru.md
CHANGED
|
@@ -73,6 +73,12 @@ Telegram HITL здесь тоже есть, но он не исчерпывае
|
|
|
73
73
|
- partner notes и partner files
|
|
74
74
|
- tools sync и version checks
|
|
75
75
|
|
|
76
|
+
Важно для `Share`:
|
|
77
|
+
|
|
78
|
+
- текущая сессия должна выполнить работу сама
|
|
79
|
+
- target-сессии отправляется только результат
|
|
80
|
+
- исходное поручение не должно пересылаться дальше как новая задача
|
|
81
|
+
|
|
76
82
|
Полный список MCP tools лучше держать ниже по README и в самом MCP server, а не на первом экране.
|
|
77
83
|
|
|
78
84
|
## Prerequisites
|
|
@@ -121,6 +127,10 @@ tmux attach -t backend
|
|
|
121
127
|
|
|
122
128
|
Если агентов несколько, лучше запускать каждого в своей tmux-сессии или pane и привязывать отдельно.
|
|
123
129
|
|
|
130
|
+
Если tmux pane пересоздан и его `pane_id` изменился, TellyMCP теперь пытается автоматически восстановить актуальный target по сохранённым tmux session/window/pane hints.
|
|
131
|
+
|
|
132
|
+
Если авто-восстановление не удалось, в Telegram придёт operational warning, а не только запись в backend log.
|
|
133
|
+
|
|
124
134
|
## Быстрый старт
|
|
125
135
|
|
|
126
136
|
### 1. Standalone client без шлюза
|
|
@@ -229,6 +239,99 @@ tellymcp run --env .env
|
|
|
229
239
|
|
|
230
240
|
- `https://your-host.example/api/mcp`
|
|
231
241
|
|
|
242
|
+
## Docker: только для инфраструктуры или для `gateway`-only
|
|
243
|
+
|
|
244
|
+
Docker больше не является основным способом запуска TellyMCP, но один container path поддерживается:
|
|
245
|
+
|
|
246
|
+
- запуск только `gateway`-ноды в контейнере
|
|
247
|
+
|
|
248
|
+
Это вариант именно для чистого control-plane узла:
|
|
249
|
+
|
|
250
|
+
- без локальных agent sessions
|
|
251
|
+
- без локального `tmux`
|
|
252
|
+
- не для `client`
|
|
253
|
+
- не для `both`
|
|
254
|
+
|
|
255
|
+
Плюс `docker-compose.yml` по-прежнему может поднимать локальную инфраструктуру:
|
|
256
|
+
|
|
257
|
+
- `redis` для всех режимов
|
|
258
|
+
- `postgres` для `gateway` / `both`
|
|
259
|
+
- `rabbitmq` только если нужен durable fanout на шлюзе
|
|
260
|
+
|
|
261
|
+
Только Redis, для `standalone` или `client`:
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
docker compose up -d redis
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
Redis + Postgres, для `gateway` или `both`:
|
|
268
|
+
|
|
269
|
+
```bash
|
|
270
|
+
docker compose --profile gateway up -d
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Добавить RabbitMQ при необходимости:
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
docker compose --profile gateway --profile rmq up -d
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Полный gateway stack в Docker:
|
|
280
|
+
|
|
281
|
+
1. Скопировать пример:
|
|
282
|
+
|
|
283
|
+
```bash
|
|
284
|
+
cp .env.example.gateway .env-gateway
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
2. Отредактировать `.env-gateway` и задать минимум:
|
|
288
|
+
|
|
289
|
+
- `TELEGRAM_BOT_TOKEN`
|
|
290
|
+
- `TELEGRAM_BOT_USERNAME`
|
|
291
|
+
- `WEBAPP_PUBLIC_URL`
|
|
292
|
+
- `GATEWAY_PUBLIC_URL`
|
|
293
|
+
- `GATEWAY_WS_URL`
|
|
294
|
+
- `MCP_HTTP_BEARER_TOKEN`
|
|
295
|
+
|
|
296
|
+
3. Запустить:
|
|
297
|
+
|
|
298
|
+
```bash
|
|
299
|
+
docker compose up -d
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
Это поднимет:
|
|
303
|
+
|
|
304
|
+
- `redis`
|
|
305
|
+
- `postgres`
|
|
306
|
+
- `tellymcp-gateway`
|
|
307
|
+
|
|
308
|
+
Внутри Docker compose сам переопределяет:
|
|
309
|
+
|
|
310
|
+
- `MCP_HTTP_HOST=0.0.0.0`
|
|
311
|
+
- `REDIS_HOST=redis`
|
|
312
|
+
- `DB_HOST=postgres`
|
|
313
|
+
|
|
314
|
+
Ожидаемые endpoint'ы:
|
|
315
|
+
|
|
316
|
+
- `http://127.0.0.1:8080/api/healthz`
|
|
317
|
+
- `http://127.0.0.1:8080/api/mcp`
|
|
318
|
+
- `http://127.0.0.1:8080/api/webapp`
|
|
319
|
+
- `http://127.0.0.1:8080/api/gateway`
|
|
320
|
+
|
|
321
|
+
Остановить всё:
|
|
322
|
+
|
|
323
|
+
```bash
|
|
324
|
+
docker compose down
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
Сам TellyMCP при этом запускается напрямую на хосте:
|
|
328
|
+
|
|
329
|
+
```bash
|
|
330
|
+
tellymcp run --env .env
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
Для `client` и `both` по-прежнему рекомендуется именно хостовый запуск.
|
|
334
|
+
|
|
232
335
|
## Как начать работу с ботом изнутри агента
|
|
233
336
|
|
|
234
337
|
После подключения MCP можно просто написать агенту обычной фразой, что нужно привязаться к Telegram.
|
|
@@ -332,6 +435,9 @@ tellymcp mcp --help
|
|
|
332
435
|
- `PROXY_USE=http|socks5`
|
|
333
436
|
- `HTTP_PROXY`
|
|
334
437
|
- `SOCKS5_PROXY`
|
|
438
|
+
- `LOG_LEVEL`
|
|
439
|
+
- `LOG_FILE_ENABLED`
|
|
440
|
+
- `LOG_FILE_PATH`
|
|
335
441
|
|
|
336
442
|
Только client:
|
|
337
443
|
|
package/README.md
CHANGED
|
@@ -122,6 +122,10 @@ Use short, meaningful names such as:
|
|
|
122
122
|
|
|
123
123
|
If you run multiple agents, put each one in its own tmux session or pane and pair them separately.
|
|
124
124
|
|
|
125
|
+
If a tmux pane is recreated and its pane id changes, TellyMCP now tries to recover the live pane target automatically from saved tmux session, window, and pane hints.
|
|
126
|
+
|
|
127
|
+
If auto-recovery fails, Telegram sends an operational warning so the problem is visible to the human user, not only in backend logs.
|
|
128
|
+
|
|
125
129
|
## Quick start
|
|
126
130
|
|
|
127
131
|
### Standalone client node
|
|
@@ -412,10 +416,11 @@ Canonical instructions:
|
|
|
412
416
|
- the `TOOLS.md` version marker
|
|
413
417
|
- the file content itself
|
|
414
418
|
|
|
415
|
-
Logs
|
|
419
|
+
Logs use one runtime model:
|
|
416
420
|
|
|
417
|
-
- pretty console output to `stderr`
|
|
418
|
-
- JSONL file
|
|
421
|
+
- `pino-pretty` console output to `stderr`
|
|
422
|
+
- optional JSONL file sink via `LOG_FILE_ENABLED=true` and `LOG_FILE_PATH=...`
|
|
423
|
+
- optional in-app `LogFeed` buffer for Telegram/UI diagnostics when `ENABLE_LOGFEED=1`
|
|
419
424
|
|
|
420
425
|
If Telegram access requires a proxy, the bot transport can use:
|
|
421
426
|
|
|
@@ -489,6 +494,8 @@ Current file model:
|
|
|
489
494
|
- `vfs/minio` are no longer part of the active Telegram file exchange path
|
|
490
495
|
- if an agent must send a real local file to a partner, prefer `send_partner_file`
|
|
491
496
|
over plain `send_partner_note`
|
|
497
|
+
- for `Share`, the current session must do the work itself and send only the result
|
|
498
|
+
- `Share` must not forward the original task into the target session as a new assignment
|
|
492
499
|
|
|
493
500
|
Current presence model:
|
|
494
501
|
|
|
@@ -552,10 +559,10 @@ Recommended local dev settings:
|
|
|
552
559
|
- install browser binaries once with `npx playwright install chromium`
|
|
553
560
|
- install browser binaries once with `tellymcp browser install`
|
|
554
561
|
|
|
555
|
-
Recommended
|
|
562
|
+
Recommended headless server settings:
|
|
556
563
|
|
|
557
564
|
- `BROWSER_HEADLESS=true`
|
|
558
|
-
- target the host
|
|
565
|
+
- target the app through a reachable host or LAN address, for example `http://127.0.0.1:3000` or `http://192.168.x.x:3000`
|
|
559
566
|
|
|
560
567
|
Current browser tools:
|
|
561
568
|
|
|
@@ -829,75 +836,111 @@ If `MCP_HTTP_BEARER_TOKEN` is configured:
|
|
|
829
836
|
`yarn dev:gw:telegram` is still available, but it only starts the `telegram_mcp` feature node.
|
|
830
837
|
It does not expose HTTP by itself anymore. `/mcp`, `/webapp`, and `/healthz` are now served only through the Moleculer API gateway aliases in the full `dev:gw` / `start:gw` runtime, or through a separate gateway node in the same namespace.
|
|
831
838
|
|
|
832
|
-
## Optional Docker
|
|
839
|
+
## Optional Docker infrastructure
|
|
840
|
+
|
|
841
|
+
Docker is no longer the default way to run TellyMCP, but there is one supported container path:
|
|
833
842
|
|
|
834
|
-
|
|
843
|
+
- `gateway`-only container deployment
|
|
835
844
|
|
|
836
|
-
This
|
|
845
|
+
This is intended for a pure control-plane node:
|
|
837
846
|
|
|
838
|
-
|
|
847
|
+
- no local agent sessions
|
|
848
|
+
- no local `tmux`
|
|
849
|
+
- no `client` mode
|
|
850
|
+
- no `both` mode
|
|
839
851
|
|
|
840
|
-
|
|
841
|
-
- `redis-server` runs on `127.0.0.1:6379`
|
|
842
|
-
- the application itself serves:
|
|
843
|
-
- `/mcp`
|
|
844
|
-
- `/webapp`
|
|
845
|
-
- `/healthz`
|
|
846
|
-
- `/sessions`
|
|
847
|
-
- `/prune`
|
|
852
|
+
The repository also keeps Docker for local infrastructure:
|
|
848
853
|
|
|
849
|
-
|
|
854
|
+
- `redis` for all modes
|
|
855
|
+
- `postgres` for `gateway` / `both`
|
|
856
|
+
- `rabbitmq` only if you want durable fanout on the gateway
|
|
850
857
|
|
|
851
|
-
|
|
858
|
+
Start Redis only, for `standalone` or `client` mode:
|
|
852
859
|
|
|
853
860
|
```bash
|
|
854
|
-
docker compose
|
|
861
|
+
docker compose up -d redis
|
|
855
862
|
```
|
|
856
863
|
|
|
857
|
-
|
|
864
|
+
Start Redis + Postgres, for `gateway` or `both` mode:
|
|
858
865
|
|
|
859
866
|
```bash
|
|
860
|
-
docker compose up -d
|
|
867
|
+
docker compose --profile gateway up -d
|
|
861
868
|
```
|
|
862
869
|
|
|
863
|
-
|
|
870
|
+
Add RabbitMQ only when you need it:
|
|
864
871
|
|
|
865
872
|
```bash
|
|
866
|
-
docker compose
|
|
873
|
+
docker compose --profile gateway --profile rmq up -d
|
|
867
874
|
```
|
|
868
875
|
|
|
869
|
-
|
|
876
|
+
Run a full gateway container stack with Redis and Postgres:
|
|
870
877
|
|
|
871
|
-
|
|
872
|
-
- injects `.env`
|
|
873
|
-
- overrides runtime networking so the app talks to local in-container Redis and listens on `0.0.0.0:8787`
|
|
874
|
-
- publishes only `8787:8787`
|
|
875
|
-
- keeps `host.docker.internal` available for optional host-side development integrations
|
|
876
|
-
- persists Redis state in `./data/redis`
|
|
878
|
+
1. Copy the example:
|
|
877
879
|
|
|
878
|
-
|
|
880
|
+
```bash
|
|
881
|
+
cp .env.example.gateway .env-gateway
|
|
882
|
+
```
|
|
879
883
|
|
|
880
|
-
-
|
|
881
|
-
|
|
882
|
-
-
|
|
884
|
+
2. Edit `.env-gateway` and set at minimum:
|
|
885
|
+
|
|
886
|
+
- `TELEGRAM_BOT_TOKEN`
|
|
887
|
+
- `TELEGRAM_BOT_USERNAME`
|
|
888
|
+
- `WEBAPP_PUBLIC_URL`
|
|
889
|
+
- `GATEWAY_PUBLIC_URL`
|
|
890
|
+
- `GATEWAY_WS_URL`
|
|
891
|
+
- `MCP_HTTP_BEARER_TOKEN`
|
|
883
892
|
|
|
884
|
-
|
|
893
|
+
3. Start the stack:
|
|
885
894
|
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
- no direct external access is needed to in-container Redis
|
|
890
|
-
- `tmux` access is expected to be direct from the running `tellymcp` process
|
|
895
|
+
```bash
|
|
896
|
+
docker compose up -d
|
|
897
|
+
```
|
|
891
898
|
|
|
892
|
-
|
|
899
|
+
This starts:
|
|
900
|
+
|
|
901
|
+
- `redis`
|
|
902
|
+
- `postgres`
|
|
903
|
+
- `tellymcp-gateway`
|
|
904
|
+
|
|
905
|
+
Inside Docker, compose overrides:
|
|
906
|
+
|
|
907
|
+
- `MCP_HTTP_HOST=0.0.0.0`
|
|
908
|
+
- `REDIS_HOST=redis`
|
|
909
|
+
- `DB_HOST=postgres`
|
|
910
|
+
|
|
911
|
+
Public endpoint expectations stay the same:
|
|
912
|
+
|
|
913
|
+
- `http://127.0.0.1:8080/api/healthz`
|
|
914
|
+
- `http://127.0.0.1:8080/api/mcp`
|
|
915
|
+
- `http://127.0.0.1:8080/api/webapp`
|
|
916
|
+
- `http://127.0.0.1:8080/api/gateway`
|
|
917
|
+
|
|
918
|
+
Stop everything:
|
|
919
|
+
|
|
920
|
+
```bash
|
|
921
|
+
docker compose down
|
|
922
|
+
```
|
|
923
|
+
|
|
924
|
+
Default published ports:
|
|
925
|
+
|
|
926
|
+
- Redis: `6379`
|
|
927
|
+
- Postgres: `5432`
|
|
928
|
+
- RabbitMQ AMQP: `5672`
|
|
929
|
+
- RabbitMQ UI: `15672`
|
|
930
|
+
|
|
931
|
+
The TellyMCP process itself should run directly on the host:
|
|
932
|
+
|
|
933
|
+
```bash
|
|
934
|
+
tellymcp run --env .env
|
|
935
|
+
```
|
|
936
|
+
|
|
937
|
+
This keeps:
|
|
893
938
|
|
|
894
|
-
-
|
|
895
|
-
-
|
|
896
|
-
-
|
|
897
|
-
- menu payload buffers
|
|
898
|
-
- WebApp launch/session state
|
|
939
|
+
- direct `tmux` access
|
|
940
|
+
- simpler debugging
|
|
941
|
+
- the same runtime model for `standalone`, `client`, `gateway`, and `both`
|
|
899
942
|
|
|
900
|
-
|
|
943
|
+
For `client` and `both`, host execution is still the recommended model.
|
|
901
944
|
|
|
902
945
|
Optional if the local tmux server uses a non-default socket:
|
|
903
946
|
|
package/VERSION.md
CHANGED
|
@@ -4,6 +4,35 @@ Public, user-facing release notes for published versions of `@deadragdoll/tellym
|
|
|
4
4
|
|
|
5
5
|
For detailed engineering history, refactors, and internal development notes, see [CHANGELOG.md](CHANGELOG.md).
|
|
6
6
|
|
|
7
|
+
## 0.0.8
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- Unified logging model based on `pino`:
|
|
11
|
+
- pretty console output by default
|
|
12
|
+
- optional JSONL file sink for Alloy or other collectors
|
|
13
|
+
- `LOG_FILE_ENABLED=true`
|
|
14
|
+
- `LOG_FILE_PATH=.tellymcp/log.jsonl`
|
|
15
|
+
- Better tmux recovery behavior:
|
|
16
|
+
- when a saved pane target becomes stale after tmux recreation, TellyMCP now tries to recover the live pane automatically from stored tmux session/window/pane hints
|
|
17
|
+
- if auto-recovery fails, Telegram sends a clear operational warning instead of leaving the problem only in logs
|
|
18
|
+
- Stronger `Share` execution guidance:
|
|
19
|
+
- the current session must do the work itself
|
|
20
|
+
- it must send only the result
|
|
21
|
+
- it must not forward the original task to the target session as a new assignment
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
- Runtime identity and service labels now use `tellymcp` naming consistently instead of older `telegram-human-mcp` tags.
|
|
25
|
+
- MCP server metadata now reports the current package version and `tellymcp` service name.
|
|
26
|
+
- Logging config is now simpler:
|
|
27
|
+
- one console logging model
|
|
28
|
+
- one optional JSON file sink
|
|
29
|
+
- optional `LogFeed` buffer for UI diagnostics
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
- Stale tmux pane ids like `%1 -> %2` no longer require manual user understanding before the service can try to wake the session again.
|
|
33
|
+
- Broken tmux nudge targets are now visible to the user in Telegram, not only in backend logs.
|
|
34
|
+
- `Share` inbox instructions are now explicit enough to reduce the chance that one agent re-delegates the task back into the collaboration graph.
|
|
35
|
+
|
|
7
36
|
## 0.0.3
|
|
8
37
|
|
|
9
38
|
### Added
|
package/dist/cli.js
CHANGED
|
@@ -82,7 +82,7 @@ async function getPlaywrightBrowserStatus(browserEnabled) {
|
|
|
82
82
|
}
|
|
83
83
|
function printHelp() {
|
|
84
84
|
const tmux = getTmuxStatus();
|
|
85
|
-
printBanner("CLI", "Telegram
|
|
85
|
+
printBanner("CLI", "Telegram control plane for MCP-connected coding agents");
|
|
86
86
|
printSection("Usage", [
|
|
87
87
|
" tellymcp init <client|gateway|both> [directory]",
|
|
88
88
|
" tellymcp run [--env <file>]",
|
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.PlaygroundDisabledError = exports.SessionIdleTimeoutExceededError = exports.TokenInvalidError = exports.TokenNotFoundError = exports.SessionTokenSignatureInvalidError = exports.SessionTokenInvalidFormatError = exports.SessionTokenMismatchError = exports.SessionTokenNotFoundError = exports.SessionTokenRevokedError = exports.SessionTokenNotActiveError = exports.SessionTokenExpiredError = exports.SessionTokenInvalidError = exports.SessionValidationError = exports.SessionRefreshError = exports.SessionMaxLifetimeExceededError = exports.SessionStealedError = exports.SessionInvalidError = exports.SessionExpiredError = exports.SessionNotFoundError = exports.ForbiddenError = exports.UnauthorizedError = exports.BackendError = void 0;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
3
|
+
exports.PlaygroundDisabledError = exports.SessionIdleTimeoutExceededError = exports.TokenInvalidError = exports.TokenNotFoundError = exports.SessionTokenSignatureInvalidError = exports.SessionTokenInvalidFormatError = exports.SessionTokenMismatchError = exports.SessionTokenNotFoundError = exports.SessionTokenRevokedError = exports.SessionTokenNotActiveError = exports.SessionTokenExpiredError = exports.SessionTokenInvalidError = exports.SessionValidationError = exports.SessionRefreshError = exports.SessionMaxLifetimeExceededError = exports.SessionStealedError = exports.SessionInvalidError = exports.SessionExpiredError = exports.SessionNotFoundError = exports.ForbiddenError = exports.UnauthorizedError = exports.wrapUnhandledBackendError = exports.buildUnhandledBackendErrorCode = exports.BackendError = void 0;
|
|
4
|
+
class BackendError extends Error {
|
|
5
|
+
statusCode;
|
|
6
|
+
code;
|
|
7
|
+
data;
|
|
8
|
+
constructor(message, statusCode = 500, code, data) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = "BackendError";
|
|
11
|
+
this.statusCode = statusCode;
|
|
12
|
+
this.code = code || String(statusCode);
|
|
13
|
+
this.data = data;
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
exports.BackendError = BackendError;
|
|
17
|
+
const buildUnhandledBackendErrorCode = (rawName) => `XC_${rawName.toUpperCase()}`;
|
|
18
|
+
exports.buildUnhandledBackendErrorCode = buildUnhandledBackendErrorCode;
|
|
19
|
+
const wrapUnhandledBackendError = (err, rawName) => new BackendError(err.message, 502, (0, exports.buildUnhandledBackendErrorCode)(rawName));
|
|
20
|
+
exports.wrapUnhandledBackendError = wrapUnhandledBackendError;
|
|
17
21
|
class UnauthorizedError extends BackendError {
|
|
18
22
|
constructor(message = "Unauthorized", data) {
|
|
19
23
|
super(message, 401, "UNAUTHORIZED", data);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createPinoTargets = createPinoTargets;
|
|
4
|
+
function createPinoTargets(config) {
|
|
5
|
+
const targets = [
|
|
6
|
+
{
|
|
7
|
+
target: "pino-pretty",
|
|
8
|
+
level: config.level,
|
|
9
|
+
options: {
|
|
10
|
+
destination: 2,
|
|
11
|
+
colorize: true,
|
|
12
|
+
translateTime: "SYS:yyyy-mm-dd HH:MM:ss.l",
|
|
13
|
+
ignore: "pid,hostname",
|
|
14
|
+
singleLine: false,
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
];
|
|
18
|
+
if (config.fileEnabled) {
|
|
19
|
+
targets.unshift({
|
|
20
|
+
target: "pino/file",
|
|
21
|
+
level: config.level,
|
|
22
|
+
options: {
|
|
23
|
+
destination: config.filePath,
|
|
24
|
+
mkdir: true,
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
return targets;
|
|
29
|
+
}
|
package/dist/moleculer.config.js
CHANGED
|
@@ -8,11 +8,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
8
8
|
const moleculer_1 = require("moleculer");
|
|
9
9
|
const dotenv_1 = __importDefault(require("dotenv"));
|
|
10
10
|
require("module-alias/register");
|
|
11
|
-
const
|
|
12
|
-
const node_path_1 = __importDefault(require("node:path"));
|
|
11
|
+
const pino_1 = __importDefault(require("pino"));
|
|
13
12
|
const logfeed_1 = require("./lib/mixins/logfeed");
|
|
14
13
|
const session_errors_1 = require("./lib/mixins/session.errors");
|
|
15
|
-
const
|
|
14
|
+
const pinoTargets_1 = require("./lib/pinoTargets");
|
|
16
15
|
dotenv_1.default.config({ path: process.env.ENV_FILE ?? ".env" });
|
|
17
16
|
/**
|
|
18
17
|
* Moleculer ServiceBroker configuration file 1
|
|
@@ -40,72 +39,34 @@ dotenv_1.default.config({ path: process.env.ENV_FILE ?? ".env" });
|
|
|
40
39
|
* }
|
|
41
40
|
*/
|
|
42
41
|
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
|
42
|
+
const pinoTransport = pino_1.default.transport({
|
|
43
|
+
targets: (0, pinoTargets_1.createPinoTargets)({
|
|
44
|
+
level: process.env.LOG_LEVEL || "info",
|
|
45
|
+
fileEnabled: process.env.LOG_FILE_ENABLED === "true",
|
|
46
|
+
filePath: process.env.LOG_FILE_PATH || ".tellymcp/log.jsonl",
|
|
47
|
+
}),
|
|
48
|
+
});
|
|
43
49
|
const logger = [
|
|
44
50
|
{
|
|
45
|
-
type: "
|
|
51
|
+
type: "Pino",
|
|
46
52
|
options: {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
53
|
+
pino: {
|
|
54
|
+
options: {
|
|
55
|
+
name: "tellymcp-broker",
|
|
56
|
+
level: process.env.LOG_LEVEL || "info",
|
|
57
|
+
timestamp: pino_1.default.stdTimeFunctions.isoTime,
|
|
58
|
+
},
|
|
59
|
+
destination: pinoTransport,
|
|
60
|
+
},
|
|
52
61
|
},
|
|
53
62
|
},
|
|
54
63
|
];
|
|
55
64
|
const metricsEnabled = process.env.MOLECULER_METRICS === "true";
|
|
56
65
|
const metricsPort = +(process.env.METRICS_PORT || 3030);
|
|
57
66
|
const metricsPath = process.env.METRICS_PATH || "/metrics";
|
|
58
|
-
const logFileEnabled = process.env.LOG_FILE_ENABLED === "true";
|
|
59
67
|
const logFeedEnabled = process.env.ENABLE_LOGFEED != null
|
|
60
68
|
? !["0", "false", "no", "off"].includes(process.env.ENABLE_LOGFEED.toLowerCase())
|
|
61
69
|
: process.env.LOGFEED_ENABLED !== "false";
|
|
62
|
-
const logFileFolder = process.env.LOG_FILE_FOLDER || "./logs";
|
|
63
|
-
const logFileName = process.env.LOG_FILE_NAME || "moleculer-{date}.log";
|
|
64
|
-
const logFileRetentionDays = +(process.env.LOG_FILE_RETENTION_DAYS || 14);
|
|
65
|
-
const cleanupLogFiles = async () => {
|
|
66
|
-
if (!logFileEnabled || !Number.isFinite(logFileRetentionDays) || logFileRetentionDays <= 0) {
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
const logFolder = node_path_1.default.resolve(logFileFolder);
|
|
70
|
-
const now = Date.now();
|
|
71
|
-
const maxAgeMs = logFileRetentionDays * 24 * 60 * 60 * 1000;
|
|
72
|
-
const filenamePattern = new RegExp(`^${logFileName
|
|
73
|
-
.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
|
|
74
|
-
.replace(/\\\{date\\\}/g, "\\d{4}-\\d{2}-\\d{2}")
|
|
75
|
-
.replace(/\\\{nodeID\\\}/g, "[^/]+")
|
|
76
|
-
.replace(/\\\{namespace\\\}/g, "[^/]+")}$`);
|
|
77
|
-
try {
|
|
78
|
-
const entries = await promises_1.default.readdir(logFolder, { withFileTypes: true });
|
|
79
|
-
await Promise.all(entries
|
|
80
|
-
.filter(entry => entry.isFile() && filenamePattern.test(entry.name))
|
|
81
|
-
.map(async (entry) => {
|
|
82
|
-
const filePath = node_path_1.default.join(logFolder, entry.name);
|
|
83
|
-
const stats = await promises_1.default.stat(filePath);
|
|
84
|
-
if (now - stats.mtimeMs > maxAgeMs) {
|
|
85
|
-
await promises_1.default.unlink(filePath);
|
|
86
|
-
}
|
|
87
|
-
}));
|
|
88
|
-
}
|
|
89
|
-
catch (error) {
|
|
90
|
-
if (error.code !== "ENOENT") {
|
|
91
|
-
console.warn("LOG_FILE cleanup failed:", error);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
};
|
|
95
|
-
if (logFileEnabled) {
|
|
96
|
-
void cleanupLogFiles();
|
|
97
|
-
logger.push({
|
|
98
|
-
type: "File",
|
|
99
|
-
options: {
|
|
100
|
-
level: process.env.LOG_FILE_LEVEL || process.env.LOG_LEVEL || "info",
|
|
101
|
-
folder: logFileFolder,
|
|
102
|
-
filename: logFileName,
|
|
103
|
-
formatter: process.env.LOG_FILE_FORMATTER || "json",
|
|
104
|
-
eol: "\n",
|
|
105
|
-
interval: +(process.env.LOG_FILE_INTERVAL_MS || 1000),
|
|
106
|
-
},
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
70
|
if (logFeedEnabled) {
|
|
110
71
|
logger.push(new logfeed_1.LogFeedLogger({
|
|
111
72
|
level: process.env.LOGFEED_LEVEL || process.env.LOG_LEVEL || "info",
|
|
@@ -206,18 +167,15 @@ const brokerConfig = {
|
|
|
206
167
|
// Enable action & event parameter validation. More info: https://moleculer.services/docs/0.14/validating.html
|
|
207
168
|
validator: true,
|
|
208
169
|
errorHandler: (err, { ctx, service }) => {
|
|
209
|
-
// ctx.service.logger.error("errorHandler", err);
|
|
210
170
|
if (err instanceof session_errors_1.BackendError) {
|
|
211
|
-
// ctx.meta.$statusCode = err.extensions.code;
|
|
212
171
|
return err;
|
|
213
172
|
}
|
|
214
173
|
else if (err instanceof Error) {
|
|
215
|
-
// ctx.meta.$statusCode = 502;
|
|
216
174
|
const rawName = ctx?.action?.rawName ||
|
|
217
175
|
ctx?.action?.name ||
|
|
218
176
|
service?.name ||
|
|
219
177
|
"UNKNOWN";
|
|
220
|
-
return
|
|
178
|
+
return (0, session_errors_1.wrapUnhandledBackendError)(err, String(rawName));
|
|
221
179
|
}
|
|
222
180
|
return err;
|
|
223
181
|
},
|
|
@@ -259,15 +217,11 @@ const brokerConfig = {
|
|
|
259
217
|
},
|
|
260
218
|
},
|
|
261
219
|
// Register custom middlewares
|
|
262
|
-
middlewares: [
|
|
220
|
+
middlewares: [],
|
|
263
221
|
// Register custom REPL commands.
|
|
264
222
|
replCommands: null,
|
|
265
223
|
// Called after broker created.
|
|
266
224
|
// created(broker: ServiceBroker): void {},
|
|
267
|
-
// Called after broker started.
|
|
268
|
-
started() {
|
|
269
|
-
console.log("ALLOWED ORIGINS", (process.env.ORIGINS ?? "").split(","));
|
|
270
|
-
},
|
|
271
225
|
// Called after broker stopped.
|
|
272
226
|
// async stopped(broker: ServiceBroker): Promise<void> {},
|
|
273
227
|
};
|
|
@@ -165,9 +165,18 @@ const envSchema = z.object({
|
|
|
165
165
|
LOG_LEVEL: z
|
|
166
166
|
.enum(["fatal", "error", "warn", "info", "debug", "trace", "silent"])
|
|
167
167
|
.default("info"),
|
|
168
|
+
LOG_FILE_ENABLED: z
|
|
169
|
+
.string()
|
|
170
|
+
.optional()
|
|
171
|
+
.transform((value) => value === "true"),
|
|
172
|
+
LOG_FILE_PATH: z.string().min(1).default(".tellymcp/log.jsonl"),
|
|
168
173
|
});
|
|
169
174
|
function loadConfig() {
|
|
170
|
-
|
|
175
|
+
const explicitEnvFile = process.env.ENV_FILE?.trim();
|
|
176
|
+
if (explicitEnvFile) {
|
|
177
|
+
process.loadEnvFile(explicitEnvFile);
|
|
178
|
+
}
|
|
179
|
+
else if ((0, node_fs_1.existsSync)(".env")) {
|
|
171
180
|
process.loadEnvFile(".env");
|
|
172
181
|
}
|
|
173
182
|
const parsed = envSchema.parse(process.env);
|
|
@@ -312,6 +321,8 @@ function loadConfig() {
|
|
|
312
321
|
},
|
|
313
322
|
logging: {
|
|
314
323
|
level: parsed.LOG_LEVEL,
|
|
324
|
+
fileEnabled: parsed.LOG_FILE_ENABLED,
|
|
325
|
+
filePath: parsed.LOG_FILE_PATH,
|
|
315
326
|
},
|
|
316
327
|
};
|
|
317
328
|
}
|
|
@@ -5,8 +5,8 @@ const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
|
5
5
|
const registry_1 = require("../../../shared/api/tool-registry/registry");
|
|
6
6
|
function createMcpServer(tools) {
|
|
7
7
|
const server = new mcp_js_1.McpServer({
|
|
8
|
-
name: "
|
|
9
|
-
version: "
|
|
8
|
+
name: "tellymcp",
|
|
9
|
+
version: "0.0.6",
|
|
10
10
|
});
|
|
11
11
|
(0, registry_1.registerTools)(server, tools);
|
|
12
12
|
return server;
|
|
@@ -241,7 +241,7 @@ class GatewayHttpService {
|
|
|
241
241
|
if (pathname === "/gateway/healthz") {
|
|
242
242
|
writeJson(res, 200, {
|
|
243
243
|
ok: true,
|
|
244
|
-
service: "
|
|
244
|
+
service: "tellymcp-gateway",
|
|
245
245
|
mode: this.config.distributed.mode,
|
|
246
246
|
databaseConfigured: Boolean(process.env.DB_HOST && process.env.DB_NAME),
|
|
247
247
|
s3Configured: Boolean(this.config.distributed.gatewayS3Bucket),
|