@2kw/ai-mcp-server 6.0.0 → 6.1.0-dev.10

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.
@@ -103,6 +103,118 @@ export function register(server, client) {
103
103
  };
104
104
  }
105
105
  });
106
+ // ── update_dataset ─────────────────────────────────────────────
107
+ server.tool("2kw_update_dataset", "Update an existing dataset's details.", {
108
+ datasetId: z.string().describe("The dataset ID"),
109
+ name: z.string().optional().describe("New name"),
110
+ description: z.string().optional().describe("New description"),
111
+ type: z.string().optional().describe("New type"),
112
+ inputSchema: z.unknown().optional().describe("New input JSON Schema definition"),
113
+ expectedOutputSchema: z.unknown().optional().describe("New expected output JSON Schema definition"),
114
+ metadata: z.unknown().optional().describe("Arbitrary metadata (JSON object)"),
115
+ }, async ({ datasetId, name, description, type, inputSchema, expectedOutputSchema, metadata }) => {
116
+ try {
117
+ const body = {};
118
+ if (name !== undefined)
119
+ body.name = name;
120
+ if (description !== undefined)
121
+ body.description = description;
122
+ if (type !== undefined)
123
+ body.type = type;
124
+ if (inputSchema !== undefined)
125
+ body.inputSchema = inputSchema;
126
+ if (expectedOutputSchema !== undefined)
127
+ body.expectedOutputSchema = expectedOutputSchema;
128
+ if (metadata !== undefined)
129
+ body.metadata = metadata;
130
+ const { data } = await client.PUT("/v1/datasets/{id}", {
131
+ params: { path: { id: datasetId } },
132
+ body: body,
133
+ });
134
+ return {
135
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
136
+ };
137
+ }
138
+ catch (error) {
139
+ return {
140
+ content: [{ type: "text", text: formatErrorForMcp(error) }],
141
+ isError: true,
142
+ };
143
+ }
144
+ });
145
+ // ── delete_dataset ─────────────────────────────────────────────
146
+ server.tool("2kw_delete_dataset", "Delete a dataset and all its versions and items. Requires admin role.", {
147
+ datasetId: z.string().describe("The dataset ID"),
148
+ }, async ({ datasetId }) => {
149
+ try {
150
+ await client.DELETE("/v1/datasets/{id}", {
151
+ params: { path: { id: datasetId } },
152
+ });
153
+ return {
154
+ content: [{ type: "text", text: `Dataset ${datasetId} deleted.` }],
155
+ };
156
+ }
157
+ catch (error) {
158
+ return {
159
+ content: [{ type: "text", text: formatErrorForMcp(error) }],
160
+ isError: true,
161
+ };
162
+ }
163
+ });
164
+ // ── get_dataset_versions ────────────────────────────────────────
165
+ server.tool("2kw_get_dataset_versions", "List all versions of a dataset with pagination.", {
166
+ datasetId: z.string().describe("The dataset ID"),
167
+ page: z.number().optional().default(0).describe("Page number (0-based)"),
168
+ size: z.number().optional().default(20).describe("Page size"),
169
+ }, async ({ datasetId, page, size }) => {
170
+ try {
171
+ const { data } = await client.GET("/v1/datasets/{id}/versions", {
172
+ params: {
173
+ path: { id: datasetId },
174
+ query: { pageable: { page, size } },
175
+ },
176
+ });
177
+ const result = data;
178
+ const lines = (result.content ?? []).map((v) => `- Version ${v.versionNumber} (id: ${v.id}, items: ${v.itemCount ?? 0})${v.changeDescription ? ` — ${v.changeDescription}` : ""}`);
179
+ return {
180
+ content: [
181
+ {
182
+ type: "text",
183
+ text: `Versions (${result.totalElements} total, page ${(result.number ?? 0) + 1}/${result.totalPages}):\n${lines.join("\n") || "(none)"}`,
184
+ },
185
+ ],
186
+ };
187
+ }
188
+ catch (error) {
189
+ return {
190
+ content: [{ type: "text", text: formatErrorForMcp(error) }],
191
+ isError: true,
192
+ };
193
+ }
194
+ });
195
+ // ── get_dataset_version ─────────────────────────────────────────
196
+ server.tool("2kw_get_dataset_version", "Get a specific dataset version by ID.", {
197
+ datasetId: z.string().describe("The dataset ID"),
198
+ versionId: z.string().describe("The dataset version ID"),
199
+ }, async ({ datasetId, versionId }) => {
200
+ try {
201
+ const { data } = await client.GET("/v1/datasets/{id}/versions/{versionId}", {
202
+ params: {
203
+ // OpenAPI spec omits `id` in declared path params though `{id}` is in the URL template.
204
+ path: { id: datasetId, versionId },
205
+ },
206
+ });
207
+ return {
208
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
209
+ };
210
+ }
211
+ catch (error) {
212
+ return {
213
+ content: [{ type: "text", text: formatErrorForMcp(error) }],
214
+ isError: true,
215
+ };
216
+ }
217
+ });
106
218
  // ── create_dataset_version ─────────────────────────────────────
107
219
  server.tool("2kw_create_dataset_version", "Create a new version of a dataset.", {
108
220
  datasetId: z.string().describe("The dataset ID"),
@@ -160,6 +272,40 @@ export function register(server, client) {
160
272
  };
161
273
  }
162
274
  });
275
+ // ── add_dataset_items ──────────────────────────────────────────
276
+ server.tool("2kw_add_dataset_items", "Add multiple items to a dataset in a single request. Uses the latest version unless versionId is specified.", {
277
+ datasetId: z.string().describe("The dataset ID"),
278
+ versionId: z.string().optional().describe("The dataset version ID (defaults to latest)"),
279
+ items: z
280
+ .array(z.object({
281
+ input: z.unknown().describe("Item input data (JSON object)"),
282
+ expectedOutput: z.unknown().optional().describe("Expected output (JSON object)"),
283
+ tags: z.array(z.string()).optional().describe("Tags for the item"),
284
+ metadata: z.unknown().optional().describe("Arbitrary metadata (JSON object)"),
285
+ }))
286
+ .min(1)
287
+ .describe("Items to add"),
288
+ }, async ({ datasetId, versionId, items }) => {
289
+ try {
290
+ const resolvedVersionId = versionId ?? (await resolveLatestVersionId(client, datasetId));
291
+ const { data } = await client.POST("/v1/datasets/{id}/versions/{versionId}/items/bulk", {
292
+ params: {
293
+ // OpenAPI spec omits `id` in declared path params though `{id}` is in the URL template.
294
+ path: { id: datasetId, versionId: resolvedVersionId },
295
+ },
296
+ body: items,
297
+ });
298
+ return {
299
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
300
+ };
301
+ }
302
+ catch (error) {
303
+ return {
304
+ content: [{ type: "text", text: formatErrorForMcp(error) }],
305
+ isError: true,
306
+ };
307
+ }
308
+ });
163
309
  // ── get_dataset_items ──────────────────────────────────────────
164
310
  server.tool("2kw_get_dataset_items", "List items in a dataset with pagination. Uses the latest version unless versionId is specified.", {
165
311
  datasetId: z.string().describe("The dataset ID"),
@@ -196,5 +342,63 @@ export function register(server, client) {
196
342
  };
197
343
  }
198
344
  });
345
+ // ── delete_dataset_item ─────────────────────────────────────────
346
+ server.tool("2kw_delete_dataset_item", "Soft-delete a dataset item by marking it as deleted. The item is retained for historical " +
347
+ "traceability (e.g. past run results) but excluded from default listings. Uses the latest " +
348
+ "version unless versionId is specified.", {
349
+ datasetId: z.string().describe("The dataset ID"),
350
+ itemId: z.string().describe("The dataset item ID"),
351
+ versionId: z.string().optional().describe("The dataset version ID (defaults to latest)"),
352
+ }, async ({ datasetId, itemId, versionId }) => {
353
+ try {
354
+ const resolvedVersionId = versionId ?? (await resolveLatestVersionId(client, datasetId));
355
+ await client.DELETE("/v1/datasets/{id}/versions/{versionId}/items/{itemId}", {
356
+ params: {
357
+ // OpenAPI spec omits `id` in declared path params though `{id}` is in the URL template.
358
+ path: { id: datasetId, versionId: resolvedVersionId, itemId },
359
+ },
360
+ });
361
+ return {
362
+ content: [{ type: "text", text: `Item ${itemId} deleted.` }],
363
+ };
364
+ }
365
+ catch (error) {
366
+ return {
367
+ content: [{ type: "text", text: formatErrorForMcp(error) }],
368
+ isError: true,
369
+ };
370
+ }
371
+ });
372
+ // ── update_dataset_item_expected_output ──────────────────────────
373
+ server.tool("2kw_update_dataset_item_expected_output", "Update only the expectedOutput of a dataset item. Other fields (input, tags, metadata) are " +
374
+ "immutable after creation. Uses the latest version unless versionId is specified.", {
375
+ datasetId: z.string().describe("The dataset ID"),
376
+ itemId: z.string().describe("The dataset item ID"),
377
+ versionId: z.string().optional().describe("The dataset version ID (defaults to latest)"),
378
+ expectedOutput: z.unknown().optional().describe("New expected output (JSON object); omit to clear it"),
379
+ }, async ({ datasetId, itemId, versionId, expectedOutput }) => {
380
+ try {
381
+ const resolvedVersionId = versionId ?? (await resolveLatestVersionId(client, datasetId));
382
+ const body = {};
383
+ if (expectedOutput !== undefined)
384
+ body.expectedOutput = expectedOutput;
385
+ const { data } = await client.PATCH("/v1/datasets/{id}/versions/{versionId}/items/{itemId}/expected-output", {
386
+ params: {
387
+ // OpenAPI spec omits `id` in declared path params though `{id}` is in the URL template.
388
+ path: { id: datasetId, versionId: resolvedVersionId, itemId },
389
+ },
390
+ body: body,
391
+ });
392
+ return {
393
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
394
+ };
395
+ }
396
+ catch (error) {
397
+ return {
398
+ content: [{ type: "text", text: formatErrorForMcp(error) }],
399
+ isError: true,
400
+ };
401
+ }
402
+ });
199
403
  }
200
404
  //# sourceMappingURL=datasets.js.map
@@ -98,6 +98,61 @@ export function register(server, client) {
98
98
  };
99
99
  }
100
100
  });
101
+ // ── update_experiment ───────────────────────────────────────────
102
+ server.tool("2kw_update_experiment", "Update an existing experiment's details.", {
103
+ experimentId: z.string().describe("The experiment ID"),
104
+ name: z.string().optional().describe("New name"),
105
+ description: z.string().optional().describe("New description"),
106
+ type: z.string().optional().describe("New type"),
107
+ datasetVersionId: z.string().optional().describe("Dataset version ID to evaluate against"),
108
+ metadata: z.unknown().optional().describe("Arbitrary metadata (JSON object)"),
109
+ }, async ({ experimentId, name, description, type, datasetVersionId, metadata }) => {
110
+ try {
111
+ const body = {};
112
+ if (name !== undefined)
113
+ body.name = name;
114
+ if (description !== undefined)
115
+ body.description = description;
116
+ if (type !== undefined)
117
+ body.type = type;
118
+ if (datasetVersionId !== undefined)
119
+ body.datasetVersionId = datasetVersionId;
120
+ if (metadata !== undefined)
121
+ body.metadata = metadata;
122
+ const { data } = await client.PUT("/v1/experiments/{id}", {
123
+ params: { path: { id: experimentId } },
124
+ body: body,
125
+ });
126
+ return {
127
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
128
+ };
129
+ }
130
+ catch (error) {
131
+ return {
132
+ content: [{ type: "text", text: formatErrorForMcp(error) }],
133
+ isError: true,
134
+ };
135
+ }
136
+ });
137
+ // ── delete_experiment ───────────────────────────────────────────
138
+ server.tool("2kw_delete_experiment", "Delete an experiment and all associated data. Requires admin role.", {
139
+ experimentId: z.string().describe("The experiment ID"),
140
+ }, async ({ experimentId }) => {
141
+ try {
142
+ await client.DELETE("/v1/experiments/{id}", {
143
+ params: { path: { id: experimentId } },
144
+ });
145
+ return {
146
+ content: [{ type: "text", text: `Experiment ${experimentId} deleted.` }],
147
+ };
148
+ }
149
+ catch (error) {
150
+ return {
151
+ content: [{ type: "text", text: formatErrorForMcp(error) }],
152
+ isError: true,
153
+ };
154
+ }
155
+ });
101
156
  // ── add_variant ─────────────────────────────────────────────────
102
157
  server.tool("2kw_add_variant", "Add a variant to an experiment with a task type and configuration.", {
103
158
  experimentId: z.string().describe("The experiment ID"),
@@ -128,6 +183,89 @@ export function register(server, client) {
128
183
  };
129
184
  }
130
185
  });
186
+ // ── get_variants ────────────────────────────────────────────────
187
+ server.tool("2kw_get_variants", "List all variants for an experiment.", {
188
+ experimentId: z.string().describe("The experiment ID"),
189
+ }, async ({ experimentId }) => {
190
+ try {
191
+ const { data } = await client.GET("/v1/experiments/{id}/variants", {
192
+ params: { path: { id: experimentId } },
193
+ });
194
+ const variants = data ?? [];
195
+ const lines = variants.map((v) => `- ${v.name} (id: ${v.id}, taskType: ${v.taskType})${v.description ? ` — ${v.description}` : ""}`);
196
+ return {
197
+ content: [
198
+ {
199
+ type: "text",
200
+ text: `Variants (${variants.length} total):\n${lines.join("\n") || "(none)"}`,
201
+ },
202
+ ],
203
+ };
204
+ }
205
+ catch (error) {
206
+ return {
207
+ content: [{ type: "text", text: formatErrorForMcp(error) }],
208
+ isError: true,
209
+ };
210
+ }
211
+ });
212
+ // ── update_variant ──────────────────────────────────────────────
213
+ server.tool("2kw_update_variant", "Update an existing variant's details.", {
214
+ experimentId: z.string().describe("The experiment ID"),
215
+ variantId: z.string().describe("The variant ID"),
216
+ name: z.string().optional().describe("New variant name"),
217
+ taskType: z.string().optional().describe("New task type"),
218
+ configuration: z.unknown().optional().describe("New variant configuration (JSON object)"),
219
+ description: z.string().optional().describe("New variant description"),
220
+ sortOrder: z.number().optional().describe("New sort order for display"),
221
+ }, async ({ experimentId, variantId, name, taskType, configuration, description, sortOrder }) => {
222
+ try {
223
+ const body = {};
224
+ if (name !== undefined)
225
+ body.name = name;
226
+ if (taskType !== undefined)
227
+ body.taskType = taskType;
228
+ if (configuration !== undefined)
229
+ body.configuration = configuration;
230
+ if (description !== undefined)
231
+ body.description = description;
232
+ if (sortOrder !== undefined)
233
+ body.sortOrder = sortOrder;
234
+ const { data } = await client.PUT("/v1/experiments/{id}/variants/{variantId}", {
235
+ params: { path: { id: experimentId, variantId } },
236
+ body: body,
237
+ });
238
+ return {
239
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
240
+ };
241
+ }
242
+ catch (error) {
243
+ return {
244
+ content: [{ type: "text", text: formatErrorForMcp(error) }],
245
+ isError: true,
246
+ };
247
+ }
248
+ });
249
+ // ── delete_variant ──────────────────────────────────────────────
250
+ server.tool("2kw_delete_variant", "Delete a variant from an experiment. Requires admin role.", {
251
+ experimentId: z.string().describe("The experiment ID"),
252
+ variantId: z.string().describe("The variant ID"),
253
+ }, async ({ experimentId, variantId }) => {
254
+ try {
255
+ await client.DELETE("/v1/experiments/{id}/variants/{variantId}", {
256
+ params: { path: { id: experimentId, variantId } },
257
+ });
258
+ return {
259
+ content: [{ type: "text", text: `Variant ${variantId} deleted.` }],
260
+ };
261
+ }
262
+ catch (error) {
263
+ return {
264
+ content: [{ type: "text", text: formatErrorForMcp(error) }],
265
+ isError: true,
266
+ };
267
+ }
268
+ });
131
269
  // ── run_experiment ──────────────────────────────────────────────
132
270
  server.tool("2kw_run_experiment", "Start an experiment run. Returns immediately with the run ID — use 2kw_get_experiment_runs to poll for completion.", {
133
271
  experimentId: z.string().describe("The experiment ID"),
@@ -189,6 +327,66 @@ export function register(server, client) {
189
327
  };
190
328
  }
191
329
  });
330
+ // ── get_run ─────────────────────────────────────────────────────
331
+ server.tool("2kw_get_run", "Get a single experiment run by ID, including status, progress, and baseline flag.", {
332
+ experimentId: z.string().describe("The experiment ID"),
333
+ runId: z.string().describe("The run ID"),
334
+ }, async ({ experimentId, runId }) => {
335
+ try {
336
+ const { data } = await client.GET("/v1/experiments/{id}/runs/{runId}", {
337
+ params: { path: { id: experimentId, runId } },
338
+ });
339
+ return {
340
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
341
+ };
342
+ }
343
+ catch (error) {
344
+ return {
345
+ content: [{ type: "text", text: formatErrorForMcp(error) }],
346
+ isError: true,
347
+ };
348
+ }
349
+ });
350
+ // ── set_baseline ────────────────────────────────────────────────
351
+ server.tool("2kw_set_baseline", "Mark a run as the baseline for its variant. Any prior baseline on the same variant is cleared.", {
352
+ experimentId: z.string().describe("The experiment ID"),
353
+ runId: z.string().describe("The run ID"),
354
+ }, async ({ experimentId, runId }) => {
355
+ try {
356
+ const { data } = await client.PUT("/v1/experiments/{id}/runs/{runId}/baseline", {
357
+ params: { path: { id: experimentId, runId } },
358
+ });
359
+ return {
360
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
361
+ };
362
+ }
363
+ catch (error) {
364
+ return {
365
+ content: [{ type: "text", text: formatErrorForMcp(error) }],
366
+ isError: true,
367
+ };
368
+ }
369
+ });
370
+ // ── clear_baseline ──────────────────────────────────────────────
371
+ server.tool("2kw_clear_baseline", "Unmark a run as the baseline for its variant.", {
372
+ experimentId: z.string().describe("The experiment ID"),
373
+ runId: z.string().describe("The run ID"),
374
+ }, async ({ experimentId, runId }) => {
375
+ try {
376
+ const { data } = await client.DELETE("/v1/experiments/{id}/runs/{runId}/baseline", {
377
+ params: { path: { id: experimentId, runId } },
378
+ });
379
+ return {
380
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
381
+ };
382
+ }
383
+ catch (error) {
384
+ return {
385
+ content: [{ type: "text", text: formatErrorForMcp(error) }],
386
+ isError: true,
387
+ };
388
+ }
389
+ });
192
390
  // ── get_run_results ─────────────────────────────────────────────
193
391
  server.tool("2kw_get_run_results", "Get detailed results for an experiment run, including output, token usage, cost, and errors per dataset item.", {
194
392
  experimentId: z.string().describe("The experiment ID"),
@@ -227,5 +425,54 @@ export function register(server, client) {
227
425
  };
228
426
  }
229
427
  });
428
+ // ── get_run_regression ───────────────────────────────────────────
429
+ server.tool("2kw_get_run_regression", "Compare an experiment run against a baseline run on the same variant, reporting regressed, improved, and ground-truth-changed items. If baselineRunId is omitted, the baseline is resolved as (1) the run flagged baseline on the same variant, or (2) the most recent prior COMPLETED run on the same variant.", {
430
+ experimentId: z.string().describe("The experiment ID"),
431
+ runId: z.string().describe("The run ID"),
432
+ baselineRunId: z.string().optional().describe("Explicit baseline run ID to compare against"),
433
+ threshold: z.number().optional().describe("Score delta threshold for classifying a regression"),
434
+ }, async ({ experimentId, runId, baselineRunId, threshold }) => {
435
+ try {
436
+ const { data } = await client.GET("/v1/experiments/{id}/runs/{runId}/regression", {
437
+ params: {
438
+ path: { id: experimentId, runId },
439
+ query: { baselineRunId, threshold },
440
+ },
441
+ });
442
+ return {
443
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
444
+ };
445
+ }
446
+ catch (error) {
447
+ return {
448
+ content: [{ type: "text", text: formatErrorForMcp(error) }],
449
+ isError: true,
450
+ };
451
+ }
452
+ });
453
+ // ── get_experiment_comparison ────────────────────────────────────
454
+ server.tool("2kw_get_experiment_comparison", "Get the paged comparison matrix for an experiment: dataset items joined with the latest completed run per variant, plus per-variant aggregates over all results.", {
455
+ experimentId: z.string().describe("The experiment ID"),
456
+ page: z.number().optional().default(0).describe("Page number (0-based)"),
457
+ size: z.number().optional().default(20).describe("Page size"),
458
+ }, async ({ experimentId, page, size }) => {
459
+ try {
460
+ const { data } = await client.GET("/v1/experiments/{id}/comparison", {
461
+ params: {
462
+ path: { id: experimentId },
463
+ query: { pageable: { page, size } },
464
+ },
465
+ });
466
+ return {
467
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
468
+ };
469
+ }
470
+ catch (error) {
471
+ return {
472
+ content: [{ type: "text", text: formatErrorForMcp(error) }],
473
+ isError: true,
474
+ };
475
+ }
476
+ });
230
477
  }
231
478
  //# sourceMappingURL=experiments.js.map
@@ -166,6 +166,27 @@ export function register(server, client) {
166
166
  };
167
167
  }
168
168
  });
169
+ // ── delete_extraction ────────────────────────────────────────────────────
170
+ server.tool("2kw_delete_extraction", "Delete an extraction and all associated results. Requires admin role. Usage records are preserved.", {
171
+ extractionId: z.string().describe("The extraction ID"),
172
+ }, async ({ extractionId }) => {
173
+ try {
174
+ await client.DELETE("/v1/extractions/{id}", {
175
+ params: { path: { id: extractionId } },
176
+ });
177
+ return {
178
+ content: [
179
+ { type: "text", text: `Extraction ${extractionId} deleted.` },
180
+ ],
181
+ };
182
+ }
183
+ catch (error) {
184
+ return {
185
+ content: [{ type: "text", text: formatErrorForMcp(error) }],
186
+ isError: true,
187
+ };
188
+ }
189
+ });
169
190
  // ── estimate_tokens ─────────────────────────────────────────────────────
170
191
  server.tool("2kw_estimate_tokens", "Estimate token usage for an extraction without executing it. Useful for cost estimation.", {
171
192
  schemaId: z.string().describe("The schema ID"),
@@ -0,0 +1,4 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { ApiClient } from "../client.js";
3
+ export declare function register(server: McpServer, client: ApiClient): void;
4
+ //# sourceMappingURL=files.d.ts.map
@@ -0,0 +1,143 @@
1
+ import { z } from "zod";
2
+ import { readFile, writeFile } from "node:fs/promises";
3
+ import { basename } from "node:path";
4
+ import { BackboneApiError, formatErrorForMcp } from "../errors.js";
5
+ import { getMimeType } from "../mime.js";
6
+ const FilePurpose = z.enum(["agent_input", "knowledge"]);
7
+ export function register(server, client) {
8
+ // ── list_files ────────────────────────────────────────────────────────────
9
+ server.tool("2kw_list_files", "List the organization's files as a page, newest first by default, optionally narrowed by purpose.", {
10
+ purpose: FilePurpose.optional().describe("Narrow the list to one purpose"),
11
+ page: z.number().optional().default(0).describe("Page number (0-based)"),
12
+ size: z.number().optional().default(20).describe("Page size"),
13
+ }, async ({ purpose, page, size }) => {
14
+ try {
15
+ const { data } = await client.GET("/v1/files", {
16
+ params: { query: { purpose, pageable: { page, size } } },
17
+ });
18
+ const result = data;
19
+ const lines = (result.content ?? []).map((f) => `- ${f.filename} (id: ${f.id}, purpose: ${f.purpose}, bytes: ${f.bytes})`);
20
+ return {
21
+ content: [
22
+ {
23
+ type: "text",
24
+ text: `Files (${result.totalElements} total, page ${(result.number ?? 0) + 1}/${result.totalPages}):\n${lines.join("\n") || "(none)"}`,
25
+ },
26
+ ],
27
+ };
28
+ }
29
+ catch (error) {
30
+ return {
31
+ content: [{ type: "text", text: formatErrorForMcp(error) }],
32
+ isError: true,
33
+ };
34
+ }
35
+ });
36
+ // ── upload_file ───────────────────────────────────────────────────────────
37
+ server.tool("2kw_upload_file", "Upload a local file and get back its file id. purpose=agent_input files expire after the configured TTL unless referenced by a turn; purpose=knowledge is for organization members only and can be ingested into a knowledge base with 2kw_attach_knowledge_document_file. Size and per-end-user quotas apply.", {
38
+ filePath: z.string().describe("Local path to the file to upload"),
39
+ purpose: FilePurpose.describe("Why the file exists — determines its lifecycle"),
40
+ }, async ({ filePath, purpose }) => {
41
+ try {
42
+ const fileBuffer = await readFile(filePath);
43
+ const filename = basename(filePath);
44
+ const mimeType = getMimeType(filename);
45
+ const formData = new FormData();
46
+ formData.append("file", new Blob([fileBuffer], { type: mimeType }), filename);
47
+ const { data } = await client.POST("/v1/files", {
48
+ params: { query: { purpose } },
49
+ // Provide placeholder body for type checking; actual payload comes from bodySerializer
50
+ body: { file: "" },
51
+ bodySerializer: () => formData,
52
+ });
53
+ return {
54
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
55
+ };
56
+ }
57
+ catch (error) {
58
+ return {
59
+ content: [{ type: "text", text: formatErrorForMcp(error) }],
60
+ isError: true,
61
+ };
62
+ }
63
+ });
64
+ // ── get_file ──────────────────────────────────────────────────────────────
65
+ server.tool("2kw_get_file", "Fetch a file's metadata by id.", { fileId: z.string().describe("The file ID") }, async ({ fileId }) => {
66
+ try {
67
+ const { data } = await client.GET("/v1/files/{fileId}", {
68
+ params: { path: { fileId } },
69
+ });
70
+ return {
71
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
72
+ };
73
+ }
74
+ catch (error) {
75
+ return {
76
+ content: [{ type: "text", text: formatErrorForMcp(error) }],
77
+ isError: true,
78
+ };
79
+ }
80
+ });
81
+ // ── delete_file ───────────────────────────────────────────────────────────
82
+ server.tool("2kw_delete_file", "Soft-delete an agent_input file. Knowledge files are not deletable through this API; their lifecycle belongs to the knowledge base's document.", { fileId: z.string().describe("The file ID to delete") }, async ({ fileId }) => {
83
+ try {
84
+ const { data } = await client.DELETE("/v1/files/{fileId}", {
85
+ params: { path: { fileId } },
86
+ });
87
+ return {
88
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
89
+ };
90
+ }
91
+ catch (error) {
92
+ return {
93
+ content: [{ type: "text", text: formatErrorForMcp(error) }],
94
+ isError: true,
95
+ };
96
+ }
97
+ });
98
+ // ── download_file ─────────────────────────────────────────────────────────
99
+ server.tool("2kw_download_file", "Download a file's bytes and save them to a local path.", {
100
+ fileId: z.string().describe("The file ID"),
101
+ savePath: z.string().describe("Local path to write the downloaded bytes to"),
102
+ }, async ({ fileId, savePath }) => {
103
+ try {
104
+ const { baseUrl, apiKey } = client._config;
105
+ const res = await fetch(`${baseUrl}/v1/files/${encodeURIComponent(fileId)}/content`, {
106
+ headers: { Authorization: `Bearer ${apiKey}` },
107
+ });
108
+ if (!res.ok) {
109
+ let body;
110
+ try {
111
+ body = await res.json();
112
+ }
113
+ catch {
114
+ body = {
115
+ error: res.statusText,
116
+ message: `HTTP ${res.status}: ${res.statusText}`,
117
+ status: res.status,
118
+ timestamp: new Date().toISOString(),
119
+ };
120
+ }
121
+ throw new BackboneApiError(body);
122
+ }
123
+ const buffer = Buffer.from(await res.arrayBuffer());
124
+ await writeFile(savePath, buffer);
125
+ const contentType = res.headers.get("content-type") ?? "application/octet-stream";
126
+ return {
127
+ content: [
128
+ {
129
+ type: "text",
130
+ text: `Downloaded ${buffer.length} bytes (${contentType}) to ${savePath}`,
131
+ },
132
+ ],
133
+ };
134
+ }
135
+ catch (error) {
136
+ return {
137
+ content: [{ type: "text", text: formatErrorForMcp(error) }],
138
+ isError: true,
139
+ };
140
+ }
141
+ });
142
+ }
143
+ //# sourceMappingURL=files.js.map