diff --git a/client/src/components/DynamicJsonForm.tsx b/client/src/components/DynamicJsonForm.tsx
index 849a2aa2..2214add9 100644
--- a/client/src/components/DynamicJsonForm.tsx
+++ b/client/src/components/DynamicJsonForm.tsx
@@ -15,12 +15,22 @@ interface DynamicJsonFormProps {
maxDepth?: number;
}
+const isTypeSupported = (
+ type: JsonSchemaType["type"],
+ supportedTypes: string[],
+): boolean => {
+ if (Array.isArray(type)) {
+ return type.every((t) => supportedTypes.includes(t));
+ }
+ return typeof type === "string" && supportedTypes.includes(type);
+};
+
const isSimpleObject = (schema: JsonSchemaType): boolean => {
const supportedTypes = ["string", "number", "integer", "boolean", "null"];
- if (schema.type && supportedTypes.includes(schema.type)) return true;
+ if (schema.type && isTypeSupported(schema.type, supportedTypes)) return true;
if (schema.type === "object") {
return Object.values(schema.properties ?? {}).every(
- (prop) => prop.type && supportedTypes.includes(prop.type),
+ (prop) => prop.type && isTypeSupported(prop.type, supportedTypes),
);
}
if (schema.type === "array") {
@@ -181,7 +191,13 @@ const DynamicJsonForm = ({
const isRequired =
parentSchema?.required?.includes(propertyName || "") ?? false;
- switch (propSchema.type) {
+ let fieldType = propSchema.type;
+ if (Array.isArray(fieldType)) {
+ // Of the possible types, find the first non-null type to determine the control to render
+ fieldType = fieldType.find((t) => t !== "null") ?? fieldType[0];
+ }
+
+ switch (fieldType) {
case "string": {
if (
propSchema.oneOf &&
@@ -337,6 +353,8 @@ const DynamicJsonForm = ({
required={isRequired}
/>
);
+ case "null":
+ return null;
case "object":
if (!propSchema.properties) {
return (
diff --git a/client/src/components/ToolsTab.tsx b/client/src/components/ToolsTab.tsx
index 2eb682e5..2654feed 100644
--- a/client/src/components/ToolsTab.tsx
+++ b/client/src/components/ToolsTab.tsx
@@ -7,7 +7,11 @@ import { TabsContent } from "@/components/ui/tabs";
import { Textarea } from "@/components/ui/textarea";
import DynamicJsonForm from "./DynamicJsonForm";
import type { JsonValue, JsonSchemaType } from "@/utils/jsonUtils";
-import { generateDefaultValue, isPropertyRequired } from "@/utils/schemaUtils";
+import {
+ generateDefaultValue,
+ isPropertyRequired,
+ normalizeUnionType,
+} from "@/utils/schemaUtils";
import {
CompatibilityCallToolResult,
ListToolsResult,
@@ -104,7 +108,7 @@ const ToolsTab = ({
{Object.entries(selectedTool.inputSchema.properties ?? []).map(
([key, value]) => {
- const prop = value as JsonSchemaType;
+ const prop = normalizeUnionType(value as JsonSchemaType);
const inputSchema =
selectedTool.inputSchema as JsonSchemaType;
const required = isPropertyRequired(key, inputSchema);
@@ -148,7 +152,10 @@ const ToolsTab = ({
onChange={(e) =>
setParams({
...params,
- [key]: e.target.value,
+ [key]:
+ e.target.value === ""
+ ? undefined
+ : e.target.value,
})
}
className="mt-1"
diff --git a/client/src/components/__tests__/AuthDebugger.test.tsx b/client/src/components/__tests__/AuthDebugger.test.tsx
index fa9f2f7d..3d8e2290 100644
--- a/client/src/components/__tests__/AuthDebugger.test.tsx
+++ b/client/src/components/__tests__/AuthDebugger.test.tsx
@@ -36,7 +36,7 @@ const mockOAuthClientInfo = {
// Mock MCP SDK functions - must be before imports
jest.mock("@modelcontextprotocol/sdk/client/auth.js", () => ({
auth: jest.fn(),
- discoverOAuthMetadata: jest.fn(),
+ discoverAuthorizationServerMetadata: jest.fn(),
registerClient: jest.fn(),
startAuthorization: jest.fn(),
exchangeAuthorization: jest.fn(),
@@ -46,7 +46,7 @@ jest.mock("@modelcontextprotocol/sdk/client/auth.js", () => ({
// Import the functions to get their types
import {
- discoverOAuthMetadata,
+ discoverAuthorizationServerMetadata,
registerClient,
startAuthorization,
exchangeAuthorization,
@@ -57,9 +57,10 @@ import { OAuthMetadata } from "@modelcontextprotocol/sdk/shared/auth.js";
import { EMPTY_DEBUGGER_STATE } from "@/lib/auth-types";
// Type the mocked functions properly
-const mockDiscoverOAuthMetadata = discoverOAuthMetadata as jest.MockedFunction<
- typeof discoverOAuthMetadata
->;
+const mockDiscoverAuthorizationServerMetadata =
+ discoverAuthorizationServerMetadata as jest.MockedFunction<
+ typeof discoverAuthorizationServerMetadata
+ >;
const mockRegisterClient = registerClient as jest.MockedFunction<
typeof registerClient
>;
@@ -102,7 +103,9 @@ describe("AuthDebugger", () => {
// Suppress console errors in tests to avoid JSDOM navigation noise
jest.spyOn(console, "error").mockImplementation(() => {});
- mockDiscoverOAuthMetadata.mockResolvedValue(mockOAuthMetadata);
+ mockDiscoverAuthorizationServerMetadata.mockResolvedValue(
+ mockOAuthMetadata,
+ );
mockRegisterClient.mockResolvedValue(mockOAuthClientInfo);
mockDiscoverOAuthProtectedResourceMetadata.mockRejectedValue(
new Error("No protected resource metadata found"),
@@ -203,7 +206,7 @@ describe("AuthDebugger", () => {
});
// Should first discover and save OAuth metadata
- expect(mockDiscoverOAuthMetadata).toHaveBeenCalledWith(
+ expect(mockDiscoverAuthorizationServerMetadata).toHaveBeenCalledWith(
new URL("https://example.com/"),
);
@@ -216,7 +219,7 @@ describe("AuthDebugger", () => {
});
it("should show error when quick OAuth flow fails to discover metadata", async () => {
- mockDiscoverOAuthMetadata.mockRejectedValue(
+ mockDiscoverAuthorizationServerMetadata.mockRejectedValue(
new Error("Metadata discovery failed"),
);
@@ -362,7 +365,7 @@ describe("AuthDebugger", () => {
fireEvent.click(screen.getByText("Continue"));
});
- expect(mockDiscoverOAuthMetadata).toHaveBeenCalledWith(
+ expect(mockDiscoverAuthorizationServerMetadata).toHaveBeenCalledWith(
new URL("https://example.com/"),
);
});
@@ -509,7 +512,9 @@ describe("AuthDebugger", () => {
mockDiscoverOAuthProtectedResourceMetadata.mockResolvedValue(
mockResourceMetadata,
);
- mockDiscoverOAuthMetadata.mockResolvedValue(mockOAuthMetadata);
+ mockDiscoverAuthorizationServerMetadata.mockResolvedValue(
+ mockOAuthMetadata,
+ );
await act(async () => {
renderAuthDebugger({
@@ -563,7 +568,9 @@ describe("AuthDebugger", () => {
// Mock failed metadata discovery
mockDiscoverOAuthProtectedResourceMetadata.mockRejectedValue(mockError);
// But OAuth metadata should still work with the original URL
- mockDiscoverOAuthMetadata.mockResolvedValue(mockOAuthMetadata);
+ mockDiscoverAuthorizationServerMetadata.mockResolvedValue(
+ mockOAuthMetadata,
+ );
await act(async () => {
renderAuthDebugger({
@@ -603,7 +610,7 @@ describe("AuthDebugger", () => {
});
// Verify that regular OAuth metadata discovery was still called
- expect(mockDiscoverOAuthMetadata).toHaveBeenCalledWith(
+ expect(mockDiscoverAuthorizationServerMetadata).toHaveBeenCalledWith(
new URL("https://example.com/"),
);
});
diff --git a/client/src/components/__tests__/DynamicJsonForm.test.tsx b/client/src/components/__tests__/DynamicJsonForm.test.tsx
index 22813c9b..318fba2d 100644
--- a/client/src/components/__tests__/DynamicJsonForm.test.tsx
+++ b/client/src/components/__tests__/DynamicJsonForm.test.tsx
@@ -35,6 +35,18 @@ describe("DynamicJsonForm String Fields", () => {
const input = screen.getByRole("textbox");
expect(input).toHaveProperty("type", "text");
});
+
+ it("should handle a union type of string and null", () => {
+ const schema: JsonSchemaType = {
+ type: ["string", "null"],
+ description: "Test string or null field",
+ };
+ render(
+ ,
+ );
+ const input = screen.getByRole("textbox");
+ expect(input).toHaveProperty("type", "text");
+ });
});
describe("Format Support", () => {
diff --git a/client/src/lib/auth.ts b/client/src/lib/auth.ts
index 9989e944..7012c782 100644
--- a/client/src/lib/auth.ts
+++ b/client/src/lib/auth.ts
@@ -8,6 +8,7 @@ import {
OAuthMetadata,
} from "@modelcontextprotocol/sdk/shared/auth.js";
import { SESSION_KEYS, getServerSpecificKey } from "./constants";
+import { generateOAuthState } from "@/utils/oauthUtils";
export const getClientInformationFromSessionStorage = async ({
serverUrl,
@@ -86,6 +87,10 @@ export class InspectorOAuthClientProvider implements OAuthClientProvider {
};
}
+ state(): string | Promise {
+ return generateOAuthState();
+ }
+
async clientInformation() {
// Try to get the preregistered client information from session storage first
const preregisteredClientInformation =
@@ -129,6 +134,12 @@ export class InspectorOAuthClientProvider implements OAuthClientProvider {
}
redirectToAuthorization(authorizationUrl: URL) {
+ if (
+ authorizationUrl.protocol !== "http:" &&
+ authorizationUrl.protocol !== "https:"
+ ) {
+ throw new Error("Authorization URL must be HTTP or HTTPS");
+ }
window.location.href = authorizationUrl.href;
}
diff --git a/client/src/lib/hooks/useConnection.ts b/client/src/lib/hooks/useConnection.ts
index 78dfa6df..12d8ff3e 100644
--- a/client/src/lib/hooks/useConnection.ts
+++ b/client/src/lib/hooks/useConnection.ts
@@ -351,6 +351,7 @@ export function useConnection({
return;
}
+ let lastRequest = "";
try {
// Inject auth manually instead of using SSEClientTransport, because we're
// proxying through the inspector server first.
@@ -564,7 +565,9 @@ export function useConnection({
}
if (capabilities?.logging && defaultLoggingLevel) {
+ lastRequest = "logging/setLevel";
await client.setLoggingLevel(defaultLoggingLevel);
+ lastRequest = "";
}
if (onElicitationRequest) {
@@ -578,6 +581,17 @@ export function useConnection({
setMcpClient(client);
setConnectionStatus("connected");
} catch (e) {
+ if (
+ lastRequest === "logging/setLevel" &&
+ e instanceof McpError &&
+ e.code === ErrorCode.MethodNotFound
+ ) {
+ toast({
+ title: "Error",
+ description: `Server declares logging capability but doesn't implement method: "${lastRequest}"`,
+ variant: "destructive",
+ });
+ }
console.error(e);
setConnectionStatus("error");
}
diff --git a/client/src/lib/oauth-state-machine.ts b/client/src/lib/oauth-state-machine.ts
index 5078cfe4..618e6453 100644
--- a/client/src/lib/oauth-state-machine.ts
+++ b/client/src/lib/oauth-state-machine.ts
@@ -1,7 +1,7 @@
import { OAuthStep, AuthDebuggerState } from "./auth-types";
import { DebugInspectorOAuthClientProvider } from "./auth";
import {
- discoverOAuthMetadata,
+ discoverAuthorizationServerMetadata,
registerClient,
startAuthorization,
exchangeAuthorization,
@@ -12,6 +12,7 @@ import {
OAuthMetadataSchema,
OAuthProtectedResourceMetadata,
} from "@modelcontextprotocol/sdk/shared/auth.js";
+import { generateOAuthState } from "@/utils/oauthUtils";
export interface StateMachineContext {
state: AuthDebuggerState;
@@ -56,7 +57,7 @@ export const oauthTransitions: Record = {
resourceMetadata ?? undefined,
);
- const metadata = await discoverOAuthMetadata(authServerUrl);
+ const metadata = await discoverAuthorizationServerMetadata(authServerUrl);
if (!metadata) {
throw new Error("Failed to discover OAuth metadata");
}
@@ -113,13 +114,6 @@ export const oauthTransitions: Record = {
scope = metadata.scopes_supported.join(" ");
}
- // Generate a random state
- const array = new Uint8Array(32);
- crypto.getRandomValues(array);
- const state = Array.from(array, (byte) =>
- byte.toString(16).padStart(2, "0"),
- ).join("");
-
const { authorizationUrl, codeVerifier } = await startAuthorization(
context.serverUrl,
{
@@ -127,7 +121,7 @@ export const oauthTransitions: Record = {
clientInformation,
redirectUrl: context.provider.redirectUrl,
scope,
- state: state,
+ state: generateOAuthState(),
resource: context.state.resource ?? undefined,
},
);
diff --git a/client/src/utils/__tests__/oauthUtils.ts b/client/src/utils/__tests__/oauthUtils.test.ts
similarity index 92%
rename from client/src/utils/__tests__/oauthUtils.ts
rename to client/src/utils/__tests__/oauthUtils.test.ts
index cc9674cb..c42f014e 100644
--- a/client/src/utils/__tests__/oauthUtils.ts
+++ b/client/src/utils/__tests__/oauthUtils.test.ts
@@ -1,6 +1,7 @@
import {
generateOAuthErrorDescription,
parseOAuthCallbackParams,
+ generateOAuthState,
} from "@/utils/oauthUtils.ts";
describe("parseOAuthCallbackParams", () => {
@@ -75,4 +76,11 @@ describe("generateOAuthErrorDescription", () => {
"Error: invalid_request.\nDetails: The request could not be completed as dialed.\nMore info: https://example.com/error-docs.",
);
});
+
+ describe("generateOAuthState", () => {
+ it("Returns a string", () => {
+ expect(generateOAuthState()).toBeDefined();
+ expect(generateOAuthState()).toHaveLength(64);
+ });
+ });
});
diff --git a/client/src/utils/__tests__/schemaUtils.test.ts b/client/src/utils/__tests__/schemaUtils.test.ts
index 133e25ec..0ecd8075 100644
--- a/client/src/utils/__tests__/schemaUtils.test.ts
+++ b/client/src/utils/__tests__/schemaUtils.test.ts
@@ -1,6 +1,7 @@
import {
generateDefaultValue,
formatFieldLabel,
+ normalizeUnionType,
cacheToolOutputSchemas,
getToolOutputValidator,
validateToolOutput,
@@ -142,6 +143,189 @@ describe("formatFieldLabel", () => {
});
});
+describe("normalizeUnionType", () => {
+ test("normalizes anyOf with string and null to string type", () => {
+ const schema: JsonSchemaType = {
+ anyOf: [{ type: "string" }, { type: "null" }],
+ description: "Optional string parameter",
+ };
+
+ const normalized = normalizeUnionType(schema);
+
+ expect(normalized.type).toBe("string");
+ expect(normalized.anyOf).toBeUndefined();
+ expect(normalized.description).toBe("Optional string parameter");
+ });
+
+ test("normalizes anyOf with boolean and null to boolean type", () => {
+ const schema: JsonSchemaType = {
+ anyOf: [{ type: "boolean" }, { type: "null" }],
+ description: "Optional boolean parameter",
+ };
+
+ const normalized = normalizeUnionType(schema);
+
+ expect(normalized.type).toBe("boolean");
+ expect(normalized.anyOf).toBeUndefined();
+ expect(normalized.description).toBe("Optional boolean parameter");
+ });
+
+ test("normalizes array type with string and null to string type", () => {
+ const schema: JsonSchemaType = {
+ type: ["string", "null"],
+ description: "Optional string parameter",
+ };
+
+ const normalized = normalizeUnionType(schema);
+
+ expect(normalized.type).toBe("string");
+ expect(normalized.description).toBe("Optional string parameter");
+ });
+
+ test("normalizes array type with boolean and null to boolean type", () => {
+ const schema: JsonSchemaType = {
+ type: ["boolean", "null"],
+ description: "Optional boolean parameter",
+ };
+
+ const normalized = normalizeUnionType(schema);
+
+ expect(normalized.type).toBe("boolean");
+ expect(normalized.description).toBe("Optional boolean parameter");
+ });
+
+ test("normalizes anyOf with number and null to number type", () => {
+ const schema: JsonSchemaType = {
+ anyOf: [{ type: "number" }, { type: "null" }],
+ description: "Optional number parameter",
+ };
+
+ const normalized = normalizeUnionType(schema);
+
+ expect(normalized.type).toBe("number");
+ expect(normalized.anyOf).toBeUndefined();
+ expect(normalized.description).toBe("Optional number parameter");
+ });
+
+ test("normalizes anyOf with integer and null to integer type", () => {
+ const schema: JsonSchemaType = {
+ anyOf: [{ type: "integer" }, { type: "null" }],
+ description: "Optional integer parameter",
+ };
+
+ const normalized = normalizeUnionType(schema);
+
+ expect(normalized.type).toBe("integer");
+ expect(normalized.anyOf).toBeUndefined();
+ expect(normalized.description).toBe("Optional integer parameter");
+ });
+
+ test("normalizes array type with number and null to number type", () => {
+ const schema: JsonSchemaType = {
+ type: ["number", "null"],
+ description: "Optional number parameter",
+ };
+
+ const normalized = normalizeUnionType(schema);
+
+ expect(normalized.type).toBe("number");
+ expect(normalized.description).toBe("Optional number parameter");
+ });
+
+ test("normalizes array type with integer and null to integer type", () => {
+ const schema: JsonSchemaType = {
+ type: ["integer", "null"],
+ description: "Optional integer parameter",
+ };
+
+ const normalized = normalizeUnionType(schema);
+
+ expect(normalized.type).toBe("integer");
+ expect(normalized.description).toBe("Optional integer parameter");
+ });
+
+ test("handles anyOf with reversed order (null first)", () => {
+ const schema: JsonSchemaType = {
+ anyOf: [{ type: "null" }, { type: "string" }],
+ };
+
+ const normalized = normalizeUnionType(schema);
+
+ expect(normalized.type).toBe("string");
+ expect(normalized.anyOf).toBeUndefined();
+ });
+
+ test("leaves non-union schemas unchanged", () => {
+ const schema: JsonSchemaType = {
+ type: "string",
+ description: "Regular string parameter",
+ };
+
+ const normalized = normalizeUnionType(schema);
+
+ expect(normalized).toEqual(schema);
+ });
+
+ test("leaves anyOf with non-matching types unchanged", () => {
+ const schema: JsonSchemaType = {
+ anyOf: [{ type: "string" }, { type: "number" }],
+ };
+
+ const normalized = normalizeUnionType(schema);
+
+ expect(normalized).toEqual(schema);
+ });
+
+ test("leaves anyOf with more than two types unchanged", () => {
+ const schema: JsonSchemaType = {
+ anyOf: [{ type: "string" }, { type: "number" }, { type: "null" }],
+ };
+
+ const normalized = normalizeUnionType(schema);
+
+ expect(normalized).toEqual(schema);
+ });
+
+ test("leaves array type with non-matching types unchanged", () => {
+ const schema: JsonSchemaType = {
+ type: ["string", "number"],
+ };
+
+ const normalized = normalizeUnionType(schema);
+
+ expect(normalized).toEqual(schema);
+ });
+
+ test("handles schemas without type or anyOf", () => {
+ const schema: JsonSchemaType = {
+ description: "Schema without type",
+ };
+
+ const normalized = normalizeUnionType(schema);
+
+ expect(normalized).toEqual(schema);
+ });
+
+ test("preserves other properties when normalizing", () => {
+ const schema: JsonSchemaType = {
+ anyOf: [{ type: "string" }, { type: "null" }],
+ description: "Optional string",
+ minLength: 1,
+ maxLength: 100,
+ pattern: "^[a-z]+$",
+ };
+
+ const normalized = normalizeUnionType(schema);
+
+ expect(normalized.type).toBe("string");
+ expect(normalized.anyOf).toBeUndefined();
+ expect(normalized.description).toBe("Optional string");
+ expect(normalized.minLength).toBe(1);
+ expect(normalized.maxLength).toBe(100);
+ expect(normalized.pattern).toBe("^[a-z]+$");
+ });
+});
+
describe("Output Schema Validation", () => {
const mockTools: Tool[] = [
{
diff --git a/client/src/utils/jsonUtils.ts b/client/src/utils/jsonUtils.ts
index 338b642a..60f5b1d6 100644
--- a/client/src/utils/jsonUtils.ts
+++ b/client/src/utils/jsonUtils.ts
@@ -21,7 +21,16 @@ export type JsonSchemaType = {
| "boolean"
| "array"
| "object"
- | "null";
+ | "null"
+ | (
+ | "string"
+ | "number"
+ | "integer"
+ | "boolean"
+ | "array"
+ | "object"
+ | "null"
+ )[];
title?: string;
description?: string;
required?: string[];
diff --git a/client/src/utils/oauthUtils.ts b/client/src/utils/oauthUtils.ts
index c971271e..83de080b 100644
--- a/client/src/utils/oauthUtils.ts
+++ b/client/src/utils/oauthUtils.ts
@@ -48,6 +48,20 @@ export const parseOAuthCallbackParams = (location: string): CallbackParams => {
};
};
+/**
+ * Generate a random state for the OAuth 2.0 flow.
+ *
+ * @returns A random state for the OAuth 2.0 flow.
+ */
+export const generateOAuthState = () => {
+ // Generate a random state
+ const array = new Uint8Array(32);
+ crypto.getRandomValues(array);
+ return Array.from(array, (byte) => byte.toString(16).padStart(2, "0")).join(
+ "",
+ );
+};
+
export const generateOAuthErrorDescription = (
params: Extract,
): string => {
diff --git a/client/src/utils/schemaUtils.ts b/client/src/utils/schemaUtils.ts
index 9fc7a724..7a175142 100644
--- a/client/src/utils/schemaUtils.ts
+++ b/client/src/utils/schemaUtils.ts
@@ -145,6 +145,95 @@ export function isPropertyRequired(
return schema.required?.includes(propertyName) ?? false;
}
+/**
+ * Normalizes union types (like string|null from FastMCP) to simple types for form rendering
+ * @param schema The JSON schema to normalize
+ * @returns A normalized schema or the original schema
+ */
+export function normalizeUnionType(schema: JsonSchemaType): JsonSchemaType {
+ // Handle anyOf with exactly string and null (FastMCP pattern)
+ 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 };
+ }
+
+ // 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 };
+ }
+
+ // 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 };
+ }
+
+ // 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 };
+ }
+
+ // Handle array type with exactly string and null
+ if (
+ Array.isArray(schema.type) &&
+ schema.type.length === 2 &&
+ schema.type.includes("string") &&
+ schema.type.includes("null")
+ ) {
+ return { ...schema, type: "string" };
+ }
+
+ // Handle array type with exactly boolean and null
+ if (
+ Array.isArray(schema.type) &&
+ schema.type.length === 2 &&
+ schema.type.includes("boolean") &&
+ schema.type.includes("null")
+ ) {
+ return { ...schema, type: "boolean" };
+ }
+
+ // Handle array type with exactly number and null
+ if (
+ Array.isArray(schema.type) &&
+ schema.type.length === 2 &&
+ schema.type.includes("number") &&
+ schema.type.includes("null")
+ ) {
+ return { ...schema, type: "number" };
+ }
+
+ // Handle array type with exactly integer and null
+ if (
+ Array.isArray(schema.type) &&
+ schema.type.length === 2 &&
+ schema.type.includes("integer") &&
+ schema.type.includes("null")
+ ) {
+ return { ...schema, type: "integer" };
+ }
+
+ return schema;
+}
+
/**
* Formats a field key into a human-readable label
* @param key The field key to format
diff --git a/package-lock.json b/package-lock.json
index e5757eb6..6103a16e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1999,9 +1999,9 @@
"link": true
},
"node_modules/@modelcontextprotocol/sdk": {
- "version": "1.17.0",
- "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.17.0.tgz",
- "integrity": "sha512-qFfbWFA7r1Sd8D697L7GkTd36yqDuTkvz0KfOGkgXR8EUhQn3/EDNIR/qUdQNMT8IjmasBvHWuXeisxtXTQT2g==",
+ "version": "1.17.2",
+ "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.17.2.tgz",
+ "integrity": "sha512-EFLRNXR/ixpXQWu6/3Cu30ndDFIFNaqUXcTqsGebujeMan9FzhAaFFswLRiFj61rgygDRr8WO1N+UijjgRxX9g==",
"license": "MIT",
"dependencies": {
"ajv": "^6.12.6",