@bryan-thompson/inspector-assessment-client 1.15.0 → 1.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/{OAuthCallback-BleN4Jjs.js → OAuthCallback-KwMiy-L3.js} +1 -1
- package/dist/assets/{OAuthDebugCallback-C__lzEyx.js → OAuthDebugCallback-hckdJlo3.js} +1 -1
- package/dist/assets/{index-CPXmfP9b.js → index-C89umkGV.js} +745 -4350
- package/dist/index.html +1 -1
- package/lib/lib/assessmentTypes.d.ts +123 -0
- package/lib/lib/assessmentTypes.d.ts.map +1 -1
- package/lib/lib/assessmentTypes.js +20 -0
- package/lib/lib/securityPatterns.d.ts +2 -2
- package/lib/lib/securityPatterns.d.ts.map +1 -1
- package/lib/lib/securityPatterns.js +290 -15
- package/lib/services/assessment/AssessmentOrchestrator.d.ts +67 -0
- package/lib/services/assessment/AssessmentOrchestrator.d.ts.map +1 -1
- package/lib/services/assessment/AssessmentOrchestrator.js +91 -1
- package/lib/services/assessment/ResponseValidator.d.ts +7 -34
- package/lib/services/assessment/ResponseValidator.d.ts.map +1 -1
- package/lib/services/assessment/ResponseValidator.js +100 -704
- package/lib/services/assessment/config/annotationPatterns.js +1 -1
- package/lib/services/assessment/lib/RequestHistoryAnalyzer.d.ts +67 -0
- package/lib/services/assessment/lib/RequestHistoryAnalyzer.d.ts.map +1 -0
- package/lib/services/assessment/lib/RequestHistoryAnalyzer.js +191 -0
- package/lib/services/assessment/lib/claudeCodeBridge.d.ts +1 -0
- package/lib/services/assessment/lib/claudeCodeBridge.d.ts.map +1 -1
- package/lib/services/assessment/lib/claudeCodeBridge.js +5 -4
- package/lib/services/assessment/modules/AuthenticationAssessor.d.ts +4 -0
- package/lib/services/assessment/modules/AuthenticationAssessor.d.ts.map +1 -1
- package/lib/services/assessment/modules/AuthenticationAssessor.js +97 -1
- package/lib/services/assessment/modules/CrossCapabilitySecurityAssessor.d.ts +39 -0
- package/lib/services/assessment/modules/CrossCapabilitySecurityAssessor.d.ts.map +1 -0
- package/lib/services/assessment/modules/CrossCapabilitySecurityAssessor.js +330 -0
- package/lib/services/assessment/modules/FunctionalityAssessor.d.ts.map +1 -1
- package/lib/services/assessment/modules/FunctionalityAssessor.js +46 -13
- package/lib/services/assessment/modules/MCPSpecComplianceAssessor.d.ts +5 -0
- package/lib/services/assessment/modules/MCPSpecComplianceAssessor.d.ts.map +1 -1
- package/lib/services/assessment/modules/MCPSpecComplianceAssessor.js +81 -0
- package/lib/services/assessment/modules/ManifestValidationAssessor.js +1 -1
- package/lib/services/assessment/modules/PromptAssessor.d.ts +30 -0
- package/lib/services/assessment/modules/PromptAssessor.d.ts.map +1 -0
- package/lib/services/assessment/modules/PromptAssessor.js +367 -0
- package/lib/services/assessment/modules/ResourceAssessor.d.ts +28 -0
- package/lib/services/assessment/modules/ResourceAssessor.d.ts.map +1 -0
- package/lib/services/assessment/modules/ResourceAssessor.js +296 -0
- package/lib/services/assessment/modules/SecurityAssessor.d.ts +4 -2
- package/lib/services/assessment/modules/SecurityAssessor.d.ts.map +1 -1
- package/lib/services/assessment/modules/SecurityAssessor.js +10 -41
- package/lib/services/assessment/modules/TemporalAssessor.d.ts +1 -0
- package/lib/services/assessment/modules/TemporalAssessor.d.ts.map +1 -1
- package/lib/services/assessment/modules/TemporalAssessor.js +35 -4
- package/lib/utils/jsonUtils.d.ts +68 -0
- package/lib/utils/jsonUtils.d.ts.map +1 -0
- package/lib/utils/jsonUtils.js +141 -0
- package/lib/utils/paramUtils.d.ts +11 -0
- package/lib/utils/paramUtils.d.ts.map +1 -0
- package/lib/utils/paramUtils.js +37 -0
- package/lib/utils/schemaUtils.d.ts +74 -0
- package/lib/utils/schemaUtils.d.ts.map +1 -0
- package/lib/utils/schemaUtils.js +268 -0
- package/package.json +1 -1
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Determines the specific data type of a JSON value
|
|
3
|
+
* @param value The JSON value to analyze
|
|
4
|
+
* @returns The specific data type including "array" and "null" as distinct types
|
|
5
|
+
*/
|
|
6
|
+
export function getDataType(value) {
|
|
7
|
+
if (Array.isArray(value))
|
|
8
|
+
return "array";
|
|
9
|
+
if (value === null)
|
|
10
|
+
return "null";
|
|
11
|
+
return typeof value;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Attempts to parse a string as JSON, only for objects and arrays
|
|
15
|
+
* @param str The string to parse
|
|
16
|
+
* @returns Object with success boolean and either parsed data or original string
|
|
17
|
+
*/
|
|
18
|
+
export function tryParseJson(str) {
|
|
19
|
+
const trimmed = str?.trim();
|
|
20
|
+
if (trimmed &&
|
|
21
|
+
!(trimmed.startsWith("{") && trimmed.endsWith("}")) &&
|
|
22
|
+
!(trimmed.startsWith("[") && trimmed.endsWith("]"))) {
|
|
23
|
+
return { success: false, data: str };
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
return { success: true, data: JSON.parse(str) };
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return { success: false, data: str };
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Updates a value at a specific path in a nested JSON structure
|
|
34
|
+
* @param obj The original JSON value
|
|
35
|
+
* @param path Array of keys/indices representing the path to the value
|
|
36
|
+
* @param value The new value to set
|
|
37
|
+
* @returns A new JSON value with the updated path
|
|
38
|
+
*/
|
|
39
|
+
export function updateValueAtPath(obj, path, value) {
|
|
40
|
+
if (path.length === 0)
|
|
41
|
+
return value;
|
|
42
|
+
if (obj === null || obj === undefined) {
|
|
43
|
+
obj = !isNaN(Number(path[0])) ? [] : {};
|
|
44
|
+
}
|
|
45
|
+
if (Array.isArray(obj)) {
|
|
46
|
+
return updateArray(obj, path, value);
|
|
47
|
+
}
|
|
48
|
+
else if (typeof obj === "object" && obj !== null) {
|
|
49
|
+
return updateObject(obj, path, value);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
console.error(`Cannot update path ${path.join(".")} in non-object/array value:`, obj);
|
|
53
|
+
return obj;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Updates an array at a specific path
|
|
58
|
+
*/
|
|
59
|
+
function updateArray(array, path, value) {
|
|
60
|
+
const [index, ...restPath] = path;
|
|
61
|
+
const arrayIndex = Number(index);
|
|
62
|
+
if (isNaN(arrayIndex)) {
|
|
63
|
+
console.error(`Invalid array index: ${index}`);
|
|
64
|
+
return array;
|
|
65
|
+
}
|
|
66
|
+
if (arrayIndex < 0) {
|
|
67
|
+
console.error(`Array index out of bounds: ${arrayIndex} < 0`);
|
|
68
|
+
return array;
|
|
69
|
+
}
|
|
70
|
+
let newArray = [];
|
|
71
|
+
for (let i = 0; i < array.length; i++) {
|
|
72
|
+
newArray[i] = i in array ? array[i] : null;
|
|
73
|
+
}
|
|
74
|
+
if (arrayIndex >= newArray.length) {
|
|
75
|
+
const extendedArray = new Array(arrayIndex).fill(null);
|
|
76
|
+
// Copy over the existing elements (now guaranteed to be dense)
|
|
77
|
+
for (let i = 0; i < newArray.length; i++) {
|
|
78
|
+
extendedArray[i] = newArray[i];
|
|
79
|
+
}
|
|
80
|
+
newArray = extendedArray;
|
|
81
|
+
}
|
|
82
|
+
if (restPath.length === 0) {
|
|
83
|
+
newArray[arrayIndex] = value;
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
newArray[arrayIndex] = updateValueAtPath(newArray[arrayIndex], restPath, value);
|
|
87
|
+
}
|
|
88
|
+
return newArray;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Updates an object at a specific path
|
|
92
|
+
*/
|
|
93
|
+
function updateObject(obj, path, value) {
|
|
94
|
+
const [key, ...restPath] = path;
|
|
95
|
+
// Validate object key
|
|
96
|
+
if (typeof key !== "string") {
|
|
97
|
+
console.error(`Invalid object key: ${key}`);
|
|
98
|
+
return obj;
|
|
99
|
+
}
|
|
100
|
+
const newObj = { ...obj };
|
|
101
|
+
if (restPath.length === 0) {
|
|
102
|
+
newObj[key] = value;
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
// Ensure key exists
|
|
106
|
+
if (!(key in newObj)) {
|
|
107
|
+
newObj[key] = {};
|
|
108
|
+
}
|
|
109
|
+
newObj[key] = updateValueAtPath(newObj[key], restPath, value);
|
|
110
|
+
}
|
|
111
|
+
return newObj;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Gets a value at a specific path in a nested JSON structure
|
|
115
|
+
* @param obj The JSON value to traverse
|
|
116
|
+
* @param path Array of keys/indices representing the path to the value
|
|
117
|
+
* @param defaultValue Value to return if path doesn't exist
|
|
118
|
+
* @returns The value at the path, or defaultValue if not found
|
|
119
|
+
*/
|
|
120
|
+
export function getValueAtPath(obj, path, defaultValue = null) {
|
|
121
|
+
if (path.length === 0)
|
|
122
|
+
return obj;
|
|
123
|
+
const [first, ...rest] = path;
|
|
124
|
+
if (obj === null || obj === undefined) {
|
|
125
|
+
return defaultValue;
|
|
126
|
+
}
|
|
127
|
+
if (Array.isArray(obj)) {
|
|
128
|
+
const index = Number(first);
|
|
129
|
+
if (isNaN(index) || index < 0 || index >= obj.length) {
|
|
130
|
+
return defaultValue;
|
|
131
|
+
}
|
|
132
|
+
return getValueAtPath(obj[index], rest, defaultValue);
|
|
133
|
+
}
|
|
134
|
+
if (typeof obj === "object" && obj !== null) {
|
|
135
|
+
if (!(first in obj)) {
|
|
136
|
+
return defaultValue;
|
|
137
|
+
}
|
|
138
|
+
return getValueAtPath(obj[first], rest, defaultValue);
|
|
139
|
+
}
|
|
140
|
+
return defaultValue;
|
|
141
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { JsonSchemaType } from "./jsonUtils.js";
|
|
2
|
+
/**
|
|
3
|
+
* Cleans parameters by removing undefined, null, and empty string values for optional fields
|
|
4
|
+
* while preserving all values for required fields and fields with explicit default values.
|
|
5
|
+
*
|
|
6
|
+
* @param params - The parameters object to clean
|
|
7
|
+
* @param schema - The JSON schema defining which fields are required
|
|
8
|
+
* @returns Cleaned parameters object with optional empty fields omitted
|
|
9
|
+
*/
|
|
10
|
+
export declare function cleanParams(params: Record<string, unknown>, schema: JsonSchemaType): Record<string, unknown>;
|
|
11
|
+
//# sourceMappingURL=paramUtils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paramUtils.d.ts","sourceRoot":"","sources":["../../src/utils/paramUtils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD;;;;;;;GAOG;AACH,wBAAgB,WAAW,CACzB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,MAAM,EAAE,cAAc,GACrB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA8BzB"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cleans parameters by removing undefined, null, and empty string values for optional fields
|
|
3
|
+
* while preserving all values for required fields and fields with explicit default values.
|
|
4
|
+
*
|
|
5
|
+
* @param params - The parameters object to clean
|
|
6
|
+
* @param schema - The JSON schema defining which fields are required
|
|
7
|
+
* @returns Cleaned parameters object with optional empty fields omitted
|
|
8
|
+
*/
|
|
9
|
+
export function cleanParams(params, schema) {
|
|
10
|
+
const cleaned = {};
|
|
11
|
+
const required = schema.required || [];
|
|
12
|
+
const properties = schema.properties || {};
|
|
13
|
+
for (const [key, value] of Object.entries(params)) {
|
|
14
|
+
const isFieldRequired = required.includes(key);
|
|
15
|
+
const fieldSchema = properties[key];
|
|
16
|
+
// Check if the field has an explicit default value
|
|
17
|
+
const hasDefault = fieldSchema && "default" in fieldSchema;
|
|
18
|
+
const defaultValue = hasDefault ? fieldSchema.default : undefined;
|
|
19
|
+
if (isFieldRequired) {
|
|
20
|
+
// Required fields: always include, even if empty string or falsy
|
|
21
|
+
cleaned[key] = value;
|
|
22
|
+
}
|
|
23
|
+
else if (hasDefault && value === defaultValue) {
|
|
24
|
+
// Field has a default value and current value matches it - preserve it
|
|
25
|
+
// This is important for cases like default: null
|
|
26
|
+
cleaned[key] = value;
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
// Optional fields: only include if they have meaningful values
|
|
30
|
+
if (value !== undefined && value !== "" && value !== null) {
|
|
31
|
+
cleaned[key] = value;
|
|
32
|
+
}
|
|
33
|
+
// Empty strings, undefined, null for optional fields → omit completely
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return cleaned;
|
|
37
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { JsonValue, JsonSchemaType } from "./jsonUtils.js";
|
|
2
|
+
import type { ValidateFunction } from "ajv";
|
|
3
|
+
import type { Tool, JSONRPCMessage } from "@modelcontextprotocol/sdk/types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Compiles and caches output schema validators for a list of tools
|
|
6
|
+
* Following the same pattern as SDK's Client.cacheToolOutputSchemas
|
|
7
|
+
* @param tools Array of tools that may have output schemas
|
|
8
|
+
*/
|
|
9
|
+
export declare function cacheToolOutputSchemas(tools: Tool[]): void;
|
|
10
|
+
/**
|
|
11
|
+
* Gets the cached output schema validator for a tool
|
|
12
|
+
* Following the same pattern as SDK's Client.getToolOutputValidator
|
|
13
|
+
* @param toolName Name of the tool
|
|
14
|
+
* @returns The compiled validator function, or undefined if not found
|
|
15
|
+
*/
|
|
16
|
+
export declare function getToolOutputValidator(toolName: string): ValidateFunction | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* Validates structured content against a tool's output schema
|
|
19
|
+
* Returns validation result with detailed error messages
|
|
20
|
+
* @param toolName Name of the tool
|
|
21
|
+
* @param structuredContent The structured content to validate
|
|
22
|
+
* @returns An object with isValid boolean and optional error message
|
|
23
|
+
*/
|
|
24
|
+
export declare function validateToolOutput(toolName: string, structuredContent: unknown): {
|
|
25
|
+
isValid: boolean;
|
|
26
|
+
error?: string;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Checks if a tool has an output schema
|
|
30
|
+
* @param toolName Name of the tool
|
|
31
|
+
* @returns true if the tool has an output schema
|
|
32
|
+
*/
|
|
33
|
+
export declare function hasOutputSchema(toolName: string): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Generates a default value based on a JSON schema type
|
|
36
|
+
* @param schema The JSON schema definition
|
|
37
|
+
* @param propertyName Optional property name for checking if it's required in parent schema
|
|
38
|
+
* @param parentSchema Optional parent schema to check required array
|
|
39
|
+
* @returns A default value matching the schema type
|
|
40
|
+
*/
|
|
41
|
+
export declare function generateDefaultValue(schema: JsonSchemaType, propertyName?: string, parentSchema?: JsonSchemaType): JsonValue;
|
|
42
|
+
/**
|
|
43
|
+
* Helper function to check if a property is required in a schema
|
|
44
|
+
* @param propertyName The name of the property to check
|
|
45
|
+
* @param schema The parent schema containing the required array
|
|
46
|
+
* @returns true if the property is required, false otherwise
|
|
47
|
+
*/
|
|
48
|
+
export declare function isPropertyRequired(propertyName: string, schema: JsonSchemaType): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Resolves $ref references in JSON schema
|
|
51
|
+
* @param schema The schema that may contain $ref
|
|
52
|
+
* @param rootSchema The root schema to resolve references against
|
|
53
|
+
* @returns The resolved schema without $ref
|
|
54
|
+
*/
|
|
55
|
+
export declare function resolveRef(schema: JsonSchemaType, rootSchema: JsonSchemaType): JsonSchemaType;
|
|
56
|
+
/**
|
|
57
|
+
* Normalizes union types (like string|null from FastMCP) to simple types for form rendering
|
|
58
|
+
* @param schema The JSON schema to normalize
|
|
59
|
+
* @returns A normalized schema or the original schema
|
|
60
|
+
*/
|
|
61
|
+
export declare function normalizeUnionType(schema: JsonSchemaType): JsonSchemaType;
|
|
62
|
+
/**
|
|
63
|
+
* Formats a field key into a human-readable label
|
|
64
|
+
* @param key The field key to format
|
|
65
|
+
* @returns A formatted label string
|
|
66
|
+
*/
|
|
67
|
+
export declare function formatFieldLabel(key: string): string;
|
|
68
|
+
/**
|
|
69
|
+
* Resolves `$ref` references in a JSON-RPC "elicitation/create" message's `requestedSchema` field
|
|
70
|
+
* @param message The JSON-RPC message that may contain $ref references
|
|
71
|
+
* @returns A new message with resolved $ref references, or the original message if no resolution is needed
|
|
72
|
+
*/
|
|
73
|
+
export declare function resolveRefsInMessage(message: JSONRPCMessage): JSONRPCMessage;
|
|
74
|
+
//# sourceMappingURL=schemaUtils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schemaUtils.d.ts","sourceRoot":"","sources":["../../src/utils/schemaUtils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAc,MAAM,aAAa,CAAC;AAEzE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,KAAK,CAAC;AAC5C,OAAO,KAAK,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAQ/E;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,IAAI,CAe1D;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,MAAM,GACf,gBAAgB,GAAG,SAAS,CAE9B;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,iBAAiB,EAAE,OAAO,GACzB;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAetC;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAEzD;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,cAAc,EACtB,YAAY,CAAC,EAAE,MAAM,EACrB,YAAY,CAAC,EAAE,cAAc,GAC5B,SAAS,CAkDX;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,cAAc,GACrB,OAAO,CAET;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CACxB,MAAM,EAAE,cAAc,EACtB,UAAU,EAAE,cAAc,GACzB,cAAc,CAiChB;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,cAAc,GAAG,cAAc,CA4FzE;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAKpD;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,cAAc,GAAG,cAAc,CA+B5E"}
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import Ajv from "ajv";
|
|
2
|
+
import { isJSONRPCRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
+
const ajv = new Ajv();
|
|
4
|
+
// Cache for compiled validators
|
|
5
|
+
const toolOutputValidators = new Map();
|
|
6
|
+
/**
|
|
7
|
+
* Compiles and caches output schema validators for a list of tools
|
|
8
|
+
* Following the same pattern as SDK's Client.cacheToolOutputSchemas
|
|
9
|
+
* @param tools Array of tools that may have output schemas
|
|
10
|
+
*/
|
|
11
|
+
export function cacheToolOutputSchemas(tools) {
|
|
12
|
+
toolOutputValidators.clear();
|
|
13
|
+
for (const tool of tools) {
|
|
14
|
+
if (tool.outputSchema) {
|
|
15
|
+
try {
|
|
16
|
+
const validator = ajv.compile(tool.outputSchema);
|
|
17
|
+
toolOutputValidators.set(tool.name, validator);
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
console.warn(`Failed to compile output schema for tool ${tool.name}:`, error);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Gets the cached output schema validator for a tool
|
|
27
|
+
* Following the same pattern as SDK's Client.getToolOutputValidator
|
|
28
|
+
* @param toolName Name of the tool
|
|
29
|
+
* @returns The compiled validator function, or undefined if not found
|
|
30
|
+
*/
|
|
31
|
+
export function getToolOutputValidator(toolName) {
|
|
32
|
+
return toolOutputValidators.get(toolName);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Validates structured content against a tool's output schema
|
|
36
|
+
* Returns validation result with detailed error messages
|
|
37
|
+
* @param toolName Name of the tool
|
|
38
|
+
* @param structuredContent The structured content to validate
|
|
39
|
+
* @returns An object with isValid boolean and optional error message
|
|
40
|
+
*/
|
|
41
|
+
export function validateToolOutput(toolName, structuredContent) {
|
|
42
|
+
const validator = getToolOutputValidator(toolName);
|
|
43
|
+
if (!validator) {
|
|
44
|
+
return { isValid: true }; // No validator means no schema to validate against
|
|
45
|
+
}
|
|
46
|
+
const isValid = validator(structuredContent);
|
|
47
|
+
if (!isValid) {
|
|
48
|
+
return {
|
|
49
|
+
isValid: false,
|
|
50
|
+
error: ajv.errorsText(validator.errors),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
return { isValid: true };
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Checks if a tool has an output schema
|
|
57
|
+
* @param toolName Name of the tool
|
|
58
|
+
* @returns true if the tool has an output schema
|
|
59
|
+
*/
|
|
60
|
+
export function hasOutputSchema(toolName) {
|
|
61
|
+
return toolOutputValidators.has(toolName);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Generates a default value based on a JSON schema type
|
|
65
|
+
* @param schema The JSON schema definition
|
|
66
|
+
* @param propertyName Optional property name for checking if it's required in parent schema
|
|
67
|
+
* @param parentSchema Optional parent schema to check required array
|
|
68
|
+
* @returns A default value matching the schema type
|
|
69
|
+
*/
|
|
70
|
+
export function generateDefaultValue(schema, propertyName, parentSchema) {
|
|
71
|
+
if ("default" in schema && schema.default !== undefined) {
|
|
72
|
+
return schema.default;
|
|
73
|
+
}
|
|
74
|
+
// Check if this property is required in the parent schema
|
|
75
|
+
const isRequired = propertyName && parentSchema
|
|
76
|
+
? isPropertyRequired(propertyName, parentSchema)
|
|
77
|
+
: false;
|
|
78
|
+
const isRootSchema = propertyName === undefined && parentSchema === undefined;
|
|
79
|
+
switch (schema.type) {
|
|
80
|
+
case "string":
|
|
81
|
+
return isRequired ? "" : undefined;
|
|
82
|
+
case "number":
|
|
83
|
+
case "integer":
|
|
84
|
+
return isRequired ? 0 : undefined;
|
|
85
|
+
case "boolean":
|
|
86
|
+
return isRequired ? false : undefined;
|
|
87
|
+
case "array":
|
|
88
|
+
return isRequired ? [] : undefined;
|
|
89
|
+
case "object": {
|
|
90
|
+
if (!schema.properties) {
|
|
91
|
+
return isRequired || isRootSchema ? {} : undefined;
|
|
92
|
+
}
|
|
93
|
+
const obj = {};
|
|
94
|
+
// Include required properties OR optional properties that declare a default
|
|
95
|
+
Object.entries(schema.properties).forEach(([key, prop]) => {
|
|
96
|
+
const hasExplicitDefault = "default" in prop && prop.default !== undefined;
|
|
97
|
+
if (isPropertyRequired(key, schema) || hasExplicitDefault) {
|
|
98
|
+
const value = generateDefaultValue(prop, key, schema);
|
|
99
|
+
if (value !== undefined) {
|
|
100
|
+
obj[key] = value;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
if (Object.keys(obj).length === 0) {
|
|
105
|
+
return isRequired || isRootSchema ? {} : undefined;
|
|
106
|
+
}
|
|
107
|
+
return obj;
|
|
108
|
+
}
|
|
109
|
+
case "null":
|
|
110
|
+
return null;
|
|
111
|
+
default:
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Helper function to check if a property is required in a schema
|
|
117
|
+
* @param propertyName The name of the property to check
|
|
118
|
+
* @param schema The parent schema containing the required array
|
|
119
|
+
* @returns true if the property is required, false otherwise
|
|
120
|
+
*/
|
|
121
|
+
export function isPropertyRequired(propertyName, schema) {
|
|
122
|
+
return schema.required?.includes(propertyName) ?? false;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Resolves $ref references in JSON schema
|
|
126
|
+
* @param schema The schema that may contain $ref
|
|
127
|
+
* @param rootSchema The root schema to resolve references against
|
|
128
|
+
* @returns The resolved schema without $ref
|
|
129
|
+
*/
|
|
130
|
+
export function resolveRef(schema, rootSchema) {
|
|
131
|
+
if (!("$ref" in schema) || !schema.$ref) {
|
|
132
|
+
return schema;
|
|
133
|
+
}
|
|
134
|
+
const ref = schema.$ref;
|
|
135
|
+
// Handle simple #/properties/name references
|
|
136
|
+
if (ref.startsWith("#/")) {
|
|
137
|
+
const path = ref.substring(2).split("/");
|
|
138
|
+
let current = rootSchema;
|
|
139
|
+
for (const segment of path) {
|
|
140
|
+
if (current &&
|
|
141
|
+
typeof current === "object" &&
|
|
142
|
+
current !== null &&
|
|
143
|
+
segment in current) {
|
|
144
|
+
current = current[segment];
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
// If reference cannot be resolved, return the original schema
|
|
148
|
+
console.warn(`Could not resolve $ref: ${ref}`);
|
|
149
|
+
return schema;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return current;
|
|
153
|
+
}
|
|
154
|
+
// For other types of references, return the original schema
|
|
155
|
+
console.warn(`Unsupported $ref format: ${ref}`);
|
|
156
|
+
return schema;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Normalizes union types (like string|null from FastMCP) to simple types for form rendering
|
|
160
|
+
* @param schema The JSON schema to normalize
|
|
161
|
+
* @returns A normalized schema or the original schema
|
|
162
|
+
*/
|
|
163
|
+
export function normalizeUnionType(schema) {
|
|
164
|
+
// Handle anyOf with exactly string and null (FastMCP pattern)
|
|
165
|
+
if (schema.anyOf &&
|
|
166
|
+
schema.anyOf.length === 2 &&
|
|
167
|
+
schema.anyOf.some((t) => t.type === "string") &&
|
|
168
|
+
schema.anyOf.some((t) => t.type === "null")) {
|
|
169
|
+
return { ...schema, type: "string", anyOf: undefined, nullable: true };
|
|
170
|
+
}
|
|
171
|
+
// Handle anyOf with exactly boolean and null (FastMCP pattern)
|
|
172
|
+
if (schema.anyOf &&
|
|
173
|
+
schema.anyOf.length === 2 &&
|
|
174
|
+
schema.anyOf.some((t) => t.type === "boolean") &&
|
|
175
|
+
schema.anyOf.some((t) => t.type === "null")) {
|
|
176
|
+
return { ...schema, type: "boolean", anyOf: undefined, nullable: true };
|
|
177
|
+
}
|
|
178
|
+
// Handle anyOf with exactly number and null (FastMCP pattern)
|
|
179
|
+
if (schema.anyOf &&
|
|
180
|
+
schema.anyOf.length === 2 &&
|
|
181
|
+
schema.anyOf.some((t) => t.type === "number") &&
|
|
182
|
+
schema.anyOf.some((t) => t.type === "null")) {
|
|
183
|
+
return { ...schema, type: "number", anyOf: undefined, nullable: true };
|
|
184
|
+
}
|
|
185
|
+
// Handle anyOf with exactly integer and null (FastMCP pattern)
|
|
186
|
+
if (schema.anyOf &&
|
|
187
|
+
schema.anyOf.length === 2 &&
|
|
188
|
+
schema.anyOf.some((t) => t.type === "integer") &&
|
|
189
|
+
schema.anyOf.some((t) => t.type === "null")) {
|
|
190
|
+
return { ...schema, type: "integer", anyOf: undefined, nullable: true };
|
|
191
|
+
}
|
|
192
|
+
// Handle anyOf with exactly array and null (FastMCP pattern)
|
|
193
|
+
if (schema.anyOf &&
|
|
194
|
+
schema.anyOf.length === 2 &&
|
|
195
|
+
schema.anyOf.some((t) => t.type === "array") &&
|
|
196
|
+
schema.anyOf.some((t) => t.type === "null")) {
|
|
197
|
+
return { ...schema, type: "array", anyOf: undefined, nullable: true };
|
|
198
|
+
}
|
|
199
|
+
// Handle array type with exactly string and null
|
|
200
|
+
if (Array.isArray(schema.type) &&
|
|
201
|
+
schema.type.length === 2 &&
|
|
202
|
+
schema.type.includes("string") &&
|
|
203
|
+
schema.type.includes("null")) {
|
|
204
|
+
return { ...schema, type: "string", nullable: true };
|
|
205
|
+
}
|
|
206
|
+
// Handle array type with exactly boolean and null
|
|
207
|
+
if (Array.isArray(schema.type) &&
|
|
208
|
+
schema.type.length === 2 &&
|
|
209
|
+
schema.type.includes("boolean") &&
|
|
210
|
+
schema.type.includes("null")) {
|
|
211
|
+
return { ...schema, type: "boolean", nullable: true };
|
|
212
|
+
}
|
|
213
|
+
// Handle array type with exactly number and null
|
|
214
|
+
if (Array.isArray(schema.type) &&
|
|
215
|
+
schema.type.length === 2 &&
|
|
216
|
+
schema.type.includes("number") &&
|
|
217
|
+
schema.type.includes("null")) {
|
|
218
|
+
return { ...schema, type: "number", nullable: true };
|
|
219
|
+
}
|
|
220
|
+
// Handle array type with exactly integer and null
|
|
221
|
+
if (Array.isArray(schema.type) &&
|
|
222
|
+
schema.type.length === 2 &&
|
|
223
|
+
schema.type.includes("integer") &&
|
|
224
|
+
schema.type.includes("null")) {
|
|
225
|
+
return { ...schema, type: "integer", nullable: true };
|
|
226
|
+
}
|
|
227
|
+
return schema;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Formats a field key into a human-readable label
|
|
231
|
+
* @param key The field key to format
|
|
232
|
+
* @returns A formatted label string
|
|
233
|
+
*/
|
|
234
|
+
export function formatFieldLabel(key) {
|
|
235
|
+
return key
|
|
236
|
+
.replace(/([A-Z])/g, " $1") // Insert space before capital letters
|
|
237
|
+
.replace(/_/g, " ") // Replace underscores with spaces
|
|
238
|
+
.replace(/^\w/, (c) => c.toUpperCase()); // Capitalize first letter
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Resolves `$ref` references in a JSON-RPC "elicitation/create" message's `requestedSchema` field
|
|
242
|
+
* @param message The JSON-RPC message that may contain $ref references
|
|
243
|
+
* @returns A new message with resolved $ref references, or the original message if no resolution is needed
|
|
244
|
+
*/
|
|
245
|
+
export function resolveRefsInMessage(message) {
|
|
246
|
+
if (!isJSONRPCRequest(message) || !message.params?.requestedSchema) {
|
|
247
|
+
return message;
|
|
248
|
+
}
|
|
249
|
+
const requestedSchema = message.params.requestedSchema;
|
|
250
|
+
if (!requestedSchema?.properties) {
|
|
251
|
+
return message;
|
|
252
|
+
}
|
|
253
|
+
const resolvedMessage = {
|
|
254
|
+
...message,
|
|
255
|
+
params: {
|
|
256
|
+
...message.params,
|
|
257
|
+
requestedSchema: {
|
|
258
|
+
...requestedSchema,
|
|
259
|
+
properties: Object.fromEntries(Object.entries(requestedSchema.properties).map(([key, propSchema]) => {
|
|
260
|
+
const resolved = resolveRef(propSchema, requestedSchema);
|
|
261
|
+
const normalized = normalizeUnionType(resolved);
|
|
262
|
+
return [key, normalized];
|
|
263
|
+
})),
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
};
|
|
267
|
+
return resolvedMessage;
|
|
268
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bryan-thompson/inspector-assessment-client",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.16.0",
|
|
4
4
|
"description": "Client-side application for the Enhanced MCP Inspector with assessment capabilities",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Bryan Thompson <bryan@triepod.ai>",
|