Skip to content

Commit 3372452

Browse files
committed
Fix optional array inputs in UI
Signed-off-by: Madhav Kandukuri <[email protected]>
1 parent fc6995b commit 3372452

File tree

1 file changed

+96
-18
lines changed

1 file changed

+96
-18
lines changed

mcpgateway/static/admin.js

Lines changed: 96 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4295,6 +4295,82 @@ let toolInputSchemaRegistry = null;
42954295
/**
42964296
* ENHANCED: Tool testing with improved race condition handling
42974297
*/
4298+
4299+
function detectArrayLike(p) {
4300+
// Returns { isArrayLike, itemsSchema, isNullable }
4301+
const out = {
4302+
isArrayLike: false,
4303+
itemsSchema: undefined,
4304+
isNullable: false,
4305+
};
4306+
if (!p || typeof p !== "object") {
4307+
return out;
4308+
}
4309+
4310+
const t = Array.isArray(p.type) ? p.type : p.type ? [p.type] : [];
4311+
if (t.includes("null")) {
4312+
out.isNullable = true;
4313+
}
4314+
if (p.nullable === true) {
4315+
out.isNullable = true;
4316+
}
4317+
if (Array.isArray(p.enum) && p.enum.includes(null)) {
4318+
out.isNullable = true;
4319+
}
4320+
if (p.const === null) {
4321+
out.isNullable = true;
4322+
}
4323+
4324+
// A) explicit array on prop
4325+
if (t.includes("array")) {
4326+
out.isArrayLike = true;
4327+
out.itemsSchema = p.items;
4328+
return out;
4329+
}
4330+
// B) items present without explicit type
4331+
if (p.items) {
4332+
out.isArrayLike = true;
4333+
out.itemsSchema = p.items;
4334+
return out;
4335+
}
4336+
4337+
// C) union variants (anyOf/oneOf) detect array + null
4338+
const unions = []
4339+
.concat(Array.isArray(p.anyOf) ? p.anyOf : [])
4340+
.concat(Array.isArray(p.oneOf) ? p.oneOf : []);
4341+
4342+
for (const v of unions) {
4343+
if (!v) {
4344+
continue;
4345+
}
4346+
const vt = Array.isArray(v.type) ? v.type : v.type ? [v.type] : [];
4347+
if (vt.includes("null")) {
4348+
out.isNullable = true;
4349+
}
4350+
if (
4351+
v.nullable === true ||
4352+
(Array.isArray(v.enum) && v.enum.includes(null)) ||
4353+
v.const === null
4354+
) {
4355+
out.isNullable = true;
4356+
}
4357+
}
4358+
4359+
for (const v of unions) {
4360+
if (!v) {
4361+
continue;
4362+
}
4363+
const vt = Array.isArray(v.type) ? v.type : v.type ? [v.type] : [];
4364+
if (vt.includes("array") || v.items) {
4365+
out.isArrayLike = true;
4366+
out.itemsSchema = v.items;
4367+
return out;
4368+
}
4369+
}
4370+
4371+
return out;
4372+
}
4373+
42984374
async function testTool(toolId) {
42994375
try {
43004376
console.log(`Testing tool ID: ${toolId}`);
@@ -4476,20 +4552,24 @@ async function testTool(toolId) {
44764552
fieldDiv.appendChild(description);
44774553
}
44784554

4479-
if (prop.type === "array") {
4555+
const { isArrayLike, itemsSchema, isNullable } =
4556+
detectArrayLike(prop);
4557+
4558+
if (isArrayLike) {
44804559
const arrayContainer = document.createElement("div");
44814560
arrayContainer.className = "space-y-2";
44824561

44834562
function createArrayInput(value = "") {
44844563
const wrapper = document.createElement("div");
44854564
wrapper.className = "flex items-center space-x-2";
44864565

4487-
const itemTypes = Array.isArray(prop.items?.anyOf)
4488-
? prop.items.anyOf.map((t) => t.type)
4489-
: [prop.items?.type];
4566+
// Use itemsSchema (NOT prop.items)
4567+
const items = itemsSchema || {};
4568+
const itemTypes = Array.isArray(items.anyOf)
4569+
? items.anyOf.map((t) => t.type).filter(Boolean)
4570+
: [items.type].filter(Boolean);
44904571

44914572
let input;
4492-
44934573
if (
44944574
itemTypes.includes("number") ||
44954575
itemTypes.includes("integer")
@@ -4548,7 +4628,6 @@ async function testTool(toolId) {
45484628
arrayContainer.removeChild(wrapper);
45494629
});
45504630

4551-
// only append if not boolean (boolean branch already appended input above)
45524631
if (!itemTypes.includes("boolean")) {
45534632
wrapper.appendChild(input);
45544633
}
@@ -4566,20 +4645,19 @@ async function testTool(toolId) {
45664645
arrayContainer.appendChild(createArrayInput());
45674646
});
45684647

4569-
if (Array.isArray(prop.default)) {
4570-
if (prop.default.length > 0) {
4571-
prop.default.forEach((val) => {
4572-
arrayContainer.appendChild(
4573-
createArrayInput(val),
4574-
);
4575-
});
4576-
} else {
4577-
// Create one empty input for empty default arrays
4578-
arrayContainer.appendChild(createArrayInput());
4579-
}
4580-
} else {
4648+
// Seed rows:
4649+
if (
4650+
Array.isArray(prop.default) &&
4651+
prop.default.length > 0
4652+
) {
4653+
prop.default.forEach((val) =>
4654+
arrayContainer.appendChild(createArrayInput(val)),
4655+
);
4656+
} else if (!isNullable) {
4657+
// Non-nullable: start with one empty row
45814658
arrayContainer.appendChild(createArrayInput());
45824659
}
4660+
// Nullable + default null/undefined → start with zero rows (button still shown)
45834661

45844662
fieldDiv.appendChild(arrayContainer);
45854663
fieldDiv.appendChild(addBtn);

0 commit comments

Comments
 (0)