fix: narrow empty MCP tool schema normalization (#60176) (thanks @Bartok9)

This commit is contained in:
Peter Steinberger 2026-04-03 20:43:05 +09:00
parent 19dbe00763
commit 57999f9965
3 changed files with 20 additions and 5 deletions

View File

@ -39,6 +39,7 @@ Docs: https://docs.openclaw.ai
- Android/assistant: keep queued App Actions prompts pending when auto-send enqueue is rejected, so transient chat-health drops do not silently lose the assistant request. Thanks @obviyus.
- Plugins/Google: separate OAuth CSRF state from PKCE code verifier during Gemini browser sign-in so state validation and token exchange use independent values. (#59116) Thanks @eleqtrizit.
- Agents/subagents: honor `agents.defaults.subagents.allowAgents` for `sessions_spawn` and `agents_list`, so default cross-agent allowlists work without duplicating per-agent config. (#59944) Thanks @hclsys.
- Agents/tools: normalize only truly empty MCP tool schemas to `{ type: "object", properties: {} }` so OpenAI accepts parameter-free tools without rewriting unrelated conditional schemas. (#60176) Thanks @Bartok9.
- Plugins/browser: block SSRF redirect bypass by installing a real-time Playwright route handler before `page.goto()` so navigation to private/internal IPs is intercepted and aborted mid-redirect instead of checked post-hoc. (#58771) Thanks @pgondhi987.
- Android/gateway: require TLS for non-loopback remote gateway endpoints while still allowing local loopback and emulator cleartext setup flows. (#58475) Thanks @eleqtrizit.
- Exec/Windows: hide transient console windows for `runExec` and `runCommandWithTimeout` child-process launches, matching other Windows exec paths and stopping visible shell flashes during tool runs. (#59466) Thanks @lawrence3699.

View File

@ -20,6 +20,20 @@ describe("normalizeToolParameters", () => {
expect(parameters.properties).toEqual({});
});
it("does not rewrite non-empty schemas that still lack type/properties", () => {
const tool: AnyAgentTool = {
name: "conditional",
label: "conditional",
description: "Conditional schema stays untouched",
parameters: { allOf: [] },
execute: vi.fn(),
};
const normalized = normalizeToolParameters(tool);
expect(normalized.parameters).toEqual({ allOf: [] });
});
it("injects properties:{} for type:object schemas missing properties (MCP no-param tools)", () => {
const tool: AnyAgentTool = {
name: "list_regions",

View File

@ -133,11 +133,11 @@ export function normalizeToolParameterSchema(
? "oneOf"
: null;
if (!variantKey) {
// Handle truly empty schemas (no type, no properties, no unions) —
// OpenAI requires `type: "object"` with `properties` for tool schemas.
// MCP tools with parameter-free schemas may return `{}` or minimal objects.
if (!("type" in schemaRecord) && !("properties" in schemaRecord)) {
return applyProviderCleaning({ type: "object", properties: {}, ...schemaRecord });
// Handle the proven MCP no-parameter case: a truly empty schema object.
// Keep other non-empty shapes unchanged so we do not silently bless
// unsupported top-level conditionals like `allOf`.
if (Object.keys(schemaRecord).length === 0) {
return applyProviderCleaning({ type: "object", properties: {} });
}
return schema;
}