Merge pull request #895 from cliffhall/fix-default-null-array-input

Handle nullable arrays in tool inputSchemas
This commit is contained in:
Ola Hungerford 2025-10-30 21:25:16 -07:00 • committed by GitHub
commit e87623ce01
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 28 additions and 15 deletions

View file

@ -113,8 +113,8 @@ const DynamicJsonForm = forwardRef<DynamicJsonFormRef, DynamicJsonFormProps>(
setJsonError(errorMessage); setJsonError(errorMessage);
// Reset to default for clearly invalid JSON (not just incomplete typing) // Reset to default for clearly invalid JSON (not just incomplete typing)
const trimmed = jsonString.trim(); const trimmed = jsonString?.trim();
if (trimmed.length > 5 && !trimmed.match(/^[\s[{]/)) { if (trimmed && trimmed.length > 5 && !trimmed.match(/^[\s[{]/)) {
onChange(generateDefaultValue(schema)); onChange(generateDefaultValue(schema));
} }
} }
@ -155,7 +155,7 @@ const DynamicJsonForm = forwardRef<DynamicJsonFormRef, DynamicJsonFormProps>(
const formatJson = () => { const formatJson = () => {
try { try {
const jsonStr = rawJsonValue.trim(); const jsonStr = rawJsonValue?.trim();
if (!jsonStr) { if (!jsonStr) {
return; return;
} }
@ -171,7 +171,7 @@ const DynamicJsonForm = forwardRef<DynamicJsonFormRef, DynamicJsonFormProps>(
const validateJson = () => { const validateJson = () => {
if (!isJsonMode) return { isValid: true, error: null }; if (!isJsonMode) return { isValid: true, error: null };
try { try {
const jsonStr = rawJsonValue.trim(); const jsonStr = rawJsonValue?.trim();
if (!jsonStr) return { isValid: true, error: null }; if (!jsonStr) return { isValid: true, error: null };
const parsed = JSON.parse(jsonStr); const parsed = JSON.parse(jsonStr);
// Clear any pending debounced update and immediately update parent // Clear any pending debounced update and immediately update parent

View file

@ -194,16 +194,18 @@ const ToolsTab = ({
...params, ...params,
[key]: checked [key]: checked
? null ? null
: prop.default !== null : prop.type === "array"
? prop.default ? undefined
: prop.type === "boolean" : prop.default !== null
? false ? prop.default
: prop.type === "string" : prop.type === "boolean"
? "" ? false
: prop.type === "number" || : prop.type === "string"
prop.type === "integer" ? ""
? undefined : prop.type === "number" ||
: undefined, prop.type === "integer"
? undefined
: undefined,
}) })
} }
/> />

View file

@ -85,8 +85,9 @@ export function tryParseJson(str: string): {
success: boolean; success: boolean;
data: JsonValue; data: JsonValue;
} { } {
const trimmed = str.trim(); const trimmed = str?.trim();
if ( if (
trimmed &&
!(trimmed.startsWith("{") && trimmed.endsWith("}")) && !(trimmed.startsWith("{") && trimmed.endsWith("}")) &&
!(trimmed.startsWith("[") && trimmed.endsWith("]")) !(trimmed.startsWith("[") && trimmed.endsWith("]"))
) { ) {

View file

@ -235,6 +235,16 @@ export function normalizeUnionType(schema: JsonSchemaType): JsonSchemaType {
return { ...schema, type: "integer", anyOf: undefined, nullable: true }; 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 // Handle array type with exactly string and null
if ( if (
Array.isArray(schema.type) && Array.isArray(schema.type) &&