@langwatch/mcp-server 1.0.0 → 2.1.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 (204) hide show
  1. package/.python-version +1 -0
  2. package/CHANGELOG.md +110 -0
  3. package/CONTRIBUTING.md +1 -1
  4. package/README.md +86 -4
  5. package/dist/{archive-project-MK3673A7.js → archive-project-W3IKI7UL.js} +5 -5
  6. package/dist/archive-run-plan-474OKHHU.js +16 -0
  7. package/dist/{archive-scenario-KATSXRIN.js → archive-scenario-V5UNYK26.js} +5 -5
  8. package/dist/archive-test-suite-O7FLDMXK.js +16 -0
  9. package/dist/{assign-prompt-tag-E3C5W6PI.js → assign-prompt-tag-Q46WRB6P.js} +4 -4
  10. package/dist/chunk-56OGTS24.js +44 -0
  11. package/dist/chunk-6DJDEXET.js +63 -0
  12. package/dist/chunk-7E4FZIYJ.js +15 -0
  13. package/dist/{chunk-J45SVWPR.js → chunk-AN4OUA4D.js} +2 -2
  14. package/dist/chunk-CKNAH5TG.js +74 -0
  15. package/dist/{chunk-CE6QMZOP.js → chunk-EYXMLE5X.js} +86 -11
  16. package/dist/{chunk-XY4STOBE.js → chunk-G4ZAZTZW.js} +2 -2
  17. package/dist/{chunk-5UWY24RP.js → chunk-GELJ2MQR.js} +12 -3
  18. package/dist/chunk-HV4DWKBI.js +46 -0
  19. package/dist/chunk-JR6DJZOU.js +49 -0
  20. package/dist/{chunk-DAIQKS27.js → chunk-JTQJRNFJ.js} +2 -2
  21. package/dist/{chunk-WWMDHZZV.js → chunk-K37ZNPXK.js} +2 -2
  22. package/dist/{chunk-EU22L52K.js → chunk-OJLYYJWI.js} +2 -2
  23. package/dist/{chunk-Y3HDZ6OW.js → chunk-QAMP3CGR.js} +2 -2
  24. package/dist/{chunk-VYPSKRPN.js → chunk-QRO35SOM.js} +347 -146
  25. package/dist/{chunk-O3LWVN67.js → chunk-RVAE2MEA.js} +2 -2
  26. package/dist/{chunk-UXXA4D6K.js → chunk-S4GVX36W.js} +2 -2
  27. package/dist/{chunk-BJ7B4R26.js → chunk-SAS5OB7F.js} +18 -2
  28. package/dist/{chunk-RYRB2YVJ.js → chunk-SR6QPRIE.js} +2 -2
  29. package/dist/chunk-UF6XIGYR.js +58 -0
  30. package/dist/{chunk-T7WJVS2N.js → chunk-UFOYFYI7.js} +2 -2
  31. package/dist/chunk-UJPXFA26.js +267 -0
  32. package/dist/{chunk-KOO5733S.js → chunk-WT4PO4A3.js} +8 -2
  33. package/dist/config.js +2 -2
  34. package/dist/{create-agent-7W57J7MT.js → create-agent-JFJ2K6X3.js} +5 -5
  35. package/dist/{create-annotation-W2XELBCQ.js → create-annotation-EUXXQ6NS.js} +5 -5
  36. package/dist/{create-api-key-MKRXKFJM.js → create-api-key-BGBB76JX.js} +5 -5
  37. package/dist/{create-dashboard-QGL7IODC.js → create-dashboard-Z76AIPLS.js} +5 -5
  38. package/dist/{create-dataset-3PH7A4HG.js → create-dataset-FD5VAXU4.js} +5 -5
  39. package/dist/{create-dataset-records-4DZS3VRL.js → create-dataset-records-WQUXXOHI.js} +5 -5
  40. package/dist/{create-evaluator-YWH6NC4M.js → create-evaluator-6NV33Z52.js} +5 -5
  41. package/dist/create-mcp-server.js +9 -4
  42. package/dist/{create-project-BOBQ32C5.js → create-project-ZQ7TFIO2.js} +5 -5
  43. package/dist/{create-prompt-FLCFGITE.js → create-prompt-BS6QRCUU.js} +4 -4
  44. package/dist/{create-prompt-tag-EMPNIKEA.js → create-prompt-tag-FCQ5ELSQ.js} +4 -4
  45. package/dist/{create-scenario-BSBIHUL6.js → create-scenario-DRPTPXU3.js} +12 -5
  46. package/dist/create-test-suite-ZIZCCFJ4.js +37 -0
  47. package/dist/{delete-agent-7MSYNY5E.js → delete-agent-SA4JMUZF.js} +5 -5
  48. package/dist/{delete-annotation-ZT5IGN24.js → delete-annotation-X3KKK7QY.js} +5 -5
  49. package/dist/{delete-dashboard-BTIY5S3L.js → delete-dashboard-LYMRPF27.js} +5 -5
  50. package/dist/{delete-dataset-U45HQHPR.js → delete-dataset-W6EZ44HY.js} +5 -5
  51. package/dist/{delete-dataset-records-R43IMAWB.js → delete-dataset-records-7CNOTALB.js} +5 -5
  52. package/dist/{delete-evaluator-U6JMQ6C7.js → delete-evaluator-RKYC4TNB.js} +5 -5
  53. package/dist/{delete-prompt-tag-4EV4TDHB.js → delete-prompt-tag-QB3OCZUV.js} +4 -4
  54. package/dist/{delete-workflow-5QYALXEL.js → delete-workflow-NG2VXYYM.js} +5 -5
  55. package/dist/{discover-evaluator-schema-MRGHVNTT.js → discover-evaluator-schema-ESSHQGPY.js} +58 -321
  56. package/dist/{discover-scenario-schema-UPBTCWPG.js → discover-scenario-schema-W7KBXA4J.js} +54 -2
  57. package/dist/{discover-schema-ICGXPHUW.js → discover-schema-L6QDPON3.js} +2 -2
  58. package/dist/get-agent-7THF7HCU.js +66 -0
  59. package/dist/{get-analytics-PVERWOA5.js → get-analytics-R6AZD3AW.js} +4 -4
  60. package/dist/{get-dashboard-ZZPC3WMD.js → get-dashboard-P4WWVHFU.js} +5 -5
  61. package/dist/{get-dataset-25TB2LQH.js → get-dataset-AKTYSQDI.js} +5 -5
  62. package/dist/{get-evaluator-3FTRQKEC.js → get-evaluator-RZ44UIN6.js} +5 -5
  63. package/dist/{get-project-5OAXYC57.js → get-project-QSQEAB33.js} +5 -5
  64. package/dist/get-prompt-GVKXF5NL.js +100 -0
  65. package/dist/get-run-plan-NY7J5GRE.js +61 -0
  66. package/dist/{get-scenario-DSSTR6FI.js → get-scenario-Q3LLQE4T.js} +9 -5
  67. package/dist/{get-simulation-run-RQPMS2MC.js → get-simulation-run-WINEYDMK.js} +14 -5
  68. package/dist/get-test-suite-4HKA7OKS.js +49 -0
  69. package/dist/{get-trace-O5VT3NAC.js → get-trace-RCUV7N4O.js} +4 -4
  70. package/dist/{get-workflow-3EZDOEIF.js → get-workflow-EEOC7Y45.js} +5 -5
  71. package/dist/http-security-EU3CHTY7.js +26 -0
  72. package/dist/{http-server-2ACJ3CXW.js → http-server-WCDUARTH.js} +1038 -693
  73. package/dist/index.js +6437 -4020
  74. package/dist/{langwatch-api-annotations-E65MI3WZ.js → langwatch-api-annotations-6DHVZWUK.js} +5 -5
  75. package/dist/{langwatch-api-dashboards-QCDECRCY.js → langwatch-api-dashboards-2KJL2M42.js} +5 -5
  76. package/dist/{langwatch-api-monitors-UIO2KTDV.js → langwatch-api-monitors-6ISFJ7WX.js} +4 -4
  77. package/dist/{langwatch-api-secrets-4T2EKSJ7.js → langwatch-api-secrets-OEBD6GBU.js} +4 -4
  78. package/dist/{langwatch-api-triggers-MPOEKPUY.js → langwatch-api-triggers-5E6DDGX6.js} +4 -4
  79. package/dist/langwatch-api-workflows-6UJL5U4Z.js +16 -0
  80. package/dist/list-agents-AMVDQPQL.js +43 -0
  81. package/dist/{list-annotations-CGX5BT7E.js → list-annotations-6PR5NLIC.js} +5 -5
  82. package/dist/{list-api-keys-JLWJPFRX.js → list-api-keys-GVPNBIHQ.js} +5 -5
  83. package/dist/{list-dashboards-RNIWX3TX.js → list-dashboards-Z3BZKDGF.js} +5 -5
  84. package/dist/{list-dataset-records-B5MHZR66.js → list-dataset-records-O67HZ35Y.js} +5 -5
  85. package/dist/{list-datasets-BVCAMQCP.js → list-datasets-WXZL6UMP.js} +5 -5
  86. package/dist/{list-evaluators-65FBTL2U.js → list-evaluators-PAX72WCN.js} +5 -5
  87. package/dist/{list-model-providers-GRK3I3VV.js → list-model-providers-VVLZMLCX.js} +5 -5
  88. package/dist/{list-projects-JLMBFMW3.js → list-projects-AQVI6SUK.js} +5 -5
  89. package/dist/{list-prompt-tags-UHNWDGQI.js → list-prompt-tags-WZFBJOHG.js} +4 -4
  90. package/dist/{list-prompts-7254B3U7.js → list-prompts-BQOHRHYT.js} +4 -4
  91. package/dist/list-run-plans-UBAU2DGH.js +46 -0
  92. package/dist/{list-scenarios-46YNICKM.js → list-scenarios-MS4IUIVM.js} +10 -7
  93. package/dist/{list-simulation-runs-LGNEFNPN.js → list-simulation-runs-D6YF4ZOU.js} +13 -7
  94. package/dist/list-test-suites-L7R2O6DR.js +35 -0
  95. package/dist/{list-workflows-MLQDM6CP.js → list-workflows-6MV4XJHF.js} +5 -5
  96. package/dist/{rename-prompt-tag-RHQMNEQL.js → rename-prompt-tag-K5LQ4CY6.js} +4 -4
  97. package/dist/rename-test-suite-NTYHAO5X.js +21 -0
  98. package/dist/report-issue-RHZZ33IS.js +885 -0
  99. package/dist/rerun-run-plan-MUDWJBE4.js +22 -0
  100. package/dist/{revoke-api-key-3Y35EMBT.js → revoke-api-key-6LAZ3QZZ.js} +5 -5
  101. package/dist/run-agent-CAGWGTYT.js +72 -0
  102. package/dist/run-plan-T4ZYOZGA.js +39 -0
  103. package/dist/run-test-suite-L4MZ7G4D.js +26 -0
  104. package/dist/{run-workflow-5LVBZYLS.js → run-workflow-DFIS23AP.js} +5 -5
  105. package/dist/{search-traces-U4HLOCZ2.js → search-traces-4XTJYXIK.js} +4 -4
  106. package/dist/{set-model-provider-V3M5CDIY.js → set-model-provider-XRNMFOVR.js} +5 -5
  107. package/dist/{update-agent-47RZIHS7.js → update-agent-DUWNYBNA.js} +5 -5
  108. package/dist/{update-dataset-G6QJJGS5.js → update-dataset-YSOMDCOB.js} +5 -5
  109. package/dist/{update-dataset-record-RMWLSWCS.js → update-dataset-record-DEA2ITYR.js} +5 -5
  110. package/dist/{update-evaluator-SFRRI5Q3.js → update-evaluator-NH7Q5GWV.js} +5 -5
  111. package/dist/{update-project-PI5DWBVK.js → update-project-Q26FBSYH.js} +5 -5
  112. package/dist/update-prompt-ZQQ2C3KU.js +143 -0
  113. package/dist/{update-scenario-G3IU2UIB.js → update-scenario-WUX34JAR.js} +12 -5
  114. package/package.json +8 -9
  115. package/pyproject.toml +5 -5
  116. package/src/__tests__/agent-tools.unit.test.ts +257 -0
  117. package/src/__tests__/all-tools.integration.test.ts +321 -75
  118. package/src/__tests__/config.unit.test.ts +33 -0
  119. package/src/__tests__/docker.integration.test.ts +91 -7
  120. package/src/__tests__/documentation-fetch-security.integration.test.ts +8 -2
  121. package/src/__tests__/feature-map-tool-drift.unit.test.ts +93 -0
  122. package/src/__tests__/http-security-origin.unit.test.ts +97 -0
  123. package/src/__tests__/http-security-sessions.unit.test.ts +209 -0
  124. package/src/__tests__/http-security-verifier.unit.test.ts +248 -0
  125. package/src/__tests__/http-server-allocation.integration.test.ts +279 -0
  126. package/src/__tests__/http-server-origin.integration.test.ts +169 -0
  127. package/src/__tests__/http-server-session-auth.integration.test.ts +341 -0
  128. package/src/__tests__/http-transport.integration.test.ts +85 -87
  129. package/src/__tests__/integration.integration.test.ts +7 -11
  130. package/src/__tests__/prompt-response-types.unit.test.ts +51 -0
  131. package/src/__tests__/prompts.integration.test.ts +736 -0
  132. package/src/__tests__/run-plan-tools.unit.test.ts +565 -0
  133. package/src/__tests__/scenario-tools.unit.test.ts +19 -0
  134. package/src/__tests__/simulation-run-tools.unit.test.ts +116 -0
  135. package/src/__tests__/support/http-server-harness.ts +138 -0
  136. package/src/__tests__/test-suite-tools.unit.test.ts +615 -0
  137. package/src/__tests__/{tools.unit.test.ts → tools.integration.test.ts} +31 -59
  138. package/src/config.ts +17 -2
  139. package/src/create-mcp-server.ts +334 -55
  140. package/src/http-security.ts +492 -0
  141. package/src/http-server.ts +536 -152
  142. package/src/index.ts +49 -2
  143. package/src/langwatch-api-agents.ts +196 -12
  144. package/src/langwatch-api-run-plans.ts +157 -0
  145. package/src/langwatch-api-scenarios.ts +9 -0
  146. package/src/langwatch-api-simulation-runs.ts +5 -0
  147. package/src/langwatch-api-test-suites.ts +135 -0
  148. package/src/langwatch-api.ts +62 -12
  149. package/src/schemas/index.ts +5 -0
  150. package/src/schemas/run-plan.ts +92 -0
  151. package/src/schemas/suite-fields.ts +149 -0
  152. package/src/tools/archive-run-plan.ts +12 -0
  153. package/src/tools/archive-test-suite.ts +12 -0
  154. package/src/tools/create-scenario.ts +8 -0
  155. package/src/tools/create-test-suite.ts +43 -0
  156. package/src/tools/discover-evaluator-schema.ts +2 -2
  157. package/src/tools/discover-scenario-schema.ts +54 -0
  158. package/src/tools/format-run-plan.ts +68 -0
  159. package/src/tools/format-scenario.ts +14 -0
  160. package/src/tools/format-suite-details.ts +96 -0
  161. package/src/tools/get-agent.ts +45 -3
  162. package/src/tools/get-prompt.ts +108 -24
  163. package/src/tools/get-run-plan.ts +62 -0
  164. package/src/tools/get-scenario.ts +3 -0
  165. package/src/tools/get-simulation-run.ts +9 -0
  166. package/src/tools/get-test-suite.ts +49 -0
  167. package/src/tools/list-agents.ts +20 -3
  168. package/src/tools/list-run-plans.ts +47 -0
  169. package/src/tools/list-scenarios.ts +12 -2
  170. package/src/tools/list-simulation-runs.ts +6 -2
  171. package/src/tools/list-test-suites.ts +35 -0
  172. package/src/tools/rename-test-suite.ts +18 -0
  173. package/src/tools/rerun-run-plan.ts +21 -0
  174. package/src/tools/run-agent.ts +54 -6
  175. package/src/tools/run-plan.ts +49 -0
  176. package/src/tools/run-test-suite.ts +30 -0
  177. package/src/tools/test-agent.ts +18 -0
  178. package/src/tools/update-prompt.ts +195 -10
  179. package/src/tools/update-scenario.ts +8 -0
  180. package/src/tools/update-test-suite.ts +45 -0
  181. package/tsconfig.json +7 -2
  182. package/uv.lock +418 -298
  183. package/dist/archive-suite-C3KX6IIZ.js +0 -16
  184. package/dist/chunk-WDH5P5RF.js +0 -48
  185. package/dist/create-suite-TWF6BBR4.js +0 -39
  186. package/dist/get-agent-7QJVPH72.js +0 -29
  187. package/dist/get-prompt-I753LZKZ.js +0 -46
  188. package/dist/get-suite-GTOCY5PL.js +0 -45
  189. package/dist/langwatch-api-workflows-5HRZ7HR4.js +0 -16
  190. package/dist/list-agents-TIJTP566.js +0 -33
  191. package/dist/list-suites-5VK4HXEK.js +0 -43
  192. package/dist/report-issue-DXJ6TPTC.js +0 -412
  193. package/dist/run-agent-RBSIFKRT.js +0 -37
  194. package/dist/run-suite-K3DNQ3SE.js +0 -30
  195. package/dist/update-prompt-RQFTEO7Y.js +0 -25
  196. package/dist/update-suite-TRGGLNR7.js +0 -40
  197. package/pnpm-workspace.yaml +0 -34
  198. package/src/langwatch-api-suites.ts +0 -118
  199. package/src/tools/archive-suite.ts +0 -12
  200. package/src/tools/create-suite.ts +0 -42
  201. package/src/tools/get-suite.ts +0 -47
  202. package/src/tools/list-suites.ts +0 -43
  203. package/src/tools/run-suite.ts +0 -28
  204. package/src/tools/update-suite.ts +0 -44
@@ -0,0 +1,18 @@
1
+ import { renameTestSuite as apiRenameTestSuite } from "../langwatch-api-test-suites.js";
2
+
3
+ /**
4
+ * Handles the platform_rename_test_suite MCP tool invocation.
5
+ */
6
+ export async function handleRenameTestSuite(params: {
7
+ id: string;
8
+ name: string;
9
+ }): Promise<string> {
10
+ const suite = await apiRenameTestSuite(params);
11
+
12
+ return [
13
+ `Test suite ${suite.id} is now named "${suite.name}".`,
14
+ "",
15
+ `**Slug**: ${suite.slug}`,
16
+ `**Scenarios**: ${suite.scenarioCount}`,
17
+ ].join("\n");
18
+ }
@@ -0,0 +1,21 @@
1
+ import {
2
+ rerunRunPlan as apiRerunRunPlan,
3
+ type RunParameters,
4
+ } from "../langwatch-api-run-plans.js";
5
+ import { formatRunPlanRun } from "./format-run-plan.js";
6
+
7
+ /**
8
+ * Handles the platform_rerun_run_plan MCP tool invocation.
9
+ */
10
+ export async function handleRerunRunPlan(params: {
11
+ id: string;
12
+ parameters?: RunParameters;
13
+ note?: string;
14
+ }): Promise<string> {
15
+ const result = await apiRerunRunPlan(params.id, {
16
+ parameters: params.parameters,
17
+ note: params.note,
18
+ });
19
+
20
+ return formatRunPlanRun(result);
21
+ }
@@ -1,22 +1,56 @@
1
- import { runAgent as apiRunAgent } from "../langwatch-api-agents.js";
1
+ import { z } from "zod";
2
+ import {
3
+ runAgent as apiRunAgent,
4
+ type AgentCallParams,
5
+ } from "../langwatch-api-agents.js";
6
+
7
+ /**
8
+ * A scalar and an array both parse as JSON, and either one reaches the agent
9
+ * as a body it cannot read, so only an object passes.
10
+ */
11
+ const jsonObjectSchema = z.looseObject({});
12
+ type JsonObject = z.infer<typeof jsonObjectSchema>;
2
13
 
3
14
  /**
4
15
  * Handles the platform_run_agent MCP tool invocation.
16
+ *
17
+ * @see specs/mcp-server/agent-tools.feature
5
18
  */
6
- export async function handleRunAgent(params: {
19
+ export async function handleRunAgent({
20
+ id,
21
+ input,
22
+ message,
23
+ parameters,
24
+ threadId,
25
+ }: {
7
26
  id: string;
8
27
  input?: string;
28
+ message?: string;
29
+ parameters?: AgentCallParams;
30
+ threadId?: string;
9
31
  }): Promise<string> {
10
- let parsedInput: Record<string, unknown> = {};
11
- if (params.input) {
32
+ let parsedInput: JsonObject = {};
33
+ if (input) {
34
+ let decoded: unknown;
12
35
  try {
13
- parsedInput = JSON.parse(params.input) as Record<string, unknown>;
36
+ decoded = JSON.parse(input);
14
37
  } catch {
15
38
  return "Error: `input` must be a valid JSON object.";
16
39
  }
40
+ const result = jsonObjectSchema.safeParse(decoded);
41
+ if (!result.success) {
42
+ return "Error: `input` must be a valid JSON object.";
43
+ }
44
+ parsedInput = result.data;
17
45
  }
18
46
 
19
- const { agentType, result } = await apiRunAgent(params.id, parsedInput);
47
+ const { agentType, result } = await apiRunAgent({
48
+ id,
49
+ input: parsedInput,
50
+ message,
51
+ parameters,
52
+ threadId,
53
+ });
20
54
 
21
55
  const lines: string[] = [];
22
56
  lines.push(`Agent executed successfully (type: ${agentType}).\n`);
@@ -30,6 +64,20 @@ export async function handleRunAgent(params: {
30
64
  lines.push("**Result:**");
31
65
  lines.push(JSON.stringify(result, null, 2));
32
66
  }
67
+ if (agentType === "connected") {
68
+ const instance = (result as { instance?: { hostname?: string; label?: string | null } }).instance;
69
+ const durationMs = (result as { durationMs?: number }).durationMs;
70
+ if (instance?.hostname) {
71
+ const label = instance.label ? ` (${instance.label})` : "";
72
+ lines.push(`\n**Instance:** ${instance.hostname}${label}`);
73
+ }
74
+ if (typeof durationMs === "number") lines.push(`**Duration:** ${durationMs} ms`);
75
+ const session = (result as { session?: unknown }).session;
76
+ if (session !== undefined && session !== null) {
77
+ lines.push("**Session:**");
78
+ lines.push(JSON.stringify(session, null, 2));
79
+ }
80
+ }
33
81
  }
34
82
 
35
83
  return lines.join("\n");
@@ -0,0 +1,49 @@
1
+ import {
2
+ runRunPlan as apiRunRunPlan,
3
+ type RunParameters,
4
+ type RunPlanScope,
5
+ type RunPlanTarget,
6
+ } from "../langwatch-api-run-plans.js";
7
+ import { toWireTargets } from "../schemas/run-plan.js";
8
+ import {
9
+ type EvaluatorAttachmentInput,
10
+ toWireAttachments,
11
+ } from "../schemas/suite-fields.js";
12
+ import { formatRunPlanRun } from "./format-run-plan.js";
13
+
14
+ /**
15
+ * Handles the platform_run_plan MCP tool invocation.
16
+ */
17
+ export async function handleRunPlan(params: {
18
+ name?: string;
19
+ scope: RunPlanScope;
20
+ scenarioIds?: string[];
21
+ targets: RunPlanTarget[];
22
+ repeatCount?: number;
23
+ simulatorModel?: string;
24
+ judgeModel?: string;
25
+ evaluators?: EvaluatorAttachmentInput[];
26
+ parameters?: RunParameters;
27
+ note?: string;
28
+ idempotencyKey?: string;
29
+ }): Promise<string> {
30
+ const result = await apiRunRunPlan({
31
+ name: params.name,
32
+ config: {
33
+ scope: params.scope,
34
+ targets: toWireTargets(params.targets),
35
+ repeatCount: params.repeatCount,
36
+ simulatorModel: params.simulatorModel,
37
+ judgeModel: params.judgeModel,
38
+ scenarioIds: params.scenarioIds,
39
+ ...(params.evaluators !== undefined
40
+ ? { evaluators: toWireAttachments(params.evaluators) }
41
+ : {}),
42
+ },
43
+ idempotencyKey: params.idempotencyKey,
44
+ parameters: params.parameters,
45
+ note: params.note,
46
+ });
47
+
48
+ return formatRunPlanRun(result);
49
+ }
@@ -0,0 +1,30 @@
1
+ import type {
2
+ RunParameters,
3
+ RunPlanTarget,
4
+ } from "../langwatch-api-run-plans.js";
5
+ import { toWireTargets } from "../schemas/run-plan.js";
6
+ import { runTestSuite as apiRunTestSuite } from "../langwatch-api-test-suites.js";
7
+ import { formatRunPlanRun } from "./format-run-plan.js";
8
+
9
+ /**
10
+ * Handles the platform_run_test_suite MCP tool invocation.
11
+ */
12
+ export async function handleRunTestSuite(params: {
13
+ id: string;
14
+ targets: RunPlanTarget[];
15
+ name?: string;
16
+ repeatCount?: number;
17
+ simulatorModel?: string;
18
+ judgeModel?: string;
19
+ parameters?: RunParameters;
20
+ note?: string;
21
+ idempotencyKey?: string;
22
+ }): Promise<string> {
23
+ const { targets, ...rest } = params;
24
+ const result = await apiRunTestSuite({
25
+ ...rest,
26
+ targets: toWireTargets(targets),
27
+ });
28
+
29
+ return formatRunPlanRun(result);
30
+ }
@@ -0,0 +1,18 @@
1
+ import { testAgent as apiTestAgent } from "../langwatch-api-agents.js";
2
+
3
+ /**
4
+ * Handles the platform_test_agent MCP tool invocation.
5
+ *
6
+ * @see specs/agents/agent-test-run.feature
7
+ */
8
+ export async function handleTestAgent(params: { id: string }): Promise<string> {
9
+ const run = await apiTestAgent(params.id);
10
+ return [
11
+ "Test run scheduled. The user sends \"ping\"; the run succeeds when the agent answers.",
12
+ "",
13
+ `**Scenario run ID:** ${run.scenarioRunId}`,
14
+ `**Batch run ID:** ${run.batchRunId}`,
15
+ "",
16
+ `Follow it with platform_get_simulation_run using scenarioRunId "${run.scenarioRunId}".`,
17
+ ].join("\n");
18
+ }
@@ -1,10 +1,24 @@
1
- import { updatePrompt as apiUpdatePrompt } from "../langwatch-api.js";
1
+ import {
2
+ getPrompt as apiGetPrompt,
3
+ getPromptVersions as apiGetPromptVersions,
4
+ updatePrompt as apiUpdatePrompt,
5
+ type PromptDetailResponse,
6
+ type PromptTag,
7
+ } from "../langwatch-api.js";
2
8
 
3
9
  /**
4
10
  * Handles the platform_update_prompt MCP tool invocation.
5
11
  *
6
- * Updates an existing prompt via the PUT endpoint.
7
- * Every update with a commitMessage creates a new version automatically.
12
+ * Updates an existing prompt via the PUT endpoint. Every update with a
13
+ * commitMessage creates a new version automatically. The mutation response
14
+ * does not carry the tags the server actually applied, so this re-fetches
15
+ * the prompt via getPrompt: the GET response is the prompt's latest version
16
+ * flattened to the top level (version, versionId, commitMessage) plus a
17
+ * `tags` array of { name, versionId } naming which version each tag points
18
+ * to. The new version is identified by matching the request's commitMessage
19
+ * against that top level (falling back to the versions listing), and
20
+ * deployment state is derived from the tags that point at it — never from
21
+ * the request's tags directly.
8
22
  */
9
23
  export async function handleUpdatePrompt(params: {
10
24
  idOrHandle: string;
@@ -15,17 +29,188 @@ export async function handleUpdatePrompt(params: {
15
29
  }): Promise<string> {
16
30
  const { idOrHandle, ...data } = params;
17
31
 
18
- const result = await apiUpdatePrompt(idOrHandle, data);
32
+ let updated;
33
+ try {
34
+ updated = await apiUpdatePrompt(idOrHandle, data);
35
+ } catch (err) {
36
+ // The platform commits the version before assigning tags, so a
37
+ // rejection while tags were requested may still have created a version.
38
+ // Re-fetch to find out rather than reporting a bare failure.
39
+ if (params.tags && params.tags.length > 0) {
40
+ return renderTagFailure({ idOrHandle, params });
41
+ }
42
+ throw err;
43
+ }
44
+
45
+ return renderUpdateSuccess({ idOrHandle, params, updated });
46
+ }
47
+
48
+ interface IdentifiedVersion {
49
+ version?: number;
50
+ versionId?: string;
51
+ }
52
+
53
+ /**
54
+ * Finds the version created by this update. The GET detail response IS the
55
+ * latest version, so a top-level commitMessage match identifies it directly;
56
+ * otherwise (e.g. a concurrent update landed after ours) the versions
57
+ * listing is consulted, best-effort.
58
+ */
59
+ async function identifyNewVersion({
60
+ idOrHandle,
61
+ prompt,
62
+ commitMessage,
63
+ }: {
64
+ idOrHandle: string;
65
+ prompt: PromptDetailResponse;
66
+ commitMessage: string;
67
+ }): Promise<IdentifiedVersion | undefined> {
68
+ if (prompt.commitMessage === commitMessage) {
69
+ return { version: prompt.version, versionId: prompt.versionId };
70
+ }
71
+ try {
72
+ const versions = await apiGetPromptVersions(idOrHandle);
73
+ const match = (Array.isArray(versions) ? versions : []).find(
74
+ (v) => v.commitMessage === commitMessage
75
+ );
76
+ if (match) return { version: match.version, versionId: match.versionId };
77
+ } catch {
78
+ // Identification failure is reported by the caller.
79
+ }
80
+ return undefined;
81
+ }
82
+
83
+ /** The prompt's deployment tags, excluding the built-in "latest" tag. */
84
+ function deploymentTagsOf(prompt: PromptDetailResponse): PromptTag[] {
85
+ return (prompt.tags ?? []).filter((tag) => tag?.name !== "latest");
86
+ }
87
+
88
+ async function renderUpdateSuccess({
89
+ idOrHandle,
90
+ params,
91
+ updated,
92
+ }: {
93
+ idOrHandle: string;
94
+ params: { commitMessage: string; tags?: string[] };
95
+ updated?: { id?: string; handle?: string };
96
+ }): Promise<string> {
97
+ let prompt;
98
+ try {
99
+ prompt = await apiGetPrompt(idOrHandle, {
100
+ version: undefined,
101
+ tag: undefined,
102
+ });
103
+ } catch {
104
+ // The update itself succeeded; a failed confirmation read must not
105
+ // surface as a tool failure — that invites a dangerous retry.
106
+ const lines: string[] = [];
107
+ lines.push("Prompt updated successfully!\n");
108
+ if (updated?.id) lines.push(`**ID**: ${updated.id}`);
109
+ if (updated?.handle) lines.push(`**Handle**: ${updated.handle}`);
110
+ lines.push(`**Commit**: ${params.commitMessage}`);
111
+ lines.push(
112
+ `**Note**: the update succeeded, but version and deployment details are unavailable (confirmation read failed). Run platform_get_prompt to inspect the current state. Do not retry the update.`
113
+ );
114
+ return lines.join("\n");
115
+ }
116
+
117
+ const newVersion = await identifyNewVersion({
118
+ idOrHandle,
119
+ prompt,
120
+ commitMessage: params.commitMessage,
121
+ });
19
122
 
20
123
  const lines: string[] = [];
21
124
  lines.push("Prompt updated successfully!\n");
22
- if (result.id) lines.push(`**ID**: ${result.id}`);
23
- if (result.handle) lines.push(`**Handle**: ${result.handle}`);
24
- if (result.latestVersionNumber != null)
25
- lines.push(`**Version**: v${result.latestVersionNumber}`);
125
+ if (prompt.id) lines.push(`**ID**: ${prompt.id}`);
126
+ if (prompt.handle) lines.push(`**Handle**: ${prompt.handle}`);
127
+ if (newVersion?.version != null)
128
+ lines.push(`**Version**: v${newVersion.version}`);
129
+ if (newVersion?.versionId)
130
+ lines.push(`**Version ID**: ${newVersion.versionId}`);
26
131
  lines.push(`**Commit**: ${params.commitMessage}`);
27
- if (params.tags && params.tags.length > 0)
28
- lines.push(`**Tags**: ${params.tags.join(", ")}`);
132
+
133
+ if (!newVersion) {
134
+ // The update succeeded but the re-fetch could not identify the new
135
+ // version by commit message — say so instead of silently omitting the
136
+ // version and deployment lines.
137
+ lines.push(
138
+ `**Note**: update succeeded, but the new version could not be identified in the re-fetched prompt — version and deployment details are unavailable. Run platform_get_prompt to inspect the current state.`
139
+ );
140
+ }
141
+
142
+ if (newVersion) {
143
+ const allDeployments = deploymentTagsOf(prompt);
144
+ const newTags = allDeployments
145
+ .filter(
146
+ (tag) =>
147
+ newVersion.versionId != null &&
148
+ tag.versionId === newVersion.versionId
149
+ )
150
+ .map((tag) => tag.name);
151
+ if (newTags.length > 0) {
152
+ lines.push(`**Deployed to**: ${newTags.join(", ")}`);
153
+ } else {
154
+ lines.push(`**Deployment**: not deployed`);
155
+
156
+ // Tags pointing at other versions are untouched by this update —
157
+ // surface them on their own line so no line pairs a version number
158
+ // with a deployment tag name.
159
+ const otherTags = Array.from(
160
+ new Set(
161
+ allDeployments
162
+ .filter((tag) => tag.versionId !== newVersion.versionId)
163
+ .map((tag) => tag.name)
164
+ )
165
+ );
166
+ if (otherTags.length > 0) {
167
+ lines.push(
168
+ `**Existing deployments (untouched)**: ${otherTags.join(", ")}`
169
+ );
170
+ }
171
+ }
172
+ }
29
173
 
30
174
  return lines.join("\n");
31
175
  }
176
+
177
+ async function renderTagFailure({
178
+ idOrHandle,
179
+ params,
180
+ }: {
181
+ idOrHandle: string;
182
+ params: { commitMessage: string; tags?: string[] };
183
+ }): Promise<string> {
184
+ const failedTags = params.tags?.join(", ") ?? "";
185
+ let prompt;
186
+ try {
187
+ prompt = await apiGetPrompt(idOrHandle, {
188
+ version: undefined,
189
+ tag: undefined,
190
+ });
191
+ } catch {
192
+ // The confirmation read failed too — report the tag-assignment failure
193
+ // without claiming whether a version was created.
194
+ return `Prompt update failed: could not assign tag(s) ${failedTags} to "${idOrHandle}". Whether a new version was created could not be confirmed (confirmation read failed) — run platform_get_prompt before retrying.`;
195
+ }
196
+
197
+ const matched = await identifyNewVersion({
198
+ idOrHandle,
199
+ prompt,
200
+ commitMessage: params.commitMessage,
201
+ });
202
+
203
+ if (matched) {
204
+ const lines: string[] = [];
205
+ lines.push("Prompt update partially failed.\n");
206
+ lines.push(
207
+ "A new version was created, but assigning the requested tag(s) failed."
208
+ );
209
+ if (matched.versionId) lines.push(`**Version ID**: ${matched.versionId}`);
210
+ lines.push(`**Status**: created, untagged`);
211
+ lines.push(`**Failed tag(s)**: ${failedTags}`);
212
+ return lines.join("\n");
213
+ }
214
+
215
+ return `Prompt update failed: could not assign tag(s) ${failedTags} to "${idOrHandle}".`;
216
+ }
@@ -1,4 +1,6 @@
1
1
  import { updateScenario as apiUpdateScenario } from "../langwatch-api-scenarios.js";
2
+ import type { ScenarioFieldValues } from "../schemas/suite-fields.js";
3
+ import { formatScenarioFields } from "./format-scenario.js";
2
4
 
3
5
  /**
4
6
  * Handles the platform_update_scenario MCP tool invocation.
@@ -12,6 +14,8 @@ export async function handleUpdateScenario(params: {
12
14
  situation?: string;
13
15
  criteria?: string[];
14
16
  labels?: string[];
17
+ testSuiteId?: string | null;
18
+ fields?: ScenarioFieldValues;
15
19
  }): Promise<string> {
16
20
  const { scenarioId, ...data } = params;
17
21
  const result = await apiUpdateScenario({ id: scenarioId, ...data });
@@ -27,6 +31,10 @@ export async function handleUpdateScenario(params: {
27
31
  if (Array.isArray(result.labels) && result.labels.length > 0) {
28
32
  lines.push(`**Labels**: ${result.labels.join(", ")}`);
29
33
  }
34
+ if (result.testSuiteId) {
35
+ lines.push(`**Test suite**: ${result.testSuiteId}`);
36
+ }
37
+ lines.push(...formatScenarioFields(result.fields));
30
38
 
31
39
  return lines.join("\n");
32
40
  }
@@ -0,0 +1,45 @@
1
+ import { updateTestSuite as apiUpdateTestSuite } from "../langwatch-api-test-suites.js";
2
+ import {
3
+ type EvaluatorAttachmentInput,
4
+ type SuiteField,
5
+ toWireAttachments,
6
+ } from "../schemas/suite-fields.js";
7
+ import {
8
+ formatEvaluatorAttachments,
9
+ formatSuiteFields,
10
+ } from "./format-suite-details.js";
11
+
12
+ /**
13
+ * Handles the platform_update_test_suite MCP tool invocation.
14
+ *
15
+ * Any of the name, the fields and the evaluators. A field list or an
16
+ * evaluator list replaces the one the suite holds; a key left out keeps
17
+ * what the suite has.
18
+ */
19
+ export async function handleUpdateTestSuite(params: {
20
+ id: string;
21
+ name?: string;
22
+ fields?: SuiteField[];
23
+ evaluators?: EvaluatorAttachmentInput[];
24
+ }): Promise<string> {
25
+ const suite = await apiUpdateTestSuite({
26
+ id: params.id,
27
+ ...(params.name !== undefined ? { name: params.name } : {}),
28
+ ...(params.fields !== undefined ? { fields: params.fields } : {}),
29
+ ...(params.evaluators !== undefined
30
+ ? { evaluators: toWireAttachments(params.evaluators) }
31
+ : {}),
32
+ });
33
+
34
+ return [
35
+ `Test suite "${suite.name}" updated.`,
36
+ "",
37
+ `**ID**: ${suite.id}`,
38
+ `**Slug**: ${suite.slug}`,
39
+ `**Scenarios**: ${suite.scenarioCount}`,
40
+ ...formatSuiteFields(suite.fields),
41
+ ...formatEvaluatorAttachments(suite.evaluators),
42
+ "",
43
+ `**View**: ${suite.platformUrl}`,
44
+ ].join("\n");
45
+ }
package/tsconfig.json CHANGED
@@ -13,13 +13,18 @@
13
13
  "skipLibCheck": true,
14
14
  "forceConsistentCasingInFileNames": true,
15
15
  "moduleResolution": "bundler",
16
- "rootDir": "..",
17
- // The evaluator catalog we import (langevals/ts-integration/evaluators.generated.ts)
16
+ "rootDir": "../..",
17
+ // The evaluator catalog we import (services/langevals/ts-integration/evaluators.generated.ts)
18
18
  // is Zod-first and imports `zod`, but lives outside this package's
19
19
  // node_modules tree, so plain resolution cannot find it. Point `zod` at this
20
20
  // package's own copy (a declared dependency) so the type-checker resolves it.
21
21
  // Paths are resolved relative to this tsconfig (no baseUrl — tsgo removed it).
22
22
  "paths": { "zod": ["./node_modules/zod"] },
23
+ // tsup's dts worker sets `baseUrl` itself, and TypeScript 6 makes that a
24
+ // hard error rather than a warning — this package cannot build without the
25
+ // transition flag until a d.ts bundler exists that speaks the TypeScript 7
26
+ // API. See ADR-099.
27
+ "ignoreDeprecations": "6.0",
23
28
  "isolatedModules": true,
24
29
  "jsx": "preserve",
25
30
  "incremental": true,