From 47c042f55b2b1ddfc27ce62faa224752364a934d Mon Sep 17 00:00:00 2001 From: CD Cabrera Date: Mon, 2 Mar 2026 15:37:16 -0500 Subject: [PATCH 1/5] feat: nodejs assertions base --- src/__tests__/server.assertions.test.ts | 226 ++++++++++++++++++++++++ src/server.assertions.ts | 141 +++++++++++++++ 2 files changed, 367 insertions(+) create mode 100644 src/__tests__/server.assertions.test.ts create mode 100644 src/server.assertions.ts diff --git a/src/__tests__/server.assertions.test.ts b/src/__tests__/server.assertions.test.ts new file mode 100644 index 00000000..623b2137 --- /dev/null +++ b/src/__tests__/server.assertions.test.ts @@ -0,0 +1,226 @@ +import { + assertInput, + assertInputString, + assertInputStringLength, + assertInputStringArrayEntryLength, + assertInputStringNumberEnumLike +} from '../server.assertions'; + +describe('assertInput', () => { + it.each([ + { + description: 'basic string validation', + param: '', + condition: (value: any) => typeof value === 'string' && value.trim().length > 0 + }, + { + description: 'pattern in string validation', + param: 'patternfly://lorem-ipsum', + condition: (value: any) => new RegExp('patternfly://', 'i').test(value) + }, + { + description: 'array entry length validation', + param: ['lorem', 'ipsum'], + condition: (value: any) => Array.isArray(value) && value.length > 2 + } + ])('should throw an error for validation, $description', ({ param, condition }) => { + const errorMessage = `Lorem ipsum error message for ${param} validation.`; + + expect(() => assertInput( + condition, + errorMessage + )).toThrow(errorMessage); + }); +}); + +describe('assertInputString', () => { + it.each([ + { + description: 'empty string', + param: '' + }, + { + description: 'undefined', + param: undefined + }, + { + description: 'number', + param: 1 + }, + { + description: 'null', + param: null + } + ])('should throw an error for validation, $description', ({ param }) => { + const errorMessage = '"Input" must be a string'; + + expect(() => assertInputString( + param + )).toThrow(errorMessage); + }); +}); + +describe('assertInputStringLength', () => { + it.each([ + { + description: 'empty string', + param: '' + }, + { + description: 'undefined', + param: undefined + }, + { + description: 'number', + param: 1 + }, + { + description: 'null', + param: null + }, + { + description: 'max', + param: 'lorem ipsum', + options: { max: 5 } + }, + { + description: 'min', + param: 'lorem ipsum', + options: { min: 15 } + }, + { + description: 'max and min', + param: 'lorem ipsum', + options: { min: 1, max: 10 } + }, + { + description: 'max and min and display name', + param: 'lorem ipsum', + options: { min: 1, max: 10, inputDisplayName: 'lorem ipsum' } + }, + { + description: 'max and min and description', + param: 'lorem ipsum', + options: { min: 1, max: 10, message: 'dolor sit amet, consectetur adipiscing elit.' } + } + ])('should throw an error for validation, $description', ({ param, options }) => { + const errorMessage = options?.message || `"${options?.inputDisplayName || 'Input'}" must be a string`; + + expect(() => assertInputStringLength( + param, + { min: 1, max: 100, ...options } as any + )).toThrow(errorMessage); + }); +}); + +describe('assertInputStringArrayEntryLength', () => { + it.each([ + { + description: 'empty string', + param: '' + }, + { + description: 'undefined', + param: undefined + }, + { + description: 'number', + param: 1 + }, + { + description: 'null', + param: null + }, + { + description: 'max', + param: ['lorem ipsum'], + options: { max: 5 } + }, + { + description: 'min', + param: ['lorem ipsum'], + options: { min: 15 } + }, + { + description: 'max and min', + param: ['lorem ipsum'], + options: { min: 1, max: 10 } + }, + { + description: 'max and min and display name', + param: ['lorem ipsum'], + options: { min: 1, max: 10, inputDisplayName: 'lorem ipsum' } + }, + { + description: 'max and min and description', + param: ['lorem ipsum'], + options: { min: 1, max: 10, message: 'dolor sit amet, consectetur adipiscing elit.' } + } + ])('should throw an error for validation, $description', ({ param, options }) => { + const errorMessage = options?.message || `"${options?.inputDisplayName || 'Input'}" array must contain strings`; + + expect(() => assertInputStringArrayEntryLength( + param as any, + { min: 1, max: 100, ...options } as any + )).toThrow(errorMessage); + }); +}); + +describe('assertInputStringNumberEnumLike', () => { + it.each([ + { + description: 'empty string', + param: '', + compare: [2, 3] + }, + { + description: 'undefined', + param: undefined, + compare: [2, 3] + }, + { + description: 'null', + param: null, + compare: [2, 3] + }, + { + description: 'number', + param: 1, + compare: [2, 3] + }, + { + description: 'string', + param: 'lorem ipsum', + compare: ['amet', 'dolor sit'] + }, + { + description: 'string and display name', + param: 'lorem ipsum', + compare: ['amet', 'dolor sit'], + options: { inputDisplayName: 'lorem ipsum' } + }, + { + description: 'string and description', + param: 'lorem ipsum', + compare: [1, 2], + options: { message: 'dolor sit amet, consectetur adipiscing elit.' } + } + ])('should throw an error for validation, $description', ({ param, compare, options }) => { + const errorMessage = options?.message || `"${options?.inputDisplayName || 'Input'}" must be one of the following values`; + + expect(() => assertInputStringNumberEnumLike( + param as any, + compare as any, + { ...options } as any + )).toThrow(errorMessage); + }); + + it('should throw an internal error for validation when missing comparison values', () => { + const errorMessage = 'List of allowed values is empty'; + + expect(() => assertInputStringNumberEnumLike( + 1, + [] + )).toThrow(errorMessage); + }); +}); diff --git a/src/server.assertions.ts b/src/server.assertions.ts new file mode 100644 index 00000000..80099d13 --- /dev/null +++ b/src/server.assertions.ts @@ -0,0 +1,141 @@ +import assert from 'node:assert'; +import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js'; + +/** + * MCP assert. Centralizes and throws an error if the validation fails. + * + * @param condition - The function or condition to be validated. + * @param message - The error message, or function that returns the error message, to be thrown if the validation fails. + * @param {McpError} code - The error code to be thrown if the validation fails. Defaults to `ErrorCode.InvalidParams`. + * + * @throws {McpError} Throws the provided error message if the input validation fails. + */ +const mcpAssert = (condition: unknown, message: string | (() => string), code: ErrorCode = ErrorCode.InvalidParams) => { + try { + const result = typeof condition === 'function' ? condition() : condition; + const resultMessage = typeof message === 'function' ? message() : message; + + assert.ok(result, resultMessage); + } catch (error) { + throw new McpError(code, (error as Error).message); + } +}; + +/** + * General purpose input assert/validation function. + * + * @alias mcpAssert + * + * @param condition - The function or condition to be validated. + * @param message - The error message, or function that returns the error message, to be thrown if the validation fails. + * @param {McpError} [code] - The error code to be thrown if the validation fails. Defaults to `ErrorCode.InvalidParams`. + * + * @throws {McpError} Throws the provided error message if the input validation fails. + */ +function assertInput( + condition: unknown, + message: string | (() => string), + code?: ErrorCode +): asserts condition { + mcpAssert(condition, message, code); +} + +/** + * Assert/validate if the input is a string. + * + * @param input - The input value + * @param options - Validation options + * @param options.inputDisplayName - Display name for the input. Used in the default error message. Defaults to 'Input'. + * @param options.message - Custom error message. A default error message with optional `inputDisplayName` is generated if not provided. + * + * @throws McpError If input is not a string OR is empty + */ +function assertInputString( + input: unknown, + { inputDisplayName, message }: { inputDisplayName?: string, message?: string } = {} +): asserts input is string { + const isValid = typeof input === 'string' && input.trim().length > 0; + + mcpAssert(isValid, message || `"${inputDisplayName || 'Input'}" must be a string`); +} + +/** + * Assert/validate if input is a string and meets length requirements. + * + * @param input - The input string + * @param options - Validation options + * @param options.max - Maximum length of the string + * @param options.min - Minimum length of the string + * @param options.inputDisplayName - Display name for the input. Used in the default error message. Defaults to 'Input'. + * @param options.message - Error description. A default error message with optional `inputDisplayName` is generated if not provided. + * + * @throws McpError If input is not a string OR does not meet length requirements + */ +function assertInputStringLength( + input: unknown, + { max, min, inputDisplayName, message }: { max: number, min: number, inputDisplayName?: string, message?: string } +): asserts input is string { + const isValid = typeof input === 'string' && input.length <= max && input.length >= min; + + mcpAssert(isValid, message || `"${inputDisplayName || 'Input'}" must be a string from "${min}" to "${max}" characters`); +} + +/** + * Assert/validate if input array entries are strings and have length. + * + * @param input - Array of strings + * @param options - Validation options + * @param options.max - Maximum length of each string in the array + * @param options.min - Minimum length of each string in the array + * @param options.inputDisplayName - Display name for the input. Used in the default error messages. Defaults to 'Input'. + * @param options.message - Error description. A default error message with optional `inputDisplayName` is generated if not provided. + * + * @throws McpError If input is not an array of strings OR does not meet length requirements + */ +function assertInputStringArrayEntryLength( + input: unknown[], + { max, min, inputDisplayName, message }: { max: number, min: number, inputDisplayName?: string, message?: string } +): asserts input is string[] { + const isValid = Array.isArray(input) && input.every(entry => typeof entry === 'string' && entry.length <= max && entry.length >= min); + + mcpAssert(isValid, message || `"${inputDisplayName || 'Input'}" array must contain strings from "${min}" to "${max}" characters`); +} + +/** + * Assert/validate if input is a string or number AND is one of the allowed values. + * + * @param input - The input value + * @param values - List of allowed values + * @param options - Validation options + * @param options.inputDisplayName - Display name for the input. Used in the default error messages. Defaults to 'Input'. + * @param options.message - Error description. A default error message with optional `inputDisplayName` is generated if not provided. + * + * @throws McpError If input is not a string or number OR is not one of the allowed values + */ +function assertInputStringNumberEnumLike( + input: unknown, + values: unknown[], + { inputDisplayName, message }: { inputDisplayName?: string, message?: string } = {} +): asserts input is unknown[] { + const hasArrayWithLength = Array.isArray(values) && values.length; + let updatedDescription = message || `"${inputDisplayName || 'Input'}" must be one of the following values: ${values.join(', ')}`; + let errorCode = ErrorCode.InvalidParams; + + if (!hasArrayWithLength) { + errorCode = ErrorCode.InternalError; + updatedDescription = `Unable to confirm "${inputDisplayName || 'input'}." List of allowed values is empty or undefined.`; + } + + const isStringOrNumber = (typeof input === 'string' && typeof input.trim().length) || typeof input === 'number'; + const isValid = isStringOrNumber && hasArrayWithLength && values.includes(input); + + mcpAssert(isValid, updatedDescription, errorCode); +} + +export { + assertInput, + assertInputString, + assertInputStringLength, + assertInputStringArrayEntryLength, + assertInputStringNumberEnumLike +}; From 1275c732784c584a906c87de06f4c2a0508b9e42 Mon Sep 17 00:00:00 2001 From: CD Cabrera Date: Tue, 3 Mar 2026 14:31:15 -0500 Subject: [PATCH 2/5] fix: review update --- src/__tests__/server.assertions.test.ts | 89 ++++++++++++------------- src/server.assertions.ts | 65 +++++++++--------- 2 files changed, 77 insertions(+), 77 deletions(-) diff --git a/src/__tests__/server.assertions.test.ts b/src/__tests__/server.assertions.test.ts index 623b2137..7d740a4d 100644 --- a/src/__tests__/server.assertions.test.ts +++ b/src/__tests__/server.assertions.test.ts @@ -10,21 +10,18 @@ describe('assertInput', () => { it.each([ { description: 'basic string validation', - param: '', - condition: (value: any) => typeof value === 'string' && value.trim().length > 0 + condition: ' '.trim().length > 0 }, { - description: 'pattern in string validation', - param: 'patternfly://lorem-ipsum', - condition: (value: any) => new RegExp('patternfly://', 'i').test(value) + description: 'pattern in string validation with callback format', + condition: () => new RegExp('patternfly://', 'i').test('fly://lorem-ipsum') }, { description: 'array entry length validation', - param: ['lorem', 'ipsum'], - condition: (value: any) => Array.isArray(value) && value.length > 2 + condition: Array.isArray(['lorem']) && ['lorem'].length > 2 } - ])('should throw an error for validation, $description', ({ param, condition }) => { - const errorMessage = `Lorem ipsum error message for ${param} validation.`; + ])('should throw an error for validation, $description', ({ condition }) => { + const errorMessage = `Lorem ipsum error message for validation.`; expect(() => assertInput( condition, @@ -37,25 +34,25 @@ describe('assertInputString', () => { it.each([ { description: 'empty string', - param: '' + input: '' }, { description: 'undefined', - param: undefined + input: undefined }, { description: 'number', - param: 1 + input: 1 }, { description: 'null', - param: null + input: null } - ])('should throw an error for validation, $description', ({ param }) => { + ])('should throw an error for validation, $description', ({ input }) => { const errorMessage = '"Input" must be a string'; expect(() => assertInputString( - param + input )).toThrow(errorMessage); }); }); @@ -64,50 +61,50 @@ describe('assertInputStringLength', () => { it.each([ { description: 'empty string', - param: '' + input: '' }, { description: 'undefined', - param: undefined + input: undefined }, { description: 'number', - param: 1 + input: 1 }, { description: 'null', - param: null + input: null }, { description: 'max', - param: 'lorem ipsum', + input: 'lorem ipsum', options: { max: 5 } }, { description: 'min', - param: 'lorem ipsum', + input: 'lorem ipsum', options: { min: 15 } }, { description: 'max and min', - param: 'lorem ipsum', + input: 'lorem ipsum', options: { min: 1, max: 10 } }, { description: 'max and min and display name', - param: 'lorem ipsum', + input: 'lorem ipsum', options: { min: 1, max: 10, inputDisplayName: 'lorem ipsum' } }, { description: 'max and min and description', - param: 'lorem ipsum', + input: 'lorem ipsum', options: { min: 1, max: 10, message: 'dolor sit amet, consectetur adipiscing elit.' } } - ])('should throw an error for validation, $description', ({ param, options }) => { + ])('should throw an error for validation, $description', ({ input, options }) => { const errorMessage = options?.message || `"${options?.inputDisplayName || 'Input'}" must be a string`; expect(() => assertInputStringLength( - param, + input, { min: 1, max: 100, ...options } as any )).toThrow(errorMessage); }); @@ -117,50 +114,50 @@ describe('assertInputStringArrayEntryLength', () => { it.each([ { description: 'empty string', - param: '' + input: '' }, { description: 'undefined', - param: undefined + input: undefined }, { description: 'number', - param: 1 + input: 1 }, { description: 'null', - param: null + input: null }, { description: 'max', - param: ['lorem ipsum'], + input: ['lorem ipsum'], options: { max: 5 } }, { description: 'min', - param: ['lorem ipsum'], + input: ['lorem ipsum'], options: { min: 15 } }, { description: 'max and min', - param: ['lorem ipsum'], + input: ['lorem ipsum'], options: { min: 1, max: 10 } }, { description: 'max and min and display name', - param: ['lorem ipsum'], + input: ['lorem ipsum'], options: { min: 1, max: 10, inputDisplayName: 'lorem ipsum' } }, { description: 'max and min and description', - param: ['lorem ipsum'], + input: ['lorem ipsum'], options: { min: 1, max: 10, message: 'dolor sit amet, consectetur adipiscing elit.' } } - ])('should throw an error for validation, $description', ({ param, options }) => { + ])('should throw an error for validation, $description', ({ input, options }) => { const errorMessage = options?.message || `"${options?.inputDisplayName || 'Input'}" array must contain strings`; expect(() => assertInputStringArrayEntryLength( - param as any, + input as any, { min: 1, max: 100, ...options } as any )).toThrow(errorMessage); }); @@ -170,46 +167,46 @@ describe('assertInputStringNumberEnumLike', () => { it.each([ { description: 'empty string', - param: '', + input: '', compare: [2, 3] }, { description: 'undefined', - param: undefined, + input: undefined, compare: [2, 3] }, { description: 'null', - param: null, + input: null, compare: [2, 3] }, { description: 'number', - param: 1, + input: 1, compare: [2, 3] }, { description: 'string', - param: 'lorem ipsum', + input: 'lorem ipsum', compare: ['amet', 'dolor sit'] }, { description: 'string and display name', - param: 'lorem ipsum', + input: 'lorem ipsum', compare: ['amet', 'dolor sit'], options: { inputDisplayName: 'lorem ipsum' } }, { description: 'string and description', - param: 'lorem ipsum', + input: 'lorem ipsum', compare: [1, 2], options: { message: 'dolor sit amet, consectetur adipiscing elit.' } } - ])('should throw an error for validation, $description', ({ param, compare, options }) => { + ])('should throw an error for validation, $description', ({ input, compare, options }) => { const errorMessage = options?.message || `"${options?.inputDisplayName || 'Input'}" must be one of the following values`; expect(() => assertInputStringNumberEnumLike( - param as any, + input as any, compare as any, { ...options } as any )).toThrow(errorMessage); diff --git a/src/server.assertions.ts b/src/server.assertions.ts index 80099d13..5228b558 100644 --- a/src/server.assertions.ts +++ b/src/server.assertions.ts @@ -4,11 +4,11 @@ import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js'; /** * MCP assert. Centralizes and throws an error if the validation fails. * - * @param condition - The function or condition to be validated. - * @param message - The error message, or function that returns the error message, to be thrown if the validation fails. - * @param {McpError} code - The error code to be thrown if the validation fails. Defaults to `ErrorCode.InvalidParams`. + * @param condition - Function or condition to be validated. + * @param message - Thrown error message, or function, that returns the error message. + * @param {ErrorCode} [code] - Thrown error code when validation fails. Defaults to `ErrorCode.InvalidParams`. * - * @throws {McpError} Throws the provided error message if the input validation fails. + * @throws {McpError} Throw the provided error message and code on failure. */ const mcpAssert = (condition: unknown, message: string | (() => string), code: ErrorCode = ErrorCode.InvalidParams) => { try { @@ -26,11 +26,11 @@ const mcpAssert = (condition: unknown, message: string | (() => string), code: E * * @alias mcpAssert * - * @param condition - The function or condition to be validated. - * @param message - The error message, or function that returns the error message, to be thrown if the validation fails. - * @param {McpError} [code] - The error code to be thrown if the validation fails. Defaults to `ErrorCode.InvalidParams`. + * @param condition - Function or condition to be validated. + * @param message - Thrown error message, or function, that returns the error message. + * @param {ErrorCode} [code] - Thrown error code when validation fails. Defaults to `ErrorCode.InvalidParams`. * - * @throws {McpError} Throws the provided error message if the input validation fails. + * @throws {McpError} Throw the provided error message and code on failure. */ function assertInput( condition: unknown, @@ -43,10 +43,10 @@ function assertInput( /** * Assert/validate if the input is a string. * - * @param input - The input value - * @param options - Validation options - * @param options.inputDisplayName - Display name for the input. Used in the default error message. Defaults to 'Input'. - * @param options.message - Custom error message. A default error message with optional `inputDisplayName` is generated if not provided. + * @param input - Input value + * @param [options] - Validation options + * @param [options.inputDisplayName] - Display name for the input. Used in the default error message. Defaults to 'Input'. + * @param [options.message] - Custom error message. A default error message with optional `inputDisplayName` is generated if not provided. * * @throws McpError If input is not a string OR is empty */ @@ -60,14 +60,14 @@ function assertInputString( } /** - * Assert/validate if input is a string and meets length requirements. + * Assert/validate if the input is a string and meets length requirements. * - * @param input - The input string + * @param input - Input string * @param options - Validation options - * @param options.max - Maximum length of the string - * @param options.min - Minimum length of the string - * @param options.inputDisplayName - Display name for the input. Used in the default error message. Defaults to 'Input'. - * @param options.message - Error description. A default error message with optional `inputDisplayName` is generated if not provided. + * @param options.max - Maximum length of the string. `Required` + * @param options.min - Minimum length of the string. `Required` + * @param [options.inputDisplayName] - Display name for the input. Used in the default error message. Defaults to 'Input'. + * @param [options.message] - Error description. A default error message with optional `inputDisplayName` is generated if not provided. * * @throws McpError If input is not a string OR does not meet length requirements */ @@ -85,10 +85,10 @@ function assertInputStringLength( * * @param input - Array of strings * @param options - Validation options - * @param options.max - Maximum length of each string in the array - * @param options.min - Minimum length of each string in the array - * @param options.inputDisplayName - Display name for the input. Used in the default error messages. Defaults to 'Input'. - * @param options.message - Error description. A default error message with optional `inputDisplayName` is generated if not provided. + * @param options.max - Maximum length of each string in the array. `Required` + * @param options.min - Minimum length of each string in the array. `Required` + * @param [options.inputDisplayName] - Display name for the input. Used in the default error messages. Defaults to 'Input'. + * @param [options.message] - Error description. A default error message with optional `inputDisplayName` is generated if not provided. * * @throws McpError If input is not an array of strings OR does not meet length requirements */ @@ -106,9 +106,9 @@ function assertInputStringArrayEntryLength( * * @param input - The input value * @param values - List of allowed values - * @param options - Validation options - * @param options.inputDisplayName - Display name for the input. Used in the default error messages. Defaults to 'Input'. - * @param options.message - Error description. A default error message with optional `inputDisplayName` is generated if not provided. + * @param [options] - Validation options + * @param [options.inputDisplayName] - Display name for the input. Used in the default error messages. Defaults to 'Input'. + * @param [options.message] - Error description. A default error message with optional `inputDisplayName` is generated if not provided. * * @throws McpError If input is not a string or number OR is not one of the allowed values */ @@ -116,17 +116,20 @@ function assertInputStringNumberEnumLike( input: unknown, values: unknown[], { inputDisplayName, message }: { inputDisplayName?: string, message?: string } = {} -): asserts input is unknown[] { - const hasArrayWithLength = Array.isArray(values) && values.length; - let updatedDescription = message || `"${inputDisplayName || 'Input'}" must be one of the following values: ${values.join(', ')}`; - let errorCode = ErrorCode.InvalidParams; +): asserts input is string | number { + const hasArrayWithLength = Array.isArray(values) && values.length > 0; + let updatedDescription; + let errorCode; - if (!hasArrayWithLength) { + if (hasArrayWithLength) { + errorCode = ErrorCode.InvalidParams; + updatedDescription = message || `"${inputDisplayName || 'Input'}" must be one of the following values: ${values.join(', ')}`; + } else { errorCode = ErrorCode.InternalError; updatedDescription = `Unable to confirm "${inputDisplayName || 'input'}." List of allowed values is empty or undefined.`; } - const isStringOrNumber = (typeof input === 'string' && typeof input.trim().length) || typeof input === 'number'; + const isStringOrNumber = (typeof input === 'string' && input.trim().length > 0) || typeof input === 'number'; const isValid = isStringOrNumber && hasArrayWithLength && values.includes(input); mcpAssert(isValid, updatedDescription, errorCode); From 363c47e3d8c66ba0b2edf39a50b192dfc768e069 Mon Sep 17 00:00:00 2001 From: CD Cabrera Date: Tue, 3 Mar 2026 14:53:29 -0500 Subject: [PATCH 3/5] fix: review update --- src/__tests__/server.assertions.test.ts | 16 ++++++++-------- src/server.assertions.ts | 22 +++++++++++----------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/__tests__/server.assertions.test.ts b/src/__tests__/server.assertions.test.ts index 7d740a4d..eb0852ee 100644 --- a/src/__tests__/server.assertions.test.ts +++ b/src/__tests__/server.assertions.test.ts @@ -49,7 +49,7 @@ describe('assertInputString', () => { input: null } ])('should throw an error for validation, $description', ({ input }) => { - const errorMessage = '"Input" must be a string'; + const errorMessage = '"Input" must be a non-empty string'; expect(() => assertInputString( input @@ -101,11 +101,11 @@ describe('assertInputStringLength', () => { options: { min: 1, max: 10, message: 'dolor sit amet, consectetur adipiscing elit.' } } ])('should throw an error for validation, $description', ({ input, options }) => { - const errorMessage = options?.message || `"${options?.inputDisplayName || 'Input'}" must be a string`; + const errorMessage = options?.message || `"${options?.inputDisplayName || 'Input'}" must be a string from`; expect(() => assertInputStringLength( input, - { min: 1, max: 100, ...options } as any + { min: 1, max: 100, ...options } )).toThrow(errorMessage); }); }); @@ -157,8 +157,8 @@ describe('assertInputStringArrayEntryLength', () => { const errorMessage = options?.message || `"${options?.inputDisplayName || 'Input'}" array must contain strings`; expect(() => assertInputStringArrayEntryLength( - input as any, - { min: 1, max: 100, ...options } as any + input, + { min: 1, max: 100, ...options } )).toThrow(errorMessage); }); }); @@ -206,9 +206,9 @@ describe('assertInputStringNumberEnumLike', () => { const errorMessage = options?.message || `"${options?.inputDisplayName || 'Input'}" must be one of the following values`; expect(() => assertInputStringNumberEnumLike( - input as any, - compare as any, - { ...options } as any + input, + compare, + { ...options } )).toThrow(errorMessage); }); diff --git a/src/server.assertions.ts b/src/server.assertions.ts index 5228b558..73253380 100644 --- a/src/server.assertions.ts +++ b/src/server.assertions.ts @@ -41,7 +41,7 @@ function assertInput( } /** - * Assert/validate if the input is a string. + * Assert/validate if the input is a non-empty string. * * @param input - Input value * @param [options] - Validation options @@ -56,11 +56,11 @@ function assertInputString( ): asserts input is string { const isValid = typeof input === 'string' && input.trim().length > 0; - mcpAssert(isValid, message || `"${inputDisplayName || 'Input'}" must be a string`); + mcpAssert(isValid, message || `"${inputDisplayName || 'Input'}" must be a non-empty string`); } /** - * Assert/validate if the input is a string and meets length requirements. + * Assert/validate if the input is a string, non-empty, and meets min and max length requirements. * * @param input - Input string * @param options - Validation options @@ -75,13 +75,13 @@ function assertInputStringLength( input: unknown, { max, min, inputDisplayName, message }: { max: number, min: number, inputDisplayName?: string, message?: string } ): asserts input is string { - const isValid = typeof input === 'string' && input.length <= max && input.length >= min; + const isValid = typeof input === 'string' && input.trim().length <= max && input.trim().length >= min; - mcpAssert(isValid, message || `"${inputDisplayName || 'Input'}" must be a string from "${min}" to "${max}" characters`); + mcpAssert(isValid, message || `"${inputDisplayName || 'Input'}" must be a string from ${min} to ${max} characters`); } /** - * Assert/validate if input array entries are strings and have length. + * Assert/validate if input array entries are strings and have min and max length. * * @param input - Array of strings * @param options - Validation options @@ -93,12 +93,12 @@ function assertInputStringLength( * @throws McpError If input is not an array of strings OR does not meet length requirements */ function assertInputStringArrayEntryLength( - input: unknown[], + input: unknown, { max, min, inputDisplayName, message }: { max: number, min: number, inputDisplayName?: string, message?: string } ): asserts input is string[] { - const isValid = Array.isArray(input) && input.every(entry => typeof entry === 'string' && entry.length <= max && entry.length >= min); + const isValid = Array.isArray(input) && input.every(entry => typeof entry === 'string' && entry.trim().length <= max && entry.trim().length >= min); - mcpAssert(isValid, message || `"${inputDisplayName || 'Input'}" array must contain strings from "${min}" to "${max}" characters`); + mcpAssert(isValid, message || `"${inputDisplayName || 'Input'}" array must contain strings from ${min} to ${max} characters`); } /** @@ -114,7 +114,7 @@ function assertInputStringArrayEntryLength( */ function assertInputStringNumberEnumLike( input: unknown, - values: unknown[], + values: unknown, { inputDisplayName, message }: { inputDisplayName?: string, message?: string } = {} ): asserts input is string | number { const hasArrayWithLength = Array.isArray(values) && values.length > 0; @@ -129,7 +129,7 @@ function assertInputStringNumberEnumLike( updatedDescription = `Unable to confirm "${inputDisplayName || 'input'}." List of allowed values is empty or undefined.`; } - const isStringOrNumber = (typeof input === 'string' && input.trim().length > 0) || typeof input === 'number'; + const isStringOrNumber = typeof input === 'string' || typeof input === 'number'; const isValid = isStringOrNumber && hasArrayWithLength && values.includes(input); mcpAssert(isValid, updatedDescription, errorCode); From aedf80edbecd6d151e03bc50726e0c0f976fa614 Mon Sep 17 00:00:00 2001 From: CD Cabrera Date: Tue, 3 Mar 2026 15:10:47 -0500 Subject: [PATCH 4/5] fix: review update --- src/__tests__/server.assertions.test.ts | 20 ++++++++++++++++++++ src/server.assertions.ts | 10 +++++----- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/__tests__/server.assertions.test.ts b/src/__tests__/server.assertions.test.ts index eb0852ee..9d0c714e 100644 --- a/src/__tests__/server.assertions.test.ts +++ b/src/__tests__/server.assertions.test.ts @@ -28,6 +28,10 @@ describe('assertInput', () => { errorMessage )).toThrow(errorMessage); }); + + it('should pass for a valid input', () => { + expect(() => assertInput('dolor'.length > 1, 'Lorem Ipsum')).not.toThrow(); + }); }); describe('assertInputString', () => { @@ -55,6 +59,10 @@ describe('assertInputString', () => { input )).toThrow(errorMessage); }); + + it('should pass for a valid string', () => { + expect(() => assertInputString('dolor')).not.toThrow(); + }); }); describe('assertInputStringLength', () => { @@ -108,6 +116,10 @@ describe('assertInputStringLength', () => { { min: 1, max: 100, ...options } )).toThrow(errorMessage); }); + + it('should pass for a valid string within range', () => { + expect(() => assertInputStringLength('dolor', { min: 1, max: 10 })).not.toThrow(); + }); }); describe('assertInputStringArrayEntryLength', () => { @@ -161,6 +173,10 @@ describe('assertInputStringArrayEntryLength', () => { { min: 1, max: 100, ...options } )).toThrow(errorMessage); }); + + it('should pass for a valid array of strings', () => { + expect(() => assertInputStringArrayEntryLength(['dolor'], { min: 1, max: 10 })).not.toThrow(); + }); }); describe('assertInputStringNumberEnumLike', () => { @@ -220,4 +236,8 @@ describe('assertInputStringNumberEnumLike', () => { [] )).toThrow(errorMessage); }); + + it('should pass for a valid value in enum-like array', () => { + expect(() => assertInputStringNumberEnumLike('dolor', ['dolor'])).not.toThrow(); + }); }); diff --git a/src/server.assertions.ts b/src/server.assertions.ts index 73253380..712ea1cb 100644 --- a/src/server.assertions.ts +++ b/src/server.assertions.ts @@ -48,7 +48,7 @@ function assertInput( * @param [options.inputDisplayName] - Display name for the input. Used in the default error message. Defaults to 'Input'. * @param [options.message] - Custom error message. A default error message with optional `inputDisplayName` is generated if not provided. * - * @throws McpError If input is not a string OR is empty + * @throws McpError If input is not a non-empty string. */ function assertInputString( input: unknown, @@ -69,7 +69,7 @@ function assertInputString( * @param [options.inputDisplayName] - Display name for the input. Used in the default error message. Defaults to 'Input'. * @param [options.message] - Error description. A default error message with optional `inputDisplayName` is generated if not provided. * - * @throws McpError If input is not a string OR does not meet length requirements + * @throws McpError If input is not a string, and does not meet length requirements. */ function assertInputStringLength( input: unknown, @@ -90,7 +90,7 @@ function assertInputStringLength( * @param [options.inputDisplayName] - Display name for the input. Used in the default error messages. Defaults to 'Input'. * @param [options.message] - Error description. A default error message with optional `inputDisplayName` is generated if not provided. * - * @throws McpError If input is not an array of strings OR does not meet length requirements + * @throws McpError If input is not an array of strings, and array entries do not meet length requirements. */ function assertInputStringArrayEntryLength( input: unknown, @@ -102,7 +102,7 @@ function assertInputStringArrayEntryLength( } /** - * Assert/validate if input is a string or number AND is one of the allowed values. + * Assert/validate if input is a string or number and is one of the allowed values. * * @param input - The input value * @param values - List of allowed values @@ -110,7 +110,7 @@ function assertInputStringArrayEntryLength( * @param [options.inputDisplayName] - Display name for the input. Used in the default error messages. Defaults to 'Input'. * @param [options.message] - Error description. A default error message with optional `inputDisplayName` is generated if not provided. * - * @throws McpError If input is not a string or number OR is not one of the allowed values + * @throws McpError If input is not a string or number, and is not one of the allowed values. */ function assertInputStringNumberEnumLike( input: unknown, From cd3aca604edccd74b52b71a854bfeaa68cfba924 Mon Sep 17 00:00:00 2001 From: CD Cabrera Date: Tue, 3 Mar 2026 15:22:37 -0500 Subject: [PATCH 5/5] fix: review update --- src/server.assertions.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/server.assertions.ts b/src/server.assertions.ts index 712ea1cb..3c12fbef 100644 --- a/src/server.assertions.ts +++ b/src/server.assertions.ts @@ -69,7 +69,7 @@ function assertInputString( * @param [options.inputDisplayName] - Display name for the input. Used in the default error message. Defaults to 'Input'. * @param [options.message] - Error description. A default error message with optional `inputDisplayName` is generated if not provided. * - * @throws McpError If input is not a string, and does not meet length requirements. + * @throws McpError If input is not a string or does not meet length requirements. */ function assertInputStringLength( input: unknown, @@ -90,7 +90,7 @@ function assertInputStringLength( * @param [options.inputDisplayName] - Display name for the input. Used in the default error messages. Defaults to 'Input'. * @param [options.message] - Error description. A default error message with optional `inputDisplayName` is generated if not provided. * - * @throws McpError If input is not an array of strings, and array entries do not meet length requirements. + * @throws McpError If input is not an array of strings or array entries do not meet length requirements. */ function assertInputStringArrayEntryLength( input: unknown, @@ -110,7 +110,7 @@ function assertInputStringArrayEntryLength( * @param [options.inputDisplayName] - Display name for the input. Used in the default error messages. Defaults to 'Input'. * @param [options.message] - Error description. A default error message with optional `inputDisplayName` is generated if not provided. * - * @throws McpError If input is not a string or number, and is not one of the allowed values. + * @throws McpError If input is not a string or number or is not one of the allowed values. */ function assertInputStringNumberEnumLike( input: unknown,