@2kw/ai-mcp-server 6.3.0-dev.14 → 6.3.0-dev.142

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.
@@ -1,5 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { formatErrorForMcp } from "../errors.js";
3
+ import { buildRunEnvelope, chatUrlFor, MODE_RULE, modeItem } from "../lib/agent-run.js";
3
4
  /**
4
5
  * The `model` value for an agent run: `agent/<ref>[@<label>]`, plus `#<model>` when the
5
6
  * request switches to another entry of the version's `models` list (#626, spec #591 §4.1).
@@ -92,7 +93,7 @@ export function register(server, client) {
92
93
  }
93
94
  });
94
95
  // ── create_response ─────────────────────────────────────────────────────
95
- 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'). With `agent`, `agentModel` runs this one request on another model from the agent version's `models` list. The reply ends with the model that answered. Always non-streaming.", {
96
+ 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'). With `agent`, `agentModel` runs this one request on another model from the agent version's `models` list. With `agent`, the reply is the run envelope as JSON (status, text, tool calls, pending approvals, response id, next step) followed by the model that answered; answer an approval pause with 2kw_decide_agent_approvals. A run paused on a relayed (client-side) tool call comes back with status requires_tool_output and `pendingToolCalls`; answer it with 2kw_decide_agent_approvals `outputs`. A run that needs the user's own sign-in to a connector pauses with status requires_tool_output and `pendingConnections` (label, host, reason): no tool here can answer that; tell the user to connect it in chat.2kw.ai → Connectors, as the envelope's `next` says. Always non-streaming. " + MODE_RULE, {
96
97
  input: z.string().min(1).describe("The input text (sent as a single user message)"),
97
98
  agent: z
98
99
  .string()
@@ -108,7 +109,11 @@ export function register(server, client) {
108
109
  .optional()
109
110
  .describe("Model identifier in 'provider/model' format. Mutually exclusive with `agent`."),
110
111
  conversation: z.string().optional().describe("Conversation ID this response belongs to"),
111
- }, async ({ input, agent, agentModel, model, conversation }) => {
112
+ mode: z
113
+ .enum(["plan", "ask", "auto"])
114
+ .optional()
115
+ .describe("Only with `agent`: the conversation mode for this and later turns (plan, ask or auto). Only on the user's explicit request."),
116
+ }, async ({ input, agent, agentModel, model, conversation, mode }) => {
112
117
  try {
113
118
  if (agent && model) {
114
119
  throw new Error("Use either `agent` or `model`, not both.");
@@ -122,13 +127,16 @@ export function register(server, client) {
122
127
  if (agentModel !== undefined && agent?.includes("#")) {
123
128
  throw new Error("Give the model switch once: either 'ref#model' in `agent` or `agentModel`.");
124
129
  }
130
+ if (mode !== undefined && !agent) {
131
+ throw new Error("`mode` applies to agent runs; it needs `agent`.");
132
+ }
125
133
  const resolvedModel = agentModelReference(agent, agentModel) ?? model;
126
134
  const body = {
127
135
  model: resolvedModel,
128
- // The wire `input` field also accepts a bare string (shorthand for
129
- // a single user message; see ResponseItemInputDeserializer), which
130
- // the generated type does not model.
131
- input,
136
+ // Without `mode` the wire `input` is the bare string (shorthand for one user message; see
137
+ // ResponseItemInputDeserializer). With it, the backbone:mode item follows the message (#656);
138
+ // that literal item array is what the cast below is for.
139
+ input: mode ? [{ type: "message", role: "user", content: input }, modeItem(mode)] : input,
132
140
  stream: false,
133
141
  ...(conversation !== undefined && { conversation }),
134
142
  };
@@ -137,6 +145,17 @@ export function register(server, client) {
137
145
  body: body,
138
146
  });
139
147
  const result = data;
148
+ if (agent) {
149
+ // The reference the run used, so the envelope's `next` keeps its label and model (D11).
150
+ const agentRef = agentModel !== undefined ? `${agent}#${agentModel}` : agent;
151
+ const envelope = buildRunEnvelope(result, agentRef, chatUrlFor(client._config?.baseUrl));
152
+ const content = [{ type: "text", text: JSON.stringify(envelope, null, 2) }];
153
+ // The echoed model is 'agent/<name>@<version>', plus '#<model>' when the request switched it (#591).
154
+ if (typeof result.model === "string") {
155
+ content.push({ type: "text", text: `[model: ${result.model}]` });
156
+ }
157
+ return { content };
158
+ }
140
159
  const parts = [];
141
160
  const output = result.output;
142
161
  for (const item of output ?? []) {
@@ -162,11 +181,6 @@ export function register(server, client) {
162
181
  if (parts.length === 0) {
163
182
  parts.push({ type: "text", text: JSON.stringify(result, null, 2) });
164
183
  }
165
- // The response echoes the model that answered: for an agent run
166
- // 'agent/<name>@<version>', plus '#<model>' when the request switched it (#591).
167
- if (agent && typeof result.model === "string") {
168
- parts.push({ type: "text", text: `[model: ${result.model}]` });
169
- }
170
184
  return { content: parts };
171
185
  }
172
186
  catch (error) {
@@ -134,6 +134,32 @@ export function register(server, client) {
134
134
  };
135
135
  }
136
136
  });
137
+ // ── cancel_conversation_turn ─────────────────────────────────────────────
138
+ server.tool("2kw_cancel_conversation_turn", "Ask a running turn of a conversation to stop. turnId is the value the client sent as the Backbone-Turn-Id header on the responses call. The stop is accepted, not confirmed: the turn's own response reports status cancelled, or completed when the stop arrived too late.", {
139
+ conversationId: z.string().describe("The conversation ID"),
140
+ turnId: z
141
+ .string()
142
+ .regex(/^[A-Za-z0-9_-]{1,64}$/)
143
+ .describe("The turn id from the Backbone-Turn-Id header: 1-64 characters of A-Z, a-z, 0-9, '_' or '-'"),
144
+ }, async ({ conversationId, turnId }) => {
145
+ try {
146
+ await client.POST("/v1/conversations/{conversationId}/cancel", {
147
+ params: { path: { conversationId } },
148
+ body: { turn_id: turnId },
149
+ });
150
+ return {
151
+ content: [
152
+ { type: "text", text: `Stop requested for turn ${turnId} of conversation ${conversationId}.` },
153
+ ],
154
+ };
155
+ }
156
+ catch (error) {
157
+ return {
158
+ content: [{ type: "text", text: formatErrorForMcp(error) }],
159
+ isError: true,
160
+ };
161
+ }
162
+ });
137
163
  // ── list_conversation_items ──────────────────────────────────────────────
138
164
  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 }) => {
139
165
  try {
@@ -1,5 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { formatErrorForMcp } from "../errors.js";
3
+ import { assertNameNotBlank, overlay } from "../lib/overlay.js";
3
4
  async function resolveLatestVersionId(client, datasetId) {
4
5
  const { data } = await client.GET("/v1/datasets/{id}/versions/latest", { params: { path: { id: datasetId } } });
5
6
  const versionId = data?.id;
@@ -104,7 +105,7 @@ export function register(server, client) {
104
105
  }
105
106
  });
106
107
  // ── update_dataset ─────────────────────────────────────────────
107
- server.tool("2kw_update_dataset", "Update an existing dataset's details.", {
108
+ server.tool("2kw_update_dataset", "Update an existing dataset's details. Omitted fields keep their current values (the tool reads the dataset first).", {
108
109
  datasetId: z.string().describe("The dataset ID"),
109
110
  name: z.string().optional().describe("New name"),
110
111
  description: z.string().optional().describe("New description"),
@@ -114,22 +115,16 @@ export function register(server, client) {
114
115
  metadata: z.unknown().optional().describe("Arbitrary metadata (JSON object)"),
115
116
  }, async ({ datasetId, name, description, type, inputSchema, expectedOutputSchema, metadata }) => {
116
117
  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;
118
+ assertNameNotBlank(name);
119
+ // PUT /v1/datasets/{id} replaces the whole dataset, so an omitted field would be stored as null.
120
+ const { data: current } = await client.GET("/v1/datasets/{id}", {
121
+ params: { path: { id: datasetId } },
122
+ });
123
+ if (!current)
124
+ throw new Error(`Dataset ${datasetId} could not be read.`);
130
125
  const { data } = await client.PUT("/v1/datasets/{id}", {
131
126
  params: { path: { id: datasetId } },
132
- body: body,
127
+ body: overlay(current, { name, description, type, inputSchema, expectedOutputSchema, metadata }),
133
128
  });
134
129
  return {
135
130
  content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
@@ -1,5 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { formatErrorForMcp } from "../errors.js";
3
+ import { assertNameNotBlank, overlay } from "../lib/overlay.js";
3
4
  export function register(server, client) {
4
5
  // ── list_experiments ────────────────────────────────────────────
5
6
  server.tool("2kw_list_experiments", "List experiments in the organization with optional search, status filter, and pagination.", {
@@ -99,7 +100,7 @@ export function register(server, client) {
99
100
  }
100
101
  });
101
102
  // ── update_experiment ───────────────────────────────────────────
102
- server.tool("2kw_update_experiment", "Update an existing experiment's details.", {
103
+ server.tool("2kw_update_experiment", "Update an existing experiment's details. Omitted fields keep their current values (the tool reads the experiment first).", {
103
104
  experimentId: z.string().describe("The experiment ID"),
104
105
  name: z.string().optional().describe("New name"),
105
106
  description: z.string().optional().describe("New description"),
@@ -108,20 +109,16 @@ export function register(server, client) {
108
109
  metadata: z.unknown().optional().describe("Arbitrary metadata (JSON object)"),
109
110
  }, async ({ experimentId, name, description, type, datasetVersionId, metadata }) => {
110
111
  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;
112
+ assertNameNotBlank(name);
113
+ // PUT /v1/experiments/{id} replaces the whole experiment, so an omitted field would be stored as null.
114
+ const { data: current } = await client.GET("/v1/experiments/{id}", {
115
+ params: { path: { id: experimentId } },
116
+ });
117
+ if (!current)
118
+ throw new Error(`Experiment ${experimentId} could not be read.`);
122
119
  const { data } = await client.PUT("/v1/experiments/{id}", {
123
120
  params: { path: { id: experimentId } },
124
- body: body,
121
+ body: overlay(current, { name, description, type, datasetVersionId, metadata }),
125
122
  });
126
123
  return {
127
124
  content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
@@ -157,7 +154,7 @@ export function register(server, client) {
157
154
  server.tool("2kw_add_variant", "Add a variant to an experiment with a task type and configuration.", {
158
155
  experimentId: z.string().describe("The experiment ID"),
159
156
  name: z.string().min(1).describe("Variant name"),
160
- taskType: z.string().min(1).describe("Task type (e.g., EXTRACTION, CLASSIFICATION)"),
157
+ taskType: z.string().min(1).describe("Task type: extraction or agent"),
161
158
  configuration: z.unknown().describe("Variant configuration (JSON object)"),
162
159
  description: z.string().optional().describe("Variant description"),
163
160
  sortOrder: z.number().optional().describe("Sort order for display"),
@@ -210,30 +207,29 @@ export function register(server, client) {
210
207
  }
211
208
  });
212
209
  // ── update_variant ──────────────────────────────────────────────
213
- server.tool("2kw_update_variant", "Update an existing variant's details.", {
210
+ server.tool("2kw_update_variant", "Update an existing variant's details. Omitted fields keep their current values (the tool reads the variant first).", {
214
211
  experimentId: z.string().describe("The experiment ID"),
215
212
  variantId: z.string().describe("The variant ID"),
216
213
  name: z.string().optional().describe("New variant name"),
217
- taskType: z.string().optional().describe("New task type"),
214
+ taskType: z.string().optional().describe("Task type; must equal the variant's current one (it cannot change)"),
218
215
  configuration: z.unknown().optional().describe("New variant configuration (JSON object)"),
219
216
  description: z.string().optional().describe("New variant description"),
220
217
  sortOrder: z.number().optional().describe("New sort order for display"),
221
218
  }, async ({ experimentId, variantId, name, taskType, configuration, description, sortOrder }) => {
222
219
  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;
220
+ assertNameNotBlank(name);
221
+ // PUT .../variants/{variantId} writes name, description and configuration as sent, so an
222
+ // omitted one would be cleared. There is no single-variant GET: read it from the list. The
223
+ // read's version goes back with the body, so a change made since is refused with 409.
224
+ const { data: variants } = await client.GET("/v1/experiments/{id}/variants", {
225
+ params: { path: { id: experimentId } },
226
+ });
227
+ const current = variants?.find((v) => v.id === variantId);
228
+ if (!current)
229
+ throw new Error(`Variant ${variantId} is not a variant of experiment ${experimentId}.`);
234
230
  const { data } = await client.PUT("/v1/experiments/{id}/variants/{variantId}", {
235
231
  params: { path: { id: experimentId, variantId } },
236
- body: body,
232
+ body: overlay(current, { name, taskType, configuration, description, sortOrder }),
237
233
  });
238
234
  return {
239
235
  content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
@@ -0,0 +1,21 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { ApiClient } from "../client.js";
3
+ export interface ToolDeps {
4
+ client: ApiClient;
5
+ baseUrl: string;
6
+ apiKey: string;
7
+ }
8
+ /**
9
+ * One tool module. `id` is its file name under src/tools; `title` and `summary` head its table on
10
+ * the public docs page, which `npm run docs:tools` generates from this list (#1148).
11
+ */
12
+ export interface ToolGroup {
13
+ id: string;
14
+ title: string;
15
+ summary: string;
16
+ register(server: McpServer, deps: ToolDeps): void;
17
+ }
18
+ /** Every tool module, in the order the docs page lists them. A new module must be added here. */
19
+ export declare const TOOL_GROUPS: readonly ToolGroup[];
20
+ export declare function registerAllTools(server: McpServer, deps: ToolDeps): void;
21
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,190 @@
1
+ import * as agents from "./agents.js";
2
+ import * as aiGateway from "./ai-gateway.js";
3
+ import * as analytics from "./analytics.js";
4
+ import * as annotationQueues from "./annotation-queues.js";
5
+ import * as billing from "./billing.js";
6
+ import * as conversations from "./conversations.js";
7
+ import * as conversion from "./conversion.js";
8
+ import * as datasets from "./datasets.js";
9
+ import * as docs from "./docs.js";
10
+ import * as evaluators from "./evaluators.js";
11
+ import * as experiments from "./experiments.js";
12
+ import * as extraction from "./extraction.js";
13
+ import * as files from "./files.js";
14
+ import * as knowledge from "./knowledge.js";
15
+ import * as memory from "./memory.js";
16
+ import * as plugins from "./plugins.js";
17
+ import * as prompts from "./prompts.js";
18
+ import * as providers from "./providers.js";
19
+ import * as schemaLabels from "./schema-labels.js";
20
+ import * as schemaTesting from "./schema-testing.js";
21
+ import * as schemaVersions from "./schema-versions.js";
22
+ import * as schemas from "./schemas.js";
23
+ import * as scores from "./scores.js";
24
+ import * as skills from "./skills.js";
25
+ import * as tracing from "./tracing.js";
26
+ import * as transcription from "./transcription.js";
27
+ /** Every tool module, in the order the docs page lists them. A new module must be added here. */
28
+ export const TOOL_GROUPS = [
29
+ {
30
+ id: "ai-gateway",
31
+ title: "AI Gateway",
32
+ summary: "Call models through the gateway — chat completions, the Responses endpoint (including stored agents) and the model list.",
33
+ register: (s, d) => aiGateway.register(s, d.client),
34
+ },
35
+ {
36
+ id: "agents",
37
+ title: "Agents",
38
+ summary: "Manage agents, their versions, labels and tool catalogs, and decide a paused run's approvals and relayed tool calls. See [Running agents](#running-agents).",
39
+ register: (s, d) => agents.register(s, d.client),
40
+ },
41
+ {
42
+ id: "conversations",
43
+ title: "Conversations",
44
+ summary: "Create, read, rename and delete conversations, list their items and cancel a running turn.",
45
+ register: (s, d) => conversations.register(s, d.client),
46
+ },
47
+ {
48
+ id: "memory",
49
+ title: "Memory",
50
+ summary: "Read and edit your own agent memory, and see or erase a member's memory as an admin.",
51
+ register: (s, d) => memory.register(s, d.client),
52
+ },
53
+ {
54
+ id: "knowledge",
55
+ title: "Knowledge Bases",
56
+ summary: "Manage knowledge bases and their documents, search them and resolve citations.",
57
+ register: (s, d) => knowledge.register(s, d.client),
58
+ },
59
+ {
60
+ id: "files",
61
+ title: "Files",
62
+ summary: "Upload, list, download and delete files.",
63
+ register: (s, d) => files.register(s, d.client),
64
+ },
65
+ {
66
+ id: "skills",
67
+ title: "Skills",
68
+ summary: "Import, read, resolve and delete skills, with their versions and labels.",
69
+ register: (s, d) => skills.register(s, d.client),
70
+ },
71
+ {
72
+ id: "plugins",
73
+ title: "Plugins",
74
+ summary: "Install, sync, update and remove plugins.",
75
+ register: (s, d) => plugins.register(s, d.client),
76
+ },
77
+ {
78
+ id: "prompts",
79
+ title: "Prompts",
80
+ summary: "Manage prompts, their versions and labels, and resolve, compile or test them.",
81
+ register: (s, d) => prompts.register(s, d.client),
82
+ },
83
+ {
84
+ id: "schemas",
85
+ title: "Schemas",
86
+ summary: "Define extraction schemas within your organization.",
87
+ register: (s, d) => schemas.register(s, d.client),
88
+ },
89
+ {
90
+ id: "schema-versions",
91
+ title: "Schema Versions",
92
+ summary: "Manage versioned snapshots of schema definitions.",
93
+ register: (s, d) => schemaVersions.register(s, d.client),
94
+ },
95
+ {
96
+ id: "schema-labels",
97
+ title: "Schema Labels",
98
+ summary: "Point named labels at schema versions.",
99
+ register: (s, d) => schemaLabels.register(s, d.client),
100
+ },
101
+ {
102
+ id: "schema-testing",
103
+ title: "Schema Testing",
104
+ summary: "Validate schemas and test extractions without persisting data.",
105
+ register: (s, d) => schemaTesting.register(s, d.client),
106
+ },
107
+ {
108
+ id: "extraction",
109
+ title: "Extractions",
110
+ summary: "Extract structured data from text using schemas and AI models.",
111
+ register: (s, d) => extraction.register(s, d.client),
112
+ },
113
+ {
114
+ id: "conversion",
115
+ title: "Document Conversion",
116
+ summary: "Convert documents (PDF, DOCX and more) to Markdown, text, HTML or JSON.",
117
+ register: (s, d) => conversion.register(s, d.client),
118
+ },
119
+ {
120
+ id: "transcription",
121
+ title: "Audio Transcription",
122
+ summary: "Transcribe audio files using AI models.",
123
+ register: (s, d) => transcription.register(s, d.client),
124
+ },
125
+ {
126
+ id: "datasets",
127
+ title: "Datasets",
128
+ summary: "Manage evaluation datasets, their versions and items.",
129
+ register: (s, d) => datasets.register(s, d.client),
130
+ },
131
+ {
132
+ id: "experiments",
133
+ title: "Experiments",
134
+ summary: "Run experiments over datasets, compare variants and track regressions against a baseline.",
135
+ register: (s, d) => experiments.register(s, d.client),
136
+ },
137
+ {
138
+ id: "evaluators",
139
+ title: "Evaluators",
140
+ summary: "List evaluator types and manage evaluator templates.",
141
+ register: (s, d) => evaluators.register(s, d.client),
142
+ },
143
+ {
144
+ id: "scores",
145
+ title: "Scores",
146
+ summary: "Record and list human scores.",
147
+ register: (s, d) => scores.register(s, d.client),
148
+ },
149
+ {
150
+ id: "annotation-queues",
151
+ title: "Annotation Queues",
152
+ summary: "Manage annotation queues and work through their items.",
153
+ register: (s, d) => annotationQueues.register(s, d.client),
154
+ },
155
+ {
156
+ id: "tracing",
157
+ title: "Tracing",
158
+ summary: "Read traces and trace sessions, and change the tracing settings.",
159
+ register: (s, d) => tracing.register(s, d.client),
160
+ },
161
+ {
162
+ id: "analytics",
163
+ title: "Analytics",
164
+ summary: "Usage, error and quality analytics for your organization.",
165
+ register: (s, d) => analytics.register(s, d.client),
166
+ },
167
+ {
168
+ id: "providers",
169
+ title: "Providers",
170
+ summary: "Manage AI providers and list their models.",
171
+ register: (s, d) => providers.register(s, d.client),
172
+ },
173
+ {
174
+ id: "billing",
175
+ title: "Billing",
176
+ summary: "Read your billing tier and limits, and check available budget.",
177
+ register: (s, d) => billing.register(s, d.client),
178
+ },
179
+ {
180
+ id: "docs",
181
+ title: "API Documentation",
182
+ summary: "Browse the backend's OpenAPI documentation by section.",
183
+ register: (s, d) => docs.register(s, d.client, d.baseUrl, d.apiKey),
184
+ },
185
+ ];
186
+ export function registerAllTools(server, deps) {
187
+ for (const group of TOOL_GROUPS)
188
+ group.register(server, deps);
189
+ }
190
+ //# sourceMappingURL=index.js.map
@@ -1,10 +1,42 @@
1
1
  import { z } from "zod";
2
2
  import { readFile } from "node:fs/promises";
3
3
  import { basename } from "node:path";
4
- import { BackboneApiError, formatErrorForMcp } from "../errors.js";
4
+ import { formatErrorForMcp } from "../errors.js";
5
5
  import { getMimeType } from "../mime.js";
6
6
  const ChunkingStrategy = z.enum(["AUTO", "FIXED", "HIERARCHICAL", "CUSTOM"]);
7
7
  const DocumentStatus = z.enum(["PENDING", "PARSING", "CHUNKING", "EMBEDDING", "READY", "ERROR"]);
8
+ /** The 29 Postgres text-search configurations the backend's TextSearchLanguage enum accepts (#1249). */
9
+ const TEXT_SEARCH_LANGUAGES = [
10
+ "arabic",
11
+ "armenian",
12
+ "basque",
13
+ "catalan",
14
+ "danish",
15
+ "dutch",
16
+ "english",
17
+ "finnish",
18
+ "french",
19
+ "german",
20
+ "greek",
21
+ "hindi",
22
+ "hungarian",
23
+ "indonesian",
24
+ "irish",
25
+ "italian",
26
+ "lithuanian",
27
+ "nepali",
28
+ "norwegian",
29
+ "portuguese",
30
+ "romanian",
31
+ "russian",
32
+ "serbian",
33
+ "simple",
34
+ "spanish",
35
+ "swedish",
36
+ "tamil",
37
+ "turkish",
38
+ "yiddish",
39
+ ];
8
40
  /**
9
41
  * Fields shared by knowledge-base create and update — the backend treats
10
42
  * every one of these as mandatory on both requests (name, slug,
@@ -24,6 +56,7 @@ const knowledgeBaseFields = {
24
56
  parentChunkSize: z.number().int().min(100).max(8000).optional().describe("Parent chunk size for hierarchical chunking (100-8000), must exceed chunkSize"),
25
57
  hybridSearchEnabled: z.boolean().optional().describe("Enable hybrid dense + lexical search"),
26
58
  rerankerProviderId: z.string().optional().describe("Provider id used for reranking"),
59
+ textSearchLanguage: z.enum(TEXT_SEARCH_LANGUAGES).optional().describe("Text-search language for the lexical leg (default simple on create). On update, omit to leave the active and any pending language untouched — resending the current value would cancel a reindex in progress; changing it starts one in the background."),
27
60
  };
28
61
  function buildKnowledgeBaseBody(params) {
29
62
  const body = {
@@ -41,6 +74,7 @@ function buildKnowledgeBaseBody(params) {
41
74
  "parentChunkSize",
42
75
  "hybridSearchEnabled",
43
76
  "rerankerProviderId",
77
+ "textSearchLanguage",
44
78
  ]) {
45
79
  if (params[key] !== undefined)
46
80
  body[key] = params[key];
@@ -203,29 +237,13 @@ export function register(server, client) {
203
237
  const filename = basename(filePath);
204
238
  formData.append("files", new Blob([buffer], { type: getMimeType(filename) }), filename);
205
239
  }
206
- const { baseUrl, apiKey } = client._config;
207
- const res = await fetch(`${baseUrl}/v1/knowledge-bases/${encodeURIComponent(knowledgeBaseId)}/documents`, {
208
- method: "POST",
209
- headers: { Authorization: `Bearer ${apiKey}` },
210
- body: formData,
240
+ const { data } = await client.POST("/v1/knowledge-bases/{knowledgeBaseId}/documents", {
241
+ params: { path: { knowledgeBaseId } },
242
+ // Provide placeholder body for type checking; actual payload comes from bodySerializer
243
+ body: { files: [] },
244
+ bodySerializer: () => formData,
211
245
  });
212
- if (!res.ok) {
213
- let body;
214
- try {
215
- body = await res.json();
216
- }
217
- catch {
218
- body = {
219
- error: res.statusText,
220
- message: `HTTP ${res.status}: ${res.statusText}`,
221
- status: res.status,
222
- timestamp: new Date().toISOString(),
223
- };
224
- }
225
- throw new BackboneApiError(body);
226
- }
227
- const data = (await res.json());
228
- const lines = data.map((r) => r.error
246
+ const lines = (data ?? []).map((r) => r.error
229
247
  ? `- ${r.filename}: rejected — ${r.error}`
230
248
  : `- ${r.filename}: accepted, documentId=${r.upload?.document?.id}, versionId=${r.upload?.acceptedVersion?.id}, status=${r.upload?.acceptedVersion?.status}`);
231
249
  return {
@@ -0,0 +1,9 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { ApiClient } from "../client.js";
3
+ /**
4
+ * Agent memory of organization members (agent memory spec §7.1, §7.4). The "my" tools act on
5
+ * the API key owner's own memory; the member tools are for ADMIN and OWNER keys and never
6
+ * show paths or content.
7
+ */
8
+ export declare function register(server: McpServer, client: ApiClient): void;
9
+ //# sourceMappingURL=memory.d.ts.map
@@ -0,0 +1,43 @@
1
+ import { z } from "zod";
2
+ import { formatErrorForMcp } from "../errors.js";
3
+ async function reply(action) {
4
+ try {
5
+ const data = await action();
6
+ return {
7
+ content: [{ type: "text", text: data === undefined ? "Done." : JSON.stringify(data, null, 2) }],
8
+ };
9
+ }
10
+ catch (error) {
11
+ return { content: [{ type: "text", text: formatErrorForMcp(error) }], isError: true };
12
+ }
13
+ }
14
+ const path = z.string().min(1).describe("Memory path, /memories or below, e.g. /memories/preferences.md");
15
+ /**
16
+ * Agent memory of organization members (agent memory spec §7.1, §7.4). The "my" tools act on
17
+ * the API key owner's own memory; the member tools are for ADMIN and OWNER keys and never
18
+ * show paths or content.
19
+ */
20
+ export function register(server, client) {
21
+ server.tool("2kw_list_my_memory_files", "List the files in your agent memory (the API key owner's), ordered by path, without content.", {}, async () => reply(async () => (await client.GET("/v1/memories/me/files")).data));
22
+ server.tool("2kw_read_my_memory_file", "Read one of your agent memory files: the raw content, its version (for ifMatchVersion) and which agent wrote it last.", { path }, async ({ path }) => reply(async () => (await client.GET("/v1/memories/me/file", { params: { query: { path } } })).data));
23
+ server.tool("2kw_write_my_memory_file", "Create or replace one of your agent memory files with the complete content. Pass ifMatchVersion (the version from 2kw_read_my_memory_file) to refuse the write if the file changed since. Credentials are refused; files are limited to 32 KB.", {
24
+ path,
25
+ content: z.string().describe("The complete UTF-8 text content of the file"),
26
+ ifMatchVersion: z.number().int().min(0).optional().describe("Only replace the file if it still has this version"),
27
+ }, async ({ path, content, ifMatchVersion }) => reply(async () => (await client.PUT("/v1/memories/me/file", {
28
+ params: { query: { path } },
29
+ body: { content },
30
+ headers: ifMatchVersion === undefined ? undefined : { "If-Match": `"${ifMatchVersion}"` },
31
+ })).data));
32
+ server.tool("2kw_delete_my_memory_file", "Delete one of your agent memory files, or a directory with everything below it. Irreversible.", { path }, async ({ path }) => reply(async () => {
33
+ await client.DELETE("/v1/memories/me/file", { params: { query: { path } } });
34
+ }));
35
+ server.tool("2kw_forget_my_memory", "Delete your whole agent memory. Irreversible: confirm with the user before calling.", {}, async () => reply(async () => {
36
+ await client.DELETE("/v1/memories/me");
37
+ }));
38
+ server.tool("2kw_list_member_memory_usage", "Agent memory totals per organization member: file count, total bytes and last write. Never paths or content. Needs an ADMIN or OWNER key.", {}, async () => reply(async () => (await client.GET("/v1/memories/users")).data));
39
+ server.tool("2kw_erase_member_memory", "Erase one member's whole agent memory in this organization. Irreversible: confirm with the user before calling. Needs an ADMIN or OWNER key.", { userId: z.string().min(1).describe("The member's user id, from 2kw_list_member_memory_usage") }, async ({ userId }) => reply(async () => {
40
+ await client.DELETE("/v1/memories/users/{userId}", { params: { path: { userId } } });
41
+ }));
42
+ }
43
+ //# sourceMappingURL=memory.js.map