Resolve ref before validation
This commit is contained in:
parent
9e6c9a2c8f
commit
2ab7d625e4
2 changed files with 93 additions and 1 deletions
|
|
@ -1,7 +1,10 @@
|
||||||
import { renderHook, act } from "@testing-library/react";
|
import { renderHook, act } from "@testing-library/react";
|
||||||
import { useConnection } from "../useConnection";
|
import { useConnection } from "../useConnection";
|
||||||
import { z } from "zod";
|
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 { DEFAULT_INSPECTOR_CONFIG, CLIENT_IDENTITY } from "../../constants";
|
||||||
import {
|
import {
|
||||||
SSEClientTransportOptions,
|
SSEClientTransportOptions,
|
||||||
|
|
@ -42,10 +45,12 @@ const mockSSETransport: {
|
||||||
start: jest.Mock;
|
start: jest.Mock;
|
||||||
url: URL | undefined;
|
url: URL | undefined;
|
||||||
options: SSEClientTransportOptions | undefined;
|
options: SSEClientTransportOptions | undefined;
|
||||||
|
onmessage?: (message: JSONRPCMessage) => void;
|
||||||
} = {
|
} = {
|
||||||
start: jest.fn(),
|
start: jest.fn(),
|
||||||
url: undefined,
|
url: undefined,
|
||||||
options: undefined,
|
options: undefined,
|
||||||
|
onmessage: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockStreamableHTTPTransport: {
|
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", () => {
|
describe("URL Port Handling", () => {
|
||||||
const SSEClientTransport = jest.requireMock(
|
const SSEClientTransport = jest.requireMock(
|
||||||
"@modelcontextprotocol/sdk/client/sse.js",
|
"@modelcontextprotocol/sdk/client/sse.js",
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,8 @@ import {
|
||||||
Progress,
|
Progress,
|
||||||
LoggingLevel,
|
LoggingLevel,
|
||||||
ElicitRequestSchema,
|
ElicitRequestSchema,
|
||||||
|
JSONRPCMessage,
|
||||||
|
isJSONRPCRequest,
|
||||||
} from "@modelcontextprotocol/sdk/types.js";
|
} from "@modelcontextprotocol/sdk/types.js";
|
||||||
import { RequestOptions } from "@modelcontextprotocol/sdk/shared/protocol.js";
|
import { RequestOptions } from "@modelcontextprotocol/sdk/shared/protocol.js";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
@ -57,6 +59,8 @@ import { getMCPServerRequestTimeout } from "@/utils/configUtils";
|
||||||
import { InspectorConfig } from "../configurationTypes";
|
import { InspectorConfig } from "../configurationTypes";
|
||||||
import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
|
import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
|
||||||
import { CustomHeaders } from "../types/customHeaders";
|
import { CustomHeaders } from "../types/customHeaders";
|
||||||
|
import { JsonSchemaType } from "@/utils/jsonUtils";
|
||||||
|
import { resolveRef } from "@/utils/schemaUtils";
|
||||||
|
|
||||||
interface UseConnectionOptions {
|
interface UseConnectionOptions {
|
||||||
transportType: "stdio" | "sse" | "streamable-http";
|
transportType: "stdio" | "sse" | "streamable-http";
|
||||||
|
|
@ -681,6 +685,38 @@ export function useConnection({
|
||||||
|
|
||||||
await client.connect(transport as Transport);
|
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);
|
setClientTransport(transport);
|
||||||
|
|
||||||
capabilities = client.getServerCapabilities();
|
capabilities = client.getServerCapabilities();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue