@langwatch/mcp-server 2.1.0 → 2.2.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 (415) hide show
  1. package/.gitignore +7 -0
  2. package/.npmignore +41 -0
  3. package/CHANGELOG.md +192 -201
  4. package/CONTRIBUTING.md +10 -0
  5. package/README.md +70 -68
  6. package/dist/add-dashboard-widget-4ZC5UN73.js +50 -0
  7. package/dist/add-dashboard-widget-ACC6QYBG.js +49 -0
  8. package/dist/archive-project-KWFM3I5Z.js +19 -0
  9. package/dist/{archive-project-W3IKI7UL.js → archive-project-SPVHXK6A.js} +4 -4
  10. package/dist/{archive-run-plan-474OKHHU.js → archive-run-plan-65QN3XRX.js} +4 -4
  11. package/dist/archive-run-plan-Z4SYE6BP.js +15 -0
  12. package/dist/{archive-scenario-V5UNYK26.js → archive-scenario-A6QV5LOW.js} +4 -4
  13. package/dist/archive-scenario-FXEYQNEH.js +19 -0
  14. package/dist/archive-test-suite-5ADQ3KPQ.js +15 -0
  15. package/dist/{archive-test-suite-O7FLDMXK.js → archive-test-suite-NKZDZY3D.js} +4 -4
  16. package/dist/assign-prompt-tag-5IUZXJ2T.js +23 -0
  17. package/dist/{assign-prompt-tag-Q46WRB6P.js → assign-prompt-tag-HBZLBIFD.js} +8 -4
  18. package/dist/{chunk-OJLYYJWI.js → chunk-3OCG3QPU.js} +7 -10
  19. package/dist/chunk-3YLN3ATJ.js +241 -0
  20. package/dist/chunk-5FNCQRCX.js +91 -0
  21. package/dist/{chunk-UF6XIGYR.js → chunk-6YZUOD34.js} +2 -5
  22. package/dist/{chunk-WVI5CJ6S.js → chunk-7AQLPVFL.js} +2 -2
  23. package/dist/chunk-7VNSRCXO.js +129 -0
  24. package/dist/{chunk-G4ZAZTZW.js → chunk-B4J3PJLX.js} +11 -10
  25. package/dist/{chunk-ZXKLPC2E.js → chunk-BIIKE3EI.js} +1 -1
  26. package/dist/{chunk-37SMPMQS.js → chunk-DMM3JQ42.js} +7 -2
  27. package/dist/{chunk-NUFPTHKU.js → chunk-FOKVY3BK.js} +1 -1
  28. package/dist/{chunk-WT4PO4A3.js → chunk-H6WGMG2T.js} +10 -7
  29. package/dist/{chunk-EYXMLE5X.js → chunk-HGELNIVY.js} +56 -47
  30. package/dist/{chunk-UJPXFA26.js → chunk-HHMNZAPD.js} +101 -74
  31. package/dist/{chunk-RVAE2MEA.js → chunk-I7ATIZED.js} +7 -7
  32. package/dist/{chunk-S4GVX36W.js → chunk-ILZDO25L.js} +7 -10
  33. package/dist/{chunk-QRO35SOM.js → chunk-J7PQKBLD.js} +652 -528
  34. package/dist/{chunk-SR6QPRIE.js → chunk-JRJPH75H.js} +7 -13
  35. package/dist/chunk-K2I7Z3LZ.js +28 -0
  36. package/dist/{chunk-UFOYFYI7.js → chunk-NKMTIHIJ.js} +6 -9
  37. package/dist/{chunk-HV4DWKBI.js → chunk-O7EMWEVJ.js} +2 -4
  38. package/dist/{chunk-56OGTS24.js → chunk-PTDR56Z3.js} +4 -11
  39. package/dist/{chunk-JR6DJZOU.js → chunk-RABYBFX5.js} +4 -10
  40. package/dist/chunk-SUPUUC4W.js +64 -0
  41. package/dist/{chunk-JTQJRNFJ.js → chunk-SXFC3FNH.js} +4 -4
  42. package/dist/{chunk-CKNAH5TG.js → chunk-U24EACC4.js} +3 -10
  43. package/dist/chunk-UNJM3BIL.js +21 -0
  44. package/dist/{chunk-QAMP3CGR.js → chunk-UTJCWXPZ.js} +4 -4
  45. package/dist/chunk-UWLE5T2F.js +312 -0
  46. package/dist/{chunk-6DJDEXET.js → chunk-VMWJD732.js} +6 -12
  47. package/dist/{chunk-7E4FZIYJ.js → chunk-XUIJYNRJ.js} +1 -1
  48. package/dist/chunk-Y34DHR5K.js +67 -0
  49. package/dist/chunk-YCARO42Z.js +516 -0
  50. package/dist/config-EAKW6F26.js +16 -0
  51. package/dist/config.cjs +109 -0
  52. package/dist/config.js +5 -4
  53. package/dist/{create-agent-JFJ2K6X3.js → create-agent-AYZ3LB3V.js} +4 -4
  54. package/dist/create-agent-S5ZBCEFT.js +23 -0
  55. package/dist/create-annotation-LHNCLHYP.js +29 -0
  56. package/dist/{create-annotation-EUXXQ6NS.js → create-annotation-VUSXULQK.js} +10 -5
  57. package/dist/{create-api-key-BGBB76JX.js → create-api-key-NNYKARMG.js} +5 -7
  58. package/dist/create-api-key-T4KSNPKB.js +24 -0
  59. package/dist/create-dashboard-DHVFMEU4.js +18 -0
  60. package/dist/{create-dashboard-Z76AIPLS.js → create-dashboard-WTKKS5Z6.js} +4 -4
  61. package/dist/{create-dataset-FD5VAXU4.js → create-dataset-2MC4SD4O.js} +6 -6
  62. package/dist/create-dataset-LUP5PVDM.js +22 -0
  63. package/dist/create-dataset-records-NSKBZP3G.js +20 -0
  64. package/dist/{create-dataset-records-WQUXXOHI.js → create-dataset-records-W4GLN2FV.js} +5 -7
  65. package/dist/{create-evaluator-6NV33Z52.js → create-evaluator-643G6LXL.js} +4 -4
  66. package/dist/create-evaluator-HMTYO5SS.js +27 -0
  67. package/dist/create-mcp-server.cjs +9513 -0
  68. package/dist/create-mcp-server.js +2647 -10
  69. package/dist/create-project-6N7PIYRY.js +44 -0
  70. package/dist/{create-project-ZQ7TFIO2.js → create-project-KAIC46UY.js} +4 -4
  71. package/dist/{create-prompt-BS6QRCUU.js → create-prompt-E67LXIYT.js} +5 -7
  72. package/dist/create-prompt-PKJ7EDRR.js +37 -0
  73. package/dist/create-prompt-tag-4YO3FSJ3.js +17 -0
  74. package/dist/{create-prompt-tag-FCQ5ELSQ.js → create-prompt-tag-OPQUEXWB.js} +3 -3
  75. package/dist/create-scenario-5PJBMAXO.js +33 -0
  76. package/dist/{create-scenario-DRPTPXU3.js → create-scenario-UEM77OE7.js} +5 -5
  77. package/dist/{create-test-suite-ZIZCCFJ4.js → create-test-suite-4IPXVSFQ.js} +6 -6
  78. package/dist/create-test-suite-SZEPVXSE.js +36 -0
  79. package/dist/create-trigger-5HZSCRAN.js +49 -0
  80. package/dist/create-trigger-PRELMT6J.js +48 -0
  81. package/dist/delete-agent-JUIZWAXG.js +18 -0
  82. package/dist/{delete-agent-SA4JMUZF.js → delete-agent-QMEK3XHK.js} +4 -4
  83. package/dist/delete-annotation-HY3O2OGR.js +17 -0
  84. package/dist/{delete-annotation-X3KKK7QY.js → delete-annotation-VKIQWUYW.js} +4 -4
  85. package/dist/delete-dashboard-76N66XXL.js +18 -0
  86. package/dist/{delete-dashboard-LYMRPF27.js → delete-dashboard-JVIVHNJM.js} +4 -4
  87. package/dist/delete-dataset-HQCR66MG.js +18 -0
  88. package/dist/{delete-dataset-W6EZ44HY.js → delete-dataset-WKRUW4FP.js} +5 -7
  89. package/dist/{delete-dataset-records-7CNOTALB.js → delete-dataset-records-C2IQFFVQ.js} +5 -7
  90. package/dist/delete-dataset-records-SDUHQLLM.js +19 -0
  91. package/dist/delete-evaluator-4SNSCLEE.js +16 -0
  92. package/dist/delete-evaluator-QP3QHTLN.js +15 -0
  93. package/dist/delete-prompt-tag-NZ4JQMYG.js +17 -0
  94. package/dist/{delete-prompt-tag-QB3OCZUV.js → delete-prompt-tag-YYQ4VMAD.js} +3 -3
  95. package/dist/{delete-workflow-NG2VXYYM.js → delete-workflow-5J46T73T.js} +4 -4
  96. package/dist/delete-workflow-JSWABL5F.js +17 -0
  97. package/dist/discover-evaluator-schema-UHWA5HVL.js +120 -0
  98. package/dist/discover-evaluator-schema-XKCLLCB3.js +118 -0
  99. package/dist/discover-scenario-schema-NOJY6S7E.js +137 -0
  100. package/dist/{discover-scenario-schema-W7KBXA4J.js → discover-scenario-schema-OOFIQKOG.js} +37 -17
  101. package/dist/{discover-schema-L6QDPON3.js → discover-schema-55YJBFGQ.js} +181 -186
  102. package/dist/discover-schema-A33SLMXW.js +442 -0
  103. package/dist/get-agent-LJQX7OF4.js +69 -0
  104. package/dist/{get-agent-7THF7HCU.js → get-agent-OTIYUFZA.js} +15 -11
  105. package/dist/get-analytics-DCS72353.js +80 -0
  106. package/dist/get-analytics-IQZKJHLW.js +79 -0
  107. package/dist/get-dashboard-5NYJEIX2.js +29 -0
  108. package/dist/{get-dashboard-P4WWVHFU.js → get-dashboard-QMTYDQAZ.js} +4 -4
  109. package/dist/get-dataset-ALSP7RTX.js +64 -0
  110. package/dist/get-dataset-X4B5JDG2.js +63 -0
  111. package/dist/{get-evaluator-RZ44UIN6.js → get-evaluator-ISMC4LLI.js} +4 -4
  112. package/dist/get-evaluator-XEUZGZCH.js +47 -0
  113. package/dist/{get-project-QSQEAB33.js → get-project-5VKROSDH.js} +4 -4
  114. package/dist/get-project-RM6FRCBK.js +26 -0
  115. package/dist/{get-prompt-GVKXF5NL.js → get-prompt-OWXJZHQQ.js} +53 -58
  116. package/dist/get-prompt-W52DY36E.js +94 -0
  117. package/dist/{get-run-plan-NY7J5GRE.js → get-run-plan-BE3M2AUX.js} +7 -9
  118. package/dist/get-run-plan-RUXQ6PAX.js +58 -0
  119. package/dist/get-scenario-RNM7BAX2.js +37 -0
  120. package/dist/{get-scenario-Q3LLQE4T.js → get-scenario-YM4ODWW6.js} +5 -5
  121. package/dist/get-simulation-run-6GIATQE5.js +69 -0
  122. package/dist/{get-simulation-run-WINEYDMK.js → get-simulation-run-LCFSMPGQ.js} +26 -22
  123. package/dist/{get-test-suite-4HKA7OKS.js → get-test-suite-GC2O7YH6.js} +5 -5
  124. package/dist/get-test-suite-P5LJ6VLF.js +48 -0
  125. package/dist/get-trace-KBRVV4NN.js +45 -0
  126. package/dist/{get-trace-RCUV7N4O.js → get-trace-SEF3FMR5.js} +11 -13
  127. package/dist/get-workflow-7PVED7JE.js +25 -0
  128. package/dist/{get-workflow-EEOC7Y45.js → get-workflow-YXULW4GC.js} +4 -4
  129. package/dist/{http-security-EU3CHTY7.js → http-security-DNLBNQZL.js} +2 -2
  130. package/dist/{http-server-WCDUARTH.js → http-server-IR4QOS43.js} +4071 -347
  131. package/dist/index.js +58671 -37983
  132. package/dist/langwatch-api-annotations-6SA4OB4C.js +17 -0
  133. package/dist/{langwatch-api-annotations-6DHVZWUK.js → langwatch-api-annotations-TBHLCQ5R.js} +4 -4
  134. package/dist/langwatch-api-dashboards-6EO2VVZW.js +17 -0
  135. package/dist/{langwatch-api-dashboards-2KJL2M42.js → langwatch-api-dashboards-NHU5BZD7.js} +4 -4
  136. package/dist/langwatch-api-monitors-CBF3FGIM.js +47 -0
  137. package/dist/{langwatch-api-monitors-6ISFJ7WX.js → langwatch-api-monitors-YNWA2M5Y.js} +11 -16
  138. package/dist/langwatch-api-secrets-ADTOJELP.js +56 -0
  139. package/dist/langwatch-api-secrets-RNFO4LF2.js +55 -0
  140. package/dist/langwatch-api-triggers-KQDQC6L5.js +22 -0
  141. package/dist/langwatch-api-triggers-QS7HDIVW.js +23 -0
  142. package/dist/{langwatch-api-workflows-6UJL5U4Z.js → langwatch-api-workflows-OKC4FXIF.js} +4 -4
  143. package/dist/langwatch-api-workflows-XGJE6SR5.js +15 -0
  144. package/dist/{list-agents-AMVDQPQL.js → list-agents-HVYWEQMI.js} +4 -4
  145. package/dist/list-agents-RBAOCF53.js +42 -0
  146. package/dist/list-annotations-HJH4NBLD.js +38 -0
  147. package/dist/{list-annotations-6PR5NLIC.js → list-annotations-J4UAHSUN.js} +10 -5
  148. package/dist/{list-api-keys-GVPNBIHQ.js → list-api-keys-7UTJRZDG.js} +10 -5
  149. package/dist/list-api-keys-A2PMV435.js +42 -0
  150. package/dist/list-dashboard-boards-OPWROHMG.js +33 -0
  151. package/dist/list-dashboard-boards-U2GJ7GC5.js +34 -0
  152. package/dist/{list-dashboards-Z3BZKDGF.js → list-dashboards-ROSYFLDQ.js} +4 -4
  153. package/dist/list-dashboards-RQ635ZU7.js +32 -0
  154. package/dist/{list-dataset-records-O67HZ35Y.js → list-dataset-records-M5TKA6LK.js} +14 -10
  155. package/dist/list-dataset-records-WFM7F33C.js +43 -0
  156. package/dist/{list-datasets-WXZL6UMP.js → list-datasets-B5X4P7BQ.js} +9 -13
  157. package/dist/list-datasets-THUKQHPX.js +56 -0
  158. package/dist/{list-evaluators-PAX72WCN.js → list-evaluators-MYH2OVCI.js} +5 -7
  159. package/dist/list-evaluators-NLVWF6K7.js +32 -0
  160. package/dist/{list-model-providers-VVLZMLCX.js → list-model-providers-3JUGP4YI.js} +4 -4
  161. package/dist/list-model-providers-H3XKORFO.js +35 -0
  162. package/dist/{list-projects-AQVI6SUK.js → list-projects-6EJ5YYEW.js} +5 -7
  163. package/dist/list-projects-YNNSRMLC.js +40 -0
  164. package/dist/{list-prompt-tags-WZFBJOHG.js → list-prompt-tags-NVFQO6WX.js} +3 -3
  165. package/dist/list-prompt-tags-XUQTTAH4.js +23 -0
  166. package/dist/{list-prompts-BQOHRHYT.js → list-prompts-HSIMMHTF.js} +4 -6
  167. package/dist/list-prompts-IRI6AIXO.js +30 -0
  168. package/dist/{list-run-plans-UBAU2DGH.js → list-run-plans-PX2XI6EK.js} +5 -5
  169. package/dist/list-run-plans-QALLCN2K.js +45 -0
  170. package/dist/{list-scenarios-MS4IUIVM.js → list-scenarios-QZRPVHS4.js} +13 -11
  171. package/dist/list-scenarios-SSJQEMJP.js +45 -0
  172. package/dist/list-simulation-runs-KNOLGMDV.js +53 -0
  173. package/dist/{list-simulation-runs-D6YF4ZOU.js → list-simulation-runs-OEADLLQR.js} +12 -8
  174. package/dist/list-test-suites-ANAQBLDG.js +34 -0
  175. package/dist/{list-test-suites-L7R2O6DR.js → list-test-suites-N4WVLBKK.js} +4 -4
  176. package/dist/list-trigger-fires-JV3WFYYG.js +19 -0
  177. package/dist/list-trigger-fires-WTW4DVIW.js +20 -0
  178. package/dist/list-triggers-SQNIG35Q.js +45 -0
  179. package/dist/list-triggers-V4Y356AK.js +46 -0
  180. package/dist/{list-workflows-6MV4XJHF.js → list-workflows-IMSX7SXU.js} +5 -7
  181. package/dist/list-workflows-ZHTX5LCL.js +33 -0
  182. package/dist/{rename-prompt-tag-K5LQ4CY6.js → rename-prompt-tag-IQI7MHOI.js} +3 -3
  183. package/dist/rename-prompt-tag-PA2BRIE6.js +18 -0
  184. package/dist/{rename-test-suite-NTYHAO5X.js → rename-test-suite-4J7UX5S2.js} +4 -4
  185. package/dist/rename-test-suite-NNVLMLKY.js +20 -0
  186. package/dist/report-issue-AS34ZMJF.js +898 -0
  187. package/dist/{report-issue-RHZZ33IS.js → report-issue-DCZFLGDO.js} +81 -67
  188. package/dist/{rerun-run-plan-MUDWJBE4.js → rerun-run-plan-TG2V4WM4.js} +5 -5
  189. package/dist/rerun-run-plan-ZI5OY3VA.js +21 -0
  190. package/dist/revoke-api-key-3J3ZA4UB.js +15 -0
  191. package/dist/{revoke-api-key-6LAZ3QZZ.js → revoke-api-key-SFMEFWJC.js} +4 -4
  192. package/dist/run-agent-W4VJP72Z.js +75 -0
  193. package/dist/run-agent-YNV6ZHW4.js +74 -0
  194. package/dist/run-plan-6C77GWG4.js +38 -0
  195. package/dist/{run-plan-T4ZYOZGA.js → run-plan-PAARQSI4.js} +7 -7
  196. package/dist/run-query-KJDH544C.js +47 -0
  197. package/dist/run-query-T4QW2Z5O.js +46 -0
  198. package/dist/{run-test-suite-L4MZ7G4D.js → run-test-suite-SQJELEZX.js} +6 -6
  199. package/dist/run-test-suite-STB6NUJM.js +25 -0
  200. package/dist/{run-workflow-DFIS23AP.js → run-workflow-D6PQNBI6.js} +4 -4
  201. package/dist/run-workflow-SP32SO5M.js +36 -0
  202. package/dist/search-traces-HCBGOG6R.js +170 -0
  203. package/dist/search-traces-HSTQ6WTQ.js +171 -0
  204. package/dist/{set-model-provider-XRNMFOVR.js → set-model-provider-GF7NXUJA.js} +4 -4
  205. package/dist/set-model-provider-KDQ7C4EZ.js +33 -0
  206. package/dist/{update-agent-DUWNYBNA.js → update-agent-DWG43T2P.js} +4 -4
  207. package/dist/update-agent-MXNOBS4H.js +20 -0
  208. package/dist/update-dashboard-widget-CJKJLCDY.js +71 -0
  209. package/dist/update-dashboard-widget-CYQDS2NR.js +70 -0
  210. package/dist/update-dataset-DQH3OHOU.js +22 -0
  211. package/dist/{update-dataset-YSOMDCOB.js → update-dataset-SQZ6T4WX.js} +6 -6
  212. package/dist/{update-dataset-record-DEA2ITYR.js → update-dataset-record-3AT4IPBC.js} +4 -4
  213. package/dist/update-dataset-record-7PLDGTMR.js +19 -0
  214. package/dist/update-evaluator-4LFTKFPT.js +24 -0
  215. package/dist/{update-evaluator-NH7Q5GWV.js → update-evaluator-IGTX6PX4.js} +4 -4
  216. package/dist/{update-project-Q26FBSYH.js → update-project-PLJIOZS6.js} +4 -4
  217. package/dist/update-project-QVHSJRUP.js +24 -0
  218. package/dist/update-prompt-GDKRYJI3.js +140 -0
  219. package/dist/{update-prompt-ZQQ2C3KU.js → update-prompt-MY263DYF.js} +27 -29
  220. package/dist/update-scenario-7KA3JDXA.js +34 -0
  221. package/dist/{update-scenario-WUX34JAR.js → update-scenario-MOHRQKAQ.js} +5 -5
  222. package/package.json +35 -39
  223. package/pyproject.toml +8 -8
  224. package/src/config.d.ts +4 -0
  225. package/src/config.ts +20 -29
  226. package/src/create-mcp-server.ts +755 -609
  227. package/src/documentation-fetch.ts +21 -20
  228. package/src/http-security.ts +166 -135
  229. package/src/http-server.ts +189 -224
  230. package/src/index.ts +14 -16
  231. package/src/langwatch-api-agents.ts +18 -21
  232. package/src/langwatch-api-annotations.ts +11 -12
  233. package/src/langwatch-api-api-keys.ts +8 -9
  234. package/src/langwatch-api-boards.ts +46 -0
  235. package/src/langwatch-api-dashboard-widgets.ts +112 -0
  236. package/src/langwatch-api-dashboards.ts +13 -10
  237. package/src/langwatch-api-datasets.ts +98 -32
  238. package/src/langwatch-api-evaluators.ts +13 -19
  239. package/src/langwatch-api-model-providers.ts +4 -4
  240. package/src/langwatch-api-monitors.ts +14 -18
  241. package/src/langwatch-api-projects.ts +27 -9
  242. package/src/langwatch-api-query.ts +114 -0
  243. package/src/langwatch-api-run-plans.ts +12 -29
  244. package/src/langwatch-api-scenarios.ts +9 -11
  245. package/src/langwatch-api-secrets.ts +31 -24
  246. package/src/langwatch-api-simulation-runs.ts +6 -8
  247. package/src/langwatch-api-test-suites.ts +14 -33
  248. package/src/langwatch-api-triggers.ts +96 -34
  249. package/src/langwatch-api-workflows.ts +9 -9
  250. package/src/langwatch-api.ts +108 -124
  251. package/src/public-http-request.ts +21 -12
  252. package/src/schemas/analytics-metrics.ts +3 -15
  253. package/src/schemas/create-dataset.ts +3 -4
  254. package/src/schemas/run-plan.ts +13 -29
  255. package/src/schemas/suite-fields.ts +6 -23
  256. package/src/schemas/triggers.ts +365 -0
  257. package/src/tools/add-dashboard-widget.ts +63 -0
  258. package/src/tools/archive-project.ts +2 -4
  259. package/src/tools/archive-run-plan.ts +2 -4
  260. package/src/tools/archive-scenario.ts +2 -4
  261. package/src/tools/archive-test-suite.ts +2 -4
  262. package/src/tools/assign-prompt-tag.ts +6 -2
  263. package/src/tools/create-agent.ts +1 -1
  264. package/src/tools/create-annotation.ts +7 -2
  265. package/src/tools/create-api-key.ts +4 -6
  266. package/src/tools/create-dashboard.ts +1 -1
  267. package/src/tools/create-dataset-records.ts +4 -8
  268. package/src/tools/create-dataset.ts +5 -7
  269. package/src/tools/create-evaluator.ts +3 -5
  270. package/src/tools/create-project.ts +2 -2
  271. package/src/tools/create-prompt-tag.ts +2 -4
  272. package/src/tools/create-prompt.ts +9 -18
  273. package/src/tools/create-scenario.ts +5 -7
  274. package/src/tools/create-test-suite.ts +4 -9
  275. package/src/tools/create-trigger.ts +55 -0
  276. package/src/tools/dashboard-exists.ts +25 -0
  277. package/src/tools/dashboard-widget-queries.ts +32 -0
  278. package/src/tools/delete-agent.ts +1 -1
  279. package/src/tools/delete-annotation.ts +1 -1
  280. package/src/tools/delete-dashboard.ts +1 -1
  281. package/src/tools/delete-dataset-records.ts +4 -8
  282. package/src/tools/delete-dataset.ts +3 -7
  283. package/src/tools/delete-evaluator.ts +4 -6
  284. package/src/tools/delete-prompt-tag.ts +2 -4
  285. package/src/tools/delete-workflow.ts +1 -1
  286. package/src/tools/discover-evaluator-schema.ts +34 -25
  287. package/src/tools/discover-scenario-schema.ts +41 -21
  288. package/src/tools/discover-schema.ts +149 -27
  289. package/src/tools/experiment-run-status.ts +2 -8
  290. package/src/tools/format-dataset-mutation.ts +5 -10
  291. package/src/tools/format-run-plan.ts +7 -21
  292. package/src/tools/format-scenario.ts +2 -4
  293. package/src/tools/format-suite-details.ts +6 -12
  294. package/src/tools/get-agent.ts +15 -6
  295. package/src/tools/get-analytics.ts +52 -46
  296. package/src/tools/get-dashboard.ts +1 -1
  297. package/src/tools/get-dataset.ts +34 -16
  298. package/src/tools/get-evaluator.ts +4 -11
  299. package/src/tools/get-experiment-results.ts +219 -186
  300. package/src/tools/get-project.ts +2 -4
  301. package/src/tools/get-prompt.ts +63 -84
  302. package/src/tools/get-run-plan.ts +4 -6
  303. package/src/tools/get-scenario.ts +4 -6
  304. package/src/tools/get-simulation-run.ts +27 -24
  305. package/src/tools/get-test-suite.ts +2 -5
  306. package/src/tools/get-trace.ts +16 -17
  307. package/src/tools/get-workflow.ts +1 -1
  308. package/src/tools/list-agents.ts +1 -1
  309. package/src/tools/list-annotations.ts +8 -5
  310. package/src/tools/list-api-keys.ts +7 -2
  311. package/src/tools/list-dashboard-boards.ts +28 -0
  312. package/src/tools/list-dashboards.ts +1 -1
  313. package/src/tools/list-dataset-records.ts +8 -6
  314. package/src/tools/list-datasets.ts +12 -23
  315. package/src/tools/list-evaluators.ts +4 -8
  316. package/src/tools/list-experiment-runs.ts +5 -9
  317. package/src/tools/list-experiments.ts +5 -11
  318. package/src/tools/list-model-providers.ts +3 -5
  319. package/src/tools/list-projects.ts +2 -4
  320. package/src/tools/list-prompt-tags.ts +3 -3
  321. package/src/tools/list-prompts.ts +4 -8
  322. package/src/tools/list-run-plans.ts +2 -2
  323. package/src/tools/list-scenarios.ts +14 -15
  324. package/src/tools/list-simulation-runs.ts +8 -4
  325. package/src/tools/list-test-suites.ts +1 -1
  326. package/src/tools/list-trigger-fires.ts +17 -0
  327. package/src/tools/list-triggers.ts +39 -0
  328. package/src/tools/list-workflows.ts +2 -4
  329. package/src/tools/rename-prompt-tag.ts +1 -1
  330. package/src/tools/rename-test-suite.ts +2 -5
  331. package/src/tools/report-issue.ts +5 -9
  332. package/src/tools/rerun-run-plan.ts +2 -5
  333. package/src/tools/revoke-api-key.ts +2 -4
  334. package/src/tools/run-agent.ts +29 -26
  335. package/src/tools/run-experiment.ts +36 -37
  336. package/src/tools/run-plan.ts +4 -7
  337. package/src/tools/run-query.ts +47 -0
  338. package/src/tools/run-test-suite.ts +4 -7
  339. package/src/tools/run-workflow.ts +2 -5
  340. package/src/tools/search-traces.ts +161 -47
  341. package/src/tools/set-model-provider.ts +4 -6
  342. package/src/tools/test-agent.ts +2 -2
  343. package/src/tools/update-agent.ts +1 -1
  344. package/src/tools/update-dashboard-widget.ts +97 -0
  345. package/src/tools/update-dataset-record.ts +1 -1
  346. package/src/tools/update-dataset.ts +5 -7
  347. package/src/tools/update-evaluator.ts +3 -5
  348. package/src/tools/update-project.ts +1 -1
  349. package/src/tools/update-prompt.ts +44 -59
  350. package/src/tools/update-scenario.ts +5 -7
  351. package/src/tools/update-test-suite.ts +6 -11
  352. package/src/utils/date-parsing.ts +1 -1
  353. package/src/utils/escape-markdown.ts +3 -8
  354. package/src/utils/format-evaluations.ts +6 -6
  355. package/src/utils/markdown-table.ts +58 -0
  356. package/src/utils/trace-id-shape.ts +20 -0
  357. package/tsconfig.app.json +24 -0
  358. package/tsconfig.json +14 -12
  359. package/vitest.integration.config.js +10 -0
  360. package/vitest.scenario.config.js +9 -0
  361. package/.eslintrc.cjs +0 -36
  362. package/dist/chunk-AN4OUA4D.js +0 -86
  363. package/dist/chunk-GELJ2MQR.js +0 -226
  364. package/dist/chunk-K37ZNPXK.js +0 -24
  365. package/dist/config.d.ts +0 -17
  366. package/dist/create-mcp-server.d.ts +0 -10
  367. package/dist/delete-evaluator-RKYC4TNB.js +0 -16
  368. package/dist/discover-evaluator-schema-ESSHQGPY.js +0 -1912
  369. package/dist/get-analytics-R6AZD3AW.js +0 -73
  370. package/dist/get-dataset-AKTYSQDI.js +0 -49
  371. package/dist/index.d.ts +0 -2
  372. package/dist/langwatch-api-secrets-OEBD6GBU.js +0 -41
  373. package/dist/langwatch-api-triggers-5E6DDGX6.js +0 -31
  374. package/dist/run-agent-CAGWGTYT.js +0 -72
  375. package/dist/search-traces-4XTJYXIK.js +0 -79
  376. package/src/__tests__/agent-tools.unit.test.ts +0 -257
  377. package/src/__tests__/all-tools.integration.test.ts +0 -1872
  378. package/src/__tests__/config.unit.test.ts +0 -202
  379. package/src/__tests__/datasets.integration.test.ts +0 -620
  380. package/src/__tests__/datasets.unit.test.ts +0 -244
  381. package/src/__tests__/date-parsing.unit.test.ts +0 -78
  382. package/src/__tests__/discover-evaluator-schema.unit.test.ts +0 -89
  383. package/src/__tests__/discover-schema.unit.test.ts +0 -118
  384. package/src/__tests__/docker.integration.test.ts +0 -318
  385. package/src/__tests__/documentation-fetch-security.integration.test.ts +0 -182
  386. package/src/__tests__/evaluator-tools.unit.test.ts +0 -262
  387. package/src/__tests__/experiment-list-tools.unit.test.ts +0 -157
  388. package/src/__tests__/experiment-status.unit.test.ts +0 -194
  389. package/src/__tests__/feature-map-tool-drift.unit.test.ts +0 -93
  390. package/src/__tests__/get-experiment-results.unit.test.ts +0 -313
  391. package/src/__tests__/http-security-origin.unit.test.ts +0 -97
  392. package/src/__tests__/http-security-sessions.unit.test.ts +0 -209
  393. package/src/__tests__/http-security-verifier.unit.test.ts +0 -248
  394. package/src/__tests__/http-server-allocation.integration.test.ts +0 -279
  395. package/src/__tests__/http-server-origin.integration.test.ts +0 -169
  396. package/src/__tests__/http-server-session-auth.integration.test.ts +0 -341
  397. package/src/__tests__/http-transport.integration.test.ts +0 -395
  398. package/src/__tests__/integration.integration.test.ts +0 -284
  399. package/src/__tests__/langwatch-api.unit.test.ts +0 -661
  400. package/src/__tests__/model-provider-tools.unit.test.ts +0 -190
  401. package/src/__tests__/project-api-key-tools.unit.test.ts +0 -451
  402. package/src/__tests__/prompt-response-types.unit.test.ts +0 -51
  403. package/src/__tests__/prompts.integration.test.ts +0 -736
  404. package/src/__tests__/public-http-request-security.integration.test.ts +0 -118
  405. package/src/__tests__/report-issue.integration.test.ts +0 -147
  406. package/src/__tests__/run-plan-tools.unit.test.ts +0 -565
  407. package/src/__tests__/scenario-tools.integration.test.ts +0 -286
  408. package/src/__tests__/scenario-tools.unit.test.ts +0 -207
  409. package/src/__tests__/schemas.unit.test.ts +0 -85
  410. package/src/__tests__/simulation-run-tools.unit.test.ts +0 -116
  411. package/src/__tests__/support/http-server-harness.ts +0 -138
  412. package/src/__tests__/test-suite-tools.unit.test.ts +0 -615
  413. package/src/__tests__/tools.integration.test.ts +0 -896
  414. package/src/schemas/filter-fields.ts +0 -119
  415. package/src/schemas/index.ts +0 -9
@@ -1,13 +1,14 @@
1
- import express from "express";
2
- import type { Request, RequestHandler, Response, NextFunction } from "express";
3
- import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
4
- import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
5
- import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
6
1
  import { randomUUID, createHash } from "node:crypto";
7
2
  import type { Server } from "node:http";
8
3
 
9
- import { getConfig, runWithConfig } from "./config.js";
10
- import { createMcpServer } from "./create-mcp-server.js";
4
+ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
5
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
6
+ import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
7
+ import express from "express";
8
+ import type { Request, RequestHandler, Response, NextFunction } from "express";
9
+
10
+ import { getConfig, runWithConfig } from "./config.ts";
11
+ import { createMcpServer } from "./create-mcp-server.ts";
11
12
  import {
12
13
  admitOAuthToken,
13
14
  apiKeysMatch,
@@ -21,7 +22,7 @@ import {
21
22
  type OAuthTokenEntry,
22
23
  type RateLimiter,
23
24
  type SessionStore,
24
- } from "./http-security.js";
25
+ } from "./http-security.ts";
25
26
 
26
27
  /** Idle time after which a session is closed and forgotten. */
27
28
  const SESSION_MAX_AGE_MS = 30 * 60 * 1000;
@@ -47,10 +48,9 @@ interface ServerRuntime {
47
48
  }
48
49
 
49
50
  /**
50
- * Resolves and verifies the caller's Bearer token. When `expectedApiKey` is
51
- * given the token has to resolve to that same key, so possession of a session
52
- * id grants nothing on its own. Sends the error response itself and resolves to
53
- * null when the request must not proceed.
51
+ * Resolves and verifies the caller's Bearer token; when `expectedApiKey`
52
+ * is given, it must resolve to that same key. Sends the error response
53
+ * itself and resolves to null when the request must not proceed.
54
54
  */
55
55
  type Authenticate = (args: {
56
56
  req: Request;
@@ -82,16 +82,7 @@ function generateAccessToken(): string {
82
82
  return createHash("sha256").update(randomUUID()).digest("hex");
83
83
  }
84
84
 
85
- /**
86
- * Client address used for rate limiting.
87
- *
88
- * Forwarded headers only mean something when a trusted proxy sets them, so this
89
- * reads the socket peer unless proxy trust is turned on explicitly. Defaulting
90
- * to the socket keeps the limits countable: a caller that reaches the port
91
- * directly cannot rotate `X-Forwarded-For` to reset its own counter. Where that
92
- * default is wrong, it is wrong in the strict direction, counting a whole proxy
93
- * as one client rather than not counting at all.
94
- */
85
+ // Rate limit key: socket peer unless LANGWATCH_MCP_TRUST_PROXY=true checks X-Forwarded-For.
95
86
  function rateLimitKey(req: Request): string {
96
87
  if (process.env.LANGWATCH_MCP_TRUST_PROXY === "true") {
97
88
  return req.ip ?? req.socket.remoteAddress ?? "unknown";
@@ -100,10 +91,9 @@ function rateLimitKey(req: Request): string {
100
91
  }
101
92
 
102
93
  /**
103
- * Resolves a Bearer token to the API key it stands for. OAuth-issued access
104
- * tokens map back to the key they were minted from; anything else is treated as
105
- * a direct API key. This only translates the token, it does not decide whether
106
- * the key is valid.
94
+ * Resolves a Bearer token to its API key. OAuth-issued access tokens map
95
+ * back to the key they were minted from; anything else is treated as a
96
+ * direct key. Only translates — does not validate.
107
97
  */
108
98
  function resolveApiKey({
109
99
  token,
@@ -121,13 +111,7 @@ function resolveApiKey({
121
111
  return null;
122
112
  }
123
113
 
124
- function sendUnauthorized({
125
- res,
126
- error,
127
- }: {
128
- res: Response;
129
- error: string;
130
- }): void {
114
+ function sendUnauthorized({ res, error }: { res: Response; error: string }): void {
131
115
  res.status(401).json({ error });
132
116
  }
133
117
 
@@ -136,23 +120,13 @@ function sendUnauthorized({
136
120
  * tool calls (which read config via `getConfig()`/`requireApiKey()`) see the
137
121
  * per-session API key instead of the global one.
138
122
  */
139
- async function handleWithSessionConfig<T>(
140
- apiKey: string,
141
- fn: () => Promise<T>
142
- ): Promise<T> {
123
+ async function handleWithSessionConfig<T>(apiKey: string, fn: () => Promise<T>): Promise<T> {
143
124
  const baseConfig = getConfig();
144
125
  return runWithConfig({ ...baseConfig, apiKey }, fn);
145
126
  }
146
127
 
147
- /**
148
- * Origin validation and CORS.
149
- *
150
- * The MCP transport specification requires servers to validate Origin on every
151
- * incoming connection, because a page on an attacker's domain can point DNS at
152
- * loopback and reach a server that only checks the token. Requests with no
153
- * Origin header are not browser requests, so they pass: browsers always send
154
- * Origin on the cross-origin requests this guards.
155
- */
128
+ // MCP requires origin validation to prevent DNS-rebinding attacks. Requests without
129
+ // Origin header pass since they're not browser requests and can't be compromised this way.
156
130
  function createOriginMiddleware({
157
131
  allowedOrigins,
158
132
  }: {
@@ -171,16 +145,13 @@ function createOriginMiddleware({
171
145
  // A cross-origin client cannot read a response header unless it is
172
146
  // exposed, and the Streamable HTTP transport reads the session id off
173
147
  // the initialize response.
174
- res.header(
175
- "Access-Control-Expose-Headers",
176
- "Mcp-Session-Id, MCP-Protocol-Version"
177
- );
148
+ res.header("Access-Control-Expose-Headers", "Mcp-Session-Id, MCP-Protocol-Version");
178
149
  }
179
150
 
180
151
  res.header("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
181
152
  res.header(
182
153
  "Access-Control-Allow-Headers",
183
- "Content-Type, Authorization, mcp-session-id, MCP-Protocol-Version"
154
+ "Content-Type, Authorization, mcp-session-id, MCP-Protocol-Version",
184
155
  );
185
156
  res.header("X-Content-Type-Options", "nosniff");
186
157
  res.header("X-Frame-Options", "DENY");
@@ -205,7 +176,10 @@ function createAuthenticator(runtime: ServerRuntime): Authenticate {
205
176
 
206
177
  const token = readBearerToken(req);
207
178
  if (!token) {
208
- sendUnauthorized({ res, error: "Authorization: Bearer <LANGWATCH_API_KEY> header required" });
179
+ sendUnauthorized({
180
+ res,
181
+ error: "Authorization: Bearer <LANGWATCH_API_KEY> header required",
182
+ });
209
183
  return null;
210
184
  }
211
185
 
@@ -247,10 +221,7 @@ function overSessionLimit({
247
221
  sessions: SessionStore<StreamableHTTPServerTransport>;
248
222
  sseSessions: SessionStore<SSEServerTransport>;
249
223
  }): boolean {
250
- return (
251
- sessions.countForKey(apiKey) + sseSessions.countForKey(apiKey) >=
252
- MAX_SESSIONS_PER_KEY
253
- );
224
+ return sessions.countForKey(apiKey) + sseSessions.countForKey(apiKey) >= MAX_SESSIONS_PER_KEY;
254
225
  }
255
226
 
256
227
  function sendSessionLimitReached(res: Response): void {
@@ -269,20 +240,17 @@ function registerOAuthRoutes({
269
240
  }): void {
270
241
  const { verifier, oauthTokens, oauthRateLimiter } = runtime;
271
242
 
272
- app.get(
273
- "/.well-known/oauth-authorization-server",
274
- (req: Request, res: Response) => {
275
- const baseUrl = `${req.protocol}://${req.get("host")}`;
276
- res.json({
277
- issuer: baseUrl,
278
- token_endpoint: `${baseUrl}/oauth/token`,
279
- token_endpoint_auth_methods_supported: ["client_secret_post"],
280
- grant_types_supported: ["client_credentials"],
281
- response_types_supported: [],
282
- scopes_supported: ["mcp:tools"],
283
- });
284
- }
285
- );
243
+ app.get("/.well-known/oauth-authorization-server", (req: Request, res: Response) => {
244
+ const baseUrl = `${req.protocol}://${req.get("host")}`;
245
+ res.json({
246
+ issuer: baseUrl,
247
+ token_endpoint: `${baseUrl}/oauth/token`,
248
+ token_endpoint_auth_methods_supported: ["client_secret_post"],
249
+ grant_types_supported: ["client_credentials"],
250
+ response_types_supported: [],
251
+ scopes_supported: ["mcp:tools"],
252
+ });
253
+ });
286
254
 
287
255
  // RFC 6749 requires application/x-www-form-urlencoded on the token endpoint.
288
256
  app.post(
@@ -310,8 +278,7 @@ function registerOAuthRoutes({
310
278
  if (!clientSecret || typeof clientSecret !== "string") {
311
279
  res.status(400).json({
312
280
  error: "invalid_request",
313
- error_description:
314
- "client_secret is required (use your LangWatch API key)",
281
+ error_description: "client_secret is required (use your LangWatch API key)",
315
282
  });
316
283
  return;
317
284
  }
@@ -343,7 +310,7 @@ function registerOAuthRoutes({
343
310
  expires_in: OAUTH_TOKEN_TTL_SECONDS,
344
311
  scope: "mcp:tools",
345
312
  });
346
- }
313
+ },
347
314
  );
348
315
  }
349
316
 
@@ -357,133 +324,145 @@ function registerStreamableHttpRoutes({
357
324
  runtime: ServerRuntime;
358
325
  authenticate: Authenticate;
359
326
  }): void {
360
- const { sessions, sseSessions } = runtime;
361
-
362
- const sessionIdOf = (req: Request): string | undefined =>
363
- req.headers["mcp-session-id"] as string | undefined;
327
+ const route = { runtime, authenticate };
328
+ app.post("/mcp", (req: Request, res: Response) => handleMcpPost({ req, res, ...route }));
329
+ app.get("/mcp", (req: Request, res: Response) => handleMcpGet({ req, res, ...route }));
330
+ app.delete("/mcp", (req: Request, res: Response) => handleMcpDelete({ req, res, ...route }));
331
+ }
364
332
 
365
- app.post("/mcp", async (req: Request, res: Response) => {
366
- const sessionId = sessionIdOf(req);
367
- const session = sessionId ? sessions.get(sessionId) : undefined;
333
+ type McpRoute = {
334
+ req: Request;
335
+ res: Response;
336
+ runtime: ServerRuntime;
337
+ authenticate: Authenticate;
338
+ };
368
339
 
369
- if (sessionId && session) {
370
- const apiKey = await authenticate({
371
- req,
372
- res,
373
- expectedApiKey: session.apiKey,
374
- });
375
- if (!apiKey) return;
340
+ const sessionIdOf = (req: Request): string | undefined =>
341
+ req.headers["mcp-session-id"] as string | undefined;
376
342
 
377
- sessions.touch(sessionId);
378
- await handleWithSessionConfig(session.apiKey, () =>
379
- session.transport.handleRequest(req, res, req.body)
380
- );
381
- return;
382
- }
343
+ async function handleMcpPost({ req, res, runtime, authenticate }: McpRoute): Promise<void> {
344
+ const { sessions } = runtime;
345
+ const sessionId = sessionIdOf(req);
346
+ const session = sessionId ? sessions.get(sessionId) : undefined;
383
347
 
384
- if (!sessionId && isInitializeRequest(req.body)) {
385
- const apiKey = await authenticate({ req, res });
386
- if (!apiKey) return;
348
+ if (sessionId && session) {
349
+ const apiKey = await authenticate({
350
+ req,
351
+ res,
352
+ expectedApiKey: session.apiKey,
353
+ });
354
+ if (!apiKey) return;
387
355
 
388
- if (overSessionLimit({ apiKey, sessions, sseSessions })) {
389
- sendSessionLimitReached(res);
390
- return;
391
- }
356
+ sessions.touch(sessionId);
357
+ await handleWithSessionConfig(session.apiKey, () =>
358
+ session.transport.handleRequest(req, res, req.body),
359
+ );
360
+ return;
361
+ }
392
362
 
393
- // Claimed before the first await. The session id only exists once
394
- // initialize completes, so without holding the slot from here concurrent
395
- // requests for one key would all pass the check above at a count of zero.
396
- const reservation = sessions.reserve(apiKey);
397
- const transport = new StreamableHTTPServerTransport({
398
- sessionIdGenerator: () => randomUUID(),
399
- onsessioninitialized: (id) => {
400
- reservation.commit({ sessionId: id, transport });
401
- },
402
- });
403
- transport.onclose = () => {
404
- if (transport.sessionId) sessions.remove(transport.sessionId);
405
- };
406
-
407
- const sessionServer = createMcpServer();
408
- try {
409
- await handleWithSessionConfig(apiKey, () =>
410
- sessionServer.connect(transport)
411
- );
412
- await handleWithSessionConfig(apiKey, () =>
413
- transport.handleRequest(req, res, req.body)
414
- );
415
- } catch (error) {
416
- if (transport.sessionId) sessions.remove(transport.sessionId);
417
- await transport.close().catch(() => undefined);
418
- throw error;
419
- } finally {
420
- // A no-op once the session took the slot, so this only returns it when
421
- // initialization never produced one.
422
- reservation.release();
423
- }
424
- return;
425
- }
363
+ if (!sessionId && isInitializeRequest(req.body)) {
364
+ await initializeSession({ req, res, runtime, authenticate });
365
+ return;
366
+ }
426
367
 
427
- // An unknown session id gets the same answer as a missing token, so the
428
- // response does not reveal whether the session exists.
429
- if (sessionId) {
430
- sendUnauthorized({ res, error: "Session expired or not found" });
431
- return;
432
- }
368
+ // An unknown session id gets the same answer as a missing token, so the
369
+ // response does not reveal whether the session exists.
370
+ if (sessionId) {
371
+ sendUnauthorized({ res, error: "Session expired or not found" });
372
+ return;
373
+ }
433
374
 
434
- res.status(400).json({
435
- error: "Invalid request, no session ID or not an initialize request",
436
- });
375
+ res.status(400).json({
376
+ error: "Invalid request, no session ID or not an initialize request",
437
377
  });
378
+ }
438
379
 
439
- app.get("/mcp", async (req: Request, res: Response) => {
440
- const sessionId = sessionIdOf(req);
441
- const session = sessionId ? sessions.get(sessionId) : undefined;
380
+ /** Opens a session for an initialize request, holding the key's slot until it exists. */
381
+ async function initializeSession({ req, res, runtime, authenticate }: McpRoute): Promise<void> {
382
+ const { sessions, sseSessions } = runtime;
383
+ const apiKey = await authenticate({ req, res });
384
+ if (!apiKey) return;
442
385
 
443
- if (sessionId && session) {
444
- const apiKey = await authenticate({
445
- req,
446
- res,
447
- expectedApiKey: session.apiKey,
448
- });
449
- if (!apiKey) return;
386
+ if (overSessionLimit({ apiKey, sessions, sseSessions })) {
387
+ sendSessionLimitReached(res);
388
+ return;
389
+ }
450
390
 
451
- sessions.touch(sessionId);
452
- await handleWithSessionConfig(session.apiKey, () =>
453
- session.transport.handleRequest(req, res)
454
- );
455
- return;
456
- }
391
+ // Claimed before the first await. The session id only exists once
392
+ // initialize completes, so without holding the slot from here concurrent
393
+ // requests for one key would all pass the check above at a count of zero.
394
+ const reservation = sessions.reserve(apiKey);
395
+ const transport = new StreamableHTTPServerTransport({
396
+ sessionIdGenerator: () => randomUUID(),
397
+ onsessioninitialized: (id) => {
398
+ reservation.commit({ sessionId: id, transport });
399
+ },
400
+ });
401
+ transport.onclose = () => {
402
+ if (transport.sessionId) sessions.remove(transport.sessionId);
403
+ };
457
404
 
458
- if (sessionId) {
459
- sendUnauthorized({ res, error: "Session expired or not found" });
460
- return;
461
- }
405
+ const sessionServer = createMcpServer();
406
+ try {
407
+ await handleWithSessionConfig(apiKey, () => sessionServer.connect(transport));
408
+ await handleWithSessionConfig(apiKey, () => transport.handleRequest(req, res, req.body));
409
+ } catch (error) {
410
+ if (transport.sessionId) sessions.remove(transport.sessionId);
411
+ await transport.close().catch(() => undefined);
412
+ throw error;
413
+ } finally {
414
+ // A no-op once the session took the slot, so this only returns it when
415
+ // initialization never produced one.
416
+ reservation.release();
417
+ }
418
+ }
462
419
 
463
- res.status(400).json({ error: "Invalid request, no valid session ID" });
464
- });
420
+ async function handleMcpGet({ req, res, runtime, authenticate }: McpRoute): Promise<void> {
421
+ const { sessions } = runtime;
422
+ const sessionId = sessionIdOf(req);
423
+ const session = sessionId ? sessions.get(sessionId) : undefined;
465
424
 
466
- app.delete("/mcp", async (req: Request, res: Response) => {
467
- // Authenticate before looking the session up, so that a session the caller
468
- // does not own is indistinguishable from one that does not exist.
469
- const apiKey = await authenticate({ req, res });
425
+ if (sessionId && session) {
426
+ const apiKey = await authenticate({
427
+ req,
428
+ res,
429
+ expectedApiKey: session.apiKey,
430
+ });
470
431
  if (!apiKey) return;
471
432
 
472
- const sessionId = sessionIdOf(req);
473
- const session = sessionId ? sessions.get(sessionId) : undefined;
474
- const owned =
475
- session !== undefined &&
476
- apiKeysMatch({ presentedKey: apiKey, expectedKey: session.apiKey });
433
+ sessions.touch(sessionId);
434
+ await handleWithSessionConfig(session.apiKey, () => session.transport.handleRequest(req, res));
435
+ return;
436
+ }
477
437
 
478
- if (!sessionId || !session || !owned) {
479
- res.status(404).json({ error: "Session not found" });
480
- return;
481
- }
438
+ if (sessionId) {
439
+ sendUnauthorized({ res, error: "Session expired or not found" });
440
+ return;
441
+ }
482
442
 
483
- sessions.remove(sessionId);
484
- await session.transport.close();
485
- res.status(200).json({ status: "session closed" });
486
- });
443
+ res.status(400).json({ error: "Invalid request, no valid session ID" });
444
+ }
445
+
446
+ async function handleMcpDelete({ req, res, runtime, authenticate }: McpRoute): Promise<void> {
447
+ const { sessions } = runtime;
448
+ // Authenticate before looking the session up, so that a session the caller
449
+ // does not own is indistinguishable from one that does not exist.
450
+ const apiKey = await authenticate({ req, res });
451
+ if (!apiKey) return;
452
+
453
+ const sessionId = sessionIdOf(req);
454
+ const session = sessionId ? sessions.get(sessionId) : undefined;
455
+ const owned =
456
+ session !== undefined && apiKeysMatch({ presentedKey: apiKey, expectedKey: session.apiKey });
457
+
458
+ if (!sessionId || !session || !owned) {
459
+ res.status(404).json({ error: "Session not found" });
460
+ return;
461
+ }
462
+
463
+ sessions.remove(sessionId);
464
+ await session.transport.close();
465
+ res.status(200).json({ status: "session closed" });
487
466
  }
488
467
 
489
468
  /** Legacy SSE transport, kept for backwards compatibility. */
@@ -517,9 +496,7 @@ function registerSseRoutes({
517
496
  });
518
497
 
519
498
  try {
520
- await handleWithSessionConfig(apiKey, () =>
521
- sessionServer.connect(transport)
522
- );
499
+ await handleWithSessionConfig(apiKey, () => sessionServer.connect(transport));
523
500
  } catch (error) {
524
501
  // Without this the entry holds one of the per-key slots until the reaper
525
502
  // sweeps it, because res "close" may never fire if the stream never
@@ -533,21 +510,33 @@ function registerSseRoutes({
533
510
  }
534
511
  });
535
512
 
536
- // Mounted at both /messages and /sse/messages because some clients resolve
537
- // the relative /messages URL differently.
538
- //
539
- // The session id travels in the query string because the SSE transport hands
540
- // the client its POST endpoint as a URI. It identifies the session and
541
- // nothing more: the Bearer token is what authorizes the request.
542
- const handleSseMessage = async (req: Request, res: Response) => {
513
+ // Mounted at both /messages and /sse/messages because some clients
514
+ // resolve the relative /messages URL differently.
515
+
516
+ // The session id in the query string only identifies the session; it's
517
+ // how the SSE transport hands the client its POST endpoint as a URI.
518
+ // The Bearer token is what authorizes the request.
519
+ const handleSseMessage = createSseMessageHandler({ authenticate, sseSessions });
520
+
521
+ app.post("/messages", handleSseMessage);
522
+ app.post("/sse/messages", handleSseMessage);
523
+ }
524
+
525
+ function createSseMessageHandler({
526
+ authenticate,
527
+ sseSessions,
528
+ }: {
529
+ authenticate: Authenticate;
530
+ sseSessions: ServerRuntime["sseSessions"];
531
+ }) {
532
+ return async (req: Request, res: Response) => {
543
533
  const apiKey = await authenticate({ req, res });
544
534
  if (!apiKey) return;
545
535
 
546
536
  const sessionId = req.query["sessionId"] as string | undefined;
547
537
  const session = sessionId ? sseSessions.get(sessionId) : undefined;
548
538
  const owned =
549
- session !== undefined &&
550
- apiKeysMatch({ presentedKey: apiKey, expectedKey: session.apiKey });
539
+ session !== undefined && apiKeysMatch({ presentedKey: apiKey, expectedKey: session.apiKey });
551
540
 
552
541
  if (!sessionId || !session || !owned) {
553
542
  res.status(400).json({ error: "Invalid or missing session ID" });
@@ -556,24 +545,15 @@ function registerSseRoutes({
556
545
 
557
546
  sseSessions.touch(sessionId);
558
547
  await handleWithSessionConfig(session.apiKey, () =>
559
- session.transport.handlePostMessage(req, res, req.body)
548
+ session.transport.handlePostMessage(req, res, req.body),
560
549
  );
561
550
  };
562
-
563
- app.post("/messages", handleSseMessage);
564
- app.post("/sse/messages", handleSseMessage);
565
551
  }
566
552
 
567
553
  /** Sweeps idle sessions, expired tokens, and stale rate limiter entries. */
568
554
  function startReaper(runtime: ServerRuntime): NodeJS.Timeout {
569
- const {
570
- sessions,
571
- sseSessions,
572
- oauthTokens,
573
- verifier,
574
- authFailRateLimiter,
575
- oauthRateLimiter,
576
- } = runtime;
555
+ const { sessions, sseSessions, oauthTokens, verifier, authFailRateLimiter, oauthRateLimiter } =
556
+ runtime;
577
557
 
578
558
  const reaper = setInterval(() => {
579
559
  sessions.sweep();
@@ -647,32 +627,17 @@ export interface StartedHttpServer {
647
627
  allowedOrigins: string[];
648
628
  }
649
629
 
650
- /**
651
- * Starts an Express HTTP server with Streamable HTTP and legacy SSE transports
652
- * for the LangWatch MCP server.
653
- *
654
- * Every request carries `Authorization: Bearer <key>`. The key is verified
655
- * against the LangWatch API before any per-session state is allocated, and
656
- * re-checked on every subsequent request against the key the session was
657
- * created with, so a session id on its own authorizes nothing.
658
- *
659
- * Endpoints:
660
- * - GET /health - Health check for Kubernetes probes (no auth)
661
- * - POST/GET/DELETE /mcp - Streamable HTTP transport (modern)
662
- * - GET /sse - Legacy SSE transport (backwards compatibility)
663
- * - POST /messages - Legacy SSE message endpoint
664
- */
630
+ // HTTP server with Streamable HTTP and legacy SSE transports; every request
631
+ // re-verifies the bearer token against the LangWatch API.
665
632
  export async function startHttpServer({
666
633
  port,
667
634
  host,
668
635
  allowedOrigins,
669
636
  apiKeyVerifier,
670
637
  }: StartHttpServerOptions): Promise<StartedHttpServer> {
671
- const bindHost =
672
- host ?? process.env.LANGWATCH_MCP_HTTP_HOST ?? DEFAULT_BIND_HOST;
638
+ const bindHost = host ?? process.env.LANGWATCH_MCP_HTTP_HOST ?? DEFAULT_BIND_HOST;
673
639
  const originAllowlist =
674
- allowedOrigins ??
675
- parseAllowedOrigins(process.env.LANGWATCH_MCP_ALLOWED_ORIGINS);
640
+ allowedOrigins ?? parseAllowedOrigins(process.env.LANGWATCH_MCP_ALLOWED_ORIGINS);
676
641
 
677
642
  const runtime = createRuntime({
678
643
  endpoint: getConfig().endpoint,
package/src/index.ts CHANGED
@@ -2,8 +2,8 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
2
2
  import yargs from "yargs";
3
3
  import { hideBin } from "yargs/helpers";
4
4
 
5
- import { initConfig } from "./config.js";
6
- import { createMcpServer } from "./create-mcp-server.js";
5
+ import { initConfig } from "./config.ts";
6
+ import { createMcpServer } from "./create-mcp-server.ts";
7
7
 
8
8
  const argv = await yargs(hideBin(process.argv))
9
9
  .option("apiKey", {
@@ -14,6 +14,10 @@ const argv = await yargs(hideBin(process.argv))
14
14
  type: "string",
15
15
  description: "LangWatch API endpoint",
16
16
  })
17
+ .option("projectId", {
18
+ type: "string",
19
+ description: "LangWatch project ID",
20
+ })
17
21
  .option("http", {
18
22
  type: "boolean",
19
23
  description: "Start HTTP/SSE server instead of stdio",
@@ -41,13 +45,12 @@ const argv = await yargs(hideBin(process.argv))
41
45
  initConfig({
42
46
  apiKey: argv.apiKey,
43
47
  endpoint: argv.endpoint,
48
+ projectId: argv.projectId,
44
49
  });
45
50
 
46
51
  if (argv.http) {
47
- const { startHttpServer } = await import("./http-server.js");
48
- const { isLoopbackHost, parseAllowedOrigins } = await import(
49
- "./http-security.js"
50
- );
52
+ const { startHttpServer } = await import("./http-server.ts");
53
+ const { isLoopbackHost, parseAllowedOrigins } = await import("./http-security.ts");
51
54
 
52
55
  const allowedOrigins =
53
56
  argv.allowedOrigin && argv.allowedOrigin.length > 0
@@ -62,19 +65,14 @@ if (argv.http) {
62
65
 
63
66
  // An IPv6 literal has to be bracketed inside a URL, and must not be
64
67
  // double-bracketed when the operator already wrote it that way.
65
- const displayHost =
66
- host.includes(":") && !host.startsWith("[") ? `[${host}]` : host;
67
- console.log(
68
- `LangWatch MCP server listening on http://${displayHost}:${port}/mcp`
69
- );
70
- console.log(
71
- "Clients must provide their API key via Authorization: Bearer <key> header"
72
- );
68
+ const displayHost = host.includes(":") && !host.startsWith("[") ? `[${host}]` : host;
69
+ console.log(`LangWatch MCP server listening on http://${displayHost}:${port}/mcp`);
70
+ console.log("Clients must provide their API key via Authorization: Bearer <key> header");
73
71
  if (!isLoopbackHost(host)) {
74
72
  console.warn(
75
73
  `Warning: bound to ${host}, so the server is reachable from other machines. ` +
76
74
  "Make sure it sits behind a trusted network boundary and that --allowedOrigin " +
77
- "lists only origins you control."
75
+ "lists only origins you control.",
78
76
  );
79
77
  }
80
78
  if (process.env.LANGWATCH_MCP_TRUST_PROXY === "true") {
@@ -83,7 +81,7 @@ if (argv.http) {
83
81
  "authentication uses the client address from X-Forwarded-For. Keep this " +
84
82
  "on only when a trusted proxy terminates in front of the port and " +
85
83
  "overwrites that header. Otherwise a client can supply any address there " +
86
- "and the limit stops counting."
84
+ "and the limit stops counting.",
87
85
  );
88
86
  }
89
87
  } else {