@langwatch/mcp-server 2.0.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 (430) hide show
  1. package/.gitignore +7 -0
  2. package/.npmignore +41 -0
  3. package/CHANGELOG.md +280 -200
  4. package/CONTRIBUTING.md +10 -0
  5. package/README.md +82 -52
  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-4EQGLBJJ.js → archive-project-SPVHXK6A.js} +4 -4
  10. package/dist/archive-run-plan-65QN3XRX.js +16 -0
  11. package/dist/archive-run-plan-Z4SYE6BP.js +15 -0
  12. package/dist/{archive-scenario-W6BXYDZ3.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-NKZDZY3D.js +16 -0
  16. package/dist/assign-prompt-tag-5IUZXJ2T.js +23 -0
  17. package/dist/{assign-prompt-tag-VLXUWEKU.js → assign-prompt-tag-HBZLBIFD.js} +8 -4
  18. package/dist/{chunk-CDYQOY5U.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-6YZUOD34.js +55 -0
  22. package/dist/{chunk-WVI5CJ6S.js → chunk-7AQLPVFL.js} +2 -2
  23. package/dist/chunk-7VNSRCXO.js +129 -0
  24. package/dist/{chunk-7RHG56OH.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-TOXJQDIZ.js → chunk-HGELNIVY.js} +135 -51
  30. package/dist/{chunk-UJPXFA26.js → chunk-HHMNZAPD.js} +101 -74
  31. package/dist/{chunk-J42FWRJ2.js → chunk-I7ATIZED.js} +7 -7
  32. package/dist/{chunk-AQJO2EI5.js → chunk-ILZDO25L.js} +7 -10
  33. package/dist/{chunk-4QC7SA2L.js → chunk-J7PQKBLD.js} +884 -559
  34. package/dist/{chunk-YX3A3CFM.js → chunk-JRJPH75H.js} +7 -13
  35. package/dist/chunk-K2I7Z3LZ.js +28 -0
  36. package/dist/{chunk-5CCZFUXS.js → chunk-NKMTIHIJ.js} +6 -9
  37. package/dist/chunk-O7EMWEVJ.js +44 -0
  38. package/dist/chunk-PTDR56Z3.js +37 -0
  39. package/dist/chunk-RABYBFX5.js +43 -0
  40. package/dist/chunk-SUPUUC4W.js +64 -0
  41. package/dist/{chunk-SFB4MU6H.js → chunk-SXFC3FNH.js} +4 -4
  42. package/dist/chunk-U24EACC4.js +67 -0
  43. package/dist/chunk-UNJM3BIL.js +21 -0
  44. package/dist/{chunk-ZWIL4EFL.js → chunk-UTJCWXPZ.js} +4 -4
  45. package/dist/chunk-UWLE5T2F.js +312 -0
  46. package/dist/chunk-VMWJD732.js +57 -0
  47. package/dist/chunk-XUIJYNRJ.js +15 -0
  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-7XHO6UIS.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-YQXRQC6A.js → create-annotation-VUSXULQK.js} +10 -5
  57. package/dist/{create-api-key-YUS36KBU.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-4Z5JO5LB.js → create-dashboard-WTKKS5Z6.js} +4 -4
  61. package/dist/{create-dataset-E6NMSVX7.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-SKHPRD2K.js → create-dataset-records-W4GLN2FV.js} +5 -7
  65. package/dist/{create-evaluator-JXF5PF33.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 -5
  69. package/dist/create-project-6N7PIYRY.js +44 -0
  70. package/dist/{create-project-CS2CB57Y.js → create-project-KAIC46UY.js} +4 -4
  71. package/dist/{create-prompt-F5RMX3L3.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-GMRWTEFR.js → create-prompt-tag-OPQUEXWB.js} +3 -3
  75. package/dist/create-scenario-5PJBMAXO.js +33 -0
  76. package/dist/{create-scenario-JDR2VB2H.js → create-scenario-UEM77OE7.js} +11 -4
  77. package/dist/create-test-suite-4IPXVSFQ.js +37 -0
  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-O3EEH4S5.js → delete-agent-QMEK3XHK.js} +4 -4
  83. package/dist/delete-annotation-HY3O2OGR.js +17 -0
  84. package/dist/{delete-annotation-XP6FQM7A.js → delete-annotation-VKIQWUYW.js} +4 -4
  85. package/dist/delete-dashboard-76N66XXL.js +18 -0
  86. package/dist/{delete-dashboard-RFUNYCNP.js → delete-dashboard-JVIVHNJM.js} +4 -4
  87. package/dist/delete-dataset-HQCR66MG.js +18 -0
  88. package/dist/{delete-dataset-3MJFRDLP.js → delete-dataset-WKRUW4FP.js} +5 -7
  89. package/dist/{delete-dataset-records-WPMU5GL5.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-KNMZVSPB.js → delete-prompt-tag-YYQ4VMAD.js} +3 -3
  95. package/dist/{delete-workflow-3NUXO46U.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-OOFIQKOG.js +139 -0
  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-OTIYUFZA.js +70 -0
  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-KHBEGZGL.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-ADMKVWYZ.js → get-evaluator-ISMC4LLI.js} +4 -4
  112. package/dist/get-evaluator-XEUZGZCH.js +47 -0
  113. package/dist/{get-project-RAK7URQJ.js → get-project-5VKROSDH.js} +4 -4
  114. package/dist/get-project-RM6FRCBK.js +26 -0
  115. package/dist/get-prompt-OWXJZHQQ.js +95 -0
  116. package/dist/get-prompt-W52DY36E.js +94 -0
  117. package/dist/get-run-plan-BE3M2AUX.js +59 -0
  118. package/dist/get-run-plan-RUXQ6PAX.js +58 -0
  119. package/dist/get-scenario-RNM7BAX2.js +37 -0
  120. package/dist/{get-scenario-ZTAJJL5T.js → get-scenario-YM4ODWW6.js} +8 -4
  121. package/dist/get-simulation-run-6GIATQE5.js +69 -0
  122. package/dist/{get-simulation-run-T2ZQWYH2.js → get-simulation-run-LCFSMPGQ.js} +33 -20
  123. package/dist/get-test-suite-GC2O7YH6.js +49 -0
  124. package/dist/get-test-suite-P5LJ6VLF.js +48 -0
  125. package/dist/get-trace-KBRVV4NN.js +45 -0
  126. package/dist/{get-trace-K7U234Q4.js → get-trace-SEF3FMR5.js} +11 -13
  127. package/dist/get-workflow-7PVED7JE.js +25 -0
  128. package/dist/{get-workflow-L5L2PSK3.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-YXHFPPGA.js → http-server-IR4QOS43.js} +4071 -342
  131. package/dist/index.js +60221 -38077
  132. package/dist/langwatch-api-annotations-6SA4OB4C.js +17 -0
  133. package/dist/{langwatch-api-annotations-FDAS6NQ7.js → langwatch-api-annotations-TBHLCQ5R.js} +4 -4
  134. package/dist/langwatch-api-dashboards-6EO2VVZW.js +17 -0
  135. package/dist/{langwatch-api-dashboards-JTVCS7ZJ.js → langwatch-api-dashboards-NHU5BZD7.js} +4 -4
  136. package/dist/langwatch-api-monitors-CBF3FGIM.js +47 -0
  137. package/dist/{langwatch-api-monitors-3J7UIQME.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-NBJX5KOG.js → langwatch-api-workflows-OKC4FXIF.js} +4 -4
  143. package/dist/langwatch-api-workflows-XGJE6SR5.js +15 -0
  144. package/dist/list-agents-HVYWEQMI.js +43 -0
  145. package/dist/list-agents-RBAOCF53.js +42 -0
  146. package/dist/list-annotations-HJH4NBLD.js +38 -0
  147. package/dist/{list-annotations-ZTVP4EEL.js → list-annotations-J4UAHSUN.js} +10 -5
  148. package/dist/{list-api-keys-CWD4UQT5.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-PWG6P6KR.js → list-dashboards-ROSYFLDQ.js} +4 -4
  153. package/dist/list-dashboards-RQ635ZU7.js +32 -0
  154. package/dist/{list-dataset-records-IOE7LQXS.js → list-dataset-records-M5TKA6LK.js} +14 -10
  155. package/dist/list-dataset-records-WFM7F33C.js +43 -0
  156. package/dist/{list-datasets-ZT4JYUDT.js → list-datasets-B5X4P7BQ.js} +9 -13
  157. package/dist/list-datasets-THUKQHPX.js +56 -0
  158. package/dist/{list-evaluators-SJHC7VBH.js → list-evaluators-MYH2OVCI.js} +5 -7
  159. package/dist/list-evaluators-NLVWF6K7.js +32 -0
  160. package/dist/{list-model-providers-PPD2WPD2.js → list-model-providers-3JUGP4YI.js} +4 -4
  161. package/dist/list-model-providers-H3XKORFO.js +35 -0
  162. package/dist/{list-projects-K7QAIX2I.js → list-projects-6EJ5YYEW.js} +5 -7
  163. package/dist/list-projects-YNNSRMLC.js +40 -0
  164. package/dist/{list-prompt-tags-FOO6TNHB.js → list-prompt-tags-NVFQO6WX.js} +3 -3
  165. package/dist/list-prompt-tags-XUQTTAH4.js +23 -0
  166. package/dist/{list-prompts-SC2HAUFG.js → list-prompts-HSIMMHTF.js} +4 -6
  167. package/dist/list-prompts-IRI6AIXO.js +30 -0
  168. package/dist/list-run-plans-PX2XI6EK.js +46 -0
  169. package/dist/list-run-plans-QALLCN2K.js +45 -0
  170. package/dist/list-scenarios-QZRPVHS4.js +46 -0
  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-KF5THG5H.js → list-simulation-runs-OEADLLQR.js} +19 -9
  174. package/dist/list-test-suites-ANAQBLDG.js +34 -0
  175. package/dist/list-test-suites-N4WVLBKK.js +35 -0
  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-QSJWURCK.js → list-workflows-IMSX7SXU.js} +5 -7
  181. package/dist/list-workflows-ZHTX5LCL.js +33 -0
  182. package/dist/{rename-prompt-tag-EKWU5QF3.js → rename-prompt-tag-IQI7MHOI.js} +3 -3
  183. package/dist/rename-prompt-tag-PA2BRIE6.js +18 -0
  184. package/dist/rename-test-suite-4J7UX5S2.js +21 -0
  185. package/dist/rename-test-suite-NNVLMLKY.js +20 -0
  186. package/dist/report-issue-AS34ZMJF.js +898 -0
  187. package/dist/report-issue-DCZFLGDO.js +899 -0
  188. package/dist/rerun-run-plan-TG2V4WM4.js +22 -0
  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-FTRTZMOF.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-PAARQSI4.js +39 -0
  196. package/dist/run-query-KJDH544C.js +47 -0
  197. package/dist/run-query-T4QW2Z5O.js +46 -0
  198. package/dist/run-test-suite-SQJELEZX.js +26 -0
  199. package/dist/run-test-suite-STB6NUJM.js +25 -0
  200. package/dist/{run-workflow-QPXC627O.js → run-workflow-D6PQNBI6.js} +4 -4
  201. package/dist/{run-agent-6SM4XNA7.js → run-workflow-SP32SO5M.js} +10 -11
  202. package/dist/search-traces-HCBGOG6R.js +170 -0
  203. package/dist/search-traces-HSTQ6WTQ.js +171 -0
  204. package/dist/{set-model-provider-3WKT63ZN.js → set-model-provider-GF7NXUJA.js} +4 -4
  205. package/dist/set-model-provider-KDQ7C4EZ.js +33 -0
  206. package/dist/{update-agent-SMAEHLXH.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-BDUK7TUO.js → update-dataset-SQZ6T4WX.js} +6 -6
  212. package/dist/{update-dataset-record-4ZSDRBSW.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-LS6TIBY7.js → update-evaluator-IGTX6PX4.js} +4 -4
  216. package/dist/{update-project-LKVYXENH.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-MY263DYF.js +141 -0
  220. package/dist/update-scenario-7KA3JDXA.js +34 -0
  221. package/dist/{update-scenario-NEXCAQI7.js → update-scenario-MOHRQKAQ.js} +11 -4
  222. package/package.json +39 -44
  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 +1021 -596
  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 +202 -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 +140 -0
  244. package/src/langwatch-api-scenarios.ts +17 -10
  245. package/src/langwatch-api-secrets.ts +31 -24
  246. package/src/langwatch-api-simulation-runs.ts +10 -7
  247. package/src/langwatch-api-test-suites.ts +116 -0
  248. package/src/langwatch-api-triggers.ts +96 -34
  249. package/src/langwatch-api-workflows.ts +9 -9
  250. package/src/langwatch-api.ts +143 -109
  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 +76 -0
  255. package/src/schemas/suite-fields.ts +132 -0
  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 +10 -0
  260. package/src/tools/archive-scenario.ts +2 -4
  261. package/src/tools/archive-test-suite.ts +10 -0
  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 +11 -5
  274. package/src/tools/create-test-suite.ts +38 -0
  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 +86 -12
  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 +54 -0
  292. package/src/tools/format-scenario.ts +12 -0
  293. package/src/tools/format-suite-details.ts +90 -0
  294. package/src/tools/get-agent.ts +54 -3
  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 +100 -37
  302. package/src/tools/get-run-plan.ts +60 -0
  303. package/src/tools/get-scenario.ts +6 -5
  304. package/src/tools/get-simulation-run.ts +32 -20
  305. package/src/tools/get-test-suite.ts +46 -0
  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 +20 -3
  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 +47 -0
  323. package/src/tools/list-scenarios.ts +26 -17
  324. package/src/tools/list-simulation-runs.ts +13 -5
  325. package/src/tools/list-test-suites.ts +35 -0
  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 +15 -0
  331. package/src/tools/report-issue.ts +5 -9
  332. package/src/tools/rerun-run-plan.ts +18 -0
  333. package/src/tools/revoke-api-key.ts +2 -4
  334. package/src/tools/run-agent.ts +65 -14
  335. package/src/tools/run-experiment.ts +36 -37
  336. package/src/tools/run-plan.ts +46 -0
  337. package/src/tools/run-query.ts +47 -0
  338. package/src/tools/run-test-suite.ts +27 -0
  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 +18 -0
  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 +185 -15
  350. package/src/tools/update-scenario.ts +11 -5
  351. package/src/tools/update-test-suite.ts +40 -0
  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 +19 -12
  359. package/uv.lock +416 -296
  360. package/vitest.integration.config.js +10 -0
  361. package/vitest.scenario.config.js +9 -0
  362. package/.eslintrc.cjs +0 -36
  363. package/dist/archive-suite-4T6BSRS7.js +0 -16
  364. package/dist/chunk-77BNWQ3U.js +0 -86
  365. package/dist/chunk-A2LEVYQ5.js +0 -217
  366. package/dist/chunk-TJCXFNDT.js +0 -24
  367. package/dist/chunk-YZLYHLLI.js +0 -48
  368. package/dist/config.d.ts +0 -17
  369. package/dist/create-mcp-server.d.ts +0 -10
  370. package/dist/create-suite-LZS6QIAF.js +0 -39
  371. package/dist/delete-evaluator-33SP2JFX.js +0 -16
  372. package/dist/discover-evaluator-schema-XTAD4X7D.js +0 -2191
  373. package/dist/discover-scenario-schema-FE64QK2D.js +0 -67
  374. package/dist/get-agent-Y7MWZ65F.js +0 -29
  375. package/dist/get-analytics-TETCSOK6.js +0 -73
  376. package/dist/get-dataset-RCMU5VAH.js +0 -49
  377. package/dist/get-prompt-OY5B7TRI.js +0 -46
  378. package/dist/get-suite-J7JVWO6O.js +0 -45
  379. package/dist/index.d.ts +0 -2
  380. package/dist/langwatch-api-secrets-BUBSNCNO.js +0 -41
  381. package/dist/langwatch-api-triggers-EDFUCJN6.js +0 -31
  382. package/dist/list-agents-KFHKGPXV.js +0 -33
  383. package/dist/list-scenarios-F2WYNRIE.js +0 -41
  384. package/dist/list-suites-CRY4VWW6.js +0 -43
  385. package/dist/report-issue-XYVAPAYJ.js +0 -412
  386. package/dist/run-suite-NKZCJ2IN.js +0 -30
  387. package/dist/search-traces-A6HFKPLJ.js +0 -79
  388. package/dist/update-prompt-ODCRQRN6.js +0 -25
  389. package/dist/update-suite-SNO3GS7T.js +0 -40
  390. package/src/__tests__/all-tools.integration.test.ts +0 -1626
  391. package/src/__tests__/config.unit.test.ts +0 -202
  392. package/src/__tests__/datasets.integration.test.ts +0 -620
  393. package/src/__tests__/datasets.unit.test.ts +0 -244
  394. package/src/__tests__/date-parsing.unit.test.ts +0 -78
  395. package/src/__tests__/discover-evaluator-schema.unit.test.ts +0 -89
  396. package/src/__tests__/discover-schema.unit.test.ts +0 -118
  397. package/src/__tests__/docker.integration.test.ts +0 -318
  398. package/src/__tests__/documentation-fetch-security.integration.test.ts +0 -182
  399. package/src/__tests__/evaluator-tools.unit.test.ts +0 -262
  400. package/src/__tests__/experiment-list-tools.unit.test.ts +0 -157
  401. package/src/__tests__/experiment-status.unit.test.ts +0 -194
  402. package/src/__tests__/feature-map-tool-drift.unit.test.ts +0 -93
  403. package/src/__tests__/get-experiment-results.unit.test.ts +0 -313
  404. package/src/__tests__/http-security-origin.unit.test.ts +0 -97
  405. package/src/__tests__/http-security-sessions.unit.test.ts +0 -209
  406. package/src/__tests__/http-security-verifier.unit.test.ts +0 -248
  407. package/src/__tests__/http-server-allocation.integration.test.ts +0 -279
  408. package/src/__tests__/http-server-origin.integration.test.ts +0 -169
  409. package/src/__tests__/http-server-session-auth.integration.test.ts +0 -341
  410. package/src/__tests__/http-transport.integration.test.ts +0 -395
  411. package/src/__tests__/integration.integration.test.ts +0 -288
  412. package/src/__tests__/langwatch-api.unit.test.ts +0 -661
  413. package/src/__tests__/model-provider-tools.unit.test.ts +0 -190
  414. package/src/__tests__/project-api-key-tools.unit.test.ts +0 -451
  415. package/src/__tests__/public-http-request-security.integration.test.ts +0 -118
  416. package/src/__tests__/report-issue.integration.test.ts +0 -147
  417. package/src/__tests__/scenario-tools.integration.test.ts +0 -286
  418. package/src/__tests__/scenario-tools.unit.test.ts +0 -188
  419. package/src/__tests__/schemas.unit.test.ts +0 -85
  420. package/src/__tests__/support/http-server-harness.ts +0 -138
  421. package/src/__tests__/tools.unit.test.ts +0 -924
  422. package/src/langwatch-api-suites.ts +0 -118
  423. package/src/schemas/filter-fields.ts +0 -119
  424. package/src/schemas/index.ts +0 -4
  425. package/src/tools/archive-suite.ts +0 -12
  426. package/src/tools/create-suite.ts +0 -42
  427. package/src/tools/get-suite.ts +0 -47
  428. package/src/tools/list-suites.ts +0 -43
  429. package/src/tools/run-suite.ts +0 -28
  430. package/src/tools/update-suite.ts +0 -44
@@ -2,21 +2,43 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import { z } from "zod";
3
3
 
4
4
  import packageJson from "../package.json" with { type: "json" };
5
- import { requireApiKey } from "./config.js";
6
- import { fetchDocumentation } from "./documentation-fetch.js";
5
+ import { requireApiKey } from "./config.ts";
6
+ import { fetchDocumentation } from "./documentation-fetch.ts";
7
+ import { createDatasetSchema, datasetColumnDefinitionSchema } from "./schemas/create-dataset.ts";
7
8
  import {
8
- createDatasetSchema,
9
- datasetColumnDefinitionSchema,
10
- } from "./schemas/create-dataset.js";
11
- import { handleExperimentResults } from "./tools/get-experiment-results.js";
12
- import { handleExperimentListRuns } from "./tools/list-experiment-runs.js";
13
- import { handleExperimentList } from "./tools/list-experiments.js";
14
- import { handleRunExperiment, handleExperimentStatus } from "./tools/run-experiment.js";
9
+ runParametersSchema,
10
+ runPlanScopeSchema,
11
+ runPlanTargetSchema,
12
+ } from "./schemas/run-plan.ts";
13
+ import {
14
+ evaluatorAttachmentsSchema,
15
+ scenarioFieldValuesSchema,
16
+ suiteFieldsSchema,
17
+ } from "./schemas/suite-fields.ts";
18
+ import {
19
+ actionParamsSchema,
20
+ alertTypeSchema,
21
+ graphAlertSchema,
22
+ notificationCadenceSchema,
23
+ reportSchema,
24
+ SLACK_DELIVERY_NOTE,
25
+ templatesSchema,
26
+ TRIGGER_FILTER_QUERY_DESCRIPTION,
27
+ TRIGGER_FILTERS_DESCRIPTION,
28
+ triggerActionSchema,
29
+ validateActionParamsForAction,
30
+ } from "./schemas/triggers.ts";
31
+ import { handleExperimentResults } from "./tools/get-experiment-results.ts";
32
+ import { handleExperimentListRuns } from "./tools/list-experiment-runs.ts";
33
+ import { handleExperimentList } from "./tools/list-experiments.ts";
34
+ import { handleRunExperiment, handleExperimentStatus } from "./tools/run-experiment.ts";
35
+ import { handleTestAgent } from "./tools/test-agent.ts";
36
+ import { handleUpdateTestSuite } from "./tools/update-test-suite.ts";
15
37
 
16
38
  const modelSchema = z
17
39
  .string()
18
40
  .describe(
19
- 'Model in "provider/model-name" format, e.g., "openai/gpt-4o", "anthropic/claude-sonnet-4-5-20250929"'
41
+ 'Model in "provider/model-name" format, e.g., "openai/gpt-4o", "anthropic/claude-sonnet-4-5-20250929"',
20
42
  );
21
43
 
22
44
  /**
@@ -59,6 +81,29 @@ function withToolLogging<T extends unknown[], R>(
59
81
  }
60
82
 
61
83
  function registerTools(server: McpServer): void {
84
+ registerDocsTools(server);
85
+ registerObservabilityTools(server);
86
+ registerPromptTools(server);
87
+ registerScenarioTools(server);
88
+ registerRunPlanTools(server);
89
+ registerTestSuiteTools(server);
90
+ registerSimulationRunTools(server);
91
+ registerEvaluatorTools(server);
92
+ registerModelProviderTools(server);
93
+ registerAgentTools(server);
94
+ registerDashboardTools(server);
95
+ registerWorkflowTools(server);
96
+ registerAnnotationTools(server);
97
+ registerTriggerTools(server);
98
+ registerMonitorTools(server);
99
+ registerSecretTools(server);
100
+ registerExperimentTools(server);
101
+ registerDatasetTools(server);
102
+ registerProjectTools(server);
103
+ registerApiKeyTools(server);
104
+ }
105
+
106
+ function registerDocsTools(server: McpServer): void {
62
107
  server.tool(
63
108
  "fetch_langwatch_docs",
64
109
  "Fetches the LangWatch docs for understanding how to implement LangWatch in your codebase. Always use this tool when the user asks for help with LangWatch. Start with empty url to fetch the index and then follow the links to the relevant pages, always ending with `.md` extension",
@@ -67,13 +112,13 @@ function registerTools(server: McpServer): void {
67
112
  .string()
68
113
  .optional()
69
114
  .describe(
70
- "The full url of the specific doc page. If not provided, the docs index will be fetched."
115
+ "The full url of the specific doc page. If not provided, the docs index will be fetched.",
71
116
  ),
72
117
  },
73
118
  withToolLogging("fetch_langwatch_docs", async ({ url }) => {
74
119
  const text = await fetchDocumentation("langwatch", url);
75
120
  return { content: [{ type: "text", text }] };
76
- })
121
+ }),
77
122
  );
78
123
 
79
124
  server.tool(
@@ -83,36 +128,33 @@ function registerTools(server: McpServer): void {
83
128
  user_approved: z
84
129
  .boolean()
85
130
  .describe(
86
- "Must be true, and only after the user explicitly agreed to send this report to LangWatch"
131
+ "Must be true, and only after the user explicitly agreed to send this report to LangWatch",
87
132
  ),
88
133
  title: z.string().max(300).describe("One-line description of the issue"),
89
134
  summary: z
90
135
  .string()
91
136
  .optional()
92
137
  .describe(
93
- "What you were trying to do, what went wrong (verbatim errors), and what you had to figure out the hard way"
138
+ "What you were trying to do, what went wrong (verbatim errors), and what you had to figure out the hard way",
94
139
  ),
95
140
  session_content: z
96
141
  .string()
97
142
  .optional()
98
143
  .describe(
99
- "Optional raw session transcript or log excerpt (JSONL or plain text); redacted locally before sending"
144
+ "Optional raw session transcript or log excerpt (JSONL or plain text); redacted locally before sending",
100
145
  ),
101
- contact_email: z
102
- .string()
103
- .optional()
104
- .describe("Optional contact email for follow-up"),
146
+ contact_email: z.string().optional().describe("Optional contact email for follow-up"),
105
147
  agent: z
106
148
  .string()
107
149
  .optional()
108
150
  .describe("Which coding agent this is, e.g. claude-code, codex, cursor"),
109
151
  },
110
152
  withToolLogging("report_issue", async (params) => {
111
- const { handleReportIssue } = await import("./tools/report-issue.js");
153
+ const { handleReportIssue } = await import("./tools/report-issue.ts");
112
154
  return {
113
155
  content: [{ type: "text", text: await handleReportIssue(params) }],
114
156
  };
115
- })
157
+ }),
116
158
  );
117
159
 
118
160
  server.tool(
@@ -123,24 +165,26 @@ function registerTools(server: McpServer): void {
123
165
  .string()
124
166
  .optional()
125
167
  .describe(
126
- "The full url of the specific doc page. If not provided, the docs index will be fetched."
168
+ "The full url of the specific doc page. If not provided, the docs index will be fetched.",
127
169
  ),
128
170
  },
129
171
  withToolLogging("fetch_scenario_docs", async ({ url }) => {
130
172
  const text = await fetchDocumentation("scenario", url);
131
173
  return { content: [{ type: "text", text }] };
132
- })
174
+ }),
133
175
  );
176
+ }
134
177
 
135
- // --- Observability Tools (require API key) ---
136
-
178
+ // --- Observability Tools (require API key) ---
179
+ function registerObservabilityTools(server: McpServer): void {
137
180
  server.tool(
138
181
  "discover_schema",
139
- "Discover available filter fields, metrics, aggregation types, group-by options, scenario schema, and evaluator types for LangWatch queries. Call this before using search_traces, get_analytics, scenario tools, or evaluator tools to understand available options.",
182
+ "Discover what LangWatch can be queried with: the trace filter fields and syntax, the analytics SQL views and columns, the analytics metrics, aggregation types, group-by options, scenario schema and evaluator types. Call this before using search_traces, run_query, get_analytics, scenario tools or evaluator tools, so you never guess a field, a column or a value.",
140
183
  {
141
184
  category: z
142
185
  .enum([
143
186
  "filters",
187
+ "lwql",
144
188
  "metrics",
145
189
  "aggregations",
146
190
  "groups",
@@ -148,113 +192,149 @@ function registerTools(server: McpServer): void {
148
192
  "evaluators",
149
193
  "all",
150
194
  ])
151
- .describe("Which schema category to discover"),
195
+ .describe(
196
+ "Which schema category to discover. 'filters' is the trace filter language, 'lwql' the analytics SQL views; both are read from the platform and need the API key",
197
+ ),
152
198
  evaluatorType: z
153
199
  .string()
154
200
  .optional()
155
201
  .describe(
156
- "When category is 'evaluators', provide a specific evaluator type (e.g. 'langevals/llm_boolean') to get its full schema details"
202
+ "When category is 'evaluators', provide a specific evaluator type (e.g. 'langevals/llm_boolean') to get its full schema details",
157
203
  ),
158
204
  },
159
- async ({ category, evaluatorType }) => {
205
+ withToolLogging("discover_schema", async ({ category, evaluatorType }) => {
160
206
  if (category === "scenarios") {
161
- const { formatScenarioSchema } = await import(
162
- "./tools/discover-scenario-schema.js"
163
- );
207
+ const { formatScenarioSchema } = await import("./tools/discover-scenario-schema.ts");
164
208
  return {
165
209
  content: [{ type: "text", text: formatScenarioSchema() }],
166
210
  };
167
211
  }
168
212
  if (category === "evaluators") {
169
- const { formatEvaluatorSchema } = await import(
170
- "./tools/discover-evaluator-schema.js"
171
- );
213
+ const { formatEvaluatorSchema } = await import("./tools/discover-evaluator-schema.ts");
172
214
  return {
173
- content: [
174
- { type: "text", text: formatEvaluatorSchema(evaluatorType) },
175
- ],
215
+ content: [{ type: "text", text: formatEvaluatorSchema(evaluatorType) }],
176
216
  };
177
217
  }
178
- const { formatSchema } = await import("./tools/discover-schema.js");
179
- let text = formatSchema(category);
218
+ const { formatSchema, needsQueryReference } = await import("./tools/discover-schema.ts");
219
+ // The filter and analytics-SQL halves are the PLATFORM's registries, not
220
+ // copies of them, so those categories need the credential. The static
221
+ // ones still answer without it.
222
+ if (needsQueryReference(category)) requireApiKey();
223
+ let text = await formatSchema(category);
180
224
  if (category === "all") {
181
- const { formatScenarioSchema } = await import(
182
- "./tools/discover-scenario-schema.js"
183
- );
225
+ const { formatScenarioSchema } = await import("./tools/discover-scenario-schema.ts");
184
226
  text += "\n\n" + formatScenarioSchema();
185
- const { formatEvaluatorSchema } = await import(
186
- "./tools/discover-evaluator-schema.js"
187
- );
227
+ const { formatEvaluatorSchema } = await import("./tools/discover-evaluator-schema.ts");
188
228
  text += "\n\n" + formatEvaluatorSchema();
189
229
  }
190
230
  return { content: [{ type: "text", text }] };
191
- }
231
+ }),
232
+ );
233
+
234
+ server.tool(
235
+ "run_query",
236
+ "Run one read-only analytics SQL statement (LangWatchQL) over this project's traces, spans, evaluations and metrics, and get the rows back as a table. This is the tool for a count, a rate, a sum, a percentile or any of them grouped or over time — questions search_traces would need many calls to answer. The statement runs exactly as written. Call discover_schema with category 'lwql' first for the views, columns and worked statements.",
237
+ {
238
+ sql: z
239
+ .string()
240
+ .describe(
241
+ "The SELECT to run. Filter on the dataset's time column so the read is bounded, and never select a whole attribute map",
242
+ ),
243
+ parameters: z
244
+ .record(z.string(), z.union([z.string(), z.number(), z.boolean(), z.null()]))
245
+ .optional()
246
+ .describe(
247
+ 'Values for the parameters the statement declares, e.g. {"days": 7} for {days:UInt32}',
248
+ ),
249
+ },
250
+ withToolLogging("run_query", async (params) => {
251
+ requireApiKey();
252
+ const { handleRunQuery } = await import("./tools/run-query.ts");
253
+ return {
254
+ content: [{ type: "text", text: await handleRunQuery(params) }],
255
+ };
256
+ }),
192
257
  );
193
258
 
194
259
  server.tool(
195
260
  "search_traces",
196
- "Search LangWatch traces with filters, text query, and date range. Returns AI-readable trace digests by default. Use format: 'json' for full raw data.",
261
+ "Search LangWatch traces by content, with filters and a date range. Returns AI-readable trace digests by default. Use format: 'json' for full raw data. To fetch a trace you already have the ID for, use get_trace instead — free text does not match trace IDs.",
197
262
  {
198
- query: z.string().optional().describe("Text search query"),
263
+ query: z
264
+ .string()
265
+ .optional()
266
+ .describe(
267
+ "Text search query. Matches captured input/output, the trace name and span names ONLY. It does not match trace IDs — to fetch a known trace ID use get_trace, or pass traceIds here for several at once.",
268
+ ),
269
+ traceIds: z
270
+ .array(z.string().min(1))
271
+ .min(1)
272
+ .max(1000)
273
+ .optional()
274
+ .describe(
275
+ "Exact trace IDs to fetch (1–1000). Use instead of query when you already know the IDs. When set and no startDate is given, the window defaults to the last 90 days rather than 24 hours.",
276
+ ),
199
277
  filters: z
200
278
  .record(z.string(), z.array(z.string()))
201
279
  .optional()
202
280
  .describe(
203
- 'Filter traces. Format: {"field": ["value"]}. Use discover_schema for field names.'
281
+ 'Filter traces by the older per-field map. Format: {"field": ["value"]}. Use discover_schema for field names.',
204
282
  ),
205
- startDate: z
283
+ filter: z
206
284
  .string()
207
285
  .optional()
208
286
  .describe(
209
- 'Start date: ISO string or relative like "24h", "7d", "30d". Default: 24h ago'
287
+ 'Filter traces with the Trace Explorer\'s own query language: "status:error AND model:gpt-*", "trace.attribute.langwatch.user_id:alice", "evaluatorVerdict:fail", a quoted phrase for free text. Reaches attribute keys, span events and evaluator verdicts that the `filters` map cannot. Combined with `filters` and `query` when you send more than one. Call discover_schema with category \'filters\' for every field and the syntax.',
210
288
  ),
211
- endDate: z
289
+ startDate: z
212
290
  .string()
291
+ .min(1)
213
292
  .optional()
214
- .describe("End date: ISO string or relative. Default: now"),
215
- pageSize: z
216
- .number()
217
- .optional()
218
- .describe("Results per page (default: 25, max: 1000)"),
219
- scrollId: z
293
+ .describe(
294
+ 'Start of the window: ISO date, or relative like "24h", "7d", "4w", "3m" — units are h (hours), d (days), w (weeks), m (30-day months). Defaults to 24h ago for a text search, or 90d when traceIds is set. Widen this when a search comes back empty.',
295
+ ),
296
+ endDate: z
220
297
  .string()
298
+ .min(1)
221
299
  .optional()
222
- .describe("Pagination token from previous search"),
300
+ .describe("End of the window: ISO date or relative. Default: now"),
301
+ pageSize: z.number().optional().describe("Results per page (default: 25, max: 1000)"),
302
+ scrollId: z.string().optional().describe("Pagination token from previous search"),
223
303
  format: z
224
304
  .enum(["digest", "json"])
225
305
  .optional()
226
- .describe(
227
- "Output format: 'digest' (default, AI-readable) or 'json' (full raw data)"
228
- ),
306
+ .describe("Output format: 'digest' (default, AI-readable) or 'json' (full raw data)"),
229
307
  },
230
308
  withToolLogging("search_traces", async (params) => {
231
309
  requireApiKey();
232
- const { handleSearchTraces } = await import("./tools/search-traces.js");
310
+ const { handleSearchTraces } = await import("./tools/search-traces.ts");
233
311
  return {
234
312
  content: [{ type: "text", text: await handleSearchTraces(params) }],
235
313
  };
236
- })
314
+ }),
237
315
  );
238
316
 
239
317
  server.tool(
240
318
  "get_trace",
241
- "Get full details of a single trace by ID. Returns AI-readable trace digest by default. Use format: 'json' for full raw data including all spans.",
319
+ "Get full details of a single trace by ID. This is the right tool whenever you have a trace ID — search_traces does not match IDs. A full ID resolves at any age; a unique 8–31 character hex prefix resolves within the last 90 days. Returns AI-readable trace digest by default. Use format: 'json' for full raw data including all spans.",
242
320
  {
243
- traceId: z.string().describe("The trace ID to retrieve"),
321
+ traceId: z
322
+ .string()
323
+ .describe(
324
+ "The trace ID to retrieve. A full ID resolves at any age; a unique 8–31 character hex prefix also resolves, git-style, within the last 90 days.",
325
+ ),
244
326
  format: z
245
327
  .enum(["digest", "json"])
246
328
  .optional()
247
- .describe(
248
- "Output format: 'digest' (default, AI-readable) or 'json' (full raw data)"
249
- ),
329
+ .describe("Output format: 'digest' (default, AI-readable) or 'json' (full raw data)"),
250
330
  },
251
331
  withToolLogging("get_trace", async (params) => {
252
332
  requireApiKey();
253
- const { handleGetTrace } = await import("./tools/get-trace.js");
333
+ const { handleGetTrace } = await import("./tools/get-trace.ts");
254
334
  return {
255
335
  content: [{ type: "text", text: await handleGetTrace(params) }],
256
336
  };
257
- })
337
+ }),
258
338
  );
259
339
 
260
340
  server.tool(
@@ -264,44 +344,40 @@ function registerTools(server: McpServer): void {
264
344
  metric: z
265
345
  .string()
266
346
  .describe(
267
- 'Metric in "category.name" format, e.g., "metadata.trace_id", "performance.total_cost"'
347
+ 'Metric in "category.name" format, e.g., "metadata.trace_id", "performance.total_cost"',
268
348
  ),
269
349
  aggregation: z
270
350
  .string()
271
351
  .optional()
272
352
  .describe(
273
- "Aggregation type: avg, sum, min, max, median, p90, p95, p99, cardinality, terms. Default: avg"
353
+ "Aggregation type: avg, sum, min, max, median, p90, p95, p99, cardinality, terms. Default: avg",
274
354
  ),
275
355
  startDate: z
276
356
  .string()
277
357
  .optional()
278
- .describe(
279
- 'Start date: ISO or relative ("7d", "30d"). Default: 7 days ago'
280
- ),
358
+ .describe('Start date: ISO or relative ("7d", "30d"). Default: 7 days ago'),
281
359
  endDate: z.string().optional().describe("End date. Default: now"),
282
360
  timeZone: z.string().optional().describe("Timezone. Default: UTC"),
283
361
  groupBy: z
284
362
  .string()
285
363
  .optional()
286
364
  .describe("Group results by field. Use discover_schema for options."),
287
- filters: z
288
- .record(z.string(), z.array(z.string()))
289
- .optional()
290
- .describe("Filters to apply"),
365
+ filters: z.record(z.string(), z.array(z.string())).optional().describe("Filters to apply"),
291
366
  },
292
367
  withToolLogging("get_analytics", async (params) => {
293
368
  requireApiKey();
294
- const { handleGetAnalytics } = await import("./tools/get-analytics.js");
369
+ const { handleGetAnalytics } = await import("./tools/get-analytics.ts");
295
370
  return {
296
371
  content: [{ type: "text", text: await handleGetAnalytics(params) }],
297
372
  };
298
- })
373
+ }),
299
374
  );
375
+ }
300
376
 
301
- // --- Platform Prompt Tools (require API key) ---
302
- // These tools manage prompts on the LangWatch platform via API.
303
- // For code-based prompt management, see `fetch_langwatch_docs` for the CLI/SDK approach.
304
-
377
+ // --- Platform Prompt Tools (require API key) ---
378
+ // These tools manage prompts on the LangWatch platform via API.
379
+ // For code-based prompt management, see `fetch_langwatch_docs` for the CLI/SDK approach.
380
+ function registerPromptTools(server: McpServer): void {
305
381
  server.tool(
306
382
  "platform_create_prompt",
307
383
  `Create a new prompt on the LangWatch platform.
@@ -317,32 +393,31 @@ NOTE: Prompts can be managed two ways. Determine which approach the user needs:
317
393
  handle: z
318
394
  .string()
319
395
  .optional()
320
- .describe(
321
- "URL-friendly handle (auto-generated from name if omitted)"
322
- ),
396
+ .describe("URL-friendly handle (auto-generated from name if omitted)"),
323
397
  messages: z
324
398
  .array(
325
399
  z.object({
326
- role: z
327
- .enum(["system", "user", "assistant"])
328
- .describe("Message role"),
400
+ role: z.enum(["system", "user", "assistant"]).describe("Message role"),
329
401
  content: z.string().describe("Message content"),
330
- })
402
+ }),
331
403
  )
332
404
  .describe("Prompt messages"),
333
405
  model: modelSchema,
334
- tags: z.array(z.string()).optional().describe(
335
- 'Tags to assign to the initial version (e.g., ["production", "staging"]). ' +
336
- 'Built-in tags: "latest" (auto-assigned), "production", "staging". Custom tags must be created first.'
337
- ),
406
+ tags: z
407
+ .array(z.string())
408
+ .optional()
409
+ .describe(
410
+ 'Tags to assign to the initial version (e.g., ["production", "staging"]). ' +
411
+ 'Built-in tags: "latest" (auto-assigned), "production", "staging". Custom tags must be created first.',
412
+ ),
338
413
  },
339
414
  withToolLogging("platform_create_prompt", async (params) => {
340
415
  requireApiKey();
341
- const { handleCreatePrompt } = await import("./tools/create-prompt.js");
416
+ const { handleCreatePrompt } = await import("./tools/create-prompt.ts");
342
417
  return {
343
418
  content: [{ type: "text", text: await handleCreatePrompt(params) }],
344
419
  };
345
- })
420
+ }),
346
421
  );
347
422
 
348
423
  server.tool(
@@ -351,26 +426,30 @@ NOTE: Prompts can be managed two ways. Determine which approach the user needs:
351
426
  {},
352
427
  withToolLogging("platform_list_prompts", async () => {
353
428
  requireApiKey();
354
- const { handleListPrompts } = await import("./tools/list-prompts.js");
429
+ const { handleListPrompts } = await import("./tools/list-prompts.ts");
355
430
  return {
356
431
  content: [{ type: "text", text: await handleListPrompts() }],
357
432
  };
358
- })
433
+ }),
359
434
  );
360
435
 
361
436
  server.tool(
362
437
  "platform_get_prompt",
363
- "Get a specific prompt from the LangWatch platform by ID or handle, including messages, model config, and version history.",
438
+ "Get a specific prompt from the LangWatch platform by ID or handle, including messages, model config, and version history. Use format: 'json' for the full raw API payload, or 'digest' (default) for a formatted summary.",
364
439
  {
365
440
  idOrHandle: z.string().describe("Prompt ID or handle"),
366
- version: z
367
- .number()
441
+ version: z.number().optional().describe("Specific version number (default: latest)"),
442
+ tag: z
443
+ .string()
368
444
  .optional()
369
- .describe("Specific version number (default: latest)"),
370
- tag: z.string().optional().describe(
371
- 'Fetch the version pointed to by this tag (e.g., "production", "staging"). ' +
372
- 'Alternatively, use shorthand in idOrHandle: "pizza-prompt:production"'
373
- ),
445
+ .describe(
446
+ 'Fetch the version pointed to by this tag (e.g., "production", "staging"). ' +
447
+ 'Alternatively, use shorthand in idOrHandle: "pizza-prompt:production"',
448
+ ),
449
+ format: z
450
+ .enum(["digest", "json"])
451
+ .optional()
452
+ .describe("Output format: 'digest' (default, AI-readable) or 'json' (full raw data)"),
374
453
  },
375
454
  withToolLogging("platform_get_prompt", async (params) => {
376
455
  if (params.version != null && params.tag) {
@@ -380,11 +459,11 @@ NOTE: Prompts can be managed two ways. Determine which approach the user needs:
380
459
  };
381
460
  }
382
461
  requireApiKey();
383
- const { handleGetPrompt } = await import("./tools/get-prompt.js");
462
+ const { handleGetPrompt } = await import("./tools/get-prompt.ts");
384
463
  return {
385
464
  content: [{ type: "text", text: await handleGetPrompt(params) }],
386
465
  };
387
- })
466
+ }),
388
467
  );
389
468
 
390
469
  server.tool(
@@ -397,31 +476,30 @@ NOTE: Prompts can be managed two ways. Determine which approach the user needs:
397
476
  z.object({
398
477
  role: z.enum(["system", "user", "assistant"]),
399
478
  content: z.string(),
400
- })
479
+ }),
401
480
  )
402
481
  .optional()
403
482
  .describe("Updated messages"),
404
483
  model: modelSchema.optional(),
405
- commitMessage: z
406
- .string()
407
- .describe("Commit message describing the change"),
408
- tags: z.array(z.string()).optional().describe(
409
- 'Tags to assign to the new version created by this update.'
410
- ),
484
+ commitMessage: z.string().describe("Commit message describing the change"),
485
+ tags: z
486
+ .array(z.string())
487
+ .optional()
488
+ .describe("Tags to assign to the new version created by this update."),
411
489
  },
412
490
  withToolLogging("platform_update_prompt", async (params) => {
413
491
  requireApiKey();
414
- const { handleUpdatePrompt } = await import("./tools/update-prompt.js");
492
+ const { handleUpdatePrompt } = await import("./tools/update-prompt.ts");
415
493
  return {
416
494
  content: [{ type: "text", text: await handleUpdatePrompt(params) }],
417
495
  };
418
- })
496
+ }),
419
497
  );
420
498
 
421
499
  server.tool(
422
500
  "platform_assign_prompt_tag",
423
501
  'Assign a tag (e.g. "production") to a specific version of a prompt. ' +
424
- 'Use this to "deploy" a version by promoting it to the production tag.',
502
+ 'Use this to "deploy" a version by promoting it to the production tag.',
425
503
  {
426
504
  idOrHandle: z.string().describe("Prompt ID or handle"),
427
505
  tag: z.string().describe('Tag name (e.g., "production", "staging")'),
@@ -429,41 +507,41 @@ NOTE: Prompts can be managed two ways. Determine which approach the user needs:
429
507
  },
430
508
  withToolLogging("platform_assign_prompt_tag", async (params) => {
431
509
  requireApiKey();
432
- const { handleAssignPromptTag } = await import("./tools/assign-prompt-tag.js");
510
+ const { handleAssignPromptTag } = await import("./tools/assign-prompt-tag.ts");
433
511
  return {
434
512
  content: [{ type: "text", text: await handleAssignPromptTag(params) }],
435
513
  };
436
- })
514
+ }),
437
515
  );
438
516
 
439
517
  server.tool(
440
518
  "platform_list_prompt_tags",
441
519
  "List all prompt tag definitions for the organization. " +
442
- "Shows built-in tags (latest, production, staging) and any custom tags.",
520
+ "Shows built-in tags (latest, production, staging) and any custom tags.",
443
521
  {},
444
522
  withToolLogging("platform_list_prompt_tags", async () => {
445
523
  requireApiKey();
446
- const { handleListPromptTags } = await import("./tools/list-prompt-tags.js");
524
+ const { handleListPromptTags } = await import("./tools/list-prompt-tags.ts");
447
525
  return {
448
526
  content: [{ type: "text", text: await handleListPromptTags() }],
449
527
  };
450
- })
528
+ }),
451
529
  );
452
530
 
453
531
  server.tool(
454
532
  "platform_create_prompt_tag",
455
533
  "Create a custom prompt tag definition for the organization. " +
456
- 'Tag names must be non-numeric and not "latest".',
534
+ 'Tag names must be non-numeric and not "latest".',
457
535
  {
458
536
  name: z.string().describe("Tag name to create"),
459
537
  },
460
538
  withToolLogging("platform_create_prompt_tag", async (params) => {
461
539
  requireApiKey();
462
- const { handleCreatePromptTag } = await import("./tools/create-prompt-tag.js");
540
+ const { handleCreatePromptTag } = await import("./tools/create-prompt-tag.ts");
463
541
  return {
464
542
  content: [{ type: "text", text: await handleCreatePromptTag(params) }],
465
543
  };
466
- })
544
+ }),
467
545
  );
468
546
 
469
547
  server.tool(
@@ -475,11 +553,11 @@ NOTE: Prompts can be managed two ways. Determine which approach the user needs:
475
553
  },
476
554
  withToolLogging("platform_rename_prompt_tag", async (params) => {
477
555
  requireApiKey();
478
- const { handleRenamePromptTag } = await import("./tools/rename-prompt-tag.js");
556
+ const { handleRenamePromptTag } = await import("./tools/rename-prompt-tag.ts");
479
557
  return {
480
558
  content: [{ type: "text", text: await handleRenamePromptTag(params) }],
481
559
  };
482
- })
560
+ }),
483
561
  );
484
562
 
485
563
  server.tool(
@@ -490,17 +568,18 @@ NOTE: Prompts can be managed two ways. Determine which approach the user needs:
490
568
  },
491
569
  withToolLogging("platform_delete_prompt_tag", async (params) => {
492
570
  requireApiKey();
493
- const { handleDeletePromptTag } = await import("./tools/delete-prompt-tag.js");
571
+ const { handleDeletePromptTag } = await import("./tools/delete-prompt-tag.ts");
494
572
  return {
495
573
  content: [{ type: "text", text: await handleDeletePromptTag(params) }],
496
574
  };
497
- })
575
+ }),
498
576
  );
577
+ }
499
578
 
500
- // --- Platform Scenario Tools (require API key) ---
501
- // These tools manage scenarios on the LangWatch platform via API.
502
- // For code-based scenario testing, see `fetch_scenario_docs` for the SDK approach.
503
-
579
+ // --- Platform Scenario Tools (require API key) ---
580
+ // These tools manage scenarios on the LangWatch platform via API.
581
+ // For code-based scenario testing, see `fetch_scenario_docs` for the SDK approach.
582
+ function registerScenarioTools(server: McpServer): void {
504
583
  server.tool(
505
584
  "platform_create_scenario",
506
585
  `Create a new scenario on the LangWatch platform. Call discover_schema({ category: 'scenarios' }) first to learn how to write effective situations and criteria.
@@ -515,55 +594,49 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
515
594
  name: z.string().describe("Scenario name"),
516
595
  situation: z
517
596
  .string()
518
- .describe(
519
- "The context or setup describing what the user/agent is doing"
520
- ),
597
+ .describe("The context or setup describing what the user/agent is doing"),
521
598
  criteria: z
522
599
  .array(z.string())
523
600
  .optional()
524
- .describe(
525
- "Pass/fail conditions the agent's response must satisfy"
526
- ),
601
+ .describe("Pass/fail conditions the agent's response must satisfy"),
527
602
  labels: z
528
603
  .array(z.string())
529
604
  .optional()
530
605
  .describe("Tags for organizing and filtering scenarios"),
606
+ testSuiteId: z
607
+ .string()
608
+ .nullish()
609
+ .describe(
610
+ "The test suite to file this scenario in. Pass a test suite ID, or null to unfile it.",
611
+ ),
612
+ fields: scenarioFieldValuesSchema.optional(),
531
613
  },
532
614
  withToolLogging("platform_create_scenario", async (params) => {
533
615
  requireApiKey();
534
- const { handleCreateScenario } = await import(
535
- "./tools/create-scenario.js"
536
- );
616
+ const { handleCreateScenario } = await import("./tools/create-scenario.ts");
537
617
  return {
538
- content: [
539
- { type: "text", text: await handleCreateScenario(params) },
540
- ],
618
+ content: [{ type: "text", text: await handleCreateScenario(params) }],
541
619
  };
542
- })
620
+ }),
543
621
  );
544
622
 
545
623
  server.tool(
546
624
  "platform_list_scenarios",
547
625
  "List all scenarios on the LangWatch platform. Returns AI-readable digest by default.",
548
626
  {
627
+ testSuiteId: z.string().optional().describe("Only the scenarios filed in this test suite"),
549
628
  format: z
550
629
  .enum(["digest", "json"])
551
630
  .optional()
552
- .describe(
553
- "Output format: 'digest' (default, AI-readable) or 'json' (full raw data)"
554
- ),
631
+ .describe("Output format: 'digest' (default, AI-readable) or 'json' (full raw data)"),
555
632
  },
556
633
  withToolLogging("platform_list_scenarios", async (params) => {
557
634
  requireApiKey();
558
- const { handleListScenarios } = await import(
559
- "./tools/list-scenarios.js"
560
- );
635
+ const { handleListScenarios } = await import("./tools/list-scenarios.ts");
561
636
  return {
562
- content: [
563
- { type: "text", text: await handleListScenarios(params) },
564
- ],
637
+ content: [{ type: "text", text: await handleListScenarios(params) }],
565
638
  };
566
- })
639
+ }),
567
640
  );
568
641
 
569
642
  server.tool(
@@ -574,17 +647,15 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
574
647
  format: z
575
648
  .enum(["digest", "json"])
576
649
  .optional()
577
- .describe(
578
- "Output format: 'digest' (default, AI-readable) or 'json' (full raw data)"
579
- ),
650
+ .describe("Output format: 'digest' (default, AI-readable) or 'json' (full raw data)"),
580
651
  },
581
652
  withToolLogging("platform_get_scenario", async (params) => {
582
653
  requireApiKey();
583
- const { handleGetScenario } = await import("./tools/get-scenario.js");
654
+ const { handleGetScenario } = await import("./tools/get-scenario.ts");
584
655
  return {
585
656
  content: [{ type: "text", text: await handleGetScenario(params) }],
586
657
  };
587
- })
658
+ }),
588
659
  );
589
660
 
590
661
  server.tool(
@@ -594,26 +665,23 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
594
665
  scenarioId: z.string().describe("The scenario ID to update"),
595
666
  name: z.string().optional().describe("Updated scenario name"),
596
667
  situation: z.string().optional().describe("Updated situation"),
597
- criteria: z
598
- .array(z.string())
599
- .optional()
600
- .describe("Updated criteria"),
601
- labels: z
602
- .array(z.string())
603
- .optional()
604
- .describe("Updated labels"),
668
+ criteria: z.array(z.string()).optional().describe("Updated criteria"),
669
+ labels: z.array(z.string()).optional().describe("Updated labels"),
670
+ testSuiteId: z
671
+ .string()
672
+ .nullish()
673
+ .describe(
674
+ "The test suite to file this scenario in. Pass a test suite ID, or null to unfile it.",
675
+ ),
676
+ fields: scenarioFieldValuesSchema.optional(),
605
677
  },
606
678
  withToolLogging("platform_update_scenario", async (params) => {
607
679
  requireApiKey();
608
- const { handleUpdateScenario } = await import(
609
- "./tools/update-scenario.js"
610
- );
680
+ const { handleUpdateScenario } = await import("./tools/update-scenario.ts");
611
681
  return {
612
- content: [
613
- { type: "text", text: await handleUpdateScenario(params) },
614
- ],
682
+ content: [{ type: "text", text: await handleUpdateScenario(params) }],
615
683
  };
616
- })
684
+ }),
617
685
  );
618
686
 
619
687
  server.tool(
@@ -624,131 +692,333 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
624
692
  },
625
693
  withToolLogging("platform_archive_scenario", async (params) => {
626
694
  requireApiKey();
627
- const { handleArchiveScenario } = await import(
628
- "./tools/archive-scenario.js"
629
- );
695
+ const { handleArchiveScenario } = await import("./tools/archive-scenario.ts");
630
696
  return {
631
- content: [
632
- { type: "text", text: await handleArchiveScenario(params) },
633
- ],
697
+ content: [{ type: "text", text: await handleArchiveScenario(params) }],
634
698
  };
635
- })
699
+ }),
636
700
  );
701
+ }
637
702
 
638
- // --- Platform Suite / Run Plan Tools (require API key) ---
639
- // These tools manage suites (run plans) on the LangWatch platform via API.
703
+ // --- Platform Run Plan Tools (require API key) ---
704
+ // A run plan is what you run, and its NAME identifies it: running a name
705
+ // that exists replaces that plan's configuration, running a new name
706
+ // creates the plan. A test suite groups scenarios; running one is
707
+ // sugar that creates or joins the plan "<suite name> <target name>".
708
+ function registerRunPlanTools(server: McpServer): void {
709
+ server.tool(
710
+ "platform_run_plan",
711
+ "Run scenarios against targets. The plan name identifies the run plan: an existing name is re-run with the configuration you send here, a new name creates the plan. Configuration is the scope, the targets, the repeat count and the models; parameters, the note and the idempotency key belong to this run alone. Send more than one target to compare them in the same run, and give a target its own parameters to compare one agent on two models.",
712
+ {
713
+ name: z
714
+ .string()
715
+ .optional()
716
+ .describe(
717
+ "The run plan to run. An existing name is replaced with this configuration, a new name creates the plan. Omit to let the server name it after the suite and the target.",
718
+ ),
719
+ scope: runPlanScopeSchema.describe(
720
+ "What the plan covers: every scenario in the project, the scenarios filed in given test suites, the scenarios carrying given labels, or the hand-picked list in scenarioIds.",
721
+ ),
722
+ scenarioIds: z
723
+ .array(z.string())
724
+ .optional()
725
+ .describe("The scenarios to run. Read only when scope.mode is 'scenarios'."),
726
+ targets: z
727
+ .array(runPlanTargetSchema)
728
+ .describe(
729
+ "What to run the scenarios against. Every target runs every scenario, and the results show one column for each, so this is how one run compares two agents or one agent on two models.",
730
+ ),
731
+ repeatCount: z
732
+ .number()
733
+ .int()
734
+ .min(1)
735
+ .max(5)
736
+ .optional()
737
+ .describe("How many times to run each scenario against each target (1 to 5, default 1)"),
738
+ simulatorModel: z
739
+ .string()
740
+ .optional()
741
+ .describe("Model that plays the user. Omit for the project default."),
742
+ judgeModel: z
743
+ .string()
744
+ .optional()
745
+ .describe("Model that judges the criteria. Omit for the project default."),
746
+ evaluators: evaluatorAttachmentsSchema
747
+ .optional()
748
+ .describe(
749
+ "The plan's own evaluators, run beside the ones attached to the test suites its scenarios belong to. A plan evaluator reads the conversation and the trace, never a scenario field. Omit to keep what the plan already holds. See discover_schema({ category: 'scenarios' }) for the mapping paths.",
750
+ ),
751
+ parameters: runParametersSchema
752
+ .optional()
753
+ .describe(
754
+ "Values for the parameters the scenarios declare, by name. They apply to every target; a target that names the same parameter overrides them for itself.",
755
+ ),
756
+ note: z
757
+ .string()
758
+ .max(200)
759
+ .optional()
760
+ .describe("One short line saying why this run was started (max 200 characters)"),
761
+ idempotencyKey: z
762
+ .string()
763
+ .optional()
764
+ .describe(
765
+ "Send the same key to make a retry join the run it already started instead of starting a second one.",
766
+ ),
767
+ },
768
+ withToolLogging("platform_run_plan", async (params) => {
769
+ requireApiKey();
770
+ const { handleRunPlan } = await import("./tools/run-plan.ts");
771
+ return {
772
+ content: [{ type: "text", text: await handleRunPlan(params) }],
773
+ };
774
+ }),
775
+ );
640
776
 
641
777
  server.tool(
642
- "platform_list_suites",
643
- "List all suites (run plans) on the LangWatch platform. A suite bundles scenarios with targets for batch execution.",
778
+ "platform_list_run_plans",
779
+ "List the run plans of the project. A run plan holds the configuration a run used, keyed by its name.",
644
780
  {
781
+ includeArchived: z.boolean().optional().describe("Include archived plans (default false)"),
645
782
  format: z
646
783
  .enum(["digest", "json"])
647
784
  .optional()
648
785
  .describe("Output format: 'digest' (default) or 'json' (raw data)"),
649
786
  },
650
- withToolLogging("platform_list_suites", async (params) => {
787
+ withToolLogging("platform_list_run_plans", async (params) => {
651
788
  requireApiKey();
652
- const { handleListSuites } = await import("./tools/list-suites.js");
789
+ const { handleListRunPlans } = await import("./tools/list-run-plans.ts");
653
790
  return {
654
- content: [{ type: "text", text: await handleListSuites(params) }],
791
+ content: [{ type: "text", text: await handleListRunPlans(params) }],
655
792
  };
656
- })
793
+ }),
657
794
  );
658
795
 
659
796
  server.tool(
660
- "platform_get_suite",
661
- "Get detailed information about a specific suite (run plan) by ID.",
797
+ "platform_get_run_plan",
798
+ "Get the full configuration of a run plan: what it covers, its targets, its repeat count and its models.",
662
799
  {
663
- id: z.string().describe("The suite ID"),
800
+ id: z.string().describe("The run plan ID"),
664
801
  format: z
665
802
  .enum(["digest", "json"])
666
803
  .optional()
667
804
  .describe("Output format: 'digest' (default) or 'json'"),
668
805
  },
669
- withToolLogging("platform_get_suite", async (params) => {
806
+ withToolLogging("platform_get_run_plan", async (params) => {
807
+ requireApiKey();
808
+ const { handleGetRunPlan } = await import("./tools/get-run-plan.ts");
809
+ return {
810
+ content: [{ type: "text", text: await handleGetRunPlan(params) }],
811
+ };
812
+ }),
813
+ );
814
+
815
+ server.tool(
816
+ "platform_rerun_run_plan",
817
+ "Run a plan again with the configuration it already holds. Only the parameters and the note belong to the new run.",
818
+ {
819
+ id: z.string().describe("The run plan ID to run again"),
820
+ parameters: runParametersSchema
821
+ .optional()
822
+ .describe("Values for the parameters the scenarios declare, by name."),
823
+ note: z
824
+ .string()
825
+ .max(200)
826
+ .optional()
827
+ .describe("One short line saying why this run was started (max 200 characters)"),
828
+ },
829
+ withToolLogging("platform_rerun_run_plan", async (params) => {
830
+ requireApiKey();
831
+ const { handleRerunRunPlan } = await import("./tools/rerun-run-plan.ts");
832
+ return {
833
+ content: [{ type: "text", text: await handleRerunRunPlan(params) }],
834
+ };
835
+ }),
836
+ );
837
+
838
+ server.tool(
839
+ "platform_archive_run_plan",
840
+ "Archive a run plan. Its past runs stay readable and the name is free for a new plan.",
841
+ {
842
+ id: z.string().describe("The run plan ID to archive"),
843
+ },
844
+ withToolLogging("platform_archive_run_plan", async (params) => {
845
+ requireApiKey();
846
+ const { handleArchiveRunPlan } = await import("./tools/archive-run-plan.ts");
847
+ return {
848
+ content: [{ type: "text", text: await handleArchiveRunPlan(params) }],
849
+ };
850
+ }),
851
+ );
852
+ }
853
+
854
+ // --- Platform Test Suite Tools (require API key) ---
855
+ // A test suite groups scenarios: a name and the scenarios filed in it.
856
+ function registerTestSuiteTools(server: McpServer): void {
857
+ server.tool(
858
+ "platform_list_test_suites",
859
+ "List the test suites of the project. A test suite groups scenarios.",
860
+ {
861
+ format: z
862
+ .enum(["digest", "json"])
863
+ .optional()
864
+ .describe("Output format: 'digest' (default) or 'json' (raw data)"),
865
+ },
866
+ withToolLogging("platform_list_test_suites", async (params) => {
670
867
  requireApiKey();
671
- const { handleGetSuite } = await import("./tools/get-suite.js");
868
+ const { handleListTestSuites } = await import("./tools/list-test-suites.ts");
672
869
  return {
673
- content: [{ type: "text", text: await handleGetSuite(params) }],
870
+ content: [{ type: "text", text: await handleListTestSuites(params) }],
674
871
  };
675
- })
872
+ }),
676
873
  );
677
874
 
678
875
  server.tool(
679
- "platform_create_suite",
680
- "Create a new suite (run plan) that bundles scenarios with targets for batch execution.",
876
+ "platform_create_test_suite",
877
+ "Create a test suite. A test suite groups scenarios: file a scenario in it by passing the suite ID as testSuiteId on platform_create_scenario or platform_update_scenario. A suite can declare typed fields every scenario carries a value for, and attach saved evaluators that run after every scenario run with mappings from their inputs to the conversation, the scenario's fields or the trace. Call discover_schema({ category: 'scenarios' }) first for the field rules and the mapping paths.",
681
878
  {
682
- name: z.string().describe("Suite name"),
683
- description: z.string().optional().describe("Suite description"),
684
- scenarioIds: z.array(z.string()).describe("Array of scenario IDs to include"),
685
- targets: z.string().describe('JSON array of target objects, e.g. [{"type":"http","referenceId":"agent_abc"}]'),
686
- repeatCount: z.number().optional().describe("Number of times to repeat each scenario-target pair (default: 1)"),
687
- labels: z.array(z.string()).optional().describe("Tags for organizing suites"),
879
+ name: z.string().describe("Test suite name"),
880
+ fields: suiteFieldsSchema.optional(),
881
+ evaluators: evaluatorAttachmentsSchema.optional(),
688
882
  },
689
- withToolLogging("platform_create_suite", async (params) => {
883
+ withToolLogging("platform_create_test_suite", async (params) => {
690
884
  requireApiKey();
691
- const { handleCreateSuite } = await import("./tools/create-suite.js");
885
+ const { handleCreateTestSuite } = await import("./tools/create-test-suite.ts");
692
886
  return {
693
- content: [{ type: "text", text: await handleCreateSuite(params) }],
887
+ content: [{ type: "text", text: await handleCreateTestSuite(params) }],
694
888
  };
695
- })
889
+ }),
696
890
  );
697
891
 
698
892
  server.tool(
699
- "platform_update_suite",
700
- "Update an existing suite (run plan).",
893
+ "platform_get_test_suite",
894
+ "Get a test suite and the scenarios filed in it.",
701
895
  {
702
- id: z.string().describe("The suite ID to update"),
703
- name: z.string().optional().describe("New suite name"),
704
- description: z.string().optional().describe("New description"),
705
- scenarioIds: z.array(z.string()).optional().describe("New array of scenario IDs"),
706
- targets: z.string().optional().describe("New JSON array of targets"),
707
- repeatCount: z.number().optional().describe("New repeat count"),
708
- labels: z.array(z.string()).optional().describe("New labels"),
896
+ id: z.string().describe("The test suite ID"),
897
+ format: z
898
+ .enum(["digest", "json"])
899
+ .optional()
900
+ .describe("Output format: 'digest' (default) or 'json'"),
901
+ },
902
+ withToolLogging("platform_get_test_suite", async (params) => {
903
+ requireApiKey();
904
+ const { handleGetTestSuite } = await import("./tools/get-test-suite.ts");
905
+ return {
906
+ content: [{ type: "text", text: await handleGetTestSuite(params) }],
907
+ };
908
+ }),
909
+ );
910
+
911
+ server.tool(
912
+ "platform_update_test_suite",
913
+ "Edit a test suite: any of its name, its fields and its evaluators. A field list or an evaluator list replaces the one the suite holds; a key left out keeps what the suite has. A field an attached evaluator still reads cannot be removed. Call discover_schema({ category: 'scenarios' }) first for the field rules and the mapping paths.",
914
+ {
915
+ id: z.string().describe("The test suite ID"),
916
+ name: z.string().optional().describe("The new name. The slug is kept."),
917
+ fields: suiteFieldsSchema
918
+ .optional()
919
+ .describe(
920
+ "The full list of fields the suite declares. It replaces the list the suite holds.",
921
+ ),
922
+ evaluators: evaluatorAttachmentsSchema
923
+ .optional()
924
+ .describe(
925
+ "The full list of evaluators attached to the suite. It replaces the list the suite holds.",
926
+ ),
709
927
  },
710
- withToolLogging("platform_update_suite", async (params) => {
928
+ withToolLogging("platform_update_test_suite", async (params) => {
711
929
  requireApiKey();
712
- const { handleUpdateSuite } = await import("./tools/update-suite.js");
713
930
  return {
714
- content: [{ type: "text", text: await handleUpdateSuite(params) }],
931
+ content: [{ type: "text", text: await handleUpdateTestSuite(params) }],
715
932
  };
716
- })
933
+ }),
717
934
  );
718
935
 
719
936
  server.tool(
720
- "platform_run_suite",
721
- "Trigger a suite run. Schedules all scenario x target x repeat jobs for execution.",
937
+ "platform_rename_test_suite",
938
+ "Rename a test suite.",
722
939
  {
723
- id: z.string().describe("The suite ID to run"),
940
+ id: z.string().describe("The test suite ID"),
941
+ name: z.string().describe("The new name"),
724
942
  },
725
- withToolLogging("platform_run_suite", async (params) => {
943
+ withToolLogging("platform_rename_test_suite", async (params) => {
726
944
  requireApiKey();
727
- const { handleRunSuite } = await import("./tools/run-suite.js");
945
+ const { handleRenameTestSuite } = await import("./tools/rename-test-suite.ts");
728
946
  return {
729
- content: [{ type: "text", text: await handleRunSuite(params) }],
947
+ content: [{ type: "text", text: await handleRenameTestSuite(params) }],
730
948
  };
731
- })
949
+ }),
732
950
  );
733
951
 
734
952
  server.tool(
735
- "platform_archive_suite",
736
- "Archive (soft-delete) a suite (run plan).",
953
+ "platform_archive_test_suite",
954
+ "Archive a test suite. The scenarios filed in it are archived with it.",
737
955
  {
738
- id: z.string().describe("The suite ID to archive"),
956
+ id: z.string().describe("The test suite ID to archive"),
739
957
  },
740
- withToolLogging("platform_archive_suite", async (params) => {
958
+ withToolLogging("platform_archive_test_suite", async (params) => {
741
959
  requireApiKey();
742
- const { handleArchiveSuite } = await import("./tools/archive-suite.js");
960
+ const { handleArchiveTestSuite } = await import("./tools/archive-test-suite.ts");
743
961
  return {
744
- content: [{ type: "text", text: await handleArchiveSuite(params) }],
962
+ content: [{ type: "text", text: await handleArchiveTestSuite(params) }],
745
963
  };
746
- })
964
+ }),
747
965
  );
748
966
 
749
- // --- Platform Simulation Run Tools (require API key) ---
750
- // These tools query simulation run results from the LangWatch platform.
967
+ server.tool(
968
+ "platform_run_test_suite",
969
+ "Run every scenario of a test suite against targets. This creates or joins the run plan named '<suite name> <target name>' unless you send a name of your own. Send more than one target to compare them in the same run, and give a target its own parameters to compare one agent on two models.",
970
+ {
971
+ id: z.string().describe("The test suite ID to run"),
972
+ targets: z
973
+ .array(runPlanTargetSchema)
974
+ .describe(
975
+ "What to run the scenarios against. Every target runs every scenario, and the results show one column for each.",
976
+ ),
977
+ name: z
978
+ .string()
979
+ .optional()
980
+ .describe(
981
+ "The run plan to run. Omit to let the server name it after the suite and the target.",
982
+ ),
983
+ repeatCount: z
984
+ .number()
985
+ .int()
986
+ .min(1)
987
+ .max(5)
988
+ .optional()
989
+ .describe("How many times to run each scenario against each target (1 to 5, default 1)"),
990
+ simulatorModel: z
991
+ .string()
992
+ .optional()
993
+ .describe("Model that plays the user. Omit for the project default."),
994
+ judgeModel: z
995
+ .string()
996
+ .optional()
997
+ .describe("Model that judges the criteria. Omit for the project default."),
998
+ parameters: runParametersSchema
999
+ .optional()
1000
+ .describe(
1001
+ "Values for the parameters the scenarios declare, by name. They apply to every target; a target that names the same parameter overrides them for itself.",
1002
+ ),
1003
+ note: z
1004
+ .string()
1005
+ .max(200)
1006
+ .optional()
1007
+ .describe("One short line saying why this run was started (max 200 characters)"),
1008
+ },
1009
+ withToolLogging("platform_run_test_suite", async (params) => {
1010
+ requireApiKey();
1011
+ const { handleRunTestSuite } = await import("./tools/run-test-suite.ts");
1012
+ return {
1013
+ content: [{ type: "text", text: await handleRunTestSuite(params) }],
1014
+ };
1015
+ }),
1016
+ );
1017
+ }
751
1018
 
1019
+ // --- Platform Simulation Run Tools (require API key) ---
1020
+ // These tools query simulation run results from the LangWatch platform.
1021
+ function registerSimulationRunTools(server: McpServer): void {
752
1022
  server.tool(
753
1023
  "platform_list_simulation_runs",
754
1024
  "List simulation run results. Filter by scenario set or batch run ID to see specific results.",
@@ -760,32 +1030,33 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
760
1030
  },
761
1031
  withToolLogging("platform_list_simulation_runs", async (params) => {
762
1032
  requireApiKey();
763
- const { handleListSimulationRuns } = await import("./tools/list-simulation-runs.js");
1033
+ const { handleListSimulationRuns } = await import("./tools/list-simulation-runs.ts");
764
1034
  return {
765
1035
  content: [{ type: "text", text: await handleListSimulationRuns(params) }],
766
1036
  };
767
- })
1037
+ }),
768
1038
  );
769
1039
 
770
1040
  server.tool(
771
1041
  "platform_get_simulation_run",
772
- "Get full details of a simulation run including conversation messages, results, costs, and verdict.",
1042
+ "Get full details of a simulation run including conversation messages, results, costs, the verdict and the result of every evaluator attached to its test suite or run plan.",
773
1043
  {
774
1044
  scenarioRunId: z.string().describe("The simulation run ID"),
775
1045
  format: z.enum(["digest", "json"]).optional().describe("Output format"),
776
1046
  },
777
1047
  withToolLogging("platform_get_simulation_run", async (params) => {
778
1048
  requireApiKey();
779
- const { handleGetSimulationRun } = await import("./tools/get-simulation-run.js");
1049
+ const { handleGetSimulationRun } = await import("./tools/get-simulation-run.ts");
780
1050
  return {
781
1051
  content: [{ type: "text", text: await handleGetSimulationRun(params) }],
782
1052
  };
783
- })
1053
+ }),
784
1054
  );
1055
+ }
785
1056
 
786
- // --- Platform Evaluator Tools (require API key) ---
787
- // These tools manage evaluators on the LangWatch platform via API.
788
-
1057
+ // --- Platform Evaluator Tools (require API key) ---
1058
+ // These tools manage evaluators on the LangWatch platform via API.
1059
+ function registerEvaluatorTools(server: McpServer): void {
789
1060
  server.tool(
790
1061
  "platform_create_evaluator",
791
1062
  `Create an evaluator on the LangWatch platform. Useful for setting up LLM-as-judge and other evaluators to use in evaluation notebooks. Call discover_schema({ category: 'evaluators' }) first to see available evaluator types and their settings.`,
@@ -794,20 +1065,16 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
794
1065
  config: z
795
1066
  .record(z.string(), z.unknown())
796
1067
  .describe(
797
- 'Evaluator config object. Must include "evaluatorType" (e.g. "langevals/llm_boolean") and optional "settings" overrides.'
1068
+ 'Evaluator config object. Must include "evaluatorType" (e.g. "langevals/llm_boolean") and optional "settings" overrides.',
798
1069
  ),
799
1070
  },
800
1071
  withToolLogging("platform_create_evaluator", async (params) => {
801
1072
  requireApiKey();
802
- const { handleCreateEvaluator } = await import(
803
- "./tools/create-evaluator.js"
804
- );
1073
+ const { handleCreateEvaluator } = await import("./tools/create-evaluator.ts");
805
1074
  return {
806
- content: [
807
- { type: "text", text: await handleCreateEvaluator(params) },
808
- ],
1075
+ content: [{ type: "text", text: await handleCreateEvaluator(params) }],
809
1076
  };
810
- })
1077
+ }),
811
1078
  );
812
1079
 
813
1080
  server.tool(
@@ -816,32 +1083,26 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
816
1083
  {},
817
1084
  withToolLogging("platform_list_evaluators", async () => {
818
1085
  requireApiKey();
819
- const { handleListEvaluators } = await import(
820
- "./tools/list-evaluators.js"
821
- );
1086
+ const { handleListEvaluators } = await import("./tools/list-evaluators.ts");
822
1087
  return {
823
1088
  content: [{ type: "text", text: await handleListEvaluators() }],
824
1089
  };
825
- })
1090
+ }),
826
1091
  );
827
1092
 
828
1093
  server.tool(
829
1094
  "platform_get_evaluator",
830
1095
  "Get full details of an evaluator on the LangWatch platform by ID or slug, including config, input fields, and output fields.",
831
1096
  {
832
- idOrSlug: z
833
- .string()
834
- .describe("The evaluator ID or slug to retrieve"),
1097
+ idOrSlug: z.string().describe("The evaluator ID or slug to retrieve"),
835
1098
  },
836
1099
  withToolLogging("platform_get_evaluator", async (params) => {
837
1100
  requireApiKey();
838
- const { handleGetEvaluator } = await import(
839
- "./tools/get-evaluator.js"
840
- );
1101
+ const { handleGetEvaluator } = await import("./tools/get-evaluator.ts");
841
1102
  return {
842
1103
  content: [{ type: "text", text: await handleGetEvaluator(params) }],
843
1104
  };
844
- })
1105
+ }),
845
1106
  );
846
1107
 
847
1108
  server.tool(
@@ -853,21 +1114,15 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
853
1114
  config: z
854
1115
  .record(z.string(), z.unknown())
855
1116
  .optional()
856
- .describe(
857
- "Updated config settings. Note: evaluatorType cannot be changed after creation."
858
- ),
1117
+ .describe("Updated config settings. Note: evaluatorType cannot be changed after creation."),
859
1118
  },
860
1119
  withToolLogging("platform_update_evaluator", async (params) => {
861
1120
  requireApiKey();
862
- const { handleUpdateEvaluator } = await import(
863
- "./tools/update-evaluator.js"
864
- );
1121
+ const { handleUpdateEvaluator } = await import("./tools/update-evaluator.ts");
865
1122
  return {
866
- content: [
867
- { type: "text", text: await handleUpdateEvaluator(params) },
868
- ],
1123
+ content: [{ type: "text", text: await handleUpdateEvaluator(params) }],
869
1124
  };
870
- })
1125
+ }),
871
1126
  );
872
1127
 
873
1128
  server.tool(
@@ -878,48 +1133,40 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
878
1133
  },
879
1134
  withToolLogging("platform_delete_evaluator", async (params) => {
880
1135
  requireApiKey();
881
- const { handleDeleteEvaluator } = await import("./tools/delete-evaluator.js");
1136
+ const { handleDeleteEvaluator } = await import("./tools/delete-evaluator.ts");
882
1137
  return {
883
1138
  content: [{ type: "text", text: await handleDeleteEvaluator(params) }],
884
1139
  };
885
- })
1140
+ }),
886
1141
  );
1142
+ }
887
1143
 
888
- // --- Platform Model Provider Tools (require API key) ---
889
- // These tools manage model provider API keys on the LangWatch platform.
890
-
1144
+ // --- Platform Model Provider Tools (require API key) ---
1145
+ // These tools manage model provider API keys on the LangWatch platform.
1146
+ function registerModelProviderTools(server: McpServer): void {
891
1147
  server.tool(
892
1148
  "platform_set_model_provider",
893
1149
  `Set or update a model provider on the LangWatch platform. Use this to configure API keys (e.g. OPENAI_API_KEY) needed to run evaluators. The API key is stored securely and never returned in responses. Omit customKeys to update other settings without changing existing keys.`,
894
1150
  {
895
1151
  provider: z
896
1152
  .string()
897
- .describe(
898
- 'Provider name, e.g., "openai", "anthropic", "azure", "custom"'
899
- ),
1153
+ .describe('Provider name, e.g., "openai", "anthropic", "azure", "custom"'),
900
1154
  enabled: z.boolean().describe("Whether the provider is enabled"),
901
1155
  customKeys: z
902
1156
  .record(z.string(), z.unknown())
903
1157
  .optional()
904
1158
  .describe(
905
- 'API key configuration, e.g. { "OPENAI_API_KEY": "sk-..." }. Omit to keep existing keys.'
1159
+ 'API key configuration, e.g. { "OPENAI_API_KEY": "sk-..." }. Omit to keep existing keys.',
906
1160
  ),
907
- defaultModel: z
908
- .string()
909
- .optional()
910
- .describe("Set as project default model"),
1161
+ defaultModel: z.string().optional().describe("Set as project default model"),
911
1162
  },
912
1163
  withToolLogging("platform_set_model_provider", async (params) => {
913
1164
  requireApiKey();
914
- const { handleSetModelProvider } = await import(
915
- "./tools/set-model-provider.js"
916
- );
1165
+ const { handleSetModelProvider } = await import("./tools/set-model-provider.ts");
917
1166
  return {
918
- content: [
919
- { type: "text", text: await handleSetModelProvider(params) },
920
- ],
1167
+ content: [{ type: "text", text: await handleSetModelProvider(params) }],
921
1168
  };
922
- })
1169
+ }),
923
1170
  );
924
1171
 
925
1172
  server.tool(
@@ -928,48 +1175,47 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
928
1175
  {},
929
1176
  withToolLogging("platform_list_model_providers", async () => {
930
1177
  requireApiKey();
931
- const { handleListModelProviders } = await import(
932
- "./tools/list-model-providers.js"
933
- );
1178
+ const { handleListModelProviders } = await import("./tools/list-model-providers.ts");
934
1179
  return {
935
1180
  content: [{ type: "text", text: await handleListModelProviders() }],
936
1181
  };
937
- })
1182
+ }),
938
1183
  );
1184
+ }
939
1185
 
940
- // --- Platform Agent Tools (require API key) ---
941
-
1186
+ // --- Platform Agent Tools (require API key) ---
1187
+ function registerAgentTools(server: McpServer): void {
942
1188
  server.tool(
943
1189
  "platform_list_agents",
944
- "List all agents in the LangWatch project with their names, types, and IDs.",
1190
+ "List all agents in the LangWatch project with their names, types and IDs. A connected agent (one that registered itself from code with connectAgent or connect_agent) also shows its environment, whether it is online, how many instances are connected, and its owner.",
945
1191
  {},
946
1192
  withToolLogging("platform_list_agents", async () => {
947
1193
  requireApiKey();
948
- const { handleListAgents } = await import("./tools/list-agents.js");
1194
+ const { handleListAgents } = await import("./tools/list-agents.ts");
949
1195
  return {
950
1196
  content: [{ type: "text", text: await handleListAgents() }],
951
1197
  };
952
- })
1198
+ }),
953
1199
  );
954
1200
 
955
1201
  server.tool(
956
1202
  "platform_get_agent",
957
- "Get detailed information about a specific agent by its ID, including its configuration.",
1203
+ "Get detailed information about a specific agent by its ID, including its configuration. A connected agent also shows its environment, status, the run parameters it declared (with options and defaults) and the instances connected right now.",
958
1204
  {
959
1205
  id: z.string().describe("The agent ID"),
960
1206
  },
961
1207
  withToolLogging("platform_get_agent", async (params) => {
962
1208
  requireApiKey();
963
- const { handleGetAgent } = await import("./tools/get-agent.js");
1209
+ const { handleGetAgent } = await import("./tools/get-agent.ts");
964
1210
  return {
965
1211
  content: [{ type: "text", text: await handleGetAgent(params) }],
966
1212
  };
967
- })
1213
+ }),
968
1214
  );
969
1215
 
970
1216
  server.tool(
971
1217
  "platform_create_agent",
972
- "Create a new agent. Supported types: 'signature' (LLM prompt), 'code' (Python), 'workflow' (sub-workflow), 'http' (external API).",
1218
+ "Create a new agent. Supported types: 'signature' (LLM prompt), 'code' (Python), 'workflow' (sub-workflow), 'http' (external API). A 'connected' agent is not created here: it registers itself from code, with connectAgent from langwatch/agent in TypeScript or langwatch.connect_agent in Python, and appears in the list once that process runs.",
973
1219
  {
974
1220
  name: z.string().describe("Agent name"),
975
1221
  type: z.enum(["signature", "code", "workflow", "http"]).describe("Agent type"),
@@ -977,12 +1223,16 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
977
1223
  },
978
1224
  withToolLogging("platform_create_agent", async (params) => {
979
1225
  requireApiKey();
980
- const { handleCreateAgent } = await import("./tools/create-agent.js");
981
- const parsedConfig = params.config ? JSON.parse(params.config) as Record<string, unknown> : undefined;
1226
+ const { handleCreateAgent } = await import("./tools/create-agent.ts");
1227
+ const parsedConfig = params.config
1228
+ ? (JSON.parse(params.config) as Record<string, unknown>)
1229
+ : undefined;
982
1230
  return {
983
- content: [{ type: "text", text: await handleCreateAgent({ ...params, config: parsedConfig }) }],
1231
+ content: [
1232
+ { type: "text", text: await handleCreateAgent({ ...params, config: parsedConfig }) },
1233
+ ],
984
1234
  };
985
- })
1235
+ }),
986
1236
  );
987
1237
 
988
1238
  server.tool(
@@ -992,16 +1242,23 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
992
1242
  id: z.string().describe("The agent ID"),
993
1243
  name: z.string().optional().describe("New agent name"),
994
1244
  type: z.string().optional().describe("New agent type: signature, code, workflow, or http"),
995
- config: z.string().optional().describe("Updated configuration as JSON string (will be parsed)"),
1245
+ config: z
1246
+ .string()
1247
+ .optional()
1248
+ .describe("Updated configuration as JSON string (will be parsed)"),
996
1249
  },
997
1250
  withToolLogging("platform_update_agent", async (params) => {
998
1251
  requireApiKey();
999
- const { handleUpdateAgent } = await import("./tools/update-agent.js");
1000
- const parsedConfig = params.config ? JSON.parse(params.config) as Record<string, unknown> : undefined;
1252
+ const { handleUpdateAgent } = await import("./tools/update-agent.ts");
1253
+ const parsedConfig = params.config
1254
+ ? (JSON.parse(params.config) as Record<string, unknown>)
1255
+ : undefined;
1001
1256
  return {
1002
- content: [{ type: "text", text: await handleUpdateAgent({ ...params, config: parsedConfig }) }],
1257
+ content: [
1258
+ { type: "text", text: await handleUpdateAgent({ ...params, config: parsedConfig }) },
1259
+ ],
1003
1260
  };
1004
- })
1261
+ }),
1005
1262
  );
1006
1263
 
1007
1264
  server.tool(
@@ -1012,42 +1269,71 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
1012
1269
  },
1013
1270
  withToolLogging("platform_delete_agent", async (params) => {
1014
1271
  requireApiKey();
1015
- const { handleDeleteAgent } = await import("./tools/delete-agent.js");
1272
+ const { handleDeleteAgent } = await import("./tools/delete-agent.ts");
1016
1273
  return {
1017
1274
  content: [{ type: "text", text: await handleDeleteAgent(params) }],
1018
1275
  };
1019
- })
1276
+ }),
1020
1277
  );
1021
1278
 
1022
1279
  server.tool(
1023
1280
  "platform_run_agent",
1024
- "Execute an agent with JSON input. HTTP agents call their configured URL directly; workflow-linked agents execute via the workflow engine.",
1281
+ "Run one turn of an agent. A connected agent runs through the platform relay on a live instance: give `message` (one user turn) or `input` with a `messages` list, plus `parameters` and `threadId` to continue a conversation. HTTP agents call their configured URL directly; workflow-linked agents execute via the workflow engine.",
1025
1282
  {
1026
1283
  id: z.string().describe("The agent ID to run"),
1027
- input: z.string().optional().describe("Input data as a JSON object string"),
1284
+ input: z
1285
+ .string()
1286
+ .optional()
1287
+ .describe(
1288
+ "Input data as a JSON object string. For a connected agent it is the relay body: messages (OpenAI style), and optionally threadId, session and params.",
1289
+ ),
1290
+ message: z
1291
+ .string()
1292
+ .optional()
1293
+ .describe("One user message to send to a connected agent, instead of input"),
1294
+ parameters: z
1295
+ .record(z.string(), z.union([z.string(), z.number(), z.boolean()]))
1296
+ .optional()
1297
+ .describe("Run parameter values for a connected agent, by name"),
1298
+ threadId: z.string().optional().describe("Continue a conversation on a connected agent"),
1028
1299
  },
1029
1300
  withToolLogging("platform_run_agent", async (params) => {
1030
1301
  requireApiKey();
1031
- const { handleRunAgent } = await import("./tools/run-agent.js");
1302
+ const { handleRunAgent } = await import("./tools/run-agent.ts");
1032
1303
  return {
1033
1304
  content: [{ type: "text", text: await handleRunAgent(params) }],
1034
1305
  };
1035
- })
1306
+ }),
1036
1307
  );
1037
1308
 
1038
- // --- Platform Dashboard Tools (require API key) ---
1309
+ server.tool(
1310
+ "platform_test_agent",
1311
+ 'Test an agent with one scripted scenario run: the user sends "ping", the agent answers, and the run succeeds when the answer arrives. No model is used, and no scenario, run plan or test suite is added to the project. Answers at once with the scenario run id to follow with platform_get_simulation_run; the run itself is asynchronous.',
1312
+ {
1313
+ id: z.string().describe("The agent ID to test"),
1314
+ },
1315
+ withToolLogging("platform_test_agent", async (params) => {
1316
+ requireApiKey();
1317
+ return {
1318
+ content: [{ type: "text", text: await handleTestAgent(params) }],
1319
+ };
1320
+ }),
1321
+ );
1322
+ }
1039
1323
 
1324
+ // --- Platform Dashboard Tools (require API key) ---
1325
+ function registerDashboardTools(server: McpServer): void {
1040
1326
  server.tool(
1041
1327
  "platform_list_dashboards",
1042
1328
  "List all analytics dashboards in the LangWatch project.",
1043
1329
  {},
1044
1330
  withToolLogging("platform_list_dashboards", async () => {
1045
1331
  requireApiKey();
1046
- const { handleListDashboards } = await import("./tools/list-dashboards.js");
1332
+ const { handleListDashboards } = await import("./tools/list-dashboards.ts");
1047
1333
  return {
1048
1334
  content: [{ type: "text", text: await handleListDashboards() }],
1049
1335
  };
1050
- })
1336
+ }),
1051
1337
  );
1052
1338
 
1053
1339
  server.tool(
@@ -1058,11 +1344,11 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
1058
1344
  },
1059
1345
  withToolLogging("platform_get_dashboard", async (params) => {
1060
1346
  requireApiKey();
1061
- const { handleGetDashboard } = await import("./tools/get-dashboard.js");
1347
+ const { handleGetDashboard } = await import("./tools/get-dashboard.ts");
1062
1348
  return {
1063
1349
  content: [{ type: "text", text: await handleGetDashboard(params) }],
1064
1350
  };
1065
- })
1351
+ }),
1066
1352
  );
1067
1353
 
1068
1354
  server.tool(
@@ -1073,11 +1359,11 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
1073
1359
  },
1074
1360
  withToolLogging("platform_create_dashboard", async (params) => {
1075
1361
  requireApiKey();
1076
- const { handleCreateDashboard } = await import("./tools/create-dashboard.js");
1362
+ const { handleCreateDashboard } = await import("./tools/create-dashboard.ts");
1077
1363
  return {
1078
1364
  content: [{ type: "text", text: await handleCreateDashboard(params) }],
1079
1365
  };
1080
- })
1366
+ }),
1081
1367
  );
1082
1368
 
1083
1369
  server.tool(
@@ -1088,11 +1374,11 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
1088
1374
  },
1089
1375
  withToolLogging("platform_delete_dashboard", async (params) => {
1090
1376
  requireApiKey();
1091
- const { handleDeleteDashboard } = await import("./tools/delete-dashboard.js");
1377
+ const { handleDeleteDashboard } = await import("./tools/delete-dashboard.ts");
1092
1378
  return {
1093
1379
  content: [{ type: "text", text: await handleDeleteDashboard(params) }],
1094
1380
  };
1095
- })
1381
+ }),
1096
1382
  );
1097
1383
 
1098
1384
  server.tool(
@@ -1104,27 +1390,127 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
1104
1390
  },
1105
1391
  withToolLogging("platform_rename_dashboard", async (params) => {
1106
1392
  requireApiKey();
1107
- const { renameDashboard } = await import("./langwatch-api-dashboards.js");
1393
+ const { renameDashboard } = await import("./langwatch-api-dashboards.ts");
1108
1394
  const result = await renameDashboard(params.id, { name: params.name });
1109
1395
  return {
1110
- content: [{ type: "text", text: `Dashboard "${result.name}" renamed successfully (ID: ${result.id}).` }],
1396
+ content: [
1397
+ {
1398
+ type: "text",
1399
+ text: `Dashboard "${result.name}" renamed successfully (ID: ${result.id}).`,
1400
+ },
1401
+ ],
1402
+ };
1403
+ }),
1404
+ );
1405
+
1406
+ server.tool(
1407
+ "list_dashboard_boards",
1408
+ "Lists the project's boards in the Dashboards area, with each one's ID and widget count. Use an ID from here as the dashboardId of add_dashboard_widget and update_dashboard_widget; the analytics dashboards platform_list_dashboards lists hold no widgets.",
1409
+ {},
1410
+ withToolLogging("list_dashboard_boards", async () => {
1411
+ requireApiKey();
1412
+ const { handleListDashboardBoards } = await import("./tools/list-dashboard-boards.js");
1413
+ return {
1414
+ content: [{ type: "text", text: await handleListDashboardBoards() }],
1111
1415
  };
1112
- })
1416
+ }),
1113
1417
  );
1114
1418
 
1115
- // --- Platform Workflow Tools (require API key) ---
1419
+ server.tool(
1420
+ "add_dashboard_widget",
1421
+ "Creates a custom dashboard widget and places it on the given dashboard. A widget's `code` is a TSX file run in a sandbox: it receives a global `LW` with `LW.useChartQuery(name)` (reads one of the widget's own named queries), `LW.navigate` and `LW.theme`. `queries` are named LangWatchQL SQL statements the code can read by name — validate each statement's SQL first with run_query before calling this tool, since a query only runs inside the sandbox at render time and a mistake surfaces there, not here. Up to 8 queries per widget.",
1422
+ {
1423
+ dashboardId: z
1424
+ .string()
1425
+ .describe("The Dashboards board to add the widget to, as list_dashboard_boards lists it"),
1426
+ name: z.string().describe("The widget's display name"),
1427
+ code: z
1428
+ .string()
1429
+ .describe(
1430
+ "The widget's TSX source. Runs sandboxed with a global LW (LW.useChartQuery(name), LW.navigate, LW.theme) in scope.",
1431
+ ),
1432
+ queries: z
1433
+ .union([
1434
+ z.record(z.string(), z.string()).describe('{"<name>": "<LWQL SQL>"}'),
1435
+ z
1436
+ .array(
1437
+ z.object({
1438
+ name: z.string().describe('Referenced from code as LW.useChartQuery("name")'),
1439
+ sql: z.string().describe("The LangWatchQL statement"),
1440
+ }),
1441
+ )
1442
+ .describe("Named LWQL queries, array form"),
1443
+ ])
1444
+ .describe(
1445
+ "The widget's named LangWatchQL queries: either {name: sql} or an array of {name, sql}. Max 8. Validate each statement with run_query first.",
1446
+ ),
1447
+ },
1448
+ withToolLogging("add_dashboard_widget", async (params) => {
1449
+ requireApiKey();
1450
+ const { handleAddDashboardWidget } = await import("./tools/add-dashboard-widget.js");
1451
+ return {
1452
+ content: [{ type: "text", text: await handleAddDashboardWidget(params) }],
1453
+ };
1454
+ }),
1455
+ );
1456
+
1457
+ server.tool(
1458
+ "update_dashboard_widget",
1459
+ "Changes one custom widget on a dashboard: its name, its TSX `code`, its named LangWatchQL `queries` or its `description`. Pass only the fields to change; every field left out keeps its stored value. Validate any new query's SQL with run_query first. Returns the updated widget.",
1460
+ {
1461
+ dashboardId: z.string().describe("The dashboard the widget is on"),
1462
+ widgetId: z.string().describe("The widget to change, as add_dashboard_widget returned it"),
1463
+ name: z.string().optional().describe("The widget's new display name"),
1464
+ code: z
1465
+ .string()
1466
+ .optional()
1467
+ .describe(
1468
+ "The widget's new TSX source. Runs sandboxed with a global LW (LW.useChartQuery(name), LW.navigate, LW.theme) in scope.",
1469
+ ),
1470
+ queries: z
1471
+ .union([
1472
+ z.record(z.string(), z.string()).describe('{"<name>": "<LWQL SQL>"}'),
1473
+ z
1474
+ .array(
1475
+ z.object({
1476
+ name: z.string().describe('Referenced from code as LW.useChartQuery("name")'),
1477
+ sql: z.string().describe("The LangWatchQL statement"),
1478
+ }),
1479
+ )
1480
+ .describe("Named LWQL queries, array form"),
1481
+ ])
1482
+ .optional()
1483
+ .describe(
1484
+ "The widget's new named LangWatchQL queries, replacing the stored set: either {name: sql} or an array of {name, sql}. Max 8.",
1485
+ ),
1486
+ description: z
1487
+ .string()
1488
+ .optional()
1489
+ .describe("What the widget's info tip says: a sentence or two"),
1490
+ },
1491
+ withToolLogging("update_dashboard_widget", async (params) => {
1492
+ requireApiKey();
1493
+ const { handleUpdateDashboardWidget } = await import("./tools/update-dashboard-widget.js");
1494
+ return {
1495
+ content: [{ type: "text", text: await handleUpdateDashboardWidget(params) }],
1496
+ };
1497
+ }),
1498
+ );
1499
+ }
1116
1500
 
1501
+ // --- Platform Workflow Tools (require API key) ---
1502
+ function registerWorkflowTools(server: McpServer): void {
1117
1503
  server.tool(
1118
1504
  "platform_list_workflows",
1119
1505
  "List all workflows in the LangWatch project.",
1120
1506
  {},
1121
1507
  withToolLogging("platform_list_workflows", async () => {
1122
1508
  requireApiKey();
1123
- const { handleListWorkflows } = await import("./tools/list-workflows.js");
1509
+ const { handleListWorkflows } = await import("./tools/list-workflows.ts");
1124
1510
  return {
1125
1511
  content: [{ type: "text", text: await handleListWorkflows() }],
1126
1512
  };
1127
- })
1513
+ }),
1128
1514
  );
1129
1515
 
1130
1516
  server.tool(
@@ -1135,11 +1521,11 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
1135
1521
  },
1136
1522
  withToolLogging("platform_get_workflow", async (params) => {
1137
1523
  requireApiKey();
1138
- const { handleGetWorkflow } = await import("./tools/get-workflow.js");
1524
+ const { handleGetWorkflow } = await import("./tools/get-workflow.ts");
1139
1525
  return {
1140
1526
  content: [{ type: "text", text: await handleGetWorkflow(params) }],
1141
1527
  };
1142
- })
1528
+ }),
1143
1529
  );
1144
1530
 
1145
1531
  server.tool(
@@ -1150,11 +1536,11 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
1150
1536
  },
1151
1537
  withToolLogging("platform_delete_workflow", async (params) => {
1152
1538
  requireApiKey();
1153
- const { handleDeleteWorkflow } = await import("./tools/delete-workflow.js");
1539
+ const { handleDeleteWorkflow } = await import("./tools/delete-workflow.ts");
1154
1540
  return {
1155
1541
  content: [{ type: "text", text: await handleDeleteWorkflow(params) }],
1156
1542
  };
1157
- })
1543
+ }),
1158
1544
  );
1159
1545
 
1160
1546
  server.tool(
@@ -1166,15 +1552,16 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
1166
1552
  },
1167
1553
  withToolLogging("platform_run_workflow", async (params) => {
1168
1554
  requireApiKey();
1169
- const { handleRunWorkflow } = await import("./tools/run-workflow.js");
1555
+ const { handleRunWorkflow } = await import("./tools/run-workflow.ts");
1170
1556
  return {
1171
1557
  content: [{ type: "text", text: await handleRunWorkflow(params) }],
1172
1558
  };
1173
- })
1559
+ }),
1174
1560
  );
1561
+ }
1175
1562
 
1176
- // --- Platform Annotation Tools (require API key) ---
1177
-
1563
+ // --- Platform Annotation Tools (require API key) ---
1564
+ function registerAnnotationTools(server: McpServer): void {
1178
1565
  server.tool(
1179
1566
  "platform_list_annotations",
1180
1567
  "List all annotations for the project, optionally filtered by trace ID.",
@@ -1183,11 +1570,11 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
1183
1570
  },
1184
1571
  withToolLogging("platform_list_annotations", async (params) => {
1185
1572
  requireApiKey();
1186
- const { handleListAnnotations } = await import("./tools/list-annotations.js");
1573
+ const { handleListAnnotations } = await import("./tools/list-annotations.ts");
1187
1574
  return {
1188
1575
  content: [{ type: "text", text: await handleListAnnotations(params) }],
1189
1576
  };
1190
- })
1577
+ }),
1191
1578
  );
1192
1579
 
1193
1580
  server.tool(
@@ -1201,11 +1588,11 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
1201
1588
  },
1202
1589
  withToolLogging("platform_create_annotation", async (params) => {
1203
1590
  requireApiKey();
1204
- const { handleCreateAnnotation } = await import("./tools/create-annotation.js");
1591
+ const { handleCreateAnnotation } = await import("./tools/create-annotation.ts");
1205
1592
  return {
1206
1593
  content: [{ type: "text", text: await handleCreateAnnotation(params) }],
1207
1594
  };
1208
- })
1595
+ }),
1209
1596
  );
1210
1597
 
1211
1598
  server.tool(
@@ -1216,11 +1603,11 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
1216
1603
  },
1217
1604
  withToolLogging("platform_delete_annotation", async (params) => {
1218
1605
  requireApiKey();
1219
- const { handleDeleteAnnotation } = await import("./tools/delete-annotation.js");
1606
+ const { handleDeleteAnnotation } = await import("./tools/delete-annotation.ts");
1220
1607
  return {
1221
1608
  content: [{ type: "text", text: await handleDeleteAnnotation(params) }],
1222
1609
  };
1223
- })
1610
+ }),
1224
1611
  );
1225
1612
 
1226
1613
  server.tool(
@@ -1231,7 +1618,7 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
1231
1618
  },
1232
1619
  withToolLogging("platform_get_annotation", async (params) => {
1233
1620
  requireApiKey();
1234
- const { getAnnotation } = await import("./langwatch-api-annotations.js");
1621
+ const { getAnnotation } = await import("./langwatch-api-annotations.ts");
1235
1622
  const annotation = await getAnnotation(params.id);
1236
1623
  const lines = [
1237
1624
  `**ID**: ${annotation.id}`,
@@ -1243,11 +1630,12 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
1243
1630
  return {
1244
1631
  content: [{ type: "text", text: lines.join("\n") }],
1245
1632
  };
1246
- })
1633
+ }),
1247
1634
  );
1635
+ }
1248
1636
 
1249
- // --- Platform Trigger/Automation Tools (require API key) ---
1250
-
1637
+ // --- Platform Trigger/Automation Tools (require API key) ---
1638
+ function registerTriggerTools(server: McpServer): void {
1251
1639
  server.tool(
1252
1640
  "platform_list_triggers",
1253
1641
  "List all triggers (automations) in the project. Triggers automate actions like sending emails, Slack messages, or adding to datasets when conditions are met.",
@@ -1256,72 +1644,155 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
1256
1644
  },
1257
1645
  withToolLogging("platform_list_triggers", async (params) => {
1258
1646
  requireApiKey();
1259
- const { listTriggers } = await import("./langwatch-api-triggers.js");
1260
- const triggers = await listTriggers();
1261
- if (params.format === "json") {
1262
- return { content: [{ type: "text", text: JSON.stringify(triggers, null, 2) }] };
1263
- }
1264
- if (triggers.length === 0) {
1265
- return { content: [{ type: "text", text: "No triggers found. Use `platform_create_trigger` to create one." }] };
1266
- }
1267
- const lines = [`# Triggers (${triggers.length} total)\n`];
1268
- for (const t of triggers) {
1269
- lines.push(`## ${t.name}`);
1270
- lines.push(`**ID**: ${t.id}`);
1271
- lines.push(`**Action**: ${t.action}`);
1272
- lines.push(`**Status**: ${t.active ? "active" : "inactive"}`);
1273
- if (t.alertType) lines.push(`**Alert**: ${t.alertType}`);
1274
- lines.push("");
1275
- }
1276
- return { content: [{ type: "text", text: lines.join("\n") }] };
1277
- })
1647
+ const { handleListTriggers } = await import("./tools/list-triggers.ts");
1648
+ return { content: [{ type: "text", text: await handleListTriggers(params) }] };
1649
+ }),
1650
+ );
1651
+
1652
+ server.tool(
1653
+ "platform_get_trigger",
1654
+ "Read one trigger (automation) by its ID, including its delivery configuration. Credential values come back as [redacted]; sending them back unchanged on an update keeps the stored values.",
1655
+ {
1656
+ id: z.string().describe("The trigger ID"),
1657
+ },
1658
+ withToolLogging("platform_get_trigger", async (params) => {
1659
+ requireApiKey();
1660
+ const { getTrigger } = await import("./langwatch-api-triggers.ts");
1661
+ const trigger = await getTrigger(params.id);
1662
+ return { content: [{ type: "text", text: JSON.stringify(trigger, null, 2) }] };
1663
+ }),
1278
1664
  );
1279
1665
 
1280
1666
  server.tool(
1281
1667
  "platform_create_trigger",
1282
- "Create a new trigger (automation) that fires when conditions are met.",
1668
+ [
1669
+ "Create a trigger (automation).",
1670
+ "",
1671
+ "It is about one of three things: matching traces (send `filters` or `filterQuery`), a metric crossing a threshold on a graph (an Alert: send `customGraphId`, `graphAlert` and `alertType`), or a schedule (a Report: send `report`, on an email or Slack channel).",
1672
+ "`actionParams` is the delivery configuration for the channel named in `action` — recipients for SEND_EMAIL, a destination for SEND_SLACK_MESSAGE, a URL for SEND_WEBHOOK, a dataset and mapping for ADD_TO_DATASET, annotators for ADD_TO_ANNOTATION_QUEUE. Omitted, it is sent as {} and the server refuses a channel that cannot deliver without one.",
1673
+ SLACK_DELIVERY_NOTE,
1674
+ `filters: ${TRIGGER_FILTERS_DESCRIPTION}`,
1675
+ "The delivery channel cannot be changed afterwards.",
1676
+ ].join("\n"),
1283
1677
  {
1284
1678
  name: z.string().describe("Trigger name"),
1285
- action: z.enum(["SEND_EMAIL", "ADD_TO_DATASET", "ADD_TO_ANNOTATION_QUEUE", "SEND_SLACK_MESSAGE"]).describe("Action to take when triggered"),
1286
- filters: z.string().optional().describe("Filter conditions as JSON string"),
1679
+ action: triggerActionSchema.describe("Which channel it delivers on"),
1680
+ actionParams: actionParamsSchema
1681
+ .optional()
1682
+ .describe("The delivery configuration the channel named in `action` reads. Defaults to {}"),
1683
+ filters: z.string().optional().describe(TRIGGER_FILTERS_DESCRIPTION),
1684
+ filterQuery: z.string().optional().describe(TRIGGER_FILTER_QUERY_DESCRIPTION),
1685
+ customGraphId: z.string().optional().describe("Set to make this an alert on that graph"),
1686
+ graphAlert: graphAlertSchema.optional(),
1687
+ report: reportSchema.optional().describe("Set to make this a scheduled report"),
1688
+ templates: templatesSchema.optional(),
1689
+ notificationCadence: notificationCadenceSchema.optional(),
1287
1690
  message: z.string().optional().describe("Custom alert message"),
1288
- alertType: z.enum(["CRITICAL", "WARNING", "INFO"]).optional().describe("Alert severity"),
1691
+ alertType: alertTypeSchema.optional().describe("Alert severity. Required for a graph alert."),
1289
1692
  },
1290
1693
  withToolLogging("platform_create_trigger", async (params) => {
1291
1694
  requireApiKey();
1292
- const { createTrigger } = await import("./langwatch-api-triggers.js");
1293
- let filters: Record<string, unknown> = {};
1294
- if (params.filters) {
1295
- try { filters = JSON.parse(params.filters) as Record<string, unknown>; }
1296
- catch { return { content: [{ type: "text", text: "Error: filters must be valid JSON" }] }; }
1297
- }
1298
- const trigger = await createTrigger({
1299
- name: params.name,
1300
- action: params.action,
1301
- filters,
1302
- message: params.message,
1303
- alertType: params.alertType,
1304
- });
1305
- return { content: [{ type: "text", text: `Trigger "${trigger.name}" created (ID: ${trigger.id}, Action: ${trigger.action}).` }] };
1306
- })
1695
+ const { handleCreateTrigger } = await import("./tools/create-trigger.ts");
1696
+ return handleCreateTrigger(params);
1697
+ }),
1307
1698
  );
1308
1699
 
1309
1700
  server.tool(
1310
1701
  "platform_update_trigger",
1311
- "Update a trigger (name, active state, message, alert type).",
1702
+ [
1703
+ "Update a trigger (automation). Anything left out is left alone.",
1704
+ "",
1705
+ "`actionParams` replaces the delivery configuration as a whole, so send every field it should have from now on. A credential the read hid comes back as [redacted]; send that back to keep the stored value. Changing a webhook's `url` means sending its header values in the same call.",
1706
+ "The delivery channel, the kind of automation and an alert's graph cannot be changed.",
1707
+ ].join("\n"),
1312
1708
  {
1313
1709
  id: z.string().describe("The trigger ID"),
1314
1710
  name: z.string().optional().describe("New name"),
1315
- active: z.boolean().optional().describe("Enable or disable"),
1316
- message: z.string().optional().describe("New alert message"),
1317
- alertType: z.enum(["CRITICAL", "WARNING", "INFO"]).optional().describe("New alert severity"),
1711
+ active: z.boolean().optional().describe("Resume or pause it"),
1712
+ actionParams: actionParamsSchema
1713
+ .optional()
1714
+ .describe("The delivery configuration this automation should have from now on"),
1715
+ filters: z.string().optional().describe(TRIGGER_FILTERS_DESCRIPTION),
1716
+ filterQuery: z
1717
+ .string()
1718
+ .nullable()
1719
+ .optional()
1720
+ .describe(`${TRIGGER_FILTER_QUERY_DESCRIPTION} null clears the saved query.`),
1721
+ graphAlert: graphAlertSchema
1722
+ .optional()
1723
+ .describe("Only for an automation that is already a graph alert"),
1724
+ report: reportSchema
1725
+ .optional()
1726
+ .describe("Only for an automation that is already a scheduled report"),
1727
+ templates: templatesSchema.optional(),
1728
+ notificationCadence: notificationCadenceSchema.optional(),
1729
+ message: z
1730
+ .string()
1731
+ .nullable()
1732
+ .optional()
1733
+ .describe("New alert message. null removes the custom message"),
1734
+ alertType: alertTypeSchema
1735
+ .nullable()
1736
+ .optional()
1737
+ .describe("New alert severity. null clears it"),
1318
1738
  },
1319
1739
  withToolLogging("platform_update_trigger", async (params) => {
1320
1740
  requireApiKey();
1321
- const { updateTrigger } = await import("./langwatch-api-triggers.js");
1322
- const trigger = await updateTrigger(params);
1323
- return { content: [{ type: "text", text: `Trigger "${trigger.name}" updated (active: ${trigger.active}).` }] };
1324
- })
1741
+ const { getTrigger, updateTrigger } = await import("./langwatch-api-triggers.ts");
1742
+ if (params.actionParams !== undefined) {
1743
+ // The channel cannot change on update, so the SAVED action decides
1744
+ // which shape the replacement configuration must fit.
1745
+ const saved = await getTrigger(params.id);
1746
+ const boundParams = validateActionParamsForAction({
1747
+ action: saved.action,
1748
+ actionParams: params.actionParams,
1749
+ });
1750
+ if (!boundParams.ok) {
1751
+ const { toolError } = await import("./tools/create-trigger.ts");
1752
+ return toolError(boundParams.message);
1753
+ }
1754
+ }
1755
+ const { parseJsonObject, toolError } = await import("./tools/create-trigger.ts");
1756
+ const filters = params.filters ? parseJsonObject(params.filters) : undefined;
1757
+ if (params.filters && !filters) {
1758
+ return toolError("filters must be a JSON object");
1759
+ }
1760
+ const trigger = await updateTrigger({ ...params, filters });
1761
+ return {
1762
+ content: [
1763
+ { type: "text", text: `Trigger "${trigger.name}" updated (active: ${trigger.active}).` },
1764
+ ],
1765
+ };
1766
+ }),
1767
+ );
1768
+
1769
+ server.tool(
1770
+ "platform_test_fire_trigger",
1771
+ "Send a trigger's message to the destination it is configured with, to confirm it arrives. Nothing is recorded as a fire.",
1772
+ {
1773
+ id: z.string().describe("The trigger ID"),
1774
+ },
1775
+ withToolLogging("platform_test_fire_trigger", async (params) => {
1776
+ requireApiKey();
1777
+ const { testFireTrigger } = await import("./langwatch-api-triggers.ts");
1778
+ const result = await testFireTrigger(params.id);
1779
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1780
+ }),
1781
+ );
1782
+
1783
+ server.tool(
1784
+ "platform_list_trigger_fires",
1785
+ "What an automation has done, newest first. Metadata only — no message content. Answers with `nextCursor`; pass it back as `cursor` for the next page (null means there is none).",
1786
+ {
1787
+ id: z.string().describe("The trigger ID"),
1788
+ limit: z.number().int().positive().optional().describe("How many fires to return"),
1789
+ cursor: z.string().optional().describe("The `nextCursor` a previous call answered with"),
1790
+ },
1791
+ withToolLogging("platform_list_trigger_fires", async (params) => {
1792
+ requireApiKey();
1793
+ const { handleListTriggerFires } = await import("./tools/list-trigger-fires.ts");
1794
+ return { content: [{ type: "text", text: await handleListTriggerFires(params) }] };
1795
+ }),
1325
1796
  );
1326
1797
 
1327
1798
  server.tool(
@@ -1332,28 +1803,29 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
1332
1803
  },
1333
1804
  withToolLogging("platform_delete_trigger", async (params) => {
1334
1805
  requireApiKey();
1335
- const { deleteTrigger } = await import("./langwatch-api-triggers.js");
1806
+ const { deleteTrigger } = await import("./langwatch-api-triggers.ts");
1336
1807
  const result = await deleteTrigger(params.id);
1337
1808
  return { content: [{ type: "text", text: `Trigger ${result.id} deleted.` }] };
1338
- })
1809
+ }),
1339
1810
  );
1811
+ }
1340
1812
 
1341
- // --- Platform Monitor Tools (require API key) ---
1342
-
1813
+ // --- Platform Monitor Tools (require API key) ---
1814
+ function registerMonitorTools(server: McpServer): void {
1343
1815
  server.tool(
1344
1816
  "platform_list_monitors",
1345
1817
  "List all online evaluation monitors for the project.",
1346
1818
  {},
1347
1819
  withToolLogging("platform_list_monitors", async () => {
1348
1820
  requireApiKey();
1349
- const { listMonitors } = await import("./langwatch-api-monitors.js");
1821
+ const { listMonitors } = await import("./langwatch-api-monitors.ts");
1350
1822
  const monitors = await listMonitors();
1351
1823
  if (monitors.length === 0) {
1352
1824
  return { content: [{ type: "text", text: "No monitors found." }] };
1353
1825
  }
1354
1826
  const lines = monitors.map(
1355
1827
  (m) =>
1356
- `• ${m.name} (id: ${m.id}, type: ${m.checkType}, ${m.enabled ? "enabled" : "disabled"}, mode: ${m.executionMode}, sample: ${Math.round(m.sample * 100)}%)`
1828
+ `• ${m.name} (id: ${m.id}, type: ${m.checkType}, ${m.enabled ? "enabled" : "disabled"}, mode: ${m.executionMode}, sample: ${Math.round(m.sample * 100)}%)`,
1357
1829
  );
1358
1830
  return {
1359
1831
  content: [
@@ -1363,7 +1835,7 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
1363
1835
  },
1364
1836
  ],
1365
1837
  };
1366
- })
1838
+ }),
1367
1839
  );
1368
1840
 
1369
1841
  server.tool(
@@ -1374,7 +1846,7 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
1374
1846
  },
1375
1847
  withToolLogging("platform_get_monitor", async (params) => {
1376
1848
  requireApiKey();
1377
- const { getMonitor } = await import("./langwatch-api-monitors.js");
1849
+ const { getMonitor } = await import("./langwatch-api-monitors.ts");
1378
1850
  const m = await getMonitor(params.id);
1379
1851
  const lines = [
1380
1852
  `**${m.name}** (${m.id})`,
@@ -1387,7 +1859,7 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
1387
1859
  `- Created: ${m.createdAt}`,
1388
1860
  ].filter(Boolean);
1389
1861
  return { content: [{ type: "text", text: lines.join("\n") }] };
1390
- })
1862
+ }),
1391
1863
  );
1392
1864
 
1393
1865
  server.tool(
@@ -1395,27 +1867,17 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
1395
1867
  "Create a new online evaluation monitor that runs an evaluator on incoming traces.",
1396
1868
  {
1397
1869
  name: z.string().describe("Monitor name"),
1398
- checkType: z
1399
- .string()
1400
- .describe("Evaluator check type (e.g. ragas/toxicity, custom/my-eval)"),
1870
+ checkType: z.string().describe("Evaluator check type (e.g. ragas/toxicity, custom/my-eval)"),
1401
1871
  executionMode: z
1402
1872
  .enum(["ON_MESSAGE", "AS_GUARDRAIL", "MANUALLY"])
1403
1873
  .optional()
1404
1874
  .describe("When to run: ON_MESSAGE (default), AS_GUARDRAIL, or MANUALLY"),
1405
- sample: z
1406
- .number()
1407
- .min(0)
1408
- .max(1)
1409
- .optional()
1410
- .describe("Sampling rate 0.0-1.0 (default: 1.0)"),
1411
- evaluatorId: z
1412
- .string()
1413
- .optional()
1414
- .describe("Link to a saved evaluator by ID"),
1875
+ sample: z.number().min(0).max(1).optional().describe("Sampling rate 0.0-1.0 (default: 1.0)"),
1876
+ evaluatorId: z.string().optional().describe("Link to a saved evaluator by ID"),
1415
1877
  },
1416
1878
  withToolLogging("platform_create_monitor", async (params) => {
1417
1879
  requireApiKey();
1418
- const { createMonitor } = await import("./langwatch-api-monitors.js");
1880
+ const { createMonitor } = await import("./langwatch-api-monitors.ts");
1419
1881
  const monitor = await createMonitor(params);
1420
1882
  return {
1421
1883
  content: [
@@ -1425,7 +1887,7 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
1425
1887
  },
1426
1888
  ],
1427
1889
  };
1428
- })
1890
+ }),
1429
1891
  );
1430
1892
 
1431
1893
  server.tool(
@@ -1443,7 +1905,7 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
1443
1905
  },
1444
1906
  withToolLogging("platform_update_monitor", async (params) => {
1445
1907
  requireApiKey();
1446
- const { updateMonitor } = await import("./langwatch-api-monitors.js");
1908
+ const { updateMonitor } = await import("./langwatch-api-monitors.ts");
1447
1909
  const monitor = await updateMonitor(params);
1448
1910
  return {
1449
1911
  content: [
@@ -1453,7 +1915,7 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
1453
1915
  },
1454
1916
  ],
1455
1917
  };
1456
- })
1918
+ }),
1457
1919
  );
1458
1920
 
1459
1921
  server.tool(
@@ -1464,32 +1926,29 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
1464
1926
  },
1465
1927
  withToolLogging("platform_delete_monitor", async (params) => {
1466
1928
  requireApiKey();
1467
- const { deleteMonitor } = await import("./langwatch-api-monitors.js");
1929
+ const { deleteMonitor } = await import("./langwatch-api-monitors.ts");
1468
1930
  const result = await deleteMonitor(params.id);
1469
1931
  return {
1470
- content: [
1471
- { type: "text", text: `Monitor ${result.id} deleted.` },
1472
- ],
1932
+ content: [{ type: "text", text: `Monitor ${result.id} deleted.` }],
1473
1933
  };
1474
- })
1934
+ }),
1475
1935
  );
1936
+ }
1476
1937
 
1477
- // --- Platform Secret Tools (require API key) ---
1478
-
1938
+ // --- Platform Secret Tools (require API key) ---
1939
+ function registerSecretTools(server: McpServer): void {
1479
1940
  server.tool(
1480
1941
  "platform_list_secrets",
1481
1942
  "List all project secrets (values are never returned, only metadata).",
1482
1943
  {},
1483
1944
  withToolLogging("platform_list_secrets", async () => {
1484
1945
  requireApiKey();
1485
- const { listSecrets } = await import("./langwatch-api-secrets.js");
1946
+ const { listSecrets } = await import("./langwatch-api-secrets.ts");
1486
1947
  const secrets = await listSecrets();
1487
1948
  if (secrets.length === 0) {
1488
1949
  return { content: [{ type: "text", text: "No secrets found." }] };
1489
1950
  }
1490
- const lines = secrets.map(
1491
- (s) => `• ${s.name} (id: ${s.id}, updated: ${s.updatedAt})`
1492
- );
1951
+ const lines = secrets.map((s) => `• ${s.name} (id: ${s.id}, updated: ${s.updatedAt})`);
1493
1952
  return {
1494
1953
  content: [
1495
1954
  {
@@ -1498,7 +1957,7 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
1498
1957
  },
1499
1958
  ],
1500
1959
  };
1501
- })
1960
+ }),
1502
1961
  );
1503
1962
 
1504
1963
  server.tool(
@@ -1510,7 +1969,7 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
1510
1969
  },
1511
1970
  withToolLogging("platform_create_secret", async (params) => {
1512
1971
  requireApiKey();
1513
- const { createSecret } = await import("./langwatch-api-secrets.js");
1972
+ const { createSecret } = await import("./langwatch-api-secrets.ts");
1514
1973
  const secret = await createSecret({ name: params.name, value: params.value });
1515
1974
  return {
1516
1975
  content: [
@@ -1520,7 +1979,7 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
1520
1979
  },
1521
1980
  ],
1522
1981
  };
1523
- })
1982
+ }),
1524
1983
  );
1525
1984
 
1526
1985
  server.tool(
@@ -1532,7 +1991,7 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
1532
1991
  },
1533
1992
  withToolLogging("platform_update_secret", async (params) => {
1534
1993
  requireApiKey();
1535
- const { updateSecret } = await import("./langwatch-api-secrets.js");
1994
+ const { updateSecret } = await import("./langwatch-api-secrets.ts");
1536
1995
  const secret = await updateSecret({ id: params.id, value: params.value });
1537
1996
  return {
1538
1997
  content: [
@@ -1542,7 +2001,7 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
1542
2001
  },
1543
2002
  ],
1544
2003
  };
1545
- })
2004
+ }),
1546
2005
  );
1547
2006
 
1548
2007
  server.tool(
@@ -1553,14 +2012,15 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
1553
2012
  },
1554
2013
  withToolLogging("platform_delete_secret", async (params) => {
1555
2014
  requireApiKey();
1556
- const { deleteSecret } = await import("./langwatch-api-secrets.js");
2015
+ const { deleteSecret } = await import("./langwatch-api-secrets.ts");
1557
2016
  const result = await deleteSecret(params.id);
1558
2017
  return { content: [{ type: "text", text: `Secret ${result.id} deleted.` }] };
1559
- })
2018
+ }),
1560
2019
  );
2020
+ }
1561
2021
 
1562
- // --- Platform Experiment Execution Tools (require API key) ---
1563
-
2022
+ // --- Platform Experiment Execution Tools (require API key) ---
2023
+ function registerExperimentTools(server: McpServer): void {
1564
2024
  server.tool(
1565
2025
  "platform_run_experiment",
1566
2026
  "Start an experiment run by slug. Returns a run ID for polling status.",
@@ -1572,7 +2032,7 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
1572
2032
  return {
1573
2033
  content: [{ type: "text", text: await handleRunExperiment(params) }],
1574
2034
  };
1575
- })
2035
+ }),
1576
2036
  );
1577
2037
 
1578
2038
  server.tool(
@@ -1592,7 +2052,7 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
1592
2052
  return {
1593
2053
  content: [{ type: "text", text: await handleExperimentStatus(params) }],
1594
2054
  };
1595
- })
2055
+ }),
1596
2056
  );
1597
2057
 
1598
2058
  server.tool(
@@ -1605,16 +2065,14 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
1605
2065
  .positive()
1606
2066
  .max(100)
1607
2067
  .optional()
1608
- .describe(
1609
- "Maximum number of experiments to include (default 25, hard-capped at 100)",
1610
- ),
2068
+ .describe("Maximum number of experiments to include (default 25, hard-capped at 100)"),
1611
2069
  },
1612
2070
  withToolLogging("platform_experiment_list", async (params) => {
1613
2071
  requireApiKey();
1614
2072
  return {
1615
2073
  content: [{ type: "text", text: await handleExperimentList(params) }],
1616
2074
  };
1617
- })
2075
+ }),
1618
2076
  );
1619
2077
 
1620
2078
  server.tool(
@@ -1623,34 +2081,28 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
1623
2081
  {
1624
2082
  experimentSlug: z
1625
2083
  .string()
1626
- .describe(
1627
- "Experiment slug from platform_experiment_list (e.g. 'checkout-flow')",
1628
- ),
2084
+ .describe("Experiment slug from platform_experiment_list (e.g. 'checkout-flow')"),
1629
2085
  limit: z
1630
2086
  .number()
1631
2087
  .int()
1632
2088
  .positive()
1633
2089
  .max(100)
1634
2090
  .optional()
1635
- .describe(
1636
- "Maximum number of runs to include (default 25, hard-capped at 100)",
1637
- ),
2091
+ .describe("Maximum number of runs to include (default 25, hard-capped at 100)"),
1638
2092
  },
1639
2093
  withToolLogging("platform_experiment_list_runs", async (params) => {
1640
2094
  requireApiKey();
1641
2095
  return {
1642
2096
  content: [{ type: "text", text: await handleExperimentListRuns(params) }],
1643
2097
  };
1644
- })
2098
+ }),
1645
2099
  );
1646
2100
 
1647
2101
  server.tool(
1648
2102
  "platform_experiment_results",
1649
2103
  "Fetch per-row results for an experiment run so you can debug evaluator scores and missed rows. Serves partial results for runs that are still running or were interrupted (it does not require a completed run), with the run status noted in the output. Returns a markdown report: per-evaluator averages plus row-by-row scores and failure details. Output is capped at 50 rows to protect the agent's context window — narrow with `filter: 'failed'` or `evaluator` to see what matters.",
1650
2104
  {
1651
- runId: z
1652
- .string()
1653
- .describe("The run ID returned from platform_run_experiment"),
2105
+ runId: z.string().describe("The run ID returned from platform_run_experiment"),
1654
2106
  experimentSlug: z
1655
2107
  .string()
1656
2108
  .optional()
@@ -1680,16 +2132,15 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
1680
2132
  withToolLogging("platform_experiment_results", async (params) => {
1681
2133
  requireApiKey();
1682
2134
  return {
1683
- content: [
1684
- { type: "text", text: await handleExperimentResults(params) },
1685
- ],
2135
+ content: [{ type: "text", text: await handleExperimentResults(params) }],
1686
2136
  };
1687
- })
2137
+ }),
1688
2138
  );
2139
+ }
1689
2140
 
1690
- // --- Platform Dataset Tools (require API key) ---
1691
- // These tools manage datasets on the LangWatch platform via API.
1692
-
2141
+ // --- Platform Dataset Tools (require API key) ---
2142
+ // These tools manage datasets on the LangWatch platform via API.
2143
+ function registerDatasetTools(server: McpServer): void {
1693
2144
  server.tool(
1694
2145
  "platform_list_datasets",
1695
2146
  "List all datasets on the LangWatch platform with their names, slugs, columns, and record counts. Returns AI-readable digest by default.",
@@ -1697,20 +2148,16 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
1697
2148
  format: z
1698
2149
  .enum(["digest", "json"])
1699
2150
  .optional()
1700
- .describe(
1701
- "Output format: 'digest' (default, AI-readable) or 'json' (full raw data)"
1702
- ),
2151
+ .describe("Output format: 'digest' (default, AI-readable) or 'json' (full raw data)"),
1703
2152
  },
1704
2153
  async (params) => {
1705
- const { requireApiKey } = await import("./config.js");
2154
+ const { requireApiKey } = await import("./config.ts");
1706
2155
  requireApiKey();
1707
- const { handleListDatasets } = await import(
1708
- "./tools/list-datasets.js"
1709
- );
2156
+ const { handleListDatasets } = await import("./tools/list-datasets.ts");
1710
2157
  return {
1711
2158
  content: [{ type: "text", text: await handleListDatasets(params) }],
1712
2159
  };
1713
- }
2160
+ },
1714
2161
  );
1715
2162
 
1716
2163
  server.tool(
@@ -1721,18 +2168,16 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
1721
2168
  format: z
1722
2169
  .enum(["digest", "json"])
1723
2170
  .optional()
1724
- .describe(
1725
- "Output format: 'digest' (default, AI-readable) or 'json' (full raw data)"
1726
- ),
2171
+ .describe("Output format: 'digest' (default, AI-readable) or 'json' (full raw data)"),
1727
2172
  },
1728
2173
  async (params) => {
1729
- const { requireApiKey } = await import("./config.js");
2174
+ const { requireApiKey } = await import("./config.ts");
1730
2175
  requireApiKey();
1731
- const { handleGetDataset } = await import("./tools/get-dataset.js");
2176
+ const { handleGetDataset } = await import("./tools/get-dataset.ts");
1732
2177
  return {
1733
2178
  content: [{ type: "text", text: await handleGetDataset(params) }],
1734
2179
  };
1735
- }
2180
+ },
1736
2181
  );
1737
2182
 
1738
2183
  server.tool(
@@ -1740,15 +2185,13 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
1740
2185
  "Create a new dataset on the LangWatch platform.",
1741
2186
  createDatasetSchema.shape,
1742
2187
  async (params) => {
1743
- const { requireApiKey } = await import("./config.js");
2188
+ const { requireApiKey } = await import("./config.ts");
1744
2189
  requireApiKey();
1745
- const { handleCreateDataset } = await import(
1746
- "./tools/create-dataset.js"
1747
- );
2190
+ const { handleCreateDataset } = await import("./tools/create-dataset.ts");
1748
2191
  return {
1749
2192
  content: [{ type: "text", text: await handleCreateDataset(params) }],
1750
2193
  };
1751
- }
2194
+ },
1752
2195
  );
1753
2196
 
1754
2197
  server.tool(
@@ -1763,15 +2206,13 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
1763
2206
  .describe("Updated column definitions"),
1764
2207
  },
1765
2208
  async (params) => {
1766
- const { requireApiKey } = await import("./config.js");
2209
+ const { requireApiKey } = await import("./config.ts");
1767
2210
  requireApiKey();
1768
- const { handleUpdateDataset } = await import(
1769
- "./tools/update-dataset.js"
1770
- );
2211
+ const { handleUpdateDataset } = await import("./tools/update-dataset.ts");
1771
2212
  return {
1772
2213
  content: [{ type: "text", text: await handleUpdateDataset(params) }],
1773
2214
  };
1774
- }
2215
+ },
1775
2216
  );
1776
2217
 
1777
2218
  server.tool(
@@ -1781,30 +2222,21 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
1781
2222
  slugOrId: z.string().describe("The dataset slug or ID to delete"),
1782
2223
  },
1783
2224
  async (params) => {
1784
- const { requireApiKey } = await import("./config.js");
2225
+ const { requireApiKey } = await import("./config.ts");
1785
2226
  requireApiKey();
1786
- const { handleDeleteDataset } = await import(
1787
- "./tools/delete-dataset.js"
1788
- );
2227
+ const { handleDeleteDataset } = await import("./tools/delete-dataset.ts");
1789
2228
  return {
1790
2229
  content: [{ type: "text", text: await handleDeleteDataset(params) }],
1791
2230
  };
1792
- }
2231
+ },
1793
2232
  );
1794
2233
 
1795
2234
  server.tool(
1796
2235
  "platform_list_dataset_records",
1797
2236
  "List records in a dataset on the LangWatch platform with pagination.",
1798
2237
  {
1799
- slugOrId: z
1800
- .string()
1801
- .describe("The dataset slug or ID to list records from"),
1802
- page: z
1803
- .number()
1804
- .int()
1805
- .min(1)
1806
- .optional()
1807
- .describe("Page number (default: 1)"),
2238
+ slugOrId: z.string().describe("The dataset slug or ID to list records from"),
2239
+ page: z.number().int().min(1).optional().describe("Page number (default: 1)"),
1808
2240
  limit: z
1809
2241
  .number()
1810
2242
  .int()
@@ -1818,26 +2250,20 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
1818
2250
  .describe("Output format: 'digest' (default, AI-readable) or 'json' (full raw data)"),
1819
2251
  },
1820
2252
  async (params) => {
1821
- const { requireApiKey } = await import("./config.js");
2253
+ const { requireApiKey } = await import("./config.ts");
1822
2254
  requireApiKey();
1823
- const { handleListDatasetRecords } = await import(
1824
- "./tools/list-dataset-records.js"
1825
- );
2255
+ const { handleListDatasetRecords } = await import("./tools/list-dataset-records.ts");
1826
2256
  return {
1827
- content: [
1828
- { type: "text", text: await handleListDatasetRecords(params) },
1829
- ],
2257
+ content: [{ type: "text", text: await handleListDatasetRecords(params) }],
1830
2258
  };
1831
- }
2259
+ },
1832
2260
  );
1833
2261
 
1834
2262
  server.tool(
1835
2263
  "platform_create_dataset_records",
1836
2264
  "Add records to a dataset on the LangWatch platform in batch (max 1000 per call).",
1837
2265
  {
1838
- slugOrId: z
1839
- .string()
1840
- .describe("The dataset slug or ID to add records to"),
2266
+ slugOrId: z.string().describe("The dataset slug or ID to add records to"),
1841
2267
  entries: z
1842
2268
  .array(z.record(z.string(), z.unknown()))
1843
2269
  .min(1)
@@ -1845,90 +2271,75 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
1845
2271
  .describe("Array of record entries to create (key-value objects matching dataset columns)"),
1846
2272
  },
1847
2273
  async (params) => {
1848
- const { requireApiKey } = await import("./config.js");
2274
+ const { requireApiKey } = await import("./config.ts");
1849
2275
  requireApiKey();
1850
- const { handleCreateDatasetRecords } = await import(
1851
- "./tools/create-dataset-records.js"
1852
- );
2276
+ const { handleCreateDatasetRecords } = await import("./tools/create-dataset-records.ts");
1853
2277
  return {
1854
- content: [
1855
- { type: "text", text: await handleCreateDatasetRecords(params) },
1856
- ],
2278
+ content: [{ type: "text", text: await handleCreateDatasetRecords(params) }],
1857
2279
  };
1858
- }
2280
+ },
1859
2281
  );
1860
2282
 
1861
2283
  server.tool(
1862
2284
  "platform_update_dataset_record",
1863
2285
  "Update a single record in a dataset on the LangWatch platform.",
1864
2286
  {
1865
- slugOrId: z
1866
- .string()
1867
- .describe("The dataset slug or ID containing the record"),
2287
+ slugOrId: z.string().describe("The dataset slug or ID containing the record"),
1868
2288
  recordId: z.string().describe("The record ID to update"),
1869
- entry: z
1870
- .record(z.string(), z.unknown())
1871
- .describe("Updated record entry (key-value object)"),
2289
+ entry: z.record(z.string(), z.unknown()).describe("Updated record entry (key-value object)"),
1872
2290
  },
1873
2291
  async (params) => {
1874
- const { requireApiKey } = await import("./config.js");
2292
+ const { requireApiKey } = await import("./config.ts");
1875
2293
  requireApiKey();
1876
- const { handleUpdateDatasetRecord } = await import(
1877
- "./tools/update-dataset-record.js"
1878
- );
2294
+ const { handleUpdateDatasetRecord } = await import("./tools/update-dataset-record.ts");
1879
2295
  return {
1880
- content: [
1881
- { type: "text", text: await handleUpdateDatasetRecord(params) },
1882
- ],
2296
+ content: [{ type: "text", text: await handleUpdateDatasetRecord(params) }],
1883
2297
  };
1884
- }
2298
+ },
1885
2299
  );
1886
2300
 
1887
2301
  server.tool(
1888
2302
  "platform_delete_dataset_records",
1889
2303
  "Delete records from a dataset on the LangWatch platform by their IDs.",
1890
2304
  {
1891
- slugOrId: z
1892
- .string()
1893
- .describe("The dataset slug or ID containing the records"),
1894
- recordIds: z
1895
- .array(z.string())
1896
- .min(1)
1897
- .max(1000)
1898
- .describe("Array of record IDs to delete"),
2305
+ slugOrId: z.string().describe("The dataset slug or ID containing the records"),
2306
+ recordIds: z.array(z.string()).min(1).max(1000).describe("Array of record IDs to delete"),
1899
2307
  },
1900
2308
  async (params) => {
1901
- const { requireApiKey } = await import("./config.js");
2309
+ const { requireApiKey } = await import("./config.ts");
1902
2310
  requireApiKey();
1903
- const { handleDeleteDatasetRecords } = await import(
1904
- "./tools/delete-dataset-records.js"
1905
- );
2311
+ const { handleDeleteDatasetRecords } = await import("./tools/delete-dataset-records.ts");
1906
2312
  return {
1907
- content: [
1908
- { type: "text", text: await handleDeleteDatasetRecords(params) },
1909
- ],
2313
+ content: [{ type: "text", text: await handleDeleteDatasetRecords(params) }],
1910
2314
  };
1911
- }
2315
+ },
1912
2316
  );
2317
+ }
1913
2318
 
1914
- // --- Platform Project Tools ---
1915
- // These tools manage projects on the LangWatch platform via the REST API.
1916
- // They require an API key with org-level permissions.
1917
-
2319
+ // --- Platform Project Tools ---
2320
+ // These tools manage projects on the LangWatch platform via the REST API.
2321
+ // They require an API key with org-level permissions.
2322
+ function registerProjectTools(server: McpServer): void {
1918
2323
  server.tool(
1919
2324
  "platform_list_projects",
1920
2325
  "List all projects in your LangWatch organization. Requires an org-level API key.",
1921
2326
  {
1922
2327
  page: z.number().int().positive().optional().describe("Page number (default: 1)"),
1923
- limit: z.number().int().positive().max(1000).optional().describe("Results per page (default: 100)"),
2328
+ limit: z
2329
+ .number()
2330
+ .int()
2331
+ .positive()
2332
+ .max(1000)
2333
+ .optional()
2334
+ .describe("Results per page (default: 100)"),
1924
2335
  },
1925
2336
  withToolLogging("platform_list_projects", async (params) => {
1926
2337
  requireApiKey();
1927
- const { handleListProjects } = await import("./tools/list-projects.js");
2338
+ const { handleListProjects } = await import("./tools/list-projects.ts");
1928
2339
  return {
1929
2340
  content: [{ type: "text", text: await handleListProjects(params) }],
1930
2341
  };
1931
- })
2342
+ }),
1932
2343
  );
1933
2344
 
1934
2345
  server.tool(
@@ -1939,11 +2350,11 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
1939
2350
  },
1940
2351
  withToolLogging("platform_get_project", async (params) => {
1941
2352
  requireApiKey();
1942
- const { handleGetProject } = await import("./tools/get-project.js");
2353
+ const { handleGetProject } = await import("./tools/get-project.ts");
1943
2354
  return {
1944
2355
  content: [{ type: "text", text: await handleGetProject(params) }],
1945
2356
  };
1946
- })
2357
+ }),
1947
2358
  );
1948
2359
 
1949
2360
  server.tool(
@@ -1960,11 +2371,11 @@ You must provide either teamId (to add the project to an existing team) or newTe
1960
2371
  },
1961
2372
  withToolLogging("platform_create_project", async (params) => {
1962
2373
  requireApiKey();
1963
- const { handleCreateProject } = await import("./tools/create-project.js");
2374
+ const { handleCreateProject } = await import("./tools/create-project.ts");
1964
2375
  return {
1965
2376
  content: [{ type: "text", text: await handleCreateProject(params) }],
1966
2377
  };
1967
- })
2378
+ }),
1968
2379
  );
1969
2380
 
1970
2381
  server.tool(
@@ -1975,15 +2386,18 @@ You must provide either teamId (to add the project to an existing team) or newTe
1975
2386
  name: z.string().optional().describe("New project name"),
1976
2387
  language: z.string().optional().describe("New programming language"),
1977
2388
  framework: z.string().optional().describe("New framework"),
1978
- piiRedactionLevel: z.enum(["STRICT", "ESSENTIAL", "DISABLED"]).optional().describe("PII redaction level"),
2389
+ piiRedactionLevel: z
2390
+ .enum(["STRICT", "ESSENTIAL", "DISABLED"])
2391
+ .optional()
2392
+ .describe("PII redaction level"),
1979
2393
  },
1980
2394
  withToolLogging("platform_update_project", async (params) => {
1981
2395
  requireApiKey();
1982
- const { handleUpdateProject } = await import("./tools/update-project.js");
2396
+ const { handleUpdateProject } = await import("./tools/update-project.ts");
1983
2397
  return {
1984
2398
  content: [{ type: "text", text: await handleUpdateProject(params) }],
1985
2399
  };
1986
- })
2400
+ }),
1987
2401
  );
1988
2402
 
1989
2403
  server.tool(
@@ -1994,27 +2408,28 @@ You must provide either teamId (to add the project to an existing team) or newTe
1994
2408
  },
1995
2409
  withToolLogging("platform_archive_project", async (params) => {
1996
2410
  requireApiKey();
1997
- const { handleArchiveProject } = await import("./tools/archive-project.js");
2411
+ const { handleArchiveProject } = await import("./tools/archive-project.ts");
1998
2412
  return {
1999
2413
  content: [{ type: "text", text: await handleArchiveProject(params) }],
2000
2414
  };
2001
- })
2415
+ }),
2002
2416
  );
2417
+ }
2003
2418
 
2004
- // --- Platform API Key Tools (require org-level API key) ---
2005
- // These tools manage API keys on the LangWatch platform via the REST API.
2006
-
2419
+ // --- Platform API Key Tools (require org-level API key) ---
2420
+ // These tools manage API keys on the LangWatch platform via the REST API.
2421
+ function registerApiKeyTools(server: McpServer): void {
2007
2422
  server.tool(
2008
2423
  "platform_list_api_keys",
2009
2424
  "List all API keys in your LangWatch organization. Shows key metadata, status (active/revoked/expired), and role bindings. API key tokens are never returned.",
2010
2425
  {},
2011
2426
  withToolLogging("platform_list_api_keys", async () => {
2012
2427
  requireApiKey();
2013
- const { handleListApiKeys } = await import("./tools/list-api-keys.js");
2428
+ const { handleListApiKeys } = await import("./tools/list-api-keys.ts");
2014
2429
  return {
2015
2430
  content: [{ type: "text", text: await handleListApiKeys() }],
2016
2431
  };
2017
- })
2432
+ }),
2018
2433
  );
2019
2434
 
2020
2435
  server.tool(
@@ -2026,24 +2441,34 @@ You must provide either teamId (to add the project to an existing team) or newTe
2026
2441
 
2027
2442
  The token is returned once and cannot be retrieved again.`,
2028
2443
  {
2029
- keyType: z.enum(["personal", "service"]).describe("Key type: 'personal' (user-tied) or 'service' (headless)"),
2444
+ keyType: z
2445
+ .enum(["personal", "service"])
2446
+ .describe("Key type: 'personal' (user-tied) or 'service' (headless)"),
2030
2447
  name: z.string().describe("Key name (max 100 chars)"),
2031
2448
  description: z.string().optional().describe("Key description (max 500 chars)"),
2032
2449
  expiresAt: z.string().optional().describe("Expiration date (ISO string)"),
2033
- bindings: z.array(z.object({
2034
- role: z.enum(["ADMIN", "MEMBER", "VIEWER"]).describe("Role"),
2035
- scopeType: z.enum(["ORGANIZATION", "TEAM", "PROJECT"]).describe("Scope type"),
2036
- scopeId: z.string().describe("Scope ID (org, team, or project ID)"),
2037
- })).optional().describe("Role bindings (required for personal keys)"),
2038
- projectIds: z.array(z.string()).optional().describe("Project IDs for service keys (creates ADMIN bindings per project)"),
2450
+ bindings: z
2451
+ .array(
2452
+ z.object({
2453
+ role: z.enum(["ADMIN", "MEMBER", "VIEWER"]).describe("Role"),
2454
+ scopeType: z.enum(["ORGANIZATION", "TEAM", "PROJECT"]).describe("Scope type"),
2455
+ scopeId: z.string().describe("Scope ID (org, team, or project ID)"),
2456
+ }),
2457
+ )
2458
+ .optional()
2459
+ .describe("Role bindings (required for personal keys)"),
2460
+ projectIds: z
2461
+ .array(z.string())
2462
+ .optional()
2463
+ .describe("Project IDs for service keys (creates ADMIN bindings per project)"),
2039
2464
  },
2040
2465
  withToolLogging("platform_create_api_key", async (params) => {
2041
2466
  requireApiKey();
2042
- const { handleCreateApiKey } = await import("./tools/create-api-key.js");
2467
+ const { handleCreateApiKey } = await import("./tools/create-api-key.ts");
2043
2468
  return {
2044
2469
  content: [{ type: "text", text: await handleCreateApiKey(params) }],
2045
2470
  };
2046
- })
2471
+ }),
2047
2472
  );
2048
2473
 
2049
2474
  server.tool(
@@ -2054,10 +2479,10 @@ The token is returned once and cannot be retrieved again.`,
2054
2479
  },
2055
2480
  withToolLogging("platform_revoke_api_key", async (params) => {
2056
2481
  requireApiKey();
2057
- const { handleRevokeApiKey } = await import("./tools/revoke-api-key.js");
2482
+ const { handleRevokeApiKey } = await import("./tools/revoke-api-key.ts");
2058
2483
  return {
2059
2484
  content: [{ type: "text", text: await handleRevokeApiKey(params) }],
2060
2485
  };
2061
- })
2486
+ }),
2062
2487
  );
2063
2488
  }