@2kw/ai-mcp-server 6.0.0-dev.4 → 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.
- package/README.md +6 -2
- package/dist/index.js +10 -0
- package/dist/tools/agents.d.ts +4 -0
- package/dist/tools/agents.js +434 -0
- package/dist/tools/ai-gateway.js +69 -0
- package/dist/tools/annotation-queues.d.ts +4 -0
- package/dist/tools/annotation-queues.js +322 -0
- package/dist/tools/billing.js +21 -0
- package/dist/tools/conversations.d.ts +4 -0
- package/dist/tools/conversations.js +102 -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/files.d.ts +4 -0
- package/dist/tools/files.js +143 -0
- package/dist/tools/knowledge.d.ts +4 -0
- package/dist/tools/knowledge.js +382 -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/scores.js +53 -3
- package/dist/tools/tracing.js +63 -0
- package/package.json +3 -2
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { formatErrorForMcp } from "../errors.js";
|
|
3
|
+
const SUBJECT_TYPE = z.enum(["RUN_RESULT", "TRACE", "SPAN", "DATASET_ITEM"]);
|
|
4
|
+
const ITEM_STATUS = z.enum(["PENDING", "IN_PROGRESS", "DONE", "SKIPPED"]);
|
|
5
|
+
export function register(server, client) {
|
|
6
|
+
// ── list_annotation_queues ──────────────────────────────────────────────
|
|
7
|
+
server.tool("2kw_list_annotation_queues", "List annotation queues in the organization.", {
|
|
8
|
+
archived: z
|
|
9
|
+
.boolean()
|
|
10
|
+
.optional()
|
|
11
|
+
.default(false)
|
|
12
|
+
.describe("List archived queues instead of active ones"),
|
|
13
|
+
}, async ({ archived }) => {
|
|
14
|
+
try {
|
|
15
|
+
const { data } = await client.GET("/v1/annotation-queues", {
|
|
16
|
+
params: { query: { archived } },
|
|
17
|
+
});
|
|
18
|
+
const queues = (data ?? []);
|
|
19
|
+
const lines = queues.map((q) => {
|
|
20
|
+
const counts = q.itemCounts ?? {};
|
|
21
|
+
return `- ${q.name} (id: ${q.id}) — pending=${counts.pending ?? 0} inProgress=${counts.inProgress ?? 0} done=${counts.done ?? 0} skipped=${counts.skipped ?? 0}${q.archivedAt ? ` [archived ${q.archivedAt}]` : ""}`;
|
|
22
|
+
});
|
|
23
|
+
return {
|
|
24
|
+
content: [
|
|
25
|
+
{
|
|
26
|
+
type: "text",
|
|
27
|
+
text: `Annotation queues (${queues.length}):\n${lines.join("\n") || "(none)"}`,
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
return {
|
|
34
|
+
content: [{ type: "text", text: formatErrorForMcp(error) }],
|
|
35
|
+
isError: true,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
// ── get_annotation_queue ─────────────────────────────────────────────────
|
|
40
|
+
server.tool("2kw_get_annotation_queue", "Get an annotation queue by its ID, including item counts by status.", {
|
|
41
|
+
queueId: z.string().describe("The annotation queue ID"),
|
|
42
|
+
}, async ({ queueId }) => {
|
|
43
|
+
try {
|
|
44
|
+
const { data } = await client.GET("/v1/annotation-queues/{id}", {
|
|
45
|
+
params: { path: { id: queueId } },
|
|
46
|
+
});
|
|
47
|
+
return {
|
|
48
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
return {
|
|
53
|
+
content: [{ type: "text", text: formatErrorForMcp(error) }],
|
|
54
|
+
isError: true,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
// ── create_annotation_queue ──────────────────────────────────────────────
|
|
59
|
+
server.tool("2kw_create_annotation_queue", "Create a new annotation queue to curate subjects for human (or agent-assisted) review.", {
|
|
60
|
+
name: z.string().min(1).max(255).describe("Queue name"),
|
|
61
|
+
description: z.string().max(4000).optional().describe("Queue description"),
|
|
62
|
+
}, async ({ name, description }) => {
|
|
63
|
+
try {
|
|
64
|
+
const { data } = await client.POST("/v1/annotation-queues", {
|
|
65
|
+
body: {
|
|
66
|
+
name,
|
|
67
|
+
...(description !== undefined && { description }),
|
|
68
|
+
},
|
|
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
|
+
// ── update_annotation_queue ──────────────────────────────────────────────
|
|
82
|
+
server.tool("2kw_update_annotation_queue", "Update an annotation queue's name or description.", {
|
|
83
|
+
queueId: z.string().describe("The annotation queue ID"),
|
|
84
|
+
name: z.string().min(1).max(255).optional().describe("New name"),
|
|
85
|
+
description: z.string().max(4000).optional().describe("New description"),
|
|
86
|
+
}, async ({ queueId, name, description }) => {
|
|
87
|
+
try {
|
|
88
|
+
const body = {};
|
|
89
|
+
if (name !== undefined)
|
|
90
|
+
body.name = name;
|
|
91
|
+
if (description !== undefined)
|
|
92
|
+
body.description = description;
|
|
93
|
+
const { data } = await client.PATCH("/v1/annotation-queues/{id}", {
|
|
94
|
+
params: { path: { id: queueId } },
|
|
95
|
+
body: body,
|
|
96
|
+
});
|
|
97
|
+
return {
|
|
98
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
return {
|
|
103
|
+
content: [{ type: "text", text: formatErrorForMcp(error) }],
|
|
104
|
+
isError: true,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
// ── archive_annotation_queue ─────────────────────────────────────────────
|
|
109
|
+
server.tool("2kw_archive_annotation_queue", "Archive an annotation queue. Idempotent — archiving an already-archived queue is a no-op.", {
|
|
110
|
+
queueId: z.string().describe("The annotation queue ID to archive"),
|
|
111
|
+
}, async ({ queueId }) => {
|
|
112
|
+
try {
|
|
113
|
+
await client.POST("/v1/annotation-queues/{id}/archive", {
|
|
114
|
+
params: { path: { id: queueId } },
|
|
115
|
+
});
|
|
116
|
+
return {
|
|
117
|
+
content: [{ type: "text", text: `Queue ${queueId} archived.` }],
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
return {
|
|
122
|
+
content: [{ type: "text", text: formatErrorForMcp(error) }],
|
|
123
|
+
isError: true,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
// ── find_queue_items_by_subject ──────────────────────────────────────────
|
|
128
|
+
server.tool("2kw_find_queue_items_by_subject", "Find every annotation queue item referencing a given subject, across all queues in the " +
|
|
129
|
+
"organization. Empty when the subject has never been queued. Useful to check whether a " +
|
|
130
|
+
"run result, trace, span, or dataset item is already under review before adding it again.", {
|
|
131
|
+
subjectType: SUBJECT_TYPE.describe("The subject's type"),
|
|
132
|
+
subjectId: z.string().describe("The subject's ID"),
|
|
133
|
+
}, async ({ subjectType, subjectId }) => {
|
|
134
|
+
try {
|
|
135
|
+
const { data } = await client.GET("/v1/annotation-queues/by-subject", {
|
|
136
|
+
params: { query: { subjectType, subjectId } },
|
|
137
|
+
});
|
|
138
|
+
const queueItems = (data ?? []);
|
|
139
|
+
const lines = queueItems.map((i) => `- queue=${i.queueId} item=${i.id} status=${i.status} assignedTo=${i.assignedTo ?? "(unassigned)"}`);
|
|
140
|
+
return {
|
|
141
|
+
content: [
|
|
142
|
+
{
|
|
143
|
+
type: "text",
|
|
144
|
+
text: `Queue items for ${subjectType} ${subjectId} (${queueItems.length}):\n${lines.join("\n") || "(none)"}`,
|
|
145
|
+
},
|
|
146
|
+
],
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
return {
|
|
151
|
+
content: [{ type: "text", text: formatErrorForMcp(error) }],
|
|
152
|
+
isError: true,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
// ── list_queue_items ──────────────────────────────────────────────────────
|
|
157
|
+
server.tool("2kw_list_queue_items", "List items in a queue, optionally filtered by status or assignee.", {
|
|
158
|
+
queueId: z.string().describe("The annotation queue ID"),
|
|
159
|
+
status: ITEM_STATUS.optional().describe("Filter by item status"),
|
|
160
|
+
assignedTo: z.string().optional().describe("Filter by assignee user ID"),
|
|
161
|
+
page: z.number().optional().default(0).describe("Page number (0-based)"),
|
|
162
|
+
size: z.number().optional().default(50).describe("Page size"),
|
|
163
|
+
}, async ({ queueId, status, assignedTo, page, size }) => {
|
|
164
|
+
try {
|
|
165
|
+
const { data } = await client.GET("/v1/annotation-queues/{id}/items", {
|
|
166
|
+
params: {
|
|
167
|
+
path: { id: queueId },
|
|
168
|
+
query: { status, assignedTo, page, size },
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
const queueItems = (data ?? []);
|
|
172
|
+
const lines = queueItems.map((i) => `- ${i.id} [${i.status}] ${i.subjectType}:${i.subjectId}${i.assignedTo ? ` assignedTo=${i.assignedTo}` : ""}`);
|
|
173
|
+
return {
|
|
174
|
+
content: [
|
|
175
|
+
{
|
|
176
|
+
type: "text",
|
|
177
|
+
text: `Queue ${queueId} items (page ${page ?? 0}, ${queueItems.length} returned):\n${lines.join("\n") || "(none)"}`,
|
|
178
|
+
},
|
|
179
|
+
],
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
return {
|
|
184
|
+
content: [{ type: "text", text: formatErrorForMcp(error) }],
|
|
185
|
+
isError: true,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
// ── add_queue_items ───────────────────────────────────────────────────────
|
|
190
|
+
server.tool("2kw_add_queue_items", "Bulk-add subjects to a queue for review. Duplicates (same subject already in the queue) " +
|
|
191
|
+
"are skipped rather than erroring, so this is safe to call repeatedly with an overlapping set.", {
|
|
192
|
+
queueId: z.string().describe("The annotation queue ID"),
|
|
193
|
+
items: z
|
|
194
|
+
.array(z.object({
|
|
195
|
+
subjectType: SUBJECT_TYPE,
|
|
196
|
+
subjectId: z.string().min(1),
|
|
197
|
+
}))
|
|
198
|
+
.min(1)
|
|
199
|
+
.max(1000)
|
|
200
|
+
.describe("Subjects to add, up to 1000 per call"),
|
|
201
|
+
}, async ({ queueId, items }) => {
|
|
202
|
+
try {
|
|
203
|
+
const { data } = await client.POST("/v1/annotation-queues/{id}/items", {
|
|
204
|
+
params: { path: { id: queueId } },
|
|
205
|
+
body: { items },
|
|
206
|
+
});
|
|
207
|
+
const added = (data ?? []);
|
|
208
|
+
return {
|
|
209
|
+
content: [
|
|
210
|
+
{
|
|
211
|
+
type: "text",
|
|
212
|
+
text: `Added ${added.length} item(s) to queue ${queueId} (duplicates skipped).\n\n${JSON.stringify(added, null, 2)}`,
|
|
213
|
+
},
|
|
214
|
+
],
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
catch (error) {
|
|
218
|
+
return {
|
|
219
|
+
content: [{ type: "text", text: formatErrorForMcp(error) }],
|
|
220
|
+
isError: true,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
// ── claim_next_queue_item ─────────────────────────────────────────────────
|
|
225
|
+
server.tool("2kw_claim_next_queue_item", "Claim the next eligible item in a queue for the caller — the entry point of a review loop. " +
|
|
226
|
+
"Returns a 'no items available' message when the queue has nothing left to claim, which is " +
|
|
227
|
+
"a normal end state, not an error. A typical loop: (1) call this to claim an item and read " +
|
|
228
|
+
"its subjectType/subjectId; (2) inspect that subject with the matching read tool (e.g. an " +
|
|
229
|
+
"experiment run result, a trace, or a dataset item); (3) file the verdict with " +
|
|
230
|
+
"2kw_record_human_score, passing the item's subjectType/subjectId and its id as " +
|
|
231
|
+
"annotationQueueItemId so the server can validate the match; (4) call 2kw_update_queue_item " +
|
|
232
|
+
"to mark the item DONE (or SKIPPED); (5) repeat from step 1 until this tool reports no items " +
|
|
233
|
+
"left.", {
|
|
234
|
+
queueId: z.string().describe("The annotation queue ID to claim from"),
|
|
235
|
+
}, async ({ queueId }) => {
|
|
236
|
+
try {
|
|
237
|
+
const { data, response } = await client.GET("/v1/annotation-queues/{id}/items/next", {
|
|
238
|
+
params: { path: { id: queueId } },
|
|
239
|
+
});
|
|
240
|
+
if (!data || response.status === 204) {
|
|
241
|
+
return {
|
|
242
|
+
content: [
|
|
243
|
+
{
|
|
244
|
+
type: "text",
|
|
245
|
+
text: `No items available to claim in queue ${queueId} — it is empty for you right now.`,
|
|
246
|
+
},
|
|
247
|
+
],
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
const item = data;
|
|
251
|
+
return {
|
|
252
|
+
content: [
|
|
253
|
+
{
|
|
254
|
+
type: "text",
|
|
255
|
+
text: `Claimed item ${item.id} (subject: ${item.subjectType}:${item.subjectId}, status: ${item.status}).\n` +
|
|
256
|
+
`Next: review the subject, file a score with 2kw_record_human_score ` +
|
|
257
|
+
`(annotationQueueItemId="${item.id}"), then call 2kw_update_queue_item to close it out.\n\n` +
|
|
258
|
+
`${JSON.stringify(item, null, 2)}`,
|
|
259
|
+
},
|
|
260
|
+
],
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
catch (error) {
|
|
264
|
+
return {
|
|
265
|
+
content: [{ type: "text", text: formatErrorForMcp(error) }],
|
|
266
|
+
isError: true,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
// ── get_queue_item ────────────────────────────────────────────────────────
|
|
271
|
+
server.tool("2kw_get_queue_item", "Get a single queue item by its ID.", {
|
|
272
|
+
queueId: z.string().describe("The annotation queue ID"),
|
|
273
|
+
itemId: z.string().describe("The queue item ID"),
|
|
274
|
+
}, async ({ queueId, itemId }) => {
|
|
275
|
+
try {
|
|
276
|
+
const { data } = await client.GET("/v1/annotation-queues/{id}/items/{itemId}", {
|
|
277
|
+
params: { path: { id: queueId, itemId } },
|
|
278
|
+
});
|
|
279
|
+
return {
|
|
280
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
catch (error) {
|
|
284
|
+
return {
|
|
285
|
+
content: [{ type: "text", text: formatErrorForMcp(error) }],
|
|
286
|
+
isError: true,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
// ── update_queue_item ─────────────────────────────────────────────────────
|
|
291
|
+
server.tool("2kw_update_queue_item", "Update a queue item's status or assignee — the closing step of a review loop started by " +
|
|
292
|
+
"2kw_claim_next_queue_item. Set status=DONE once a verdict has been filed (see " +
|
|
293
|
+
"2kw_record_human_score), or SKIPPED to pass on an item without scoring it. Clear " +
|
|
294
|
+
"assignedTo (pass an empty string) to release it back to the pool for another reviewer.", {
|
|
295
|
+
queueId: z.string().describe("The annotation queue ID"),
|
|
296
|
+
itemId: z.string().describe("The queue item ID"),
|
|
297
|
+
status: ITEM_STATUS.optional().describe("New status"),
|
|
298
|
+
assignedTo: z.string().optional().describe("New assignee user ID (empty string to unassign)"),
|
|
299
|
+
}, async ({ queueId, itemId, status, assignedTo }) => {
|
|
300
|
+
try {
|
|
301
|
+
const body = {};
|
|
302
|
+
if (status !== undefined)
|
|
303
|
+
body.status = status;
|
|
304
|
+
if (assignedTo !== undefined)
|
|
305
|
+
body.assignedTo = assignedTo;
|
|
306
|
+
const { data } = await client.PATCH("/v1/annotation-queues/{id}/items/{itemId}", {
|
|
307
|
+
params: { path: { id: queueId, itemId } },
|
|
308
|
+
body: body,
|
|
309
|
+
});
|
|
310
|
+
return {
|
|
311
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
catch (error) {
|
|
315
|
+
return {
|
|
316
|
+
content: [{ type: "text", text: formatErrorForMcp(error) }],
|
|
317
|
+
isError: true,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
//# sourceMappingURL=annotation-queues.js.map
|
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"])
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { formatErrorForMcp } from "../errors.js";
|
|
3
|
+
export function register(server, client) {
|
|
4
|
+
// ── create_conversation ─────────────────────────────────────────────────
|
|
5
|
+
server.tool("2kw_create_conversation", "Create a conversation whose id can be passed as the 'conversation' parameter on the responses API, so an agent turn can be replayed and extended across calls.", {
|
|
6
|
+
items: z.array(z.unknown()).optional().describe("Initial items to seed the conversation with"),
|
|
7
|
+
metadata: z.record(z.string(), z.unknown()).optional().describe("Arbitrary metadata (JSON object)"),
|
|
8
|
+
}, async ({ items, metadata }) => {
|
|
9
|
+
try {
|
|
10
|
+
const body = {};
|
|
11
|
+
if (items !== undefined)
|
|
12
|
+
body.items = items;
|
|
13
|
+
if (metadata !== undefined)
|
|
14
|
+
body.metadata = metadata;
|
|
15
|
+
const { data } = await client.POST("/v1/conversations", {
|
|
16
|
+
body: Object.keys(body).length > 0 ? body : undefined,
|
|
17
|
+
});
|
|
18
|
+
return {
|
|
19
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
return {
|
|
24
|
+
content: [{ type: "text", text: formatErrorForMcp(error) }],
|
|
25
|
+
isError: true,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
// ── get_conversation ─────────────────────────────────────────────────────
|
|
30
|
+
server.tool("2kw_get_conversation", "Fetch a conversation by id.", { conversationId: z.string().describe("The conversation ID") }, async ({ conversationId }) => {
|
|
31
|
+
try {
|
|
32
|
+
const { data } = await client.GET("/v1/conversations/{conversationId}", {
|
|
33
|
+
params: { path: { conversationId } },
|
|
34
|
+
});
|
|
35
|
+
return {
|
|
36
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
return {
|
|
41
|
+
content: [{ type: "text", text: formatErrorForMcp(error) }],
|
|
42
|
+
isError: true,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
// ── update_conversation ──────────────────────────────────────────────────
|
|
47
|
+
server.tool("2kw_update_conversation", "Replace a conversation's metadata.", {
|
|
48
|
+
conversationId: z.string().describe("The conversation ID"),
|
|
49
|
+
metadata: z.record(z.string(), z.unknown()).optional().describe("Arbitrary metadata (JSON object)"),
|
|
50
|
+
}, async ({ conversationId, metadata }) => {
|
|
51
|
+
try {
|
|
52
|
+
const { data } = await client.POST("/v1/conversations/{conversationId}", {
|
|
53
|
+
params: { path: { conversationId } },
|
|
54
|
+
body: { metadata },
|
|
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
|
+
});
|
|
67
|
+
// ── delete_conversation ───────────────────────────────────────────────────
|
|
68
|
+
server.tool("2kw_delete_conversation", "Delete a conversation and the responses recorded against it.", { conversationId: z.string().describe("The conversation ID to delete") }, async ({ conversationId }) => {
|
|
69
|
+
try {
|
|
70
|
+
const { data } = await client.DELETE("/v1/conversations/{conversationId}", {
|
|
71
|
+
params: { path: { conversationId } },
|
|
72
|
+
});
|
|
73
|
+
return {
|
|
74
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
return {
|
|
79
|
+
content: [{ type: "text", text: formatErrorForMcp(error) }],
|
|
80
|
+
isError: true,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
// ── list_conversation_items ──────────────────────────────────────────────
|
|
85
|
+
server.tool("2kw_list_conversation_items", "Fetch a conversation's items in replay order, oldest first.", { conversationId: z.string().describe("The conversation ID") }, async ({ conversationId }) => {
|
|
86
|
+
try {
|
|
87
|
+
const { data } = await client.GET("/v1/conversations/{conversationId}/items", {
|
|
88
|
+
params: { path: { conversationId } },
|
|
89
|
+
});
|
|
90
|
+
return {
|
|
91
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
return {
|
|
96
|
+
content: [{ type: "text", text: formatErrorForMcp(error) }],
|
|
97
|
+
isError: true,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=conversations.js.map
|
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"),
|