Merge branch 'main' into fix/set-scope-dcr

This commit is contained in:
Cliff Hall 2025-11-04 09:35:15 -05:00 • committed by GitHub
commit 63eb231650
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 93 additions and 22 deletions

View file

@ -113,8 +113,8 @@ const DynamicJsonForm = forwardRef<DynamicJsonFormRef, DynamicJsonFormProps>(
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<DynamicJsonFormRef, DynamicJsonFormProps>(
const formatJson = () => {
try {
const jsonStr = rawJsonValue.trim();
const jsonStr = rawJsonValue?.trim();
if (!jsonStr) {
return;
}
@ -171,7 +171,7 @@ const DynamicJsonForm = forwardRef<DynamicJsonFormRef, DynamicJsonFormProps>(
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

View file

@ -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 = ({
</p>
{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);
@ -181,16 +194,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,
})
}
/>

View file

@ -48,6 +48,7 @@ export type JsonSchemaType = {
const?: JsonValue;
oneOf?: (JsonSchemaType | JsonSchemaConst)[];
anyOf?: (JsonSchemaType | JsonSchemaConst)[];
$ref?: string;
};
export type JsonObject = { [key: string]: JsonValue };
@ -84,8 +85,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("]"))
) {

View file

@ -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<string, unknown>)[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
@ -191,6 +235,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) &&