From 37acee9283642bbc826ea09d26565a4a01c61979 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Wed, 29 Oct 2025 14:28:49 -0400 Subject: [PATCH] Handle nullable arrays in tool inputSchemas * In DynamicJsonForm.tsx - In debouncedUpdateParent, formatJson, and validateJson, - when trimming the JSON value, use ?.trim() in case the field is null * In jsonUtils.ts - in tryParseJson - use ?.trim() in case the field is null * In schemaUtils.ts - in normalizeUnionType - add condition to handle the case where there is an anyOf with array or null as options * In ToolsTab.tsx - for nullable fields, in null checkbox, in onCheckedChange handler - when not checked, if prop type is array, set to undefined (empty input field that can be edited rather than the word "null" as the default could provide Fixes #846 --- client/src/components/DynamicJsonForm.tsx | 8 ++++---- client/src/components/ToolsTab.tsx | 22 ++++++++++++---------- client/src/utils/jsonUtils.ts | 3 ++- client/src/utils/schemaUtils.ts | 10 ++++++++++ 4 files changed, 28 insertions(+), 15 deletions(-) diff --git a/client/src/components/DynamicJsonForm.tsx b/client/src/components/DynamicJsonForm.tsx index 96b03b3a4..685401ab9 100644 --- a/client/src/components/DynamicJsonForm.tsx +++ b/client/src/components/DynamicJsonForm.tsx @@ -113,8 +113,8 @@ const DynamicJsonForm = forwardRef( setJsonError(errorMessage); // Reset to default for clearly invalid JSON (not just incomplete typing) - const trimmed = jsonString.trim(); - if (trimmed.length > 5 && !trimmed.match(/^[\s[{]/)) { + const trimmed = jsonString?.trim(); + if (trimmed && trimmed.length > 5 && !trimmed.match(/^[\s[{]/)) { onChange(generateDefaultValue(schema)); } } @@ -155,7 +155,7 @@ const DynamicJsonForm = forwardRef( const formatJson = () => { try { - const jsonStr = rawJsonValue.trim(); + const jsonStr = rawJsonValue?.trim(); if (!jsonStr) { return; } @@ -171,7 +171,7 @@ const DynamicJsonForm = forwardRef( const validateJson = () => { if (!isJsonMode) return { isValid: true, error: null }; try { - const jsonStr = rawJsonValue.trim(); + const jsonStr = rawJsonValue?.trim(); if (!jsonStr) return { isValid: true, error: null }; const parsed = JSON.parse(jsonStr); // Clear any pending debounced update and immediately update parent diff --git a/client/src/components/ToolsTab.tsx b/client/src/components/ToolsTab.tsx index 740cc5edb..6b9c1c3ef 100644 --- a/client/src/components/ToolsTab.tsx +++ b/client/src/components/ToolsTab.tsx @@ -181,16 +181,18 @@ const ToolsTab = ({ ...params, [key]: checked ? null - : prop.default !== null - ? prop.default - : prop.type === "boolean" - ? false - : prop.type === "string" - ? "" - : prop.type === "number" || - prop.type === "integer" - ? undefined - : undefined, + : prop.type === "array" + ? undefined + : prop.default !== null + ? prop.default + : prop.type === "boolean" + ? false + : prop.type === "string" + ? "" + : prop.type === "number" || + prop.type === "integer" + ? undefined + : undefined, }) } /> diff --git a/client/src/utils/jsonUtils.ts b/client/src/utils/jsonUtils.ts index 3a9336f15..c2f9af526 100644 --- a/client/src/utils/jsonUtils.ts +++ b/client/src/utils/jsonUtils.ts @@ -84,8 +84,9 @@ export function tryParseJson(str: string): { success: boolean; data: JsonValue; } { - const trimmed = str.trim(); + const trimmed = str?.trim(); if ( + trimmed && !(trimmed.startsWith("{") && trimmed.endsWith("}")) && !(trimmed.startsWith("[") && trimmed.endsWith("]")) ) { diff --git a/client/src/utils/schemaUtils.ts b/client/src/utils/schemaUtils.ts index 731378050..27f13eaf1 100644 --- a/client/src/utils/schemaUtils.ts +++ b/client/src/utils/schemaUtils.ts @@ -191,6 +191,16 @@ export function normalizeUnionType(schema: JsonSchemaType): JsonSchemaType { return { ...schema, type: "integer", anyOf: undefined, nullable: true }; } + // Handle anyOf with exactly array and null (FastMCP pattern) + if ( + schema.anyOf && + schema.anyOf.length === 2 && + schema.anyOf.some((t) => (t as JsonSchemaType).type === "array") && + schema.anyOf.some((t) => (t as JsonSchemaType).type === "null") + ) { + return { ...schema, type: "array", anyOf: undefined, nullable: true }; + } + // Handle array type with exactly string and null if ( Array.isArray(schema.type) &&