Resolve ref before validation

This commit is contained in:
Vincent 2025-11-01 21:03:02 -07:00
commit 2ab7d625e4
2 changed files with 93 additions and 1 deletions

View file

@ -1,7 +1,10 @@
import { renderHook, act } from "@testing-library/react";
import { useConnection } from "../useConnection";
import { z } from "zod";
import { ClientRequest } from "@modelcontextprotocol/sdk/types.js";
import {
ClientRequest,
JSONRPCMessage,
} from "@modelcontextprotocol/sdk/types.js";
import { DEFAULT_INSPECTOR_CONFIG, CLIENT_IDENTITY } from "../../constants";
import {
SSEClientTransportOptions,
@ -42,10 +45,12 @@ const mockSSETransport: {
start: jest.Mock;
url: URL | undefined;
options: SSEClientTransportOptions | undefined;
onmessage?: (message: JSONRPCMessage) => void;
} = {
start: jest.fn(),
url: undefined,
options: undefined,
onmessage: undefined,
};
const mockStreamableHTTPTransport: {
@ -480,6 +485,57 @@ describe("useConnection", () => {
});
});
describe("Ref Resolution", () => {
beforeEach(() => {
jest.clearAllMocks();
});
test("resolves $ref references in requestedSchema properties before validation", async () => {
const mockProtocolOnMessage = jest.fn();
mockSSETransport.onmessage = mockProtocolOnMessage;
const { result } = renderHook(() => useConnection(defaultProps));
await act(async () => {
await result.current.connect();
});
const mockRequestWithRef: JSONRPCMessage = {
jsonrpc: "2.0",
id: 1,
method: "elicitation/create",
params: {
message: "Please provide your information",
requestedSchema: {
type: "object",
properties: {
name: {
$ref: "#/properties/nameDef",
},
nameDef: {
type: "string",
title: "Name",
},
},
},
},
};
await act(async () => {
mockSSETransport.onmessage!(mockRequestWithRef);
});
expect(mockProtocolOnMessage).toHaveBeenCalledTimes(1);
const message = mockProtocolOnMessage.mock.calls[0][0];
expect(message.params.requestedSchema.properties.name).toEqual({
type: "string",
title: "Name",
});
});
});
describe("URL Port Handling", () => {
const SSEClientTransport = jest.requireMock(
"@modelcontextprotocol/sdk/client/sse.js",

View file

@ -30,6 +30,8 @@ import {
Progress,
LoggingLevel,
ElicitRequestSchema,
JSONRPCMessage,
isJSONRPCRequest,
} from "@modelcontextprotocol/sdk/types.js";
import { RequestOptions } from "@modelcontextprotocol/sdk/shared/protocol.js";
import { useEffect, useState } from "react";
@ -57,6 +59,8 @@ import { getMCPServerRequestTimeout } from "@/utils/configUtils";
import { InspectorConfig } from "../configurationTypes";
import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
import { CustomHeaders } from "../types/customHeaders";
import { JsonSchemaType } from "@/utils/jsonUtils";
import { resolveRef } from "@/utils/schemaUtils";
interface UseConnectionOptions {
transportType: "stdio" | "sse" | "streamable-http";
@ -681,6 +685,38 @@ export function useConnection({
await client.connect(transport as Transport);
const protocolOnMessage = transport.onmessage;
if (protocolOnMessage) {
transport.onmessage = (message: JSONRPCMessage) => {
// Resolve $ref references in requests before validation
if (isJSONRPCRequest(message) && message.params?.requestedSchema) {
const requestedSchema = message.params
.requestedSchema as JsonSchemaType;
if (requestedSchema?.properties) {
const resolvedProperties = Object.fromEntries(
Object.entries(
requestedSchema.properties as Record<
string,
JsonSchemaType
>,
).map(([key, propSchema]) => [
key,
resolveRef(propSchema, requestedSchema),
]),
);
message.params = {
...message.params,
requestedSchema: {
...requestedSchema,
properties: resolvedProperties,
},
};
}
}
protocolOnMessage(message);
};
}
setClientTransport(transport);
capabilities = client.getServerCapabilities();