palaryn 0.1.0

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 (607) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +716 -0
  3. package/dist/sdk/typescript/src/client.d.ts +71 -0
  4. package/dist/sdk/typescript/src/client.d.ts.map +1 -0
  5. package/dist/sdk/typescript/src/client.js +176 -0
  6. package/dist/sdk/typescript/src/client.js.map +1 -0
  7. package/dist/sdk/typescript/src/errors.d.ts +50 -0
  8. package/dist/sdk/typescript/src/errors.d.ts.map +1 -0
  9. package/dist/sdk/typescript/src/errors.js +103 -0
  10. package/dist/sdk/typescript/src/errors.js.map +1 -0
  11. package/dist/sdk/typescript/src/index.d.ts +4 -0
  12. package/dist/sdk/typescript/src/index.d.ts.map +1 -0
  13. package/dist/sdk/typescript/src/index.js +15 -0
  14. package/dist/sdk/typescript/src/index.js.map +1 -0
  15. package/dist/sdk/typescript/src/types.d.ts +101 -0
  16. package/dist/sdk/typescript/src/types.d.ts.map +1 -0
  17. package/dist/sdk/typescript/src/types.js +6 -0
  18. package/dist/sdk/typescript/src/types.js.map +1 -0
  19. package/dist/src/admin/index.d.ts +2 -0
  20. package/dist/src/admin/index.d.ts.map +1 -0
  21. package/dist/src/admin/index.js +6 -0
  22. package/dist/src/admin/index.js.map +1 -0
  23. package/dist/src/admin/routes.d.ts +5 -0
  24. package/dist/src/admin/routes.d.ts.map +1 -0
  25. package/dist/src/admin/routes.js +471 -0
  26. package/dist/src/admin/routes.js.map +1 -0
  27. package/dist/src/admin/templates.d.ts +51 -0
  28. package/dist/src/admin/templates.d.ts.map +1 -0
  29. package/dist/src/admin/templates.js +500 -0
  30. package/dist/src/admin/templates.js.map +1 -0
  31. package/dist/src/anomaly/detector.d.ts +141 -0
  32. package/dist/src/anomaly/detector.d.ts.map +1 -0
  33. package/dist/src/anomaly/detector.js +554 -0
  34. package/dist/src/anomaly/detector.js.map +1 -0
  35. package/dist/src/anomaly/index.d.ts +2 -0
  36. package/dist/src/anomaly/index.d.ts.map +1 -0
  37. package/dist/src/anomaly/index.js +7 -0
  38. package/dist/src/anomaly/index.js.map +1 -0
  39. package/dist/src/approval/manager.d.ts +147 -0
  40. package/dist/src/approval/manager.d.ts.map +1 -0
  41. package/dist/src/approval/manager.js +511 -0
  42. package/dist/src/approval/manager.js.map +1 -0
  43. package/dist/src/approval/webhook.d.ts +36 -0
  44. package/dist/src/approval/webhook.d.ts.map +1 -0
  45. package/dist/src/approval/webhook.js +135 -0
  46. package/dist/src/approval/webhook.js.map +1 -0
  47. package/dist/src/audit/logger.d.ts +70 -0
  48. package/dist/src/audit/logger.d.ts.map +1 -0
  49. package/dist/src/audit/logger.js +440 -0
  50. package/dist/src/audit/logger.js.map +1 -0
  51. package/dist/src/auth/index.d.ts +6 -0
  52. package/dist/src/auth/index.d.ts.map +1 -0
  53. package/dist/src/auth/index.js +22 -0
  54. package/dist/src/auth/index.js.map +1 -0
  55. package/dist/src/auth/password.d.ts +3 -0
  56. package/dist/src/auth/password.d.ts.map +1 -0
  57. package/dist/src/auth/password.js +25 -0
  58. package/dist/src/auth/password.js.map +1 -0
  59. package/dist/src/auth/pkce.d.ts +13 -0
  60. package/dist/src/auth/pkce.d.ts.map +1 -0
  61. package/dist/src/auth/pkce.js +58 -0
  62. package/dist/src/auth/pkce.js.map +1 -0
  63. package/dist/src/auth/providers.d.ts +28 -0
  64. package/dist/src/auth/providers.d.ts.map +1 -0
  65. package/dist/src/auth/providers.js +198 -0
  66. package/dist/src/auth/providers.js.map +1 -0
  67. package/dist/src/auth/routes.d.ts +14 -0
  68. package/dist/src/auth/routes.d.ts.map +1 -0
  69. package/dist/src/auth/routes.js +431 -0
  70. package/dist/src/auth/routes.js.map +1 -0
  71. package/dist/src/auth/session.d.ts +24 -0
  72. package/dist/src/auth/session.d.ts.map +1 -0
  73. package/dist/src/auth/session.js +105 -0
  74. package/dist/src/auth/session.js.map +1 -0
  75. package/dist/src/billing/index.d.ts +7 -0
  76. package/dist/src/billing/index.d.ts.map +1 -0
  77. package/dist/src/billing/index.js +14 -0
  78. package/dist/src/billing/index.js.map +1 -0
  79. package/dist/src/billing/plan-enforcer.d.ts +44 -0
  80. package/dist/src/billing/plan-enforcer.d.ts.map +1 -0
  81. package/dist/src/billing/plan-enforcer.js +110 -0
  82. package/dist/src/billing/plan-enforcer.js.map +1 -0
  83. package/dist/src/billing/routes.d.ts +15 -0
  84. package/dist/src/billing/routes.d.ts.map +1 -0
  85. package/dist/src/billing/routes.js +193 -0
  86. package/dist/src/billing/routes.js.map +1 -0
  87. package/dist/src/billing/stripe-client.d.ts +14 -0
  88. package/dist/src/billing/stripe-client.d.ts.map +1 -0
  89. package/dist/src/billing/stripe-client.js +51 -0
  90. package/dist/src/billing/stripe-client.js.map +1 -0
  91. package/dist/src/billing/webhook-handler.d.ts +19 -0
  92. package/dist/src/billing/webhook-handler.d.ts.map +1 -0
  93. package/dist/src/billing/webhook-handler.js +169 -0
  94. package/dist/src/billing/webhook-handler.js.map +1 -0
  95. package/dist/src/billing/webhook-routes.d.ts +5 -0
  96. package/dist/src/billing/webhook-routes.d.ts.map +1 -0
  97. package/dist/src/billing/webhook-routes.js +30 -0
  98. package/dist/src/billing/webhook-routes.js.map +1 -0
  99. package/dist/src/budget/manager.d.ts +95 -0
  100. package/dist/src/budget/manager.d.ts.map +1 -0
  101. package/dist/src/budget/manager.js +547 -0
  102. package/dist/src/budget/manager.js.map +1 -0
  103. package/dist/src/budget/usage-extractor.d.ts +38 -0
  104. package/dist/src/budget/usage-extractor.d.ts.map +1 -0
  105. package/dist/src/budget/usage-extractor.js +165 -0
  106. package/dist/src/budget/usage-extractor.js.map +1 -0
  107. package/dist/src/cli.d.ts +3 -0
  108. package/dist/src/cli.d.ts.map +1 -0
  109. package/dist/src/cli.js +115 -0
  110. package/dist/src/cli.js.map +1 -0
  111. package/dist/src/config/defaults.d.ts +3 -0
  112. package/dist/src/config/defaults.d.ts.map +1 -0
  113. package/dist/src/config/defaults.js +243 -0
  114. package/dist/src/config/defaults.js.map +1 -0
  115. package/dist/src/config/validate.d.ts +15 -0
  116. package/dist/src/config/validate.d.ts.map +1 -0
  117. package/dist/src/config/validate.js +105 -0
  118. package/dist/src/config/validate.js.map +1 -0
  119. package/dist/src/dlp/composite-scanner.d.ts +47 -0
  120. package/dist/src/dlp/composite-scanner.d.ts.map +1 -0
  121. package/dist/src/dlp/composite-scanner.js +186 -0
  122. package/dist/src/dlp/composite-scanner.js.map +1 -0
  123. package/dist/src/dlp/index.d.ts +10 -0
  124. package/dist/src/dlp/index.d.ts.map +1 -0
  125. package/dist/src/dlp/index.js +26 -0
  126. package/dist/src/dlp/index.js.map +1 -0
  127. package/dist/src/dlp/interfaces.d.ts +33 -0
  128. package/dist/src/dlp/interfaces.d.ts.map +1 -0
  129. package/dist/src/dlp/interfaces.js +3 -0
  130. package/dist/src/dlp/interfaces.js.map +1 -0
  131. package/dist/src/dlp/patterns.d.ts +9 -0
  132. package/dist/src/dlp/patterns.d.ts.map +1 -0
  133. package/dist/src/dlp/patterns.js +25 -0
  134. package/dist/src/dlp/patterns.js.map +1 -0
  135. package/dist/src/dlp/prompt-injection-backend.d.ts +68 -0
  136. package/dist/src/dlp/prompt-injection-backend.d.ts.map +1 -0
  137. package/dist/src/dlp/prompt-injection-backend.js +148 -0
  138. package/dist/src/dlp/prompt-injection-backend.js.map +1 -0
  139. package/dist/src/dlp/prompt-injection-patterns.d.ts +32 -0
  140. package/dist/src/dlp/prompt-injection-patterns.d.ts.map +1 -0
  141. package/dist/src/dlp/prompt-injection-patterns.js +290 -0
  142. package/dist/src/dlp/prompt-injection-patterns.js.map +1 -0
  143. package/dist/src/dlp/regex-backend.d.ts +32 -0
  144. package/dist/src/dlp/regex-backend.d.ts.map +1 -0
  145. package/dist/src/dlp/regex-backend.js +153 -0
  146. package/dist/src/dlp/regex-backend.js.map +1 -0
  147. package/dist/src/dlp/scanner.d.ts +122 -0
  148. package/dist/src/dlp/scanner.d.ts.map +1 -0
  149. package/dist/src/dlp/scanner.js +444 -0
  150. package/dist/src/dlp/scanner.js.map +1 -0
  151. package/dist/src/dlp/text-normalizer.d.ts +41 -0
  152. package/dist/src/dlp/text-normalizer.d.ts.map +1 -0
  153. package/dist/src/dlp/text-normalizer.js +203 -0
  154. package/dist/src/dlp/text-normalizer.js.map +1 -0
  155. package/dist/src/dlp/trufflehog-backend.d.ts +64 -0
  156. package/dist/src/dlp/trufflehog-backend.d.ts.map +1 -0
  157. package/dist/src/dlp/trufflehog-backend.js +151 -0
  158. package/dist/src/dlp/trufflehog-backend.js.map +1 -0
  159. package/dist/src/executor/http-executor.d.ts +25 -0
  160. package/dist/src/executor/http-executor.d.ts.map +1 -0
  161. package/dist/src/executor/http-executor.js +333 -0
  162. package/dist/src/executor/http-executor.js.map +1 -0
  163. package/dist/src/executor/index.d.ts +6 -0
  164. package/dist/src/executor/index.d.ts.map +1 -0
  165. package/dist/src/executor/index.js +12 -0
  166. package/dist/src/executor/index.js.map +1 -0
  167. package/dist/src/executor/interfaces.d.ts +11 -0
  168. package/dist/src/executor/interfaces.d.ts.map +1 -0
  169. package/dist/src/executor/interfaces.js +3 -0
  170. package/dist/src/executor/interfaces.js.map +1 -0
  171. package/dist/src/executor/noop-executor.d.ts +13 -0
  172. package/dist/src/executor/noop-executor.d.ts.map +1 -0
  173. package/dist/src/executor/noop-executor.js +21 -0
  174. package/dist/src/executor/noop-executor.js.map +1 -0
  175. package/dist/src/executor/registry.d.ts +30 -0
  176. package/dist/src/executor/registry.d.ts.map +1 -0
  177. package/dist/src/executor/registry.js +62 -0
  178. package/dist/src/executor/registry.js.map +1 -0
  179. package/dist/src/executor/slack-executor.d.ts +24 -0
  180. package/dist/src/executor/slack-executor.d.ts.map +1 -0
  181. package/dist/src/executor/slack-executor.js +147 -0
  182. package/dist/src/executor/slack-executor.js.map +1 -0
  183. package/dist/src/index.d.ts +25 -0
  184. package/dist/src/index.d.ts.map +1 -0
  185. package/dist/src/index.js +74 -0
  186. package/dist/src/index.js.map +1 -0
  187. package/dist/src/mcp/auth-verifier.d.ts +23 -0
  188. package/dist/src/mcp/auth-verifier.d.ts.map +1 -0
  189. package/dist/src/mcp/auth-verifier.js +162 -0
  190. package/dist/src/mcp/auth-verifier.js.map +1 -0
  191. package/dist/src/mcp/bridge.d.ts +132 -0
  192. package/dist/src/mcp/bridge.d.ts.map +1 -0
  193. package/dist/src/mcp/bridge.js +734 -0
  194. package/dist/src/mcp/bridge.js.map +1 -0
  195. package/dist/src/mcp/http-transport.d.ts +32 -0
  196. package/dist/src/mcp/http-transport.d.ts.map +1 -0
  197. package/dist/src/mcp/http-transport.js +538 -0
  198. package/dist/src/mcp/http-transport.js.map +1 -0
  199. package/dist/src/mcp/index.d.ts +10 -0
  200. package/dist/src/mcp/index.d.ts.map +1 -0
  201. package/dist/src/mcp/index.js +17 -0
  202. package/dist/src/mcp/index.js.map +1 -0
  203. package/dist/src/mcp/oauth-pages.d.ts +23 -0
  204. package/dist/src/mcp/oauth-pages.d.ts.map +1 -0
  205. package/dist/src/mcp/oauth-pages.js +121 -0
  206. package/dist/src/mcp/oauth-pages.js.map +1 -0
  207. package/dist/src/mcp/oauth-postgres-stores.d.ts +55 -0
  208. package/dist/src/mcp/oauth-postgres-stores.d.ts.map +1 -0
  209. package/dist/src/mcp/oauth-postgres-stores.js +226 -0
  210. package/dist/src/mcp/oauth-postgres-stores.js.map +1 -0
  211. package/dist/src/mcp/oauth-provider.d.ts +95 -0
  212. package/dist/src/mcp/oauth-provider.d.ts.map +1 -0
  213. package/dist/src/mcp/oauth-provider.js +360 -0
  214. package/dist/src/mcp/oauth-provider.js.map +1 -0
  215. package/dist/src/mcp/oauth-stores.d.ts +62 -0
  216. package/dist/src/mcp/oauth-stores.d.ts.map +1 -0
  217. package/dist/src/mcp/oauth-stores.js +154 -0
  218. package/dist/src/mcp/oauth-stores.js.map +1 -0
  219. package/dist/src/mcp/server.d.ts +18 -0
  220. package/dist/src/mcp/server.d.ts.map +1 -0
  221. package/dist/src/mcp/server.js +51 -0
  222. package/dist/src/mcp/server.js.map +1 -0
  223. package/dist/src/metrics/collector.d.ts +106 -0
  224. package/dist/src/metrics/collector.d.ts.map +1 -0
  225. package/dist/src/metrics/collector.js +311 -0
  226. package/dist/src/metrics/collector.js.map +1 -0
  227. package/dist/src/metrics/index.d.ts +2 -0
  228. package/dist/src/metrics/index.d.ts.map +1 -0
  229. package/dist/src/metrics/index.js +6 -0
  230. package/dist/src/metrics/index.js.map +1 -0
  231. package/dist/src/middleware/auth.d.ts +77 -0
  232. package/dist/src/middleware/auth.d.ts.map +1 -0
  233. package/dist/src/middleware/auth.js +720 -0
  234. package/dist/src/middleware/auth.js.map +1 -0
  235. package/dist/src/middleware/session.d.ts +18 -0
  236. package/dist/src/middleware/session.d.ts.map +1 -0
  237. package/dist/src/middleware/session.js +67 -0
  238. package/dist/src/middleware/session.js.map +1 -0
  239. package/dist/src/middleware/validate.d.ts +3 -0
  240. package/dist/src/middleware/validate.d.ts.map +1 -0
  241. package/dist/src/middleware/validate.js +85 -0
  242. package/dist/src/middleware/validate.js.map +1 -0
  243. package/dist/src/policy/engine.d.ts +107 -0
  244. package/dist/src/policy/engine.d.ts.map +1 -0
  245. package/dist/src/policy/engine.js +646 -0
  246. package/dist/src/policy/engine.js.map +1 -0
  247. package/dist/src/policy/index.d.ts +3 -0
  248. package/dist/src/policy/index.d.ts.map +1 -0
  249. package/dist/src/policy/index.js +8 -0
  250. package/dist/src/policy/index.js.map +1 -0
  251. package/dist/src/policy/opa-engine.d.ts +176 -0
  252. package/dist/src/policy/opa-engine.d.ts.map +1 -0
  253. package/dist/src/policy/opa-engine.js +790 -0
  254. package/dist/src/policy/opa-engine.js.map +1 -0
  255. package/dist/src/proxy/forward-proxy.d.ts +30 -0
  256. package/dist/src/proxy/forward-proxy.d.ts.map +1 -0
  257. package/dist/src/proxy/forward-proxy.js +580 -0
  258. package/dist/src/proxy/forward-proxy.js.map +1 -0
  259. package/dist/src/proxy/index.d.ts +2 -0
  260. package/dist/src/proxy/index.d.ts.map +1 -0
  261. package/dist/src/proxy/index.js +8 -0
  262. package/dist/src/proxy/index.js.map +1 -0
  263. package/dist/src/ratelimit/limiter.d.ts +45 -0
  264. package/dist/src/ratelimit/limiter.d.ts.map +1 -0
  265. package/dist/src/ratelimit/limiter.js +158 -0
  266. package/dist/src/ratelimit/limiter.js.map +1 -0
  267. package/dist/src/replay/engine.d.ts +40 -0
  268. package/dist/src/replay/engine.d.ts.map +1 -0
  269. package/dist/src/replay/engine.js +106 -0
  270. package/dist/src/replay/engine.js.map +1 -0
  271. package/dist/src/replay/index.d.ts +2 -0
  272. package/dist/src/replay/index.d.ts.map +1 -0
  273. package/dist/src/replay/index.js +6 -0
  274. package/dist/src/replay/index.js.map +1 -0
  275. package/dist/src/saas/index.d.ts +2 -0
  276. package/dist/src/saas/index.d.ts.map +1 -0
  277. package/dist/src/saas/index.js +18 -0
  278. package/dist/src/saas/index.js.map +1 -0
  279. package/dist/src/saas/routes.d.ts +18 -0
  280. package/dist/src/saas/routes.d.ts.map +1 -0
  281. package/dist/src/saas/routes.js +1566 -0
  282. package/dist/src/saas/routes.js.map +1 -0
  283. package/dist/src/server/app.d.ts +44 -0
  284. package/dist/src/server/app.d.ts.map +1 -0
  285. package/dist/src/server/app.js +854 -0
  286. package/dist/src/server/app.js.map +1 -0
  287. package/dist/src/server/errors.d.ts +32 -0
  288. package/dist/src/server/errors.d.ts.map +1 -0
  289. package/dist/src/server/errors.js +39 -0
  290. package/dist/src/server/errors.js.map +1 -0
  291. package/dist/src/server/gateway.d.ts +165 -0
  292. package/dist/src/server/gateway.d.ts.map +1 -0
  293. package/dist/src/server/gateway.js +964 -0
  294. package/dist/src/server/gateway.js.map +1 -0
  295. package/dist/src/server/index.d.ts +2 -0
  296. package/dist/src/server/index.d.ts.map +1 -0
  297. package/dist/src/server/index.js +295 -0
  298. package/dist/src/server/index.js.map +1 -0
  299. package/dist/src/server/logger.d.ts +33 -0
  300. package/dist/src/server/logger.d.ts.map +1 -0
  301. package/dist/src/server/logger.js +230 -0
  302. package/dist/src/server/logger.js.map +1 -0
  303. package/dist/src/server/stream-proxy.d.ts +32 -0
  304. package/dist/src/server/stream-proxy.d.ts.map +1 -0
  305. package/dist/src/server/stream-proxy.js +184 -0
  306. package/dist/src/server/stream-proxy.js.map +1 -0
  307. package/dist/src/storage/file-persistence.d.ts +48 -0
  308. package/dist/src/storage/file-persistence.d.ts.map +1 -0
  309. package/dist/src/storage/file-persistence.js +280 -0
  310. package/dist/src/storage/file-persistence.js.map +1 -0
  311. package/dist/src/storage/index.d.ts +5 -0
  312. package/dist/src/storage/index.d.ts.map +1 -0
  313. package/dist/src/storage/index.js +21 -0
  314. package/dist/src/storage/index.js.map +1 -0
  315. package/dist/src/storage/interfaces.d.ts +237 -0
  316. package/dist/src/storage/interfaces.d.ts.map +1 -0
  317. package/dist/src/storage/interfaces.js +3 -0
  318. package/dist/src/storage/interfaces.js.map +1 -0
  319. package/dist/src/storage/memory.d.ts +162 -0
  320. package/dist/src/storage/memory.d.ts.map +1 -0
  321. package/dist/src/storage/memory.js +603 -0
  322. package/dist/src/storage/memory.js.map +1 -0
  323. package/dist/src/storage/postgres.d.ts +267 -0
  324. package/dist/src/storage/postgres.d.ts.map +1 -0
  325. package/dist/src/storage/postgres.js +1555 -0
  326. package/dist/src/storage/postgres.js.map +1 -0
  327. package/dist/src/storage/redis.d.ts +202 -0
  328. package/dist/src/storage/redis.d.ts.map +1 -0
  329. package/dist/src/storage/redis.js +629 -0
  330. package/dist/src/storage/redis.js.map +1 -0
  331. package/dist/src/tracing/index.d.ts +2 -0
  332. package/dist/src/tracing/index.d.ts.map +1 -0
  333. package/dist/src/tracing/index.js +6 -0
  334. package/dist/src/tracing/index.js.map +1 -0
  335. package/dist/src/tracing/provider.d.ts +43 -0
  336. package/dist/src/tracing/provider.d.ts.map +1 -0
  337. package/dist/src/tracing/provider.js +74 -0
  338. package/dist/src/tracing/provider.js.map +1 -0
  339. package/dist/src/trust/calculator.d.ts +54 -0
  340. package/dist/src/trust/calculator.d.ts.map +1 -0
  341. package/dist/src/trust/calculator.js +102 -0
  342. package/dist/src/trust/calculator.js.map +1 -0
  343. package/dist/src/trust/index.d.ts +2 -0
  344. package/dist/src/trust/index.d.ts.map +1 -0
  345. package/dist/src/trust/index.js +7 -0
  346. package/dist/src/trust/index.js.map +1 -0
  347. package/dist/src/types/budget.d.ts +30 -0
  348. package/dist/src/types/budget.d.ts.map +1 -0
  349. package/dist/src/types/budget.js +3 -0
  350. package/dist/src/types/budget.js.map +1 -0
  351. package/dist/src/types/config.d.ts +176 -0
  352. package/dist/src/types/config.d.ts.map +1 -0
  353. package/dist/src/types/config.js +3 -0
  354. package/dist/src/types/config.js.map +1 -0
  355. package/dist/src/types/events.d.ts +24 -0
  356. package/dist/src/types/events.d.ts.map +1 -0
  357. package/dist/src/types/events.js +3 -0
  358. package/dist/src/types/events.js.map +1 -0
  359. package/dist/src/types/index.d.ts +8 -0
  360. package/dist/src/types/index.d.ts.map +1 -0
  361. package/dist/src/types/index.js +24 -0
  362. package/dist/src/types/index.js.map +1 -0
  363. package/dist/src/types/policy.d.ts +60 -0
  364. package/dist/src/types/policy.d.ts.map +1 -0
  365. package/dist/src/types/policy.js +3 -0
  366. package/dist/src/types/policy.js.map +1 -0
  367. package/dist/src/types/stripe-config.d.ts +12 -0
  368. package/dist/src/types/stripe-config.d.ts.map +1 -0
  369. package/dist/src/types/stripe-config.js +3 -0
  370. package/dist/src/types/stripe-config.js.map +1 -0
  371. package/dist/src/types/subscription.d.ts +24 -0
  372. package/dist/src/types/subscription.d.ts.map +1 -0
  373. package/dist/src/types/subscription.js +38 -0
  374. package/dist/src/types/subscription.js.map +1 -0
  375. package/dist/src/types/tool-call.d.ts +42 -0
  376. package/dist/src/types/tool-call.d.ts.map +1 -0
  377. package/dist/src/types/tool-call.js +3 -0
  378. package/dist/src/types/tool-call.js.map +1 -0
  379. package/dist/src/types/tool-result.d.ts +58 -0
  380. package/dist/src/types/tool-result.d.ts.map +1 -0
  381. package/dist/src/types/tool-result.js +3 -0
  382. package/dist/src/types/tool-result.js.map +1 -0
  383. package/dist/src/types/user.d.ts +101 -0
  384. package/dist/src/types/user.d.ts.map +1 -0
  385. package/dist/src/types/user.js +6 -0
  386. package/dist/src/types/user.js.map +1 -0
  387. package/dist/tests/integration/api.test.d.ts +2 -0
  388. package/dist/tests/integration/api.test.d.ts.map +1 -0
  389. package/dist/tests/integration/api.test.js +1199 -0
  390. package/dist/tests/integration/api.test.js.map +1 -0
  391. package/dist/tests/integration/proxy.test.d.ts +2 -0
  392. package/dist/tests/integration/proxy.test.d.ts.map +1 -0
  393. package/dist/tests/integration/proxy.test.js +251 -0
  394. package/dist/tests/integration/proxy.test.js.map +1 -0
  395. package/dist/tests/integration/storage.test.d.ts +16 -0
  396. package/dist/tests/integration/storage.test.d.ts.map +1 -0
  397. package/dist/tests/integration/storage.test.js +826 -0
  398. package/dist/tests/integration/storage.test.js.map +1 -0
  399. package/dist/tests/unit/admin.test.d.ts +2 -0
  400. package/dist/tests/unit/admin.test.d.ts.map +1 -0
  401. package/dist/tests/unit/admin.test.js +698 -0
  402. package/dist/tests/unit/admin.test.js.map +1 -0
  403. package/dist/tests/unit/anomaly-detector.test.d.ts +2 -0
  404. package/dist/tests/unit/anomaly-detector.test.d.ts.map +1 -0
  405. package/dist/tests/unit/anomaly-detector.test.js +903 -0
  406. package/dist/tests/unit/anomaly-detector.test.js.map +1 -0
  407. package/dist/tests/unit/approval-manager.test.d.ts +2 -0
  408. package/dist/tests/unit/approval-manager.test.d.ts.map +1 -0
  409. package/dist/tests/unit/approval-manager.test.js +528 -0
  410. package/dist/tests/unit/approval-manager.test.js.map +1 -0
  411. package/dist/tests/unit/approval-webhook.test.d.ts +2 -0
  412. package/dist/tests/unit/approval-webhook.test.d.ts.map +1 -0
  413. package/dist/tests/unit/approval-webhook.test.js +355 -0
  414. package/dist/tests/unit/approval-webhook.test.js.map +1 -0
  415. package/dist/tests/unit/audit-logger.test.d.ts +2 -0
  416. package/dist/tests/unit/audit-logger.test.d.ts.map +1 -0
  417. package/dist/tests/unit/audit-logger.test.js +635 -0
  418. package/dist/tests/unit/audit-logger.test.js.map +1 -0
  419. package/dist/tests/unit/auth-routes.test.d.ts +2 -0
  420. package/dist/tests/unit/auth-routes.test.d.ts.map +1 -0
  421. package/dist/tests/unit/auth-routes.test.js +281 -0
  422. package/dist/tests/unit/auth-routes.test.js.map +1 -0
  423. package/dist/tests/unit/auth.test.d.ts +2 -0
  424. package/dist/tests/unit/auth.test.d.ts.map +1 -0
  425. package/dist/tests/unit/auth.test.js +1382 -0
  426. package/dist/tests/unit/auth.test.js.map +1 -0
  427. package/dist/tests/unit/billing.test.d.ts +2 -0
  428. package/dist/tests/unit/billing.test.d.ts.map +1 -0
  429. package/dist/tests/unit/billing.test.js +579 -0
  430. package/dist/tests/unit/billing.test.js.map +1 -0
  431. package/dist/tests/unit/budget-manager.test.d.ts +2 -0
  432. package/dist/tests/unit/budget-manager.test.d.ts.map +1 -0
  433. package/dist/tests/unit/budget-manager.test.js +778 -0
  434. package/dist/tests/unit/budget-manager.test.js.map +1 -0
  435. package/dist/tests/unit/budget-race.test.d.ts +2 -0
  436. package/dist/tests/unit/budget-race.test.d.ts.map +1 -0
  437. package/dist/tests/unit/budget-race.test.js +58 -0
  438. package/dist/tests/unit/budget-race.test.js.map +1 -0
  439. package/dist/tests/unit/cli.test.d.ts +2 -0
  440. package/dist/tests/unit/cli.test.d.ts.map +1 -0
  441. package/dist/tests/unit/cli.test.js +93 -0
  442. package/dist/tests/unit/cli.test.js.map +1 -0
  443. package/dist/tests/unit/concurrency.test.d.ts +2 -0
  444. package/dist/tests/unit/concurrency.test.d.ts.map +1 -0
  445. package/dist/tests/unit/concurrency.test.js +1270 -0
  446. package/dist/tests/unit/concurrency.test.js.map +1 -0
  447. package/dist/tests/unit/config-validate.test.d.ts +2 -0
  448. package/dist/tests/unit/config-validate.test.d.ts.map +1 -0
  449. package/dist/tests/unit/config-validate.test.js +230 -0
  450. package/dist/tests/unit/config-validate.test.js.map +1 -0
  451. package/dist/tests/unit/defaults.test.d.ts +2 -0
  452. package/dist/tests/unit/defaults.test.d.ts.map +1 -0
  453. package/dist/tests/unit/defaults.test.js +364 -0
  454. package/dist/tests/unit/defaults.test.js.map +1 -0
  455. package/dist/tests/unit/dlp-backends.test.d.ts +2 -0
  456. package/dist/tests/unit/dlp-backends.test.d.ts.map +1 -0
  457. package/dist/tests/unit/dlp-backends.test.js +563 -0
  458. package/dist/tests/unit/dlp-backends.test.js.map +1 -0
  459. package/dist/tests/unit/dlp-scanner.test.d.ts +2 -0
  460. package/dist/tests/unit/dlp-scanner.test.d.ts.map +1 -0
  461. package/dist/tests/unit/dlp-scanner.test.js +739 -0
  462. package/dist/tests/unit/dlp-scanner.test.js.map +1 -0
  463. package/dist/tests/unit/error-responses.test.d.ts +2 -0
  464. package/dist/tests/unit/error-responses.test.d.ts.map +1 -0
  465. package/dist/tests/unit/error-responses.test.js +101 -0
  466. package/dist/tests/unit/error-responses.test.js.map +1 -0
  467. package/dist/tests/unit/executor-registry.test.d.ts +2 -0
  468. package/dist/tests/unit/executor-registry.test.d.ts.map +1 -0
  469. package/dist/tests/unit/executor-registry.test.js +390 -0
  470. package/dist/tests/unit/executor-registry.test.js.map +1 -0
  471. package/dist/tests/unit/forward-proxy.test.d.ts +2 -0
  472. package/dist/tests/unit/forward-proxy.test.d.ts.map +1 -0
  473. package/dist/tests/unit/forward-proxy.test.js +621 -0
  474. package/dist/tests/unit/forward-proxy.test.js.map +1 -0
  475. package/dist/tests/unit/gateway-features.test.d.ts +2 -0
  476. package/dist/tests/unit/gateway-features.test.d.ts.map +1 -0
  477. package/dist/tests/unit/gateway-features.test.js +753 -0
  478. package/dist/tests/unit/gateway-features.test.js.map +1 -0
  479. package/dist/tests/unit/http-executor.test.d.ts +2 -0
  480. package/dist/tests/unit/http-executor.test.d.ts.map +1 -0
  481. package/dist/tests/unit/http-executor.test.js +310 -0
  482. package/dist/tests/unit/http-executor.test.js.map +1 -0
  483. package/dist/tests/unit/mcp-bridge.test.d.ts +2 -0
  484. package/dist/tests/unit/mcp-bridge.test.d.ts.map +1 -0
  485. package/dist/tests/unit/mcp-bridge.test.js +1136 -0
  486. package/dist/tests/unit/mcp-bridge.test.js.map +1 -0
  487. package/dist/tests/unit/mcp-http-transport.test.d.ts +2 -0
  488. package/dist/tests/unit/mcp-http-transport.test.d.ts.map +1 -0
  489. package/dist/tests/unit/mcp-http-transport.test.js +899 -0
  490. package/dist/tests/unit/mcp-http-transport.test.js.map +1 -0
  491. package/dist/tests/unit/mcp-oauth.test.d.ts +2 -0
  492. package/dist/tests/unit/mcp-oauth.test.d.ts.map +1 -0
  493. package/dist/tests/unit/mcp-oauth.test.js +759 -0
  494. package/dist/tests/unit/mcp-oauth.test.js.map +1 -0
  495. package/dist/tests/unit/mcp-server.test.d.ts +15 -0
  496. package/dist/tests/unit/mcp-server.test.d.ts.map +1 -0
  497. package/dist/tests/unit/mcp-server.test.js +158 -0
  498. package/dist/tests/unit/mcp-server.test.js.map +1 -0
  499. package/dist/tests/unit/metrics.test.d.ts +2 -0
  500. package/dist/tests/unit/metrics.test.d.ts.map +1 -0
  501. package/dist/tests/unit/metrics.test.js +208 -0
  502. package/dist/tests/unit/metrics.test.js.map +1 -0
  503. package/dist/tests/unit/oauth.test.d.ts +2 -0
  504. package/dist/tests/unit/oauth.test.d.ts.map +1 -0
  505. package/dist/tests/unit/oauth.test.js +281 -0
  506. package/dist/tests/unit/oauth.test.js.map +1 -0
  507. package/dist/tests/unit/opa-circuit-breaker.test.d.ts +2 -0
  508. package/dist/tests/unit/opa-circuit-breaker.test.d.ts.map +1 -0
  509. package/dist/tests/unit/opa-circuit-breaker.test.js +297 -0
  510. package/dist/tests/unit/opa-circuit-breaker.test.js.map +1 -0
  511. package/dist/tests/unit/opa-engine.test.d.ts +2 -0
  512. package/dist/tests/unit/opa-engine.test.d.ts.map +1 -0
  513. package/dist/tests/unit/opa-engine.test.js +1813 -0
  514. package/dist/tests/unit/opa-engine.test.js.map +1 -0
  515. package/dist/tests/unit/pipeline-timing.test.d.ts +2 -0
  516. package/dist/tests/unit/pipeline-timing.test.d.ts.map +1 -0
  517. package/dist/tests/unit/pipeline-timing.test.js +528 -0
  518. package/dist/tests/unit/pipeline-timing.test.js.map +1 -0
  519. package/dist/tests/unit/policy-engine.test.d.ts +2 -0
  520. package/dist/tests/unit/policy-engine.test.d.ts.map +1 -0
  521. package/dist/tests/unit/policy-engine.test.js +1345 -0
  522. package/dist/tests/unit/policy-engine.test.js.map +1 -0
  523. package/dist/tests/unit/policy-store.test.d.ts +2 -0
  524. package/dist/tests/unit/policy-store.test.d.ts.map +1 -0
  525. package/dist/tests/unit/policy-store.test.js +60 -0
  526. package/dist/tests/unit/policy-store.test.js.map +1 -0
  527. package/dist/tests/unit/postgres-storage.test.d.ts +2 -0
  528. package/dist/tests/unit/postgres-storage.test.d.ts.map +1 -0
  529. package/dist/tests/unit/postgres-storage.test.js +614 -0
  530. package/dist/tests/unit/postgres-storage.test.js.map +1 -0
  531. package/dist/tests/unit/prompt-injection-backend.test.d.ts +2 -0
  532. package/dist/tests/unit/prompt-injection-backend.test.d.ts.map +1 -0
  533. package/dist/tests/unit/prompt-injection-backend.test.js +621 -0
  534. package/dist/tests/unit/prompt-injection-backend.test.js.map +1 -0
  535. package/dist/tests/unit/proxy-hardening.test.d.ts +2 -0
  536. package/dist/tests/unit/proxy-hardening.test.d.ts.map +1 -0
  537. package/dist/tests/unit/proxy-hardening.test.js +166 -0
  538. package/dist/tests/unit/proxy-hardening.test.js.map +1 -0
  539. package/dist/tests/unit/rate-limiter.test.d.ts +2 -0
  540. package/dist/tests/unit/rate-limiter.test.d.ts.map +1 -0
  541. package/dist/tests/unit/rate-limiter.test.js +443 -0
  542. package/dist/tests/unit/rate-limiter.test.js.map +1 -0
  543. package/dist/tests/unit/redis-storage.test.d.ts +2 -0
  544. package/dist/tests/unit/redis-storage.test.d.ts.map +1 -0
  545. package/dist/tests/unit/redis-storage.test.js +766 -0
  546. package/dist/tests/unit/redis-storage.test.js.map +1 -0
  547. package/dist/tests/unit/replay-engine.test.d.ts +2 -0
  548. package/dist/tests/unit/replay-engine.test.d.ts.map +1 -0
  549. package/dist/tests/unit/replay-engine.test.js +371 -0
  550. package/dist/tests/unit/replay-engine.test.js.map +1 -0
  551. package/dist/tests/unit/saas-routes.test.d.ts +2 -0
  552. package/dist/tests/unit/saas-routes.test.d.ts.map +1 -0
  553. package/dist/tests/unit/saas-routes.test.js +1399 -0
  554. package/dist/tests/unit/saas-routes.test.js.map +1 -0
  555. package/dist/tests/unit/session.test.d.ts +2 -0
  556. package/dist/tests/unit/session.test.d.ts.map +1 -0
  557. package/dist/tests/unit/session.test.js +532 -0
  558. package/dist/tests/unit/session.test.js.map +1 -0
  559. package/dist/tests/unit/slack-executor.test.d.ts +2 -0
  560. package/dist/tests/unit/slack-executor.test.d.ts.map +1 -0
  561. package/dist/tests/unit/slack-executor.test.js +209 -0
  562. package/dist/tests/unit/slack-executor.test.js.map +1 -0
  563. package/dist/tests/unit/storage-hardening.test.d.ts +2 -0
  564. package/dist/tests/unit/storage-hardening.test.d.ts.map +1 -0
  565. package/dist/tests/unit/storage-hardening.test.js +165 -0
  566. package/dist/tests/unit/storage-hardening.test.js.map +1 -0
  567. package/dist/tests/unit/storage.test.d.ts +2 -0
  568. package/dist/tests/unit/storage.test.d.ts.map +1 -0
  569. package/dist/tests/unit/storage.test.js +698 -0
  570. package/dist/tests/unit/storage.test.js.map +1 -0
  571. package/dist/tests/unit/text-normalizer.test.d.ts +2 -0
  572. package/dist/tests/unit/text-normalizer.test.d.ts.map +1 -0
  573. package/dist/tests/unit/text-normalizer.test.js +229 -0
  574. package/dist/tests/unit/text-normalizer.test.js.map +1 -0
  575. package/dist/tests/unit/tracing.test.d.ts +2 -0
  576. package/dist/tests/unit/tracing.test.d.ts.map +1 -0
  577. package/dist/tests/unit/tracing.test.js +611 -0
  578. package/dist/tests/unit/tracing.test.js.map +1 -0
  579. package/dist/tests/unit/trust-calculator.test.d.ts +2 -0
  580. package/dist/tests/unit/trust-calculator.test.d.ts.map +1 -0
  581. package/dist/tests/unit/trust-calculator.test.js +497 -0
  582. package/dist/tests/unit/trust-calculator.test.js.map +1 -0
  583. package/dist/tests/unit/ts-sdk.test.d.ts +2 -0
  584. package/dist/tests/unit/ts-sdk.test.d.ts.map +1 -0
  585. package/dist/tests/unit/ts-sdk.test.js +421 -0
  586. package/dist/tests/unit/ts-sdk.test.js.map +1 -0
  587. package/dist/tests/unit/usage-extractor-llm.test.d.ts +2 -0
  588. package/dist/tests/unit/usage-extractor-llm.test.d.ts.map +1 -0
  589. package/dist/tests/unit/usage-extractor-llm.test.js +139 -0
  590. package/dist/tests/unit/usage-extractor-llm.test.js.map +1 -0
  591. package/dist/tests/unit/usage-extractor.test.d.ts +2 -0
  592. package/dist/tests/unit/usage-extractor.test.d.ts.map +1 -0
  593. package/dist/tests/unit/usage-extractor.test.js +271 -0
  594. package/dist/tests/unit/usage-extractor.test.js.map +1 -0
  595. package/dist/tests/unit/user-stores.test.d.ts +2 -0
  596. package/dist/tests/unit/user-stores.test.d.ts.map +1 -0
  597. package/dist/tests/unit/user-stores.test.js +687 -0
  598. package/dist/tests/unit/user-stores.test.js.map +1 -0
  599. package/dist/tests/unit/validate.test.d.ts +2 -0
  600. package/dist/tests/unit/validate.test.d.ts.map +1 -0
  601. package/dist/tests/unit/validate.test.js +545 -0
  602. package/dist/tests/unit/validate.test.js.map +1 -0
  603. package/package.json +86 -0
  604. package/policy-packs/README.md +42 -0
  605. package/policy-packs/default.yaml +46 -0
  606. package/policy-packs/dev_fast.yaml +54 -0
  607. package/policy-packs/prod_strict.yaml +83 -0
@@ -0,0 +1,1136 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const stream_1 = require("stream");
4
+ const bridge_1 = require("../../src/mcp/bridge");
5
+ // ---------------------------------------------------------------------------
6
+ // Mock Gateway
7
+ // ---------------------------------------------------------------------------
8
+ function createMockGateway() {
9
+ return {
10
+ execute: jest.fn(),
11
+ shutdown: jest.fn(),
12
+ registerExecutor: jest.fn(),
13
+ };
14
+ }
15
+ // ---------------------------------------------------------------------------
16
+ // Helpers
17
+ // ---------------------------------------------------------------------------
18
+ /** Build a minimal ToolResult for testing formatResult. */
19
+ function buildToolResult(overrides) {
20
+ return {
21
+ tool_call_id: 'tc-001',
22
+ task_id: 'task-001',
23
+ status: 'ok',
24
+ policy: {
25
+ decision: 'allow',
26
+ rule_id: 'rule-1',
27
+ reasons: ['Allowed by default policy'],
28
+ },
29
+ dlp: {
30
+ detected: [],
31
+ redactions: [],
32
+ severity: 'low',
33
+ },
34
+ budget: {
35
+ estimated_cost_usd: 0.001,
36
+ spent_cost_usd_task: 0.005,
37
+ remaining_cost_usd_task: 1.995,
38
+ },
39
+ output: {
40
+ http_status: 200,
41
+ body: { data: 'test response' },
42
+ },
43
+ timing: {
44
+ started_at: new Date().toISOString(),
45
+ duration_ms: 42,
46
+ },
47
+ ...overrides,
48
+ };
49
+ }
50
+ /**
51
+ * Create a pair of streams for testing the stdio transport.
52
+ * Returns an input Readable that we can push data into and an output
53
+ * Writable that captures what the bridge writes.
54
+ */
55
+ function createTestStreams() {
56
+ const input = new stream_1.Readable({ read() { } });
57
+ const outputChunks = [];
58
+ const output = new stream_1.Writable({
59
+ write(chunk, _encoding, callback) {
60
+ outputChunks.push(chunk.toString());
61
+ callback();
62
+ },
63
+ });
64
+ return { input, output, outputChunks };
65
+ }
66
+ /** Send a JSON-RPC message through the input stream and wait briefly for processing. */
67
+ async function sendMessage(input, message) {
68
+ input.push(JSON.stringify(message) + '\n');
69
+ // Allow the event loop to process the message
70
+ await new Promise((resolve) => setTimeout(resolve, 20));
71
+ }
72
+ /** Parse the last JSON-RPC response from the output chunks. */
73
+ function getLastResponse(outputChunks) {
74
+ const allOutput = outputChunks.join('');
75
+ const lines = allOutput.split('\n').filter((l) => l.trim());
76
+ const lastLine = lines[lines.length - 1];
77
+ return JSON.parse(lastLine);
78
+ }
79
+ // ---------------------------------------------------------------------------
80
+ // Tests
81
+ // ---------------------------------------------------------------------------
82
+ describe('MCPBridge', () => {
83
+ // -------------------------------------------------------------------------
84
+ // Constructor & public methods
85
+ // -------------------------------------------------------------------------
86
+ describe('constructor', () => {
87
+ it('creates an MCPBridge with default bridge config', () => {
88
+ const mockGateway = createMockGateway();
89
+ const bridge = new bridge_1.MCPBridge(mockGateway);
90
+ expect(bridge).toBeDefined();
91
+ expect(bridge.getGateway()).toBe(mockGateway);
92
+ expect(bridge.isInitialized()).toBe(false);
93
+ });
94
+ it('merges custom bridge config over defaults', () => {
95
+ const mockGateway = createMockGateway();
96
+ const customConfig = {
97
+ workspace_id: 'ws_custom',
98
+ actor: { type: 'user', id: 'custom-user', display: 'Custom User' },
99
+ source: { platform: 'custom-platform' },
100
+ task_id: 'task-fixed-id',
101
+ };
102
+ const bridge = new bridge_1.MCPBridge(mockGateway, customConfig);
103
+ const bridgeConfig = bridge.bridgeConfig;
104
+ expect(bridgeConfig.workspace_id).toBe('ws_custom');
105
+ expect(bridgeConfig.actor).toEqual({ type: 'user', id: 'custom-user', display: 'Custom User' });
106
+ expect(bridgeConfig.source).toEqual({ platform: 'custom-platform' });
107
+ expect(bridgeConfig.task_id).toBe('task-fixed-id');
108
+ });
109
+ it('uses default values when no custom config provided', () => {
110
+ const mockGateway = createMockGateway();
111
+ const bridge = new bridge_1.MCPBridge(mockGateway);
112
+ const bridgeConfig = bridge.bridgeConfig;
113
+ expect(bridgeConfig.workspace_id).toBe('ws_mcp_default');
114
+ expect(bridgeConfig.actor).toEqual({ type: 'agent', id: 'mcp-agent', display: 'MCP Agent' });
115
+ expect(bridgeConfig.source).toEqual({ platform: 'mcp' });
116
+ expect(bridgeConfig.task_id).toBe('');
117
+ });
118
+ it('partially overrides bridge config while keeping defaults for missing fields', () => {
119
+ const mockGateway = createMockGateway();
120
+ const partialConfig = {
121
+ workspace_id: 'ws_partial',
122
+ };
123
+ const bridge = new bridge_1.MCPBridge(mockGateway, partialConfig);
124
+ const bridgeConfig = bridge.bridgeConfig;
125
+ expect(bridgeConfig.workspace_id).toBe('ws_partial');
126
+ expect(bridgeConfig.actor).toEqual({ type: 'agent', id: 'mcp-agent', display: 'MCP Agent' });
127
+ expect(bridgeConfig.source).toEqual({ platform: 'mcp' });
128
+ expect(bridgeConfig.task_id).toBe('');
129
+ });
130
+ });
131
+ describe('isInitialized()', () => {
132
+ it('returns false before the initialize handshake', () => {
133
+ const mockGateway = createMockGateway();
134
+ const bridge = new bridge_1.MCPBridge(mockGateway);
135
+ expect(bridge.isInitialized()).toBe(false);
136
+ });
137
+ it('returns true after the initialize handshake', async () => {
138
+ const mockGateway = createMockGateway();
139
+ const bridge = new bridge_1.MCPBridge(mockGateway);
140
+ const { input, output } = createTestStreams();
141
+ await bridge.connectStdio(input, output);
142
+ await sendMessage(input, {
143
+ jsonrpc: '2.0',
144
+ id: 1,
145
+ method: 'initialize',
146
+ params: {
147
+ protocolVersion: '2025-03-26',
148
+ capabilities: {},
149
+ clientInfo: { name: 'test', version: '1.0' },
150
+ },
151
+ });
152
+ expect(bridge.isInitialized()).toBe(true);
153
+ await bridge.close();
154
+ });
155
+ });
156
+ describe('getGateway()', () => {
157
+ it('returns the gateway instance passed to the constructor', () => {
158
+ const mockGateway = createMockGateway();
159
+ const bridge = new bridge_1.MCPBridge(mockGateway);
160
+ expect(bridge.getGateway()).toBe(mockGateway);
161
+ });
162
+ });
163
+ describe('close()', () => {
164
+ it('calls gateway.shutdown()', async () => {
165
+ const mockGateway = createMockGateway();
166
+ const bridge = new bridge_1.MCPBridge(mockGateway);
167
+ await bridge.close();
168
+ expect(mockGateway.shutdown).toHaveBeenCalledTimes(1);
169
+ });
170
+ it('cleans up the transport', async () => {
171
+ const mockGateway = createMockGateway();
172
+ const bridge = new bridge_1.MCPBridge(mockGateway);
173
+ const { input, output } = createTestStreams();
174
+ await bridge.connectStdio(input, output);
175
+ expect(bridge.transport).not.toBeNull();
176
+ await bridge.close();
177
+ expect(bridge.transport).toBeNull();
178
+ });
179
+ it('is safe to call close when no transport is connected', async () => {
180
+ const mockGateway = createMockGateway();
181
+ const bridge = new bridge_1.MCPBridge(mockGateway);
182
+ await bridge.close();
183
+ expect(mockGateway.shutdown).toHaveBeenCalledTimes(1);
184
+ });
185
+ });
186
+ // -------------------------------------------------------------------------
187
+ // Tool registration (static schemas)
188
+ // -------------------------------------------------------------------------
189
+ describe('registered tools', () => {
190
+ it('lists exactly 3 tools: http_request, http_get, http_post via tools/list', async () => {
191
+ const mockGateway = createMockGateway();
192
+ const bridge = new bridge_1.MCPBridge(mockGateway);
193
+ const { input, output, outputChunks } = createTestStreams();
194
+ await bridge.connectStdio(input, output);
195
+ await sendMessage(input, {
196
+ jsonrpc: '2.0',
197
+ id: 1,
198
+ method: 'tools/list',
199
+ });
200
+ const response = getLastResponse(outputChunks);
201
+ expect(response.result.tools).toHaveLength(3);
202
+ const toolNames = response.result.tools.map((t) => t.name);
203
+ expect(toolNames).toContain('http_request');
204
+ expect(toolNames).toContain('http_get');
205
+ expect(toolNames).toContain('http_post');
206
+ await bridge.close();
207
+ });
208
+ it('each tool has an inputSchema with required url field', async () => {
209
+ const mockGateway = createMockGateway();
210
+ const bridge = new bridge_1.MCPBridge(mockGateway);
211
+ const { input, output, outputChunks } = createTestStreams();
212
+ await bridge.connectStdio(input, output);
213
+ await sendMessage(input, {
214
+ jsonrpc: '2.0',
215
+ id: 1,
216
+ method: 'tools/list',
217
+ });
218
+ const response = getLastResponse(outputChunks);
219
+ for (const tool of response.result.tools) {
220
+ expect(tool.inputSchema.type).toBe('object');
221
+ expect(tool.inputSchema.required).toContain('url');
222
+ expect(tool.inputSchema.properties.url).toBeDefined();
223
+ }
224
+ await bridge.close();
225
+ });
226
+ });
227
+ // -------------------------------------------------------------------------
228
+ // buildToolCall (private, tested via reflection)
229
+ // -------------------------------------------------------------------------
230
+ describe('buildToolCall', () => {
231
+ let bridge;
232
+ let buildToolCall;
233
+ beforeEach(() => {
234
+ const mockGateway = createMockGateway();
235
+ bridge = new bridge_1.MCPBridge(mockGateway);
236
+ buildToolCall = bridge.buildToolCall.bind(bridge);
237
+ });
238
+ it('creates a valid ToolCall with required fields', () => {
239
+ const result = buildToolCall({
240
+ toolName: 'http.request',
241
+ capability: 'read',
242
+ args: { method: 'GET', url: 'https://example.com' },
243
+ });
244
+ expect(result.tool_call_id).toBeDefined();
245
+ expect(typeof result.tool_call_id).toBe('string');
246
+ expect(result.tool_call_id.length).toBeGreaterThan(0);
247
+ expect(result.task_id).toBeDefined();
248
+ expect(typeof result.task_id).toBe('string');
249
+ expect(result.workspace_id).toBe('ws_mcp_default');
250
+ expect(result.actor).toEqual({ type: 'agent', id: 'mcp-agent', display: 'MCP Agent' });
251
+ expect(result.source).toEqual({ platform: 'mcp' });
252
+ expect(result.tool).toEqual({
253
+ name: 'http.request',
254
+ version: '1.0.0',
255
+ capability: 'read',
256
+ });
257
+ expect(result.args).toEqual({ method: 'GET', url: 'https://example.com' });
258
+ expect(result.timestamp).toBeDefined();
259
+ });
260
+ it('generates unique tool_call_id for each call', () => {
261
+ const r1 = buildToolCall({
262
+ toolName: 'http.get',
263
+ capability: 'read',
264
+ args: { method: 'GET', url: 'https://a.com' },
265
+ });
266
+ const r2 = buildToolCall({
267
+ toolName: 'http.get',
268
+ capability: 'read',
269
+ args: { method: 'GET', url: 'https://b.com' },
270
+ });
271
+ expect(r1.tool_call_id).not.toBe(r2.tool_call_id);
272
+ });
273
+ it('generates a new task_id when bridgeConfig.task_id is empty', () => {
274
+ const result = buildToolCall({
275
+ toolName: 'http.request',
276
+ capability: 'read',
277
+ args: { method: 'GET', url: 'https://example.com' },
278
+ });
279
+ expect(result.task_id).toBeDefined();
280
+ expect(result.task_id.length).toBeGreaterThan(0);
281
+ expect(result.task_id).not.toBe('');
282
+ });
283
+ it('uses the configured task_id when one is set', () => {
284
+ const mockGateway = createMockGateway();
285
+ const customBridge = new bridge_1.MCPBridge(mockGateway, { task_id: 'fixed-task-123' });
286
+ const customBuildToolCall = customBridge.buildToolCall.bind(customBridge);
287
+ const result = customBuildToolCall({
288
+ toolName: 'http.request',
289
+ capability: 'read',
290
+ args: { method: 'GET', url: 'https://example.com' },
291
+ });
292
+ expect(result.task_id).toBe('fixed-task-123');
293
+ });
294
+ it('applies constraints when provided', () => {
295
+ const result = buildToolCall({
296
+ toolName: 'http.request',
297
+ capability: 'write',
298
+ args: { method: 'POST', url: 'https://example.com/api' },
299
+ constraints: { timeout_ms: 5000, max_cost_usd: 0.50 },
300
+ });
301
+ expect(result.constraints).toBeDefined();
302
+ expect(result.constraints.timeout_ms).toBe(5000);
303
+ expect(result.constraints.max_cost_usd).toBe(0.50);
304
+ });
305
+ it('does not add constraints when none are specified', () => {
306
+ const result = buildToolCall({
307
+ toolName: 'http.get',
308
+ capability: 'read',
309
+ args: { method: 'GET', url: 'https://example.com' },
310
+ });
311
+ expect(result.constraints).toBeUndefined();
312
+ });
313
+ it('does not add constraints when both values are undefined', () => {
314
+ const result = buildToolCall({
315
+ toolName: 'http.get',
316
+ capability: 'read',
317
+ args: { method: 'GET', url: 'https://example.com' },
318
+ constraints: { timeout_ms: undefined, max_cost_usd: undefined },
319
+ });
320
+ expect(result.constraints).toBeUndefined();
321
+ });
322
+ it('handles zero-value constraints correctly (0 is not null)', () => {
323
+ const result = buildToolCall({
324
+ toolName: 'http.request',
325
+ capability: 'read',
326
+ args: { method: 'GET', url: 'https://example.com' },
327
+ constraints: { timeout_ms: 0, max_cost_usd: 0 },
328
+ });
329
+ expect(result.constraints).toBeDefined();
330
+ expect(result.constraints.timeout_ms).toBe(0);
331
+ expect(result.constraints.max_cost_usd).toBe(0);
332
+ });
333
+ it('adds only the timeout_ms constraint when max_cost_usd is omitted', () => {
334
+ const result = buildToolCall({
335
+ toolName: 'http.request',
336
+ capability: 'read',
337
+ args: { method: 'GET', url: 'https://example.com' },
338
+ constraints: { timeout_ms: 3000 },
339
+ });
340
+ expect(result.constraints).toBeDefined();
341
+ expect(result.constraints.timeout_ms).toBe(3000);
342
+ expect(result.constraints.max_cost_usd).toBeUndefined();
343
+ });
344
+ it('adds only the max_cost_usd constraint when timeout_ms is omitted', () => {
345
+ const result = buildToolCall({
346
+ toolName: 'http.request',
347
+ capability: 'read',
348
+ args: { method: 'GET', url: 'https://example.com' },
349
+ constraints: { max_cost_usd: 1.0 },
350
+ });
351
+ expect(result.constraints).toBeDefined();
352
+ expect(result.constraints.max_cost_usd).toBe(1.0);
353
+ expect(result.constraints.timeout_ms).toBeUndefined();
354
+ });
355
+ it('applies context with purpose and labels when provided', () => {
356
+ const result = buildToolCall({
357
+ toolName: 'http.post',
358
+ capability: 'write',
359
+ args: { method: 'POST', url: 'https://example.com/api', body: '{}' },
360
+ context: { purpose: 'Fetch user data', labels: ['user-data', 'pii'] },
361
+ });
362
+ expect(result.context).toBeDefined();
363
+ expect(result.context.purpose).toBe('Fetch user data');
364
+ expect(result.context.labels).toEqual(['user-data', 'pii']);
365
+ });
366
+ it('does not add context when none is specified', () => {
367
+ const result = buildToolCall({
368
+ toolName: 'http.get',
369
+ capability: 'read',
370
+ args: { method: 'GET', url: 'https://example.com' },
371
+ });
372
+ expect(result.context).toBeUndefined();
373
+ });
374
+ it('does not add context when both purpose and labels are undefined', () => {
375
+ const result = buildToolCall({
376
+ toolName: 'http.get',
377
+ capability: 'read',
378
+ args: { method: 'GET', url: 'https://example.com' },
379
+ context: { purpose: undefined, labels: undefined },
380
+ });
381
+ expect(result.context).toBeUndefined();
382
+ });
383
+ it('adds only purpose when labels are omitted', () => {
384
+ const result = buildToolCall({
385
+ toolName: 'http.get',
386
+ capability: 'read',
387
+ args: { method: 'GET', url: 'https://example.com' },
388
+ context: { purpose: 'Health check' },
389
+ });
390
+ expect(result.context).toBeDefined();
391
+ expect(result.context.purpose).toBe('Health check');
392
+ expect(result.context.labels).toBeUndefined();
393
+ });
394
+ it('adds only labels when purpose is omitted', () => {
395
+ const result = buildToolCall({
396
+ toolName: 'http.get',
397
+ capability: 'read',
398
+ args: { method: 'GET', url: 'https://example.com' },
399
+ context: { labels: ['internal'] },
400
+ });
401
+ expect(result.context).toBeDefined();
402
+ expect(result.context.labels).toEqual(['internal']);
403
+ expect(result.context.purpose).toBeUndefined();
404
+ });
405
+ it('sets the correct capability for different tool types', () => {
406
+ const readResult = buildToolCall({
407
+ toolName: 'http.get',
408
+ capability: 'read',
409
+ args: { method: 'GET', url: 'https://example.com' },
410
+ });
411
+ expect(readResult.tool.capability).toBe('read');
412
+ const writeResult = buildToolCall({
413
+ toolName: 'http.post',
414
+ capability: 'write',
415
+ args: { method: 'POST', url: 'https://example.com' },
416
+ });
417
+ expect(writeResult.tool.capability).toBe('write');
418
+ const deleteResult = buildToolCall({
419
+ toolName: 'http.request',
420
+ capability: 'delete',
421
+ args: { method: 'DELETE', url: 'https://example.com/resource/1' },
422
+ });
423
+ expect(deleteResult.tool.capability).toBe('delete');
424
+ });
425
+ });
426
+ // -------------------------------------------------------------------------
427
+ // formatResult (private, tested via reflection)
428
+ // -------------------------------------------------------------------------
429
+ describe('formatResult', () => {
430
+ let bridge;
431
+ let formatResult;
432
+ beforeEach(() => {
433
+ const mockGateway = createMockGateway();
434
+ bridge = new bridge_1.MCPBridge(mockGateway);
435
+ formatResult = bridge.formatResult.bind(bridge);
436
+ });
437
+ it('converts an ok ToolResult to MCPCallToolResult with isError=false', () => {
438
+ const toolResult = buildToolResult({
439
+ status: 'ok',
440
+ output: { http_status: 200, body: { success: true } },
441
+ });
442
+ const mcpResult = formatResult(toolResult);
443
+ expect(mcpResult.isError).toBe(false);
444
+ expect(mcpResult.content).toHaveLength(2);
445
+ expect(mcpResult.content[0].type).toBe('text');
446
+ });
447
+ it('converts a blocked ToolResult to MCPCallToolResult with isError=true', () => {
448
+ const toolResult = buildToolResult({
449
+ status: 'blocked',
450
+ error: 'Blocked by policy: domain not in allowlist',
451
+ output: undefined,
452
+ });
453
+ const mcpResult = formatResult(toolResult);
454
+ expect(mcpResult.isError).toBe(true);
455
+ expect(mcpResult.content[0].text).toBe('Blocked by policy: domain not in allowlist');
456
+ });
457
+ it('converts an error ToolResult to MCPCallToolResult with isError=true', () => {
458
+ const toolResult = buildToolResult({
459
+ status: 'error',
460
+ error: 'Connection timeout after 15000ms',
461
+ output: undefined,
462
+ });
463
+ const mcpResult = formatResult(toolResult);
464
+ expect(mcpResult.isError).toBe(true);
465
+ expect(mcpResult.content[0].text).toBe('Connection timeout after 15000ms');
466
+ });
467
+ it('uses the output body as primary text when no error is present', () => {
468
+ const toolResult = buildToolResult({
469
+ status: 'ok',
470
+ output: { http_status: 200, body: { items: [1, 2, 3] } },
471
+ error: undefined,
472
+ });
473
+ const mcpResult = formatResult(toolResult);
474
+ const parsed = JSON.parse(mcpResult.content[0].text);
475
+ expect(parsed).toEqual({ items: [1, 2, 3] });
476
+ });
477
+ it('uses string body directly without double-encoding', () => {
478
+ const toolResult = buildToolResult({
479
+ status: 'ok',
480
+ output: { http_status: 200, body: 'plain text response' },
481
+ error: undefined,
482
+ });
483
+ const mcpResult = formatResult(toolResult);
484
+ expect(mcpResult.content[0].text).toBe('plain text response');
485
+ });
486
+ it('shows fallback text when no output body and no error', () => {
487
+ const toolResult = buildToolResult({
488
+ status: 'ok',
489
+ output: { http_status: 204 },
490
+ error: undefined,
491
+ });
492
+ const mcpResult = formatResult(toolResult);
493
+ expect(mcpResult.content[0].text).toBe('Request completed with status: ok');
494
+ });
495
+ it('shows fallback text when output is undefined', () => {
496
+ const toolResult = buildToolResult({
497
+ status: 'ok',
498
+ output: undefined,
499
+ error: undefined,
500
+ });
501
+ const mcpResult = formatResult(toolResult);
502
+ expect(mcpResult.content[0].text).toBe('Request completed with status: ok');
503
+ });
504
+ it('includes gateway metadata as the second content block', () => {
505
+ const toolResult = buildToolResult({
506
+ tool_call_id: 'tc-meta-001',
507
+ task_id: 'task-meta-001',
508
+ status: 'ok',
509
+ output: { http_status: 200, body: 'ok' },
510
+ });
511
+ const mcpResult = formatResult(toolResult);
512
+ expect(mcpResult.content).toHaveLength(2);
513
+ expect(mcpResult.content[1].type).toBe('text');
514
+ expect(mcpResult.content[1].text).toContain('--- Gateway Metadata ---');
515
+ const metadataJson = mcpResult.content[1].text.replace('--- Gateway Metadata ---\n', '');
516
+ const metadata = JSON.parse(metadataJson);
517
+ expect(metadata.tool_call_id).toBe('tc-meta-001');
518
+ expect(metadata.task_id).toBe('task-meta-001');
519
+ expect(metadata.status).toBe('ok');
520
+ expect(metadata.http_status).toBe(200);
521
+ expect(metadata.policy).toBeDefined();
522
+ expect(metadata.dlp).toBeDefined();
523
+ expect(metadata.budget).toBeDefined();
524
+ expect(metadata.timing).toBeDefined();
525
+ });
526
+ it('includes DLP summary in metadata (detected count and severity)', () => {
527
+ const toolResult = buildToolResult({
528
+ dlp: {
529
+ detected: ['aws_key', 'email'],
530
+ redactions: [
531
+ { path: 'body.api_key', method: 'mask' },
532
+ { path: 'body.email', method: 'mask' },
533
+ ],
534
+ severity: 'high',
535
+ },
536
+ });
537
+ const mcpResult = formatResult(toolResult);
538
+ const metadataJson = mcpResult.content[1].text.replace('--- Gateway Metadata ---\n', '');
539
+ const metadata = JSON.parse(metadataJson);
540
+ expect(metadata.dlp.detected).toEqual(['aws_key', 'email']);
541
+ expect(metadata.dlp.severity).toBe('high');
542
+ expect(metadata.dlp.redaction_count).toBe(2);
543
+ });
544
+ it('prefers error over output body for primary text', () => {
545
+ const toolResult = buildToolResult({
546
+ status: 'error',
547
+ error: 'Something went wrong',
548
+ output: { http_status: 500, body: 'Internal Server Error' },
549
+ });
550
+ const mcpResult = formatResult(toolResult);
551
+ expect(mcpResult.content[0].text).toBe('Something went wrong');
552
+ });
553
+ });
554
+ // -------------------------------------------------------------------------
555
+ // executeAndFormat (private, tested via reflection)
556
+ // -------------------------------------------------------------------------
557
+ describe('executeAndFormat', () => {
558
+ it('calls gateway.execute and formats the result', async () => {
559
+ const mockGateway = createMockGateway();
560
+ const successResult = buildToolResult({
561
+ status: 'ok',
562
+ output: { http_status: 200, body: { message: 'success' } },
563
+ });
564
+ mockGateway.execute.mockResolvedValue(successResult);
565
+ const bridge = new bridge_1.MCPBridge(mockGateway);
566
+ const executeAndFormat = bridge.executeAndFormat.bind(bridge);
567
+ const toolCall = {
568
+ tool_call_id: 'tc-exec-001',
569
+ task_id: 'task-exec-001',
570
+ workspace_id: 'ws_mcp_default',
571
+ actor: { type: 'agent', id: 'mcp-agent' },
572
+ source: { platform: 'mcp' },
573
+ tool: { name: 'http.request', version: '1.0.0', capability: 'read' },
574
+ args: { method: 'GET', url: 'https://api.example.com/data' },
575
+ timestamp: new Date().toISOString(),
576
+ };
577
+ const mcpResult = await executeAndFormat(toolCall);
578
+ expect(mockGateway.execute).toHaveBeenCalledTimes(1);
579
+ expect(mockGateway.execute).toHaveBeenCalledWith(toolCall);
580
+ expect(mcpResult.isError).toBe(false);
581
+ expect(mcpResult.content).toHaveLength(2);
582
+ const parsed = JSON.parse(mcpResult.content[0].text);
583
+ expect(parsed).toEqual({ message: 'success' });
584
+ });
585
+ it('returns isError=true when gateway throws an Error', async () => {
586
+ const mockGateway = createMockGateway();
587
+ mockGateway.execute.mockRejectedValue(new Error('Gateway internal failure'));
588
+ const bridge = new bridge_1.MCPBridge(mockGateway);
589
+ const executeAndFormat = bridge.executeAndFormat.bind(bridge);
590
+ const toolCall = {
591
+ tool_call_id: 'tc-err-001',
592
+ task_id: 'task-err-001',
593
+ workspace_id: 'ws_mcp_default',
594
+ actor: { type: 'agent', id: 'mcp-agent' },
595
+ source: { platform: 'mcp' },
596
+ tool: { name: 'http.request', version: '1.0.0', capability: 'write' },
597
+ args: { method: 'POST', url: 'https://api.example.com/submit' },
598
+ timestamp: new Date().toISOString(),
599
+ };
600
+ const mcpResult = await executeAndFormat(toolCall);
601
+ expect(mcpResult.isError).toBe(true);
602
+ expect(mcpResult.content).toHaveLength(1);
603
+ const parsed = JSON.parse(mcpResult.content[0].text);
604
+ expect(parsed.error).toBe('Gateway internal failure');
605
+ expect(parsed.tool_call_id).toBe('tc-err-001');
606
+ expect(parsed.status).toBe('error');
607
+ });
608
+ it('returns isError=true when gateway throws a non-Error value', async () => {
609
+ const mockGateway = createMockGateway();
610
+ mockGateway.execute.mockRejectedValue('string error');
611
+ const bridge = new bridge_1.MCPBridge(mockGateway);
612
+ const executeAndFormat = bridge.executeAndFormat.bind(bridge);
613
+ const toolCall = {
614
+ tool_call_id: 'tc-strerr-001',
615
+ task_id: 'task-strerr-001',
616
+ workspace_id: 'ws_mcp_default',
617
+ actor: { type: 'agent', id: 'mcp-agent' },
618
+ source: { platform: 'mcp' },
619
+ tool: { name: 'http.get', version: '1.0.0', capability: 'read' },
620
+ args: { method: 'GET', url: 'https://example.com' },
621
+ timestamp: new Date().toISOString(),
622
+ };
623
+ const mcpResult = await executeAndFormat(toolCall);
624
+ expect(mcpResult.isError).toBe(true);
625
+ const parsed = JSON.parse(mcpResult.content[0].text);
626
+ expect(parsed.error).toBe('string error');
627
+ });
628
+ it('formats a blocked ToolResult from gateway correctly', async () => {
629
+ const mockGateway = createMockGateway();
630
+ const blockedResult = buildToolResult({
631
+ status: 'blocked',
632
+ error: 'Blocked by policy: DELETE not allowed',
633
+ output: undefined,
634
+ policy: {
635
+ decision: 'deny',
636
+ rule_id: 'deny-delete',
637
+ reasons: ['DELETE not allowed'],
638
+ },
639
+ });
640
+ mockGateway.execute.mockResolvedValue(blockedResult);
641
+ const bridge = new bridge_1.MCPBridge(mockGateway);
642
+ const executeAndFormat = bridge.executeAndFormat.bind(bridge);
643
+ const toolCall = {
644
+ tool_call_id: 'tc-block-001',
645
+ task_id: 'task-block-001',
646
+ workspace_id: 'ws_mcp_default',
647
+ actor: { type: 'agent', id: 'mcp-agent' },
648
+ source: { platform: 'mcp' },
649
+ tool: { name: 'http.request', version: '1.0.0', capability: 'delete' },
650
+ args: { method: 'DELETE', url: 'https://api.example.com/resource/1' },
651
+ timestamp: new Date().toISOString(),
652
+ };
653
+ const mcpResult = await executeAndFormat(toolCall);
654
+ expect(mcpResult.isError).toBe(true);
655
+ expect(mcpResult.content[0].text).toBe('Blocked by policy: DELETE not allowed');
656
+ });
657
+ });
658
+ // -------------------------------------------------------------------------
659
+ // methodToCapability (private, tested via reflection)
660
+ // -------------------------------------------------------------------------
661
+ describe('methodToCapability', () => {
662
+ let methodToCapability;
663
+ beforeEach(() => {
664
+ const mockGateway = createMockGateway();
665
+ const bridge = new bridge_1.MCPBridge(mockGateway);
666
+ methodToCapability = bridge.methodToCapability.bind(bridge);
667
+ });
668
+ it('maps GET to read', () => {
669
+ expect(methodToCapability('GET')).toBe('read');
670
+ });
671
+ it('maps HEAD to read', () => {
672
+ expect(methodToCapability('HEAD')).toBe('read');
673
+ });
674
+ it('maps OPTIONS to read', () => {
675
+ expect(methodToCapability('OPTIONS')).toBe('read');
676
+ });
677
+ it('maps POST to write', () => {
678
+ expect(methodToCapability('POST')).toBe('write');
679
+ });
680
+ it('maps PUT to write', () => {
681
+ expect(methodToCapability('PUT')).toBe('write');
682
+ });
683
+ it('maps PATCH to write', () => {
684
+ expect(methodToCapability('PATCH')).toBe('write');
685
+ });
686
+ it('maps DELETE to delete', () => {
687
+ expect(methodToCapability('DELETE')).toBe('delete');
688
+ });
689
+ it('maps unknown methods to write as default', () => {
690
+ expect(methodToCapability('CUSTOM')).toBe('write');
691
+ });
692
+ it('is case-insensitive', () => {
693
+ expect(methodToCapability('get')).toBe('read');
694
+ expect(methodToCapability('post')).toBe('write');
695
+ expect(methodToCapability('delete')).toBe('delete');
696
+ });
697
+ });
698
+ // -------------------------------------------------------------------------
699
+ // parseBody (private, tested via reflection)
700
+ // -------------------------------------------------------------------------
701
+ describe('parseBody', () => {
702
+ let parseBody;
703
+ beforeEach(() => {
704
+ const mockGateway = createMockGateway();
705
+ const bridge = new bridge_1.MCPBridge(mockGateway);
706
+ parseBody = bridge.parseBody.bind(bridge);
707
+ });
708
+ it('parses valid JSON strings into objects', () => {
709
+ const result = parseBody('{"key":"value"}');
710
+ expect(result).toEqual({ key: 'value' });
711
+ });
712
+ it('parses JSON arrays', () => {
713
+ const result = parseBody('[1, 2, 3]');
714
+ expect(result).toEqual([1, 2, 3]);
715
+ });
716
+ it('returns the raw string for invalid JSON', () => {
717
+ const result = parseBody('not json');
718
+ expect(result).toBe('not json');
719
+ });
720
+ it('parses JSON primitive strings', () => {
721
+ const result = parseBody('"hello"');
722
+ expect(result).toBe('hello');
723
+ });
724
+ it('parses JSON numbers', () => {
725
+ const result = parseBody('42');
726
+ expect(result).toBe(42);
727
+ });
728
+ });
729
+ // -------------------------------------------------------------------------
730
+ // JSON-RPC protocol handling via stdio transport
731
+ // -------------------------------------------------------------------------
732
+ describe('JSON-RPC protocol', () => {
733
+ it('responds to initialize with server info and capabilities', async () => {
734
+ const mockGateway = createMockGateway();
735
+ const bridge = new bridge_1.MCPBridge(mockGateway);
736
+ const { input, output, outputChunks } = createTestStreams();
737
+ await bridge.connectStdio(input, output);
738
+ await sendMessage(input, {
739
+ jsonrpc: '2.0',
740
+ id: 1,
741
+ method: 'initialize',
742
+ params: {
743
+ protocolVersion: '2025-03-26',
744
+ capabilities: {},
745
+ clientInfo: { name: 'test', version: '1.0' },
746
+ },
747
+ });
748
+ const response = getLastResponse(outputChunks);
749
+ expect(response.jsonrpc).toBe('2.0');
750
+ expect(response.id).toBe(1);
751
+ expect(response.result.protocolVersion).toBe('2025-03-26');
752
+ expect(response.result.serverInfo.name).toBe('palaryn-mcp-bridge');
753
+ expect(response.result.serverInfo.version).toBe('1.0.0');
754
+ expect(response.result.capabilities.tools).toBeDefined();
755
+ await bridge.close();
756
+ });
757
+ it('responds to ping with empty result', async () => {
758
+ const mockGateway = createMockGateway();
759
+ const bridge = new bridge_1.MCPBridge(mockGateway);
760
+ const { input, output, outputChunks } = createTestStreams();
761
+ await bridge.connectStdio(input, output);
762
+ await sendMessage(input, { jsonrpc: '2.0', id: 42, method: 'ping' });
763
+ const response = getLastResponse(outputChunks);
764
+ expect(response.id).toBe(42);
765
+ expect(response.result).toEqual({});
766
+ await bridge.close();
767
+ });
768
+ it('returns method not found for unknown methods', async () => {
769
+ const mockGateway = createMockGateway();
770
+ const bridge = new bridge_1.MCPBridge(mockGateway);
771
+ const { input, output, outputChunks } = createTestStreams();
772
+ await bridge.connectStdio(input, output);
773
+ await sendMessage(input, { jsonrpc: '2.0', id: 5, method: 'unknown/method' });
774
+ const response = getLastResponse(outputChunks);
775
+ expect(response.id).toBe(5);
776
+ expect(response.error).toBeDefined();
777
+ expect(response.error.code).toBe(-32601);
778
+ expect(response.error.message).toContain('Method not found');
779
+ await bridge.close();
780
+ });
781
+ it('handles notifications/initialized silently (no response)', async () => {
782
+ const mockGateway = createMockGateway();
783
+ const bridge = new bridge_1.MCPBridge(mockGateway);
784
+ const { input, output, outputChunks } = createTestStreams();
785
+ await bridge.connectStdio(input, output);
786
+ await sendMessage(input, {
787
+ jsonrpc: '2.0',
788
+ method: 'notifications/initialized',
789
+ });
790
+ const allOutput = outputChunks.join('');
791
+ expect(allOutput.trim()).toBe('');
792
+ await bridge.close();
793
+ });
794
+ it('returns parse error and clears buffer when data exceeds 10MB without newline', async () => {
795
+ const mockGateway = createMockGateway();
796
+ const bridge = new bridge_1.MCPBridge(mockGateway);
797
+ const { input, output, outputChunks } = createTestStreams();
798
+ await bridge.connectStdio(input, output);
799
+ const largeChunk = 'x'.repeat(11 * 1024 * 1024);
800
+ const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => { });
801
+ input.push(largeChunk);
802
+ await new Promise((resolve) => setTimeout(resolve, 50));
803
+ const response = getLastResponse(outputChunks);
804
+ expect(response.error).toBeDefined();
805
+ expect(response.error.code).toBe(-32700);
806
+ expect(response.error.message).toContain('Message too large');
807
+ expect(response.error.message).toContain('10MB');
808
+ expect(response.id).toBeNull();
809
+ consoleSpy.mockRestore();
810
+ await bridge.close();
811
+ });
812
+ it('processes messages normally after buffer overflow reset', async () => {
813
+ const mockGateway = createMockGateway();
814
+ mockGateway.execute.mockResolvedValue(buildToolResult());
815
+ const bridge = new bridge_1.MCPBridge(mockGateway);
816
+ const { input, output, outputChunks } = createTestStreams();
817
+ await bridge.connectStdio(input, output);
818
+ const largeChunk = 'x'.repeat(11 * 1024 * 1024);
819
+ const consoleSpy = jest.spyOn(console, 'error').mockImplementation(() => { });
820
+ input.push(largeChunk);
821
+ await new Promise((resolve) => setTimeout(resolve, 50));
822
+ consoleSpy.mockRestore();
823
+ await sendMessage(input, {
824
+ jsonrpc: '2.0',
825
+ id: 99,
826
+ method: 'ping',
827
+ });
828
+ const allOutput = outputChunks.join('');
829
+ const lines = allOutput.split('\n').filter((l) => l.trim());
830
+ const lastLine = lines[lines.length - 1];
831
+ const response = JSON.parse(lastLine);
832
+ expect(response.id).toBe(99);
833
+ expect(response.result).toEqual({});
834
+ await bridge.close();
835
+ });
836
+ it('returns parse error for invalid JSON input', async () => {
837
+ const mockGateway = createMockGateway();
838
+ const bridge = new bridge_1.MCPBridge(mockGateway);
839
+ const { input, output, outputChunks } = createTestStreams();
840
+ await bridge.connectStdio(input, output);
841
+ input.push('this is not json\n');
842
+ await new Promise((resolve) => setTimeout(resolve, 20));
843
+ const response = getLastResponse(outputChunks);
844
+ expect(response.error).toBeDefined();
845
+ expect(response.error.code).toBe(-32700);
846
+ expect(response.id).toBeNull();
847
+ await bridge.close();
848
+ });
849
+ it('returns error for tools/call with unknown tool name', async () => {
850
+ const mockGateway = createMockGateway();
851
+ const bridge = new bridge_1.MCPBridge(mockGateway);
852
+ const { input, output, outputChunks } = createTestStreams();
853
+ await bridge.connectStdio(input, output);
854
+ await sendMessage(input, {
855
+ jsonrpc: '2.0',
856
+ id: 10,
857
+ method: 'tools/call',
858
+ params: { name: 'nonexistent_tool', arguments: {} },
859
+ });
860
+ const response = getLastResponse(outputChunks);
861
+ expect(response.id).toBe(10);
862
+ expect(response.error).toBeDefined();
863
+ expect(response.error.code).toBe(-32602);
864
+ expect(response.error.message).toContain('Unknown tool');
865
+ await bridge.close();
866
+ });
867
+ it('returns error for tools/call with missing name parameter', async () => {
868
+ const mockGateway = createMockGateway();
869
+ const bridge = new bridge_1.MCPBridge(mockGateway);
870
+ const { input, output, outputChunks } = createTestStreams();
871
+ await bridge.connectStdio(input, output);
872
+ await sendMessage(input, {
873
+ jsonrpc: '2.0',
874
+ id: 11,
875
+ method: 'tools/call',
876
+ params: {},
877
+ });
878
+ const response = getLastResponse(outputChunks);
879
+ expect(response.id).toBe(11);
880
+ expect(response.error).toBeDefined();
881
+ expect(response.error.code).toBe(-32602);
882
+ expect(response.error.message).toContain('Missing required parameter: name');
883
+ await bridge.close();
884
+ });
885
+ });
886
+ // -------------------------------------------------------------------------
887
+ // End-to-end tool execution via JSON-RPC
888
+ // -------------------------------------------------------------------------
889
+ describe('tools/call via JSON-RPC', () => {
890
+ it('executes http_request tool through the gateway', async () => {
891
+ const mockGateway = createMockGateway();
892
+ const successResult = buildToolResult({
893
+ status: 'ok',
894
+ output: { http_status: 200, body: { data: 'hello' } },
895
+ });
896
+ mockGateway.execute.mockResolvedValue(successResult);
897
+ const bridge = new bridge_1.MCPBridge(mockGateway);
898
+ const { input, output, outputChunks } = createTestStreams();
899
+ await bridge.connectStdio(input, output);
900
+ await sendMessage(input, {
901
+ jsonrpc: '2.0',
902
+ id: 20,
903
+ method: 'tools/call',
904
+ params: {
905
+ name: 'http_request',
906
+ arguments: { url: 'https://api.example.com/data', method: 'GET' },
907
+ },
908
+ });
909
+ const response = getLastResponse(outputChunks);
910
+ expect(response.id).toBe(20);
911
+ expect(response.result).toBeDefined();
912
+ expect(response.result.isError).toBe(false);
913
+ expect(response.result.content).toHaveLength(2);
914
+ expect(mockGateway.execute).toHaveBeenCalledTimes(1);
915
+ const passedToolCall = mockGateway.execute.mock.calls[0][0];
916
+ expect(passedToolCall.tool.name).toBe('http.request');
917
+ expect(passedToolCall.args.method).toBe('GET');
918
+ expect(passedToolCall.args.url).toBe('https://api.example.com/data');
919
+ await bridge.close();
920
+ });
921
+ it('executes http_get tool through the gateway', async () => {
922
+ const mockGateway = createMockGateway();
923
+ const successResult = buildToolResult({
924
+ status: 'ok',
925
+ output: { http_status: 200, body: 'ok' },
926
+ });
927
+ mockGateway.execute.mockResolvedValue(successResult);
928
+ const bridge = new bridge_1.MCPBridge(mockGateway);
929
+ const { input, output, outputChunks } = createTestStreams();
930
+ await bridge.connectStdio(input, output);
931
+ await sendMessage(input, {
932
+ jsonrpc: '2.0',
933
+ id: 21,
934
+ method: 'tools/call',
935
+ params: {
936
+ name: 'http_get',
937
+ arguments: { url: 'https://api.example.com/health' },
938
+ },
939
+ });
940
+ const response = getLastResponse(outputChunks);
941
+ expect(response.result.isError).toBe(false);
942
+ const passedToolCall = mockGateway.execute.mock.calls[0][0];
943
+ expect(passedToolCall.tool.name).toBe('http.get');
944
+ expect(passedToolCall.tool.capability).toBe('read');
945
+ expect(passedToolCall.args.method).toBe('GET');
946
+ await bridge.close();
947
+ });
948
+ it('executes http_post tool through the gateway', async () => {
949
+ const mockGateway = createMockGateway();
950
+ const successResult = buildToolResult({
951
+ status: 'ok',
952
+ output: { http_status: 201, body: { id: 123 } },
953
+ });
954
+ mockGateway.execute.mockResolvedValue(successResult);
955
+ const bridge = new bridge_1.MCPBridge(mockGateway);
956
+ const { input, output, outputChunks } = createTestStreams();
957
+ await bridge.connectStdio(input, output);
958
+ await sendMessage(input, {
959
+ jsonrpc: '2.0',
960
+ id: 22,
961
+ method: 'tools/call',
962
+ params: {
963
+ name: 'http_post',
964
+ arguments: {
965
+ url: 'https://api.example.com/items',
966
+ body: '{"name":"test"}',
967
+ },
968
+ },
969
+ });
970
+ const response = getLastResponse(outputChunks);
971
+ expect(response.result.isError).toBe(false);
972
+ const passedToolCall = mockGateway.execute.mock.calls[0][0];
973
+ expect(passedToolCall.tool.name).toBe('http.post');
974
+ expect(passedToolCall.tool.capability).toBe('write');
975
+ expect(passedToolCall.args.method).toBe('POST');
976
+ expect(passedToolCall.args.body).toEqual({ name: 'test' });
977
+ await bridge.close();
978
+ });
979
+ it('returns isError=true when gateway execution fails', async () => {
980
+ const mockGateway = createMockGateway();
981
+ mockGateway.execute.mockRejectedValue(new Error('Gateway exploded'));
982
+ const bridge = new bridge_1.MCPBridge(mockGateway);
983
+ const { input, output, outputChunks } = createTestStreams();
984
+ await bridge.connectStdio(input, output);
985
+ await sendMessage(input, {
986
+ jsonrpc: '2.0',
987
+ id: 23,
988
+ method: 'tools/call',
989
+ params: {
990
+ name: 'http_get',
991
+ arguments: { url: 'https://api.example.com/fail' },
992
+ },
993
+ });
994
+ const response = getLastResponse(outputChunks);
995
+ expect(response.result).toBeDefined();
996
+ expect(response.result.isError).toBe(true);
997
+ const errorContent = JSON.parse(response.result.content[0].text);
998
+ expect(errorContent.error).toBe('Gateway exploded');
999
+ await bridge.close();
1000
+ });
1001
+ it('passes constraints and context through to the gateway', async () => {
1002
+ const mockGateway = createMockGateway();
1003
+ mockGateway.execute.mockResolvedValue(buildToolResult());
1004
+ const bridge = new bridge_1.MCPBridge(mockGateway);
1005
+ const { input, output, outputChunks } = createTestStreams();
1006
+ await bridge.connectStdio(input, output);
1007
+ await sendMessage(input, {
1008
+ jsonrpc: '2.0',
1009
+ id: 24,
1010
+ method: 'tools/call',
1011
+ params: {
1012
+ name: 'http_request',
1013
+ arguments: {
1014
+ url: 'https://api.example.com/data',
1015
+ method: 'POST',
1016
+ timeout_ms: 5000,
1017
+ max_cost_usd: 0.25,
1018
+ purpose: 'Test purpose',
1019
+ labels: ['test', 'ci'],
1020
+ },
1021
+ },
1022
+ });
1023
+ const passedToolCall = mockGateway.execute.mock.calls[0][0];
1024
+ expect(passedToolCall.constraints).toBeDefined();
1025
+ expect(passedToolCall.constraints.timeout_ms).toBe(5000);
1026
+ expect(passedToolCall.constraints.max_cost_usd).toBe(0.25);
1027
+ expect(passedToolCall.context).toBeDefined();
1028
+ expect(passedToolCall.context.purpose).toBe('Test purpose');
1029
+ expect(passedToolCall.context.labels).toEqual(['test', 'ci']);
1030
+ await bridge.close();
1031
+ });
1032
+ it('returns tool error when http_request is called without url', async () => {
1033
+ const mockGateway = createMockGateway();
1034
+ const bridge = new bridge_1.MCPBridge(mockGateway);
1035
+ const { input, output, outputChunks } = createTestStreams();
1036
+ await bridge.connectStdio(input, output);
1037
+ await sendMessage(input, {
1038
+ jsonrpc: '2.0',
1039
+ id: 30,
1040
+ method: 'tools/call',
1041
+ params: {
1042
+ name: 'http_request',
1043
+ arguments: { method: 'GET' },
1044
+ },
1045
+ });
1046
+ const response = getLastResponse(outputChunks);
1047
+ expect(response.result).toBeDefined();
1048
+ expect(response.result.isError).toBe(true);
1049
+ expect(response.result.content[0].text).toContain('Missing required argument: url');
1050
+ expect(mockGateway.execute).not.toHaveBeenCalled();
1051
+ await bridge.close();
1052
+ });
1053
+ it('returns tool error when http_get is called without url', async () => {
1054
+ const mockGateway = createMockGateway();
1055
+ const bridge = new bridge_1.MCPBridge(mockGateway);
1056
+ const { input, output, outputChunks } = createTestStreams();
1057
+ await bridge.connectStdio(input, output);
1058
+ await sendMessage(input, {
1059
+ jsonrpc: '2.0',
1060
+ id: 31,
1061
+ method: 'tools/call',
1062
+ params: {
1063
+ name: 'http_get',
1064
+ arguments: {},
1065
+ },
1066
+ });
1067
+ const response = getLastResponse(outputChunks);
1068
+ expect(response.result.isError).toBe(true);
1069
+ expect(response.result.content[0].text).toContain('Missing required argument: url');
1070
+ await bridge.close();
1071
+ });
1072
+ it('returns tool error when http_post is called without url', async () => {
1073
+ const mockGateway = createMockGateway();
1074
+ const bridge = new bridge_1.MCPBridge(mockGateway);
1075
+ const { input, output, outputChunks } = createTestStreams();
1076
+ await bridge.connectStdio(input, output);
1077
+ await sendMessage(input, {
1078
+ jsonrpc: '2.0',
1079
+ id: 32,
1080
+ method: 'tools/call',
1081
+ params: {
1082
+ name: 'http_post',
1083
+ arguments: { body: '{"data":"test"}' },
1084
+ },
1085
+ });
1086
+ const response = getLastResponse(outputChunks);
1087
+ expect(response.result.isError).toBe(true);
1088
+ expect(response.result.content[0].text).toContain('Missing required argument: url');
1089
+ await bridge.close();
1090
+ });
1091
+ it('defaults http_request method to GET when not specified', async () => {
1092
+ const mockGateway = createMockGateway();
1093
+ mockGateway.execute.mockResolvedValue(buildToolResult());
1094
+ const bridge = new bridge_1.MCPBridge(mockGateway);
1095
+ const { input, output, outputChunks } = createTestStreams();
1096
+ await bridge.connectStdio(input, output);
1097
+ await sendMessage(input, {
1098
+ jsonrpc: '2.0',
1099
+ id: 33,
1100
+ method: 'tools/call',
1101
+ params: {
1102
+ name: 'http_request',
1103
+ arguments: { url: 'https://example.com' },
1104
+ },
1105
+ });
1106
+ const passedToolCall = mockGateway.execute.mock.calls[0][0];
1107
+ expect(passedToolCall.args.method).toBe('GET');
1108
+ expect(passedToolCall.tool.capability).toBe('read');
1109
+ await bridge.close();
1110
+ });
1111
+ });
1112
+ // -------------------------------------------------------------------------
1113
+ // toolError (private, tested via reflection)
1114
+ // -------------------------------------------------------------------------
1115
+ describe('toolError', () => {
1116
+ it('creates an error result with isError=true', () => {
1117
+ const mockGateway = createMockGateway();
1118
+ const bridge = new bridge_1.MCPBridge(mockGateway);
1119
+ const toolError = bridge.toolError.bind(bridge);
1120
+ const result = toolError('Something broke');
1121
+ expect(result.isError).toBe(true);
1122
+ expect(result.content).toHaveLength(1);
1123
+ expect(result.content[0].type).toBe('text');
1124
+ expect(result.content[0].text).toBe('Something broke');
1125
+ });
1126
+ });
1127
+ // -------------------------------------------------------------------------
1128
+ // startMCPBridge export
1129
+ // -------------------------------------------------------------------------
1130
+ describe('startMCPBridge', () => {
1131
+ it('is exported as a function', () => {
1132
+ expect(typeof bridge_1.startMCPBridge).toBe('function');
1133
+ });
1134
+ });
1135
+ });
1136
+ //# sourceMappingURL=mcp-bridge.test.js.map