Merge branch 'main' of https://github.com/modelcontextprotocol/inspector into meta_properies_in_tools
This commit is contained in:
commit
c0b3b952f5
10 changed files with 197 additions and 20 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@modelcontextprotocol/inspector-cli",
|
"name": "@modelcontextprotocol/inspector-cli",
|
||||||
"version": "0.17.3",
|
"version": "0.17.4",
|
||||||
"description": "CLI for the Model Context Protocol inspector",
|
"description": "CLI for the Model Context Protocol inspector",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"author": "Anthropic, PBC (https://anthropic.com)",
|
"author": "Anthropic, PBC (https://anthropic.com)",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@modelcontextprotocol/inspector-client",
|
"name": "@modelcontextprotocol/inspector-client",
|
||||||
"version": "0.17.3",
|
"version": "0.17.4",
|
||||||
"description": "Client-side application for the Model Context Protocol inspector",
|
"description": "Client-side application for the Model Context Protocol inspector",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"author": "Anthropic, PBC (https://anthropic.com)",
|
"author": "Anthropic, PBC (https://anthropic.com)",
|
||||||
|
|
|
||||||
|
|
@ -205,7 +205,8 @@ const ToolResults = ({
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{item.type === "resource" &&
|
{item.type === "resource" &&
|
||||||
(item.resource?.mimeType?.startsWith("audio/") ? (
|
(item.resource?.mimeType?.startsWith("audio/") &&
|
||||||
|
"blob" in item.resource ? (
|
||||||
<audio
|
<audio
|
||||||
controls
|
controls
|
||||||
src={`data:${item.resource.mimeType};base64,${item.resource.blob}`}
|
src={`data:${item.resource.mimeType};base64,${item.resource.blob}`}
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ describe("HistoryAndNotifications", () => {
|
||||||
method: "notifications/message",
|
method: "notifications/message",
|
||||||
params: {
|
params: {
|
||||||
level: "info" as const,
|
level: "info" as const,
|
||||||
message: "First notification",
|
data: "First notification",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -36,7 +36,7 @@ describe("HistoryAndNotifications", () => {
|
||||||
params: {
|
params: {
|
||||||
progressToken: "test-token",
|
progressToken: "test-token",
|
||||||
progress: 50,
|
progress: 50,
|
||||||
message: "Second notification",
|
total: 100,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -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: {
|
||||||
|
|
@ -482,6 +487,129 @@ 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: {
|
||||||
|
source: {
|
||||||
|
type: "string",
|
||||||
|
minLength: 1,
|
||||||
|
title: "A Connectable Node",
|
||||||
|
},
|
||||||
|
target: {
|
||||||
|
$ref: "#/properties/source",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
mockSSETransport.onmessage!(mockRequestWithRef);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockProtocolOnMessage).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
const message = mockProtocolOnMessage.mock.calls[0][0];
|
||||||
|
expect(message.params.requestedSchema.properties.target).toEqual({
|
||||||
|
type: "string",
|
||||||
|
minLength: 1,
|
||||||
|
title: "A Connectable Node",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("resolves $ref references to $defs in requestedSchema", async () => {
|
||||||
|
const mockProtocolOnMessage = jest.fn();
|
||||||
|
|
||||||
|
mockSSETransport.onmessage = mockProtocolOnMessage;
|
||||||
|
|
||||||
|
const { result } = renderHook(() => useConnection(defaultProps));
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
await result.current.connect();
|
||||||
|
});
|
||||||
|
|
||||||
|
const mockRequestWithDefs: JSONRPCMessage = {
|
||||||
|
jsonrpc: "2.0",
|
||||||
|
id: 1,
|
||||||
|
method: "elicitation/create",
|
||||||
|
params: {
|
||||||
|
message: "Please provide your information",
|
||||||
|
requestedSchema: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
user: {
|
||||||
|
$ref: "#/$defs/UserInput",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
$defs: {
|
||||||
|
UserInput: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
name: {
|
||||||
|
type: "string",
|
||||||
|
title: "Name",
|
||||||
|
},
|
||||||
|
age: {
|
||||||
|
type: "integer",
|
||||||
|
title: "Age",
|
||||||
|
minimum: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ["name"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
mockSSETransport.onmessage!(mockRequestWithDefs);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockProtocolOnMessage).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
const message = mockProtocolOnMessage.mock.calls[0][0];
|
||||||
|
// The $ref should be resolved to the actual UserInput definition
|
||||||
|
expect(message.params.requestedSchema.properties.user).toEqual({
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
name: {
|
||||||
|
type: "string",
|
||||||
|
title: "Name",
|
||||||
|
},
|
||||||
|
age: {
|
||||||
|
type: "integer",
|
||||||
|
title: "Age",
|
||||||
|
minimum: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ["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",
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,7 @@ 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 { resolveRefsInMessage } from "@/utils/schemaUtils";
|
||||||
|
|
||||||
interface UseConnectionOptions {
|
interface UseConnectionOptions {
|
||||||
transportType: "stdio" | "sse" | "streamable-http";
|
transportType: "stdio" | "sse" | "streamable-http";
|
||||||
|
|
@ -711,6 +712,14 @@ export function useConnection({
|
||||||
|
|
||||||
await client.connect(transport as Transport);
|
await client.connect(transport as Transport);
|
||||||
|
|
||||||
|
const protocolOnMessage = transport.onmessage;
|
||||||
|
if (protocolOnMessage) {
|
||||||
|
transport.onmessage = (message) => {
|
||||||
|
const resolvedMessage = resolveRefsInMessage(message);
|
||||||
|
protocolOnMessage(resolvedMessage);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
setClientTransport(transport);
|
setClientTransport(transport);
|
||||||
|
|
||||||
capabilities = client.getServerCapabilities();
|
capabilities = client.getServerCapabilities();
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
import type { JsonValue, JsonSchemaType, JsonObject } from "./jsonUtils";
|
import type { JsonValue, JsonSchemaType, JsonObject } from "./jsonUtils";
|
||||||
import Ajv from "ajv";
|
import Ajv from "ajv";
|
||||||
import type { ValidateFunction } from "ajv";
|
import type { ValidateFunction } from "ajv";
|
||||||
import type { Tool } from "@modelcontextprotocol/sdk/types.js";
|
import type { Tool, JSONRPCMessage } from "@modelcontextprotocol/sdk/types.js";
|
||||||
|
import { isJSONRPCRequest } from "@modelcontextprotocol/sdk/types.js";
|
||||||
|
|
||||||
const ajv = new Ajv();
|
const ajv = new Ajv();
|
||||||
|
|
||||||
|
|
@ -299,3 +300,41 @@ export function formatFieldLabel(key: string): string {
|
||||||
.replace(/_/g, " ") // Replace underscores with spaces
|
.replace(/_/g, " ") // Replace underscores with spaces
|
||||||
.replace(/^\w/, (c) => c.toUpperCase()); // Capitalize first letter
|
.replace(/^\w/, (c) => c.toUpperCase()); // Capitalize first letter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves `$ref` references in a JSON-RPC "elicitation/create" message's `requestedSchema` field
|
||||||
|
* @param message The JSON-RPC message that may contain $ref references
|
||||||
|
* @returns A new message with resolved $ref references, or the original message if no resolution is needed
|
||||||
|
*/
|
||||||
|
export function resolveRefsInMessage(message: JSONRPCMessage): JSONRPCMessage {
|
||||||
|
if (!isJSONRPCRequest(message) || !message.params?.requestedSchema) {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
const requestedSchema = message.params.requestedSchema as JsonSchemaType;
|
||||||
|
|
||||||
|
if (!requestedSchema?.properties) {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolvedMessage = {
|
||||||
|
...message,
|
||||||
|
params: {
|
||||||
|
...message.params,
|
||||||
|
requestedSchema: {
|
||||||
|
...requestedSchema,
|
||||||
|
properties: Object.fromEntries(
|
||||||
|
Object.entries(requestedSchema.properties).map(
|
||||||
|
([key, propSchema]) => {
|
||||||
|
const resolved = resolveRef(propSchema, requestedSchema);
|
||||||
|
const normalized = normalizeUnionType(resolved);
|
||||||
|
return [key, normalized];
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return resolvedMessage;
|
||||||
|
}
|
||||||
|
|
|
||||||
16
package-lock.json
generated
16
package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "@modelcontextprotocol/inspector",
|
"name": "@modelcontextprotocol/inspector",
|
||||||
"version": "0.17.3",
|
"version": "0.17.4",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@modelcontextprotocol/inspector",
|
"name": "@modelcontextprotocol/inspector",
|
||||||
"version": "0.17.3",
|
"version": "0.17.4",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"client",
|
"client",
|
||||||
|
|
@ -14,9 +14,9 @@
|
||||||
"cli"
|
"cli"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/inspector-cli": "^0.17.3",
|
"@modelcontextprotocol/inspector-cli": "^0.17.4",
|
||||||
"@modelcontextprotocol/inspector-client": "^0.17.3",
|
"@modelcontextprotocol/inspector-client": "^0.17.4",
|
||||||
"@modelcontextprotocol/inspector-server": "^0.17.3",
|
"@modelcontextprotocol/inspector-server": "^0.17.4",
|
||||||
"@modelcontextprotocol/sdk": "^1.20.1",
|
"@modelcontextprotocol/sdk": "^1.20.1",
|
||||||
"concurrently": "^9.2.0",
|
"concurrently": "^9.2.0",
|
||||||
"node-fetch": "^3.3.2",
|
"node-fetch": "^3.3.2",
|
||||||
|
|
@ -48,7 +48,7 @@
|
||||||
},
|
},
|
||||||
"cli": {
|
"cli": {
|
||||||
"name": "@modelcontextprotocol/inspector-cli",
|
"name": "@modelcontextprotocol/inspector-cli",
|
||||||
"version": "0.17.3",
|
"version": "0.17.4",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/sdk": "^1.20.1",
|
"@modelcontextprotocol/sdk": "^1.20.1",
|
||||||
|
|
@ -71,7 +71,7 @@
|
||||||
},
|
},
|
||||||
"client": {
|
"client": {
|
||||||
"name": "@modelcontextprotocol/inspector-client",
|
"name": "@modelcontextprotocol/inspector-client",
|
||||||
"version": "0.17.3",
|
"version": "0.17.4",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/sdk": "^1.20.1",
|
"@modelcontextprotocol/sdk": "^1.20.1",
|
||||||
|
|
@ -12028,7 +12028,7 @@
|
||||||
},
|
},
|
||||||
"server": {
|
"server": {
|
||||||
"name": "@modelcontextprotocol/inspector-server",
|
"name": "@modelcontextprotocol/inspector-server",
|
||||||
"version": "0.17.3",
|
"version": "0.17.4",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/sdk": "^1.20.1",
|
"@modelcontextprotocol/sdk": "^1.20.1",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@modelcontextprotocol/inspector",
|
"name": "@modelcontextprotocol/inspector",
|
||||||
"version": "0.17.3",
|
"version": "0.17.4",
|
||||||
"description": "Model Context Protocol inspector",
|
"description": "Model Context Protocol inspector",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"author": "Anthropic, PBC (https://anthropic.com)",
|
"author": "Anthropic, PBC (https://anthropic.com)",
|
||||||
|
|
@ -47,9 +47,9 @@
|
||||||
"check-version": "node scripts/check-version-consistency.js"
|
"check-version": "node scripts/check-version-consistency.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/inspector-cli": "^0.17.3",
|
"@modelcontextprotocol/inspector-cli": "^0.17.4",
|
||||||
"@modelcontextprotocol/inspector-client": "^0.17.3",
|
"@modelcontextprotocol/inspector-client": "^0.17.4",
|
||||||
"@modelcontextprotocol/inspector-server": "^0.17.3",
|
"@modelcontextprotocol/inspector-server": "^0.17.4",
|
||||||
"@modelcontextprotocol/sdk": "^1.20.1",
|
"@modelcontextprotocol/sdk": "^1.20.1",
|
||||||
"concurrently": "^9.2.0",
|
"concurrently": "^9.2.0",
|
||||||
"node-fetch": "^3.3.2",
|
"node-fetch": "^3.3.2",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@modelcontextprotocol/inspector-server",
|
"name": "@modelcontextprotocol/inspector-server",
|
||||||
"version": "0.17.3",
|
"version": "0.17.4",
|
||||||
"description": "Server-side application for the Model Context Protocol inspector",
|
"description": "Server-side application for the Model Context Protocol inspector",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"author": "Anthropic, PBC (https://anthropic.com)",
|
"author": "Anthropic, PBC (https://anthropic.com)",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue