mirror of
https://github.com/hansjone/netxops.git
synced 2026-10-20 07:40:46 +08:00
Netx tools: convert MCP inputSchema to the DSH value schema DSL
defineTool rejected netx tools whose parameters carry standard JSON
Schema constraints ("parameters.page.minimum is not supported by the
value schema DSL"), and the old pass-through dropped the top-level
required list.
- mcp-schema.ts: fold minimum/maxItems/pattern/... into the description,
mark required per property, map anyOf/oneOf/type arrays (minus null)
to oneOf, default additionalProperties to true.
- Test compiles all 39 real /mcp tool schemas through defineTool.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
2ef3a43c63
commit
277a61894d
12 changed files with 2852 additions and 117 deletions
|
|
@ -1803,6 +1803,104 @@ import { readFileSync as readFileSync5 } from "node:fs";
|
|||
import { dirname as dirname5, join as join9 } from "node:path";
|
||||
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
||||
import { defineTool as defineTool2 } from "@deepseek-ai/dsh-tools";
|
||||
|
||||
// src/netx/mcp-schema.ts
|
||||
var ANNOTATIONS = ["description", "title", "default", "examples"];
|
||||
var SCALAR_TYPES = new Set(["string", "number", "integer", "boolean", "null"]);
|
||||
var CONSTRAINTS = [
|
||||
"minimum",
|
||||
"maximum",
|
||||
"exclusiveMinimum",
|
||||
"exclusiveMaximum",
|
||||
"multipleOf",
|
||||
"minLength",
|
||||
"maxLength",
|
||||
"pattern",
|
||||
"format",
|
||||
"minItems",
|
||||
"maxItems",
|
||||
"uniqueItems"
|
||||
];
|
||||
function isRecord(value) {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
}
|
||||
function withAnnotations(source, target) {
|
||||
for (const key of ANNOTATIONS) {
|
||||
if (Object.hasOwn(source, key))
|
||||
target[key] = source[key];
|
||||
}
|
||||
const notes = CONSTRAINTS.filter((key) => Object.hasOwn(source, key)).map((key) => `${key}=${JSON.stringify(source[key])}`);
|
||||
if (notes.length > 0) {
|
||||
const base = typeof target.description === "string" ? target.description : "";
|
||||
target.description = `${base}${base ? " " : ""}(${notes.join(", ")})`;
|
||||
}
|
||||
return target;
|
||||
}
|
||||
function variants(node) {
|
||||
const list = Array.isArray(node.oneOf) ? node.oneOf : Array.isArray(node.anyOf) ? node.anyOf : undefined;
|
||||
if (list)
|
||||
return list.filter(isRecord).filter((branch) => branch.type !== "null");
|
||||
if (Array.isArray(node.type)) {
|
||||
const { type: _types, ...rest } = node;
|
||||
return node.type.filter((t) => t !== "null").map((t) => ({ ...rest, type: t }));
|
||||
}
|
||||
return;
|
||||
}
|
||||
function toValue(node) {
|
||||
if (!isRecord(node))
|
||||
return { type: "json" };
|
||||
const branches = variants(node);
|
||||
if (branches) {
|
||||
if (branches.length === 0)
|
||||
return withAnnotations(node, { type: "json" });
|
||||
if (branches.length === 1)
|
||||
return withAnnotations(node, toValue({ ...branches[0] }));
|
||||
return withAnnotations(node, { oneOf: branches.map(toValue) });
|
||||
}
|
||||
const type = node.type;
|
||||
if (type === "object" || type === undefined && isRecord(node.properties)) {
|
||||
const out = {
|
||||
type: "object",
|
||||
additionalProperties: typeof node.additionalProperties === "boolean" ? node.additionalProperties : true
|
||||
};
|
||||
if (isRecord(node.properties))
|
||||
out.properties = toProperties(node.properties, node.required);
|
||||
return withAnnotations(node, out);
|
||||
}
|
||||
if (type === "array") {
|
||||
const out = { type: "array" };
|
||||
if (Object.hasOwn(node, "items"))
|
||||
out.items = toValue(node.items);
|
||||
return withAnnotations(node, out);
|
||||
}
|
||||
if (typeof type === "string" && SCALAR_TYPES.has(type)) {
|
||||
const out = { type };
|
||||
if (Array.isArray(node.enum) && node.enum.length > 0)
|
||||
out.enum = node.enum;
|
||||
if (Object.hasOwn(node, "const"))
|
||||
out.const = node.const;
|
||||
return withAnnotations(node, out);
|
||||
}
|
||||
return withAnnotations(node, { type: "json" });
|
||||
}
|
||||
function toProperties(properties, required) {
|
||||
const requiredSet = new Set(Array.isArray(required) ? required.filter((k) => typeof k === "string") : []);
|
||||
const out = {};
|
||||
for (const [key, value] of Object.entries(properties)) {
|
||||
const converted = toValue(value);
|
||||
if (requiredSet.has(key))
|
||||
converted.required = true;
|
||||
out[key] = converted;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
function parametersFromInputSchema(schema) {
|
||||
if (!isRecord(schema) || !isRecord(schema.properties))
|
||||
return {};
|
||||
return toProperties(schema.properties, schema.required);
|
||||
}
|
||||
|
||||
// src/netx/tools.ts
|
||||
function packageVersion() {
|
||||
try {
|
||||
const here = dirname5(fileURLToPath2(import.meta.url));
|
||||
|
|
@ -1828,18 +1926,6 @@ var mcpOut = {
|
|||
schema: { type: "object", additionalProperties: true },
|
||||
render: renderMcpResult
|
||||
};
|
||||
function parametersFromInputSchema(schema) {
|
||||
if (schema && typeof schema === "object" && !Array.isArray(schema)) {
|
||||
const props = schema.properties;
|
||||
if (props && typeof props === "object" && !Array.isArray(props)) {
|
||||
return props;
|
||||
}
|
||||
if (schema.type === undefined && schema.properties === undefined) {
|
||||
return schema;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
function defineRemoteTool(tool2, timeoutMs) {
|
||||
const name = publicToolName(tool2.name);
|
||||
return defineTool2({
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue