@2kw/ai-mcp-server 6.2.0-dev.14 → 6.2.0-dev.30
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/dist/client.js +9 -2
- package/dist/tools/agents.d.ts +14 -0
- package/dist/tools/agents.js +51 -14
- package/dist/tools/ai-gateway.d.ts +6 -0
- package/dist/tools/ai-gateway.js +30 -4
- package/dist/tools/providers.js +1 -1
- package/dist/tools/skills.d.ts +1 -1
- package/dist/tools/skills.js +15 -1
- package/package.json +2 -1
package/dist/client.js
CHANGED
|
@@ -2,6 +2,8 @@ import createClient from "openapi-fetch";
|
|
|
2
2
|
import { BackboneApiError } from "./errors.js";
|
|
3
3
|
/**
|
|
4
4
|
* Error-handling middleware: intercepts non-ok responses and throws BackboneApiError.
|
|
5
|
+
* The api-key filter now returns the OpenAI envelope (#658); the surface
|
|
6
|
+
* filter still sends the flat shape, so both are normalized here.
|
|
5
7
|
*/
|
|
6
8
|
const errorMiddleware = {
|
|
7
9
|
async onResponse({ response }) {
|
|
@@ -21,8 +23,13 @@ const errorMiddleware = {
|
|
|
21
23
|
}
|
|
22
24
|
throw new BackboneApiError({
|
|
23
25
|
status: body.status ?? response.status,
|
|
24
|
-
error:
|
|
25
|
-
|
|
26
|
+
error: typeof body.error === "string"
|
|
27
|
+
? body.error
|
|
28
|
+
: (body.error?.code ?? body.error?.type ?? body.title ?? response.statusText),
|
|
29
|
+
message: body.detail ??
|
|
30
|
+
body.message ??
|
|
31
|
+
body.error?.message ??
|
|
32
|
+
`HTTP ${response.status}: ${response.statusText}`,
|
|
26
33
|
timestamp: body.timestamp ?? new Date().toISOString(),
|
|
27
34
|
});
|
|
28
35
|
},
|
package/dist/tools/agents.d.ts
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import type { ApiClient } from "../client.js";
|
|
3
|
+
/**
|
|
4
|
+
* The `model`/`models` pair of an agent write (#626, spec #591 §3.3). The backend owns the
|
|
5
|
+
* rules (derivation, uniqueness, existence); this only refuses the two shapes a caller can
|
|
6
|
+
* get wrong locally, so the error names the tool's own parameters instead of a 400.
|
|
7
|
+
*/
|
|
8
|
+
export declare function modelFields(model: string | undefined, models: string[] | undefined, required: boolean): {
|
|
9
|
+
model?: string;
|
|
10
|
+
models?: string[];
|
|
11
|
+
};
|
|
12
|
+
/** `model: a` for a single-model agent, `models: a (default), b` otherwise. */
|
|
13
|
+
export declare function describeModels(row: {
|
|
14
|
+
model?: string;
|
|
15
|
+
models?: string[];
|
|
16
|
+
}): string;
|
|
3
17
|
export declare function register(server: McpServer, client: ApiClient): void;
|
|
4
18
|
//# sourceMappingURL=agents.d.ts.map
|
package/dist/tools/agents.js
CHANGED
|
@@ -1,5 +1,39 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { formatErrorForMcp } from "../errors.js";
|
|
3
|
+
const modelSchema = z
|
|
4
|
+
.string()
|
|
5
|
+
.min(1)
|
|
6
|
+
.describe("Default model in 'provider/model' format (e.g. 'openai/gpt-4o') or a bare platform model name. Shorthand for a one-entry `models` list; if both are given it must equal models[0].");
|
|
7
|
+
const modelsSchema = z
|
|
8
|
+
.array(z.string().min(1))
|
|
9
|
+
.min(1)
|
|
10
|
+
.max(10)
|
|
11
|
+
.describe("Ordered model list, 1-10 unique entries. The first entry is the default the agent runs on; the others are the models a single request may switch to with `agent/<ref>#<model>` (see 2kw_create_response `agentModel`).");
|
|
12
|
+
/**
|
|
13
|
+
* The `model`/`models` pair of an agent write (#626, spec #591 §3.3). The backend owns the
|
|
14
|
+
* rules (derivation, uniqueness, existence); this only refuses the two shapes a caller can
|
|
15
|
+
* get wrong locally, so the error names the tool's own parameters instead of a 400.
|
|
16
|
+
*/
|
|
17
|
+
export function modelFields(model, models, required) {
|
|
18
|
+
if (models !== undefined && models.length > 0) {
|
|
19
|
+
if (model !== undefined && model !== models[0]) {
|
|
20
|
+
throw new Error(`\`model\` ('${model}') must equal the first entry of \`models\` ('${models[0]}'), which is the default. Omit \`model\` when sending \`models\`.`);
|
|
21
|
+
}
|
|
22
|
+
return model !== undefined ? { model, models } : { models };
|
|
23
|
+
}
|
|
24
|
+
if (model !== undefined)
|
|
25
|
+
return { model };
|
|
26
|
+
if (required)
|
|
27
|
+
throw new Error("Either `model` or `models` is required.");
|
|
28
|
+
return {};
|
|
29
|
+
}
|
|
30
|
+
/** `model: a` for a single-model agent, `models: a (default), b` otherwise. */
|
|
31
|
+
export function describeModels(row) {
|
|
32
|
+
const models = row.models && row.models.length > 0 ? row.models : row.model ? [row.model] : [];
|
|
33
|
+
if (models.length <= 1)
|
|
34
|
+
return `model: ${models[0] ?? row.model ?? "?"}`;
|
|
35
|
+
return `models: ${models[0]} (default), ${models.slice(1).join(", ")}`;
|
|
36
|
+
}
|
|
3
37
|
export function register(server, client) {
|
|
4
38
|
// ── list_agents ─────────────────────────────────────────────────────────
|
|
5
39
|
server.tool("2kw_list_agents", "List agents in the organization with optional search and pagination.", {
|
|
@@ -18,7 +52,7 @@ export function register(server, client) {
|
|
|
18
52
|
},
|
|
19
53
|
});
|
|
20
54
|
const result = data;
|
|
21
|
-
const lines = (result.content ?? []).map((a) => `- ${a.name} (id: ${a.id},
|
|
55
|
+
const lines = (result.content ?? []).map((a) => `- ${a.name} (id: ${a.id}, ${describeModels(a)})${a.description ? ` — ${a.description}` : ""}`);
|
|
22
56
|
return {
|
|
23
57
|
content: [
|
|
24
58
|
{
|
|
@@ -53,18 +87,19 @@ export function register(server, client) {
|
|
|
53
87
|
}
|
|
54
88
|
});
|
|
55
89
|
// ── create_agent ────────────────────────────────────────────────────────
|
|
56
|
-
server.tool("2kw_create_agent", "Create a new agent for the organization.", {
|
|
90
|
+
server.tool("2kw_create_agent", "Create a new agent for the organization. Give its model as `model` (one model) or `models` (an ordered list whose first entry is the default); one of the two is required.", {
|
|
57
91
|
name: z.string().min(1).describe("Agent name"),
|
|
58
|
-
model:
|
|
92
|
+
model: modelSchema.optional(),
|
|
93
|
+
models: modelsSchema.optional(),
|
|
59
94
|
description: z.string().optional().describe("Agent description"),
|
|
60
95
|
instructions: z.string().optional().describe("System instructions for the agent"),
|
|
61
96
|
options: z.unknown().optional().describe("Arbitrary model options (JSON object)"),
|
|
62
97
|
tools: z.unknown().optional().describe("Tool configuration (JSON object)"),
|
|
63
98
|
skills: z.unknown().optional().describe("Skill configuration (JSON object)"),
|
|
64
99
|
hitlPolicy: z.unknown().optional().describe("Human-in-the-loop approval policy (JSON object)"),
|
|
65
|
-
}, async ({ name, model, description, instructions, options, tools, skills, hitlPolicy }) => {
|
|
100
|
+
}, async ({ name, model, models, description, instructions, options, tools, skills, hitlPolicy }) => {
|
|
66
101
|
try {
|
|
67
|
-
const body = { name, model };
|
|
102
|
+
const body = { name, ...modelFields(model, models, true) };
|
|
68
103
|
if (description !== undefined)
|
|
69
104
|
body.description = description;
|
|
70
105
|
if (instructions !== undefined)
|
|
@@ -92,19 +127,20 @@ export function register(server, client) {
|
|
|
92
127
|
}
|
|
93
128
|
});
|
|
94
129
|
// ── update_agent ────────────────────────────────────────────────────────
|
|
95
|
-
server.tool("2kw_update_agent", "Update an agent's metadata and configuration. name
|
|
130
|
+
server.tool("2kw_update_agent", "Update an agent's metadata and configuration. name is required on every update. `model` or `models` replaces the stored model list (a shorter list leaves no tail); omit both to keep the current models. Does not create a version.", {
|
|
96
131
|
agentId: z.string().describe("The agent ID"),
|
|
97
132
|
name: z.string().min(1).describe("Agent name"),
|
|
98
|
-
model:
|
|
133
|
+
model: modelSchema.optional(),
|
|
134
|
+
models: modelsSchema.optional(),
|
|
99
135
|
description: z.string().optional().describe("Agent description"),
|
|
100
136
|
instructions: z.string().optional().describe("System instructions for the agent"),
|
|
101
137
|
options: z.unknown().optional().describe("Arbitrary model options (JSON object)"),
|
|
102
138
|
tools: z.unknown().optional().describe("Tool configuration (JSON object)"),
|
|
103
139
|
skills: z.unknown().optional().describe("Skill configuration (JSON object)"),
|
|
104
140
|
hitlPolicy: z.unknown().optional().describe("Human-in-the-loop approval policy (JSON object)"),
|
|
105
|
-
}, async ({ agentId, name, model, description, instructions, options, tools, skills, hitlPolicy }) => {
|
|
141
|
+
}, async ({ agentId, name, model, models, description, instructions, options, tools, skills, hitlPolicy }) => {
|
|
106
142
|
try {
|
|
107
|
-
const body = { name, model };
|
|
143
|
+
const body = { name, ...modelFields(model, models, false) };
|
|
108
144
|
if (description !== undefined)
|
|
109
145
|
body.description = description;
|
|
110
146
|
if (instructions !== undefined)
|
|
@@ -160,7 +196,7 @@ export function register(server, client) {
|
|
|
160
196
|
params: { path: { agentId }, query: { pageable: { page, size } } },
|
|
161
197
|
});
|
|
162
198
|
const result = data;
|
|
163
|
-
const lines = (result.content ?? []).map((v) => `- v${v.versionNumber} (id: ${v.id})${v.changeDescription ? ` — ${v.changeDescription}` : ""}`);
|
|
199
|
+
const lines = (result.content ?? []).map((v) => `- v${v.versionNumber} (id: ${v.id}, ${describeModels(v)})${v.changeDescription ? ` — ${v.changeDescription}` : ""}`);
|
|
164
200
|
return {
|
|
165
201
|
content: [
|
|
166
202
|
{
|
|
@@ -178,18 +214,19 @@ export function register(server, client) {
|
|
|
178
214
|
}
|
|
179
215
|
});
|
|
180
216
|
// ── create_agent_version ────────────────────────────────────────────────
|
|
181
|
-
server.tool("2kw_create_agent_version", "Create a new version of an agent. Auto-updates the 'latest' label.", {
|
|
217
|
+
server.tool("2kw_create_agent_version", "Create a new version of an agent. Auto-updates the 'latest' label. Give its model as `model` (one model) or `models` (an ordered list whose first entry is the default); one of the two is required.", {
|
|
182
218
|
agentId: z.string().describe("The agent ID"),
|
|
183
|
-
model:
|
|
219
|
+
model: modelSchema.optional(),
|
|
220
|
+
models: modelsSchema.optional(),
|
|
184
221
|
changeDescription: z.string().optional().describe("Description of what changed in this version"),
|
|
185
222
|
instructions: z.string().optional().describe("System instructions for the agent"),
|
|
186
223
|
options: z.unknown().optional().describe("Arbitrary model options (JSON object)"),
|
|
187
224
|
tools: z.unknown().optional().describe("Tool configuration (JSON object)"),
|
|
188
225
|
skills: z.unknown().optional().describe("Skill configuration (JSON object)"),
|
|
189
226
|
hitlPolicy: z.unknown().optional().describe("Human-in-the-loop approval policy (JSON object)"),
|
|
190
|
-
}, async ({ agentId, model, changeDescription, instructions, options, tools, skills, hitlPolicy }) => {
|
|
227
|
+
}, async ({ agentId, model, models, changeDescription, instructions, options, tools, skills, hitlPolicy }) => {
|
|
191
228
|
try {
|
|
192
|
-
const body = { model };
|
|
229
|
+
const body = { ...modelFields(model, models, true) };
|
|
193
230
|
if (changeDescription !== undefined)
|
|
194
231
|
body.changeDescription = changeDescription;
|
|
195
232
|
if (instructions !== undefined)
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import type { ApiClient } from "../client.js";
|
|
3
|
+
/**
|
|
4
|
+
* The `model` value for an agent run: `agent/<ref>[@<label>]`, plus `#<model>` when the
|
|
5
|
+
* request switches to another entry of the version's `models` list (#626, spec #591 §4.1).
|
|
6
|
+
* Returns undefined when no agent is given.
|
|
7
|
+
*/
|
|
8
|
+
export declare function agentModelReference(agent: string | undefined, agentModel?: string): string | undefined;
|
|
3
9
|
export declare function register(server: McpServer, client: ApiClient): void;
|
|
4
10
|
//# sourceMappingURL=ai-gateway.d.ts.map
|
package/dist/tools/ai-gateway.js
CHANGED
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { formatErrorForMcp } from "../errors.js";
|
|
3
|
+
/**
|
|
4
|
+
* The `model` value for an agent run: `agent/<ref>[@<label>]`, plus `#<model>` when the
|
|
5
|
+
* request switches to another entry of the version's `models` list (#626, spec #591 §4.1).
|
|
6
|
+
* Returns undefined when no agent is given.
|
|
7
|
+
*/
|
|
8
|
+
export function agentModelReference(agent, agentModel) {
|
|
9
|
+
if (!agent)
|
|
10
|
+
return undefined;
|
|
11
|
+
return agentModel ? `agent/${agent}#${agentModel}` : `agent/${agent}`;
|
|
12
|
+
}
|
|
3
13
|
export function register(server, client) {
|
|
4
14
|
// ── chat ────────────────────────────────────────────────────────────────
|
|
5
|
-
server.tool("2kw_chat", "Send a chat completion request through Backbone's OpenAI-compatible AI gateway. Supports
|
|
15
|
+
server.tool("2kw_chat", "Send a chat completion request through Backbone's OpenAI-compatible AI gateway. Supports OpenAI, Azure OpenAI, Anthropic, xAI, Mistral and Ollama. Model format: 'provider/model' (e.g. 'openai/gpt-4o', 'anthropic/claude-sonnet-4-5-20250929'). Always non-streaming.", {
|
|
6
16
|
model: z.string().describe("Model identifier in 'provider/model' format"),
|
|
7
17
|
messages: z
|
|
8
18
|
.array(z.object({
|
|
@@ -82,18 +92,23 @@ export function register(server, client) {
|
|
|
82
92
|
}
|
|
83
93
|
});
|
|
84
94
|
// ── 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.", {
|
|
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.", {
|
|
86
96
|
input: z.string().min(1).describe("The input text (sent as a single user message)"),
|
|
87
97
|
agent: z
|
|
88
98
|
.string()
|
|
89
99
|
.optional()
|
|
90
100
|
.describe("Stored agent id or name to invoke, optionally 'ref@label'. Mutually exclusive with `model`."),
|
|
101
|
+
agentModel: z
|
|
102
|
+
.string()
|
|
103
|
+
.min(1)
|
|
104
|
+
.optional()
|
|
105
|
+
.describe("Only with `agent`: run this request on this model instead of the agent's default. Must be one of the resolved version's `models`; sent as 'agent/<ref>#<model>'."),
|
|
91
106
|
model: z
|
|
92
107
|
.string()
|
|
93
108
|
.optional()
|
|
94
109
|
.describe("Model identifier in 'provider/model' format. Mutually exclusive with `agent`."),
|
|
95
110
|
conversation: z.string().optional().describe("Conversation ID this response belongs to"),
|
|
96
|
-
}, async ({ input, agent, model, conversation }) => {
|
|
111
|
+
}, async ({ input, agent, agentModel, model, conversation }) => {
|
|
97
112
|
try {
|
|
98
113
|
if (agent && model) {
|
|
99
114
|
throw new Error("Use either `agent` or `model`, not both.");
|
|
@@ -101,7 +116,13 @@ export function register(server, client) {
|
|
|
101
116
|
if (!agent && !model) {
|
|
102
117
|
throw new Error("Either `agent` or `model` is required.");
|
|
103
118
|
}
|
|
104
|
-
|
|
119
|
+
if (agentModel !== undefined && !agent) {
|
|
120
|
+
throw new Error("`agentModel` switches a stored agent's model; it needs `agent`.");
|
|
121
|
+
}
|
|
122
|
+
if (agentModel !== undefined && agent?.includes("#")) {
|
|
123
|
+
throw new Error("Give the model switch once: either 'ref#model' in `agent` or `agentModel`.");
|
|
124
|
+
}
|
|
125
|
+
const resolvedModel = agentModelReference(agent, agentModel) ?? model;
|
|
105
126
|
const body = {
|
|
106
127
|
model: resolvedModel,
|
|
107
128
|
// The wire `input` field also accepts a bare string (shorthand for
|
|
@@ -141,6 +162,11 @@ export function register(server, client) {
|
|
|
141
162
|
if (parts.length === 0) {
|
|
142
163
|
parts.push({ type: "text", text: JSON.stringify(result, null, 2) });
|
|
143
164
|
}
|
|
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
|
+
}
|
|
144
170
|
return { content: parts };
|
|
145
171
|
}
|
|
146
172
|
catch (error) {
|
package/dist/tools/providers.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { formatErrorForMcp } from "../errors.js";
|
|
3
|
+
// The API enum also has VERTEX_AI, but the gateway cannot build a model for it yet (#118).
|
|
3
4
|
const ProviderType = z.enum([
|
|
4
5
|
"OPENAI",
|
|
5
6
|
"AZURE_OPENAI",
|
|
6
7
|
"ANTHROPIC",
|
|
7
8
|
"XAI",
|
|
8
9
|
"MISTRAL",
|
|
9
|
-
"VERTEX_AI",
|
|
10
10
|
"OLLAMA",
|
|
11
11
|
]);
|
|
12
12
|
export function register(server, client) {
|
package/dist/tools/skills.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import type { ApiClient } from "../client.js";
|
|
3
|
-
/** Register skill read and
|
|
3
|
+
/** Register skill read, import and delete tools (#642, #683). */
|
|
4
4
|
export declare function register(server: McpServer, client: ApiClient): void;
|
|
5
5
|
//# sourceMappingURL=skills.d.ts.map
|
package/dist/tools/skills.js
CHANGED
|
@@ -10,7 +10,7 @@ function paginationParams(params) {
|
|
|
10
10
|
sort: params.sort ? [params.sort] : undefined,
|
|
11
11
|
};
|
|
12
12
|
}
|
|
13
|
-
/** Register skill read and
|
|
13
|
+
/** Register skill read, import and delete tools (#642, #683). */
|
|
14
14
|
export function register(server, client) {
|
|
15
15
|
server.tool("2kw_list_skills", "List org skills with optional name/status filter and pagination.", {
|
|
16
16
|
search: z.string().optional().describe("Filter by name"),
|
|
@@ -131,6 +131,20 @@ export function register(server, client) {
|
|
|
131
131
|
};
|
|
132
132
|
}
|
|
133
133
|
});
|
|
134
|
+
server.tool("2kw_delete_skill", "Delete a skill and all its versions. Refused with 409 while an agent version binds it; the message names the agents.", { skillId: z.string() }, async (params) => {
|
|
135
|
+
try {
|
|
136
|
+
await client.DELETE("/v1/skills/{id}", { params: { path: { id: params.skillId } } });
|
|
137
|
+
return {
|
|
138
|
+
content: [{ type: "text", text: JSON.stringify({ deleted: params.skillId }, null, 2) }],
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
return {
|
|
143
|
+
content: [{ type: "text", text: formatErrorForMcp(error) }],
|
|
144
|
+
isError: true,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
});
|
|
134
148
|
server.tool("2kw_import_skill", "Import a local SKILL.md or .zip file. Returns outcome, version, renamedFrom and skipped files. Zip directories before importing.", { filePath: z.string().min(1).describe("Local path to SKILL.md or a .zip archive") }, async (params) => {
|
|
135
149
|
try {
|
|
136
150
|
const info = await stat(params.filePath);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@2kw/ai-mcp-server",
|
|
3
|
-
"version": "6.2.0-dev.
|
|
3
|
+
"version": "6.2.0-dev.30",
|
|
4
4
|
"description": "MCP server for 2kw.ai — EU-hosted AI platform: OpenAI-compatible LLM gateway, schema-driven document extraction, transcription, agents with a knowledge base, and cost observability. 158 tools for Claude Code, Cursor, and Windsurf.",
|
|
5
5
|
"mcpName": "ai.2kw/mcp-server",
|
|
6
6
|
"keywords": [
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"check:generated": "tsx openapi/scripts/check-generated.ts",
|
|
42
42
|
"fix:generated": "tsx openapi/scripts/check-generated.ts --write",
|
|
43
43
|
"check:coverage": "tsx ../cli/openapi/scripts/check-coverage.ts",
|
|
44
|
+
"test": "tsx --test tests/*.test.ts",
|
|
44
45
|
"typecheck": "tsc --noEmit"
|
|
45
46
|
},
|
|
46
47
|
"dependencies": {
|