atabey 0.0.13 → 0.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (282) hide show
  1. package/ATABEY.md +1 -1
  2. package/PRIVACY.md +8 -14
  3. package/README.md +405 -56
  4. package/bin/cli.js +72 -5
  5. package/dist/framework-mcp/src/index.js +988 -63
  6. package/dist/framework-mcp/src/index.js.map +1 -1
  7. package/dist/framework-mcp/src/tools/compliance/mask_pii.d.ts +17 -0
  8. package/dist/framework-mcp/src/tools/compliance/mask_pii.js +56 -0
  9. package/dist/framework-mcp/src/tools/compliance/mask_pii.js.map +1 -0
  10. package/dist/framework-mcp/src/tools/definitions.js +10 -9
  11. package/dist/framework-mcp/src/tools/definitions.js.map +1 -1
  12. package/dist/framework-mcp/src/tools/framework/run_tests.js +25 -0
  13. package/dist/framework-mcp/src/tools/framework/run_tests.js.map +1 -1
  14. package/dist/framework-mcp/src/tools/index.js +7 -8
  15. package/dist/framework-mcp/src/tools/index.js.map +1 -1
  16. package/dist/framework-mcp/src/tools/messaging/approve_operation.d.ts +22 -0
  17. package/dist/framework-mcp/src/tools/messaging/approve_operation.js +81 -0
  18. package/dist/framework-mcp/src/tools/messaging/approve_operation.js.map +1 -0
  19. package/dist/framework-mcp/src/tools/messaging/ask_human.d.ts +15 -0
  20. package/dist/framework-mcp/src/tools/messaging/ask_human.js +89 -20
  21. package/dist/framework-mcp/src/tools/messaging/ask_human.js.map +1 -1
  22. package/dist/framework-mcp/src/tools/observability/check_ports.d.ts +1 -0
  23. package/dist/framework-mcp/src/tools/observability/check_ports.js +24 -8
  24. package/dist/framework-mcp/src/tools/observability/check_ports.js.map +1 -1
  25. package/dist/framework-mcp/src/tools/quality/analyze_code_quality.js +13 -64
  26. package/dist/framework-mcp/src/tools/quality/analyze_code_quality.js.map +1 -1
  27. package/dist/framework-mcp/src/tools/schemas.d.ts +48 -53
  28. package/dist/framework-mcp/src/tools/schemas.js +18 -22
  29. package/dist/framework-mcp/src/tools/schemas.js.map +1 -1
  30. package/dist/framework-mcp/src/tools/shell/run_command.js +146 -90
  31. package/dist/framework-mcp/src/tools/shell/run_command.js.map +1 -1
  32. package/dist/framework-mcp/src/tools/types.d.ts +11 -105
  33. package/dist/framework-mcp/src/tools/types.js +1 -0
  34. package/dist/framework-mcp/src/tools/types.js.map +1 -1
  35. package/dist/framework-mcp/src/utils/auth.d.ts +36 -0
  36. package/dist/framework-mcp/src/utils/auth.js +79 -0
  37. package/dist/framework-mcp/src/utils/auth.js.map +1 -0
  38. package/dist/framework-mcp/src/utils/auto-rollback.d.ts +71 -0
  39. package/dist/framework-mcp/src/utils/auto-rollback.js +266 -0
  40. package/dist/framework-mcp/src/utils/auto-rollback.js.map +1 -0
  41. package/dist/framework-mcp/src/utils/compliance.js +12 -4
  42. package/dist/framework-mcp/src/utils/compliance.js.map +1 -1
  43. package/dist/framework-mcp/src/utils/context-optimizer.d.ts +69 -0
  44. package/dist/framework-mcp/src/utils/context-optimizer.js +157 -0
  45. package/dist/framework-mcp/src/utils/context-optimizer.js.map +1 -0
  46. package/dist/framework-mcp/src/utils/discipline.d.ts +68 -0
  47. package/dist/framework-mcp/src/utils/discipline.js +218 -0
  48. package/dist/framework-mcp/src/utils/discipline.js.map +1 -0
  49. package/dist/framework-mcp/src/utils/finops.d.ts +143 -0
  50. package/dist/framework-mcp/src/utils/finops.js +326 -0
  51. package/dist/framework-mcp/src/utils/finops.js.map +1 -0
  52. package/dist/framework-mcp/src/utils/human-in-loop.d.ts +95 -0
  53. package/dist/framework-mcp/src/utils/human-in-loop.js +216 -0
  54. package/dist/framework-mcp/src/utils/human-in-loop.js.map +1 -0
  55. package/dist/framework-mcp/src/utils/license-scanner.d.ts +71 -0
  56. package/dist/framework-mcp/src/utils/license-scanner.js +284 -0
  57. package/dist/framework-mcp/src/utils/license-scanner.js.map +1 -0
  58. package/dist/framework-mcp/src/utils/loop-detector.d.ts +105 -0
  59. package/dist/framework-mcp/src/utils/loop-detector.js +362 -0
  60. package/dist/framework-mcp/src/utils/loop-detector.js.map +1 -0
  61. package/dist/framework-mcp/src/utils/quality.d.ts +19 -0
  62. package/dist/framework-mcp/src/utils/quality.js +125 -0
  63. package/dist/framework-mcp/src/utils/quality.js.map +1 -0
  64. package/dist/framework-mcp/src/utils/rules-engine.d.ts +78 -0
  65. package/dist/framework-mcp/src/utils/rules-engine.js +232 -0
  66. package/dist/framework-mcp/src/utils/rules-engine.js.map +1 -0
  67. package/dist/framework-mcp/src/utils/silent-router.d.ts +49 -0
  68. package/dist/framework-mcp/src/utils/silent-router.js +155 -0
  69. package/dist/framework-mcp/src/utils/silent-router.js.map +1 -0
  70. package/dist/framework-mcp/src/utils/storage.d.ts +1 -0
  71. package/dist/framework-mcp/src/utils/storage.js +2 -2
  72. package/dist/framework-mcp/src/utils/storage.js.map +1 -1
  73. package/dist/framework-mcp/src/utils/telemetry-streamer.d.ts +133 -0
  74. package/dist/framework-mcp/src/utils/telemetry-streamer.js +397 -0
  75. package/dist/framework-mcp/src/utils/telemetry-streamer.js.map +1 -0
  76. package/dist/framework-mcp/tests/tools/file_system/file_system_tools.test.js +2 -2
  77. package/dist/framework-mcp/tests/tools/file_system/file_system_tools.test.js.map +1 -1
  78. package/dist/framework-mcp/tests/tools/framework/run_tests.test.d.ts +1 -0
  79. package/dist/framework-mcp/tests/tools/framework/run_tests.test.js +49 -0
  80. package/dist/framework-mcp/tests/tools/framework/run_tests.test.js.map +1 -0
  81. package/dist/framework-mcp/tests/tools/messaging/send_message.test.js +14 -5
  82. package/dist/framework-mcp/tests/tools/messaging/send_message.test.js.map +1 -1
  83. package/dist/framework-mcp/tests/tools/observability/check_ports.test.d.ts +1 -0
  84. package/dist/framework-mcp/tests/tools/observability/check_ports.test.js +44 -0
  85. package/dist/framework-mcp/tests/tools/observability/check_ports.test.js.map +1 -0
  86. package/dist/framework-mcp/tests/tools/shell/run_command.test.js +44 -14
  87. package/dist/framework-mcp/tests/tools/shell/run_command.test.js.map +1 -1
  88. package/dist/framework-mcp/tests/utils/auto-rollback.test.d.ts +7 -0
  89. package/dist/framework-mcp/tests/utils/auto-rollback.test.js +178 -0
  90. package/dist/framework-mcp/tests/utils/auto-rollback.test.js.map +1 -0
  91. package/dist/framework-mcp/tests/utils/context-optimizer.test.d.ts +1 -0
  92. package/dist/framework-mcp/tests/utils/context-optimizer.test.js +62 -0
  93. package/dist/framework-mcp/tests/utils/context-optimizer.test.js.map +1 -0
  94. package/dist/framework-mcp/tests/utils/finops.test.d.ts +7 -0
  95. package/dist/framework-mcp/tests/utils/finops.test.js +157 -0
  96. package/dist/framework-mcp/tests/utils/finops.test.js.map +1 -0
  97. package/dist/framework-mcp/tests/utils/human-in-loop.test.d.ts +1 -0
  98. package/dist/framework-mcp/tests/utils/human-in-loop.test.js +59 -0
  99. package/dist/framework-mcp/tests/utils/human-in-loop.test.js.map +1 -0
  100. package/dist/framework-mcp/tests/utils/license-scanner.test.d.ts +7 -0
  101. package/dist/framework-mcp/tests/utils/license-scanner.test.js +131 -0
  102. package/dist/framework-mcp/tests/utils/license-scanner.test.js.map +1 -0
  103. package/dist/framework-mcp/tests/utils/loop-detector.test.d.ts +7 -0
  104. package/dist/framework-mcp/tests/utils/loop-detector.test.js +204 -0
  105. package/dist/framework-mcp/tests/utils/loop-detector.test.js.map +1 -0
  106. package/dist/framework-mcp/tests/utils/rules-engine.test.d.ts +1 -0
  107. package/dist/framework-mcp/tests/utils/rules-engine.test.js +106 -0
  108. package/dist/framework-mcp/tests/utils/rules-engine.test.js.map +1 -0
  109. package/dist/framework-mcp/tests/utils/telemetry-streamer.test.d.ts +7 -0
  110. package/dist/framework-mcp/tests/utils/telemetry-streamer.test.js +201 -0
  111. package/dist/framework-mcp/tests/utils/telemetry-streamer.test.js.map +1 -0
  112. package/dist/src/cli/commands/check.js +16 -1
  113. package/dist/src/cli/commands/check.js.map +1 -1
  114. package/dist/src/cli/commands/dashboard.js +188 -164
  115. package/dist/src/cli/commands/dashboard.js.map +1 -1
  116. package/dist/src/cli/commands/gateway.js +2 -2
  117. package/dist/src/cli/commands/gateway.js.map +1 -1
  118. package/dist/src/cli/commands/init/scaffold-core.d.ts +2 -0
  119. package/dist/src/cli/commands/init/scaffold-core.js +16 -1
  120. package/dist/src/cli/commands/init/scaffold-core.js.map +1 -1
  121. package/dist/src/cli/commands/init/scaffold-ops.js +2 -2
  122. package/dist/src/cli/commands/init/scaffold-ops.js.map +1 -1
  123. package/dist/src/cli/commands/init/scaffold-standards.js +13 -1
  124. package/dist/src/cli/commands/init/scaffold-standards.js.map +1 -1
  125. package/dist/src/cli/commands/init.d.ts +4 -0
  126. package/dist/src/cli/commands/init.js +94 -31
  127. package/dist/src/cli/commands/init.js.map +1 -1
  128. package/dist/src/cli/commands/mcp.js +3 -1
  129. package/dist/src/cli/commands/mcp.js.map +1 -1
  130. package/dist/src/cli/commands/quickstart.d.ts +1 -1
  131. package/dist/src/cli/commands/quickstart.js +12 -24
  132. package/dist/src/cli/commands/quickstart.js.map +1 -1
  133. package/dist/src/cli/commands/script.js +8 -1
  134. package/dist/src/cli/commands/script.js.map +1 -1
  135. package/dist/src/cli/index.js +24 -23
  136. package/dist/src/cli/index.js.map +1 -1
  137. package/dist/src/cli/shims.js +7 -7
  138. package/dist/src/cli/utils/pkg.js +2 -3
  139. package/dist/src/cli/utils/pkg.js.map +1 -1
  140. package/dist/src/cli/utils/quality.d.ts +10 -0
  141. package/dist/src/cli/utils/quality.js +10 -0
  142. package/dist/src/cli/utils/quality.js.map +1 -0
  143. package/dist/src/modules/agents/definitions.js +45 -40
  144. package/dist/src/modules/agents/definitions.js.map +1 -1
  145. package/dist/src/modules/agents/registry/manager.js +6 -1
  146. package/dist/src/modules/agents/registry/manager.js.map +1 -1
  147. package/dist/src/modules/engines/agent-executor.d.ts +6 -10
  148. package/dist/src/modules/engines/agent-executor.js +63 -21
  149. package/dist/src/modules/engines/agent-executor.js.map +1 -1
  150. package/dist/src/modules/engines/agent-loop.d.ts +34 -0
  151. package/dist/src/modules/engines/agent-loop.js +102 -0
  152. package/dist/src/modules/engines/agent-loop.js.map +1 -0
  153. package/dist/src/modules/engines/evaluation-engine.d.ts +12 -2
  154. package/dist/src/modules/engines/evaluation-engine.js +78 -11
  155. package/dist/src/modules/engines/evaluation-engine.js.map +1 -1
  156. package/dist/src/modules/engines/quality-gate.d.ts +13 -5
  157. package/dist/src/modules/engines/quality-gate.js +39 -31
  158. package/dist/src/modules/engines/quality-gate.js.map +1 -1
  159. package/dist/src/modules/engines/routing-engine.d.ts +5 -3
  160. package/dist/src/modules/engines/routing-engine.js +16 -9
  161. package/dist/src/modules/engines/routing-engine.js.map +1 -1
  162. package/dist/src/modules/gateway/cost-tracker.d.ts +5 -0
  163. package/dist/src/modules/gateway/cost-tracker.js +71 -0
  164. package/dist/src/modules/gateway/cost-tracker.js.map +1 -1
  165. package/dist/src/modules/gateway/gateway.d.ts +4 -0
  166. package/dist/src/modules/gateway/gateway.js +8 -0
  167. package/dist/src/modules/gateway/gateway.js.map +1 -1
  168. package/dist/src/shared/audit.js +3 -2
  169. package/dist/src/shared/audit.js.map +1 -1
  170. package/dist/src/shared/pii.d.ts +41 -9
  171. package/dist/src/shared/pii.js +122 -31
  172. package/dist/src/shared/pii.js.map +1 -1
  173. package/dist/src/shared/storage.d.ts +62 -0
  174. package/dist/src/shared/storage.js +47 -7
  175. package/dist/src/shared/storage.js.map +1 -1
  176. package/dist/tests/embedding.test.d.ts +1 -0
  177. package/dist/tests/embedding.test.js +82 -0
  178. package/dist/tests/embedding.test.js.map +1 -0
  179. package/dist/tests/engines/evaluation-engine.test.d.ts +1 -0
  180. package/dist/tests/engines/evaluation-engine.test.js +71 -0
  181. package/dist/tests/engines/evaluation-engine.test.js.map +1 -0
  182. package/dist/tests/engines/planning-engine.test.d.ts +1 -0
  183. package/dist/tests/engines/planning-engine.test.js +100 -0
  184. package/dist/tests/engines/planning-engine.test.js.map +1 -0
  185. package/dist/tests/engines/quality-gate.test.d.ts +1 -0
  186. package/dist/tests/engines/quality-gate.test.js +59 -0
  187. package/dist/tests/engines/quality-gate.test.js.map +1 -0
  188. package/dist/tests/engines/risk-engine.test.d.ts +1 -0
  189. package/dist/tests/engines/risk-engine.test.js +80 -0
  190. package/dist/tests/engines/risk-engine.test.js.map +1 -0
  191. package/dist/tests/engines/routing-engine.test.d.ts +1 -0
  192. package/dist/tests/engines/routing-engine.test.js +78 -0
  193. package/dist/tests/engines/routing-engine.test.js.map +1 -0
  194. package/dist/tests/integration/hermes_locking.test.js +1 -0
  195. package/dist/tests/integration/hermes_locking.test.js.map +1 -1
  196. package/dist/tests/modules/engines/agent-executor.test.js +1 -1
  197. package/dist/tests/modules/engines/agent-executor.test.js.map +1 -1
  198. package/dist/tests/modules/gateway/cost-tracker.test.js +20 -1
  199. package/dist/tests/modules/gateway/cost-tracker.test.js.map +1 -1
  200. package/dist/tests/shared/lock.test.d.ts +1 -0
  201. package/dist/tests/shared/lock.test.js +95 -0
  202. package/dist/tests/shared/lock.test.js.map +1 -0
  203. package/dist/tests/shared/pii.test.d.ts +1 -0
  204. package/dist/tests/shared/pii.test.js +183 -0
  205. package/dist/tests/shared/pii.test.js.map +1 -0
  206. package/framework-mcp/README.md +307 -35
  207. package/framework-mcp/dist/dashboard/assets/index-B_rK57vi.js +1221 -0
  208. package/framework-mcp/dist/dashboard/index.html +1 -1
  209. package/framework-mcp/dist/framework-mcp/src/index.js +988 -63
  210. package/framework-mcp/dist/framework-mcp/src/tools/compliance/mask_pii.js +55 -0
  211. package/framework-mcp/dist/framework-mcp/src/tools/definitions.js +10 -9
  212. package/framework-mcp/dist/framework-mcp/src/tools/framework/run_tests.js +25 -0
  213. package/framework-mcp/dist/framework-mcp/src/tools/index.js +7 -8
  214. package/framework-mcp/dist/framework-mcp/src/tools/messaging/approve_operation.js +80 -0
  215. package/framework-mcp/dist/framework-mcp/src/tools/messaging/ask_human.js +89 -20
  216. package/framework-mcp/dist/framework-mcp/src/tools/observability/check_ports.js +24 -8
  217. package/framework-mcp/dist/framework-mcp/src/tools/quality/analyze_code_quality.js +13 -64
  218. package/framework-mcp/dist/framework-mcp/src/tools/schemas.js +18 -22
  219. package/framework-mcp/dist/framework-mcp/src/tools/shell/run_command.js +146 -90
  220. package/framework-mcp/dist/framework-mcp/src/tools/types.js +1 -0
  221. package/framework-mcp/dist/framework-mcp/src/utils/auth.js +78 -0
  222. package/framework-mcp/dist/framework-mcp/src/utils/auto-rollback.js +265 -0
  223. package/framework-mcp/dist/framework-mcp/src/utils/compliance.js +12 -4
  224. package/framework-mcp/dist/framework-mcp/src/utils/context-optimizer.js +156 -0
  225. package/framework-mcp/dist/framework-mcp/src/utils/discipline.js +217 -0
  226. package/framework-mcp/dist/framework-mcp/src/utils/finops.js +325 -0
  227. package/framework-mcp/dist/framework-mcp/src/utils/human-in-loop.js +215 -0
  228. package/framework-mcp/dist/framework-mcp/src/utils/license-scanner.js +283 -0
  229. package/framework-mcp/dist/framework-mcp/src/utils/loop-detector.js +361 -0
  230. package/framework-mcp/dist/framework-mcp/src/utils/quality.js +124 -0
  231. package/framework-mcp/dist/framework-mcp/src/utils/rules-engine.js +231 -0
  232. package/framework-mcp/dist/framework-mcp/src/utils/silent-router.js +154 -0
  233. package/framework-mcp/dist/framework-mcp/src/utils/storage.js +2 -2
  234. package/framework-mcp/dist/framework-mcp/src/utils/telemetry-streamer.js +396 -0
  235. package/framework-mcp/dist/src/cli/platforms/core.js +72 -0
  236. package/framework-mcp/dist/src/cli/platforms/index.js +5 -0
  237. package/framework-mcp/dist/src/cli/platforms/paths.js +101 -0
  238. package/framework-mcp/dist/src/cli/platforms/scaffold.js +72 -0
  239. package/framework-mcp/dist/src/cli/platforms/utils.js +75 -0
  240. package/framework-mcp/dist/src/cli/utils/claude.js +57 -0
  241. package/framework-mcp/dist/src/cli/utils/compliance.js +12 -0
  242. package/framework-mcp/dist/src/cli/utils/fs.js +138 -0
  243. package/framework-mcp/dist/src/cli/utils/pkg.js +282 -0
  244. package/framework-mcp/dist/src/cli/utils/quality.js +9 -0
  245. package/framework-mcp/dist/src/modules/agents/definitions.js +563 -0
  246. package/framework-mcp/dist/src/modules/agents/registry/analyst.js +39 -0
  247. package/framework-mcp/dist/src/modules/agents/registry/architect.js +42 -0
  248. package/framework-mcp/dist/src/modules/agents/registry/backend.js +50 -0
  249. package/framework-mcp/dist/src/modules/agents/registry/database.js +45 -0
  250. package/framework-mcp/dist/src/modules/agents/registry/devops.js +45 -0
  251. package/framework-mcp/dist/src/modules/agents/registry/explorer.js +37 -0
  252. package/framework-mcp/dist/src/modules/agents/registry/frontend.js +52 -0
  253. package/framework-mcp/dist/src/modules/agents/registry/git.js +36 -0
  254. package/framework-mcp/dist/src/modules/agents/registry/manager.js +64 -0
  255. package/framework-mcp/dist/src/modules/agents/registry/mobile.js +41 -0
  256. package/framework-mcp/dist/src/modules/agents/registry/native.js +41 -0
  257. package/framework-mcp/dist/src/modules/agents/registry/quality.js +43 -0
  258. package/framework-mcp/dist/src/modules/agents/registry/security.js +42 -0
  259. package/framework-mcp/dist/src/modules/agents/types.js +1 -0
  260. package/framework-mcp/dist/src/modules/engines/evaluation-engine.js +169 -0
  261. package/framework-mcp/dist/src/modules/engines/quality-gate.js +92 -0
  262. package/framework-mcp/dist/src/modules/engines/risk-engine.js +105 -0
  263. package/framework-mcp/dist/src/modules/engines/routing-engine.js +408 -0
  264. package/framework-mcp/dist/src/modules/engines/types.js +1 -0
  265. package/framework-mcp/dist/src/modules/providers/definitions.js +140 -0
  266. package/framework-mcp/dist/src/modules/providers/registry.js +18 -0
  267. package/framework-mcp/dist/src/modules/providers/shared.js +104 -0
  268. package/framework-mcp/dist/src/modules/providers/types.js +1 -0
  269. package/framework-mcp/dist/src/modules/skills/adapter-skills.js +449 -0
  270. package/framework-mcp/dist/src/modules/skills/definitions.js +70 -0
  271. package/framework-mcp/dist/src/shared/audit.js +220 -0
  272. package/framework-mcp/dist/src/shared/errors.js +68 -0
  273. package/framework-mcp/dist/src/shared/lock.js +159 -0
  274. package/framework-mcp/dist/src/shared/pii.js +122 -31
  275. package/framework-mcp/dist/src/shared/retention.js +170 -0
  276. package/framework-mcp/dist/src/shared/storage.js +47 -7
  277. package/framework-mcp/dist/src/shared/string.js +29 -0
  278. package/framework-mcp/package.json +8 -2
  279. package/mcp.json +12 -6
  280. package/package.json +11 -5
  281. package/templates/full/ATABEY_FULL.md +1 -1
  282. package/framework-mcp/dist/dashboard/assets/index-BAPGLiWE.js +0 -267
@@ -1,15 +1,43 @@
1
1
  #!/usr/bin/env node
2
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
+ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
4
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
+ import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
2
6
  import fs from "fs";
7
+ import http from "http";
3
8
  import path from "path";
4
9
  import { fileURLToPath } from "url";
5
- import { Server } from "@modelcontextprotocol/sdk/server/index.js";
6
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
7
- import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
8
- import { TOOLS, toolHandlers, toolSchemas } from "./tools/index.js";
10
+ import { WebSocket, WebSocketServer } from "ws";
11
+ import { generateULID } from "../../src/cli/utils/time.js";
12
+ import { Audit } from "../../src/shared/audit.js";
13
+ import { maskText, maskToolArgs, maskToolResult } from "../../src/shared/pii.js";
14
+ import { Storage } from "../../src/shared/storage.js";
9
15
  import { RESOURCES, handleReadResource } from "./resources/index.js";
10
- // ─── Server Setup ─────────────────────────────────────────────────
16
+ import { TOOLS, toolHandlers, toolSchemas } from "./tools/index.js";
17
+ // ─── Paths ────────────────────────────────────────────────────────
11
18
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
12
- // Robustly find package.json by walking up from __dirname
19
+ // ─── Friendly Startup Validation ──────────────────────────────────
20
+ function validateEnvironment() {
21
+ // Node.js version check
22
+ const nodeMajor = parseInt(process.versions.node.split(".")[0], 10);
23
+ if (nodeMajor < 18) {
24
+ process.stderr.write(`
25
+ ╔══════════════════════════════════════════════════════════╗
26
+ ║ ⚠️ Unsupported Node.js version ║
27
+ ╠══════════════════════════════════════════════════════════╣
28
+ ║ Atabey MCP requires Node.js >= 18.0.0 ║
29
+ ║ ║
30
+ ║ Current version: ${process.versions.node} ║
31
+ ║ ║
32
+ ║ 📋 To fix this: ║
33
+ ║ nvm install 18 && nvm use 18 (if using nvm) ║
34
+ ║ brew install node (if using Homebrew) ║
35
+ ║ https://nodejs.org (official installer) ║
36
+ ╚══════════════════════════════════════════════════════════╝
37
+ `);
38
+ process.exit(1);
39
+ }
40
+ }
13
41
  function findPackageJson(startDir) {
14
42
  let currentDir = startDir;
15
43
  while (currentDir !== path.parse(currentDir).root) {
@@ -18,24 +46,92 @@ function findPackageJson(startDir) {
18
46
  return pkgPath;
19
47
  currentDir = path.dirname(currentDir);
20
48
  }
21
- throw new Error("Could not find package.json for atabey-mcp");
49
+ process.stderr.write(`
50
+ ╔══════════════════════════════════════════════════════════╗
51
+ ║ 🚧 Package not found ║
52
+ ╠══════════════════════════════════════════════════════════╣
53
+ ║ Atabey MCP package.json could not be located. ║
54
+ ║ ║
55
+ ║ This usually means: ║
56
+ ║ 1. The package was not installed correctly ║
57
+ ║ 2. Node modules are missing ║
58
+ ║ ║
59
+ ║ 📋 To fix this: ║
60
+ ║ npm install -g atabey (global install) ║
61
+ ║ npm install (local install) ║
62
+ ║ npm run build (if developing) ║
63
+ ╚══════════════════════════════════════════════════════════╝
64
+ `);
65
+ process.exit(1);
22
66
  }
67
+ validateEnvironment();
23
68
  const pkgPath = findPackageJson(__dirname);
24
69
  const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
25
70
  const serverVersion = pkg.version;
26
- const server = new Server({
27
- name: "atabey-mcp",
28
- version: serverVersion,
29
- }, {
30
- capabilities: {
31
- tools: {},
32
- resources: {},
33
- },
34
- });
35
- /**
36
- * Validates tool arguments using Zod schemas for "Zero Type Hole" compliance.
37
- * Falls back to basic presence check if no Zod schema is defined for the tool.
38
- */
71
+ // Validate environment variables with friendly messages
72
+ const PROJECT_ROOT = process.env.ATABEY_PROJECT_ROOT || process.cwd();
73
+ if (!process.env.ATABEY_PROJECT_ROOT) {
74
+ const transportMode = process.env.MCP_TRANSPORT || "unified";
75
+ if (transportMode !== "stdio") {
76
+ process.stderr.write(`
77
+ ╔══════════════════════════════════════════════════════════╗
78
+ ║ 💡 Tip: Set ATABEY_PROJECT_ROOT ║
79
+ ╠══════════════════════════════════════════════════════════╣
80
+ ║ In HTTP/SSE mode, it's recommended to explicitly set ║
81
+ ║ the project root to avoid confusion.
82
+ ║ ║
83
+ ║ Example: ║
84
+ ║ export ATABEY_PROJECT_ROOT=/path/to/your/project ║
85
+ ║ ║
86
+ ║ Using current directory: ${process.cwd()} ║
87
+ ╚══════════════════════════════════════════════════════════╝
88
+ `);
89
+ }
90
+ }
91
+ const FRAMEWORK_DIR = process.env.ATABEY_FRAMEWORK_DIR || path.join(PROJECT_ROOT, ".atabey");
92
+ const UI_DIST_PATH = path.join(__dirname, "../../dashboard/dist");
93
+ // ─── Ports ────────────────────────────────────────────────────────
94
+ const PORT = parseInt(process.env.MCP_PORT || "5858", 10);
95
+ if (isNaN(PORT) || PORT < 1 || PORT > 65535) {
96
+ process.stderr.write(`
97
+ ╔══════════════════════════════════════════════════════════╗
98
+ ║ ❌ Invalid MCP_PORT ║
99
+ ╠══════════════════════════════════════════════════════════╣
100
+ ║ The port must be a number between 1 and 65535. ║
101
+ ║ ║
102
+ ║ You set: MCP_PORT=${process.env.MCP_PORT || "(empty)"} ║
103
+ ║ ║
104
+ ║ 📋 To fix this: ║
105
+ ║ export MCP_PORT=5858 (default) ║
106
+ ║ export MCP_PORT=8080 (alternative) ║
107
+ ╚══════════════════════════════════════════════════════════╝
108
+ `);
109
+ process.exit(1);
110
+ }
111
+ const HOST = process.env.MCP_HOST || "0.0.0.0";
112
+ // ─── MCP Server ───────────────────────────────────────────────────
113
+ const server = new Server({ name: "atabey-mcp", version: serverVersion }, { capabilities: { tools: {}, resources: {} } });
114
+ // Track multiple SSE sessions
115
+ const sessions = new Map();
116
+ // ─── WebSocket Clients ────────────────────────────────────────────
117
+ const wsClients = new Set();
118
+ function broadcastWS(type, payload) {
119
+ const msg = JSON.stringify({ type, payload });
120
+ wsClients.forEach((ws) => {
121
+ if (ws.readyState === WebSocket.OPEN) {
122
+ try {
123
+ ws.send(msg);
124
+ }
125
+ catch {
126
+ wsClients.delete(ws);
127
+ }
128
+ }
129
+ else {
130
+ wsClients.delete(ws);
131
+ }
132
+ });
133
+ }
134
+ // ─── Validation ───────────────────────────────────────────────────
39
135
  function validateArgs(toolName, args) {
40
136
  const schema = toolSchemas[toolName];
41
137
  if (schema) {
@@ -45,7 +141,6 @@ function validateArgs(toolName, args) {
45
141
  }
46
142
  return null;
47
143
  }
48
- // Fallback for tools without Zod schemas yet
49
144
  const definition = TOOLS.find(t => t.name === toolName);
50
145
  if (!definition)
51
146
  return `Unknown tool: ${toolName}`;
@@ -57,11 +152,11 @@ function validateArgs(toolName, args) {
57
152
  }
58
153
  return null;
59
154
  }
155
+ // ─── MCP Handlers ─────────────────────────────────────────────────
60
156
  server.setRequestHandler(ListToolsRequestSchema, async (request) => {
61
- // 2026 Stateless Spec: Log client info from metadata if available
62
157
  const meta = request._meta;
63
158
  if (meta) {
64
- process.stderr.write(`[MCP] Stateless ListTools from ${meta.client?.name || "unknown"} v${meta.client?.version || "?.?"}\n`);
159
+ process.stderr.write(`[MCP] ListTools from ${meta.client?.name || "unknown"} v${meta.client?.version || "?.?"}\n`);
65
160
  }
66
161
  return { tools: TOOLS };
67
162
  });
@@ -72,15 +167,7 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
72
167
  const uri = request.params.uri;
73
168
  try {
74
169
  const content = await handleReadResource(uri);
75
- return {
76
- contents: [
77
- {
78
- uri,
79
- mimeType: "text/markdown",
80
- text: content,
81
- },
82
- ],
83
- };
170
+ return { contents: [{ uri, mimeType: "text/markdown", text: content }] };
84
171
  }
85
172
  catch (error) {
86
173
  const message = error instanceof Error ? error.message : String(error);
@@ -91,67 +178,905 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
91
178
  const req = request;
92
179
  const { name, arguments: args } = req.params;
93
180
  const meta = request._meta;
94
- // 2026 Stateless Spec: Prioritize metadata-driven context
95
181
  if (meta) {
96
- process.stderr.write(`[MCP] Stateless CallTool: ${name} (Client: ${meta.client?.name || "unknown"})\n`);
182
+ process.stderr.write(`[MCP] CallTool: ${name} (Client: ${meta.client?.name || "unknown"})\n`);
97
183
  }
98
- const projectRoot = process.env.ATABEY_PROJECT_ROOT || process.cwd();
99
184
  try {
100
185
  const handler = toolHandlers[name];
101
186
  if (!handler) {
102
- return {
103
- isError: true,
104
- content: [{ type: "text", text: `[ERROR] Unknown tool: ${name}` }],
105
- };
187
+ return { isError: true, content: [{ type: "text", text: `[ERROR] Unknown tool: ${name}` }] };
106
188
  }
107
- // [SECURITY] Runtime Validation
108
189
  const validationError = validateArgs(name, args || {});
109
190
  if (validationError) {
110
- return {
111
- isError: true,
112
- content: [{ type: "text", text: `[ERROR] Validation Error: ${validationError}` }],
113
- };
191
+ return { isError: true, content: [{ type: "text", text: `[ERROR] Validation Error: ${validationError}` }] };
192
+ }
193
+ // [PII/GDPR/KVKK] Mask args before handler
194
+ const maskedArgs = maskToolArgs(args || {});
195
+ // [TOKEN ECONOMY] Log token usage
196
+ const argsJson = JSON.stringify(maskedArgs);
197
+ const estimatedTokens = Math.ceil(argsJson.length / 4);
198
+ const clientAgent = meta?.client?.name || "mcp-client";
199
+ try {
200
+ const { Metrics } = await import("./utils/metrics.js");
201
+ Metrics.logUsage(PROJECT_ROOT, clientAgent, name, estimatedTokens);
202
+ }
203
+ catch { /* metrics should not block */ }
204
+ // [GOVERNANCE] Validate arguments against governance rules (pre-execution)
205
+ try {
206
+ const { validateArgsAgainstRules } = await import("./utils/rules-engine.js");
207
+ const governanceError = validateArgsAgainstRules(name, maskedArgs);
208
+ if (governanceError) {
209
+ process.stderr.write(`[GOVERNANCE] Blocked: ${governanceError}\n`);
210
+ broadcastWS("governance_violation", { agent: clientAgent, tool: name, error: governanceError, timestamp: new Date().toISOString() });
211
+ return { isError: true, content: [{ type: "text", text: governanceError }] };
212
+ }
114
213
  }
115
- return await handler(projectRoot, args || {});
214
+ catch { /* governance should not block */ }
215
+ // [DISCIPLINE] Enforce AI discipline at tool level BEFORE execution
216
+ try {
217
+ const { enforceDiscipline } = await import("./utils/discipline.js");
218
+ const disciplineError = await enforceDiscipline(clientAgent, name, maskedArgs);
219
+ if (disciplineError) {
220
+ process.stderr.write(`[DISCIPLINE] Blocked: ${disciplineError}\n`);
221
+ broadcastWS("discipline_violation", { agent: clientAgent, tool: name, error: disciplineError, timestamp: new Date().toISOString() });
222
+ return { isError: true, content: [{ type: "text", text: `[DISCIPLINE] ${disciplineError}` }] };
223
+ }
224
+ }
225
+ catch { /* discipline should not block execution if module fails */ }
226
+ // [SILENT ROUTER] Detect agent from context and inject rules
227
+ const traceId = generateULID();
228
+ let detectedAgent = clientAgent;
229
+ try {
230
+ const { detectAgent, stealthNotify } = await import("./utils/silent-router.js");
231
+ detectedAgent = detectAgent(name, maskedArgs);
232
+ if (detectedAgent !== clientAgent) {
233
+ stealthNotify(detectedAgent, name, `Silently routed from ${clientAgent}`);
234
+ process.stderr.write(`[SILENT ROUTER] ${name} → ${detectedAgent} (from ${clientAgent})\n`);
235
+ }
236
+ }
237
+ catch { /* silent router should not block */ }
238
+ // [LOOP DETECTION] Check for infinite loop patterns
239
+ try {
240
+ const { recordAndCheck } = await import("./utils/loop-detector.js");
241
+ const loopAlert = recordAndCheck(detectedAgent, name, maskedArgs);
242
+ if (loopAlert) {
243
+ process.stderr.write(`[LOOP DETECT] ${loopAlert.type}: ${loopAlert.detail}\n`);
244
+ broadcastWS("loop_detected", { agent: detectedAgent, tool: name, alert: loopAlert, timestamp: new Date().toISOString() });
245
+ if (loopAlert.severity === "critical" && loopAlert.cooldownUntil) {
246
+ const remaining = Math.ceil((loopAlert.cooldownUntil - Date.now()) / 1000);
247
+ return {
248
+ isError: true,
249
+ content: [{ type: "text", text: `[LOOP DETECTED] ${loopAlert.detail} Cooldown: ${remaining}s. Please change your approach.` }]
250
+ };
251
+ }
252
+ }
253
+ }
254
+ catch { /* loop detection should not block */ }
255
+ // [FINOPS] Check budget BEFORE execution (record usage)
256
+ try {
257
+ const { budgetManager } = await import("./utils/finops.js");
258
+ const budgetError = budgetManager.recordUsage(detectedAgent, estimatedTokens);
259
+ if (budgetError) {
260
+ process.stderr.write(`[FINOPS] Budget blocked: ${budgetError}\n`);
261
+ broadcastWS("budget_blocked", { agent: detectedAgent, tool: name, error: budgetError, timestamp: new Date().toISOString() });
262
+ return { isError: true, content: [{ type: "text", text: budgetError }] };
263
+ }
264
+ }
265
+ catch { /* finops should not block */ }
266
+ // [LICENSE] Validate write content for license compliance (pre-execution)
267
+ if (name === "write_file" || name === "replace_text" || name === "patch_file") {
268
+ try {
269
+ const { validateLicenseCompliance } = await import("./utils/license-scanner.js");
270
+ const filePath = maskedArgs.path || "";
271
+ const content = maskedArgs.content || "";
272
+ if (filePath && content) {
273
+ const licenseError = validateLicenseCompliance(filePath, content);
274
+ if (licenseError) {
275
+ process.stderr.write(`[LICENSE] Blocked: ${licenseError}\n`);
276
+ broadcastWS("license_violation", { agent: detectedAgent, tool: name, error: licenseError, timestamp: new Date().toISOString() });
277
+ return { isError: true, content: [{ type: "text", text: licenseError }] };
278
+ }
279
+ }
280
+ }
281
+ catch { /* license scan should not block */ }
282
+ }
283
+ // [AUTO-ROLLBACK] Prepare snapshot for write operations
284
+ if (name === "write_file" || name === "replace_text" || name === "patch_file") {
285
+ try {
286
+ const { AutoRollbackEngine } = await import("./utils/auto-rollback.js");
287
+ const filePath = maskedArgs.path || "";
288
+ if (filePath) {
289
+ AutoRollbackEngine.prepareWrite(filePath, traceId);
290
+ }
291
+ }
292
+ catch { /* auto-rollback should not block */ }
293
+ }
294
+ // [HUMAN-IN-LOOP] Check risk gate before execution
295
+ try {
296
+ const { RiskEngine } = await import("../../src/modules/engines/risk-engine.js");
297
+ const riskContext = `${name} ${JSON.stringify(maskedArgs)}`;
298
+ const riskResult = RiskEngine.assessTaskRisk(riskContext);
299
+ if (riskResult.totalScore > 0) {
300
+ const { checkRiskGate } = await import("./utils/human-in-loop.js");
301
+ const riskReason = riskResult.factors.map(f => f.description).join("; ") || "High-risk operation detected";
302
+ const gateResult = checkRiskGate(traceId, name, detectedAgent, riskResult.totalScore, riskReason, maskedArgs);
303
+ if (gateResult?.blocked) {
304
+ process.stderr.write(`[RISK GATE] Blocked: ${riskReason} (score: ${riskResult.totalScore})\n`);
305
+ broadcastWS("risk_blocked", { agent: detectedAgent, tool: name, riskScore: riskResult.totalScore, traceId, timestamp: new Date().toISOString() });
306
+ return { isError: true, content: [{ type: "text", text: gateResult.message }] };
307
+ }
308
+ if (gateResult?.warning) {
309
+ process.stderr.write(`[RISK WARNING] ${gateResult.warning}\n`);
310
+ }
311
+ }
312
+ }
313
+ catch { /* risk gate should not block */ }
314
+ // Execute
315
+ const result = await handler(PROJECT_ROOT, maskedArgs);
316
+ // [AUTO-ROLLBACK] Check write results for governance violations
317
+ if (name === "write_file" || name === "replace_text" || name === "patch_file") {
318
+ try {
319
+ const filePath = maskedArgs.path || "";
320
+ const content = maskedArgs.content || "";
321
+ if (filePath && content) {
322
+ const { AutoRollbackEngine } = await import("./utils/auto-rollback.js");
323
+ const { scanFileForViolations } = await import("./utils/rules-engine.js");
324
+ const violations = scanFileForViolations(filePath, content);
325
+ if (violations.length > 0) {
326
+ const violationRecords = violations.map(v => ({
327
+ rule: v.rule.name,
328
+ severity: v.rule.priority,
329
+ filePath,
330
+ line: v.line,
331
+ detail: `Line ${v.line}: ${v.match}`,
332
+ regenerateInstruction: v.rule.errorMessage,
333
+ }));
334
+ const rolledBack = AutoRollbackEngine.validateAndRollback(filePath, content, violationRecords);
335
+ if (rolledBack) {
336
+ const instruction = AutoRollbackEngine.buildRegenerateInstruction(rolledBack, name);
337
+ broadcastWS("rollback_violation", { agent: detectedAgent, tool: name, violations: rolledBack, timestamp: new Date().toISOString() });
338
+ return { isError: true, content: [{ type: "text", text: instruction }] };
339
+ }
340
+ }
341
+ }
342
+ }
343
+ catch { /* auto-rollback should not block */ }
344
+ }
345
+ // [DISCIPLINE] Validate response content BEFORE returning to AI
346
+ try {
347
+ const { validateResponse } = await import("./utils/discipline.js");
348
+ const responseError = validateResponse(name, result);
349
+ if (responseError) {
350
+ process.stderr.write(`[DISCIPLINE] Response blocked: ${responseError}\n`);
351
+ broadcastWS("discipline_violation", { agent: detectedAgent, tool: name, error: responseError, timestamp: new Date().toISOString() });
352
+ return { isError: true, content: [{ type: "text", text: `[DISCIPLINE] ${responseError}` }] };
353
+ }
354
+ }
355
+ catch { /* response validation should not block */ }
356
+ // [GOVERNANCE] Validate response against governance rules (post-execution)
357
+ try {
358
+ const { validateResponseAgainstRules } = await import("./utils/rules-engine.js");
359
+ const responseText = result.content?.filter(b => b.type === "text").map(b => b.text).join(" ") || "";
360
+ const govResponseError = validateResponseAgainstRules(name, responseText);
361
+ if (govResponseError) {
362
+ process.stderr.write(`[GOVERNANCE] Response violation: ${govResponseError}\n`);
363
+ broadcastWS("governance_violation", { agent: detectedAgent, tool: name, error: govResponseError, timestamp: new Date().toISOString() });
364
+ // Don't block the response, just warn
365
+ }
366
+ }
367
+ catch { /* governance response validation should not block */ }
368
+ // [CONTEXT OPTIMIZER] Check token budget after execution
369
+ try {
370
+ const { checkTokenBudget } = await import("./utils/context-optimizer.js");
371
+ const responseText = result.content?.filter(b => b.type === "text").map(b => b.text).join(" ") || "";
372
+ const budgetError = checkTokenBudget(detectedAgent, name, responseText);
373
+ if (budgetError) {
374
+ process.stderr.write(`[TOKEN BUDGET] Warning: ${budgetError}\n`);
375
+ broadcastWS("token_budget_warning", { agent: detectedAgent, tool: name, error: budgetError, timestamp: new Date().toISOString() });
376
+ }
377
+ }
378
+ catch { /* token budget should not block */ }
379
+ // [SILENT ROUTER] Build silent context injection
380
+ try {
381
+ const { buildSilentContext } = await import("./utils/silent-router.js");
382
+ if (result.content) {
383
+ for (let i = 0; i < result.content.length; i++) {
384
+ const block = result.content[i];
385
+ if (block.type === "text") {
386
+ result.content[i] = {
387
+ ...block,
388
+ text: await buildSilentContext(detectedAgent, name, block.text),
389
+ };
390
+ }
391
+ }
392
+ }
393
+ }
394
+ catch { /* silent context injection should not block */ }
395
+ // Broadcast to dashboard WS
396
+ broadcastWS("tool_call", { agent: detectedAgent, action: name, tokens: estimatedTokens, traceId, timestamp: new Date().toISOString() });
397
+ // [PII/GDPR/KVKK] Mask result before returning to AI
398
+ return maskToolResult(result);
116
399
  }
117
400
  catch (error) {
118
401
  const message = error instanceof Error ? error.message : "Unknown error occurred";
119
- return {
120
- isError: true,
121
- content: [{ type: "text", text: `[ERROR] Execution failed: ${message}` }],
122
- };
402
+ return { isError: true, content: [{ type: "text", text: `[ERROR] Execution failed: ${message}` }] };
123
403
  }
124
404
  });
125
- // ─── Graceful Startup & Shutdown ──────────────────────────────────
405
+ // ─── API Helpers ──────────────────────────────────────────────────
406
+ function serveJson(res, statusCode, data) {
407
+ res.writeHead(statusCode, { "Content-Type": "application/json" });
408
+ res.end(JSON.stringify(data));
409
+ }
410
+ function serveFile(res, filePath, contentType) {
411
+ if (fs.existsSync(filePath)) {
412
+ try {
413
+ const content = fs.readFileSync(filePath);
414
+ res.writeHead(200, { "Content-Type": contentType });
415
+ res.end(content);
416
+ }
417
+ catch (e) {
418
+ res.writeHead(500);
419
+ res.end(e.message);
420
+ }
421
+ }
422
+ else {
423
+ res.writeHead(404);
424
+ res.end("Not Found");
425
+ }
426
+ }
427
+ function parseUrl(url) {
428
+ const [pathname, queryString] = url.split("?");
429
+ const params = {};
430
+ if (queryString) {
431
+ queryString.split("&").forEach((pair) => {
432
+ const [key, value] = pair.split("=");
433
+ params[decodeURIComponent(key)] = decodeURIComponent(value || "");
434
+ });
435
+ }
436
+ return { pathname, params };
437
+ }
438
+ // ─── Unified HTTP Server ──────────────────────────────────────────
439
+ function createUnifiedServer() {
440
+ const httpServer = http.createServer(async (req, res) => {
441
+ const url = req.url || "/";
442
+ const { pathname, params } = parseUrl(url);
443
+ const method = req.method || "GET";
444
+ // CORS
445
+ res.setHeader("Access-Control-Allow-Origin", "*");
446
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
447
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
448
+ if (req.method === "OPTIONS") {
449
+ res.writeHead(204);
450
+ res.end();
451
+ return;
452
+ }
453
+ // [SECURITY] Authentication check for API/MCP routes
454
+ // Skip auth for static UI and health endpoints
455
+ const needsAuth = pathname.startsWith("/api/") || pathname.startsWith("/mcp/");
456
+ const isPublicPath = pathname === "/api/health" || pathname === "/mcp/health" || !pathname.startsWith("/api/") && !pathname.startsWith("/mcp/");
457
+ if (needsAuth && !isPublicPath) {
458
+ const { authenticate } = await import("./utils/auth.js");
459
+ const auth = authenticate(req);
460
+ if (!auth.authenticated) {
461
+ process.stderr.write(`[AUTH] Unauthorized request: ${pathname}\n`);
462
+ serveJson(res, 401, { error: "Unauthorized. Provide Authorization: Bearer <token> header. Set MCP_AUTH_TOKEN env var to configure." });
463
+ return;
464
+ }
465
+ }
466
+ // ───────────── MCP ROUTES (prefix: /mcp) ─────────────
467
+ if (pathname === "/mcp/health") {
468
+ serveJson(res, 200, { status: "ok", version: serverVersion, sessions: sessions.size });
469
+ return;
470
+ }
471
+ if (pathname === "/mcp/sse") {
472
+ const transport = new SSEServerTransport("/mcp/messages", res);
473
+ const sessionId = transport.sessionId;
474
+ sessions.set(sessionId, transport);
475
+ process.stderr.write(`[MCP] Client connected: ${sessionId} (total: ${sessions.size})\n`);
476
+ broadcastWS("mcp_session", { sessionId, action: "connected", total: sessions.size });
477
+ res.on("close", () => {
478
+ sessions.delete(sessionId);
479
+ process.stderr.write(`[MCP] Client disconnected: ${sessionId} (remaining: ${sessions.size})\n`);
480
+ broadcastWS("mcp_session", { sessionId, action: "disconnected", total: sessions.size });
481
+ });
482
+ await server.connect(transport);
483
+ return;
484
+ }
485
+ if (pathname === "/mcp/messages") {
486
+ const sessionId = params.sessionId;
487
+ if (!sessionId || !sessions.has(sessionId)) {
488
+ serveJson(res, 400, { error: "Invalid or missing sessionId" });
489
+ return;
490
+ }
491
+ const transport = sessions.get(sessionId);
492
+ await transport.handlePostMessage(req, res);
493
+ return;
494
+ }
495
+ // ───────────── DASHBOARD API ROUTES (/api) ───────────
496
+ // Health
497
+ if (pathname === "/api/health") {
498
+ serveJson(res, 200, { status: "healthy", version: serverVersion, frameworkDir: FRAMEWORK_DIR });
499
+ return;
500
+ }
501
+ // Status
502
+ if (pathname === "/api/status") {
503
+ const statusPath = path.join(FRAMEWORK_DIR, "memory", "status.json");
504
+ if (fs.existsSync(statusPath)) {
505
+ serveFile(res, statusPath, "application/json");
506
+ return;
507
+ }
508
+ serveJson(res, 200, { success: true, data: { status: "initializing" } });
509
+ return;
510
+ }
511
+ // Memory (PROJECT_MEMORY.md)
512
+ if (pathname === "/api/memory") {
513
+ const memoryPath = path.join(FRAMEWORK_DIR, "memory", "PROJECT_MEMORY.md");
514
+ if (fs.existsSync(memoryPath)) {
515
+ serveFile(res, memoryPath, "text/markdown");
516
+ return;
517
+ }
518
+ serveJson(res, 200, { success: true, data: "No memory file yet." });
519
+ return;
520
+ }
521
+ // Memory Search
522
+ if (pathname === "/api/memory/search") {
523
+ try {
524
+ const query = params.q || "";
525
+ const limit = parseInt(params.limit || "10", 10);
526
+ if (!query) {
527
+ const db = Storage.getDB();
528
+ const rows = db.prepare("SELECT * FROM vector_memory ORDER BY created_at DESC LIMIT ?").all(limit);
529
+ // [KVKK/GDPR] Mask PII in memory content before returning
530
+ const results = rows.map(row => {
531
+ const meta = JSON.parse(row.metadata || "{}");
532
+ return { id: row.id, content: maskText(row.content), type: row.category || meta.category || "memory", timestamp: meta.createdAt || row.created_at, relevance: 1.0 };
533
+ });
534
+ serveJson(res, 200, { success: true, data: results });
535
+ return;
536
+ }
537
+ const { CoreMemory } = await import("../../src/modules/memory/core.js");
538
+ const results = await CoreMemory.recall(query, { limit });
539
+ // [KVKK/GDPR] Mask PII in memory content before returning
540
+ const flattened = results.map(r => ({
541
+ id: r.entry.id, content: maskText(r.entry.content), type: r.entry.metadata.category || "memory",
542
+ timestamp: r.entry.metadata.createdAt || new Date().toISOString(), relevance: r.score
543
+ }));
544
+ serveJson(res, 200, { success: true, data: flattened });
545
+ }
546
+ catch (e) {
547
+ serveJson(res, 500, { success: false, error: e.message });
548
+ }
549
+ return;
550
+ }
551
+ // Agents
552
+ if (pathname === "/api/agents") {
553
+ try {
554
+ const agents = Storage.getAllAgents();
555
+ serveJson(res, 200, { success: true, data: agents });
556
+ }
557
+ catch (e) {
558
+ serveJson(res, 500, { success: false, error: e.message });
559
+ }
560
+ return;
561
+ }
562
+ // Messages (Hermes queue)
563
+ if (pathname === "/api/messages") {
564
+ try {
565
+ const messages = Storage.getPendingMessages();
566
+ serveJson(res, 200, { success: true, data: messages });
567
+ }
568
+ catch (e) {
569
+ serveJson(res, 500, { success: false, error: e.message });
570
+ }
571
+ return;
572
+ }
573
+ // Hermes Stats
574
+ if (pathname === "/api/hermes/stats") {
575
+ try {
576
+ const messages = Storage.getPendingMessages();
577
+ const totalMessages = Storage.getDB().prepare("SELECT COUNT(*) as count FROM messages").get();
578
+ const byCategory = {};
579
+ const byStatus = {};
580
+ messages.forEach(m => {
581
+ byCategory[m.category] = (byCategory[m.category] || 0) + 1;
582
+ byStatus[m.status] = (byStatus[m.status] || 0) + 1;
583
+ });
584
+ serveJson(res, 200, {
585
+ success: true, data: {
586
+ total: totalMessages.count, pending: messages.length, byCategory, byStatus,
587
+ lastMessage: messages.length > 0 ? messages[messages.length - 1] : null,
588
+ }
589
+ });
590
+ }
591
+ catch (e) {
592
+ serveJson(res, 500, { success: false, error: e.message });
593
+ }
594
+ return;
595
+ }
596
+ // Tasks
597
+ if (pathname === "/api/tasks") {
598
+ try {
599
+ const { asTraceID } = await import("../../src/shared/types.js");
600
+ const traceId = params.traceId ? asTraceID(params.traceId) : undefined;
601
+ const tasks = Storage.getTasks(traceId);
602
+ // [KVKK/GDPR] Mask PII in task descriptions before returning
603
+ const maskedTasks = tasks.map(t => ({
604
+ ...t,
605
+ description: maskText(t.description),
606
+ }));
607
+ serveJson(res, 200, { success: true, data: maskedTasks });
608
+ }
609
+ catch (e) {
610
+ serveJson(res, 500, { success: false, error: e.message });
611
+ }
612
+ return;
613
+ }
614
+ // Logs
615
+ if (pathname.startsWith("/api/logs")) {
616
+ try {
617
+ const logs = Storage.getLogs();
618
+ const agent = pathname.split("/api/logs/")[1];
619
+ const filteredLogs = agent ? logs.filter(l => l.agent === agent) : logs;
620
+ serveJson(res, 200, { success: true, data: filteredLogs });
621
+ }
622
+ catch (e) {
623
+ serveJson(res, 500, { success: false, error: e.message });
624
+ }
625
+ return;
626
+ }
627
+ // Approvals
628
+ if (pathname === "/api/approvals") {
629
+ try {
630
+ const allMessages = Storage.getPendingMessages();
631
+ // [KVKK/GDPR] Mask PII in approval descriptions
632
+ const approvals = allMessages.filter(m => m.category === "ALERT" || m.category === "ACTION")
633
+ .map(m => ({
634
+ id: String(m.id || m.traceId), traceId: m.traceId,
635
+ description: m.content ? (typeof m.content === "string" ? maskText(m.content) : maskText(JSON.stringify(m.content))) : "Approval required",
636
+ status: m.status || "PENDING", timestamp: m.timestamp || new Date().toISOString()
637
+ }));
638
+ serveJson(res, 200, { success: true, data: approvals });
639
+ }
640
+ catch (e) {
641
+ serveJson(res, 500, { success: false, error: e.message });
642
+ }
643
+ return;
644
+ }
645
+ // Approve
646
+ if (pathname.startsWith("/api/approve/") && method === "POST") {
647
+ const traceId = pathname.replace("/api/approve/", "");
648
+ if (!traceId) {
649
+ serveJson(res, 400, { success: false, error: "Trace ID required" });
650
+ return;
651
+ }
652
+ try {
653
+ const pendingMessages = Storage.getPendingMessages().filter(m => m.traceId === traceId && (m.category === "ALERT" || m.category === "ACTION"));
654
+ if (pendingMessages.length === 0) {
655
+ serveJson(res, 404, { success: false, error: "No pending approval found" });
656
+ return;
657
+ }
658
+ pendingMessages.forEach(msg => { if (msg.id !== undefined)
659
+ Storage.updateMessageStatus(msg.id, "APPROVED"); });
660
+ broadcastWS("approval", { traceId, status: "APPROVED" });
661
+ serveJson(res, 200, { success: true, message: `Trace ${traceId} approved`, approved: pendingMessages.length });
662
+ }
663
+ catch (e) {
664
+ serveJson(res, 500, { success: false, error: e.message });
665
+ }
666
+ return;
667
+ }
668
+ // Reject
669
+ if (pathname.startsWith("/api/reject/") && method === "POST") {
670
+ const traceId = pathname.replace("/api/reject/", "");
671
+ if (!traceId) {
672
+ serveJson(res, 400, { success: false, error: "Trace ID required" });
673
+ return;
674
+ }
675
+ try {
676
+ const pendingMessages = Storage.getPendingMessages().filter(m => m.traceId === traceId && (m.category === "ALERT" || m.category === "ACTION"));
677
+ if (pendingMessages.length === 0) {
678
+ serveJson(res, 404, { success: false, error: "No pending approval found" });
679
+ return;
680
+ }
681
+ pendingMessages.forEach(msg => { if (msg.id !== undefined)
682
+ Storage.updateMessageStatus(msg.id, "REJECTED"); });
683
+ broadcastWS("approval", { traceId, status: "REJECTED" });
684
+ serveJson(res, 200, { success: true, message: `Trace ${traceId} rejected`, rejected: pendingMessages.length });
685
+ }
686
+ catch (e) {
687
+ serveJson(res, 500, { success: false, error: e.message });
688
+ }
689
+ return;
690
+ }
691
+ // Audit (GDPR/KVKK)
692
+ if (pathname === "/api/audit") {
693
+ try {
694
+ Audit.initialize();
695
+ const stats = Audit.getStats();
696
+ const entries = Audit.query({ limit: 100 });
697
+ serveJson(res, 200, { success: true, data: { stats, entries } });
698
+ }
699
+ catch (e) {
700
+ serveJson(res, 500, { success: false, error: e.message });
701
+ }
702
+ return;
703
+ }
704
+ // Audit Erase (GDPR/KVKK Right to Erasure)
705
+ if (pathname === "/api/audit/erase" && method === "POST") {
706
+ try {
707
+ Audit.initialize();
708
+ let body = "";
709
+ req.on("data", chunk => { body += chunk.toString(); });
710
+ req.on("end", () => {
711
+ try {
712
+ const parsed = JSON.parse(body);
713
+ const confirmation = parsed.confirmationCode || "";
714
+ const changes = Audit.clearAll(confirmation);
715
+ serveJson(res, 200, { success: true, message: `${changes} records cleared under KVKK/GDPR right to erasure.` });
716
+ }
717
+ catch (err) {
718
+ serveJson(res, 400, { success: false, error: err.message });
719
+ }
720
+ });
721
+ }
722
+ catch (e) {
723
+ serveJson(res, 500, { success: false, error: e.message });
724
+ }
725
+ return;
726
+ }
727
+ // Compliance
728
+ if (pathname === "/api/compliance") {
729
+ try {
730
+ const { scanProjectCompliance } = await import("../../src/cli/utils/compliance.js");
731
+ const targetPath = params.path || "src";
732
+ const scanPath = path.join(PROJECT_ROOT, targetPath);
733
+ const rawIssues = scanProjectCompliance(scanPath);
734
+ const violations = rawIssues.map(issue => {
735
+ const ruleLower = issue.rule.toLowerCase();
736
+ const type = ruleLower.includes("any") ? "any-type" : ruleLower.includes("console") ? "console-log" : "other";
737
+ return { file: path.relative(PROJECT_ROOT, issue.file), line: issue.line, type, message: issue.rule.replace(/^\[ERROR\]\s*Corporate\s*Compliance\s*Breach:\s*/i, "") };
738
+ });
739
+ const violatingFiles = new Set(violations.map(v => v.file));
740
+ serveJson(res, 200, {
741
+ success: true, data: {
742
+ summary: { totalFiles: violatingFiles.size, totalViolations: violations.length, byType: { "any-type": violations.filter(v => v.type === "any-type").length, "console-log": violations.filter(v => v.type === "console-log").length } },
743
+ violations
744
+ }
745
+ });
746
+ }
747
+ catch (e) {
748
+ serveJson(res, 500, { success: false, error: e.message });
749
+ }
750
+ return;
751
+ }
752
+ // Quality
753
+ if (pathname === "/api/quality") {
754
+ try {
755
+ const { analyzePathQuality } = await import("../../src/cli/utils/quality.js");
756
+ const targetPath = params.path || "src";
757
+ const result = analyzePathQuality(PROJECT_ROOT, targetPath);
758
+ serveJson(res, 200, {
759
+ success: true, data: {
760
+ totalFiles: result.totalFiles, totalIssues: result.totalIssues,
761
+ longFunctions: result.longFunctions, deepNesting: result.deepNesting,
762
+ anyTypes: result.anyTypes, issues: result.issues.slice(0, 50),
763
+ }
764
+ });
765
+ }
766
+ catch (e) {
767
+ serveJson(res, 500, { success: false, error: e.message });
768
+ }
769
+ return;
770
+ }
771
+ // Adapter Skills
772
+ if (pathname === "/api/adapters/skills") {
773
+ try {
774
+ const { ADAPTER_SKILLS } = await import("../../src/modules/skills/adapter-skills.js");
775
+ const summary = Object.entries(ADAPTER_SKILLS).map(([id, config]) => ({
776
+ id, skillCount: Object.keys(config.enabledSkills).length,
777
+ toolsCount: config.toolMapping ? Object.keys(config.toolMapping).length : 0,
778
+ skills: Object.keys(config.enabledSkills),
779
+ }));
780
+ serveJson(res, 200, { success: true, data: summary });
781
+ }
782
+ catch (e) {
783
+ serveJson(res, 500, { success: false, error: e.message });
784
+ }
785
+ return;
786
+ }
787
+ // MCP Sessions
788
+ if (pathname === "/api/mcp/sessions") {
789
+ const sessionList = Array.from(sessions.keys()).map(id => ({ id, connectedAt: new Date().toISOString() }));
790
+ serveJson(res, 200, { success: true, data: { total: sessions.size, sessions: sessionList } });
791
+ return;
792
+ }
793
+ // Discipline Stats
794
+ if (pathname === "/api/discipline") {
795
+ try {
796
+ const { getAllDisciplineStats } = await import("./utils/discipline.js");
797
+ const stats = getAllDisciplineStats();
798
+ serveJson(res, 200, { success: true, data: stats });
799
+ }
800
+ catch (e) {
801
+ serveJson(res, 500, { success: false, error: e.message });
802
+ }
803
+ return;
804
+ }
805
+ // Token Economy / Metrics
806
+ if (pathname === "/api/metrics") {
807
+ try {
808
+ const metricsPath = path.join(FRAMEWORK_DIR, "observability/metrics.json");
809
+ let entries = [];
810
+ if (fs.existsSync(metricsPath)) {
811
+ entries = JSON.parse(fs.readFileSync(metricsPath, "utf8"));
812
+ }
813
+ const byAgent = {};
814
+ const byAction = {};
815
+ let totalTokens = 0;
816
+ for (const entry of entries) {
817
+ const agent = entry.agent || "unknown";
818
+ const action = entry.action || "unknown";
819
+ const tokens = entry.estimatedTokens || 0;
820
+ if (!byAgent[agent])
821
+ byAgent[agent] = { calls: 0, tokens: 0, cost: 0 };
822
+ byAgent[agent].calls++;
823
+ byAgent[agent].tokens += tokens;
824
+ byAgent[agent].cost = +(byAgent[agent].tokens / 1000 * 0.003).toFixed(4);
825
+ if (!byAction[action])
826
+ byAction[action] = { calls: 0, tokens: 0 };
827
+ byAction[action].calls++;
828
+ byAction[action].tokens += tokens;
829
+ totalTokens += tokens;
830
+ }
831
+ serveJson(res, 200, {
832
+ success: true, data: {
833
+ totalToolCalls: entries.length, totalEstimatedTokens: totalTokens,
834
+ totalEstimatedCost: +(totalTokens / 1000 * 0.003).toFixed(4),
835
+ mcpSessions: sessions.size, byAgent, byAction, recentEntries: entries.slice(-100),
836
+ }
837
+ });
838
+ }
839
+ catch (e) {
840
+ serveJson(res, 500, { success: false, error: e.message });
841
+ }
842
+ return;
843
+ }
844
+ // Telemetry Status
845
+ if (pathname === "/api/telemetry") {
846
+ try {
847
+ const { telemetryStreamer, TelemetryConfig } = await import("./utils/telemetry-streamer.js");
848
+ const status = telemetryStreamer.getStatus();
849
+ serveJson(res, 200, { success: true, data: { ...status, config: { ...TelemetryConfig, AUTH_TOKEN: TelemetryConfig.AUTH_TOKEN ? "***" : "" } } });
850
+ }
851
+ catch (e) {
852
+ serveJson(res, 500, { success: false, error: e.message });
853
+ }
854
+ return;
855
+ }
856
+ // Loop Detection Stats
857
+ if (pathname === "/api/loop-detector") {
858
+ try {
859
+ const { getAllLoopStats } = await import("./utils/loop-detector.js");
860
+ const agent = params.agent;
861
+ const stats = agent
862
+ ? (await import("./utils/loop-detector.js")).getLoopStats(agent)
863
+ : getAllLoopStats();
864
+ serveJson(res, 200, { success: true, data: stats });
865
+ }
866
+ catch (e) {
867
+ serveJson(res, 500, { success: false, error: e.message });
868
+ }
869
+ return;
870
+ }
871
+ // Clear Loop Cooldown
872
+ if (pathname.startsWith("/api/loop-detector/clear/") && method === "POST") {
873
+ const agent = decodeURIComponent(pathname.replace("/api/loop-detector/clear/", ""));
874
+ try {
875
+ const { clearCooldown } = await import("./utils/loop-detector.js");
876
+ const cleared = clearCooldown(agent);
877
+ serveJson(res, 200, { success: true, data: { agent, cleared } });
878
+ }
879
+ catch (e) {
880
+ serveJson(res, 500, { success: false, error: e.message });
881
+ }
882
+ return;
883
+ }
884
+ // Budget / FinOps Status
885
+ if (pathname === "/api/finops") {
886
+ try {
887
+ const { budgetManager } = await import("./utils/finops.js");
888
+ const state = budgetManager.getState();
889
+ serveJson(res, 200, { success: true, data: state });
890
+ }
891
+ catch (e) {
892
+ serveJson(res, 500, { success: false, error: e.message });
893
+ }
894
+ return;
895
+ }
896
+ // Budget Check for an agent
897
+ if (pathname === "/api/finops/check") {
898
+ try {
899
+ const agent = params.agent || "default";
900
+ const { budgetManager } = await import("./utils/finops.js");
901
+ const result = budgetManager.checkBudget(agent);
902
+ serveJson(res, 200, { success: true, data: result });
903
+ }
904
+ catch (e) {
905
+ serveJson(res, 500, { success: false, error: e.message });
906
+ }
907
+ return;
908
+ }
909
+ // Budget Reset
910
+ if (pathname === "/api/finops/reset" && method === "POST") {
911
+ try {
912
+ const { budgetManager } = await import("./utils/finops.js");
913
+ budgetManager.resetPeriod();
914
+ serveJson(res, 200, { success: true, message: "Budget period reset." });
915
+ }
916
+ catch (e) {
917
+ serveJson(res, 500, { success: false, error: e.message });
918
+ }
919
+ return;
920
+ }
921
+ // License Scanner
922
+ if (pathname === "/api/license") {
923
+ try {
924
+ const { scanForLicenses, getLicenseSeveritySummary, LicenseScannerConfig } = await import("./utils/license-scanner.js");
925
+ const filePath = params.path || "";
926
+ const content = params.content || "";
927
+ const matches = filePath && content ? scanForLicenses(filePath, content) : [];
928
+ const summary = matches.length > 0 ? getLicenseSeveritySummary(matches) : null;
929
+ serveJson(res, 200, {
930
+ success: true, data: {
931
+ matches,
932
+ summary,
933
+ config: LicenseScannerConfig,
934
+ }
935
+ });
936
+ }
937
+ catch (e) {
938
+ serveJson(res, 500, { success: false, error: e.message });
939
+ }
940
+ return;
941
+ }
942
+ // Auto-Rollback Stats
943
+ if (pathname === "/api/rollback") {
944
+ try {
945
+ const { AutoRollbackEngine } = await import("./utils/auto-rollback.js");
946
+ const stats = AutoRollbackEngine.getSnapshotStats();
947
+ serveJson(res, 200, { success: true, data: stats });
948
+ }
949
+ catch (e) {
950
+ serveJson(res, 500, { success: false, error: e.message });
951
+ }
952
+ return;
953
+ }
954
+ // All Governance Stats (combined endpoint)
955
+ if (pathname === "/api/governance") {
956
+ try {
957
+ const { getAllDisciplineStats } = await import("./utils/discipline.js");
958
+ const { getAllTokenBudgetStats } = await import("./utils/context-optimizer.js");
959
+ const { getAllLoopStats } = await import("./utils/loop-detector.js");
960
+ const { getAllRules } = await import("./utils/rules-engine.js");
961
+ const { AutoRollbackEngine } = await import("./utils/auto-rollback.js");
962
+ const { budgetManager } = await import("./utils/finops.js");
963
+ serveJson(res, 200, {
964
+ success: true, data: {
965
+ discipline: getAllDisciplineStats(),
966
+ tokenBudget: getAllTokenBudgetStats(),
967
+ loopDetection: getAllLoopStats(),
968
+ rules: getAllRules().map(r => ({ id: r.id, name: r.name, priority: r.priority, bypassable: r.bypassable })),
969
+ rollback: AutoRollbackEngine.getSnapshotStats(),
970
+ budget: budgetManager.getState(),
971
+ telemetry: (await import("./utils/telemetry-streamer.js")).telemetryStreamer.getStatus(),
972
+ }
973
+ });
974
+ }
975
+ catch (e) {
976
+ serveJson(res, 500, { success: false, error: e.message });
977
+ }
978
+ return;
979
+ }
980
+ // SSE (legacy support)
981
+ if (pathname === "/api/events") {
982
+ res.writeHead(200, { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", "Connection": "keep-alive" });
983
+ res.write(": connected\n\n");
984
+ const keepAlive = setInterval(() => res.write(": keepalive\n\n"), 30000);
985
+ req.on("close", () => clearInterval(keepAlive));
986
+ return;
987
+ }
988
+ // ───────────── STATIC UI ─────────────────────────────
989
+ const uiPath = path.join(UI_DIST_PATH, pathname === "/" ? "index.html" : pathname);
990
+ const finalPath = (fs.existsSync(uiPath) && !fs.statSync(uiPath).isDirectory())
991
+ ? uiPath
992
+ : path.join(UI_DIST_PATH, "index.html");
993
+ if (fs.existsSync(finalPath) && !fs.statSync(finalPath).isDirectory()) {
994
+ const ext = path.extname(finalPath).toLowerCase();
995
+ const mimeTypes = {
996
+ ".html": "text/html", ".js": "application/javascript", ".css": "text/css",
997
+ ".json": "application/json", ".png": "image/png", ".jpg": "image/jpg",
998
+ ".gif": "image/gif", ".svg": "image/svg+xml", ".ico": "image/x-icon",
999
+ };
1000
+ serveFile(res, finalPath, mimeTypes[ext] || "application/octet-stream");
1001
+ }
1002
+ else {
1003
+ serveJson(res, 404, { error: "Not Found" });
1004
+ }
1005
+ });
1006
+ // ─── WebSocket Server ────────────────────────────────────
1007
+ const wss = new WebSocketServer({ server: httpServer, path: "/ws" });
1008
+ wss.on("connection", (ws) => {
1009
+ wsClients.add(ws);
1010
+ ws.send(JSON.stringify({ type: "mcp_session", payload: { total: sessions.size, sessions: Array.from(sessions.keys()) } }));
1011
+ ws.on("close", () => wsClients.delete(ws));
1012
+ ws.on("message", (data) => {
1013
+ try {
1014
+ const msg = JSON.parse(data.toString());
1015
+ if (msg.type === "ping")
1016
+ ws.send(JSON.stringify({ type: "pong" }));
1017
+ }
1018
+ catch { /* ignore */ }
1019
+ });
1020
+ });
1021
+ // ─── Periodic Broadcast ──────────────────────────────────
1022
+ const broadcastInterval = setInterval(() => {
1023
+ try {
1024
+ broadcastWS("mcp_heartbeat", { sessions: sessions.size, timestamp: new Date().toISOString() });
1025
+ }
1026
+ catch { /* silent */ }
1027
+ }, 10000);
1028
+ httpServer.on("close", () => {
1029
+ clearInterval(broadcastInterval);
1030
+ wsClients.clear();
1031
+ });
1032
+ // ─── Start ───────────────────────────────────────────────
1033
+ httpServer.listen(PORT, HOST, () => {
1034
+ process.stderr.write("\n");
1035
+ process.stderr.write("╔══════════════════════════════════════════════════════╗\n");
1036
+ process.stderr.write("║ ATABEY UNIFIED SERVER ║\n");
1037
+ process.stderr.write("╠══════════════════════════════════════════════════════╣\n");
1038
+ process.stderr.write(`║ Dashboard: http://localhost:${PORT} \n`);
1039
+ process.stderr.write(`║ MCP SSE: http://localhost:${PORT}/mcp/sse \n`);
1040
+ process.stderr.write(`║ WS Live: ws://localhost:${PORT}/ws \n`);
1041
+ process.stderr.write(`║ API: http://localhost:${PORT}/api/health \n`);
1042
+ process.stderr.write(`║ Sessions: ${sessions.size} active \n`);
1043
+ process.stderr.write("╚══════════════════════════════════════════════════════╝\n");
1044
+ process.stderr.write("\n");
1045
+ process.stderr.write(`[atabey-mcp] Unified server ready on port ${PORT}\n`);
1046
+ process.stderr.write(`[atabey-mcp] mcp.json: { "url": "http://localhost:${PORT}/mcp/sse" }\n`);
1047
+ process.stderr.write("\n");
1048
+ });
1049
+ }
1050
+ // ─── Startup ──────────────────────────────────────────────────────
126
1051
  async function run() {
127
- const transport = new StdioServerTransport();
128
- // Prevent unhandled errors from crashing the MCP stream
1052
+ const transportMode = process.env.MCP_TRANSPORT || "unified";
129
1053
  process.on("uncaughtException", (error) => {
130
- process.stderr.write(`[atabey-mcp] Uncaught exception: ${error.message}
131
- `);
1054
+ process.stderr.write(`[atabey-mcp] Uncaught exception: ${error.message}\n`);
132
1055
  });
133
1056
  process.on("unhandledRejection", (reason) => {
134
1057
  const message = reason instanceof Error ? reason.message : String(reason);
135
- process.stderr.write(`[atabey-mcp] Unhandled rejection: ${message}
136
- `);
1058
+ process.stderr.write(`[atabey-mcp] Unhandled rejection: ${message}\n`);
137
1059
  });
138
- // Graceful shutdown on SIGINT/SIGTERM
139
1060
  const shutdown = async () => {
140
1061
  try {
141
1062
  await server.close();
142
1063
  }
143
- catch {
144
- // Already closed or failed — safe to ignore
145
- }
1064
+ catch { /* ignore */ }
146
1065
  process.exit(0);
147
1066
  };
148
1067
  process.on("SIGINT", shutdown);
149
1068
  process.on("SIGTERM", shutdown);
150
- await server.connect(transport);
1069
+ if (transportMode === "stdio") {
1070
+ const transport = new StdioServerTransport();
1071
+ process.stderr.write("[atabey-mcp] Stdio mode (single client).\n");
1072
+ await server.connect(transport);
1073
+ }
1074
+ else {
1075
+ createUnifiedServer();
1076
+ }
151
1077
  }
152
1078
  run().catch((error) => {
153
- process.stderr.write(`[atabey-mcp] Fatal startup error: ${error.message}
154
- `);
1079
+ process.stderr.write(`[atabey-mcp] Fatal startup error: ${error.message}\n`);
155
1080
  process.exit(1);
156
1081
  });
157
1082
  //# sourceMappingURL=index.js.map