From fb7fae1436ac25328a2c8d4bf248735dcee7b2f9 Mon Sep 17 00:00:00 2001
From: yanOO1497
Date: Sat, 25 Oct 2025 17:02:25 +0800
Subject: [PATCH 1/2] fix: resolve $ref references in tool input schema
properties
---
client/src/components/ToolsTab.tsx | 27 +++++++++++++-----
client/src/utils/jsonUtils.ts | 1 +
client/src/utils/schemaUtils.ts | 44 ++++++++++++++++++++++++++++++
3 files changed, 65 insertions(+), 7 deletions(-)
diff --git a/client/src/components/ToolsTab.tsx b/client/src/components/ToolsTab.tsx
index 740cc5ed..b8c163a2 100644
--- a/client/src/components/ToolsTab.tsx
+++ b/client/src/components/ToolsTab.tsx
@@ -18,6 +18,7 @@ import {
generateDefaultValue,
isPropertyRequired,
normalizeUnionType,
+ resolveRef,
} from "@/utils/schemaUtils";
import {
CompatibilityCallToolResult,
@@ -90,14 +91,21 @@ const ToolsTab = ({
useEffect(() => {
const params = Object.entries(
selectedTool?.inputSchema.properties ?? [],
- ).map(([key, value]) => [
- key,
- generateDefaultValue(
+ ).map(([key, value]) => {
+ // First resolve any $ref references
+ const resolvedValue = resolveRef(
value as JsonSchemaType,
- key,
selectedTool?.inputSchema as JsonSchemaType,
- ),
- ]);
+ );
+ return [
+ key,
+ generateDefaultValue(
+ resolvedValue,
+ key,
+ selectedTool?.inputSchema as JsonSchemaType,
+ ),
+ ];
+ });
setParams(Object.fromEntries(params));
// Reset validation errors when switching tools
@@ -154,7 +162,12 @@ const ToolsTab = ({
{Object.entries(selectedTool.inputSchema.properties ?? []).map(
([key, value]) => {
- const prop = normalizeUnionType(value as JsonSchemaType);
+ // First resolve any $ref references
+ const resolvedValue = resolveRef(
+ value as JsonSchemaType,
+ selectedTool.inputSchema as JsonSchemaType,
+ );
+ const prop = normalizeUnionType(resolvedValue);
const inputSchema =
selectedTool.inputSchema as JsonSchemaType;
const required = isPropertyRequired(key, inputSchema);
diff --git a/client/src/utils/jsonUtils.ts b/client/src/utils/jsonUtils.ts
index 3a9336f1..f8378966 100644
--- a/client/src/utils/jsonUtils.ts
+++ b/client/src/utils/jsonUtils.ts
@@ -48,6 +48,7 @@ export type JsonSchemaType = {
const?: JsonValue;
oneOf?: (JsonSchemaType | JsonSchemaConst)[];
anyOf?: (JsonSchemaType | JsonSchemaConst)[];
+ $ref?: string;
};
export type JsonObject = { [key: string]: JsonValue };
diff --git a/client/src/utils/schemaUtils.ts b/client/src/utils/schemaUtils.ts
index 73137805..5beb25e2 100644
--- a/client/src/utils/schemaUtils.ts
+++ b/client/src/utils/schemaUtils.ts
@@ -145,6 +145,50 @@ export function isPropertyRequired(
return schema.required?.includes(propertyName) ?? false;
}
+/**
+ * Resolves $ref references in JSON schema
+ * @param schema The schema that may contain $ref
+ * @param rootSchema The root schema to resolve references against
+ * @returns The resolved schema without $ref
+ */
+export function resolveRef(
+ schema: JsonSchemaType,
+ rootSchema: JsonSchemaType,
+): JsonSchemaType {
+ if (!("$ref" in schema) || !schema.$ref) {
+ return schema;
+ }
+
+ const ref = schema.$ref;
+
+ // Handle simple #/properties/name references
+ if (ref.startsWith("#/")) {
+ const path = ref.substring(2).split("/");
+ let current: unknown = rootSchema;
+
+ for (const segment of path) {
+ if (
+ current &&
+ typeof current === "object" &&
+ current !== null &&
+ segment in current
+ ) {
+ current = (current as Record)[segment];
+ } else {
+ // If reference cannot be resolved, return the original schema
+ console.warn(`Could not resolve $ref: ${ref}`);
+ return schema;
+ }
+ }
+
+ return current as JsonSchemaType;
+ }
+
+ // For other types of references, return the original schema
+ console.warn(`Unsupported $ref format: ${ref}`);
+ return schema;
+}
+
/**
* Normalizes union types (like string|null from FastMCP) to simple types for form rendering
* @param schema The JSON schema to normalize
From 37acee9283642bbc826ea09d26565a4a01c61979 Mon Sep 17 00:00:00 2001
From: cliffhall
Date: Wed, 29 Oct 2025 14:28:49 -0400
Subject: [PATCH 2/2] 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 96b03b3a..685401ab 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 740cc5ed..6b9c1c3e 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 3a9336f1..c2f9af52 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 73137805..27f13eaf 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) &&