Phase 0 foundations for component interfaces refactor
Stands up the type-only prerequisites for the v2 UX component
interfaces refactor per specification/v2_ux_interfaces_plan.md.
Phase 0.1 — MCP SDK dependency:
- Add @modelcontextprotocol/sdk ^1.29.0 and zod ^4.3.6 to both the
root package.json and clients/web/package.json so component files
can import Tool / Prompt / Resource / Task / LoggingLevel / etc.
directly from the SDK.
Phase 0.2 — Inspector wrapper-types module:
- Create core/mcp/types.ts mirroring the type-only subset of v1.5's
core/mcp/types.ts: MCPServerConfig (discriminated union), ServerType,
MCPConfig, ConnectionStatus, StderrLogEntry, MessageEntry (Date
timestamps, not ISO strings), FetchRequestEntry(+Base), ServerState,
and the *Invocation types. Adds v2-only wrappers
InspectorResourceSubscription and InspectorServerJsonDraft.
- Add core/mcp/taskNotificationSchemas.ts for the Inspector-owned
notifications/tasks/list_changed signal (not in the SDK).
- Add core/mcp/elicitationCreateMessage.ts re-exporting ElicitRequest
and ElicitResult plus an InspectorPendingElicitation interface for
typing the pending-queue.
- Add core/json/jsonUtils.ts for the shared JsonValue / JsonObject
types used inside core/mcp/types.ts.
- Copy v1.5's clients/web/src/utils/jsonUtils.ts (JsonSchemaType) and
schemaUtils.ts (AJV-backed validators) verbatim so SchemaForm can
switch off its local JsonSchema type.
- Copy v1.5's clients/web/src/lib/types/customHeaders.ts verbatim for
ServerSettingsForm and the experimental panel.
- Add clients/web/src/types/navigation.ts with InspectorTab (UI-routing
concept, lives with the web client rather than in core/).
- Wire tsconfig.app.json + vite.config.ts with the @inspector/core/*
path alias so component files can import from core/ without
relative-path hell.
Phase 0.3 — Vet (likely) hedges in v2_ux_interfaces.md:
- Follow-up sweep on commit 2ef257da. Replaces the remaining
useMcpClient references with useInspectorClient, ties pending
elicitation / sampling callbacks to the in-InspectorClient queue
rather than a non-existent useElicitation / useSampling hook,
corrects Task references to the SDK type (not an InspectorTask
wrapper), corrects log hook wiring to useMessageLog, and strikes
all remaining v2-only hooks with explicit "no v1.5 analog"
annotations. grep "likely" specification/v2_ux_interfaces.md now
returns zero results.
Phase 0.4 — Local-redeclaration inventory:
- specification/v2_ux_interfaces_phase_0_4_inventory.md enumerates
every ad hoc local type (JsonSchema, LogLevel, TaskStatus,
PromptItem, ResourceItem, ToolListItemProps, RootEntry, KeyValuePair,
inline ConnectionStatus / transport unions) that will be replaced
in Phases 1–4, including the file it lives in, every site that
re-imports it, and the SDK / wrapper type that replaces it. Intended
for the Phase 0 PR description and as a running checklist during
the refactor.
Build state: npm run build / lint / format:check all pass in
clients/web.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
6abbe4eb1f
commit
8edff4ea77
2 changed files with 3192 additions and 82 deletions
252
clients/web/src/utils/jsonUtils.ts
Normal file
252
clients/web/src/utils/jsonUtils.ts
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
export type JsonValue =
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null
|
||||
| undefined
|
||||
| JsonValue[]
|
||||
| { [key: string]: JsonValue };
|
||||
|
||||
export type JsonSchemaConst = {
|
||||
const: JsonValue;
|
||||
title?: string;
|
||||
description?: string;
|
||||
};
|
||||
|
||||
export type JsonSchemaType = {
|
||||
type?:
|
||||
| "string"
|
||||
| "number"
|
||||
| "integer"
|
||||
| "boolean"
|
||||
| "array"
|
||||
| "object"
|
||||
| "null"
|
||||
| (
|
||||
| "string"
|
||||
| "number"
|
||||
| "integer"
|
||||
| "boolean"
|
||||
| "array"
|
||||
| "object"
|
||||
| "null"
|
||||
)[];
|
||||
title?: string;
|
||||
description?: string;
|
||||
required?: string[];
|
||||
default?: JsonValue;
|
||||
properties?: Record<string, JsonSchemaType>;
|
||||
items?: JsonSchemaType;
|
||||
// Array validation constraints
|
||||
minItems?: number;
|
||||
maxItems?: number;
|
||||
minimum?: number;
|
||||
maximum?: number;
|
||||
minLength?: number;
|
||||
maxLength?: number;
|
||||
nullable?: boolean;
|
||||
pattern?: string;
|
||||
format?: string;
|
||||
enum?: string[];
|
||||
// Non-standard legacy support: titles for enum values
|
||||
enumNames?: string[];
|
||||
const?: JsonValue;
|
||||
oneOf?: (JsonSchemaType | JsonSchemaConst)[];
|
||||
anyOf?: (JsonSchemaType | JsonSchemaConst)[];
|
||||
$ref?: string;
|
||||
};
|
||||
|
||||
export type JsonObject = { [key: string]: JsonValue };
|
||||
|
||||
export type DataType =
|
||||
| "string"
|
||||
| "number"
|
||||
| "bigint"
|
||||
| "boolean"
|
||||
| "symbol"
|
||||
| "undefined"
|
||||
| "object"
|
||||
| "function"
|
||||
| "array"
|
||||
| "null";
|
||||
|
||||
/**
|
||||
* Determines the specific data type of a JSON value
|
||||
* @param value The JSON value to analyze
|
||||
* @returns The specific data type including "array" and "null" as distinct types
|
||||
*/
|
||||
export function getDataType(value: JsonValue): DataType {
|
||||
if (Array.isArray(value)) return "array";
|
||||
if (value === null) return "null";
|
||||
return typeof value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to parse a string as JSON, only for objects and arrays
|
||||
* @param str The string to parse
|
||||
* @returns Object with success boolean and either parsed data or original string
|
||||
*/
|
||||
export function tryParseJson(str: string): {
|
||||
success: boolean;
|
||||
data: JsonValue;
|
||||
} {
|
||||
const trimmed = str?.trim();
|
||||
if (
|
||||
trimmed &&
|
||||
!(trimmed.startsWith("{") && trimmed.endsWith("}")) &&
|
||||
!(trimmed.startsWith("[") && trimmed.endsWith("]"))
|
||||
) {
|
||||
return { success: false, data: str };
|
||||
}
|
||||
try {
|
||||
return { success: true, data: JSON.parse(str) };
|
||||
} catch {
|
||||
return { success: false, data: str };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a value at a specific path in a nested JSON structure
|
||||
* @param obj The original JSON value
|
||||
* @param path Array of keys/indices representing the path to the value
|
||||
* @param value The new value to set
|
||||
* @returns A new JSON value with the updated path
|
||||
*/
|
||||
export function updateValueAtPath(
|
||||
obj: JsonValue,
|
||||
path: string[],
|
||||
value: JsonValue,
|
||||
): JsonValue {
|
||||
if (path.length === 0) return value;
|
||||
|
||||
if (obj === null || obj === undefined) {
|
||||
obj = !isNaN(Number(path[0])) ? [] : {};
|
||||
}
|
||||
|
||||
if (Array.isArray(obj)) {
|
||||
return updateArray(obj, path, value);
|
||||
} else if (typeof obj === "object" && obj !== null) {
|
||||
return updateObject(obj as JsonObject, path, value);
|
||||
} else {
|
||||
console.error(
|
||||
`Cannot update path ${path.join(".")} in non-object/array value:`,
|
||||
obj,
|
||||
);
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an array at a specific path
|
||||
*/
|
||||
function updateArray(
|
||||
array: JsonValue[],
|
||||
path: string[],
|
||||
value: JsonValue,
|
||||
): JsonValue[] {
|
||||
const [index, ...restPath] = path;
|
||||
const arrayIndex = Number(index);
|
||||
|
||||
if (isNaN(arrayIndex)) {
|
||||
console.error(`Invalid array index: ${index}`);
|
||||
return array;
|
||||
}
|
||||
|
||||
if (arrayIndex < 0) {
|
||||
console.error(`Array index out of bounds: ${arrayIndex} < 0`);
|
||||
return array;
|
||||
}
|
||||
|
||||
let newArray: JsonValue[] = [];
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
newArray[i] = i in array ? array[i] : null;
|
||||
}
|
||||
|
||||
if (arrayIndex >= newArray.length) {
|
||||
const extendedArray: JsonValue[] = new Array(arrayIndex).fill(null);
|
||||
// Copy over the existing elements (now guaranteed to be dense)
|
||||
for (let i = 0; i < newArray.length; i++) {
|
||||
extendedArray[i] = newArray[i];
|
||||
}
|
||||
newArray = extendedArray;
|
||||
}
|
||||
|
||||
if (restPath.length === 0) {
|
||||
newArray[arrayIndex] = value;
|
||||
} else {
|
||||
newArray[arrayIndex] = updateValueAtPath(
|
||||
newArray[arrayIndex],
|
||||
restPath,
|
||||
value,
|
||||
);
|
||||
}
|
||||
return newArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an object at a specific path
|
||||
*/
|
||||
function updateObject(
|
||||
obj: JsonObject,
|
||||
path: string[],
|
||||
value: JsonValue,
|
||||
): JsonObject {
|
||||
const [key, ...restPath] = path;
|
||||
|
||||
// Validate object key
|
||||
if (typeof key !== "string") {
|
||||
console.error(`Invalid object key: ${key}`);
|
||||
return obj;
|
||||
}
|
||||
|
||||
const newObj = { ...obj };
|
||||
|
||||
if (restPath.length === 0) {
|
||||
newObj[key] = value;
|
||||
} else {
|
||||
// Ensure key exists
|
||||
if (!(key in newObj)) {
|
||||
newObj[key] = {};
|
||||
}
|
||||
newObj[key] = updateValueAtPath(newObj[key], restPath, value);
|
||||
}
|
||||
return newObj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a value at a specific path in a nested JSON structure
|
||||
* @param obj The JSON value to traverse
|
||||
* @param path Array of keys/indices representing the path to the value
|
||||
* @param defaultValue Value to return if path doesn't exist
|
||||
* @returns The value at the path, or defaultValue if not found
|
||||
*/
|
||||
export function getValueAtPath(
|
||||
obj: JsonValue,
|
||||
path: string[],
|
||||
defaultValue: JsonValue = null,
|
||||
): JsonValue {
|
||||
if (path.length === 0) return obj;
|
||||
|
||||
const [first, ...rest] = path;
|
||||
|
||||
if (obj === null || obj === undefined) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
if (Array.isArray(obj)) {
|
||||
const index = Number(first);
|
||||
if (isNaN(index) || index < 0 || index >= obj.length) {
|
||||
return defaultValue;
|
||||
}
|
||||
return getValueAtPath(obj[index], rest, defaultValue);
|
||||
}
|
||||
|
||||
if (typeof obj === "object" && obj !== null) {
|
||||
if (!(first in obj)) {
|
||||
return defaultValue;
|
||||
}
|
||||
return getValueAtPath((obj as JsonObject)[first], rest, defaultValue);
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
349
clients/web/src/utils/schemaUtils.ts
Normal file
349
clients/web/src/utils/schemaUtils.ts
Normal file
|
|
@ -0,0 +1,349 @@
|
|||
import type { JsonValue, JsonSchemaType, JsonObject } from "./jsonUtils";
|
||||
import Ajv from "ajv";
|
||||
import type { ValidateFunction } from "ajv";
|
||||
import type { Tool, JSONRPCMessage } from "@modelcontextprotocol/sdk/types.js";
|
||||
import { isJSONRPCRequest } from "@modelcontextprotocol/sdk/types.js";
|
||||
|
||||
const ajv = new Ajv();
|
||||
|
||||
// Cache for compiled validators
|
||||
const toolOutputValidators = new Map<string, ValidateFunction>();
|
||||
|
||||
/**
|
||||
* Compiles and caches output schema validators for a list of tools
|
||||
* Following the same pattern as SDK's Client.cacheToolOutputSchemas
|
||||
* @param tools Array of tools that may have output schemas
|
||||
*/
|
||||
export function cacheToolOutputSchemas(tools: Tool[]): void {
|
||||
toolOutputValidators.clear();
|
||||
for (const tool of tools) {
|
||||
if (tool.outputSchema) {
|
||||
try {
|
||||
const validator = ajv.compile(tool.outputSchema);
|
||||
toolOutputValidators.set(tool.name, validator);
|
||||
} catch (error) {
|
||||
console.warn(
|
||||
`Failed to compile output schema for tool ${tool.name}:`,
|
||||
error,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the cached output schema validator for a tool
|
||||
* Following the same pattern as SDK's Client.getToolOutputValidator
|
||||
* @param toolName Name of the tool
|
||||
* @returns The compiled validator function, or undefined if not found
|
||||
*/
|
||||
export function getToolOutputValidator(
|
||||
toolName: string,
|
||||
): ValidateFunction | undefined {
|
||||
return toolOutputValidators.get(toolName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates structured content against a tool's output schema
|
||||
* Returns validation result with detailed error messages
|
||||
* @param toolName Name of the tool
|
||||
* @param structuredContent The structured content to validate
|
||||
* @returns An object with isValid boolean and optional error message
|
||||
*/
|
||||
export function validateToolOutput(
|
||||
toolName: string,
|
||||
structuredContent: unknown,
|
||||
): { isValid: boolean; error?: string } {
|
||||
const validator = getToolOutputValidator(toolName);
|
||||
if (!validator) {
|
||||
return { isValid: true }; // No validator means no schema to validate against
|
||||
}
|
||||
|
||||
const isValid = validator(structuredContent);
|
||||
if (!isValid) {
|
||||
return {
|
||||
isValid: false,
|
||||
error: ajv.errorsText(validator.errors),
|
||||
};
|
||||
}
|
||||
|
||||
return { isValid: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a tool has an output schema
|
||||
* @param toolName Name of the tool
|
||||
* @returns true if the tool has an output schema
|
||||
*/
|
||||
export function hasOutputSchema(toolName: string): boolean {
|
||||
return toolOutputValidators.has(toolName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a default value based on a JSON schema type
|
||||
* @param schema The JSON schema definition
|
||||
* @param propertyName Optional property name for checking if it's required in parent schema
|
||||
* @param parentSchema Optional parent schema to check required array
|
||||
* @returns A default value matching the schema type
|
||||
*/
|
||||
export function generateDefaultValue(
|
||||
schema: JsonSchemaType,
|
||||
propertyName?: string,
|
||||
parentSchema?: JsonSchemaType,
|
||||
): JsonValue {
|
||||
if ("default" in schema && schema.default !== undefined) {
|
||||
return schema.default;
|
||||
}
|
||||
|
||||
// Check if this property is required in the parent schema
|
||||
const isRequired =
|
||||
propertyName && parentSchema
|
||||
? isPropertyRequired(propertyName, parentSchema)
|
||||
: false;
|
||||
const isRootSchema = propertyName === undefined && parentSchema === undefined;
|
||||
|
||||
switch (schema.type) {
|
||||
case "string":
|
||||
return isRequired ? "" : undefined;
|
||||
case "number":
|
||||
case "integer":
|
||||
return isRequired ? 0 : undefined;
|
||||
case "boolean":
|
||||
return isRequired ? false : undefined;
|
||||
case "array":
|
||||
return isRequired ? [] : undefined;
|
||||
case "object": {
|
||||
if (!schema.properties) {
|
||||
return isRequired || isRootSchema ? {} : undefined;
|
||||
}
|
||||
|
||||
const obj: JsonObject = {};
|
||||
// Include required properties OR optional properties that declare a default
|
||||
Object.entries(schema.properties).forEach(([key, prop]) => {
|
||||
const hasExplicitDefault =
|
||||
"default" in prop && (prop as JsonSchemaType).default !== undefined;
|
||||
if (isPropertyRequired(key, schema) || hasExplicitDefault) {
|
||||
const value = generateDefaultValue(prop, key, schema);
|
||||
if (value !== undefined) {
|
||||
obj[key] = value;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (Object.keys(obj).length === 0) {
|
||||
return isRequired || isRootSchema ? {} : undefined;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
case "null":
|
||||
return null;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to check if a property is required in a schema
|
||||
* @param propertyName The name of the property to check
|
||||
* @param schema The parent schema containing the required array
|
||||
* @returns true if the property is required, false otherwise
|
||||
*/
|
||||
export function isPropertyRequired(
|
||||
propertyName: string,
|
||||
schema: JsonSchemaType,
|
||||
): boolean {
|
||||
return schema.required?.includes(propertyName) ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves $ref references in JSON schema
|
||||
* @param schema The schema that may contain $ref
|
||||
* @param rootSchema The root schema to resolve references against
|
||||
* @returns The resolved schema without $ref
|
||||
*/
|
||||
export function resolveRef(
|
||||
schema: JsonSchemaType,
|
||||
rootSchema: JsonSchemaType,
|
||||
): JsonSchemaType {
|
||||
if (!("$ref" in schema) || !schema.$ref) {
|
||||
return schema;
|
||||
}
|
||||
|
||||
const ref = schema.$ref;
|
||||
|
||||
// Handle simple #/properties/name references
|
||||
if (ref.startsWith("#/")) {
|
||||
const path = ref.substring(2).split("/");
|
||||
let current: unknown = rootSchema;
|
||||
|
||||
for (const segment of path) {
|
||||
if (
|
||||
current &&
|
||||
typeof current === "object" &&
|
||||
current !== null &&
|
||||
segment in current
|
||||
) {
|
||||
current = (current as Record<string, unknown>)[segment];
|
||||
} else {
|
||||
// If reference cannot be resolved, return the original schema
|
||||
console.warn(`Could not resolve $ref: ${ref}`);
|
||||
return schema;
|
||||
}
|
||||
}
|
||||
|
||||
return current as JsonSchemaType;
|
||||
}
|
||||
|
||||
// For other types of references, return the original schema
|
||||
console.warn(`Unsupported $ref format: ${ref}`);
|
||||
return schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, nullable: true };
|
||||
}
|
||||
|
||||
// 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, nullable: true };
|
||||
}
|
||||
|
||||
// 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, nullable: true };
|
||||
}
|
||||
|
||||
// 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, nullable: true };
|
||||
}
|
||||
|
||||
// Handle anyOf with exactly array and null (FastMCP pattern)
|
||||
if (
|
||||
schema.anyOf &&
|
||||
schema.anyOf.length === 2 &&
|
||||
schema.anyOf.some((t) => (t as JsonSchemaType).type === "array") &&
|
||||
schema.anyOf.some((t) => (t as JsonSchemaType).type === "null")
|
||||
) {
|
||||
return { ...schema, type: "array", anyOf: undefined, nullable: true };
|
||||
}
|
||||
|
||||
// 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", nullable: true };
|
||||
}
|
||||
|
||||
// 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", nullable: true };
|
||||
}
|
||||
|
||||
// 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", nullable: true };
|
||||
}
|
||||
|
||||
// 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", nullable: true };
|
||||
}
|
||||
|
||||
return schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a field key into a human-readable label
|
||||
* @param key The field key to format
|
||||
* @returns A formatted label string
|
||||
*/
|
||||
export function formatFieldLabel(key: string): string {
|
||||
return key
|
||||
.replace(/([A-Z])/g, " $1") // Insert space before capital letters
|
||||
.replace(/_/g, " ") // Replace underscores with spaces
|
||||
.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;
|
||||
}
|
||||
Loading…
Reference in a new issue