@2kw/ai-mcp-server 6.0.0-dev.4 → 6.1.0-dev.7
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.
- package/README.md +2 -2
- package/dist/tools/ai-gateway.js +104 -0
- package/dist/tools/billing.js +21 -0
- package/dist/tools/conversion.js +81 -0
- package/dist/tools/datasets.js +204 -0
- package/dist/tools/experiments.js +247 -0
- package/dist/tools/extraction.js +21 -0
- package/dist/tools/prompts.js +37 -0
- package/dist/tools/providers.js +43 -0
- package/dist/tools/schema-versions.js +25 -0
- package/dist/tools/schemas.js +26 -0
- package/dist/tools/tracing.js +63 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# 2kw.ai MCP Server
|
|
2
2
|
|
|
3
|
-
An [MCP (Model Context Protocol)](https://modelcontextprotocol.io/) server for [2kw.ai](https://2kw.ai) — the EU-hosted AI integration platform. It gives AI coding assistants like Claude Code, Cursor, and Windsurf direct access to an [OpenAI-compatible LLM gateway](https://2kw.ai/gateway), [schema-driven document extraction](https://2kw.ai/extraction), audio transcription, and [cost observability](https://2kw.ai/observability) —
|
|
3
|
+
An [MCP (Model Context Protocol)](https://modelcontextprotocol.io/) server for [2kw.ai](https://2kw.ai) — the EU-hosted AI integration platform. It gives AI coding assistants like Claude Code, Cursor, and Windsurf direct access to an [OpenAI-compatible LLM gateway](https://2kw.ai/gateway), [schema-driven document extraction](https://2kw.ai/extraction), audio transcription, and [cost observability](https://2kw.ai/observability) — 88 tools, straight from your editor.
|
|
4
4
|
|
|
5
5
|
Typical prompts once installed: *"extract the line items from this invoice PDF"*, *"create a schema for purchase orders and test it against these samples"*, *"transcribe this call recording"*, *"what did our extraction traffic cost this week?"*.
|
|
6
6
|
|
|
@@ -51,7 +51,7 @@ Get an API key from your [2kw.ai dashboard](https://2kw.ai) — paid plans start
|
|
|
51
51
|
|----------|---------------------------|
|
|
52
52
|
| **Document conversion** | Convert PDF, DOCX, XLSX, images, and 20+ formats to Markdown/text/HTML/JSON |
|
|
53
53
|
| **Data extraction** | Run schema-driven extractions (sync/async), estimate tokens, re-run, inspect results |
|
|
54
|
-
| **AI Gateway** | Chat completions through the OpenAI-compatible gateway, list available models |
|
|
54
|
+
| **AI Gateway** | Chat completions and Responses (agent or direct model) through the OpenAI-compatible gateway, embeddings, list available models |
|
|
55
55
|
| **Transcription** | Transcribe audio (FLAC, MP3, MP4, OGG, WAV, WebM) to text, JSON, SRT, VTT |
|
|
56
56
|
| **Schemas** | CRUD, versioning, labels, validation, and testing against sample documents |
|
|
57
57
|
| **Prompts** | Versioned prompt management, labels, compilation, testing |
|
package/dist/tools/ai-gateway.js
CHANGED
|
@@ -81,6 +81,110 @@ export function register(server, client) {
|
|
|
81
81
|
};
|
|
82
82
|
}
|
|
83
83
|
});
|
|
84
|
+
// ── create_response ─────────────────────────────────────────────────────
|
|
85
|
+
server.tool("2kw_create_response", "Send a request through Backbone's OpenAI-compatible OpenResponses endpoint (POST /v1/responses). Model format: 'provider/model' for a direct gateway call via `model`, or use `agent` to invoke a stored agent by id or name (optionally 'ref@label', e.g. 'support-bot@latest'). Always non-streaming.", {
|
|
86
|
+
input: z.string().min(1).describe("The input text (sent as a single user message)"),
|
|
87
|
+
agent: z
|
|
88
|
+
.string()
|
|
89
|
+
.optional()
|
|
90
|
+
.describe("Stored agent id or name to invoke, optionally 'ref@label'. Mutually exclusive with `model`."),
|
|
91
|
+
model: z
|
|
92
|
+
.string()
|
|
93
|
+
.optional()
|
|
94
|
+
.describe("Model identifier in 'provider/model' format. Mutually exclusive with `agent`."),
|
|
95
|
+
conversation: z.string().optional().describe("Conversation ID this response belongs to"),
|
|
96
|
+
}, async ({ input, agent, model, conversation }) => {
|
|
97
|
+
try {
|
|
98
|
+
if (agent && model) {
|
|
99
|
+
throw new Error("Use either `agent` or `model`, not both.");
|
|
100
|
+
}
|
|
101
|
+
if (!agent && !model) {
|
|
102
|
+
throw new Error("Either `agent` or `model` is required.");
|
|
103
|
+
}
|
|
104
|
+
const resolvedModel = agent ? `agent/${agent}` : model;
|
|
105
|
+
const body = {
|
|
106
|
+
model: resolvedModel,
|
|
107
|
+
// The wire `input` field also accepts a bare string (shorthand for
|
|
108
|
+
// a single user message; see ResponseItemInputDeserializer), which
|
|
109
|
+
// the generated type does not model.
|
|
110
|
+
input,
|
|
111
|
+
stream: false,
|
|
112
|
+
...(conversation !== undefined && { conversation }),
|
|
113
|
+
};
|
|
114
|
+
const { data } = await client.POST("/v1/responses", {
|
|
115
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
116
|
+
body: body,
|
|
117
|
+
});
|
|
118
|
+
const result = data;
|
|
119
|
+
const parts = [];
|
|
120
|
+
const output = result.output;
|
|
121
|
+
for (const item of output ?? []) {
|
|
122
|
+
if (item.type !== "message")
|
|
123
|
+
continue;
|
|
124
|
+
for (const part of item.content ?? []) {
|
|
125
|
+
if (part.type === "output_text" && part.text) {
|
|
126
|
+
parts.push({ type: "text", text: part.text });
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const status = result.status;
|
|
131
|
+
if (status && status !== "completed") {
|
|
132
|
+
parts.push({ type: "text", text: `\n[status: ${status}]` });
|
|
133
|
+
}
|
|
134
|
+
const usage = result.usage;
|
|
135
|
+
if (usage) {
|
|
136
|
+
parts.push({
|
|
137
|
+
type: "text",
|
|
138
|
+
text: `\n[Usage: ${usage.input_tokens} input + ${usage.output_tokens} output = ${usage.total_tokens} total tokens]`,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
if (parts.length === 0) {
|
|
142
|
+
parts.push({ type: "text", text: JSON.stringify(result, null, 2) });
|
|
143
|
+
}
|
|
144
|
+
return { content: parts };
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
return {
|
|
148
|
+
content: [{ type: "text", text: formatErrorForMcp(error) }],
|
|
149
|
+
isError: true,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
// ── create_embeddings ───────────────────────────────────────────────────
|
|
154
|
+
server.tool("2kw_create_embeddings", "Create embeddings for input text through Backbone's OpenAI-compatible embeddings endpoint (POST /v1/embeddings).", {
|
|
155
|
+
input: z.string().min(1).describe("The text to embed"),
|
|
156
|
+
model: z.string().describe("Model identifier in 'provider/model' format"),
|
|
157
|
+
}, async ({ input, model }) => {
|
|
158
|
+
try {
|
|
159
|
+
const { data } = await client.POST("/v1/embeddings", {
|
|
160
|
+
// POST /v1/embeddings has no request body in the generated spec
|
|
161
|
+
// (backend: "Not yet implemented"); cast, same as the untyped
|
|
162
|
+
// convert.ts source-conversion calls in the CLI.
|
|
163
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
164
|
+
body: { model, input },
|
|
165
|
+
});
|
|
166
|
+
const result = data;
|
|
167
|
+
const items = result.data ?? [];
|
|
168
|
+
const lines = items.map((item, i) => `[${item.index ?? i}] ${item.embedding?.length ?? 0} dimensions`);
|
|
169
|
+
if (result.usage) {
|
|
170
|
+
lines.push(`\n[Usage: ${result.usage.prompt_tokens} prompt / ${result.usage.total_tokens} total tokens]`);
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
content: [
|
|
174
|
+
{
|
|
175
|
+
type: "text",
|
|
176
|
+
text: `Embeddings (model: ${result.model}):\n${lines.join("\n") || "(none)"}`,
|
|
177
|
+
},
|
|
178
|
+
],
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
catch (error) {
|
|
182
|
+
return {
|
|
183
|
+
content: [{ type: "text", text: formatErrorForMcp(error) }],
|
|
184
|
+
isError: true,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
});
|
|
84
188
|
// ── list_models ─────────────────────────────────────────────────────────
|
|
85
189
|
server.tool("2kw_list_models", "List available AI models and providers accessible through the Backbone AI gateway.", {}, async () => {
|
|
86
190
|
try {
|
package/dist/tools/billing.js
CHANGED
|
@@ -43,6 +43,27 @@ export function register(server, client) {
|
|
|
43
43
|
};
|
|
44
44
|
}
|
|
45
45
|
});
|
|
46
|
+
server.tool("2kw_billing_has_available", "Check whether the org has enough available capacity for a metered resource (extraction credits, conversion credits, gateway tokens) before consuming it.", {
|
|
47
|
+
type: z
|
|
48
|
+
.enum(["EXTRACTION_CREDITS", "CONVERSION_CREDITS", "GATEWAY_TOKENS"])
|
|
49
|
+
.describe("Resource type to check"),
|
|
50
|
+
required: z.number().optional().describe("Amount required (server defaults to 1)"),
|
|
51
|
+
}, async (params) => {
|
|
52
|
+
try {
|
|
53
|
+
const { data } = await client.GET("/v1/billing/has-available", {
|
|
54
|
+
params: { query: { type: params.type, required: params.required } },
|
|
55
|
+
});
|
|
56
|
+
return {
|
|
57
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
return {
|
|
62
|
+
content: [{ type: "text", text: formatErrorForMcp(error) }],
|
|
63
|
+
isError: true,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
});
|
|
46
67
|
server.tool("2kw_billing_check", "Check whether the org can create another resource of the given type before attempting it. Returns a single boolean answer.", {
|
|
47
68
|
resource: z
|
|
48
69
|
.enum(["schema", "prompt", "team-member"])
|
package/dist/tools/conversion.js
CHANGED
|
@@ -234,6 +234,87 @@ export function register(server, client) {
|
|
|
234
234
|
};
|
|
235
235
|
}
|
|
236
236
|
});
|
|
237
|
+
// ── convert_file ────────────────────────────────────────────────────────
|
|
238
|
+
server.tool("2kw_convert_file", "Convert one or more local files (PDF, DOCX, XLSX, images, etc.) synchronously via direct multipart upload. Simpler single-purpose alternative to 2kw_convert_document for local-file-only conversions.", {
|
|
239
|
+
paths: z.array(z.string()).min(1).describe("Local file paths to upload and convert"),
|
|
240
|
+
pipeline: z
|
|
241
|
+
.string()
|
|
242
|
+
.optional()
|
|
243
|
+
.describe("Processing pipeline: 'fast' (default), 'ocr', or 'vlm'"),
|
|
244
|
+
outputFormats: z
|
|
245
|
+
.array(z.string())
|
|
246
|
+
.optional()
|
|
247
|
+
.describe("Output formats, e.g. ['md', 'text', 'html', 'json']. Only relevant for ocr/vlm pipelines."),
|
|
248
|
+
}, async ({ paths, pipeline, outputFormats }) => {
|
|
249
|
+
try {
|
|
250
|
+
const formData = new FormData();
|
|
251
|
+
for (const p of paths) {
|
|
252
|
+
const buffer = await readFile(p);
|
|
253
|
+
const filename = basename(p);
|
|
254
|
+
formData.append("files", new Blob([buffer], { type: getMimeType(filename) }), filename);
|
|
255
|
+
}
|
|
256
|
+
if (outputFormats?.length) {
|
|
257
|
+
formData.append("options", new Blob([JSON.stringify({ outputFormats: outputFormats.map(mapOutputFormat) })], { type: "application/json" }));
|
|
258
|
+
}
|
|
259
|
+
const { data } = await client.POST("/v1/convert/file", {
|
|
260
|
+
params: { query: { pipeline } },
|
|
261
|
+
// Provide placeholder body for type checking; actual payload comes from bodySerializer
|
|
262
|
+
body: { files: paths },
|
|
263
|
+
bodySerializer: () => formData,
|
|
264
|
+
});
|
|
265
|
+
return { content: formatConvertResponse(data) };
|
|
266
|
+
}
|
|
267
|
+
catch (error) {
|
|
268
|
+
return {
|
|
269
|
+
content: [{ type: "text", text: formatErrorForMcp(error) }],
|
|
270
|
+
isError: true,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
// ── convert_file_async ───────────────────────────────────────────────────
|
|
275
|
+
server.tool("2kw_convert_file_async", "Start an asynchronous conversion of one or more local files via direct multipart upload. Returns a task ID — poll with 2kw_get_task_status / 2kw_get_task_result.", {
|
|
276
|
+
paths: z.array(z.string()).min(1).describe("Local file paths to upload and convert"),
|
|
277
|
+
pipeline: z
|
|
278
|
+
.string()
|
|
279
|
+
.optional()
|
|
280
|
+
.describe("Processing pipeline: 'fast' (default), 'ocr', or 'vlm'"),
|
|
281
|
+
outputFormats: z
|
|
282
|
+
.array(z.string())
|
|
283
|
+
.optional()
|
|
284
|
+
.describe("Output formats, e.g. ['md', 'text', 'html', 'json']. Only relevant for ocr/vlm pipelines."),
|
|
285
|
+
}, async ({ paths, pipeline, outputFormats }) => {
|
|
286
|
+
try {
|
|
287
|
+
const formData = new FormData();
|
|
288
|
+
for (const p of paths) {
|
|
289
|
+
const buffer = await readFile(p);
|
|
290
|
+
const filename = basename(p);
|
|
291
|
+
formData.append("files", new Blob([buffer], { type: getMimeType(filename) }), filename);
|
|
292
|
+
}
|
|
293
|
+
if (outputFormats?.length) {
|
|
294
|
+
formData.append("options", new Blob([JSON.stringify({ outputFormats: outputFormats.map(mapOutputFormat) })], { type: "application/json" }));
|
|
295
|
+
}
|
|
296
|
+
const { data } = await client.POST("/v1/convert/file/async", {
|
|
297
|
+
params: { query: { pipeline } },
|
|
298
|
+
body: { files: paths },
|
|
299
|
+
bodySerializer: () => formData,
|
|
300
|
+
});
|
|
301
|
+
const taskId = data?.taskId;
|
|
302
|
+
return {
|
|
303
|
+
content: [
|
|
304
|
+
{
|
|
305
|
+
type: "text",
|
|
306
|
+
text: `Conversion submitted (task: ${taskId}). Poll with 2kw_get_task_status / 2kw_get_task_result.`,
|
|
307
|
+
},
|
|
308
|
+
],
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
catch (error) {
|
|
312
|
+
return {
|
|
313
|
+
content: [{ type: "text", text: formatErrorForMcp(error) }],
|
|
314
|
+
isError: true,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
});
|
|
237
318
|
// ── get_task_status ─────────────────────────────────────────────────────
|
|
238
319
|
server.tool("2kw_get_task_status", "Check the status of an async conversion task. Supports long polling with the wait parameter.", {
|
|
239
320
|
taskId: z.string().describe("The async task ID returned by convert_document"),
|
package/dist/tools/datasets.js
CHANGED
|
@@ -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
|
package/dist/tools/extraction.js
CHANGED
|
@@ -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"),
|
package/dist/tools/prompts.js
CHANGED
|
@@ -243,6 +243,43 @@ export function register(server, client) {
|
|
|
243
243
|
};
|
|
244
244
|
}
|
|
245
245
|
});
|
|
246
|
+
server.tool("2kw_get_latest_prompt_version", "Get the latest version of a prompt.", { promptId: z.string() }, async (params) => {
|
|
247
|
+
try {
|
|
248
|
+
const { data } = await client.GET("/v1/prompts/{promptId}/versions/latest", { params: { path: { promptId: params.promptId } } });
|
|
249
|
+
return {
|
|
250
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
catch (error) {
|
|
254
|
+
return {
|
|
255
|
+
content: [{ type: "text", text: formatErrorForMcp(error) }],
|
|
256
|
+
isError: true,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
server.tool("2kw_delete_prompt_version", "Delete (deactivate) a specific prompt version.", { promptId: z.string(), versionId: z.string() }, async (params) => {
|
|
261
|
+
try {
|
|
262
|
+
await client.DELETE("/v1/prompts/{promptId}/versions/{versionId}", {
|
|
263
|
+
params: {
|
|
264
|
+
path: { promptId: params.promptId, versionId: params.versionId },
|
|
265
|
+
},
|
|
266
|
+
});
|
|
267
|
+
return {
|
|
268
|
+
content: [
|
|
269
|
+
{
|
|
270
|
+
type: "text",
|
|
271
|
+
text: `Prompt version ${params.versionId} deleted.`,
|
|
272
|
+
},
|
|
273
|
+
],
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
catch (error) {
|
|
277
|
+
return {
|
|
278
|
+
content: [{ type: "text", text: formatErrorForMcp(error) }],
|
|
279
|
+
isError: true,
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
});
|
|
246
283
|
server.tool("2kw_create_prompt_version", "Create a new prompt version. The latest version is the implicit active one unless you activate a specific one.", {
|
|
247
284
|
promptId: z.string(),
|
|
248
285
|
content: z.string().describe("Prompt template content"),
|
package/dist/tools/providers.js
CHANGED
|
@@ -130,6 +130,49 @@ export function register(server, client) {
|
|
|
130
130
|
};
|
|
131
131
|
}
|
|
132
132
|
});
|
|
133
|
+
server.tool("2kw_list_all_providers", "List every AI provider configured for the org, unpaginated. Useful when you need the full set at once (e.g. to check for a duplicate type before creating a new one).", {}, async () => {
|
|
134
|
+
try {
|
|
135
|
+
const { data } = await client.GET("/v1/providers/all");
|
|
136
|
+
return {
|
|
137
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
return {
|
|
142
|
+
content: [{ type: "text", text: formatErrorForMcp(error) }],
|
|
143
|
+
isError: true,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
server.tool("2kw_test_provider", "Test connectivity/credentials for an AI provider before (or without) saving it. Pass a fresh apiKey to test ad-hoc credentials, or providerId to re-test an existing provider's stored key.", {
|
|
148
|
+
provider: ProviderType.describe("Provider vendor type"),
|
|
149
|
+
apiKey: z.string().optional().describe("Credential to test (omit to use providerId's stored key)"),
|
|
150
|
+
providerId: z.string().optional().describe("Existing provider ID whose stored credentials should be used"),
|
|
151
|
+
config: z
|
|
152
|
+
.record(z.string(), z.unknown())
|
|
153
|
+
.optional()
|
|
154
|
+
.describe("Provider-specific config (e.g. endpoint, region)"),
|
|
155
|
+
}, async (params) => {
|
|
156
|
+
try {
|
|
157
|
+
const { data } = await client.POST("/v1/providers/test", {
|
|
158
|
+
body: {
|
|
159
|
+
provider: params.provider,
|
|
160
|
+
apiKey: params.apiKey,
|
|
161
|
+
providerId: params.providerId,
|
|
162
|
+
config: params.config ?? {},
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
return {
|
|
166
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
return {
|
|
171
|
+
content: [{ type: "text", text: formatErrorForMcp(error) }],
|
|
172
|
+
isError: true,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
});
|
|
133
176
|
server.tool("2kw_list_provider_models", "List every model exposed by every configured provider. Useful to discover which provider/model ids are valid for extractions, chat, and experiments.", {}, async () => {
|
|
134
177
|
try {
|
|
135
178
|
const { data } = await client.GET("/v1/providers/models");
|
|
@@ -103,6 +103,31 @@ export function register(server, client) {
|
|
|
103
103
|
};
|
|
104
104
|
}
|
|
105
105
|
});
|
|
106
|
+
// ── delete_schema_version ───────────────────────────────────────────────
|
|
107
|
+
server.tool("2kw_delete_schema_version", "Delete (deactivate) a schema version. Requires admin role.", {
|
|
108
|
+
schemaId: z.string().describe("The schema ID"),
|
|
109
|
+
versionId: z.string().describe("The version ID to delete"),
|
|
110
|
+
}, async ({ schemaId, versionId }) => {
|
|
111
|
+
try {
|
|
112
|
+
await client.DELETE("/v1/schemas/{schemaId}/versions/{versionId}", {
|
|
113
|
+
params: { path: { schemaId, versionId } },
|
|
114
|
+
});
|
|
115
|
+
return {
|
|
116
|
+
content: [
|
|
117
|
+
{
|
|
118
|
+
type: "text",
|
|
119
|
+
text: `Schema version ${versionId} deleted.`,
|
|
120
|
+
},
|
|
121
|
+
],
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
return {
|
|
126
|
+
content: [{ type: "text", text: formatErrorForMcp(error) }],
|
|
127
|
+
isError: true,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
});
|
|
106
131
|
// ── activate_schema_version ─────────────────────────────────────────────
|
|
107
132
|
server.tool("2kw_activate_schema_version", "Re-activate a historical schema version by creating a copy as the newest active version.", {
|
|
108
133
|
schemaId: z.string().describe("The schema ID"),
|
package/dist/tools/schemas.js
CHANGED
|
@@ -111,6 +111,32 @@ export function register(server, client) {
|
|
|
111
111
|
};
|
|
112
112
|
}
|
|
113
113
|
});
|
|
114
|
+
// ── resolve_schema ──────────────────────────────────────────────────────
|
|
115
|
+
server.tool("2kw_resolve_schema", "Resolve a schema's active or labeled content — returns the version that would be used at runtime.", {
|
|
116
|
+
schemaId: z.string().describe("The schema ID"),
|
|
117
|
+
label: z
|
|
118
|
+
.string()
|
|
119
|
+
.optional()
|
|
120
|
+
.describe("Label (e.g. 'production') — otherwise returns the active version"),
|
|
121
|
+
}, async ({ schemaId, label }) => {
|
|
122
|
+
try {
|
|
123
|
+
const { data } = await client.GET("/v1/schemas/{schemaId}/resolve", {
|
|
124
|
+
params: {
|
|
125
|
+
path: { schemaId },
|
|
126
|
+
query: { label },
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
return {
|
|
130
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
return {
|
|
135
|
+
content: [{ type: "text", text: formatErrorForMcp(error) }],
|
|
136
|
+
isError: true,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
});
|
|
114
140
|
// ── delete_schema ───────────────────────────────────────────────────────
|
|
115
141
|
server.tool("2kw_delete_schema", "Delete a schema and all its versions. This action is irreversible.", {
|
|
116
142
|
schemaId: z.string().describe("The schema ID to delete"),
|
package/dist/tools/tracing.js
CHANGED
|
@@ -120,5 +120,68 @@ export function register(server, client) {
|
|
|
120
120
|
};
|
|
121
121
|
}
|
|
122
122
|
});
|
|
123
|
+
// ── sessions ────────────────────────────────────────────────────────────
|
|
124
|
+
server.tool("2kw_list_trace_sessions", "List sessions (spans grouped by an exporter-stamped session id) in the org, with turn/error counts, duration, tokens and cost. Optional free-text search across session id and name, and a start-time range.", {
|
|
125
|
+
search: z.string().optional().describe("Free-text search across session id and name"),
|
|
126
|
+
from: z.string().optional().describe("Start of time range (ISO-8601)"),
|
|
127
|
+
to: z.string().optional().describe("End of time range (ISO-8601)"),
|
|
128
|
+
page: z.number().int().min(0).optional().describe("Page index (0-based)"),
|
|
129
|
+
size: z.number().int().min(1).max(100).optional().describe("Page size"),
|
|
130
|
+
}, async (params) => {
|
|
131
|
+
try {
|
|
132
|
+
const { data } = await client.GET("/v1/traces/sessions", {
|
|
133
|
+
params: {
|
|
134
|
+
query: {
|
|
135
|
+
search: params.search,
|
|
136
|
+
from: params.from,
|
|
137
|
+
to: params.to,
|
|
138
|
+
page: params.page ?? 0,
|
|
139
|
+
size: params.size ?? 20,
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
const result = data;
|
|
144
|
+
const lines = (result.content ?? []).map((s) => {
|
|
145
|
+
const services = Array.isArray(s.sourceServices)
|
|
146
|
+
? s.sourceServices.join(", ")
|
|
147
|
+
: "";
|
|
148
|
+
return `- ${s.sessionId} ${s.name ?? "(unnamed)"} · ${s.durationMs}ms · ${s.turnCount} turns · ${s.errorCount} errors · ${services}`;
|
|
149
|
+
});
|
|
150
|
+
return {
|
|
151
|
+
content: [
|
|
152
|
+
{
|
|
153
|
+
type: "text",
|
|
154
|
+
text: `Sessions (${result.totalElements ?? 0} total, page ${(result.number ?? 0) + 1}/${result.totalPages ?? 1}):\n${lines.join("\n") || "(none)"}`,
|
|
155
|
+
},
|
|
156
|
+
],
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
return {
|
|
161
|
+
content: [{ type: "text", text: formatErrorForMcp(error) }],
|
|
162
|
+
isError: true,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
server.tool("2kw_get_trace_session", "Get every span recorded under a session, flat and sorted by start time — reconstruct the conversation chronologically. The id is the external session id stamped by the exporter.", {
|
|
167
|
+
sessionId: z.string().describe("Session id"),
|
|
168
|
+
}, async (params) => {
|
|
169
|
+
try {
|
|
170
|
+
const { data } = await client.GET("/v1/traces/sessions/{sessionId}", {
|
|
171
|
+
params: { path: { sessionId: params.sessionId } },
|
|
172
|
+
});
|
|
173
|
+
return {
|
|
174
|
+
content: [
|
|
175
|
+
{ type: "text", text: JSON.stringify(data, null, 2) },
|
|
176
|
+
],
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
return {
|
|
181
|
+
content: [{ type: "text", text: formatErrorForMcp(error) }],
|
|
182
|
+
isError: true,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
});
|
|
123
186
|
}
|
|
124
187
|
//# sourceMappingURL=tracing.js.map
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@2kw/ai-mcp-server",
|
|
3
|
-
"version": "6.
|
|
4
|
-
"description": "MCP server for 2kw.ai — EU-hosted AI platform: OpenAI-compatible LLM gateway, schema-driven document extraction, transcription, and cost observability.
|
|
3
|
+
"version": "6.1.0-dev.7",
|
|
4
|
+
"description": "MCP server for 2kw.ai — EU-hosted AI platform: OpenAI-compatible LLM gateway, schema-driven document extraction, transcription, and cost observability. 88 tools for Claude Code, Cursor, and Windsurf.",
|
|
5
5
|
"mcpName": "ai.2kw/mcp-server",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"mcp",
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
"check:spec": "tsx --env-file=.env openapi/scripts/check-drift.ts",
|
|
41
41
|
"check:generated": "tsx openapi/scripts/check-generated.ts",
|
|
42
42
|
"fix:generated": "tsx openapi/scripts/check-generated.ts --write",
|
|
43
|
+
"check:coverage": "tsx ../cli/openapi/scripts/check-coverage.ts",
|
|
43
44
|
"typecheck": "tsc --noEmit"
|
|
44
45
|
},
|
|
45
46
|
"dependencies": {
|