Skip to content

Commit 6d18cf3

Browse files
authored
Merge branch 'main' into hide-run-as-task-if-not-supported
2 parents 96b1b58 + 8cf0ac1 commit 6d18cf3

6 files changed

Lines changed: 68 additions & 62 deletions

File tree

.github/workflows/claude.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ jobs:
3333
github.event_name == 'pull_request_review_comment' ||
3434
github.event_name == 'pull_request_review'
3535
id: pr
36-
uses: actions/github-script@v7
36+
uses: actions/github-script@v8
3737
with:
3838
script: |
3939
let prNumber;
@@ -54,15 +54,15 @@ jobs:
5454
5555
- name: Checkout PR branch
5656
if: steps.pr.outcome == 'success'
57-
uses: actions/checkout@v4
57+
uses: actions/checkout@v6
5858
with:
5959
ref: ${{ steps.pr.outputs.sha }}
6060
repository: ${{ steps.pr.outputs.repo }}
6161
fetch-depth: 0
6262

6363
- name: Checkout repository
6464
if: steps.pr.outcome != 'success'
65-
uses: actions/checkout@v4
65+
uses: actions/checkout@v6
6666
with:
6767
fetch-depth: 0
6868

.github/workflows/cli_tests.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@ jobs:
1515
run:
1616
working-directory: ./cli
1717
steps:
18-
- uses: actions/checkout@v4
18+
- uses: actions/checkout@v6
1919

2020
- name: Set up Node.js
21-
uses: actions/setup-node@v4
21+
uses: actions/setup-node@v6
2222
with:
2323
node-version-file: package.json
2424
cache: npm

.github/workflows/e2e_tests.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ jobs:
1818
sudo apt-get update
1919
sudo apt-get install -y libwoff1
2020
21-
- uses: actions/checkout@v4
21+
- uses: actions/checkout@v6
2222

23-
- uses: actions/setup-node@v4
23+
- uses: actions/setup-node@v6
2424
id: setup_node
2525
with:
2626
node-version-file: package.json
@@ -29,7 +29,7 @@ jobs:
2929
# Cache Playwright browsers
3030
- name: Cache Playwright browsers
3131
id: cache-playwright
32-
uses: actions/cache@v4
32+
uses: actions/cache@v5
3333
with:
3434
path: ~/.cache/ms-playwright # The default Playwright cache path
3535
key: ${{ runner.os }}-playwright-${{ hashFiles('package-lock.json') }} # Cache key based on OS and package-lock.json
@@ -51,7 +51,7 @@ jobs:
5151
run: npm run test:e2e
5252

5353
- name: Upload Playwright Report and Screenshots
54-
uses: actions/upload-artifact@v4
54+
uses: actions/upload-artifact@v6
5555
if: steps.playwright-tests.conclusion != 'skipped'
5656
with:
5757
name: playwright-report

.github/workflows/main.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@ jobs:
1212
runs-on: ubuntu-latest
1313

1414
steps:
15-
- uses: actions/checkout@v4
15+
- uses: actions/checkout@v6
1616

1717
- name: Check formatting
1818
run: npx prettier --check .
1919

20-
- uses: actions/setup-node@v4
20+
- uses: actions/setup-node@v6
2121
with:
2222
node-version-file: package.json
2323
cache: npm
@@ -50,8 +50,8 @@ jobs:
5050
id-token: write
5151

5252
steps:
53-
- uses: actions/checkout@v4
54-
- uses: actions/setup-node@v4
53+
- uses: actions/checkout@v6
54+
- uses: actions/setup-node@v6
5555
with:
5656
node-version-file: package.json
5757
cache: npm
@@ -77,7 +77,7 @@ jobs:
7777
attestations: write
7878
id-token: write
7979
steps:
80-
- uses: actions/checkout@v4
80+
- uses: actions/checkout@v6
8181

8282
- name: Log in to the Container registry
8383
uses: docker/login-action@v3
@@ -109,7 +109,7 @@ jobs:
109109
labels: ${{ steps.meta.outputs.labels }}
110110

111111
- name: Generate artifact attestation
112-
uses: actions/attest-build-provenance@v2
112+
uses: actions/attest-build-provenance@v3
113113
with:
114114
subject-name: ghcr.io/${{ github.repository }}
115115
subject-digest: ${{ steps.push.outputs.digest }}

client/src/components/ToolsTab.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ const ToolsTab = ({
203203
<IconDisplay icons={(tool as ExtendedTool).icons} size="sm" />
204204
</div>
205205
<div className="flex flex-col flex-1 min-w-0">
206-
<span className="truncate">{tool.name}</span>
206+
<span className="truncate">{tool.title || tool.name}</span>
207207
<span className="text-sm text-gray-500 text-left line-clamp-2">
208208
{tool.description}
209209
</span>
@@ -226,7 +226,9 @@ const ToolsTab = ({
226226
/>
227227
)}
228228
<h3 className="font-semibold">
229-
{selectedTool ? selectedTool.name : "Select a tool"}
229+
{selectedTool
230+
? selectedTool.title || selectedTool.name
231+
: "Select a tool"}
230232
</h3>
231233
</div>
232234
</div>

client/src/utils/schemaUtils.ts

Lines changed: 49 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -159,20 +159,46 @@ export function isPropertyRequired(
159159
* Resolves $ref references in JSON schema
160160
* @param schema The schema that may contain $ref
161161
* @param rootSchema The root schema to resolve references against
162+
* @param visitedRefs Optional set of visited $ref paths to detect circular references
162163
* @returns The resolved schema without $ref
163164
*/
164165
export function resolveRef(
165166
schema: JsonSchemaType,
166167
rootSchema: JsonSchemaType,
168+
visitedRefs: Set<string> = new Set(),
167169
): JsonSchemaType {
170+
if (!schema) return schema;
171+
168172
if (!("$ref" in schema) || !schema.$ref) {
173+
// Recursively resolve $ref in anyOf (and other nested structures)
174+
if (schema.anyOf && Array.isArray(schema.anyOf)) {
175+
const resolvedAnyOf = schema.anyOf.map((item) => {
176+
if (typeof item === "object" && item !== null) {
177+
return resolveRef(item, rootSchema, visitedRefs);
178+
}
179+
return item;
180+
});
181+
return {
182+
...schema,
183+
anyOf: resolvedAnyOf,
184+
};
185+
}
169186
return schema;
170187
}
171188

172189
const ref = schema.$ref;
173190

174-
// Handle simple #/properties/name references
191+
// Handle all #/ formats (#/properties/, #/$defs/, etc.)
175192
if (ref.startsWith("#/")) {
193+
// Check for circular reference
194+
if (visitedRefs.has(ref)) {
195+
console.warn(`Circular reference detected: ${ref}`);
196+
return schema;
197+
}
198+
199+
// Add current ref to visited set
200+
visitedRefs.add(ref);
201+
176202
const path = ref.substring(2).split("/");
177203
let current: unknown = rootSchema;
178204

@@ -186,12 +212,16 @@ export function resolveRef(
186212
current = (current as Record<string, unknown>)[segment];
187213
} else {
188214
// If reference cannot be resolved, return the original schema
215+
visitedRefs.delete(ref); // Clean up on failure
189216
console.warn(`Could not resolve $ref: ${ref}`);
190217
return schema;
191218
}
192219
}
193220

194-
return current as JsonSchemaType;
221+
const resolved = current as JsonSchemaType;
222+
223+
// Recursively resolve nested structures (anyOf, oneOf, items, properties)
224+
return resolveRef(resolved, rootSchema, visitedRefs);
195225
}
196226

197227
// For other types of references, return the original schema
@@ -205,54 +235,28 @@ export function resolveRef(
205235
* @returns A normalized schema or the original schema
206236
*/
207237
export function normalizeUnionType(schema: JsonSchemaType): JsonSchemaType {
208-
// Handle anyOf with exactly string and null (FastMCP pattern)
238+
// Handle anyOf with exactly 2 items (type and null) - unified handling
239+
// Preserves enum and other properties automatically
209240
if (
210241
schema.anyOf &&
211242
schema.anyOf.length === 2 &&
212-
schema.anyOf.some((t) => (t as JsonSchemaType).type === "string") &&
213243
schema.anyOf.some((t) => (t as JsonSchemaType).type === "null")
214244
) {
215-
return { ...schema, type: "string", anyOf: undefined, nullable: true };
216-
}
217-
218-
// Handle anyOf with exactly boolean and null (FastMCP pattern)
219-
if (
220-
schema.anyOf &&
221-
schema.anyOf.length === 2 &&
222-
schema.anyOf.some((t) => (t as JsonSchemaType).type === "boolean") &&
223-
schema.anyOf.some((t) => (t as JsonSchemaType).type === "null")
224-
) {
225-
return { ...schema, type: "boolean", anyOf: undefined, nullable: true };
226-
}
227-
228-
// Handle anyOf with exactly number and null (FastMCP pattern)
229-
if (
230-
schema.anyOf &&
231-
schema.anyOf.length === 2 &&
232-
schema.anyOf.some((t) => (t as JsonSchemaType).type === "number") &&
233-
schema.anyOf.some((t) => (t as JsonSchemaType).type === "null")
234-
) {
235-
return { ...schema, type: "number", anyOf: undefined, nullable: true };
236-
}
237-
238-
// Handle anyOf with exactly integer and null (FastMCP pattern)
239-
if (
240-
schema.anyOf &&
241-
schema.anyOf.length === 2 &&
242-
schema.anyOf.some((t) => (t as JsonSchemaType).type === "integer") &&
243-
schema.anyOf.some((t) => (t as JsonSchemaType).type === "null")
244-
) {
245-
return { ...schema, type: "integer", anyOf: undefined, nullable: true };
246-
}
247-
248-
// Handle anyOf with exactly array and null (FastMCP pattern)
249-
if (
250-
schema.anyOf &&
251-
schema.anyOf.length === 2 &&
252-
schema.anyOf.some((t) => (t as JsonSchemaType).type === "array") &&
253-
schema.anyOf.some((t) => (t as JsonSchemaType).type === "null")
254-
) {
255-
return { ...schema, type: "array", anyOf: undefined, nullable: true };
245+
const nonNullItem = schema.anyOf.find((t) => {
246+
const item = t as JsonSchemaType;
247+
return item?.type !== "null";
248+
}) as JsonSchemaType;
249+
250+
// Only process if non-null item has type or enum
251+
if (nonNullItem?.type || nonNullItem?.enum) {
252+
return {
253+
...schema,
254+
...nonNullItem,
255+
type: nonNullItem?.type || (nonNullItem?.enum ? "string" : undefined),
256+
nullable: true,
257+
anyOf: undefined,
258+
};
259+
}
256260
}
257261

258262
// Handle array type with exactly string and null

0 commit comments

Comments
 (0)