@langwatch/mcp-server 0.6.0 → 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 +30 -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
@@ -0,0 +1,603 @@
1
+ import { createServer, type Server } from "http";
2
+ import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest";
3
+ import { initConfig } from "../config.js";
4
+
5
+ // --- Canned responses for dataset API endpoints ---
6
+
7
+ const CANNED_DATASETS_LIST = {
8
+ data: [
9
+ {
10
+ id: "ds_abc123",
11
+ name: "User Feedback",
12
+ slug: "user-feedback",
13
+ columnTypes: [
14
+ { name: "input", type: "string" },
15
+ { name: "output", type: "string" },
16
+ ],
17
+ recordCount: 25,
18
+ createdAt: "2025-01-01T00:00:00.000Z",
19
+ updatedAt: "2025-01-02T00:00:00.000Z",
20
+ },
21
+ {
22
+ id: "ds_def456",
23
+ name: "Training Data",
24
+ slug: "training-data",
25
+ columnTypes: [{ name: "prompt", type: "string" }],
26
+ recordCount: 100,
27
+ createdAt: "2025-01-03T00:00:00.000Z",
28
+ updatedAt: "2025-01-04T00:00:00.000Z",
29
+ },
30
+ ],
31
+ pagination: { total: 2, page: 1, limit: 50, totalPages: 1 },
32
+ };
33
+
34
+ const CANNED_DATASETS_EMPTY = {
35
+ data: [],
36
+ pagination: { total: 0, page: 1, limit: 50, totalPages: 0 },
37
+ };
38
+
39
+ const CANNED_DATASET_DETAIL = {
40
+ id: "ds_abc123",
41
+ name: "My Dataset",
42
+ slug: "my-dataset",
43
+ columnTypes: [
44
+ { name: "input", type: "string" },
45
+ { name: "output", type: "string" },
46
+ ],
47
+ createdAt: "2025-01-01T00:00:00.000Z",
48
+ updatedAt: "2025-01-02T00:00:00.000Z",
49
+ data: [
50
+ { id: "rec-1", entry: { input: "hello", output: "world" } },
51
+ { id: "rec-2", entry: { input: "foo", output: "bar" } },
52
+ ],
53
+ };
54
+
55
+ const CANNED_DATASET_CREATED = {
56
+ id: "ds_new789",
57
+ name: "Test Data",
58
+ slug: "test-data",
59
+ columnTypes: [
60
+ { name: "input", type: "string" },
61
+ { name: "output", type: "string" },
62
+ ],
63
+ createdAt: "2025-01-01T00:00:00.000Z",
64
+ updatedAt: "2025-01-01T00:00:00.000Z",
65
+ };
66
+
67
+ const CANNED_DATASET_CREATED_EMPTY = {
68
+ id: "ds_new790",
69
+ name: "Empty Schema",
70
+ slug: "empty-schema",
71
+ columnTypes: [],
72
+ createdAt: "2025-01-01T00:00:00.000Z",
73
+ updatedAt: "2025-01-01T00:00:00.000Z",
74
+ };
75
+
76
+ const CANNED_DATASET_UPDATED = {
77
+ id: "ds_abc123",
78
+ name: "New Name",
79
+ slug: "new-name",
80
+ columnTypes: [
81
+ { name: "input", type: "string" },
82
+ { name: "output", type: "string" },
83
+ ],
84
+ createdAt: "2025-01-01T00:00:00.000Z",
85
+ updatedAt: "2025-01-05T00:00:00.000Z",
86
+ };
87
+
88
+ const CANNED_DATASET_UPDATED_COLUMNS = {
89
+ id: "ds_abc123",
90
+ name: "My Dataset",
91
+ slug: "my-dataset",
92
+ columnTypes: [{ name: "question", type: "string" }],
93
+ createdAt: "2025-01-01T00:00:00.000Z",
94
+ updatedAt: "2025-01-05T00:00:00.000Z",
95
+ };
96
+
97
+ const CANNED_DATASET_ARCHIVED = {
98
+ id: "ds_abc123",
99
+ archived: true,
100
+ };
101
+
102
+ const CANNED_RECORDS_CREATED = {
103
+ data: [
104
+ { id: "rec-new-1", entry: { input: "hello", output: "world" } },
105
+ { id: "rec-new-2", entry: { input: "foo", output: "bar" } },
106
+ ],
107
+ };
108
+
109
+ const CANNED_RECORD_UPDATED = {
110
+ id: "rec-123",
111
+ entry: { input: "updated" },
112
+ };
113
+
114
+ const CANNED_RECORDS_DELETED = {
115
+ deletedCount: 2,
116
+ };
117
+
118
+ // --- Mock HTTP Server ---
119
+
120
+ let emptyListMode = false;
121
+
122
+ function createMockServer(): Server {
123
+ return createServer((req, res) => {
124
+ const authToken = req.headers["x-auth-token"];
125
+ if (authToken !== "test-integration-key") {
126
+ res.writeHead(401, { "Content-Type": "application/json" });
127
+ res.end(JSON.stringify({ message: "Invalid auth token." }));
128
+ return;
129
+ }
130
+
131
+ let body = "";
132
+ req.on("data", (chunk: string) => (body += chunk));
133
+ req.on("end", () => {
134
+ const url = req.url ?? "";
135
+ res.setHeader("Content-Type", "application/json");
136
+
137
+ // GET /api/dataset - list datasets
138
+ if (url.match(/^\/api\/dataset(\?|$)/) && req.method === "GET") {
139
+ if (emptyListMode) {
140
+ res.writeHead(200);
141
+ res.end(JSON.stringify(CANNED_DATASETS_EMPTY));
142
+ } else {
143
+ res.writeHead(200);
144
+ res.end(JSON.stringify(CANNED_DATASETS_LIST));
145
+ }
146
+ }
147
+ // GET /api/dataset/my-dataset - get dataset detail
148
+ else if (
149
+ url.match(/^\/api\/dataset\/my-dataset(\?|$)/) &&
150
+ req.method === "GET"
151
+ ) {
152
+ res.writeHead(200);
153
+ res.end(JSON.stringify(CANNED_DATASET_DETAIL));
154
+ }
155
+ // GET /api/dataset/does-not-exist - not found
156
+ else if (
157
+ url.match(/^\/api\/dataset\/does-not-exist(\?|$)/) &&
158
+ req.method === "GET"
159
+ ) {
160
+ res.writeHead(404);
161
+ res.end(JSON.stringify({ message: "Dataset not found" }));
162
+ }
163
+ // POST /api/dataset - create dataset
164
+ else if (url === "/api/dataset" && req.method === "POST") {
165
+ const parsed = JSON.parse(body);
166
+ if (
167
+ parsed.columnTypes &&
168
+ Array.isArray(parsed.columnTypes) &&
169
+ parsed.columnTypes.length > 0
170
+ ) {
171
+ res.writeHead(201);
172
+ res.end(JSON.stringify(CANNED_DATASET_CREATED));
173
+ } else {
174
+ res.writeHead(201);
175
+ res.end(JSON.stringify(CANNED_DATASET_CREATED_EMPTY));
176
+ }
177
+ }
178
+ // PATCH /api/dataset/old-name - update dataset name
179
+ else if (
180
+ url.match(/^\/api\/dataset\/old-name$/) &&
181
+ req.method === "PATCH"
182
+ ) {
183
+ res.writeHead(200);
184
+ res.end(JSON.stringify(CANNED_DATASET_UPDATED));
185
+ }
186
+ // PATCH /api/dataset/my-dataset - update dataset columns
187
+ else if (
188
+ url.match(/^\/api\/dataset\/my-dataset$/) &&
189
+ req.method === "PATCH"
190
+ ) {
191
+ res.writeHead(200);
192
+ res.end(JSON.stringify(CANNED_DATASET_UPDATED_COLUMNS));
193
+ }
194
+ // PATCH /api/dataset/ghost - not found
195
+ else if (
196
+ url.match(/^\/api\/dataset\/ghost$/) &&
197
+ req.method === "PATCH"
198
+ ) {
199
+ res.writeHead(404);
200
+ res.end(JSON.stringify({ message: "Dataset not found" }));
201
+ }
202
+ // DELETE /api/dataset/to-delete - archive dataset
203
+ else if (
204
+ url.match(/^\/api\/dataset\/to-delete$/) &&
205
+ req.method === "DELETE"
206
+ ) {
207
+ res.writeHead(200);
208
+ res.end(JSON.stringify(CANNED_DATASET_ARCHIVED));
209
+ }
210
+ // DELETE /api/dataset/ghost - not found
211
+ else if (
212
+ url.match(/^\/api\/dataset\/ghost$/) &&
213
+ req.method === "DELETE"
214
+ ) {
215
+ res.writeHead(404);
216
+ res.end(JSON.stringify({ message: "Dataset not found" }));
217
+ }
218
+ // POST /api/dataset/my-dataset/records - create records
219
+ else if (
220
+ url.match(/^\/api\/dataset\/my-dataset\/records$/) &&
221
+ req.method === "POST"
222
+ ) {
223
+ res.writeHead(201);
224
+ res.end(JSON.stringify(CANNED_RECORDS_CREATED));
225
+ }
226
+ // POST /api/dataset/ghost/records - not found
227
+ else if (
228
+ url.match(/^\/api\/dataset\/ghost\/records$/) &&
229
+ req.method === "POST"
230
+ ) {
231
+ res.writeHead(404);
232
+ res.end(JSON.stringify({ message: "Dataset not found" }));
233
+ }
234
+ // PATCH /api/dataset/my-dataset/records/rec-123 - update record
235
+ else if (
236
+ url.match(/^\/api\/dataset\/my-dataset\/records\/rec-123$/) &&
237
+ req.method === "PATCH"
238
+ ) {
239
+ res.writeHead(200);
240
+ res.end(JSON.stringify(CANNED_RECORD_UPDATED));
241
+ }
242
+ // PATCH /api/dataset/ghost/records/rec-1 - not found
243
+ else if (
244
+ url.match(/^\/api\/dataset\/ghost\/records\//) &&
245
+ req.method === "PATCH"
246
+ ) {
247
+ res.writeHead(404);
248
+ res.end(JSON.stringify({ message: "Dataset not found" }));
249
+ }
250
+ // DELETE /api/dataset/my-dataset/records - delete records
251
+ else if (
252
+ url.match(/^\/api\/dataset\/my-dataset\/records$/) &&
253
+ req.method === "DELETE"
254
+ ) {
255
+ res.writeHead(200);
256
+ res.end(JSON.stringify(CANNED_RECORDS_DELETED));
257
+ }
258
+ // DELETE /api/dataset/ghost/records - not found
259
+ else if (
260
+ url.match(/^\/api\/dataset\/ghost\/records$/) &&
261
+ req.method === "DELETE"
262
+ ) {
263
+ res.writeHead(404);
264
+ res.end(JSON.stringify({ message: "Dataset not found" }));
265
+ } else {
266
+ res.writeHead(404);
267
+ res.end(
268
+ JSON.stringify({ message: `Not found: ${req.method} ${url}` }),
269
+ );
270
+ }
271
+ });
272
+ });
273
+ }
274
+
275
+ // --- Integration Tests ---
276
+
277
+ describe("MCP dataset tools integration", () => {
278
+ let server: Server;
279
+ let port: number;
280
+
281
+ beforeEach(() => {
282
+ emptyListMode = false;
283
+ });
284
+
285
+ beforeAll(async () => {
286
+ server = createMockServer();
287
+ await new Promise<void>((resolve) => {
288
+ server.listen(0, () => {
289
+ const addr = server.address();
290
+ port = typeof addr === "object" && addr ? addr.port : 0;
291
+ initConfig({
292
+ apiKey: "test-integration-key",
293
+ endpoint: `http://localhost:${port}`,
294
+ });
295
+ resolve();
296
+ });
297
+ });
298
+ });
299
+
300
+ afterAll(async () => {
301
+ await new Promise<void>((resolve) => server.close(() => resolve()));
302
+ });
303
+
304
+ // ── List Datasets ──────────────────────────────────────────────
305
+
306
+ describe("platform_list_datasets", () => {
307
+ describe("when the project has datasets", () => {
308
+ it("returns a formatted list showing both datasets with their names, slugs, and record counts", async () => {
309
+ const { handleListDatasets } = await import(
310
+ "../tools/list-datasets.js"
311
+ );
312
+ const result = await handleListDatasets();
313
+ expect(result).toContain("User Feedback");
314
+ expect(result).toContain("Training Data");
315
+ expect(result).toContain("user-feedback");
316
+ expect(result).toContain("training-data");
317
+ expect(result).toContain("25");
318
+ expect(result).toContain("100");
319
+ });
320
+ });
321
+
322
+ describe("when the project has no datasets", () => {
323
+ it("returns a helpful message indicating no datasets were found", async () => {
324
+ emptyListMode = true;
325
+ const { handleListDatasets } = await import(
326
+ "../tools/list-datasets.js"
327
+ );
328
+ const result = await handleListDatasets();
329
+ expect(result).toContain("No datasets found");
330
+ });
331
+
332
+ it("suggests using platform_create_dataset", async () => {
333
+ emptyListMode = true;
334
+ const { handleListDatasets } = await import(
335
+ "../tools/list-datasets.js"
336
+ );
337
+ const result = await handleListDatasets();
338
+ expect(result).toContain("platform_create_dataset");
339
+ });
340
+ });
341
+
342
+ describe("when format is json", () => {
343
+ it("returns parseable JSON containing all datasets and total", async () => {
344
+ const { handleListDatasets } = await import(
345
+ "../tools/list-datasets.js"
346
+ );
347
+ const result = await handleListDatasets({ format: "json" });
348
+ const parsed = JSON.parse(result);
349
+ expect(parsed.data).toEqual(CANNED_DATASETS_LIST.data);
350
+ expect(parsed.total).toBe(CANNED_DATASETS_LIST.pagination.total);
351
+ });
352
+ });
353
+ });
354
+
355
+ // ── Get Dataset ────────────────────────────────────────────────
356
+
357
+ describe("platform_get_dataset", () => {
358
+ describe("when the dataset exists", () => {
359
+ it("returns the dataset name, slug, and column definitions", async () => {
360
+ const { handleGetDataset } = await import(
361
+ "../tools/get-dataset.js"
362
+ );
363
+ const result = await handleGetDataset({ slugOrId: "my-dataset" });
364
+ expect(result).toContain("My Dataset");
365
+ expect(result).toContain("my-dataset");
366
+ expect(result).toContain("input");
367
+ expect(result).toContain("output");
368
+ });
369
+
370
+ it("returns a preview of records", async () => {
371
+ const { handleGetDataset } = await import(
372
+ "../tools/get-dataset.js"
373
+ );
374
+ const result = await handleGetDataset({ slugOrId: "my-dataset" });
375
+ expect(result).toContain("hello");
376
+ expect(result).toContain("world");
377
+ });
378
+ });
379
+
380
+ describe("when format is json", () => {
381
+ it("returns parseable JSON matching the API response", async () => {
382
+ const { handleGetDataset } = await import(
383
+ "../tools/get-dataset.js"
384
+ );
385
+ const result = await handleGetDataset({ slugOrId: "my-dataset", format: "json" });
386
+ expect(JSON.parse(result)).toEqual(CANNED_DATASET_DETAIL);
387
+ });
388
+ });
389
+
390
+ describe("when the dataset does not exist", () => {
391
+ it("propagates the 404 error", async () => {
392
+ const { handleGetDataset } = await import(
393
+ "../tools/get-dataset.js"
394
+ );
395
+ await expect(
396
+ handleGetDataset({ slugOrId: "does-not-exist" }),
397
+ ).rejects.toThrow("404");
398
+ });
399
+ });
400
+ });
401
+
402
+ // ── Create Dataset ─────────────────────────────────────────────
403
+
404
+ describe("platform_create_dataset", () => {
405
+ describe("when creating with name and columns", () => {
406
+ it("returns confirmation including the generated slug", async () => {
407
+ const { handleCreateDataset } = await import(
408
+ "../tools/create-dataset.js"
409
+ );
410
+ const result = await handleCreateDataset({
411
+ name: "Test Data",
412
+ columnTypes: [
413
+ { name: "input", type: "string" },
414
+ { name: "output", type: "string" },
415
+ ],
416
+ });
417
+ expect(result).toContain("test-data");
418
+ expect(result).toContain("created");
419
+ });
420
+ });
421
+
422
+ describe("when creating with only a name", () => {
423
+ it("returns confirmation including the slug", async () => {
424
+ const { handleCreateDataset } = await import(
425
+ "../tools/create-dataset.js"
426
+ );
427
+ const result = await handleCreateDataset({
428
+ name: "Empty Schema",
429
+ });
430
+ expect(result).toContain("empty-schema");
431
+ expect(result).toContain("created");
432
+ });
433
+ });
434
+ });
435
+
436
+ // ── Update Dataset ─────────────────────────────────────────────
437
+
438
+ describe("platform_update_dataset", () => {
439
+ describe("when updating the dataset name", () => {
440
+ it("returns confirmation reflecting the new name", async () => {
441
+ const { handleUpdateDataset } = await import(
442
+ "../tools/update-dataset.js"
443
+ );
444
+ const result = await handleUpdateDataset({
445
+ slugOrId: "old-name",
446
+ name: "New Name",
447
+ });
448
+ expect(result).toContain("New Name");
449
+ expect(result).toContain("updated");
450
+ });
451
+ });
452
+
453
+ describe("when updating dataset column types", () => {
454
+ it("returns confirmation reflecting the new columns", async () => {
455
+ const { handleUpdateDataset } = await import(
456
+ "../tools/update-dataset.js"
457
+ );
458
+ const result = await handleUpdateDataset({
459
+ slugOrId: "my-dataset",
460
+ columnTypes: [{ name: "question", type: "string" }],
461
+ });
462
+ expect(result).toContain("question");
463
+ expect(result).toContain("updated");
464
+ });
465
+ });
466
+
467
+ describe("when the dataset does not exist", () => {
468
+ it("propagates the 404 error", async () => {
469
+ const { handleUpdateDataset } = await import(
470
+ "../tools/update-dataset.js"
471
+ );
472
+ await expect(
473
+ handleUpdateDataset({ slugOrId: "ghost", name: "Whatever" }),
474
+ ).rejects.toThrow("404");
475
+ });
476
+ });
477
+ });
478
+
479
+ // ── Delete Dataset ─────────────────────────────────────────────
480
+
481
+ describe("platform_delete_dataset", () => {
482
+ describe("when the dataset exists", () => {
483
+ it("returns confirmation that the dataset was deleted", async () => {
484
+ const { handleDeleteDataset } = await import(
485
+ "../tools/delete-dataset.js"
486
+ );
487
+ const result = await handleDeleteDataset({ slugOrId: "to-delete" });
488
+ expect(result).toContain("deleted");
489
+ });
490
+ });
491
+
492
+ describe("when the dataset does not exist", () => {
493
+ it("propagates the 404 error", async () => {
494
+ const { handleDeleteDataset } = await import(
495
+ "../tools/delete-dataset.js"
496
+ );
497
+ await expect(
498
+ handleDeleteDataset({ slugOrId: "ghost" }),
499
+ ).rejects.toThrow("404");
500
+ });
501
+ });
502
+ });
503
+
504
+ // ── Create Records ─────────────────────────────────────────────
505
+
506
+ describe("platform_create_dataset_records", () => {
507
+ describe("when the dataset exists", () => {
508
+ it("returns confirmation with the count of records created", async () => {
509
+ const { handleCreateDatasetRecords } = await import(
510
+ "../tools/create-dataset-records.js"
511
+ );
512
+ const result = await handleCreateDatasetRecords({
513
+ slugOrId: "my-dataset",
514
+ entries: [
515
+ { input: "hello", output: "world" },
516
+ { input: "foo", output: "bar" },
517
+ ],
518
+ });
519
+ expect(result).toContain("2");
520
+ expect(result).toContain("created");
521
+ });
522
+ });
523
+
524
+ describe("when the dataset does not exist", () => {
525
+ it("propagates the 404 error", async () => {
526
+ const { handleCreateDatasetRecords } = await import(
527
+ "../tools/create-dataset-records.js"
528
+ );
529
+ await expect(
530
+ handleCreateDatasetRecords({
531
+ slugOrId: "ghost",
532
+ entries: [{ input: "hello" }],
533
+ }),
534
+ ).rejects.toThrow("404");
535
+ });
536
+ });
537
+ });
538
+
539
+ // ── Update Record ──────────────────────────────────────────────
540
+
541
+ describe("platform_update_dataset_record", () => {
542
+ describe("when the record exists", () => {
543
+ it("returns confirmation that the record was updated", async () => {
544
+ const { handleUpdateDatasetRecord } = await import(
545
+ "../tools/update-dataset-record.js"
546
+ );
547
+ const result = await handleUpdateDatasetRecord({
548
+ slugOrId: "my-dataset",
549
+ recordId: "rec-123",
550
+ entry: { input: "updated" },
551
+ });
552
+ expect(result).toContain("updated");
553
+ });
554
+ });
555
+
556
+ describe("when the dataset does not exist", () => {
557
+ it("propagates the 404 error", async () => {
558
+ const { handleUpdateDatasetRecord } = await import(
559
+ "../tools/update-dataset-record.js"
560
+ );
561
+ await expect(
562
+ handleUpdateDatasetRecord({
563
+ slugOrId: "ghost",
564
+ recordId: "rec-1",
565
+ entry: { input: "x" },
566
+ }),
567
+ ).rejects.toThrow("404");
568
+ });
569
+ });
570
+ });
571
+
572
+ // ── Delete Records ─────────────────────────────────────────────
573
+
574
+ describe("platform_delete_dataset_records", () => {
575
+ describe("when the dataset exists", () => {
576
+ it("returns confirmation with the count of records deleted", async () => {
577
+ const { handleDeleteDatasetRecords } = await import(
578
+ "../tools/delete-dataset-records.js"
579
+ );
580
+ const result = await handleDeleteDatasetRecords({
581
+ slugOrId: "my-dataset",
582
+ recordIds: ["rec-1", "rec-2"],
583
+ });
584
+ expect(result).toContain("2");
585
+ expect(result).toContain("deleted");
586
+ });
587
+ });
588
+
589
+ describe("when the dataset does not exist", () => {
590
+ it("propagates the 404 error", async () => {
591
+ const { handleDeleteDatasetRecords } = await import(
592
+ "../tools/delete-dataset-records.js"
593
+ );
594
+ await expect(
595
+ handleDeleteDatasetRecords({
596
+ slugOrId: "ghost",
597
+ recordIds: ["rec-1"],
598
+ }),
599
+ ).rejects.toThrow("404");
600
+ });
601
+ });
602
+ });
603
+ });