From dd0985cb11d3a1449d2658efa4e8cb17cbd4abeb Mon Sep 17 00:00:00 2001 From: Edison-A-N Date: Sun, 2 Nov 2025 11:39:35 +0800 Subject: [PATCH 1/4] fix: enhance resolution and enum handling in anyOf schemas --- client/src/utils/schemaUtils.ts | 92 +++++++++++++++++---------------- 1 file changed, 48 insertions(+), 44 deletions(-) diff --git a/client/src/utils/schemaUtils.ts b/client/src/utils/schemaUtils.ts index 36587021..d1ed1e96 100644 --- a/client/src/utils/schemaUtils.ts +++ b/client/src/utils/schemaUtils.ts @@ -149,20 +149,46 @@ export function isPropertyRequired( * Resolves $ref references in JSON schema * @param schema The schema that may contain $ref * @param rootSchema The root schema to resolve references against + * @param visitedRefs Optional set of visited $ref paths to detect circular references * @returns The resolved schema without $ref */ export function resolveRef( schema: JsonSchemaType, rootSchema: JsonSchemaType, + visitedRefs: Set = new Set(), ): JsonSchemaType { + if (!schema) return schema; + if (!("$ref" in schema) || !schema.$ref) { + // Recursively resolve $ref in anyOf (and other nested structures) + if (schema.anyOf && Array.isArray(schema.anyOf)) { + const resolvedAnyOf = schema.anyOf.map((item) => { + if (typeof item === "object" && item !== null) { + return resolveRef(item, rootSchema, visitedRefs); + } + return item; + }); + return { + ...schema, + anyOf: resolvedAnyOf, + }; + } return schema; } const ref = schema.$ref; - // Handle simple #/properties/name references + // Handle all #/ formats (#/properties/, #/$defs/, etc.) if (ref.startsWith("#/")) { + // Check for circular reference + if (visitedRefs.has(ref)) { + console.warn(`Circular reference detected: ${ref}`); + return schema; + } + + // Add current ref to visited set + visitedRefs.add(ref); + const path = ref.substring(2).split("/"); let current: unknown = rootSchema; @@ -176,12 +202,16 @@ export function resolveRef( current = (current as Record)[segment]; } else { // If reference cannot be resolved, return the original schema + visitedRefs.delete(ref); // Clean up on failure console.warn(`Could not resolve $ref: ${ref}`); return schema; } } - return current as JsonSchemaType; + const resolved = current as JsonSchemaType; + + // Recursively resolve nested structures (anyOf, oneOf, items, properties) + return resolveRef(resolved, rootSchema, visitedRefs); } // For other types of references, return the original schema @@ -195,54 +225,28 @@ export function resolveRef( * @returns A normalized schema or the original schema */ export function normalizeUnionType(schema: JsonSchemaType): JsonSchemaType { - // Handle anyOf with exactly string and null (FastMCP pattern) + // Handle anyOf with exactly 2 items (type and null) - unified handling + // Preserves enum and other properties automatically if ( schema.anyOf && schema.anyOf.length === 2 && - schema.anyOf.some((t) => (t as JsonSchemaType).type === "string") && schema.anyOf.some((t) => (t as JsonSchemaType).type === "null") ) { - return { ...schema, type: "string", anyOf: undefined, nullable: true }; - } + const nonNullItem = schema.anyOf.find((t) => { + const item = t as JsonSchemaType; + return item?.type !== "null"; + }) as JsonSchemaType; - // Handle anyOf with exactly boolean and null (FastMCP pattern) - if ( - schema.anyOf && - schema.anyOf.length === 2 && - schema.anyOf.some((t) => (t as JsonSchemaType).type === "boolean") && - schema.anyOf.some((t) => (t as JsonSchemaType).type === "null") - ) { - return { ...schema, type: "boolean", anyOf: undefined, nullable: true }; - } - - // Handle anyOf with exactly number and null (FastMCP pattern) - if ( - schema.anyOf && - schema.anyOf.length === 2 && - schema.anyOf.some((t) => (t as JsonSchemaType).type === "number") && - schema.anyOf.some((t) => (t as JsonSchemaType).type === "null") - ) { - return { ...schema, type: "number", anyOf: undefined, nullable: true }; - } - - // Handle anyOf with exactly integer and null (FastMCP pattern) - if ( - schema.anyOf && - schema.anyOf.length === 2 && - schema.anyOf.some((t) => (t as JsonSchemaType).type === "integer") && - schema.anyOf.some((t) => (t as JsonSchemaType).type === "null") - ) { - 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 }; + // Only process if non-null item has type or enum + if (nonNullItem?.type || nonNullItem?.enum) { + return { + ...schema, + ...nonNullItem, + type: nonNullItem?.type || (nonNullItem?.enum ? "string" : undefined), + nullable: true, + anyOf: undefined, + }; + } } // Handle array type with exactly string and null From 39c2c9f8ce41957018e391cace9b794bec05071f Mon Sep 17 00:00:00 2001 From: David Soria Parra Date: Mon, 9 Feb 2026 18:27:16 +0000 Subject: [PATCH 2/4] Remove environment constraint from release workflow jobs Co-Authored-By: Claude Opus 4.6 --- .github/workflows/main.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 96262fda..60262085 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -42,7 +42,6 @@ jobs: publish: runs-on: ubuntu-latest if: github.event_name == 'release' - environment: release needs: build permissions: @@ -69,7 +68,6 @@ jobs: publish-github-container-registry: runs-on: ubuntu-latest if: github.event_name == 'release' - environment: release needs: build permissions: contents: read From 1f424f252546b8611e275a158d1756fd30517d87 Mon Sep 17 00:00:00 2001 From: David Soria Parra Date: Mon, 9 Feb 2026 18:30:00 +0000 Subject: [PATCH 3/4] Revert "Remove environment constraint from release workflow jobs" This reverts commit 39c2c9f8ce41957018e391cace9b794bec05071f. --- .github/workflows/main.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 60262085..96262fda 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -42,6 +42,7 @@ jobs: publish: runs-on: ubuntu-latest if: github.event_name == 'release' + environment: release needs: build permissions: @@ -68,6 +69,7 @@ jobs: publish-github-container-registry: runs-on: ubuntu-latest if: github.event_name == 'release' + environment: release needs: build permissions: contents: read From 1f9d922475b8cd0598bea0d3306f24935cda9cbf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Feb 2026 23:27:21 +0000 Subject: [PATCH 4/4] Bump actions/checkout from 4 to 6 Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 6. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v6) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/claude.yml | 4 ++-- .github/workflows/cli_tests.yml | 2 +- .github/workflows/e2e_tests.yml | 2 +- .github/workflows/main.yml | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index 52ca5ce4..9fdebc23 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -54,7 +54,7 @@ jobs: - name: Checkout PR branch if: steps.pr.outcome == 'success' - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: ref: ${{ steps.pr.outputs.sha }} repository: ${{ steps.pr.outputs.repo }} @@ -62,7 +62,7 @@ jobs: - name: Checkout repository if: steps.pr.outcome != 'success' - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 0 diff --git a/.github/workflows/cli_tests.yml b/.github/workflows/cli_tests.yml index 3b46b431..106d98f1 100644 --- a/.github/workflows/cli_tests.yml +++ b/.github/workflows/cli_tests.yml @@ -15,7 +15,7 @@ jobs: run: working-directory: ./cli steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up Node.js uses: actions/setup-node@v6 diff --git a/.github/workflows/e2e_tests.yml b/.github/workflows/e2e_tests.yml index 4d0c4cec..4013a905 100644 --- a/.github/workflows/e2e_tests.yml +++ b/.github/workflows/e2e_tests.yml @@ -18,7 +18,7 @@ jobs: sudo apt-get update sudo apt-get install -y libwoff1 - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: actions/setup-node@v6 id: setup_node diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 96262fda..2cc4537b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Check formatting run: npx prettier --check . @@ -50,7 +50,7 @@ jobs: id-token: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: actions/setup-node@v6 with: node-version-file: package.json @@ -77,7 +77,7 @@ jobs: attestations: write id-token: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Log in to the Container registry uses: docker/login-action@v3