mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-17 20:21:13 +00:00
88 lines
2.8 KiB
TypeScript
88 lines
2.8 KiB
TypeScript
import { logWarn } from "../logger.js";
|
|
import { resolveGatewayScopedTools } from "./tool-resolution.js";
|
|
|
|
export type McpLoopbackTool = ReturnType<typeof resolveGatewayScopedTools>["tools"][number];
|
|
|
|
export type McpToolSchemaEntry = {
|
|
name: string;
|
|
description: string | undefined;
|
|
inputSchema: Record<string, unknown>;
|
|
};
|
|
|
|
function flattenUnionSchema(raw: Record<string, unknown>): Record<string, unknown> {
|
|
const variants = (raw.anyOf ?? raw.oneOf) as Record<string, unknown>[] | undefined;
|
|
if (!Array.isArray(variants) || variants.length === 0) {
|
|
return raw;
|
|
}
|
|
const mergedProps: Record<string, unknown> = {};
|
|
const requiredSets: Set<string>[] = [];
|
|
for (const variant of variants) {
|
|
const props = variant.properties as Record<string, unknown> | undefined;
|
|
if (props) {
|
|
for (const [key, schema] of Object.entries(props)) {
|
|
if (!(key in mergedProps)) {
|
|
mergedProps[key] = schema;
|
|
continue;
|
|
}
|
|
const existing = mergedProps[key] as Record<string, unknown>;
|
|
const incoming = schema as Record<string, unknown>;
|
|
if (Array.isArray(existing.enum) && Array.isArray(incoming.enum)) {
|
|
mergedProps[key] = {
|
|
...existing,
|
|
enum: [...new Set([...(existing.enum as unknown[]), ...(incoming.enum as unknown[])])],
|
|
};
|
|
continue;
|
|
}
|
|
if ("const" in existing && "const" in incoming && existing.const !== incoming.const) {
|
|
const merged: Record<string, unknown> = {
|
|
...existing,
|
|
enum: [existing.const, incoming.const],
|
|
};
|
|
delete merged.const;
|
|
mergedProps[key] = merged;
|
|
continue;
|
|
}
|
|
logWarn(
|
|
`mcp loopback: conflicting schema definitions for "${key}", keeping the first variant`,
|
|
);
|
|
}
|
|
}
|
|
requiredSets.push(
|
|
new Set(Array.isArray(variant.required) ? (variant.required as string[]) : []),
|
|
);
|
|
}
|
|
const required =
|
|
requiredSets.length > 0
|
|
? [
|
|
...requiredSets.reduce(
|
|
(left, right) => new Set([...left].filter((key) => right.has(key))),
|
|
),
|
|
]
|
|
: [];
|
|
const { anyOf: _anyOf, oneOf: _oneOf, ...rest } = raw;
|
|
return { ...rest, type: "object", properties: mergedProps, required };
|
|
}
|
|
|
|
export function buildMcpToolSchema(tools: McpLoopbackTool[]): McpToolSchemaEntry[] {
|
|
return tools.map((tool) => {
|
|
let raw =
|
|
tool.parameters && typeof tool.parameters === "object"
|
|
? { ...(tool.parameters as Record<string, unknown>) }
|
|
: {};
|
|
if (raw.anyOf || raw.oneOf) {
|
|
raw = flattenUnionSchema(raw);
|
|
}
|
|
if (raw.type !== "object") {
|
|
raw.type = "object";
|
|
if (!raw.properties) {
|
|
raw.properties = {};
|
|
}
|
|
}
|
|
return {
|
|
name: tool.name,
|
|
description: tool.description,
|
|
inputSchema: raw,
|
|
};
|
|
});
|
|
}
|