mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-09 15:59:30 +00:00
fix(gateway): merge MCP schema properties by own keys (#102856)
Co-authored-by: Alix-007 <li.long15@xydigit.com>
This commit is contained in:
parent
aaf5af2fbc
commit
29517fe178
2 changed files with 87 additions and 3 deletions
84
src/gateway/mcp-http.schema.test.ts
Normal file
84
src/gateway/mcp-http.schema.test.ts
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
// Gateway tests cover MCP loopback schema projection behavior.
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { buildMcpToolSchema } from "./mcp-http.schema.js";
|
||||||
|
|
||||||
|
describe("buildMcpToolSchema", () => {
|
||||||
|
it("keeps union schema properties named like Object prototype keys", () => {
|
||||||
|
const [entry] = buildMcpToolSchema([
|
||||||
|
{
|
||||||
|
name: "proof_tool",
|
||||||
|
description: "proof",
|
||||||
|
parameters: {
|
||||||
|
anyOf: [
|
||||||
|
{
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
toString: { type: "string" },
|
||||||
|
},
|
||||||
|
required: ["toString"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
} as never,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const inputSchema = entry?.inputSchema as
|
||||||
|
| { properties?: Record<string, unknown>; required?: string[] }
|
||||||
|
| undefined;
|
||||||
|
const propertySchema = inputSchema?.properties?.["toString"];
|
||||||
|
|
||||||
|
expect(Object.hasOwn(inputSchema?.properties ?? {}, "toString")).toBe(true);
|
||||||
|
expect(propertySchema).toEqual({ type: "string" });
|
||||||
|
expect(inputSchema?.required).toEqual(["toString"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("serializes union schema properties named __proto__ as own keys", () => {
|
||||||
|
const protoKey = "__proto__";
|
||||||
|
const [entry] = buildMcpToolSchema([
|
||||||
|
{
|
||||||
|
name: "proof_tool",
|
||||||
|
description: "proof",
|
||||||
|
parameters: {
|
||||||
|
anyOf: [
|
||||||
|
{
|
||||||
|
type: "object",
|
||||||
|
properties: Object.fromEntries([[protoKey, { type: "string" }]]),
|
||||||
|
required: [protoKey],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
} as never,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const inputSchema = entry?.inputSchema as
|
||||||
|
| { properties?: Record<string, unknown>; required?: string[] }
|
||||||
|
| undefined;
|
||||||
|
|
||||||
|
expect(Object.hasOwn(inputSchema?.properties ?? {}, protoKey)).toBe(true);
|
||||||
|
expect(inputSchema?.properties?.[protoKey]).toEqual({ type: "string" });
|
||||||
|
expect(JSON.stringify(inputSchema?.properties)).toContain('"__proto__"');
|
||||||
|
expect(inputSchema?.required).toEqual([protoKey]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not keep inherited prototype names as required schema keys", () => {
|
||||||
|
const [entry] = buildMcpToolSchema([
|
||||||
|
{
|
||||||
|
name: "proof_tool",
|
||||||
|
description: "proof",
|
||||||
|
parameters: {
|
||||||
|
anyOf: [
|
||||||
|
{
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
value: { type: "string" },
|
||||||
|
},
|
||||||
|
required: ["toString"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
} as never,
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(entry?.inputSchema.required).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -67,7 +67,7 @@ function flattenUnionSchema(
|
||||||
if (!Array.isArray(variants) || variants.length === 0) {
|
if (!Array.isArray(variants) || variants.length === 0) {
|
||||||
return raw;
|
return raw;
|
||||||
}
|
}
|
||||||
const mergedProps: Record<string, unknown> = {};
|
const mergedProps = Object.create(null) as Record<string, unknown>;
|
||||||
const requiredSets: Set<string>[] = [];
|
const requiredSets: Set<string>[] = [];
|
||||||
for (const variant of variants) {
|
for (const variant of variants) {
|
||||||
if (variant === true) {
|
if (variant === true) {
|
||||||
|
|
@ -86,7 +86,7 @@ function flattenUnionSchema(
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!(key in mergedProps)) {
|
if (!Object.hasOwn(mergedProps, key)) {
|
||||||
mergedProps[key] = schema;
|
mergedProps[key] = schema;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -139,7 +139,7 @@ function flattenUnionSchema(
|
||||||
const required =
|
const required =
|
||||||
requiredSets.length > 0
|
requiredSets.length > 0
|
||||||
? [...(requiredSets[0] ?? [])].filter(
|
? [...(requiredSets[0] ?? [])].filter(
|
||||||
(key) => key in mergedProps && requiredSets.every((set) => set.has(key)),
|
(key) => Object.hasOwn(mergedProps, key) && requiredSets.every((set) => set.has(key)),
|
||||||
)
|
)
|
||||||
: [];
|
: [];
|
||||||
const { anyOf: _anyOf, oneOf: _oneOf, ...rest } = raw;
|
const { anyOf: _anyOf, oneOf: _oneOf, ...rest } = raw;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue