@tokenlabai/mcp-server 0.6.7 → 0.6.8
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 +4 -4
- package/contract/mcp-overlay.json +27 -0
- package/contract/openapi.json +420 -537
- package/generated/public-contract.json +7 -8
- package/generated/tools.json +174 -196
- package/package.json +1 -1
- package/scripts/generate-contract.mjs +108 -35
|
@@ -112,6 +112,17 @@ for (const [profileName, profile] of profileEntries) {
|
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
|
+
for (const [operationId, variants] of Object.entries(overlay.operation_variants || {})) {
|
|
116
|
+
if (!operations.has(operationId)) {
|
|
117
|
+
throw new Error(`MCP operation variants reference an operation missing from OpenAPI: ${operationId}`);
|
|
118
|
+
}
|
|
119
|
+
if (!Array.isArray(variants) || variants.length === 0) {
|
|
120
|
+
throw new Error(`MCP operation variants for ${operationId} must be a non-empty array`);
|
|
121
|
+
}
|
|
122
|
+
for (const variant of variants) {
|
|
123
|
+
if (!variant.tool_name) throw new Error(`MCP operation variant for ${operationId} is missing tool_name`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
115
126
|
|
|
116
127
|
function operationProfiles(operationId, tags) {
|
|
117
128
|
return profileEntries
|
|
@@ -136,7 +147,24 @@ function chooseContentTypes(operationId, operation, override) {
|
|
|
136
147
|
return [available.includes("application/json") ? "application/json" : available[0]];
|
|
137
148
|
}
|
|
138
149
|
|
|
139
|
-
function
|
|
150
|
+
function requestSchemaForOverride(operationId, media, override) {
|
|
151
|
+
if (!override.request_schema) return media.schema || { type: "object", additionalProperties: true };
|
|
152
|
+
if (typeof override.request_schema === "string") {
|
|
153
|
+
if (!override.request_schema.startsWith("#/")) {
|
|
154
|
+
throw new Error(`${operationId} request_schema must be a local OpenAPI reference`);
|
|
155
|
+
}
|
|
156
|
+
if (!getByPointer(override.request_schema)) {
|
|
157
|
+
throw new Error(`${operationId} request_schema does not resolve: ${override.request_schema}`);
|
|
158
|
+
}
|
|
159
|
+
return { $ref: override.request_schema };
|
|
160
|
+
}
|
|
161
|
+
if (typeof override.request_schema !== "object" || Array.isArray(override.request_schema)) {
|
|
162
|
+
throw new Error(`${operationId} request_schema must be an object or local OpenAPI reference`);
|
|
163
|
+
}
|
|
164
|
+
return override.request_schema;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function buildInputSchema(operationId, pathItem, operation, contentType, override) {
|
|
140
168
|
const properties = {};
|
|
141
169
|
const required = [];
|
|
142
170
|
const bindings = { path: [], query: [], header: [], body: [], files: [] };
|
|
@@ -156,7 +184,7 @@ function buildInputSchema(pathItem, operation, contentType) {
|
|
|
156
184
|
|
|
157
185
|
if (contentType) {
|
|
158
186
|
const media = operation.requestBody.content[contentType];
|
|
159
|
-
const bodySchema = normalizeSchema(media
|
|
187
|
+
const bodySchema = normalizeSchema(requestSchemaForOverride(operationId, media, override));
|
|
160
188
|
if (bodySchema.type === "object" || bodySchema.properties) {
|
|
161
189
|
for (const [name, schema] of Object.entries(bodySchema.properties || {})) {
|
|
162
190
|
if (properties[name]) throw new Error(`MCP argument collision for ${operation.operationId}: ${name}`);
|
|
@@ -227,6 +255,41 @@ function applyInputOverrides(schema, bindings, override) {
|
|
|
227
255
|
}
|
|
228
256
|
schema.properties[name] = { ...schema.properties[name], default: value };
|
|
229
257
|
}
|
|
258
|
+
for (const [name, value] of Object.entries(override.fixed_arguments || {})) {
|
|
259
|
+
const property = schema.properties[name];
|
|
260
|
+
if (!property) throw new Error(`Cannot fix unknown MCP argument: ${name}`);
|
|
261
|
+
if (Array.isArray(property.enum) && !property.enum.includes(value)) {
|
|
262
|
+
throw new Error(`Fixed MCP argument ${name} is outside its enum`);
|
|
263
|
+
}
|
|
264
|
+
if (Object.hasOwn(property, "const") && property.const !== value) {
|
|
265
|
+
throw new Error(`Fixed MCP argument ${name} does not match its const value`);
|
|
266
|
+
}
|
|
267
|
+
delete schema.properties[name];
|
|
268
|
+
if (schema.required) schema.required = schema.required.filter((entry) => entry !== name);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function mergeOperationOverride(base, variant) {
|
|
273
|
+
return {
|
|
274
|
+
...base,
|
|
275
|
+
...variant,
|
|
276
|
+
input_property_overrides: {
|
|
277
|
+
...(base.input_property_overrides || {}),
|
|
278
|
+
...(variant.input_property_overrides || {})
|
|
279
|
+
},
|
|
280
|
+
default_arguments: {
|
|
281
|
+
...(base.default_arguments || {}),
|
|
282
|
+
...(variant.default_arguments || {})
|
|
283
|
+
},
|
|
284
|
+
fixed_arguments: {
|
|
285
|
+
...(base.fixed_arguments || {}),
|
|
286
|
+
...(variant.fixed_arguments || {})
|
|
287
|
+
},
|
|
288
|
+
annotations: {
|
|
289
|
+
...(base.annotations || {}),
|
|
290
|
+
...(variant.annotations || {})
|
|
291
|
+
}
|
|
292
|
+
};
|
|
230
293
|
}
|
|
231
294
|
|
|
232
295
|
function annotations(method) {
|
|
@@ -244,39 +307,49 @@ for (const [operationId, indexed] of operations) {
|
|
|
244
307
|
const profiles = operationProfiles(operationId, tags);
|
|
245
308
|
if (profiles.length === 0) continue;
|
|
246
309
|
|
|
247
|
-
const
|
|
248
|
-
const
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
.
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
310
|
+
const baseOverride = overlay.operation_overrides[operationId] || {};
|
|
311
|
+
const configurations = [
|
|
312
|
+
baseOverride,
|
|
313
|
+
...(overlay.operation_variants?.[operationId] || []).map((variant) => mergeOperationOverride(baseOverride, variant))
|
|
314
|
+
];
|
|
315
|
+
for (const override of configurations) {
|
|
316
|
+
const contentTypes = chooseContentTypes(operationId, indexed.operation, override);
|
|
317
|
+
for (const contentType of contentTypes) {
|
|
318
|
+
const suffix = contentTypes.length > 1 && contentType !== "application/json"
|
|
319
|
+
? `_${contentType.split("/").at(-1).replace(/[^A-Za-z0-9]+/g, "_")}`
|
|
320
|
+
: "";
|
|
321
|
+
const toolName = override.tool_names_by_content_type?.[contentType]
|
|
322
|
+
|| `${override.tool_name || snakeCase(operationId)}${suffix}`;
|
|
323
|
+
const { schema, bindings } = buildInputSchema(operationId, indexed.pathItem, indexed.operation, contentType, override);
|
|
324
|
+
applyInputOverrides(schema, bindings, override);
|
|
325
|
+
const description = override.description || [indexed.operation.summary, indexed.operation.description]
|
|
326
|
+
.filter(Boolean)
|
|
327
|
+
.join(" ")
|
|
328
|
+
.replace(/\s+/g, " ")
|
|
329
|
+
.trim();
|
|
330
|
+
const defaultArguments = {
|
|
331
|
+
...(override.default_arguments || {}),
|
|
332
|
+
...(override.fixed_arguments || {})
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
tools.push({
|
|
336
|
+
name: toolName,
|
|
337
|
+
title: override.title || indexed.operation.summary?.replace(/\s+/g, " ").trim() || toolName,
|
|
338
|
+
operation_id: operationId,
|
|
339
|
+
method: indexed.method,
|
|
340
|
+
path: indexed.path,
|
|
341
|
+
content_type: contentType,
|
|
342
|
+
auth: publicAuth.has(operationId) ? "optional" : "required",
|
|
343
|
+
tags,
|
|
344
|
+
profiles,
|
|
345
|
+
description,
|
|
346
|
+
input_schema: schema,
|
|
347
|
+
bindings,
|
|
348
|
+
...(Object.keys(defaultArguments).length > 0 ? { default_arguments: defaultArguments } : {}),
|
|
349
|
+
annotations: { ...annotations(indexed.method), ...(override.annotations || {}) },
|
|
350
|
+
...(override.task ? { task: override.task } : {})
|
|
351
|
+
});
|
|
352
|
+
}
|
|
280
353
|
}
|
|
281
354
|
}
|
|
282
355
|
|