@langwatch/mcp-server 0.6.1 → 0.7.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 (258) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/Dockerfile +22 -0
  3. package/README.md +51 -8
  4. package/dist/{archive-scenario-YFD5THOR.js → archive-scenario-WJ2A7ETN.js} +4 -4
  5. package/dist/archive-suite-4JOM4SSL.js +15 -0
  6. package/dist/archive-suite-4JOM4SSL.js.map +1 -0
  7. package/dist/assign-prompt-tag-GL7EAAF3.js +19 -0
  8. package/dist/assign-prompt-tag-GL7EAAF3.js.map +1 -0
  9. package/dist/{chunk-6U4TCGFC.js → chunk-2GT7IFWK.js} +2 -2
  10. package/dist/{chunk-LLRQIF52.js → chunk-2WPLXEKF.js} +49 -5
  11. package/dist/chunk-2WPLXEKF.js.map +1 -0
  12. package/dist/chunk-4AUB2BIA.js +71 -0
  13. package/dist/chunk-4AUB2BIA.js.map +1 -0
  14. package/dist/chunk-BWWY5KEF.js +71 -0
  15. package/dist/chunk-BWWY5KEF.js.map +1 -0
  16. package/dist/{chunk-IX6QJKAD.js → chunk-CCWCIJJ3.js} +2 -2
  17. package/dist/chunk-E5JYYU3J.js +1567 -0
  18. package/dist/chunk-E5JYYU3J.js.map +1 -0
  19. package/dist/chunk-EGH5RPXM.js +25 -0
  20. package/dist/chunk-EGH5RPXM.js.map +1 -0
  21. package/dist/chunk-EUQDVOSU.js +39 -0
  22. package/dist/chunk-EUQDVOSU.js.map +1 -0
  23. package/dist/{chunk-5UOPNRXW.js → chunk-HYMIWO3U.js} +10 -3
  24. package/dist/{chunk-5UOPNRXW.js.map → chunk-HYMIWO3U.js.map} +1 -1
  25. package/dist/chunk-IE4M5OGE.js +42 -0
  26. package/dist/chunk-IE4M5OGE.js.map +1 -0
  27. package/dist/chunk-LUMEZEHL.js +48 -0
  28. package/dist/chunk-LUMEZEHL.js.map +1 -0
  29. package/dist/chunk-NUFPTHKU.js +9 -0
  30. package/dist/chunk-NUFPTHKU.js.map +1 -0
  31. package/dist/chunk-WQG2DFHX.js +86 -0
  32. package/dist/chunk-WQG2DFHX.js.map +1 -0
  33. package/dist/chunk-WVI5CJ6S.js +21 -0
  34. package/dist/chunk-WVI5CJ6S.js.map +1 -0
  35. package/dist/chunk-ZVS4UKN2.js +35 -0
  36. package/dist/chunk-ZVS4UKN2.js.map +1 -0
  37. package/dist/config.d.ts +17 -0
  38. package/dist/config.js +13 -0
  39. package/dist/create-agent-MBOOT3XK.js +23 -0
  40. package/dist/create-agent-MBOOT3XK.js.map +1 -0
  41. package/dist/create-annotation-XMV6A7ED.js +24 -0
  42. package/dist/create-annotation-XMV6A7ED.js.map +1 -0
  43. package/dist/create-dashboard-VRKLFVZJ.js +18 -0
  44. package/dist/create-dashboard-VRKLFVZJ.js.map +1 -0
  45. package/dist/create-dataset-7HQPZCRE.js +22 -0
  46. package/dist/create-dataset-7HQPZCRE.js.map +1 -0
  47. package/dist/create-dataset-records-2NIVFYTV.js +22 -0
  48. package/dist/create-dataset-records-2NIVFYTV.js.map +1 -0
  49. package/dist/{create-evaluator-E5X5ZP3B.js → create-evaluator-WYFRFLRC.js} +4 -4
  50. package/dist/create-mcp-server.d.ts +8 -0
  51. package/dist/create-mcp-server.js +8 -0
  52. package/dist/create-mcp-server.js.map +1 -0
  53. package/dist/{create-prompt-7Z35MIL6.js → create-prompt-IEN7O6JT.js} +7 -4
  54. package/dist/create-prompt-IEN7O6JT.js.map +1 -0
  55. package/dist/create-prompt-tag-CAEZ45VO.js +17 -0
  56. package/dist/create-prompt-tag-CAEZ45VO.js.map +1 -0
  57. package/dist/{create-scenario-DIMPJRPY.js → create-scenario-6YHSMGLP.js} +4 -4
  58. package/dist/create-suite-H5XOFDE5.js +38 -0
  59. package/dist/create-suite-H5XOFDE5.js.map +1 -0
  60. package/dist/delete-agent-JKTNBWAA.js +18 -0
  61. package/dist/delete-agent-JKTNBWAA.js.map +1 -0
  62. package/dist/delete-annotation-ACMDV6QO.js +17 -0
  63. package/dist/delete-annotation-ACMDV6QO.js.map +1 -0
  64. package/dist/delete-dashboard-VJSR7CRU.js +18 -0
  65. package/dist/delete-dashboard-VJSR7CRU.js.map +1 -0
  66. package/dist/delete-dataset-RTTTWP4D.js +20 -0
  67. package/dist/delete-dataset-RTTTWP4D.js.map +1 -0
  68. package/dist/delete-dataset-records-KNQTIPQK.js +21 -0
  69. package/dist/delete-dataset-records-KNQTIPQK.js.map +1 -0
  70. package/dist/delete-evaluator-7FVC5WM4.js +15 -0
  71. package/dist/delete-evaluator-7FVC5WM4.js.map +1 -0
  72. package/dist/delete-prompt-tag-ZMZUAGFW.js +17 -0
  73. package/dist/delete-prompt-tag-ZMZUAGFW.js.map +1 -0
  74. package/dist/delete-workflow-EYQ3T4RQ.js +17 -0
  75. package/dist/delete-workflow-EYQ3T4RQ.js.map +1 -0
  76. package/dist/get-agent-E3QSTVKG.js +28 -0
  77. package/dist/get-agent-E3QSTVKG.js.map +1 -0
  78. package/dist/{get-analytics-4YJW4S5L.js → get-analytics-2OHV763I.js} +22 -5
  79. package/dist/get-analytics-2OHV763I.js.map +1 -0
  80. package/dist/get-dashboard-PC7JDHH6.js +29 -0
  81. package/dist/get-dashboard-PC7JDHH6.js.map +1 -0
  82. package/dist/get-dataset-H5IK5FVU.js +48 -0
  83. package/dist/get-dataset-H5IK5FVU.js.map +1 -0
  84. package/dist/{get-evaluator-WDEH2F7M.js → get-evaluator-UWVUPYTM.js} +4 -4
  85. package/dist/{get-prompt-F6PDVC76.js → get-prompt-WZUOLLQC.js} +4 -4
  86. package/dist/get-prompt-WZUOLLQC.js.map +1 -0
  87. package/dist/{get-scenario-H24ZYNT5.js → get-scenario-Q6LCNUYN.js} +4 -4
  88. package/dist/get-simulation-run-Z2XKO256.js +56 -0
  89. package/dist/get-simulation-run-Z2XKO256.js.map +1 -0
  90. package/dist/get-suite-BXK5TN7P.js +44 -0
  91. package/dist/get-suite-BXK5TN7P.js.map +1 -0
  92. package/dist/{get-trace-27USKGO7.js → get-trace-4EJAYVBG.js} +3 -3
  93. package/dist/get-workflow-O34QJLHD.js +25 -0
  94. package/dist/get-workflow-O34QJLHD.js.map +1 -0
  95. package/dist/http-server-ISHF7NJX.js +224 -0
  96. package/dist/http-server-ISHF7NJX.js.map +1 -0
  97. package/dist/index.js +61109 -31519
  98. package/dist/index.js.map +1 -1
  99. package/dist/langwatch-api-annotations-ERBG27RF.js +17 -0
  100. package/dist/langwatch-api-annotations-ERBG27RF.js.map +1 -0
  101. package/dist/langwatch-api-dashboards-G6TR7VVO.js +17 -0
  102. package/dist/langwatch-api-dashboards-G6TR7VVO.js.map +1 -0
  103. package/dist/langwatch-api-monitors-WSXZLS2B.js +52 -0
  104. package/dist/langwatch-api-monitors-WSXZLS2B.js.map +1 -0
  105. package/dist/langwatch-api-secrets-RR5DKG2X.js +40 -0
  106. package/dist/langwatch-api-secrets-RR5DKG2X.js.map +1 -0
  107. package/dist/langwatch-api-triggers-2RTNSWQY.js +30 -0
  108. package/dist/langwatch-api-triggers-2RTNSWQY.js.map +1 -0
  109. package/dist/langwatch-api-workflows-VJRIIDZ7.js +15 -0
  110. package/dist/langwatch-api-workflows-VJRIIDZ7.js.map +1 -0
  111. package/dist/list-agents-AW3P6FJV.js +32 -0
  112. package/dist/list-agents-AW3P6FJV.js.map +1 -0
  113. package/dist/list-annotations-QNCGWPQ3.js +33 -0
  114. package/dist/list-annotations-QNCGWPQ3.js.map +1 -0
  115. package/dist/list-dashboards-R7RK2IXL.js +32 -0
  116. package/dist/list-dashboards-R7RK2IXL.js.map +1 -0
  117. package/dist/list-dataset-records-WVCSQP7P.js +39 -0
  118. package/dist/list-dataset-records-WVCSQP7P.js.map +1 -0
  119. package/dist/list-datasets-KEXRLDEU.js +60 -0
  120. package/dist/list-datasets-KEXRLDEU.js.map +1 -0
  121. package/dist/{list-evaluators-KRGI72EH.js → list-evaluators-7R2LB4JH.js} +4 -4
  122. package/dist/{list-model-providers-A5YCFTPI.js → list-model-providers-OBYJCKBX.js} +4 -4
  123. package/dist/list-prompt-tags-6LY3ZPYI.js +23 -0
  124. package/dist/list-prompt-tags-6LY3ZPYI.js.map +1 -0
  125. package/dist/{list-prompts-LKJSE7XN.js → list-prompts-VWEZ4CDE.js} +3 -3
  126. package/dist/{list-scenarios-ZK5CMGC4.js → list-scenarios-TXD4W2ZJ.js} +4 -4
  127. package/dist/list-simulation-runs-RHPOLMFI.js +43 -0
  128. package/dist/list-simulation-runs-RHPOLMFI.js.map +1 -0
  129. package/dist/list-suites-FSGBX27Z.js +42 -0
  130. package/dist/list-suites-FSGBX27Z.js.map +1 -0
  131. package/dist/list-workflows-VRGEZ3EN.js +35 -0
  132. package/dist/list-workflows-VRGEZ3EN.js.map +1 -0
  133. package/dist/rename-prompt-tag-VOJJLX4M.js +18 -0
  134. package/dist/rename-prompt-tag-VOJJLX4M.js.map +1 -0
  135. package/dist/run-agent-KTCC7TB7.js +36 -0
  136. package/dist/run-agent-KTCC7TB7.js.map +1 -0
  137. package/dist/run-evaluation-2X3V3Q6L.js +64 -0
  138. package/dist/run-evaluation-2X3V3Q6L.js.map +1 -0
  139. package/dist/run-suite-BTMHGI2D.js +29 -0
  140. package/dist/run-suite-BTMHGI2D.js.map +1 -0
  141. package/dist/run-workflow-IGAYGKJL.js +36 -0
  142. package/dist/run-workflow-IGAYGKJL.js.map +1 -0
  143. package/dist/{search-traces-SOKAAMAR.js → search-traces-VYFUIMXH.js} +3 -3
  144. package/dist/{set-model-provider-7MGULZDH.js → set-model-provider-W5IUI326.js} +4 -4
  145. package/dist/update-agent-4BB7EYLR.js +20 -0
  146. package/dist/update-agent-4BB7EYLR.js.map +1 -0
  147. package/dist/update-dataset-CXBKZ4W3.js +22 -0
  148. package/dist/update-dataset-CXBKZ4W3.js.map +1 -0
  149. package/dist/update-dataset-record-HOHAMHXG.js +19 -0
  150. package/dist/update-dataset-record-HOHAMHXG.js.map +1 -0
  151. package/dist/{update-evaluator-A3XINFLJ.js → update-evaluator-IVOR6FTR.js} +4 -4
  152. package/dist/{update-prompt-IW7X2UQM.js → update-prompt-OWQAKE7K.js} +5 -3
  153. package/dist/update-prompt-OWQAKE7K.js.map +1 -0
  154. package/dist/{update-scenario-ZT7TOBFR.js → update-scenario-GDFVILPN.js} +4 -4
  155. package/dist/update-suite-BXYUYSPT.js +39 -0
  156. package/dist/update-suite-BXYUYSPT.js.map +1 -0
  157. package/package.json +17 -3
  158. package/src/__tests__/all-tools.integration.test.ts +344 -0
  159. package/src/__tests__/config.unit.test.ts +81 -1
  160. package/src/__tests__/datasets.integration.test.ts +603 -0
  161. package/src/__tests__/datasets.unit.test.ts +240 -0
  162. package/src/__tests__/docker.integration.test.ts +234 -0
  163. package/src/__tests__/http-transport.integration.test.ts +397 -0
  164. package/src/__tests__/langwatch-api.unit.test.ts +174 -0
  165. package/src/__tests__/tools.unit.test.ts +177 -0
  166. package/src/config.d.ts +15 -0
  167. package/src/config.ts +83 -7
  168. package/src/create-mcp-server.d.ts +6 -0
  169. package/src/create-mcp-server.ts +1799 -0
  170. package/src/http-server.ts +329 -0
  171. package/src/index.ts +22 -593
  172. package/src/langwatch-api-agents.ts +108 -0
  173. package/src/langwatch-api-annotations.ts +49 -0
  174. package/src/langwatch-api-dashboards.ts +48 -0
  175. package/src/langwatch-api-datasets.ts +191 -0
  176. package/src/langwatch-api-evaluators.ts +10 -0
  177. package/src/langwatch-api-monitors.ts +82 -0
  178. package/src/langwatch-api-secrets.ts +48 -0
  179. package/src/langwatch-api-simulation-runs.ts +51 -0
  180. package/src/langwatch-api-suites.ts +118 -0
  181. package/src/langwatch-api-triggers.ts +48 -0
  182. package/src/langwatch-api-workflows.ts +41 -0
  183. package/src/langwatch-api.ts +60 -2
  184. package/src/schemas/create-dataset.ts +48 -0
  185. package/src/schemas/index.ts +1 -0
  186. package/src/tools/archive-suite.ts +12 -0
  187. package/src/tools/assign-prompt-tag.ts +16 -0
  188. package/src/tools/create-agent.ts +15 -0
  189. package/src/tools/create-annotation.ts +18 -0
  190. package/src/tools/create-dashboard.ts +7 -0
  191. package/src/tools/create-dataset-records.ts +23 -0
  192. package/src/tools/create-dataset.ts +22 -0
  193. package/src/tools/create-prompt-tag.ts +12 -0
  194. package/src/tools/create-prompt.ts +4 -0
  195. package/src/tools/create-suite.ts +42 -0
  196. package/src/tools/delete-agent.ts +7 -0
  197. package/src/tools/delete-annotation.ts +7 -0
  198. package/src/tools/delete-dashboard.ts +7 -0
  199. package/src/tools/delete-dataset-records.ts +22 -0
  200. package/src/tools/delete-dataset.ts +20 -0
  201. package/src/tools/delete-evaluator.ts +12 -0
  202. package/src/tools/delete-prompt-tag.ts +12 -0
  203. package/src/tools/delete-workflow.ts +7 -0
  204. package/src/tools/format-dataset-mutation.ts +25 -0
  205. package/src/tools/get-agent.ts +21 -0
  206. package/src/tools/get-analytics.ts +30 -3
  207. package/src/tools/get-dashboard.ts +22 -0
  208. package/src/tools/get-dataset.ts +63 -0
  209. package/src/tools/get-prompt.ts +2 -1
  210. package/src/tools/get-simulation-run.ts +60 -0
  211. package/src/tools/get-suite.ts +47 -0
  212. package/src/tools/get-workflow.ts +17 -0
  213. package/src/tools/list-agents.ts +27 -0
  214. package/src/tools/list-annotations.ts +33 -0
  215. package/src/tools/list-dashboards.ts +27 -0
  216. package/src/tools/list-dataset-records.ts +46 -0
  217. package/src/tools/list-datasets.ts +80 -0
  218. package/src/tools/list-prompt-tags.ts +21 -0
  219. package/src/tools/list-simulation-runs.ts +49 -0
  220. package/src/tools/list-suites.ts +43 -0
  221. package/src/tools/list-workflows.ts +31 -0
  222. package/src/tools/rename-prompt-tag.ts +14 -0
  223. package/src/tools/run-agent.ts +36 -0
  224. package/src/tools/run-evaluation.ts +87 -0
  225. package/src/tools/run-suite.ts +28 -0
  226. package/src/tools/run-workflow.ts +36 -0
  227. package/src/tools/update-agent.ts +13 -0
  228. package/src/tools/update-dataset-record.ts +21 -0
  229. package/src/tools/update-dataset.ts +23 -0
  230. package/src/tools/update-prompt.ts +3 -0
  231. package/src/tools/update-suite.ts +44 -0
  232. package/src/utils/escape-markdown.ts +12 -0
  233. package/tsup.config.ts +5 -1
  234. package/dist/chunk-AAQNA53E.js +0 -28
  235. package/dist/chunk-AAQNA53E.js.map +0 -1
  236. package/dist/chunk-LLRQIF52.js.map +0 -1
  237. package/dist/config-FIQWQRUB.js +0 -11
  238. package/dist/create-prompt-7Z35MIL6.js.map +0 -1
  239. package/dist/get-analytics-4YJW4S5L.js.map +0 -1
  240. package/dist/get-prompt-F6PDVC76.js.map +0 -1
  241. package/dist/update-prompt-IW7X2UQM.js.map +0 -1
  242. /package/dist/{archive-scenario-YFD5THOR.js.map → archive-scenario-WJ2A7ETN.js.map} +0 -0
  243. /package/dist/{chunk-6U4TCGFC.js.map → chunk-2GT7IFWK.js.map} +0 -0
  244. /package/dist/{chunk-IX6QJKAD.js.map → chunk-CCWCIJJ3.js.map} +0 -0
  245. /package/dist/{config-FIQWQRUB.js.map → config.js.map} +0 -0
  246. /package/dist/{create-evaluator-E5X5ZP3B.js.map → create-evaluator-WYFRFLRC.js.map} +0 -0
  247. /package/dist/{create-scenario-DIMPJRPY.js.map → create-scenario-6YHSMGLP.js.map} +0 -0
  248. /package/dist/{get-evaluator-WDEH2F7M.js.map → get-evaluator-UWVUPYTM.js.map} +0 -0
  249. /package/dist/{get-scenario-H24ZYNT5.js.map → get-scenario-Q6LCNUYN.js.map} +0 -0
  250. /package/dist/{get-trace-27USKGO7.js.map → get-trace-4EJAYVBG.js.map} +0 -0
  251. /package/dist/{list-evaluators-KRGI72EH.js.map → list-evaluators-7R2LB4JH.js.map} +0 -0
  252. /package/dist/{list-model-providers-A5YCFTPI.js.map → list-model-providers-OBYJCKBX.js.map} +0 -0
  253. /package/dist/{list-prompts-LKJSE7XN.js.map → list-prompts-VWEZ4CDE.js.map} +0 -0
  254. /package/dist/{list-scenarios-ZK5CMGC4.js.map → list-scenarios-TXD4W2ZJ.js.map} +0 -0
  255. /package/dist/{search-traces-SOKAAMAR.js.map → search-traces-VYFUIMXH.js.map} +0 -0
  256. /package/dist/{set-model-provider-7MGULZDH.js.map → set-model-provider-W5IUI326.js.map} +0 -0
  257. /package/dist/{update-evaluator-A3XINFLJ.js.map → update-evaluator-IVOR6FTR.js.map} +0 -0
  258. /package/dist/{update-scenario-ZT7TOBFR.js.map → update-scenario-GDFVILPN.js.map} +0 -0
@@ -8,6 +8,11 @@ vi.mock("../langwatch-api.js", () => ({
8
8
  getPrompt: vi.fn(),
9
9
  createPrompt: vi.fn(),
10
10
  updatePrompt: vi.fn(),
11
+ assignPromptTag: vi.fn(),
12
+ listPromptTags: vi.fn(),
13
+ createPromptTag: vi.fn(),
14
+ renamePromptTag: vi.fn(),
15
+ deletePromptTag: vi.fn(),
11
16
  }));
12
17
 
13
18
  import {
@@ -18,6 +23,11 @@ import {
18
23
  getPrompt,
19
24
  createPrompt,
20
25
  updatePrompt,
26
+ assignPromptTag,
27
+ listPromptTags,
28
+ createPromptTag,
29
+ renamePromptTag,
30
+ deletePromptTag,
21
31
  type PromptSummary,
22
32
  } from "../langwatch-api.js";
23
33
 
@@ -28,6 +38,11 @@ import { handleListPrompts } from "../tools/list-prompts.js";
28
38
  import { handleGetPrompt } from "../tools/get-prompt.js";
29
39
  import { handleCreatePrompt } from "../tools/create-prompt.js";
30
40
  import { handleUpdatePrompt } from "../tools/update-prompt.js";
41
+ import { handleAssignPromptTag } from "../tools/assign-prompt-tag.js";
42
+ import { handleListPromptTags } from "../tools/list-prompt-tags.js";
43
+ import { handleCreatePromptTag } from "../tools/create-prompt-tag.js";
44
+ import { handleRenamePromptTag } from "../tools/rename-prompt-tag.js";
45
+ import { handleDeletePromptTag } from "../tools/delete-prompt-tag.js";
31
46
 
32
47
  const mockSearchTraces = vi.mocked(searchTraces);
33
48
  const mockGetTraceById = vi.mocked(getTraceById);
@@ -36,6 +51,11 @@ const mockListPrompts = vi.mocked(listPrompts);
36
51
  const mockGetPrompt = vi.mocked(getPrompt);
37
52
  const mockCreatePrompt = vi.mocked(createPrompt);
38
53
  const mockUpdatePrompt = vi.mocked(updatePrompt);
54
+ const mockAssignPromptTag = vi.mocked(assignPromptTag);
55
+ const mockListPromptTags = vi.mocked(listPromptTags);
56
+ const mockCreatePromptTag = vi.mocked(createPromptTag);
57
+ const mockRenamePromptTag = vi.mocked(renamePromptTag);
58
+ const mockDeletePromptTag = vi.mocked(deletePromptTag);
39
59
 
40
60
  beforeEach(() => {
41
61
  vi.clearAllMocks();
@@ -390,6 +410,31 @@ describe("handleGetAnalytics()", () => {
390
410
  expect(result).toContain("Grouped by: metadata.model");
391
411
  });
392
412
 
413
+ it("formats grouped data with group column", async () => {
414
+ mockGetAnalytics.mockResolvedValue({
415
+ currentPeriod: [
416
+ {
417
+ date: "2024-01-01",
418
+ "sentiment.thumbs_up_down": {
419
+ "Thumbs Up": { "0/metadata.trace_id/cardinality": 10 },
420
+ "Thumbs Down": { "0/metadata.trace_id/cardinality": 5 },
421
+ },
422
+ },
423
+ ],
424
+ previousPeriod: [],
425
+ });
426
+
427
+ const result = await handleGetAnalytics({
428
+ metric: "metadata.trace_id",
429
+ aggregation: "cardinality",
430
+ groupBy: "sentiment.thumbs_up_down",
431
+ });
432
+
433
+ expect(result).toContain("| Date | Group | Value |");
434
+ expect(result).toContain("| 2024-01-01 | Thumbs Up | 10 |");
435
+ expect(result).toContain("| 2024-01-01 | Thumbs Down | 5 |");
436
+ });
437
+
393
438
  it("uses the specified aggregation", async () => {
394
439
  mockGetAnalytics.mockResolvedValue({ currentPeriod: [], previousPeriod: [] });
395
440
 
@@ -721,3 +766,135 @@ describe("handleUpdatePrompt()", () => {
721
766
  });
722
767
  });
723
768
  });
769
+
770
+ describe("handleGetPrompt() with tag options", () => {
771
+ describe("when called with a tag", () => {
772
+ it("passes tag option to the API", async () => {
773
+ mockGetPrompt.mockResolvedValue({ name: "Test" });
774
+
775
+ await handleGetPrompt({ idOrHandle: "pizza-prompt", tag: "production" });
776
+
777
+ expect(mockGetPrompt).toHaveBeenCalledWith("pizza-prompt", { version: undefined, tag: "production" });
778
+ });
779
+ });
780
+ });
781
+
782
+ describe("handleCreatePrompt() with tags", () => {
783
+ describe("when called with tags", () => {
784
+ it("includes tags in the output", async () => {
785
+ mockCreatePrompt.mockResolvedValue({ id: "p1", handle: "test" });
786
+
787
+ const result = await handleCreatePrompt({
788
+ name: "Test",
789
+ messages: [{ role: "system", content: "hi" }],
790
+ model: "openai/gpt-5-mini",
791
+ tags: ["production"],
792
+ });
793
+
794
+ expect(result).toContain("**Tags**: production");
795
+ });
796
+ });
797
+ });
798
+
799
+ describe("handleUpdatePrompt() with tags", () => {
800
+ describe("when called with tags", () => {
801
+ it("includes tags in the output", async () => {
802
+ mockUpdatePrompt.mockResolvedValue({ id: "p1", handle: "test", latestVersionNumber: 2 });
803
+
804
+ const result = await handleUpdatePrompt({
805
+ idOrHandle: "test",
806
+ commitMessage: "add tags",
807
+ tags: ["staging"],
808
+ });
809
+
810
+ expect(result).toContain("**Tags**: staging");
811
+ });
812
+ });
813
+ });
814
+
815
+ describe("handleAssignPromptTag()", () => {
816
+ describe("when tag is assigned successfully", () => {
817
+ it("returns a confirmation with prompt, tag, and version", async () => {
818
+ mockAssignPromptTag.mockResolvedValue({ success: true });
819
+
820
+ const result = await handleAssignPromptTag({
821
+ idOrHandle: "pizza-prompt",
822
+ tag: "production",
823
+ versionId: "v123",
824
+ });
825
+
826
+ expect(result).toContain("Tag assigned successfully!");
827
+ expect(result).toContain("**Prompt**: pizza-prompt");
828
+ expect(result).toContain("**Tag**: production");
829
+ expect(result).toContain("**Version ID**: v123");
830
+ });
831
+ });
832
+ });
833
+
834
+ describe("handleListPromptTags()", () => {
835
+ describe("when tags exist", () => {
836
+ it("formats tags as a markdown list", async () => {
837
+ mockListPromptTags.mockResolvedValue([
838
+ { id: "1", name: "production", createdAt: "2024-01-01" },
839
+ { id: "2", name: "staging" },
840
+ ]);
841
+
842
+ const result = await handleListPromptTags();
843
+
844
+ expect(result).toContain("# Prompt Tags");
845
+ expect(result).toContain("**production**");
846
+ expect(result).toContain("**staging**");
847
+ });
848
+ });
849
+
850
+ describe("when no tags exist", () => {
851
+ it("returns an empty-state message mentioning latest", async () => {
852
+ mockListPromptTags.mockResolvedValue([]);
853
+
854
+ const result = await handleListPromptTags();
855
+
856
+ expect(result).toContain("No prompt tags found");
857
+ expect(result).toContain("`latest`");
858
+ });
859
+ });
860
+ });
861
+
862
+ describe("handleCreatePromptTag()", () => {
863
+ describe("when tag is created successfully", () => {
864
+ it("returns a confirmation with the tag name", async () => {
865
+ mockCreatePromptTag.mockResolvedValue({ id: "t1", name: "canary" });
866
+
867
+ const result = await handleCreatePromptTag({ name: "canary" });
868
+
869
+ expect(result).toContain("Tag created successfully!");
870
+ expect(result).toContain("**Name**: canary");
871
+ });
872
+ });
873
+ });
874
+
875
+ describe("handleRenamePromptTag()", () => {
876
+ describe("when tag is renamed successfully", () => {
877
+ it("returns a confirmation with old and new names", async () => {
878
+ mockRenamePromptTag.mockResolvedValue({ id: "t1", name: "preview" });
879
+
880
+ const result = await handleRenamePromptTag({ tag: "canary", name: "preview" });
881
+
882
+ expect(result).toContain("Tag renamed successfully!");
883
+ expect(result).toContain("**Old name**: canary");
884
+ expect(result).toContain("**New name**: preview");
885
+ });
886
+ });
887
+ });
888
+
889
+ describe("handleDeletePromptTag()", () => {
890
+ describe("when tag is deleted successfully", () => {
891
+ it("returns a confirmation with the tag name", async () => {
892
+ mockDeletePromptTag.mockResolvedValue(null);
893
+
894
+ const result = await handleDeletePromptTag({ tag: "canary" });
895
+
896
+ expect(result).toContain("Tag deleted successfully!");
897
+ expect(result).toContain("**Tag**: canary");
898
+ });
899
+ });
900
+ });
@@ -0,0 +1,15 @@
1
+ export interface McpConfig {
2
+ apiKey: string | undefined;
3
+ endpoint: string;
4
+ }
5
+
6
+ export declare function initConfig(args: {
7
+ apiKey?: string;
8
+ endpoint?: string;
9
+ }): void;
10
+
11
+ export declare function getConfig(): McpConfig;
12
+
13
+ export declare function requireApiKey(): string;
14
+
15
+ export declare function runWithConfig<T>(config: McpConfig, fn: () => T): T;
package/src/config.ts CHANGED
@@ -1,12 +1,56 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+
1
3
  export interface McpConfig {
2
4
  apiKey: string | undefined;
3
5
  endpoint: string;
4
6
  }
5
7
 
6
- let config: McpConfig | undefined;
8
+ // ---------------------------------------------------------------------------
9
+ // Singleton storage on globalThis
10
+ //
11
+ // When the langwatch app (CJS, no "type": "module") imports this module,
12
+ // tsx creates a CJS-cached copy. When mcp-server's own ESM dist chunks
13
+ // do `await import("./search-traces-*.js")`, Node creates a separate
14
+ // ESM-cached copy. Module-level variables are NOT shared between them.
15
+ //
16
+ // By storing config on globalThis, both CJS and ESM instances read/write
17
+ // the same object, fixing the "Config not initialized" error that occurs
18
+ // when initConfig() runs on the CJS side but tool handlers execute on
19
+ // the ESM side.
20
+ // ---------------------------------------------------------------------------
21
+
22
+ const GLOBAL_KEY = "__langwatch_mcp_config" as const;
23
+ const STORAGE_KEY = "__langwatch_mcp_config_storage" as const;
24
+
25
+ interface McpGlobalState {
26
+ globalConfig: McpConfig | undefined;
27
+ configStorage: AsyncLocalStorage<McpConfig>;
28
+ }
29
+
30
+ function getGlobalState(): McpGlobalState {
31
+ const g = globalThis as unknown as Record<string, unknown>;
32
+ if (!g[GLOBAL_KEY]) {
33
+ g[GLOBAL_KEY] = undefined;
34
+ }
35
+ if (!g[STORAGE_KEY]) {
36
+ g[STORAGE_KEY] = new AsyncLocalStorage<McpConfig>();
37
+ }
38
+ return {
39
+ get globalConfig() {
40
+ return g[GLOBAL_KEY] as McpConfig | undefined;
41
+ },
42
+ set globalConfig(val: McpConfig | undefined) {
43
+ g[GLOBAL_KEY] = val;
44
+ },
45
+ get configStorage() {
46
+ return g[STORAGE_KEY] as AsyncLocalStorage<McpConfig>;
47
+ },
48
+ };
49
+ }
7
50
 
8
51
  export function initConfig(args: { apiKey?: string; endpoint?: string }): void {
9
- config = {
52
+ const state = getGlobalState();
53
+ state.globalConfig = {
10
54
  apiKey: args.apiKey || process.env.LANGWATCH_API_KEY,
11
55
  endpoint:
12
56
  args.endpoint ||
@@ -15,17 +59,49 @@ export function initConfig(args: { apiKey?: string; endpoint?: string }): void {
15
59
  };
16
60
  }
17
61
 
62
+ /**
63
+ * Returns the current config: the per-request scoped config if inside
64
+ * a `runWithConfig()` callback, otherwise the global config.
65
+ */
18
66
  export function getConfig(): McpConfig {
19
- if (!config) throw new Error("Config not initialized");
20
- return config;
67
+ const state = getGlobalState();
68
+ const scoped = state.configStorage.getStore();
69
+ if (scoped) return scoped;
70
+ if (!state.globalConfig) {
71
+ console.error(
72
+ "[MCP config] getConfig() failed: globalConfig is null, no scoped config active. " +
73
+ "Was initConfig() called? Stack:",
74
+ new Error().stack,
75
+ );
76
+ throw new Error("Config not initialized");
77
+ }
78
+ return state.globalConfig;
21
79
  }
22
80
 
23
81
  export function requireApiKey(): string {
24
- const { apiKey } = getConfig();
25
- if (!apiKey) {
82
+ const config = getConfig();
83
+ if (!config.apiKey) {
84
+ const state = getGlobalState();
85
+ const hasScoped = !!state.configStorage.getStore();
86
+ console.error(
87
+ "[MCP config] requireApiKey() failed: apiKey is undefined. " +
88
+ `scopedConfig=${hasScoped}, endpoint=${config.endpoint}. ` +
89
+ "In HTTP mode, the API key should be set per-session via runWithConfig(). Stack:",
90
+ new Error().stack,
91
+ );
26
92
  throw new Error(
27
93
  "LANGWATCH_API_KEY is required. Set it via --apiKey flag or LANGWATCH_API_KEY environment variable."
28
94
  );
29
95
  }
30
- return apiKey;
96
+ return config.apiKey;
97
+ }
98
+
99
+ /**
100
+ * Runs `fn` with a per-request scoped config. All calls to `getConfig()`
101
+ * and `requireApiKey()` within `fn` (including async continuations) will
102
+ * see the provided config instead of the global one.
103
+ */
104
+ export function runWithConfig<T>(config: McpConfig, fn: () => T): T {
105
+ const state = getGlobalState();
106
+ return state.configStorage.run(config, fn);
31
107
  }
@@ -0,0 +1,6 @@
1
+ // Minimal type declaration — avoids pulling mcp-server's full dependency tree
2
+ // into the langwatch app's typecheck scope. The actual return type is McpServer,
3
+ // but we only expose the connect() method that handler.ts uses.
4
+ export declare function createMcpServer(): {
5
+ connect(transport: unknown): Promise<void>;
6
+ };