@langwatch/mcp-server 2.1.0 → 3.0.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 (428) hide show
  1. package/.gitignore +7 -0
  2. package/.npmignore +41 -0
  3. package/CHANGELOG.md +218 -201
  4. package/CONTRIBUTING.md +10 -0
  5. package/README.md +70 -68
  6. package/dist/add-dashboard-widget-55UI6H22.js +44 -0
  7. package/dist/add-dashboard-widget-MJHIC3CC.js +45 -0
  8. package/dist/{archive-project-W3IKI7UL.js → archive-project-K5UD7A6X.js} +4 -4
  9. package/dist/archive-project-L4PAKT34.js +19 -0
  10. package/dist/{archive-run-plan-474OKHHU.js → archive-run-plan-5HSQG2HX.js} +4 -4
  11. package/dist/archive-run-plan-AOKNYQIO.js +15 -0
  12. package/dist/archive-scenario-2CWJW2VD.js +19 -0
  13. package/dist/{archive-scenario-V5UNYK26.js → archive-scenario-5IJQESHH.js} +4 -4
  14. package/dist/{archive-test-suite-O7FLDMXK.js → archive-test-suite-OPB7NFI7.js} +4 -4
  15. package/dist/archive-test-suite-ZC3TN6VH.js +15 -0
  16. package/dist/assign-prompt-tag-6DDE6ZZ5.js +23 -0
  17. package/dist/{assign-prompt-tag-Q46WRB6P.js → assign-prompt-tag-D7O23MJM.js} +8 -4
  18. package/dist/chunk-3YLN3ATJ.js +241 -0
  19. package/dist/{chunk-EYXMLE5X.js → chunk-4RX3G62A.js} +56 -47
  20. package/dist/{chunk-UF6XIGYR.js → chunk-6YZUOD34.js} +2 -5
  21. package/dist/{chunk-WVI5CJ6S.js → chunk-7AQLPVFL.js} +2 -2
  22. package/dist/chunk-7CZQDZ2K.js +319 -0
  23. package/dist/{chunk-RVAE2MEA.js → chunk-7GETGE6B.js} +7 -7
  24. package/dist/{chunk-OJLYYJWI.js → chunk-AASW2TO2.js} +7 -10
  25. package/dist/{chunk-QAMP3CGR.js → chunk-B4RFWIUP.js} +4 -4
  26. package/dist/{chunk-ZXKLPC2E.js → chunk-BIIKE3EI.js} +1 -1
  27. package/dist/{chunk-G4ZAZTZW.js → chunk-D2HS73VI.js} +7 -10
  28. package/dist/{chunk-37SMPMQS.js → chunk-DMM3JQ42.js} +7 -2
  29. package/dist/{chunk-WT4PO4A3.js → chunk-DULFP3MK.js} +10 -7
  30. package/dist/chunk-EYPJBL7P.js +516 -0
  31. package/dist/{chunk-NUFPTHKU.js → chunk-FOKVY3BK.js} +1 -1
  32. package/dist/chunk-GA5JUSPT.js +64 -0
  33. package/dist/{chunk-UJPXFA26.js → chunk-HHMNZAPD.js} +101 -74
  34. package/dist/chunk-HLQJXUR6.js +67 -0
  35. package/dist/{chunk-JTQJRNFJ.js → chunk-KILVK3FS.js} +4 -4
  36. package/dist/chunk-MOCU5XWF.js +79 -0
  37. package/dist/chunk-O2WGVCED.js +41 -0
  38. package/dist/{chunk-HV4DWKBI.js → chunk-O7EMWEVJ.js} +2 -4
  39. package/dist/{chunk-UFOYFYI7.js → chunk-QXLMHLRC.js} +6 -9
  40. package/dist/{chunk-QRO35SOM.js → chunk-R5YJIYPF.js} +981 -747
  41. package/dist/{chunk-JR6DJZOU.js → chunk-RABYBFX5.js} +4 -10
  42. package/dist/{chunk-SR6QPRIE.js → chunk-RFC2SBEX.js} +7 -13
  43. package/dist/chunk-SHSVN3SE.js +21 -0
  44. package/dist/{chunk-CKNAH5TG.js → chunk-U24EACC4.js} +3 -10
  45. package/dist/{chunk-56OGTS24.js → chunk-VOUGDY2O.js} +4 -11
  46. package/dist/chunk-WCKZDOGF.js +31 -0
  47. package/dist/chunk-XQYH52SI.js +129 -0
  48. package/dist/{chunk-7E4FZIYJ.js → chunk-XUIJYNRJ.js} +1 -1
  49. package/dist/{chunk-6DJDEXET.js → chunk-YCIUEZVK.js} +6 -12
  50. package/dist/config-2WQGMAEN.js +16 -0
  51. package/dist/config.cjs +109 -0
  52. package/dist/config.js +5 -4
  53. package/dist/{create-agent-JFJ2K6X3.js → create-agent-6JNLMYQD.js} +4 -4
  54. package/dist/create-agent-IRZOU4R5.js +23 -0
  55. package/dist/create-analytics-report-N4L4V5QL.js +19 -0
  56. package/dist/create-analytics-report-QEHXG7FU.js +18 -0
  57. package/dist/{create-annotation-EUXXQ6NS.js → create-annotation-CCEGIZ67.js} +10 -5
  58. package/dist/create-annotation-DTHCMWHB.js +29 -0
  59. package/dist/{create-api-key-BGBB76JX.js → create-api-key-JKM2DSZO.js} +5 -7
  60. package/dist/create-api-key-JPEUIXGM.js +24 -0
  61. package/dist/create-dataset-DJNXYJ5B.js +22 -0
  62. package/dist/{create-dataset-FD5VAXU4.js → create-dataset-WON4SOTC.js} +6 -6
  63. package/dist/create-dataset-records-TMEOTLTO.js +20 -0
  64. package/dist/{create-dataset-records-WQUXXOHI.js → create-dataset-records-UJBW3C7C.js} +5 -7
  65. package/dist/create-evaluator-N5AEHI4K.js +27 -0
  66. package/dist/{create-evaluator-6NV33Z52.js → create-evaluator-XYNY2YFY.js} +4 -4
  67. package/dist/create-mcp-server.cjs +9712 -0
  68. package/dist/create-mcp-server.js +2757 -10
  69. package/dist/{create-project-ZQ7TFIO2.js → create-project-EY4XZ7TH.js} +4 -4
  70. package/dist/create-project-YL345T5L.js +44 -0
  71. package/dist/{create-prompt-BS6QRCUU.js → create-prompt-JZQNQ7I3.js} +5 -7
  72. package/dist/create-prompt-ZUMQHHX7.js +37 -0
  73. package/dist/create-prompt-tag-CJ5FZ436.js +17 -0
  74. package/dist/{create-prompt-tag-FCQ5ELSQ.js → create-prompt-tag-JFP2Q3PO.js} +3 -3
  75. package/dist/create-scenario-CQU2SSFB.js +33 -0
  76. package/dist/{create-scenario-DRPTPXU3.js → create-scenario-S6OWWKJO.js} +5 -5
  77. package/dist/create-test-suite-OLGCAGJA.js +36 -0
  78. package/dist/{create-test-suite-ZIZCCFJ4.js → create-test-suite-Y7P2RI4L.js} +6 -6
  79. package/dist/create-trigger-4CNBTISJ.js +48 -0
  80. package/dist/create-trigger-RQ653WYF.js +49 -0
  81. package/dist/dashboards-BA7M6CMO.js +90 -0
  82. package/dist/dashboards-U663GUIB.js +91 -0
  83. package/dist/{delete-agent-SA4JMUZF.js → delete-agent-J2Q4DRZ2.js} +4 -4
  84. package/dist/delete-agent-ZAU4LTXB.js +18 -0
  85. package/dist/delete-analytics-report-QK6X7P4S.js +19 -0
  86. package/dist/delete-analytics-report-YUYLDHLL.js +18 -0
  87. package/dist/delete-annotation-7X4NYXXM.js +17 -0
  88. package/dist/{delete-annotation-X3KKK7QY.js → delete-annotation-KJOCDLV3.js} +4 -4
  89. package/dist/{delete-dataset-W6EZ44HY.js → delete-dataset-NXP7ZPPV.js} +5 -7
  90. package/dist/delete-dataset-RUTZBBVD.js +18 -0
  91. package/dist/{delete-dataset-records-7CNOTALB.js → delete-dataset-records-4Z7DZCDW.js} +5 -7
  92. package/dist/delete-dataset-records-75UAR5IS.js +19 -0
  93. package/dist/delete-evaluator-E3CUNGTQ.js +16 -0
  94. package/dist/delete-evaluator-RI4GALGX.js +15 -0
  95. package/dist/delete-prompt-tag-LE3WJPKS.js +17 -0
  96. package/dist/{delete-prompt-tag-QB3OCZUV.js → delete-prompt-tag-S4KBZTNG.js} +3 -3
  97. package/dist/delete-workflow-4CZFIQCQ.js +17 -0
  98. package/dist/{delete-workflow-NG2VXYYM.js → delete-workflow-PWQPDW6H.js} +4 -4
  99. package/dist/discover-evaluator-schema-UHWA5HVL.js +120 -0
  100. package/dist/discover-evaluator-schema-XKCLLCB3.js +118 -0
  101. package/dist/discover-scenario-schema-NOJY6S7E.js +137 -0
  102. package/dist/{discover-scenario-schema-W7KBXA4J.js → discover-scenario-schema-OOFIQKOG.js} +37 -17
  103. package/dist/discover-schema-7WHIIATI.js +442 -0
  104. package/dist/{discover-schema-L6QDPON3.js → discover-schema-J2ANOBKO.js} +181 -186
  105. package/dist/get-agent-5T3CXQOR.js +69 -0
  106. package/dist/{get-agent-7THF7HCU.js → get-agent-ZKYQJUQ7.js} +15 -11
  107. package/dist/get-analytics-4MPEK4KV.js +79 -0
  108. package/dist/get-analytics-ZLI3A4F5.js +80 -0
  109. package/dist/get-analytics-report-JCKYMA2S.js +29 -0
  110. package/dist/get-analytics-report-SKIP2JWH.js +30 -0
  111. package/dist/get-dataset-APGZBYMH.js +64 -0
  112. package/dist/get-dataset-LWXDVMVE.js +63 -0
  113. package/dist/get-evaluator-3A7BJBAB.js +47 -0
  114. package/dist/{get-evaluator-RZ44UIN6.js → get-evaluator-IWNJINRY.js} +4 -4
  115. package/dist/get-project-EQVB4LEL.js +26 -0
  116. package/dist/{get-project-QSQEAB33.js → get-project-RK6AJTEP.js} +4 -4
  117. package/dist/{get-prompt-GVKXF5NL.js → get-prompt-TLG2T2DS.js} +53 -58
  118. package/dist/get-prompt-ZER557X3.js +94 -0
  119. package/dist/get-run-plan-5X7DDJRN.js +58 -0
  120. package/dist/{get-run-plan-NY7J5GRE.js → get-run-plan-PEHRO5M3.js} +7 -9
  121. package/dist/{get-scenario-Q3LLQE4T.js → get-scenario-375W5D6I.js} +5 -5
  122. package/dist/get-scenario-UQX45ZZO.js +37 -0
  123. package/dist/{get-simulation-run-WINEYDMK.js → get-simulation-run-2NBXJADG.js} +26 -22
  124. package/dist/get-simulation-run-QZTLGL7W.js +69 -0
  125. package/dist/{get-test-suite-4HKA7OKS.js → get-test-suite-GVIBDG7K.js} +5 -5
  126. package/dist/get-test-suite-OODIR3RI.js +48 -0
  127. package/dist/{get-trace-RCUV7N4O.js → get-trace-AGCHVPJK.js} +11 -13
  128. package/dist/get-trace-KJLYAZRC.js +45 -0
  129. package/dist/get-workflow-3TV2YYC5.js +25 -0
  130. package/dist/{get-workflow-EEOC7Y45.js → get-workflow-7LZSVPP2.js} +4 -4
  131. package/dist/{http-security-EU3CHTY7.js → http-security-DNLBNQZL.js} +2 -2
  132. package/dist/{http-server-WCDUARTH.js → http-server-7CCN3EHJ.js} +4249 -325
  133. package/dist/index.js +59595 -38027
  134. package/dist/langwatch-api-analytics-reports-6CF6KUET.js +17 -0
  135. package/dist/langwatch-api-analytics-reports-RPFB5AO5.js +18 -0
  136. package/dist/{langwatch-api-annotations-6DHVZWUK.js → langwatch-api-annotations-H4AI4TKG.js} +4 -4
  137. package/dist/langwatch-api-annotations-QSJNUQQS.js +17 -0
  138. package/dist/langwatch-api-monitors-ENHLOL3S.js +47 -0
  139. package/dist/{langwatch-api-monitors-6ISFJ7WX.js → langwatch-api-monitors-OHSGADTH.js} +11 -16
  140. package/dist/langwatch-api-secrets-AGW6UUMD.js +55 -0
  141. package/dist/langwatch-api-secrets-CLTS766Q.js +56 -0
  142. package/dist/langwatch-api-triggers-G5UWFNN2.js +22 -0
  143. package/dist/langwatch-api-triggers-QMR7KEXW.js +23 -0
  144. package/dist/langwatch-api-workflows-HKTBIKKY.js +15 -0
  145. package/dist/{langwatch-api-workflows-6UJL5U4Z.js → langwatch-api-workflows-OMROZH2O.js} +4 -4
  146. package/dist/{list-agents-AMVDQPQL.js → list-agents-BYWDNAHT.js} +4 -4
  147. package/dist/list-agents-MG24MHZE.js +42 -0
  148. package/dist/list-analytics-reports-G3EPWGSQ.js +32 -0
  149. package/dist/list-analytics-reports-PPWIFRES.js +33 -0
  150. package/dist/list-annotations-QCLGEK5A.js +38 -0
  151. package/dist/{list-annotations-6PR5NLIC.js → list-annotations-Y4EUBJHG.js} +10 -5
  152. package/dist/list-api-keys-DAVOBPFK.js +42 -0
  153. package/dist/{list-api-keys-GVPNBIHQ.js → list-api-keys-JMW5WRXX.js} +10 -5
  154. package/dist/list-dashboards-2P3VJFRS.js +29 -0
  155. package/dist/list-dashboards-MW5O3MKH.js +30 -0
  156. package/dist/list-dataset-records-KEHT7G6Q.js +43 -0
  157. package/dist/{list-dataset-records-O67HZ35Y.js → list-dataset-records-KMLLMZAZ.js} +14 -10
  158. package/dist/{list-datasets-WXZL6UMP.js → list-datasets-P3JK2ZHS.js} +9 -13
  159. package/dist/list-datasets-Z7OOE4KI.js +56 -0
  160. package/dist/{list-evaluators-PAX72WCN.js → list-evaluators-7RRESBOU.js} +5 -7
  161. package/dist/list-evaluators-VATJVOHR.js +32 -0
  162. package/dist/{list-model-providers-VVLZMLCX.js → list-model-providers-OXDRPUCJ.js} +4 -4
  163. package/dist/list-model-providers-VXQJWDXU.js +35 -0
  164. package/dist/{list-projects-AQVI6SUK.js → list-projects-HUVK7L3W.js} +5 -7
  165. package/dist/list-projects-X72ZXVY5.js +40 -0
  166. package/dist/{list-prompt-tags-WZFBJOHG.js → list-prompt-tags-3I3LWFIR.js} +3 -3
  167. package/dist/list-prompt-tags-DRHUH2ND.js +23 -0
  168. package/dist/list-prompts-6DX3QECJ.js +30 -0
  169. package/dist/{list-prompts-BQOHRHYT.js → list-prompts-PS423RU3.js} +4 -6
  170. package/dist/list-run-plans-5SXPOV3X.js +45 -0
  171. package/dist/{list-run-plans-UBAU2DGH.js → list-run-plans-6VNHINPY.js} +5 -5
  172. package/dist/list-scenarios-EQWCW7Q4.js +45 -0
  173. package/dist/{list-scenarios-MS4IUIVM.js → list-scenarios-GKNH3YND.js} +13 -11
  174. package/dist/{list-simulation-runs-D6YF4ZOU.js → list-simulation-runs-KHY7KALI.js} +12 -8
  175. package/dist/list-simulation-runs-PUDK32C2.js +53 -0
  176. package/dist/list-test-suites-7H2DRILK.js +34 -0
  177. package/dist/{list-test-suites-L7R2O6DR.js → list-test-suites-GY56LGLY.js} +4 -4
  178. package/dist/list-trigger-fires-7RZUP54Y.js +19 -0
  179. package/dist/list-trigger-fires-OZUF65LI.js +20 -0
  180. package/dist/list-triggers-BGUCUXTJ.js +45 -0
  181. package/dist/list-triggers-XC5KMX4C.js +46 -0
  182. package/dist/list-workflows-55NYC3PC.js +33 -0
  183. package/dist/{list-workflows-6MV4XJHF.js → list-workflows-HV7FUWZA.js} +5 -7
  184. package/dist/{rename-prompt-tag-K5LQ4CY6.js → rename-prompt-tag-2I7Z5FCY.js} +3 -3
  185. package/dist/rename-prompt-tag-SZYTOWDX.js +18 -0
  186. package/dist/{rename-test-suite-NTYHAO5X.js → rename-test-suite-27AYY33K.js} +4 -4
  187. package/dist/rename-test-suite-CLIEPZDO.js +20 -0
  188. package/dist/report-issue-5HSL7CNB.js +924 -0
  189. package/dist/{report-issue-RHZZ33IS.js → report-issue-N7ND5TM6.js} +107 -67
  190. package/dist/{rerun-run-plan-MUDWJBE4.js → rerun-run-plan-O3VUZ6CG.js} +5 -5
  191. package/dist/rerun-run-plan-OJTVVGJI.js +21 -0
  192. package/dist/{revoke-api-key-6LAZ3QZZ.js → revoke-api-key-BU6BKGCG.js} +4 -4
  193. package/dist/revoke-api-key-NZNCFOIR.js +15 -0
  194. package/dist/run-agent-244O4Y5M.js +75 -0
  195. package/dist/run-agent-HPCGZGYW.js +74 -0
  196. package/dist/run-plan-2ZPGGZ4H.js +38 -0
  197. package/dist/{run-plan-T4ZYOZGA.js → run-plan-KSXUYQT3.js} +7 -7
  198. package/dist/run-query-4LMOQ5UW.js +47 -0
  199. package/dist/run-query-BQUHIUC2.js +46 -0
  200. package/dist/{run-test-suite-L4MZ7G4D.js → run-test-suite-P3WLVPXC.js} +6 -6
  201. package/dist/run-test-suite-UEOMCON6.js +25 -0
  202. package/dist/run-workflow-EFZ6HG5B.js +36 -0
  203. package/dist/{run-workflow-DFIS23AP.js → run-workflow-I4IHEN54.js} +4 -4
  204. package/dist/search-traces-6IR7PR4L.js +170 -0
  205. package/dist/search-traces-BYML2NQ4.js +171 -0
  206. package/dist/set-model-provider-RBNGWFVI.js +33 -0
  207. package/dist/{set-model-provider-XRNMFOVR.js → set-model-provider-SHEVKWRC.js} +4 -4
  208. package/dist/{update-agent-DUWNYBNA.js → update-agent-PTKTM2B5.js} +4 -4
  209. package/dist/update-agent-ZPXWRMS2.js +20 -0
  210. package/dist/update-dashboard-widget-HKCBXH7X.js +62 -0
  211. package/dist/update-dashboard-widget-IP2TTQ5G.js +63 -0
  212. package/dist/update-dataset-GHIN6JR3.js +22 -0
  213. package/dist/{update-dataset-YSOMDCOB.js → update-dataset-IRVMY5PF.js} +6 -6
  214. package/dist/update-dataset-record-6VI7K2CL.js +19 -0
  215. package/dist/{update-dataset-record-DEA2ITYR.js → update-dataset-record-Z3L7ACNF.js} +4 -4
  216. package/dist/update-evaluator-5NLNE6VY.js +24 -0
  217. package/dist/{update-evaluator-NH7Q5GWV.js → update-evaluator-FEH7H6NS.js} +4 -4
  218. package/dist/update-project-75H3LDQT.js +24 -0
  219. package/dist/{update-project-Q26FBSYH.js → update-project-QPR3QX55.js} +4 -4
  220. package/dist/update-prompt-BCDFTNLK.js +140 -0
  221. package/dist/{update-prompt-ZQQ2C3KU.js → update-prompt-NPRXYSWA.js} +27 -29
  222. package/dist/update-scenario-BNOMDCMV.js +34 -0
  223. package/dist/{update-scenario-WUX34JAR.js → update-scenario-EM5XGMB7.js} +5 -5
  224. package/package.json +35 -39
  225. package/pyproject.toml +23 -8
  226. package/src/config.d.ts +6 -0
  227. package/src/config.ts +22 -29
  228. package/src/create-mcp-server.ts +887 -634
  229. package/src/documentation-fetch.ts +21 -20
  230. package/src/http-security.ts +166 -135
  231. package/src/http-server.ts +189 -224
  232. package/src/index.ts +14 -16
  233. package/src/langwatch-api-agents.ts +18 -21
  234. package/src/langwatch-api-analytics-reports.ts +62 -0
  235. package/src/langwatch-api-annotations.ts +11 -12
  236. package/src/langwatch-api-api-keys.ts +8 -9
  237. package/src/langwatch-api-dashboard-widgets.ts +47 -0
  238. package/src/langwatch-api-dashboards.ts +109 -24
  239. package/src/langwatch-api-datasets.ts +98 -32
  240. package/src/langwatch-api-evaluators.ts +13 -19
  241. package/src/langwatch-api-model-providers.ts +4 -4
  242. package/src/langwatch-api-monitors.ts +14 -18
  243. package/src/langwatch-api-projects.ts +10 -9
  244. package/src/langwatch-api-query.ts +114 -0
  245. package/src/langwatch-api-run-plans.ts +12 -29
  246. package/src/langwatch-api-scenarios.ts +9 -11
  247. package/src/langwatch-api-secrets.ts +31 -24
  248. package/src/langwatch-api-simulation-runs.ts +6 -8
  249. package/src/langwatch-api-test-suites.ts +14 -33
  250. package/src/langwatch-api-triggers.ts +96 -34
  251. package/src/langwatch-api-workflows.ts +9 -9
  252. package/src/langwatch-api.ts +118 -124
  253. package/src/public-http-request.ts +21 -12
  254. package/src/schemas/analytics-metrics.ts +3 -15
  255. package/src/schemas/create-dataset.ts +3 -4
  256. package/src/schemas/run-plan.ts +13 -29
  257. package/src/schemas/suite-fields.ts +6 -23
  258. package/src/schemas/triggers.ts +365 -0
  259. package/src/schemas/widget-sources.ts +33 -0
  260. package/src/tools/add-dashboard-widget.ts +51 -0
  261. package/src/tools/archive-project.ts +2 -4
  262. package/src/tools/archive-run-plan.ts +2 -4
  263. package/src/tools/archive-scenario.ts +2 -4
  264. package/src/tools/archive-test-suite.ts +2 -4
  265. package/src/tools/assign-prompt-tag.ts +6 -2
  266. package/src/tools/create-agent.ts +1 -1
  267. package/src/tools/create-analytics-report.ts +7 -0
  268. package/src/tools/create-annotation.ts +7 -2
  269. package/src/tools/create-api-key.ts +4 -6
  270. package/src/tools/create-dataset-records.ts +4 -8
  271. package/src/tools/create-dataset.ts +5 -7
  272. package/src/tools/create-evaluator.ts +3 -5
  273. package/src/tools/create-project.ts +2 -2
  274. package/src/tools/create-prompt-tag.ts +2 -4
  275. package/src/tools/create-prompt.ts +9 -18
  276. package/src/tools/create-scenario.ts +5 -7
  277. package/src/tools/create-test-suite.ts +4 -9
  278. package/src/tools/create-trigger.ts +55 -0
  279. package/src/tools/dashboard-widget-queries.ts +45 -0
  280. package/src/tools/dashboards.ts +104 -0
  281. package/src/tools/delete-agent.ts +1 -1
  282. package/src/tools/delete-analytics-report.ts +7 -0
  283. package/src/tools/delete-annotation.ts +1 -1
  284. package/src/tools/delete-dataset-records.ts +4 -8
  285. package/src/tools/delete-dataset.ts +3 -7
  286. package/src/tools/delete-evaluator.ts +4 -6
  287. package/src/tools/delete-prompt-tag.ts +2 -4
  288. package/src/tools/delete-workflow.ts +1 -1
  289. package/src/tools/discover-evaluator-schema.ts +34 -25
  290. package/src/tools/discover-scenario-schema.ts +41 -21
  291. package/src/tools/discover-schema.ts +149 -27
  292. package/src/tools/experiment-run-status.ts +2 -8
  293. package/src/tools/format-dataset-mutation.ts +5 -10
  294. package/src/tools/format-run-plan.ts +7 -21
  295. package/src/tools/format-scenario.ts +2 -4
  296. package/src/tools/format-suite-details.ts +6 -12
  297. package/src/tools/get-agent.ts +15 -6
  298. package/src/tools/get-analytics-report.ts +23 -0
  299. package/src/tools/get-analytics.ts +52 -46
  300. package/src/tools/get-dataset.ts +34 -16
  301. package/src/tools/get-evaluator.ts +4 -11
  302. package/src/tools/get-experiment-results.ts +219 -186
  303. package/src/tools/get-project.ts +2 -4
  304. package/src/tools/get-prompt.ts +63 -84
  305. package/src/tools/get-run-plan.ts +4 -6
  306. package/src/tools/get-scenario.ts +4 -6
  307. package/src/tools/get-simulation-run.ts +27 -24
  308. package/src/tools/get-test-suite.ts +2 -5
  309. package/src/tools/get-trace.ts +16 -17
  310. package/src/tools/get-workflow.ts +1 -1
  311. package/src/tools/list-agents.ts +1 -1
  312. package/src/tools/list-analytics-reports.ts +27 -0
  313. package/src/tools/list-annotations.ts +8 -5
  314. package/src/tools/list-api-keys.ts +7 -2
  315. package/src/tools/list-dashboards.ts +20 -21
  316. package/src/tools/list-dataset-records.ts +8 -6
  317. package/src/tools/list-datasets.ts +12 -23
  318. package/src/tools/list-evaluators.ts +4 -8
  319. package/src/tools/list-experiment-runs.ts +5 -9
  320. package/src/tools/list-experiments.ts +5 -11
  321. package/src/tools/list-model-providers.ts +3 -5
  322. package/src/tools/list-projects.ts +2 -4
  323. package/src/tools/list-prompt-tags.ts +3 -3
  324. package/src/tools/list-prompts.ts +4 -8
  325. package/src/tools/list-run-plans.ts +2 -2
  326. package/src/tools/list-scenarios.ts +14 -15
  327. package/src/tools/list-simulation-runs.ts +8 -4
  328. package/src/tools/list-test-suites.ts +1 -1
  329. package/src/tools/list-trigger-fires.ts +17 -0
  330. package/src/tools/list-triggers.ts +39 -0
  331. package/src/tools/list-workflows.ts +2 -4
  332. package/src/tools/rename-prompt-tag.ts +1 -1
  333. package/src/tools/rename-test-suite.ts +2 -5
  334. package/src/tools/report-issue.ts +7 -9
  335. package/src/tools/rerun-run-plan.ts +2 -5
  336. package/src/tools/revoke-api-key.ts +2 -4
  337. package/src/tools/run-agent.ts +29 -26
  338. package/src/tools/run-experiment.ts +37 -34
  339. package/src/tools/run-plan.ts +4 -7
  340. package/src/tools/run-query.ts +47 -0
  341. package/src/tools/run-test-suite.ts +4 -7
  342. package/src/tools/run-workflow.ts +2 -5
  343. package/src/tools/search-traces.ts +161 -47
  344. package/src/tools/set-model-provider.ts +4 -6
  345. package/src/tools/test-agent.ts +2 -2
  346. package/src/tools/update-agent.ts +1 -1
  347. package/src/tools/update-dashboard-widget.ts +86 -0
  348. package/src/tools/update-dataset-record.ts +1 -1
  349. package/src/tools/update-dataset.ts +5 -7
  350. package/src/tools/update-evaluator.ts +3 -5
  351. package/src/tools/update-project.ts +1 -1
  352. package/src/tools/update-prompt.ts +44 -59
  353. package/src/tools/update-scenario.ts +5 -7
  354. package/src/tools/update-test-suite.ts +6 -11
  355. package/src/utils/date-parsing.ts +1 -1
  356. package/src/utils/escape-markdown.ts +3 -8
  357. package/src/utils/format-evaluations.ts +6 -6
  358. package/src/utils/markdown-table.ts +58 -0
  359. package/src/utils/trace-id-shape.ts +20 -0
  360. package/tsconfig.app.json +24 -0
  361. package/tsconfig.json +14 -12
  362. package/uv.lock +667 -341
  363. package/vitest.integration.config.js +10 -0
  364. package/vitest.scenario.config.js +9 -0
  365. package/.eslintrc.cjs +0 -36
  366. package/dist/chunk-AN4OUA4D.js +0 -86
  367. package/dist/chunk-GELJ2MQR.js +0 -226
  368. package/dist/chunk-K37ZNPXK.js +0 -24
  369. package/dist/chunk-S4GVX36W.js +0 -39
  370. package/dist/config.d.ts +0 -17
  371. package/dist/create-dashboard-Z76AIPLS.js +0 -19
  372. package/dist/create-mcp-server.d.ts +0 -10
  373. package/dist/delete-dashboard-LYMRPF27.js +0 -19
  374. package/dist/delete-evaluator-RKYC4TNB.js +0 -16
  375. package/dist/discover-evaluator-schema-ESSHQGPY.js +0 -1912
  376. package/dist/get-analytics-R6AZD3AW.js +0 -73
  377. package/dist/get-dashboard-P4WWVHFU.js +0 -30
  378. package/dist/get-dataset-AKTYSQDI.js +0 -49
  379. package/dist/index.d.ts +0 -2
  380. package/dist/langwatch-api-dashboards-2KJL2M42.js +0 -18
  381. package/dist/langwatch-api-secrets-OEBD6GBU.js +0 -41
  382. package/dist/langwatch-api-triggers-5E6DDGX6.js +0 -31
  383. package/dist/list-dashboards-Z3BZKDGF.js +0 -33
  384. package/dist/run-agent-CAGWGTYT.js +0 -72
  385. package/dist/search-traces-4XTJYXIK.js +0 -79
  386. package/src/__tests__/agent-tools.unit.test.ts +0 -257
  387. package/src/__tests__/all-tools.integration.test.ts +0 -1872
  388. package/src/__tests__/config.unit.test.ts +0 -202
  389. package/src/__tests__/datasets.integration.test.ts +0 -620
  390. package/src/__tests__/datasets.unit.test.ts +0 -244
  391. package/src/__tests__/date-parsing.unit.test.ts +0 -78
  392. package/src/__tests__/discover-evaluator-schema.unit.test.ts +0 -89
  393. package/src/__tests__/discover-schema.unit.test.ts +0 -118
  394. package/src/__tests__/docker.integration.test.ts +0 -318
  395. package/src/__tests__/documentation-fetch-security.integration.test.ts +0 -182
  396. package/src/__tests__/evaluator-tools.unit.test.ts +0 -262
  397. package/src/__tests__/experiment-list-tools.unit.test.ts +0 -157
  398. package/src/__tests__/experiment-status.unit.test.ts +0 -194
  399. package/src/__tests__/feature-map-tool-drift.unit.test.ts +0 -93
  400. package/src/__tests__/get-experiment-results.unit.test.ts +0 -313
  401. package/src/__tests__/http-security-origin.unit.test.ts +0 -97
  402. package/src/__tests__/http-security-sessions.unit.test.ts +0 -209
  403. package/src/__tests__/http-security-verifier.unit.test.ts +0 -248
  404. package/src/__tests__/http-server-allocation.integration.test.ts +0 -279
  405. package/src/__tests__/http-server-origin.integration.test.ts +0 -169
  406. package/src/__tests__/http-server-session-auth.integration.test.ts +0 -341
  407. package/src/__tests__/http-transport.integration.test.ts +0 -395
  408. package/src/__tests__/integration.integration.test.ts +0 -284
  409. package/src/__tests__/langwatch-api.unit.test.ts +0 -661
  410. package/src/__tests__/model-provider-tools.unit.test.ts +0 -190
  411. package/src/__tests__/project-api-key-tools.unit.test.ts +0 -451
  412. package/src/__tests__/prompt-response-types.unit.test.ts +0 -51
  413. package/src/__tests__/prompts.integration.test.ts +0 -736
  414. package/src/__tests__/public-http-request-security.integration.test.ts +0 -118
  415. package/src/__tests__/report-issue.integration.test.ts +0 -147
  416. package/src/__tests__/run-plan-tools.unit.test.ts +0 -565
  417. package/src/__tests__/scenario-tools.integration.test.ts +0 -286
  418. package/src/__tests__/scenario-tools.unit.test.ts +0 -207
  419. package/src/__tests__/schemas.unit.test.ts +0 -85
  420. package/src/__tests__/simulation-run-tools.unit.test.ts +0 -116
  421. package/src/__tests__/support/http-server-harness.ts +0 -138
  422. package/src/__tests__/test-suite-tools.unit.test.ts +0 -615
  423. package/src/__tests__/tools.integration.test.ts +0 -896
  424. package/src/schemas/filter-fields.ts +0 -119
  425. package/src/schemas/index.ts +0 -9
  426. package/src/tools/create-dashboard.ts +0 -7
  427. package/src/tools/delete-dashboard.ts +0 -7
  428. package/src/tools/get-dashboard.ts +0 -22
@@ -5,20 +5,21 @@ const MAX_DOCUMENTATION_BYTES = 2 * 1024 * 1024;
5
5
  const DOCUMENTATION_TIMEOUT_MS = 30_000;
6
6
  const DOCUMENTATION_CONTENT_TYPES = new Set(["text/markdown", "text/plain"]);
7
7
 
8
- const DOCUMENTATION_CONFIG: Record<DocumentationKind, { defaultPath: string; namespace: string }> = {
9
- langwatch: {
10
- defaultPath: "/docs/llms.txt",
11
- namespace: "/docs",
12
- },
13
- scenario: {
14
- defaultPath: "/scenario/llms.txt",
15
- namespace: "/scenario",
16
- },
17
- };
8
+ const DOCUMENTATION_CONFIG: Record<DocumentationKind, { defaultPath: string; namespace: string }> =
9
+ {
10
+ langwatch: {
11
+ defaultPath: "/docs/llms.txt",
12
+ namespace: "/docs",
13
+ },
14
+ scenario: {
15
+ defaultPath: "/scenario/llms.txt",
16
+ namespace: "/scenario",
17
+ },
18
+ };
18
19
 
19
20
  function documentationUrlError(namespace: string): Error {
20
21
  return new Error(
21
- `Only a trusted LangWatch documentation URL under ${TRUSTED_DOCUMENTATION_ORIGIN}${namespace}/ may be fetched`
22
+ `Only a trusted LangWatch documentation URL under ${TRUSTED_DOCUMENTATION_ORIGIN}${namespace}/ may be fetched`,
22
23
  );
23
24
  }
24
25
 
@@ -45,14 +46,14 @@ export function resolveDocumentationUrl(kind: DocumentationKind, input?: string)
45
46
  throw documentationUrlError(namespace);
46
47
  }
47
48
 
48
- if (
49
- url.protocol !== "https:" ||
50
- url.hostname !== "langwatch.ai" ||
51
- url.port !== "" ||
52
- url.username !== "" ||
53
- url.password !== "" ||
54
- !url.pathname.startsWith(`${namespace}/`)
55
- ) {
49
+ const isTrustedDocumentationUrl =
50
+ url.protocol === "https:" &&
51
+ url.hostname === "langwatch.ai" &&
52
+ url.port === "" &&
53
+ url.username === "" &&
54
+ url.password === "" &&
55
+ url.pathname.startsWith(`${namespace}/`);
56
+ if (!isTrustedDocumentationUrl) {
56
57
  throw documentationUrlError(namespace);
57
58
  }
58
59
 
@@ -67,7 +68,7 @@ export function resolveDocumentationUrl(kind: DocumentationKind, input?: string)
67
68
  export async function fetchDocumentation(
68
69
  kind: DocumentationKind,
69
70
  input?: string,
70
- fetchImplementation: typeof fetch = fetch
71
+ fetchImplementation: typeof fetch = fetch,
71
72
  ): Promise<string> {
72
73
  const url = resolveDocumentationUrl(kind, input);
73
74
  const response = await fetchImplementation(url, {
@@ -1,12 +1,7 @@
1
1
  /**
2
- * Security primitives for the standalone MCP HTTP server.
3
- *
4
- * The standalone server runs on developer machines and inside clusters with no
5
- * database of its own, so it cannot look an API key up locally the way the
6
- * in-app handler does. These helpers give it the same guarantees over HTTP:
7
- * an origin allowlist, API key verification against the LangWatch API with a
8
- * short-lived cache, per-IP rate limiting of failed authentication, and a
9
- * session store that expires idle sessions and caps concurrency per key.
2
+ * Security primitives for the standalone MCP HTTP server. The standalone server runs on
3
+ * developer machines and inside clusters with no database of its own, so it cannot look an API
4
+ * key up locally the way the in-app handler does.
10
5
  */
11
6
 
12
7
  import { createHmac, randomBytes, timingSafeEqual } from "node:crypto";
@@ -38,25 +33,18 @@ export function isLoopbackHost(host: string): boolean {
38
33
  // ---------------------------------------------------------------------------
39
34
 
40
35
  /**
41
- * Origins accepted without configuration.
42
- *
43
- * A DNS rebinding attack reaches the server through an attacker-controlled
44
- * hostname that resolves to a loopback address, so the browser sends that
45
- * attacker hostname in the Origin header. It can never send a loopback origin
46
- * for a page it did not actually load from this machine, which is the case the
47
- * server exists to serve.
36
+ * Origins accepted without configuration. A DNS rebinding attack reaches the server through an
37
+ * attacker-controlled hostname that resolves to a loopback address, so the browser sends that
38
+ * attacker hostname in the Origin header.
48
39
  */
49
40
  const ALWAYS_ALLOWED_ORIGIN_HOSTS = new Set(["localhost", "127.0.0.1", "::1"]);
50
41
 
51
42
  /**
52
- * Reduces an origin to `scheme://host[:port]` so comparisons ignore trailing
53
- * slashes, paths, and case differences. Returns null for anything that is not
54
- * a usable origin, including the opaque `null` origin sent by sandboxed frames
55
- * and `file://` pages.
43
+ * Reduces an origin to `scheme://host[:port]` so comparisons ignore trailing slashes, paths,
44
+ * and case differences. Returns null for anything that is not a usable origin, including the
45
+ * opaque `null` origin sent by sandboxed frames and `file://` pages.
56
46
  */
57
- function normalizeOrigin(
58
- origin: string
59
- ): { origin: string; hostname: string } | null {
47
+ function normalizeOrigin(origin: string): { origin: string; hostname: string } | null {
60
48
  let parsed: URL;
61
49
  try {
62
50
  parsed = new URL(origin.trim());
@@ -105,21 +93,15 @@ export function isOriginAllowed({
105
93
  // ---------------------------------------------------------------------------
106
94
 
107
95
  /**
108
- * Per-process key for deriving identifiers from API keys.
109
- *
110
- * Regenerated on every start. The derived values only index in-memory maps and
111
- * are only compared within one process, so they never need to be reproducible
112
- * across restarts. Keying the digest means that anything which exposes those
113
- * maps, a heap dump or debug output, still does not let a list of candidate
114
- * keys be confirmed offline the way a bare digest would.
96
+ * Per-process key for deriving identifiers from API keys. Regenerated on every start. The
97
+ * derived values only index in-memory maps and are only compared within one process, so they
98
+ * never need to be reproducible across restarts.
115
99
  */
116
100
  const KEY_DERIVATION_SECRET = randomBytes(32);
117
101
 
118
102
  /** Derives an opaque identifier from an API key, for use as a map key. */
119
103
  export function hashApiKey(apiKey: string): string {
120
- return createHmac("sha256", KEY_DERIVATION_SECRET)
121
- .update(apiKey)
122
- .digest("hex");
104
+ return createHmac("sha256", KEY_DERIVATION_SECRET).update(apiKey).digest("hex");
123
105
  }
124
106
 
125
107
  /** Constant-time API key comparison over fixed-length digests. */
@@ -130,20 +112,14 @@ export function apiKeysMatch({
130
112
  presentedKey: string;
131
113
  expectedKey: string;
132
114
  }): boolean {
133
- const presented = createHmac("sha256", KEY_DERIVATION_SECRET)
134
- .update(presentedKey)
135
- .digest();
136
- const expected = createHmac("sha256", KEY_DERIVATION_SECRET)
137
- .update(expectedKey)
138
- .digest();
115
+ const presented = createHmac("sha256", KEY_DERIVATION_SECRET).update(presentedKey).digest();
116
+ const expected = createHmac("sha256", KEY_DERIVATION_SECRET).update(expectedKey).digest();
139
117
  return timingSafeEqual(presented, expected);
140
118
  }
141
119
 
142
- // ---------------------------------------------------------------------------
143
- // Rate limiter (fixed window per client address)
144
- //
145
- // Counts reset in whole windows rather than sliding, so a burst straddling a
146
- // window boundary can reach twice maxRequests. That is acceptable for the
120
+ // --------------------------------------------------------------------------- Rate limiter
121
+ // (fixed window per client address) Counts reset in whole windows rather than sliding, so a
122
+ // burst straddling a window boundary can reach twice maxRequests. That is acceptable for the
147
123
  // failed-authentication and OAuth limits this backs.
148
124
  // ---------------------------------------------------------------------------
149
125
 
@@ -203,7 +179,7 @@ export function createRateLimiter({
203
179
  * project API key and returns only the identity of the project behind it, so
204
180
  * it answers "is this key real" without granting or costing anything else.
205
181
  */
206
- const VERIFY_PATH = "/api/me/project";
182
+ const VERIFY_PATH = "/api/v1/me/project";
207
183
 
208
184
  export interface ApiKeyVerifier {
209
185
  /** True when the LangWatch API recognises this key. */
@@ -214,6 +190,75 @@ export interface ApiKeyVerifier {
214
190
  clear(): void;
215
191
  }
216
192
 
193
+ type VerdictCache = Map<string, { valid: boolean; expiresAt: number }>;
194
+
195
+ function sweepVerdicts(cache: VerdictCache): void {
196
+ const now = Date.now();
197
+ for (const [key, entry] of cache) {
198
+ if (now >= entry.expiresAt) cache.delete(key);
199
+ }
200
+ }
201
+
202
+ function rememberVerdict({
203
+ cache,
204
+ hashed,
205
+ valid,
206
+ maxEntries,
207
+ ttlMs,
208
+ }: {
209
+ cache: VerdictCache;
210
+ hashed: string;
211
+ valid: boolean;
212
+ maxEntries: number;
213
+ ttlMs: number;
214
+ }): void {
215
+ if (cache.size >= maxEntries) {
216
+ sweepVerdicts(cache);
217
+ // Map iteration is insertion ordered, so the first key is the oldest.
218
+ while (cache.size >= maxEntries) {
219
+ const oldest = cache.keys().next();
220
+ if (oldest.done) break;
221
+ cache.delete(oldest.value);
222
+ }
223
+ }
224
+ cache.set(hashed, { valid, expiresAt: Date.now() + ttlMs });
225
+ }
226
+
227
+ /** The API's verdict on a key: true, false on 401/403, null when it could not answer. */
228
+ async function askVerifyEndpoint({
229
+ endpoint,
230
+ apiKey,
231
+ fetchImpl,
232
+ requestTimeoutMs,
233
+ }: {
234
+ endpoint: string;
235
+ apiKey: string;
236
+ fetchImpl: typeof fetch;
237
+ requestTimeoutMs: number;
238
+ }): Promise<boolean | null> {
239
+ let response: Response;
240
+ try {
241
+ response = await fetchImpl(`${endpoint}${VERIFY_PATH}`, {
242
+ method: "GET",
243
+ headers: { "X-Auth-Token": apiKey },
244
+ // An upstream that accepts the connection and never answers would
245
+ // otherwise leave the in-flight promise unsettled, parking every
246
+ // request for this key behind it until the socket dies.
247
+ signal: AbortSignal.timeout(requestTimeoutMs),
248
+ });
249
+ } catch {
250
+ return null;
251
+ }
252
+
253
+ // Only the status matters. Releasing the body returns the socket to the
254
+ // pool instead of holding it until garbage collection.
255
+ await response.body?.cancel().catch(() => undefined);
256
+
257
+ if (response.ok) return true;
258
+ if (response.status === 401 || response.status === 403) return false;
259
+ return null;
260
+ }
261
+
217
262
  export function createApiKeyVerifier({
218
263
  endpoint,
219
264
  positiveTtlMs = 60_000,
@@ -229,55 +274,20 @@ export function createApiKeyVerifier({
229
274
  requestTimeoutMs?: number;
230
275
  fetchImpl?: typeof fetch;
231
276
  }): ApiKeyVerifier {
232
- const cache = new Map<string, { valid: boolean; expiresAt: number }>();
277
+ const cache: VerdictCache = new Map();
233
278
  const inFlight = new Map<string, Promise<boolean>>();
234
279
 
235
- function sweep(): void {
236
- const now = Date.now();
237
- for (const [key, entry] of cache) {
238
- if (now >= entry.expiresAt) cache.delete(key);
239
- }
240
- }
241
-
242
- function remember(hashed: string, valid: boolean): void {
243
- if (cache.size >= maxEntries) {
244
- sweep();
245
- // Map iteration is insertion ordered, so the first key is the oldest.
246
- while (cache.size >= maxEntries) {
247
- const oldest = cache.keys().next();
248
- if (oldest.done) break;
249
- cache.delete(oldest.value);
250
- }
251
- }
252
- cache.set(hashed, {
280
+ const sweep = (): void => sweepVerdicts(cache);
281
+ const remember = (hashed: string, valid: boolean): void =>
282
+ rememberVerdict({
283
+ cache,
284
+ hashed,
253
285
  valid,
254
- expiresAt: Date.now() + (valid ? positiveTtlMs : negativeTtlMs),
286
+ maxEntries,
287
+ ttlMs: valid ? positiveTtlMs : negativeTtlMs,
255
288
  });
256
- }
257
-
258
- async function askApi(apiKey: string): Promise<boolean | null> {
259
- let response: Response;
260
- try {
261
- response = await fetchImpl(`${endpoint}${VERIFY_PATH}`, {
262
- method: "GET",
263
- headers: { "X-Auth-Token": apiKey },
264
- // An upstream that accepts the connection and never answers would
265
- // otherwise leave the in-flight promise unsettled, parking every
266
- // request for this key behind it until the socket dies.
267
- signal: AbortSignal.timeout(requestTimeoutMs),
268
- });
269
- } catch {
270
- return null;
271
- }
272
-
273
- // Only the status matters. Releasing the body returns the socket to the
274
- // pool instead of holding it until garbage collection.
275
- await response.body?.cancel().catch(() => undefined);
276
-
277
- if (response.ok) return true;
278
- if (response.status === 401 || response.status === 403) return false;
279
- return null;
280
- }
289
+ const askApi = (apiKey: string) =>
290
+ askVerifyEndpoint({ endpoint, apiKey, fetchImpl, requestTimeoutMs });
281
291
 
282
292
  return {
283
293
  async verify(apiKey) {
@@ -355,6 +365,67 @@ export interface SessionStore<TTransport> {
355
365
  readonly size: number;
356
366
  }
357
367
 
368
+ /** What a session store holds: the live sessions and how many each hashed key owns. */
369
+ interface SessionTable<TTransport> {
370
+ sessions: Map<string, SessionRecord<TTransport>>;
371
+ countByKey: Map<string, number>;
372
+ }
373
+
374
+ function releaseSlot({ countByKey }: { countByKey: Map<string, number> }, apiKey: string): void {
375
+ const hashed = hashApiKey(apiKey);
376
+ const next = (countByKey.get(hashed) ?? 1) - 1;
377
+ if (next <= 0) countByKey.delete(hashed);
378
+ else countByKey.set(hashed, next);
379
+ }
380
+
381
+ function dropSession<TTransport>({
382
+ table,
383
+ sessionId,
384
+ record,
385
+ }: {
386
+ table: SessionTable<TTransport>;
387
+ sessionId: string;
388
+ record: SessionRecord<TTransport>;
389
+ }): void {
390
+ table.sessions.delete(sessionId);
391
+ releaseSlot(table, record.apiKey);
392
+ }
393
+
394
+ /** Counts a slot for the key now; the session lands on commit, or the slot is given back. */
395
+ function reserveSlot<TTransport>({
396
+ table,
397
+ apiKey,
398
+ }: {
399
+ table: SessionTable<TTransport>;
400
+ apiKey: string;
401
+ }): SessionReservation<TTransport> {
402
+ const hashed = hashApiKey(apiKey);
403
+ table.countByKey.set(hashed, (table.countByKey.get(hashed) ?? 0) + 1);
404
+ let settled = false;
405
+
406
+ return {
407
+ commit({ sessionId, transport }) {
408
+ if (settled) return;
409
+ settled = true;
410
+
411
+ const existing = table.sessions.get(sessionId);
412
+ if (existing) dropSession({ table, sessionId, record: existing });
413
+
414
+ // The count already carries this slot from the reservation.
415
+ table.sessions.set(sessionId, {
416
+ transport,
417
+ apiKey,
418
+ lastActivityAt: Date.now(),
419
+ });
420
+ },
421
+ release() {
422
+ if (settled) return;
423
+ settled = true;
424
+ releaseSlot(table, apiKey);
425
+ },
426
+ };
427
+ }
428
+
358
429
  export function createSessionStore<TTransport>({
359
430
  maxAgeMs,
360
431
  closeTransport,
@@ -367,45 +438,10 @@ export function createSessionStore<TTransport>({
367
438
  const sessions = new Map<string, SessionRecord<TTransport>>();
368
439
  const countByKey = new Map<string, number>();
369
440
 
370
- function decrement(apiKey: string): void {
371
- const hashed = hashApiKey(apiKey);
372
- const next = (countByKey.get(hashed) ?? 1) - 1;
373
- if (next <= 0) countByKey.delete(hashed);
374
- else countByKey.set(hashed, next);
375
- }
376
-
377
- function drop(sessionId: string, record: SessionRecord<TTransport>): void {
378
- sessions.delete(sessionId);
379
- decrement(record.apiKey);
380
- }
381
-
382
- function reserve(apiKey: string): SessionReservation<TTransport> {
383
- const hashed = hashApiKey(apiKey);
384
- countByKey.set(hashed, (countByKey.get(hashed) ?? 0) + 1);
385
- let settled = false;
386
-
387
- return {
388
- commit({ sessionId, transport }) {
389
- if (settled) return;
390
- settled = true;
391
-
392
- const existing = sessions.get(sessionId);
393
- if (existing) drop(sessionId, existing);
394
-
395
- // The count already carries this slot from the reservation.
396
- sessions.set(sessionId, {
397
- transport,
398
- apiKey,
399
- lastActivityAt: Date.now(),
400
- });
401
- },
402
- release() {
403
- if (settled) return;
404
- settled = true;
405
- decrement(apiKey);
406
- },
407
- };
408
- }
441
+ const table: SessionTable<TTransport> = { sessions, countByKey };
442
+ const reserve = (apiKey: string) => reserveSlot({ table, apiKey });
443
+ const drop = (sessionId: string, record: SessionRecord<TTransport>) =>
444
+ dropSession({ table, sessionId, record });
409
445
 
410
446
  return {
411
447
  get(sessionId) {
@@ -454,13 +490,8 @@ export interface OAuthTokenEntry {
454
490
  }
455
491
 
456
492
  /**
457
- * Makes room for one more token before it is issued.
458
- *
459
- * Tokens outlive the requests that created them, so a key asking for them in a
460
- * loop would otherwise retain entries until each expired. The per-address rate
461
- * limit does not bound this on its own, because one key can be presented from
462
- * many addresses. Expired entries anywhere in the map are dropped on the way
463
- * past.
493
+ * Makes room for one more token before it is issued. Tokens outlive the requests that created
494
+ * them, so a key asking for them in a loop would otherwise retain entries until each expired.
464
495
  */
465
496
  export function admitOAuthToken({
466
497
  apiKey,