@langwatch/mcp-server 0.8.0 → 0.10.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 (121) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/LICENSE +7 -0
  3. package/dist/archive-project-HZBRPR2S.js +19 -0
  4. package/dist/{archive-scenario-YKGLJ4MZ.js → archive-scenario-P5RRITRM.js} +3 -3
  5. package/dist/{archive-suite-QFNDO3Y3.js → archive-suite-NUUFD2EM.js} +3 -3
  6. package/dist/{assign-prompt-tag-Y2B4CBBT.js → assign-prompt-tag-FKCCZPML.js} +2 -2
  7. package/dist/{chunk-YH2IBTEL.js → chunk-42ZHHHIJ.js} +14 -3
  8. package/dist/chunk-ADSJ2FDA.js +44 -0
  9. package/dist/{chunk-L6QENXYA.js → chunk-BB7EOKHR.js} +2 -2
  10. package/dist/chunk-BBJ5WAJB.js +24 -0
  11. package/dist/{chunk-PZUWCWOS.js → chunk-BYWYM2RP.js} +2 -2
  12. package/dist/{chunk-NACSR6B7.js → chunk-CCM4ZFGF.js} +2 -2
  13. package/dist/{chunk-FBRKHHJV.js → chunk-CHKCEZBP.js} +2 -2
  14. package/dist/{chunk-B57MZ4WI.js → chunk-FVDAZ4XG.js} +763 -90
  15. package/dist/{chunk-PO7FM7IX.js → chunk-HKY3GW45.js} +2 -2
  16. package/dist/{chunk-KZNBCKWG.js → chunk-JHOSVOHT.js} +2 -2
  17. package/dist/{chunk-HDJDFKZR.js → chunk-R7C7OUVG.js} +2 -2
  18. package/dist/{chunk-OVQF3D5R.js → chunk-TROIWSEQ.js} +2 -2
  19. package/dist/{chunk-CPUPGDNA.js → chunk-X2SEURT2.js} +2 -2
  20. package/dist/{chunk-YI3OG4PI.js → chunk-XC62KJIT.js} +3 -3
  21. package/dist/{create-agent-YRSDH7P7.js → create-agent-K5Q7BFGZ.js} +3 -3
  22. package/dist/{create-annotation-FYQNKMVJ.js → create-annotation-KN3GE3DK.js} +3 -3
  23. package/dist/create-api-key-OQI5PUNQ.js +26 -0
  24. package/dist/{create-dashboard-CORBR4YQ.js → create-dashboard-G4QA5AXP.js} +3 -3
  25. package/dist/{create-dataset-ZC63T6BB.js → create-dataset-WQSC37N7.js} +3 -3
  26. package/dist/{create-dataset-records-ILVNVKVR.js → create-dataset-records-B4RL3FLC.js} +3 -3
  27. package/dist/{create-evaluator-2AU3PXFB.js → create-evaluator-YFFQIR7E.js} +3 -3
  28. package/dist/create-mcp-server.d.ts +3 -1
  29. package/dist/create-mcp-server.js +2 -1
  30. package/dist/create-project-CUVQ67VN.js +44 -0
  31. package/dist/{create-prompt-NKRJ47YY.js → create-prompt-NGXKSP4X.js} +2 -2
  32. package/dist/{create-prompt-tag-S4JX3ULL.js → create-prompt-tag-IUKYPVHH.js} +2 -2
  33. package/dist/{create-scenario-BPRF75KF.js → create-scenario-FNBXALH3.js} +3 -3
  34. package/dist/{create-suite-4YN7D6K2.js → create-suite-DJEPD4ZB.js} +3 -3
  35. package/dist/{delete-agent-Y72R2YQA.js → delete-agent-TJI5XBBM.js} +3 -3
  36. package/dist/{delete-annotation-UHWDMHSH.js → delete-annotation-KRP53TUU.js} +3 -3
  37. package/dist/{delete-dashboard-LPWT36KN.js → delete-dashboard-N5SZJIR3.js} +3 -3
  38. package/dist/{delete-dataset-GGPQRHKX.js → delete-dataset-KDUOHL2P.js} +3 -3
  39. package/dist/{delete-dataset-records-LTG5LT26.js → delete-dataset-records-DM4W3PAW.js} +3 -3
  40. package/dist/{delete-evaluator-F7QLXDHU.js → delete-evaluator-7ZATLJOA.js} +3 -3
  41. package/dist/{delete-prompt-tag-B5UKZL45.js → delete-prompt-tag-I6HNMSVJ.js} +2 -2
  42. package/dist/{delete-workflow-4DGUDIDE.js → delete-workflow-QEO74JQR.js} +3 -3
  43. package/dist/{get-agent-YEVICUX4.js → get-agent-3KDWCP5N.js} +3 -3
  44. package/dist/{get-analytics-3W5CMU3F.js → get-analytics-GPVJX7SF.js} +2 -2
  45. package/dist/{get-dashboard-NCQHN6MW.js → get-dashboard-YPTIE5YY.js} +3 -3
  46. package/dist/{get-dataset-KBK6PH36.js → get-dataset-6DQFYITM.js} +3 -3
  47. package/dist/{get-evaluator-QDUL5LXR.js → get-evaluator-PCM6XQAU.js} +3 -3
  48. package/dist/get-project-JCMWXVLU.js +26 -0
  49. package/dist/{get-prompt-XCSSNRKR.js → get-prompt-5XAGWWBG.js} +2 -2
  50. package/dist/{get-scenario-FNRKOY7E.js → get-scenario-7XBCDZWX.js} +3 -3
  51. package/dist/{get-simulation-run-WOEKUF7K.js → get-simulation-run-AV24FJWI.js} +3 -3
  52. package/dist/{get-suite-U5AOD5TC.js → get-suite-4DBVHFEM.js} +3 -3
  53. package/dist/{get-trace-MUWEJM2Z.js → get-trace-MRBDS3HJ.js} +2 -2
  54. package/dist/{get-workflow-SOA7MT7L.js → get-workflow-UR4Y6UFX.js} +3 -3
  55. package/dist/{http-server-7DMOCB75.js → http-server-XUQLIFE7.js} +5 -3
  56. package/dist/index.js +1175 -222
  57. package/dist/{langwatch-api-annotations-Q57NDUVR.js → langwatch-api-annotations-ZJDRB4JH.js} +3 -3
  58. package/dist/{langwatch-api-dashboards-VBWWXV3Y.js → langwatch-api-dashboards-WARLI6NF.js} +3 -3
  59. package/dist/{langwatch-api-monitors-L3IY7FDT.js → langwatch-api-monitors-4XKXHYCG.js} +2 -2
  60. package/dist/{langwatch-api-secrets-S5HHF3XR.js → langwatch-api-secrets-6SHK5G7Q.js} +2 -2
  61. package/dist/{langwatch-api-triggers-ME33QE26.js → langwatch-api-triggers-5SX6R2WU.js} +2 -2
  62. package/dist/{langwatch-api-workflows-4CJQAYP2.js → langwatch-api-workflows-NLJENNXE.js} +3 -3
  63. package/dist/{list-agents-3HGIBJFM.js → list-agents-NECO7MPI.js} +3 -3
  64. package/dist/{list-annotations-KWXYVEPI.js → list-annotations-3OJEO774.js} +3 -3
  65. package/dist/list-api-keys-AWW7ROLB.js +37 -0
  66. package/dist/{list-dashboards-VQ2LH666.js → list-dashboards-DVPVVVKW.js} +3 -3
  67. package/dist/{list-dataset-records-RNRGROJP.js → list-dataset-records-I4BYORF5.js} +3 -3
  68. package/dist/{list-datasets-I4A2LT2S.js → list-datasets-WUAE5HGX.js} +3 -3
  69. package/dist/{list-evaluators-AKKRJ5MX.js → list-evaluators-B6OSVAM2.js} +3 -3
  70. package/dist/{list-model-providers-DVFEHFHZ.js → list-model-providers-NL6IP4UZ.js} +3 -3
  71. package/dist/list-projects-5FHMJ4PQ.js +42 -0
  72. package/dist/{list-prompt-tags-PL5OISUA.js → list-prompt-tags-Z2DD6FLQ.js} +2 -2
  73. package/dist/{list-prompts-7VTFWE2Z.js → list-prompts-VNXKHK7T.js} +2 -2
  74. package/dist/{list-scenarios-5BZWZ6ZN.js → list-scenarios-CFZHXL76.js} +3 -3
  75. package/dist/{list-simulation-runs-WCZ6RLPE.js → list-simulation-runs-AM2BXLBW.js} +3 -3
  76. package/dist/{list-suites-RRQ2JX5Y.js → list-suites-ZDOHAAXF.js} +3 -3
  77. package/dist/{list-workflows-UQB5FCX5.js → list-workflows-WBRBKLIT.js} +3 -3
  78. package/dist/{rename-prompt-tag-TURD6KYU.js → rename-prompt-tag-A4RJUK7Z.js} +2 -2
  79. package/dist/revoke-api-key-ZSPSPF5G.js +15 -0
  80. package/dist/{run-agent-BEJ4K4CM.js → run-agent-JFNDY7MR.js} +3 -3
  81. package/dist/{run-suite-U7V432G2.js → run-suite-TH7LO5TJ.js} +3 -3
  82. package/dist/{run-workflow-5WX7DK5T.js → run-workflow-CK3JP5U2.js} +3 -3
  83. package/dist/{search-traces-ZCHNOMTX.js → search-traces-3BBVYPC3.js} +2 -2
  84. package/dist/{set-model-provider-6CFHMCNE.js → set-model-provider-WOC5FNMV.js} +3 -3
  85. package/dist/{update-agent-UE6UWLZP.js → update-agent-YMEV5MTL.js} +3 -3
  86. package/dist/{update-dataset-4XQZZE4S.js → update-dataset-EYT5POFM.js} +3 -3
  87. package/dist/{update-dataset-record-GNNM5TKM.js → update-dataset-record-VFF5JTX4.js} +3 -3
  88. package/dist/{update-evaluator-LHVDXIZD.js → update-evaluator-6IJHFG2E.js} +3 -3
  89. package/dist/update-project-OIT4WPFI.js +24 -0
  90. package/dist/{update-prompt-3XEHECJ7.js → update-prompt-ZU2DD6KM.js} +2 -2
  91. package/dist/{update-scenario-DFPREYCD.js → update-scenario-3VUTPDRU.js} +3 -3
  92. package/dist/{update-suite-RATU5THO.js → update-suite-C6JFDQHL.js} +3 -3
  93. package/package.json +2 -2
  94. package/pnpm-workspace.yaml +7 -0
  95. package/pyproject.toml +3 -0
  96. package/src/__tests__/experiment-list-tools.unit.test.ts +157 -0
  97. package/src/__tests__/experiment-status.unit.test.ts +194 -0
  98. package/src/__tests__/get-experiment-results.unit.test.ts +313 -0
  99. package/src/__tests__/project-api-key-tools.unit.test.ts +451 -0
  100. package/src/create-mcp-server.d.ts +3 -1
  101. package/src/create-mcp-server.ts +263 -13
  102. package/src/http-server.ts +5 -0
  103. package/src/langwatch-api-api-keys.ts +54 -0
  104. package/src/langwatch-api-projects.ts +82 -0
  105. package/src/langwatch-api.ts +17 -3
  106. package/src/tools/archive-project.ts +14 -0
  107. package/src/tools/create-api-key.ts +29 -0
  108. package/src/tools/create-project.ts +45 -0
  109. package/src/tools/experiment-run-status.ts +36 -0
  110. package/src/tools/get-experiment-results.ts +306 -0
  111. package/src/tools/get-project.ts +20 -0
  112. package/src/tools/list-api-keys.ts +33 -0
  113. package/src/tools/list-experiment-runs.ts +142 -0
  114. package/src/tools/list-experiments.ts +95 -0
  115. package/src/tools/list-projects.ts +41 -0
  116. package/src/tools/revoke-api-key.ts +9 -0
  117. package/src/tools/run-experiment.ts +182 -0
  118. package/src/tools/update-project.ts +22 -0
  119. package/uv.lock +7 -3
  120. package/dist/run-evaluation-AJ7WZNQD.js +0 -64
  121. package/src/tools/run-evaluation.ts +0 -87
@@ -1,3 +1,7 @@
1
+ import {
2
+ LangWatchApiError,
3
+ makeRequest
4
+ } from "./chunk-42ZHHHIJ.js";
1
5
  import {
2
6
  requireApiKey
3
7
  } from "./chunk-KOO5733S.js";
@@ -9,7 +13,7 @@ import { z as z2 } from "zod";
9
13
  // package.json
10
14
  var package_default = {
11
15
  name: "@langwatch/mcp-server",
12
- version: "0.8.0",
16
+ version: "0.10.0",
13
17
  description: "An MCP server for Langwatch.",
14
18
  type: "module",
15
19
  main: "./dist/index.js",
@@ -60,7 +64,7 @@ var package_default = {
60
64
  "@types/node": "^25.6.0",
61
65
  "@types/yargs": "^17.0.33",
62
66
  "@typescript/native-preview": "7.0.0-dev.20250911.1",
63
- ai: "^6.0.0",
67
+ ai: "^6.0.161",
64
68
  dotenv: "^17.2.2",
65
69
  esbuild: "^0.25.0",
66
70
  eslint: "^10.2.1",
@@ -87,8 +91,7 @@ var package_default = {
87
91
  },
88
92
  pnpm: {
89
93
  overrides: {
90
- protobufjs: ">=7.5.6 <8.0.0 || >=8.0.2",
91
- "fast-uri@<3.1.2": "3.1.2"
94
+ liquidjs: ">=10.26.0"
92
95
  }
93
96
  }
94
97
  };
@@ -119,6 +122,482 @@ var createDatasetSchema = z.object({
119
122
  columnTypes: z.array(datasetColumnDefinitionSchema).optional().describe("Column definitions for the dataset")
120
123
  });
121
124
 
125
+ // src/tools/experiment-run-status.ts
126
+ var INTERRUPTED_THRESHOLD_MS = 5 * 60 * 1e3;
127
+ var deriveRunStatus = (timestamps, now = Date.now()) => {
128
+ if (timestamps.stoppedAt != null) return "stopped";
129
+ if (timestamps.finishedAt != null) return "completed";
130
+ if (timestamps.updatedAt != null && now - timestamps.updatedAt > INTERRUPTED_THRESHOLD_MS) {
131
+ return "interrupted";
132
+ }
133
+ return "running";
134
+ };
135
+ var isTerminalStatus = (status) => status === "completed" || status === "stopped";
136
+
137
+ // src/tools/get-experiment-results.ts
138
+ var DEFAULT_ROW_CAP = 50;
139
+ var rowKey = (index, targetId) => `${index}:${targetId ?? ""}`;
140
+ var summarizeEntry = (entry) => {
141
+ const candidates = ["input", "question", "query", "prompt", "user"];
142
+ for (const key of candidates) {
143
+ const value = entry[key];
144
+ if (typeof value === "string" && value.length > 0) {
145
+ return value.length > 80 ? `${value.slice(0, 77)}...` : value;
146
+ }
147
+ }
148
+ return "";
149
+ };
150
+ var isFailedEvaluation = (e) => e.status === "error" || e.passed === false;
151
+ var isFailedRow = ({
152
+ entry,
153
+ evaluations
154
+ }) => Boolean(entry.error) || evaluations.some(isFailedEvaluation);
155
+ async function handleExperimentResults(params) {
156
+ const filter = params.filter ?? "all";
157
+ const evaluatorFilter = params.evaluator?.trim();
158
+ const limit = typeof params.limit === "number" && params.limit > 0 ? Math.min(params.limit, DEFAULT_ROW_CAP) : DEFAULT_ROW_CAP;
159
+ const search = new URLSearchParams();
160
+ if (params.experimentSlug) search.set("experimentSlug", params.experimentSlug);
161
+ const qs = search.toString() ? `?${search.toString()}` : "";
162
+ let results;
163
+ try {
164
+ results = await makeRequest(
165
+ "GET",
166
+ `/api/experiments/runs/${encodeURIComponent(params.runId)}/results${qs}`
167
+ );
168
+ } catch (error) {
169
+ const message = error instanceof Error ? error.message : String(error);
170
+ const status = error instanceof LangWatchApiError ? error.status : error && typeof error === "object" && "status" in error && typeof error.status === "number" ? error.status : void 0;
171
+ if (status === 404 || status === void 0 && /404|not found/i.test(message)) {
172
+ return [
173
+ `# Evaluation Results: ${params.runId}`,
174
+ "",
175
+ "**Status**: not found",
176
+ "",
177
+ `Could not load results for run \`${params.runId}\`. The run id may be incorrect, or its run state may have expired (Redis keeps it for 24h).`,
178
+ "",
179
+ "> Pass `experimentSlug` to load results for runs older than 24h. Discover the slug with `platform_experiment_list`, then `platform_experiment_list_runs` for the run ids."
180
+ ].join("\n");
181
+ }
182
+ throw error;
183
+ }
184
+ if (!results) {
185
+ return [
186
+ `# Evaluation Results: ${params.runId}`,
187
+ "",
188
+ "**Status**: not found",
189
+ "",
190
+ `Could not load results for run \`${params.runId}\`.`,
191
+ "",
192
+ "> Pass `experimentSlug` if the run is older than 24h. Discover the slug with `platform_experiment_list`, then `platform_experiment_list_runs` to confirm the run id."
193
+ ].join("\n");
194
+ }
195
+ const runStatus2 = deriveRunStatus(results.timestamps);
196
+ const partial = !isTerminalStatus(runStatus2);
197
+ const evaluationsByRow = /* @__PURE__ */ new Map();
198
+ for (const evaluation of results.evaluations) {
199
+ if (evaluatorFilter && evaluation.evaluator !== evaluatorFilter) continue;
200
+ const key = rowKey(evaluation.index, evaluation.targetId);
201
+ const list = evaluationsByRow.get(key) ?? [];
202
+ list.push(evaluation);
203
+ evaluationsByRow.set(key, list);
204
+ }
205
+ const evaluatorNames = Array.from(
206
+ new Set(
207
+ (evaluatorFilter ? results.evaluations.filter((e) => e.evaluator === evaluatorFilter) : results.evaluations).map((e) => e.evaluator)
208
+ )
209
+ );
210
+ let rows = results.dataset.map((entry) => ({
211
+ entry,
212
+ evaluations: evaluationsByRow.get(rowKey(entry.index, entry.targetId)) ?? []
213
+ }));
214
+ if (filter === "failed") {
215
+ rows = rows.filter(
216
+ (r) => isFailedRow({ entry: r.entry, evaluations: r.evaluations })
217
+ );
218
+ }
219
+ const totalMatching = rows.length;
220
+ const rowsForSummary = rows;
221
+ const truncated = rows.length > limit;
222
+ rows = rows.slice(0, limit);
223
+ const evaluatorAverages = /* @__PURE__ */ new Map();
224
+ for (const r of rowsForSummary) {
225
+ for (const e of r.evaluations) {
226
+ const stats = evaluatorAverages.get(e.evaluator) ?? {
227
+ sum: 0,
228
+ count: 0,
229
+ passed: 0,
230
+ failed: 0,
231
+ errored: 0
232
+ };
233
+ if (typeof e.score === "number") {
234
+ stats.sum += e.score;
235
+ stats.count += 1;
236
+ }
237
+ if (e.status === "error") stats.errored += 1;
238
+ else if (e.passed === true) stats.passed += 1;
239
+ else if (e.passed === false) stats.failed += 1;
240
+ evaluatorAverages.set(e.evaluator, stats);
241
+ }
242
+ }
243
+ const lines = [];
244
+ lines.push(`# Evaluation Results: ${results.runId}`);
245
+ lines.push("");
246
+ lines.push(`**Experiment**: ${results.experimentId}`);
247
+ lines.push(`**Status**: ${runStatus2}`);
248
+ if (typeof results.total === "number" && results.total > 0) {
249
+ lines.push(
250
+ `**Progress**: ${results.progress ?? results.dataset.length}/${results.total} rows`
251
+ );
252
+ }
253
+ lines.push(`**Total rows**: ${results.dataset.length}`);
254
+ lines.push(`**Total evaluations**: ${results.evaluations.length}`);
255
+ if (filter === "failed") {
256
+ lines.push(`**Filter**: failed only`);
257
+ }
258
+ if (evaluatorFilter) {
259
+ lines.push(`**Evaluator filter**: ${evaluatorFilter}`);
260
+ }
261
+ if (partial) {
262
+ lines.push("");
263
+ lines.push(
264
+ runStatus2 === "interrupted" ? "> These are partial results. The run never sent a finished/stopped marker and has had no updates recently, so it likely was interrupted before completing. The rows below are everything recorded so far." : "> These are partial results. The run is still in progress, so more rows may appear on a later call."
265
+ );
266
+ }
267
+ lines.push("");
268
+ if (evaluatorAverages.size > 0) {
269
+ lines.push("## Evaluator Summary");
270
+ lines.push("");
271
+ lines.push("| Evaluator | Avg Score | Passed | Failed | Errored |");
272
+ lines.push("| --- | --- | --- | --- | --- |");
273
+ for (const [name, stats] of evaluatorAverages) {
274
+ const avg = stats.count > 0 ? (stats.sum / stats.count).toFixed(3) : "\u2014";
275
+ lines.push(
276
+ `| ${name} | ${avg} | ${stats.passed} | ${stats.failed} | ${stats.errored} |`
277
+ );
278
+ }
279
+ lines.push("");
280
+ }
281
+ if (rows.length === 0) {
282
+ if (filter === "failed") {
283
+ lines.push("_No rows matched the filter._");
284
+ } else if (runStatus2 === "running") {
285
+ lines.push(
286
+ "_No rows recorded yet. The run is still in progress; call again shortly._"
287
+ );
288
+ } else if (runStatus2 === "interrupted") {
289
+ lines.push("_No rows were recorded before the run was interrupted._");
290
+ } else {
291
+ lines.push("_No rows recorded for this run._");
292
+ }
293
+ return lines.join("\n");
294
+ }
295
+ lines.push(
296
+ `## Rows (${rows.length}${truncated ? ` of ${totalMatching}` : ""})`
297
+ );
298
+ lines.push("");
299
+ for (const { entry, evaluations } of rows) {
300
+ const summary = summarizeEntry(entry.entry);
301
+ lines.push(`### Row #${entry.index}${summary ? ` \u2014 ${summary}` : ""}`);
302
+ if (entry.error) {
303
+ lines.push(`- **Error**: ${entry.error}`);
304
+ }
305
+ if (entry.traceId) {
306
+ lines.push(`- **Trace ID**: \`${entry.traceId}\``);
307
+ }
308
+ if (evaluations.length === 0) {
309
+ lines.push(
310
+ evaluatorNames.length === 0 ? "- _No evaluations recorded_" : "- _No evaluations recorded for this row_"
311
+ );
312
+ }
313
+ for (const e of evaluations) {
314
+ const parts = [`**${e.evaluator}**`];
315
+ if (e.status === "error") parts.push("status=error");
316
+ else if (typeof e.score === "number") parts.push(`score=${e.score.toFixed(3)}`);
317
+ if (typeof e.passed === "boolean") {
318
+ parts.push(`passed=${e.passed ? "yes" : "no"}`);
319
+ }
320
+ if (e.label) parts.push(`label=${e.label}`);
321
+ lines.push(`- ${parts.join(" \xB7 ")}`);
322
+ if (e.details) {
323
+ lines.push(` - details: ${e.details}`);
324
+ }
325
+ }
326
+ lines.push("");
327
+ }
328
+ if (truncated) {
329
+ lines.push(
330
+ `> Output truncated to ${limit} rows of ${totalMatching} matching to protect the agent's context window. Pass \`limit\` to expand or filter further.`
331
+ );
332
+ }
333
+ return lines.join("\n");
334
+ }
335
+
336
+ // src/tools/list-experiment-runs.ts
337
+ var DEFAULT_LIMIT = 25;
338
+ var MAX_LIMIT = 100;
339
+ var formatEpoch = (epoch) => {
340
+ if (!epoch || !Number.isFinite(epoch)) return "\u2014";
341
+ return new Date(epoch).toISOString().replace("T", " ").slice(0, 19) + " UTC";
342
+ };
343
+ var passRate = (evaluations) => {
344
+ const entries = Object.values(evaluations ?? {});
345
+ if (entries.length === 0) return "\u2014";
346
+ const passed = entries.filter((e) => typeof e.averagePassed === "number");
347
+ if (passed.length === 0) {
348
+ const scored = entries.filter((e) => typeof e.averageScore === "number");
349
+ if (scored.length === 0) return "\u2014";
350
+ const avg2 = scored.reduce((sum, e) => sum + (e.averageScore ?? 0), 0) / scored.length;
351
+ return `${avg2.toFixed(2)} avg`;
352
+ }
353
+ const avg = passed.reduce((sum, e) => sum + (e.averagePassed ?? 0), 0) / passed.length;
354
+ return `${(avg * 100).toFixed(0)}% pass`;
355
+ };
356
+ var runStatus = (run) => {
357
+ if (run.timestamps.stoppedAt) return "stopped";
358
+ if (run.timestamps.finishedAt) return "completed";
359
+ return "running";
360
+ };
361
+ async function handleExperimentListRuns(params) {
362
+ const requested = typeof params.limit === "number" && params.limit > 0 ? params.limit : DEFAULT_LIMIT;
363
+ const effectiveLimit = Math.min(requested, MAX_LIMIT);
364
+ const search = new URLSearchParams();
365
+ search.set("experimentSlug", params.experimentSlug);
366
+ search.set("pageSize", String(effectiveLimit));
367
+ let result;
368
+ try {
369
+ result = await makeRequest(
370
+ "GET",
371
+ `/api/experiments/runs?${search.toString()}`
372
+ );
373
+ } catch (error) {
374
+ const message = error instanceof Error ? error.message : String(error);
375
+ if (/404|not found/i.test(message)) {
376
+ return [
377
+ `# Evaluation Runs: ${params.experimentSlug}`,
378
+ "",
379
+ "**Status**: experiment not found",
380
+ "",
381
+ `No experiment with slug \`${params.experimentSlug}\` exists in this project.`,
382
+ "",
383
+ "> Run `platform_experiment_list` to discover available experiment slugs."
384
+ ].join("\n");
385
+ }
386
+ throw error;
387
+ }
388
+ if (result.runs.length === 0) {
389
+ return [
390
+ `# Evaluation Runs: ${result.experimentSlug}`,
391
+ "",
392
+ "_This experiment has no runs yet._",
393
+ "",
394
+ `> Trigger a run with \`platform_run_experiment\` (slug: \`${result.experimentSlug}\`) to populate this list.`
395
+ ].join("\n");
396
+ }
397
+ const lines = [];
398
+ lines.push(`# Evaluation Runs: ${result.experimentSlug}`);
399
+ lines.push("");
400
+ lines.push(
401
+ `Showing ${result.runs.length} of ${result.pagination.totalHits} run${result.pagination.totalHits === 1 ? "" : "s"}.`
402
+ );
403
+ if (result.pagination.hasMore) {
404
+ lines.push(
405
+ `> More runs exist beyond this limit. Re-run with a higher \`limit\` (max ${MAX_LIMIT}) if needed.`
406
+ );
407
+ }
408
+ lines.push("");
409
+ lines.push("| Run ID | Status | Started | Finished | Result |");
410
+ lines.push("|--------|--------|---------|----------|--------|");
411
+ for (const run of result.runs) {
412
+ lines.push(
413
+ `| \`${run.runId}\` | ${runStatus(run)} | ${formatEpoch(run.timestamps.createdAt)} | ${formatEpoch(run.timestamps.finishedAt ?? null)} | ${passRate(run.summary?.evaluations ?? {})} |`
414
+ );
415
+ }
416
+ lines.push("");
417
+ lines.push(
418
+ "> Use `platform_experiment_status <runId>` for live progress, or `platform_experiment_results <runId>` for per-row results."
419
+ );
420
+ return lines.join("\n");
421
+ }
422
+
423
+ // src/tools/list-experiments.ts
424
+ var DEFAULT_LIMIT2 = 25;
425
+ var MAX_LIMIT2 = 100;
426
+ var formatTimestamp = (iso) => {
427
+ if (!iso) return "\u2014";
428
+ const d = new Date(iso);
429
+ if (Number.isNaN(d.getTime())) return iso;
430
+ return d.toISOString().replace("T", " ").slice(0, 19) + " UTC";
431
+ };
432
+ var escapeMarkdownTableCell = (value) => value.replace(/[\\|]/g, (c) => `\\${c}`).replace(/\n/g, " ");
433
+ var formatInlineCode = (value) => `\`${value.replace(/[\\`|]/g, (c) => `\\${c}`).replace(/\n/g, " ")}\``;
434
+ async function handleExperimentList(params) {
435
+ const requested = typeof params.limit === "number" && params.limit > 0 ? params.limit : DEFAULT_LIMIT2;
436
+ const effectiveLimit = Math.min(requested, MAX_LIMIT2);
437
+ const search = new URLSearchParams();
438
+ search.set("pageSize", String(effectiveLimit));
439
+ const result = await makeRequest(
440
+ "GET",
441
+ `/api/experiments?${search.toString()}`
442
+ );
443
+ if (result.experiments.length === 0) {
444
+ return [
445
+ "# Experiments",
446
+ "",
447
+ "_No experiments found in this project._",
448
+ "",
449
+ "> Create an experiment in the LangWatch dashboard, then re-run this tool to discover its slug."
450
+ ].join("\n");
451
+ }
452
+ const lines = [];
453
+ lines.push("# Experiments");
454
+ lines.push("");
455
+ lines.push(
456
+ `Showing ${result.experiments.length} of ${result.pagination.totalHits} experiment${result.pagination.totalHits === 1 ? "" : "s"}.`
457
+ );
458
+ if (result.pagination.hasMore) {
459
+ lines.push(
460
+ `> More experiments exist beyond this limit. Re-run with a higher \`limit\` (max ${MAX_LIMIT2}) if needed.`
461
+ );
462
+ }
463
+ lines.push("");
464
+ lines.push("| Slug | Name | Runs | Last Run |");
465
+ lines.push("|------|------|------|----------|");
466
+ for (const exp of result.experiments) {
467
+ const name = escapeMarkdownTableCell(exp.name ?? exp.slug);
468
+ lines.push(
469
+ `| ${formatInlineCode(exp.slug)} | ${name} | ${exp.runsCount} | ${formatTimestamp(exp.lastRunAt)} |`
470
+ );
471
+ }
472
+ lines.push("");
473
+ lines.push(
474
+ "> Use `platform_experiment_list_runs` with one of these slugs to see its runs."
475
+ );
476
+ return lines.join("\n");
477
+ }
478
+
479
+ // src/tools/run-experiment.ts
480
+ async function handleRunExperiment(params) {
481
+ const result = await makeRequest(
482
+ "POST",
483
+ `/api/experiments/${encodeURIComponent(params.slug)}/run`
484
+ );
485
+ const lines = [];
486
+ lines.push(`Evaluation started!
487
+ `);
488
+ lines.push(`**Run ID**: ${result.runId}`);
489
+ lines.push(`**Status**: ${result.status}`);
490
+ lines.push(`**Total cells**: ${result.total}`);
491
+ if (result.runUrl) lines.push(`**View at**: ${result.runUrl}`);
492
+ lines.push("");
493
+ lines.push(
494
+ `> Use \`platform_experiment_status\` with run ID "${result.runId}" to check progress.`
495
+ );
496
+ return lines.join("\n");
497
+ }
498
+ async function statusFromResults(params) {
499
+ const search = new URLSearchParams();
500
+ if (params.experimentSlug) search.set("experimentSlug", params.experimentSlug);
501
+ const qs = search.toString() ? `?${search.toString()}` : "";
502
+ let results;
503
+ try {
504
+ results = await makeRequest(
505
+ "GET",
506
+ `/api/experiments/runs/${encodeURIComponent(params.runId)}/results${qs}`
507
+ );
508
+ } catch (error) {
509
+ const code = error instanceof LangWatchApiError ? error.status : void 0;
510
+ const message = error instanceof Error ? error.message : String(error);
511
+ if (code === 404 || code === void 0 && /404|not found/i.test(message)) {
512
+ return null;
513
+ }
514
+ throw error;
515
+ }
516
+ const status = deriveRunStatus(results.timestamps);
517
+ const total = results.total ?? results.dataset?.length ?? 0;
518
+ const progress = results.progress ?? results.dataset?.length ?? 0;
519
+ const lines = [];
520
+ lines.push(`# Evaluation Run ${params.runId}
521
+ `);
522
+ lines.push(`**Status**: ${status}`);
523
+ lines.push(`**Progress**: ${progress}/${total} cells`);
524
+ if (results.timestamps.createdAt) {
525
+ lines.push(
526
+ `**Started**: ${new Date(results.timestamps.createdAt).toISOString()}`
527
+ );
528
+ }
529
+ if (results.timestamps.finishedAt) {
530
+ lines.push(
531
+ `**Finished**: ${new Date(results.timestamps.finishedAt).toISOString()}`
532
+ );
533
+ }
534
+ if (results.timestamps.stoppedAt) {
535
+ lines.push(
536
+ `**Stopped**: ${new Date(results.timestamps.stoppedAt).toISOString()}`
537
+ );
538
+ }
539
+ lines.push("");
540
+ lines.push(
541
+ "> Use `platform_experiment_results` to fetch the per-row scores (partial results are available even while running)."
542
+ );
543
+ return lines.join("\n");
544
+ }
545
+ async function handleExperimentStatus(params) {
546
+ let status;
547
+ try {
548
+ status = await makeRequest(
549
+ "GET",
550
+ `/api/experiments/runs/${encodeURIComponent(params.runId)}`
551
+ );
552
+ } catch (error) {
553
+ const code = error instanceof LangWatchApiError ? error.status : void 0;
554
+ const message = error instanceof Error ? error.message : String(error);
555
+ if (code === 404 || /404|not found/i.test(message)) {
556
+ const fallback = await statusFromResults(params);
557
+ if (fallback) return fallback;
558
+ return [
559
+ `# Evaluation Run ${params.runId}`,
560
+ "",
561
+ "**Status**: not found",
562
+ "",
563
+ `Could not find run \`${params.runId}\`. SDK-logged runs and runs older than 24h are not in the live run-state and must be resolved by experiment slug.`,
564
+ "",
565
+ "> Pass `experimentSlug`: discover it with `platform_experiment_list`, then use `platform_experiment_list_runs` for the run ids. Or fetch the rows directly with `platform_experiment_results`."
566
+ ].join("\n");
567
+ }
568
+ throw error;
569
+ }
570
+ const lines = [];
571
+ lines.push(`# Evaluation Run ${status.runId}
572
+ `);
573
+ lines.push(`**Status**: ${status.status}`);
574
+ lines.push(`**Progress**: ${status.progress}/${status.total} cells`);
575
+ if (status.startedAt) {
576
+ lines.push(`**Started**: ${new Date(status.startedAt).toISOString()}`);
577
+ }
578
+ if (status.finishedAt) {
579
+ lines.push(`**Finished**: ${new Date(status.finishedAt).toISOString()}`);
580
+ }
581
+ if (status.summary) {
582
+ lines.push("\n## Summary\n");
583
+ if (status.summary.completedCells !== void 0) {
584
+ lines.push(`**Completed**: ${status.summary.completedCells}`);
585
+ }
586
+ if (status.summary.failedCells) {
587
+ lines.push(`**Failed**: ${status.summary.failedCells}`);
588
+ }
589
+ if (status.summary.duration) {
590
+ lines.push(
591
+ `**Duration**: ${(status.summary.duration / 1e3).toFixed(1)}s`
592
+ );
593
+ }
594
+ if (status.summary.runUrl) {
595
+ lines.push(`**View results**: ${status.summary.runUrl}`);
596
+ }
597
+ }
598
+ return lines.join("\n");
599
+ }
600
+
122
601
  // src/create-mcp-server.ts
123
602
  var modelSchema = z2.string().describe(
124
603
  'Model in "provider/model-name" format, e.g., "openai/gpt-4o", "anthropic/claude-sonnet-4-5-20250929"'
@@ -259,7 +738,7 @@ function registerTools(server) {
259
738
  },
260
739
  withToolLogging("search_traces", async (params) => {
261
740
  requireApiKey();
262
- const { handleSearchTraces } = await import("./search-traces-ZCHNOMTX.js");
741
+ const { handleSearchTraces } = await import("./search-traces-3BBVYPC3.js");
263
742
  return {
264
743
  content: [{ type: "text", text: await handleSearchTraces(params) }]
265
744
  };
@@ -276,7 +755,7 @@ function registerTools(server) {
276
755
  },
277
756
  withToolLogging("get_trace", async (params) => {
278
757
  requireApiKey();
279
- const { handleGetTrace } = await import("./get-trace-MUWEJM2Z.js");
758
+ const { handleGetTrace } = await import("./get-trace-MRBDS3HJ.js");
280
759
  return {
281
760
  content: [{ type: "text", text: await handleGetTrace(params) }]
282
761
  };
@@ -302,7 +781,7 @@ function registerTools(server) {
302
781
  },
303
782
  withToolLogging("get_analytics", async (params) => {
304
783
  requireApiKey();
305
- const { handleGetAnalytics } = await import("./get-analytics-3W5CMU3F.js");
784
+ const { handleGetAnalytics } = await import("./get-analytics-GPVJX7SF.js");
306
785
  return {
307
786
  content: [{ type: "text", text: await handleGetAnalytics(params) }]
308
787
  };
@@ -336,7 +815,7 @@ NOTE: Prompts can be managed two ways. Determine which approach the user needs:
336
815
  },
337
816
  withToolLogging("platform_create_prompt", async (params) => {
338
817
  requireApiKey();
339
- const { handleCreatePrompt } = await import("./create-prompt-NKRJ47YY.js");
818
+ const { handleCreatePrompt } = await import("./create-prompt-NGXKSP4X.js");
340
819
  return {
341
820
  content: [{ type: "text", text: await handleCreatePrompt(params) }]
342
821
  };
@@ -348,7 +827,7 @@ NOTE: Prompts can be managed two ways. Determine which approach the user needs:
348
827
  {},
349
828
  withToolLogging("platform_list_prompts", async () => {
350
829
  requireApiKey();
351
- const { handleListPrompts } = await import("./list-prompts-7VTFWE2Z.js");
830
+ const { handleListPrompts } = await import("./list-prompts-VNXKHK7T.js");
352
831
  return {
353
832
  content: [{ type: "text", text: await handleListPrompts() }]
354
833
  };
@@ -372,7 +851,7 @@ NOTE: Prompts can be managed two ways. Determine which approach the user needs:
372
851
  };
373
852
  }
374
853
  requireApiKey();
375
- const { handleGetPrompt } = await import("./get-prompt-XCSSNRKR.js");
854
+ const { handleGetPrompt } = await import("./get-prompt-5XAGWWBG.js");
376
855
  return {
377
856
  content: [{ type: "text", text: await handleGetPrompt(params) }]
378
857
  };
@@ -397,7 +876,7 @@ NOTE: Prompts can be managed two ways. Determine which approach the user needs:
397
876
  },
398
877
  withToolLogging("platform_update_prompt", async (params) => {
399
878
  requireApiKey();
400
- const { handleUpdatePrompt } = await import("./update-prompt-3XEHECJ7.js");
879
+ const { handleUpdatePrompt } = await import("./update-prompt-ZU2DD6KM.js");
401
880
  return {
402
881
  content: [{ type: "text", text: await handleUpdatePrompt(params) }]
403
882
  };
@@ -413,7 +892,7 @@ NOTE: Prompts can be managed two ways. Determine which approach the user needs:
413
892
  },
414
893
  withToolLogging("platform_assign_prompt_tag", async (params) => {
415
894
  requireApiKey();
416
- const { handleAssignPromptTag } = await import("./assign-prompt-tag-Y2B4CBBT.js");
895
+ const { handleAssignPromptTag } = await import("./assign-prompt-tag-FKCCZPML.js");
417
896
  return {
418
897
  content: [{ type: "text", text: await handleAssignPromptTag(params) }]
419
898
  };
@@ -425,7 +904,7 @@ NOTE: Prompts can be managed two ways. Determine which approach the user needs:
425
904
  {},
426
905
  withToolLogging("platform_list_prompt_tags", async () => {
427
906
  requireApiKey();
428
- const { handleListPromptTags } = await import("./list-prompt-tags-PL5OISUA.js");
907
+ const { handleListPromptTags } = await import("./list-prompt-tags-Z2DD6FLQ.js");
429
908
  return {
430
909
  content: [{ type: "text", text: await handleListPromptTags() }]
431
910
  };
@@ -439,7 +918,7 @@ NOTE: Prompts can be managed two ways. Determine which approach the user needs:
439
918
  },
440
919
  withToolLogging("platform_create_prompt_tag", async (params) => {
441
920
  requireApiKey();
442
- const { handleCreatePromptTag } = await import("./create-prompt-tag-S4JX3ULL.js");
921
+ const { handleCreatePromptTag } = await import("./create-prompt-tag-IUKYPVHH.js");
443
922
  return {
444
923
  content: [{ type: "text", text: await handleCreatePromptTag(params) }]
445
924
  };
@@ -454,7 +933,7 @@ NOTE: Prompts can be managed two ways. Determine which approach the user needs:
454
933
  },
455
934
  withToolLogging("platform_rename_prompt_tag", async (params) => {
456
935
  requireApiKey();
457
- const { handleRenamePromptTag } = await import("./rename-prompt-tag-TURD6KYU.js");
936
+ const { handleRenamePromptTag } = await import("./rename-prompt-tag-A4RJUK7Z.js");
458
937
  return {
459
938
  content: [{ type: "text", text: await handleRenamePromptTag(params) }]
460
939
  };
@@ -468,7 +947,7 @@ NOTE: Prompts can be managed two ways. Determine which approach the user needs:
468
947
  },
469
948
  withToolLogging("platform_delete_prompt_tag", async (params) => {
470
949
  requireApiKey();
471
- const { handleDeletePromptTag } = await import("./delete-prompt-tag-B5UKZL45.js");
950
+ const { handleDeletePromptTag } = await import("./delete-prompt-tag-I6HNMSVJ.js");
472
951
  return {
473
952
  content: [{ type: "text", text: await handleDeletePromptTag(params) }]
474
953
  };
@@ -496,7 +975,7 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
496
975
  },
497
976
  withToolLogging("platform_create_scenario", async (params) => {
498
977
  requireApiKey();
499
- const { handleCreateScenario } = await import("./create-scenario-BPRF75KF.js");
978
+ const { handleCreateScenario } = await import("./create-scenario-FNBXALH3.js");
500
979
  return {
501
980
  content: [
502
981
  { type: "text", text: await handleCreateScenario(params) }
@@ -514,7 +993,7 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
514
993
  },
515
994
  withToolLogging("platform_list_scenarios", async (params) => {
516
995
  requireApiKey();
517
- const { handleListScenarios } = await import("./list-scenarios-5BZWZ6ZN.js");
996
+ const { handleListScenarios } = await import("./list-scenarios-CFZHXL76.js");
518
997
  return {
519
998
  content: [
520
999
  { type: "text", text: await handleListScenarios(params) }
@@ -533,7 +1012,7 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
533
1012
  },
534
1013
  withToolLogging("platform_get_scenario", async (params) => {
535
1014
  requireApiKey();
536
- const { handleGetScenario } = await import("./get-scenario-FNRKOY7E.js");
1015
+ const { handleGetScenario } = await import("./get-scenario-7XBCDZWX.js");
537
1016
  return {
538
1017
  content: [{ type: "text", text: await handleGetScenario(params) }]
539
1018
  };
@@ -551,7 +1030,7 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
551
1030
  },
552
1031
  withToolLogging("platform_update_scenario", async (params) => {
553
1032
  requireApiKey();
554
- const { handleUpdateScenario } = await import("./update-scenario-DFPREYCD.js");
1033
+ const { handleUpdateScenario } = await import("./update-scenario-3VUTPDRU.js");
555
1034
  return {
556
1035
  content: [
557
1036
  { type: "text", text: await handleUpdateScenario(params) }
@@ -567,7 +1046,7 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
567
1046
  },
568
1047
  withToolLogging("platform_archive_scenario", async (params) => {
569
1048
  requireApiKey();
570
- const { handleArchiveScenario } = await import("./archive-scenario-YKGLJ4MZ.js");
1049
+ const { handleArchiveScenario } = await import("./archive-scenario-P5RRITRM.js");
571
1050
  return {
572
1051
  content: [
573
1052
  { type: "text", text: await handleArchiveScenario(params) }
@@ -583,7 +1062,7 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
583
1062
  },
584
1063
  withToolLogging("platform_list_suites", async (params) => {
585
1064
  requireApiKey();
586
- const { handleListSuites } = await import("./list-suites-RRQ2JX5Y.js");
1065
+ const { handleListSuites } = await import("./list-suites-ZDOHAAXF.js");
587
1066
  return {
588
1067
  content: [{ type: "text", text: await handleListSuites(params) }]
589
1068
  };
@@ -598,7 +1077,7 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
598
1077
  },
599
1078
  withToolLogging("platform_get_suite", async (params) => {
600
1079
  requireApiKey();
601
- const { handleGetSuite } = await import("./get-suite-U5AOD5TC.js");
1080
+ const { handleGetSuite } = await import("./get-suite-4DBVHFEM.js");
602
1081
  return {
603
1082
  content: [{ type: "text", text: await handleGetSuite(params) }]
604
1083
  };
@@ -617,7 +1096,7 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
617
1096
  },
618
1097
  withToolLogging("platform_create_suite", async (params) => {
619
1098
  requireApiKey();
620
- const { handleCreateSuite } = await import("./create-suite-4YN7D6K2.js");
1099
+ const { handleCreateSuite } = await import("./create-suite-DJEPD4ZB.js");
621
1100
  return {
622
1101
  content: [{ type: "text", text: await handleCreateSuite(params) }]
623
1102
  };
@@ -637,7 +1116,7 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
637
1116
  },
638
1117
  withToolLogging("platform_update_suite", async (params) => {
639
1118
  requireApiKey();
640
- const { handleUpdateSuite } = await import("./update-suite-RATU5THO.js");
1119
+ const { handleUpdateSuite } = await import("./update-suite-C6JFDQHL.js");
641
1120
  return {
642
1121
  content: [{ type: "text", text: await handleUpdateSuite(params) }]
643
1122
  };
@@ -651,7 +1130,7 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
651
1130
  },
652
1131
  withToolLogging("platform_run_suite", async (params) => {
653
1132
  requireApiKey();
654
- const { handleRunSuite } = await import("./run-suite-U7V432G2.js");
1133
+ const { handleRunSuite } = await import("./run-suite-TH7LO5TJ.js");
655
1134
  return {
656
1135
  content: [{ type: "text", text: await handleRunSuite(params) }]
657
1136
  };
@@ -665,7 +1144,7 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
665
1144
  },
666
1145
  withToolLogging("platform_archive_suite", async (params) => {
667
1146
  requireApiKey();
668
- const { handleArchiveSuite } = await import("./archive-suite-QFNDO3Y3.js");
1147
+ const { handleArchiveSuite } = await import("./archive-suite-NUUFD2EM.js");
669
1148
  return {
670
1149
  content: [{ type: "text", text: await handleArchiveSuite(params) }]
671
1150
  };
@@ -682,7 +1161,7 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
682
1161
  },
683
1162
  withToolLogging("platform_list_simulation_runs", async (params) => {
684
1163
  requireApiKey();
685
- const { handleListSimulationRuns } = await import("./list-simulation-runs-WCZ6RLPE.js");
1164
+ const { handleListSimulationRuns } = await import("./list-simulation-runs-AM2BXLBW.js");
686
1165
  return {
687
1166
  content: [{ type: "text", text: await handleListSimulationRuns(params) }]
688
1167
  };
@@ -697,7 +1176,7 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
697
1176
  },
698
1177
  withToolLogging("platform_get_simulation_run", async (params) => {
699
1178
  requireApiKey();
700
- const { handleGetSimulationRun } = await import("./get-simulation-run-WOEKUF7K.js");
1179
+ const { handleGetSimulationRun } = await import("./get-simulation-run-AV24FJWI.js");
701
1180
  return {
702
1181
  content: [{ type: "text", text: await handleGetSimulationRun(params) }]
703
1182
  };
@@ -714,7 +1193,7 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
714
1193
  },
715
1194
  withToolLogging("platform_create_evaluator", async (params) => {
716
1195
  requireApiKey();
717
- const { handleCreateEvaluator } = await import("./create-evaluator-2AU3PXFB.js");
1196
+ const { handleCreateEvaluator } = await import("./create-evaluator-YFFQIR7E.js");
718
1197
  return {
719
1198
  content: [
720
1199
  { type: "text", text: await handleCreateEvaluator(params) }
@@ -728,7 +1207,7 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
728
1207
  {},
729
1208
  withToolLogging("platform_list_evaluators", async () => {
730
1209
  requireApiKey();
731
- const { handleListEvaluators } = await import("./list-evaluators-AKKRJ5MX.js");
1210
+ const { handleListEvaluators } = await import("./list-evaluators-B6OSVAM2.js");
732
1211
  return {
733
1212
  content: [{ type: "text", text: await handleListEvaluators() }]
734
1213
  };
@@ -742,7 +1221,7 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
742
1221
  },
743
1222
  withToolLogging("platform_get_evaluator", async (params) => {
744
1223
  requireApiKey();
745
- const { handleGetEvaluator } = await import("./get-evaluator-QDUL5LXR.js");
1224
+ const { handleGetEvaluator } = await import("./get-evaluator-PCM6XQAU.js");
746
1225
  return {
747
1226
  content: [{ type: "text", text: await handleGetEvaluator(params) }]
748
1227
  };
@@ -760,7 +1239,7 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
760
1239
  },
761
1240
  withToolLogging("platform_update_evaluator", async (params) => {
762
1241
  requireApiKey();
763
- const { handleUpdateEvaluator } = await import("./update-evaluator-LHVDXIZD.js");
1242
+ const { handleUpdateEvaluator } = await import("./update-evaluator-6IJHFG2E.js");
764
1243
  return {
765
1244
  content: [
766
1245
  { type: "text", text: await handleUpdateEvaluator(params) }
@@ -776,7 +1255,7 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
776
1255
  },
777
1256
  withToolLogging("platform_delete_evaluator", async (params) => {
778
1257
  requireApiKey();
779
- const { handleDeleteEvaluator } = await import("./delete-evaluator-F7QLXDHU.js");
1258
+ const { handleDeleteEvaluator } = await import("./delete-evaluator-7ZATLJOA.js");
780
1259
  return {
781
1260
  content: [{ type: "text", text: await handleDeleteEvaluator(params) }]
782
1261
  };
@@ -797,7 +1276,7 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
797
1276
  },
798
1277
  withToolLogging("platform_set_model_provider", async (params) => {
799
1278
  requireApiKey();
800
- const { handleSetModelProvider } = await import("./set-model-provider-6CFHMCNE.js");
1279
+ const { handleSetModelProvider } = await import("./set-model-provider-WOC5FNMV.js");
801
1280
  return {
802
1281
  content: [
803
1282
  { type: "text", text: await handleSetModelProvider(params) }
@@ -811,7 +1290,7 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
811
1290
  {},
812
1291
  withToolLogging("platform_list_model_providers", async () => {
813
1292
  requireApiKey();
814
- const { handleListModelProviders } = await import("./list-model-providers-DVFEHFHZ.js");
1293
+ const { handleListModelProviders } = await import("./list-model-providers-NL6IP4UZ.js");
815
1294
  return {
816
1295
  content: [{ type: "text", text: await handleListModelProviders() }]
817
1296
  };
@@ -823,7 +1302,7 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
823
1302
  {},
824
1303
  withToolLogging("platform_list_agents", async () => {
825
1304
  requireApiKey();
826
- const { handleListAgents } = await import("./list-agents-3HGIBJFM.js");
1305
+ const { handleListAgents } = await import("./list-agents-NECO7MPI.js");
827
1306
  return {
828
1307
  content: [{ type: "text", text: await handleListAgents() }]
829
1308
  };
@@ -837,7 +1316,7 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
837
1316
  },
838
1317
  withToolLogging("platform_get_agent", async (params) => {
839
1318
  requireApiKey();
840
- const { handleGetAgent } = await import("./get-agent-YEVICUX4.js");
1319
+ const { handleGetAgent } = await import("./get-agent-3KDWCP5N.js");
841
1320
  return {
842
1321
  content: [{ type: "text", text: await handleGetAgent(params) }]
843
1322
  };
@@ -853,7 +1332,7 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
853
1332
  },
854
1333
  withToolLogging("platform_create_agent", async (params) => {
855
1334
  requireApiKey();
856
- const { handleCreateAgent } = await import("./create-agent-YRSDH7P7.js");
1335
+ const { handleCreateAgent } = await import("./create-agent-K5Q7BFGZ.js");
857
1336
  const parsedConfig = params.config ? JSON.parse(params.config) : void 0;
858
1337
  return {
859
1338
  content: [{ type: "text", text: await handleCreateAgent({ ...params, config: parsedConfig }) }]
@@ -871,7 +1350,7 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
871
1350
  },
872
1351
  withToolLogging("platform_update_agent", async (params) => {
873
1352
  requireApiKey();
874
- const { handleUpdateAgent } = await import("./update-agent-UE6UWLZP.js");
1353
+ const { handleUpdateAgent } = await import("./update-agent-YMEV5MTL.js");
875
1354
  const parsedConfig = params.config ? JSON.parse(params.config) : void 0;
876
1355
  return {
877
1356
  content: [{ type: "text", text: await handleUpdateAgent({ ...params, config: parsedConfig }) }]
@@ -886,7 +1365,7 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
886
1365
  },
887
1366
  withToolLogging("platform_delete_agent", async (params) => {
888
1367
  requireApiKey();
889
- const { handleDeleteAgent } = await import("./delete-agent-Y72R2YQA.js");
1368
+ const { handleDeleteAgent } = await import("./delete-agent-TJI5XBBM.js");
890
1369
  return {
891
1370
  content: [{ type: "text", text: await handleDeleteAgent(params) }]
892
1371
  };
@@ -901,7 +1380,7 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
901
1380
  },
902
1381
  withToolLogging("platform_run_agent", async (params) => {
903
1382
  requireApiKey();
904
- const { handleRunAgent } = await import("./run-agent-BEJ4K4CM.js");
1383
+ const { handleRunAgent } = await import("./run-agent-JFNDY7MR.js");
905
1384
  return {
906
1385
  content: [{ type: "text", text: await handleRunAgent(params) }]
907
1386
  };
@@ -913,7 +1392,7 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
913
1392
  {},
914
1393
  withToolLogging("platform_list_dashboards", async () => {
915
1394
  requireApiKey();
916
- const { handleListDashboards } = await import("./list-dashboards-VQ2LH666.js");
1395
+ const { handleListDashboards } = await import("./list-dashboards-DVPVVVKW.js");
917
1396
  return {
918
1397
  content: [{ type: "text", text: await handleListDashboards() }]
919
1398
  };
@@ -927,7 +1406,7 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
927
1406
  },
928
1407
  withToolLogging("platform_get_dashboard", async (params) => {
929
1408
  requireApiKey();
930
- const { handleGetDashboard } = await import("./get-dashboard-NCQHN6MW.js");
1409
+ const { handleGetDashboard } = await import("./get-dashboard-YPTIE5YY.js");
931
1410
  return {
932
1411
  content: [{ type: "text", text: await handleGetDashboard(params) }]
933
1412
  };
@@ -941,7 +1420,7 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
941
1420
  },
942
1421
  withToolLogging("platform_create_dashboard", async (params) => {
943
1422
  requireApiKey();
944
- const { handleCreateDashboard } = await import("./create-dashboard-CORBR4YQ.js");
1423
+ const { handleCreateDashboard } = await import("./create-dashboard-G4QA5AXP.js");
945
1424
  return {
946
1425
  content: [{ type: "text", text: await handleCreateDashboard(params) }]
947
1426
  };
@@ -955,7 +1434,7 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
955
1434
  },
956
1435
  withToolLogging("platform_delete_dashboard", async (params) => {
957
1436
  requireApiKey();
958
- const { handleDeleteDashboard } = await import("./delete-dashboard-LPWT36KN.js");
1437
+ const { handleDeleteDashboard } = await import("./delete-dashboard-N5SZJIR3.js");
959
1438
  return {
960
1439
  content: [{ type: "text", text: await handleDeleteDashboard(params) }]
961
1440
  };
@@ -970,7 +1449,7 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
970
1449
  },
971
1450
  withToolLogging("platform_rename_dashboard", async (params) => {
972
1451
  requireApiKey();
973
- const { renameDashboard } = await import("./langwatch-api-dashboards-VBWWXV3Y.js");
1452
+ const { renameDashboard } = await import("./langwatch-api-dashboards-WARLI6NF.js");
974
1453
  const result = await renameDashboard(params.id, { name: params.name });
975
1454
  return {
976
1455
  content: [{ type: "text", text: `Dashboard "${result.name}" renamed successfully (ID: ${result.id}).` }]
@@ -983,7 +1462,7 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
983
1462
  {},
984
1463
  withToolLogging("platform_list_workflows", async () => {
985
1464
  requireApiKey();
986
- const { handleListWorkflows } = await import("./list-workflows-UQB5FCX5.js");
1465
+ const { handleListWorkflows } = await import("./list-workflows-WBRBKLIT.js");
987
1466
  return {
988
1467
  content: [{ type: "text", text: await handleListWorkflows() }]
989
1468
  };
@@ -997,7 +1476,7 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
997
1476
  },
998
1477
  withToolLogging("platform_get_workflow", async (params) => {
999
1478
  requireApiKey();
1000
- const { handleGetWorkflow } = await import("./get-workflow-SOA7MT7L.js");
1479
+ const { handleGetWorkflow } = await import("./get-workflow-UR4Y6UFX.js");
1001
1480
  return {
1002
1481
  content: [{ type: "text", text: await handleGetWorkflow(params) }]
1003
1482
  };
@@ -1011,7 +1490,7 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
1011
1490
  },
1012
1491
  withToolLogging("platform_delete_workflow", async (params) => {
1013
1492
  requireApiKey();
1014
- const { handleDeleteWorkflow } = await import("./delete-workflow-4DGUDIDE.js");
1493
+ const { handleDeleteWorkflow } = await import("./delete-workflow-QEO74JQR.js");
1015
1494
  return {
1016
1495
  content: [{ type: "text", text: await handleDeleteWorkflow(params) }]
1017
1496
  };
@@ -1026,7 +1505,7 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
1026
1505
  },
1027
1506
  withToolLogging("platform_run_workflow", async (params) => {
1028
1507
  requireApiKey();
1029
- const { handleRunWorkflow } = await import("./run-workflow-5WX7DK5T.js");
1508
+ const { handleRunWorkflow } = await import("./run-workflow-CK3JP5U2.js");
1030
1509
  return {
1031
1510
  content: [{ type: "text", text: await handleRunWorkflow(params) }]
1032
1511
  };
@@ -1040,7 +1519,7 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
1040
1519
  },
1041
1520
  withToolLogging("platform_list_annotations", async (params) => {
1042
1521
  requireApiKey();
1043
- const { handleListAnnotations } = await import("./list-annotations-KWXYVEPI.js");
1522
+ const { handleListAnnotations } = await import("./list-annotations-3OJEO774.js");
1044
1523
  return {
1045
1524
  content: [{ type: "text", text: await handleListAnnotations(params) }]
1046
1525
  };
@@ -1057,7 +1536,7 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
1057
1536
  },
1058
1537
  withToolLogging("platform_create_annotation", async (params) => {
1059
1538
  requireApiKey();
1060
- const { handleCreateAnnotation } = await import("./create-annotation-FYQNKMVJ.js");
1539
+ const { handleCreateAnnotation } = await import("./create-annotation-KN3GE3DK.js");
1061
1540
  return {
1062
1541
  content: [{ type: "text", text: await handleCreateAnnotation(params) }]
1063
1542
  };
@@ -1071,7 +1550,7 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
1071
1550
  },
1072
1551
  withToolLogging("platform_delete_annotation", async (params) => {
1073
1552
  requireApiKey();
1074
- const { handleDeleteAnnotation } = await import("./delete-annotation-UHWDMHSH.js");
1553
+ const { handleDeleteAnnotation } = await import("./delete-annotation-KRP53TUU.js");
1075
1554
  return {
1076
1555
  content: [{ type: "text", text: await handleDeleteAnnotation(params) }]
1077
1556
  };
@@ -1085,7 +1564,7 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
1085
1564
  },
1086
1565
  withToolLogging("platform_get_annotation", async (params) => {
1087
1566
  requireApiKey();
1088
- const { getAnnotation } = await import("./langwatch-api-annotations-Q57NDUVR.js");
1567
+ const { getAnnotation } = await import("./langwatch-api-annotations-ZJDRB4JH.js");
1089
1568
  const annotation = await getAnnotation(params.id);
1090
1569
  const lines = [
1091
1570
  `**ID**: ${annotation.id}`,
@@ -1107,7 +1586,7 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
1107
1586
  },
1108
1587
  withToolLogging("platform_list_triggers", async (params) => {
1109
1588
  requireApiKey();
1110
- const { listTriggers } = await import("./langwatch-api-triggers-ME33QE26.js");
1589
+ const { listTriggers } = await import("./langwatch-api-triggers-5SX6R2WU.js");
1111
1590
  const triggers = await listTriggers();
1112
1591
  if (params.format === "json") {
1113
1592
  return { content: [{ type: "text", text: JSON.stringify(triggers, null, 2) }] };
@@ -1140,7 +1619,7 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
1140
1619
  },
1141
1620
  withToolLogging("platform_create_trigger", async (params) => {
1142
1621
  requireApiKey();
1143
- const { createTrigger } = await import("./langwatch-api-triggers-ME33QE26.js");
1622
+ const { createTrigger } = await import("./langwatch-api-triggers-5SX6R2WU.js");
1144
1623
  let filters = {};
1145
1624
  if (params.filters) {
1146
1625
  try {
@@ -1171,7 +1650,7 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
1171
1650
  },
1172
1651
  withToolLogging("platform_update_trigger", async (params) => {
1173
1652
  requireApiKey();
1174
- const { updateTrigger } = await import("./langwatch-api-triggers-ME33QE26.js");
1653
+ const { updateTrigger } = await import("./langwatch-api-triggers-5SX6R2WU.js");
1175
1654
  const trigger = await updateTrigger(params);
1176
1655
  return { content: [{ type: "text", text: `Trigger "${trigger.name}" updated (active: ${trigger.active}).` }] };
1177
1656
  })
@@ -1184,7 +1663,7 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
1184
1663
  },
1185
1664
  withToolLogging("platform_delete_trigger", async (params) => {
1186
1665
  requireApiKey();
1187
- const { deleteTrigger } = await import("./langwatch-api-triggers-ME33QE26.js");
1666
+ const { deleteTrigger } = await import("./langwatch-api-triggers-5SX6R2WU.js");
1188
1667
  const result = await deleteTrigger(params.id);
1189
1668
  return { content: [{ type: "text", text: `Trigger ${result.id} deleted.` }] };
1190
1669
  })
@@ -1195,7 +1674,7 @@ NOTE: Scenarios can be created two ways. Determine which approach the user needs
1195
1674
  {},
1196
1675
  withToolLogging("platform_list_monitors", async () => {
1197
1676
  requireApiKey();
1198
- const { listMonitors } = await import("./langwatch-api-monitors-L3IY7FDT.js");
1677
+ const { listMonitors } = await import("./langwatch-api-monitors-4XKXHYCG.js");
1199
1678
  const monitors = await listMonitors();
1200
1679
  if (monitors.length === 0) {
1201
1680
  return { content: [{ type: "text", text: "No monitors found." }] };
@@ -1222,7 +1701,7 @@ ${lines.join("\n")}`
1222
1701
  },
1223
1702
  withToolLogging("platform_get_monitor", async (params) => {
1224
1703
  requireApiKey();
1225
- const { getMonitor } = await import("./langwatch-api-monitors-L3IY7FDT.js");
1704
+ const { getMonitor } = await import("./langwatch-api-monitors-4XKXHYCG.js");
1226
1705
  const m = await getMonitor(params.id);
1227
1706
  const lines = [
1228
1707
  `**${m.name}** (${m.id})`,
@@ -1249,7 +1728,7 @@ ${lines.join("\n")}`
1249
1728
  },
1250
1729
  withToolLogging("platform_create_monitor", async (params) => {
1251
1730
  requireApiKey();
1252
- const { createMonitor } = await import("./langwatch-api-monitors-L3IY7FDT.js");
1731
+ const { createMonitor } = await import("./langwatch-api-monitors-4XKXHYCG.js");
1253
1732
  const monitor = await createMonitor(params);
1254
1733
  return {
1255
1734
  content: [
@@ -1273,7 +1752,7 @@ ${lines.join("\n")}`
1273
1752
  },
1274
1753
  withToolLogging("platform_update_monitor", async (params) => {
1275
1754
  requireApiKey();
1276
- const { updateMonitor } = await import("./langwatch-api-monitors-L3IY7FDT.js");
1755
+ const { updateMonitor } = await import("./langwatch-api-monitors-4XKXHYCG.js");
1277
1756
  const monitor = await updateMonitor(params);
1278
1757
  return {
1279
1758
  content: [
@@ -1293,7 +1772,7 @@ ${lines.join("\n")}`
1293
1772
  },
1294
1773
  withToolLogging("platform_delete_monitor", async (params) => {
1295
1774
  requireApiKey();
1296
- const { deleteMonitor } = await import("./langwatch-api-monitors-L3IY7FDT.js");
1775
+ const { deleteMonitor } = await import("./langwatch-api-monitors-4XKXHYCG.js");
1297
1776
  const result = await deleteMonitor(params.id);
1298
1777
  return {
1299
1778
  content: [
@@ -1308,7 +1787,7 @@ ${lines.join("\n")}`
1308
1787
  {},
1309
1788
  withToolLogging("platform_list_secrets", async () => {
1310
1789
  requireApiKey();
1311
- const { listSecrets } = await import("./langwatch-api-secrets-S5HHF3XR.js");
1790
+ const { listSecrets } = await import("./langwatch-api-secrets-6SHK5G7Q.js");
1312
1791
  const secrets = await listSecrets();
1313
1792
  if (secrets.length === 0) {
1314
1793
  return { content: [{ type: "text", text: "No secrets found." }] };
@@ -1336,7 +1815,7 @@ ${lines.join("\n")}`
1336
1815
  },
1337
1816
  withToolLogging("platform_create_secret", async (params) => {
1338
1817
  requireApiKey();
1339
- const { createSecret } = await import("./langwatch-api-secrets-S5HHF3XR.js");
1818
+ const { createSecret } = await import("./langwatch-api-secrets-6SHK5G7Q.js");
1340
1819
  const secret = await createSecret({ name: params.name, value: params.value });
1341
1820
  return {
1342
1821
  content: [
@@ -1357,7 +1836,7 @@ ${lines.join("\n")}`
1357
1836
  },
1358
1837
  withToolLogging("platform_update_secret", async (params) => {
1359
1838
  requireApiKey();
1360
- const { updateSecret } = await import("./langwatch-api-secrets-S5HHF3XR.js");
1839
+ const { updateSecret } = await import("./langwatch-api-secrets-6SHK5G7Q.js");
1361
1840
  const secret = await updateSecret({ id: params.id, value: params.value });
1362
1841
  return {
1363
1842
  content: [
@@ -1377,36 +1856,95 @@ ${lines.join("\n")}`
1377
1856
  },
1378
1857
  withToolLogging("platform_delete_secret", async (params) => {
1379
1858
  requireApiKey();
1380
- const { deleteSecret } = await import("./langwatch-api-secrets-S5HHF3XR.js");
1859
+ const { deleteSecret } = await import("./langwatch-api-secrets-6SHK5G7Q.js");
1381
1860
  const result = await deleteSecret(params.id);
1382
1861
  return { content: [{ type: "text", text: `Secret ${result.id} deleted.` }] };
1383
1862
  })
1384
1863
  );
1385
1864
  server.tool(
1386
- "platform_run_evaluation",
1387
- "Start an evaluation run by slug. Returns a run ID for polling status.",
1865
+ "platform_run_experiment",
1866
+ "Start an experiment run by slug. Returns a run ID for polling status.",
1867
+ {
1868
+ slug: z2.string().describe("The experiment slug to run")
1869
+ },
1870
+ withToolLogging("platform_run_experiment", async (params) => {
1871
+ requireApiKey();
1872
+ return {
1873
+ content: [{ type: "text", text: await handleRunExperiment(params) }]
1874
+ };
1875
+ })
1876
+ );
1877
+ server.tool(
1878
+ "platform_experiment_status",
1879
+ "Check the status of an experiment run (running, completed, stopped, or interrupted) plus its progress. Works for both platform-run and SDK-logged experiments; pass experimentSlug for SDK runs or runs older than 24h.",
1880
+ {
1881
+ runId: z2.string().describe("The run ID returned from platform_run_experiment"),
1882
+ experimentSlug: z2.string().optional().describe(
1883
+ "Experiment slug \u2014 required for SDK-logged runs and runs older than 24h (the Redis run-state they would otherwise be read from is absent/expired). Use platform_experiment_list_runs to find the slug."
1884
+ )
1885
+ },
1886
+ withToolLogging("platform_experiment_status", async (params) => {
1887
+ requireApiKey();
1888
+ return {
1889
+ content: [{ type: "text", text: await handleExperimentStatus(params) }]
1890
+ };
1891
+ })
1892
+ );
1893
+ server.tool(
1894
+ "platform_experiment_list",
1895
+ "List experiments configured in the LangWatch project. Returns a markdown table with each experiment's slug, name, last-run timestamp, and run count. Use this to discover experiment slugs before drilling into runs with platform_experiment_list_runs. Output is capped (default 25, max 100) to protect the agent's context window.",
1896
+ {
1897
+ limit: z2.number().int().positive().max(100).optional().describe(
1898
+ "Maximum number of experiments to include (default 25, hard-capped at 100)"
1899
+ )
1900
+ },
1901
+ withToolLogging("platform_experiment_list", async (params) => {
1902
+ requireApiKey();
1903
+ return {
1904
+ content: [{ type: "text", text: await handleExperimentList(params) }]
1905
+ };
1906
+ })
1907
+ );
1908
+ server.tool(
1909
+ "platform_experiment_list_runs",
1910
+ "List experiment runs for a specific experiment slug. Returns a markdown table with each run's id, status, started/finished timestamps, and pass-rate summary. Use this to discover run ids before calling platform_experiment_results. Output is capped (default 25, max 100) to protect the agent's context window.",
1388
1911
  {
1389
- slug: z2.string().describe("The evaluation slug to run")
1912
+ experimentSlug: z2.string().describe(
1913
+ "Experiment slug from platform_experiment_list (e.g. 'checkout-flow')"
1914
+ ),
1915
+ limit: z2.number().int().positive().max(100).optional().describe(
1916
+ "Maximum number of runs to include (default 25, hard-capped at 100)"
1917
+ )
1390
1918
  },
1391
- withToolLogging("platform_run_evaluation", async (params) => {
1919
+ withToolLogging("platform_experiment_list_runs", async (params) => {
1392
1920
  requireApiKey();
1393
- const { handleRunEvaluation } = await import("./run-evaluation-AJ7WZNQD.js");
1394
1921
  return {
1395
- content: [{ type: "text", text: await handleRunEvaluation(params) }]
1922
+ content: [{ type: "text", text: await handleExperimentListRuns(params) }]
1396
1923
  };
1397
1924
  })
1398
1925
  );
1399
1926
  server.tool(
1400
- "platform_evaluation_status",
1401
- "Check the status of an evaluation run. Returns progress and summary when completed.",
1927
+ "platform_experiment_results",
1928
+ "Fetch per-row results for an experiment run so you can debug evaluator scores and missed rows. Serves partial results for runs that are still running or were interrupted (it does not require a completed run), with the run status noted in the output. Returns a markdown report: per-evaluator averages plus row-by-row scores and failure details. Output is capped at 50 rows to protect the agent's context window \u2014 narrow with `filter: 'failed'` or `evaluator` to see what matters.",
1402
1929
  {
1403
- runId: z2.string().describe("The run ID returned from platform_run_evaluation")
1930
+ runId: z2.string().describe("The run ID returned from platform_run_experiment"),
1931
+ experimentSlug: z2.string().optional().describe(
1932
+ "Experiment slug \u2014 required when fetching results for runs older than 24h (Redis state may have expired). Use platform_experiment_list_runs to find the slug."
1933
+ ),
1934
+ filter: z2.enum(["all", "failed"]).optional().describe(
1935
+ "Filter rows: 'failed' shows only rows that errored or failed an evaluation; 'all' (default) shows everything"
1936
+ ),
1937
+ evaluator: z2.string().optional().describe("Show only this evaluator's results (matched on `evaluator` field)"),
1938
+ limit: z2.number().int().positive().max(50).optional().describe(
1939
+ "Maximum number of rows to include in the markdown output (default 50, hard-capped at 50)."
1940
+ )
1404
1941
  },
1405
- withToolLogging("platform_evaluation_status", async (params) => {
1942
+ withToolLogging("platform_experiment_results", async (params) => {
1406
1943
  requireApiKey();
1407
- const { handleEvaluationStatus } = await import("./run-evaluation-AJ7WZNQD.js");
1408
1944
  return {
1409
- content: [{ type: "text", text: await handleEvaluationStatus(params) }]
1945
+ content: [
1946
+ { type: "text", text: await handleExperimentResults(params) }
1947
+ ]
1410
1948
  };
1411
1949
  })
1412
1950
  );
@@ -1421,7 +1959,7 @@ ${lines.join("\n")}`
1421
1959
  async (params) => {
1422
1960
  const { requireApiKey: requireApiKey2 } = await import("./config.js");
1423
1961
  requireApiKey2();
1424
- const { handleListDatasets } = await import("./list-datasets-I4A2LT2S.js");
1962
+ const { handleListDatasets } = await import("./list-datasets-WUAE5HGX.js");
1425
1963
  return {
1426
1964
  content: [{ type: "text", text: await handleListDatasets(params) }]
1427
1965
  };
@@ -1439,7 +1977,7 @@ ${lines.join("\n")}`
1439
1977
  async (params) => {
1440
1978
  const { requireApiKey: requireApiKey2 } = await import("./config.js");
1441
1979
  requireApiKey2();
1442
- const { handleGetDataset } = await import("./get-dataset-KBK6PH36.js");
1980
+ const { handleGetDataset } = await import("./get-dataset-6DQFYITM.js");
1443
1981
  return {
1444
1982
  content: [{ type: "text", text: await handleGetDataset(params) }]
1445
1983
  };
@@ -1452,7 +1990,7 @@ ${lines.join("\n")}`
1452
1990
  async (params) => {
1453
1991
  const { requireApiKey: requireApiKey2 } = await import("./config.js");
1454
1992
  requireApiKey2();
1455
- const { handleCreateDataset } = await import("./create-dataset-ZC63T6BB.js");
1993
+ const { handleCreateDataset } = await import("./create-dataset-WQSC37N7.js");
1456
1994
  return {
1457
1995
  content: [{ type: "text", text: await handleCreateDataset(params) }]
1458
1996
  };
@@ -1469,7 +2007,7 @@ ${lines.join("\n")}`
1469
2007
  async (params) => {
1470
2008
  const { requireApiKey: requireApiKey2 } = await import("./config.js");
1471
2009
  requireApiKey2();
1472
- const { handleUpdateDataset } = await import("./update-dataset-4XQZZE4S.js");
2010
+ const { handleUpdateDataset } = await import("./update-dataset-EYT5POFM.js");
1473
2011
  return {
1474
2012
  content: [{ type: "text", text: await handleUpdateDataset(params) }]
1475
2013
  };
@@ -1484,7 +2022,7 @@ ${lines.join("\n")}`
1484
2022
  async (params) => {
1485
2023
  const { requireApiKey: requireApiKey2 } = await import("./config.js");
1486
2024
  requireApiKey2();
1487
- const { handleDeleteDataset } = await import("./delete-dataset-GGPQRHKX.js");
2025
+ const { handleDeleteDataset } = await import("./delete-dataset-KDUOHL2P.js");
1488
2026
  return {
1489
2027
  content: [{ type: "text", text: await handleDeleteDataset(params) }]
1490
2028
  };
@@ -1502,7 +2040,7 @@ ${lines.join("\n")}`
1502
2040
  async (params) => {
1503
2041
  const { requireApiKey: requireApiKey2 } = await import("./config.js");
1504
2042
  requireApiKey2();
1505
- const { handleListDatasetRecords } = await import("./list-dataset-records-RNRGROJP.js");
2043
+ const { handleListDatasetRecords } = await import("./list-dataset-records-I4BYORF5.js");
1506
2044
  return {
1507
2045
  content: [
1508
2046
  { type: "text", text: await handleListDatasetRecords(params) }
@@ -1520,7 +2058,7 @@ ${lines.join("\n")}`
1520
2058
  async (params) => {
1521
2059
  const { requireApiKey: requireApiKey2 } = await import("./config.js");
1522
2060
  requireApiKey2();
1523
- const { handleCreateDatasetRecords } = await import("./create-dataset-records-ILVNVKVR.js");
2061
+ const { handleCreateDatasetRecords } = await import("./create-dataset-records-B4RL3FLC.js");
1524
2062
  return {
1525
2063
  content: [
1526
2064
  { type: "text", text: await handleCreateDatasetRecords(params) }
@@ -1539,7 +2077,7 @@ ${lines.join("\n")}`
1539
2077
  async (params) => {
1540
2078
  const { requireApiKey: requireApiKey2 } = await import("./config.js");
1541
2079
  requireApiKey2();
1542
- const { handleUpdateDatasetRecord } = await import("./update-dataset-record-GNNM5TKM.js");
2080
+ const { handleUpdateDatasetRecord } = await import("./update-dataset-record-VFF5JTX4.js");
1543
2081
  return {
1544
2082
  content: [
1545
2083
  { type: "text", text: await handleUpdateDatasetRecord(params) }
@@ -1557,7 +2095,7 @@ ${lines.join("\n")}`
1557
2095
  async (params) => {
1558
2096
  const { requireApiKey: requireApiKey2 } = await import("./config.js");
1559
2097
  requireApiKey2();
1560
- const { handleDeleteDatasetRecords } = await import("./delete-dataset-records-LTG5LT26.js");
2098
+ const { handleDeleteDatasetRecords } = await import("./delete-dataset-records-DM4W3PAW.js");
1561
2099
  return {
1562
2100
  content: [
1563
2101
  { type: "text", text: await handleDeleteDatasetRecords(params) }
@@ -1565,9 +2103,144 @@ ${lines.join("\n")}`
1565
2103
  };
1566
2104
  }
1567
2105
  );
2106
+ server.tool(
2107
+ "platform_list_projects",
2108
+ "List all projects in your LangWatch organization. Requires an org-level API key.",
2109
+ {
2110
+ page: z2.number().int().positive().optional().describe("Page number (default: 1)"),
2111
+ limit: z2.number().int().positive().max(1e3).optional().describe("Results per page (default: 100)")
2112
+ },
2113
+ withToolLogging("platform_list_projects", async (params) => {
2114
+ requireApiKey();
2115
+ const { handleListProjects } = await import("./list-projects-5FHMJ4PQ.js");
2116
+ return {
2117
+ content: [{ type: "text", text: await handleListProjects(params) }]
2118
+ };
2119
+ })
2120
+ );
2121
+ server.tool(
2122
+ "platform_get_project",
2123
+ "Get details of a specific project by its ID.",
2124
+ {
2125
+ id: z2.string().describe("The project ID")
2126
+ },
2127
+ withToolLogging("platform_get_project", async (params) => {
2128
+ requireApiKey();
2129
+ const { handleGetProject } = await import("./get-project-JCMWXVLU.js");
2130
+ return {
2131
+ content: [{ type: "text", text: await handleGetProject(params) }]
2132
+ };
2133
+ })
2134
+ );
2135
+ server.tool(
2136
+ "platform_create_project",
2137
+ `Create a new project in your LangWatch organization. Returns a one-time service API key for the project.
2138
+
2139
+ You must provide either teamId (to add the project to an existing team) or newTeamName (to create a new team for the project).`,
2140
+ {
2141
+ name: z2.string().describe("Project name"),
2142
+ language: z2.string().describe('Programming language (e.g. "python", "typescript")'),
2143
+ framework: z2.string().describe('Framework (e.g. "openai", "langchain", "custom")'),
2144
+ teamId: z2.string().optional().describe("ID of an existing team to add the project to"),
2145
+ newTeamName: z2.string().optional().describe("Name for a new team to create for this project")
2146
+ },
2147
+ withToolLogging("platform_create_project", async (params) => {
2148
+ requireApiKey();
2149
+ const { handleCreateProject } = await import("./create-project-CUVQ67VN.js");
2150
+ return {
2151
+ content: [{ type: "text", text: await handleCreateProject(params) }]
2152
+ };
2153
+ })
2154
+ );
2155
+ server.tool(
2156
+ "platform_update_project",
2157
+ "Update an existing project's name, language, framework, or PII redaction level.",
2158
+ {
2159
+ id: z2.string().describe("The project ID to update"),
2160
+ name: z2.string().optional().describe("New project name"),
2161
+ language: z2.string().optional().describe("New programming language"),
2162
+ framework: z2.string().optional().describe("New framework"),
2163
+ piiRedactionLevel: z2.enum(["STRICT", "ESSENTIAL", "DISABLED"]).optional().describe("PII redaction level")
2164
+ },
2165
+ withToolLogging("platform_update_project", async (params) => {
2166
+ requireApiKey();
2167
+ const { handleUpdateProject } = await import("./update-project-OIT4WPFI.js");
2168
+ return {
2169
+ content: [{ type: "text", text: await handleUpdateProject(params) }]
2170
+ };
2171
+ })
2172
+ );
2173
+ server.tool(
2174
+ "platform_archive_project",
2175
+ "Archive (soft-delete) a project. The project can be restored later if needed.",
2176
+ {
2177
+ id: z2.string().describe("The project ID to archive")
2178
+ },
2179
+ withToolLogging("platform_archive_project", async (params) => {
2180
+ requireApiKey();
2181
+ const { handleArchiveProject } = await import("./archive-project-HZBRPR2S.js");
2182
+ return {
2183
+ content: [{ type: "text", text: await handleArchiveProject(params) }]
2184
+ };
2185
+ })
2186
+ );
2187
+ server.tool(
2188
+ "platform_list_api_keys",
2189
+ "List all API keys in your LangWatch organization. Shows key metadata, status (active/revoked/expired), and role bindings. API key tokens are never returned.",
2190
+ {},
2191
+ withToolLogging("platform_list_api_keys", async () => {
2192
+ requireApiKey();
2193
+ const { handleListApiKeys } = await import("./list-api-keys-AWW7ROLB.js");
2194
+ return {
2195
+ content: [{ type: "text", text: await handleListApiKeys() }]
2196
+ };
2197
+ })
2198
+ );
2199
+ server.tool(
2200
+ "platform_create_api_key",
2201
+ `Create a new API key. Two types available:
2202
+
2203
+ - **personal**: Tied to a user, requires explicit role bindings (e.g. ADMIN on PROJECT:proj_123).
2204
+ - **service**: Not tied to a user. Use projectIds for project-scoped service keys, or omit for org-wide access.
2205
+
2206
+ The token is returned once and cannot be retrieved again.`,
2207
+ {
2208
+ keyType: z2.enum(["personal", "service"]).describe("Key type: 'personal' (user-tied) or 'service' (headless)"),
2209
+ name: z2.string().describe("Key name (max 100 chars)"),
2210
+ description: z2.string().optional().describe("Key description (max 500 chars)"),
2211
+ expiresAt: z2.string().optional().describe("Expiration date (ISO string)"),
2212
+ bindings: z2.array(z2.object({
2213
+ role: z2.enum(["ADMIN", "MEMBER", "VIEWER"]).describe("Role"),
2214
+ scopeType: z2.enum(["ORGANIZATION", "TEAM", "PROJECT"]).describe("Scope type"),
2215
+ scopeId: z2.string().describe("Scope ID (org, team, or project ID)")
2216
+ })).optional().describe("Role bindings (required for personal keys)"),
2217
+ projectIds: z2.array(z2.string()).optional().describe("Project IDs for service keys (creates ADMIN bindings per project)")
2218
+ },
2219
+ withToolLogging("platform_create_api_key", async (params) => {
2220
+ requireApiKey();
2221
+ const { handleCreateApiKey } = await import("./create-api-key-OQI5PUNQ.js");
2222
+ return {
2223
+ content: [{ type: "text", text: await handleCreateApiKey(params) }]
2224
+ };
2225
+ })
2226
+ );
2227
+ server.tool(
2228
+ "platform_revoke_api_key",
2229
+ "Revoke an API key. Once revoked, the key can no longer be used for authentication.",
2230
+ {
2231
+ id: z2.string().describe("The API key ID to revoke")
2232
+ },
2233
+ withToolLogging("platform_revoke_api_key", async (params) => {
2234
+ requireApiKey();
2235
+ const { handleRevokeApiKey } = await import("./revoke-api-key-ZSPSPF5G.js");
2236
+ return {
2237
+ content: [{ type: "text", text: await handleRevokeApiKey(params) }]
2238
+ };
2239
+ })
2240
+ );
1568
2241
  }
1569
2242
 
1570
2243
  export {
1571
2244
  createMcpServer
1572
2245
  };
1573
- //# sourceMappingURL=chunk-B57MZ4WI.js.map
2246
+ //# sourceMappingURL=chunk-FVDAZ4XG.js.map