Skip to content

Commit 63eb231

Browse files
authored
Merge branch 'main' into fix/set-scope-dcr
2 parents 9912093 + e87623c commit 63eb231

File tree

4 files changed

+93
-22
lines changed

4 files changed

+93
-22
lines changed

client/src/components/DynamicJsonForm.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,8 @@ const DynamicJsonForm = forwardRef<DynamicJsonFormRef, DynamicJsonFormProps>(
113113
setJsonError(errorMessage);
114114

115115
// Reset to default for clearly invalid JSON (not just incomplete typing)
116-
const trimmed = jsonString.trim();
117-
if (trimmed.length > 5 && !trimmed.match(/^[\s[{]/)) {
116+
const trimmed = jsonString?.trim();
117+
if (trimmed && trimmed.length > 5 && !trimmed.match(/^[\s[{]/)) {
118118
onChange(generateDefaultValue(schema));
119119
}
120120
}
@@ -155,7 +155,7 @@ const DynamicJsonForm = forwardRef<DynamicJsonFormRef, DynamicJsonFormProps>(
155155

156156
const formatJson = () => {
157157
try {
158-
const jsonStr = rawJsonValue.trim();
158+
const jsonStr = rawJsonValue?.trim();
159159
if (!jsonStr) {
160160
return;
161161
}
@@ -171,7 +171,7 @@ const DynamicJsonForm = forwardRef<DynamicJsonFormRef, DynamicJsonFormProps>(
171171
const validateJson = () => {
172172
if (!isJsonMode) return { isValid: true, error: null };
173173
try {
174-
const jsonStr = rawJsonValue.trim();
174+
const jsonStr = rawJsonValue?.trim();
175175
if (!jsonStr) return { isValid: true, error: null };
176176
const parsed = JSON.parse(jsonStr);
177177
// Clear any pending debounced update and immediately update parent

client/src/components/ToolsTab.tsx

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
generateDefaultValue,
1919
isPropertyRequired,
2020
normalizeUnionType,
21+
resolveRef,
2122
} from "@/utils/schemaUtils";
2223
import {
2324
CompatibilityCallToolResult,
@@ -90,14 +91,21 @@ const ToolsTab = ({
9091
useEffect(() => {
9192
const params = Object.entries(
9293
selectedTool?.inputSchema.properties ?? [],
93-
).map(([key, value]) => [
94-
key,
95-
generateDefaultValue(
94+
).map(([key, value]) => {
95+
// First resolve any $ref references
96+
const resolvedValue = resolveRef(
9697
value as JsonSchemaType,
97-
key,
9898
selectedTool?.inputSchema as JsonSchemaType,
99-
),
100-
]);
99+
);
100+
return [
101+
key,
102+
generateDefaultValue(
103+
resolvedValue,
104+
key,
105+
selectedTool?.inputSchema as JsonSchemaType,
106+
),
107+
];
108+
});
101109
setParams(Object.fromEntries(params));
102110

103111
// Reset validation errors when switching tools
@@ -154,7 +162,12 @@ const ToolsTab = ({
154162
</p>
155163
{Object.entries(selectedTool.inputSchema.properties ?? []).map(
156164
([key, value]) => {
157-
const prop = normalizeUnionType(value as JsonSchemaType);
165+
// First resolve any $ref references
166+
const resolvedValue = resolveRef(
167+
value as JsonSchemaType,
168+
selectedTool.inputSchema as JsonSchemaType,
169+
);
170+
const prop = normalizeUnionType(resolvedValue);
158171
const inputSchema =
159172
selectedTool.inputSchema as JsonSchemaType;
160173
const required = isPropertyRequired(key, inputSchema);
@@ -181,16 +194,18 @@ const ToolsTab = ({
181194
...params,
182195
[key]: checked
183196
? null
184-
: prop.default !== null
185-
? prop.default
186-
: prop.type === "boolean"
187-
? false
188-
: prop.type === "string"
189-
? ""
190-
: prop.type === "number" ||
191-
prop.type === "integer"
192-
? undefined
193-
: undefined,
197+
: prop.type === "array"
198+
? undefined
199+
: prop.default !== null
200+
? prop.default
201+
: prop.type === "boolean"
202+
? false
203+
: prop.type === "string"
204+
? ""
205+
: prop.type === "number" ||
206+
prop.type === "integer"
207+
? undefined
208+
: undefined,
194209
})
195210
}
196211
/>

client/src/utils/jsonUtils.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ export type JsonSchemaType = {
4848
const?: JsonValue;
4949
oneOf?: (JsonSchemaType | JsonSchemaConst)[];
5050
anyOf?: (JsonSchemaType | JsonSchemaConst)[];
51+
$ref?: string;
5152
};
5253

5354
export type JsonObject = { [key: string]: JsonValue };
@@ -84,8 +85,9 @@ export function tryParseJson(str: string): {
8485
success: boolean;
8586
data: JsonValue;
8687
} {
87-
const trimmed = str.trim();
88+
const trimmed = str?.trim();
8889
if (
90+
trimmed &&
8991
!(trimmed.startsWith("{") && trimmed.endsWith("}")) &&
9092
!(trimmed.startsWith("[") && trimmed.endsWith("]"))
9193
) {

client/src/utils/schemaUtils.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,50 @@ export function isPropertyRequired(
145145
return schema.required?.includes(propertyName) ?? false;
146146
}
147147

148+
/**
149+
* Resolves $ref references in JSON schema
150+
* @param schema The schema that may contain $ref
151+
* @param rootSchema The root schema to resolve references against
152+
* @returns The resolved schema without $ref
153+
*/
154+
export function resolveRef(
155+
schema: JsonSchemaType,
156+
rootSchema: JsonSchemaType,
157+
): JsonSchemaType {
158+
if (!("$ref" in schema) || !schema.$ref) {
159+
return schema;
160+
}
161+
162+
const ref = schema.$ref;
163+
164+
// Handle simple #/properties/name references
165+
if (ref.startsWith("#/")) {
166+
const path = ref.substring(2).split("/");
167+
let current: unknown = rootSchema;
168+
169+
for (const segment of path) {
170+
if (
171+
current &&
172+
typeof current === "object" &&
173+
current !== null &&
174+
segment in current
175+
) {
176+
current = (current as Record<string, unknown>)[segment];
177+
} else {
178+
// If reference cannot be resolved, return the original schema
179+
console.warn(`Could not resolve $ref: ${ref}`);
180+
return schema;
181+
}
182+
}
183+
184+
return current as JsonSchemaType;
185+
}
186+
187+
// For other types of references, return the original schema
188+
console.warn(`Unsupported $ref format: ${ref}`);
189+
return schema;
190+
}
191+
148192
/**
149193
* Normalizes union types (like string|null from FastMCP) to simple types for form rendering
150194
* @param schema The JSON schema to normalize
@@ -191,6 +235,16 @@ export function normalizeUnionType(schema: JsonSchemaType): JsonSchemaType {
191235
return { ...schema, type: "integer", anyOf: undefined, nullable: true };
192236
}
193237

238+
// Handle anyOf with exactly array and null (FastMCP pattern)
239+
if (
240+
schema.anyOf &&
241+
schema.anyOf.length === 2 &&
242+
schema.anyOf.some((t) => (t as JsonSchemaType).type === "array") &&
243+
schema.anyOf.some((t) => (t as JsonSchemaType).type === "null")
244+
) {
245+
return { ...schema, type: "array", anyOf: undefined, nullable: true };
246+
}
247+
194248
// Handle array type with exactly string and null
195249
if (
196250
Array.isArray(schema.type) &&

0 commit comments

Comments
 (0)