UNPKG

npmplus-mcp-server

Version:

Production-ready MCP server for intelligent JavaScript package management. Works with Claude, Windsurf, Cursor, VS Code, and any MCP-compatible AI editor.

215 lines (176 loc) 7.61 kB
import { describe, it, expect } from '@jest/globals'; import { convertZodToJsonSchema } from '../utils/schema-converter.js'; import { InstallPackagesSchema, SearchPackagesSchema } from '../validators/tools.js'; describe('MCP Schema Validation Tests', () => { describe('Schema Converter', () => { it('should convert Zod schema to valid JSON Schema', () => { const jsonSchema = convertZodToJsonSchema(InstallPackagesSchema); // Basic JSON Schema structure validation expect(jsonSchema).toHaveProperty('type'); expect(jsonSchema.type).toBe('object'); expect(jsonSchema).toHaveProperty('properties'); // Validate specific properties expect(jsonSchema.properties).toHaveProperty('packages'); expect(jsonSchema.properties.packages.type).toBe('array'); expect(jsonSchema.properties.packages.items.type).toBe('string'); expect(jsonSchema.properties).toHaveProperty('cwd'); expect(jsonSchema.properties.cwd.type).toBe('string'); expect(jsonSchema.properties).toHaveProperty('dev'); expect(jsonSchema.properties.dev.type).toBe('boolean'); // Required fields expect(jsonSchema.required).toContain('packages'); }); it('should convert search packages schema correctly', () => { const jsonSchema = convertZodToJsonSchema(SearchPackagesSchema); expect(jsonSchema.type).toBe('object'); expect(jsonSchema.properties).toHaveProperty('query'); expect(jsonSchema.properties.query.type).toBe('string'); expect(jsonSchema.properties).toHaveProperty('limit'); expect(['number', 'integer'].includes(jsonSchema.properties.limit.type)).toBe(true); expect(jsonSchema.required).toContain('query'); }); it('should handle default values in schema conversion', () => { const jsonSchema = convertZodToJsonSchema(InstallPackagesSchema); // Check that default values are preserved if (jsonSchema.properties.save) { expect(jsonSchema.properties.save.default).toBe(true); } }); it('should validate enum types are converted correctly', () => { // Test with a schema that has enum values const jsonSchema = convertZodToJsonSchema(SearchPackagesSchema); // The schema should be valid JSON Schema format expect(typeof jsonSchema).toBe('object'); expect(jsonSchema).not.toBe(null); // Should not have undefined values when stringified expect(JSON.stringify(jsonSchema)).not.toContain('undefined'); }); }); describe('JSON Schema Compliance', () => { it('should produce schemas that conform to JSON Schema Draft 7', () => { const schemas = [ InstallPackagesSchema, SearchPackagesSchema ]; schemas.forEach((schema) => { const jsonSchema = convertZodToJsonSchema(schema); // Basic Draft 7 compliance checks expect(jsonSchema.type).toBeDefined(); expect(['object', 'array', 'string', 'number', 'boolean', 'null'].includes(jsonSchema.type)).toBe(true); if (jsonSchema.properties) { Object.values(jsonSchema.properties).forEach((property: any) => { // Some properties might be complex objects, check if type exists if (property.type) { expect(typeof property.type).toBe('string'); } // Allow for oneOf, anyOf patterns if (!property.type && !property.oneOf && !property.anyOf) { console.warn('Property missing type:', property); } }); } if (jsonSchema.required) { expect(Array.isArray(jsonSchema.required)).toBe(true); } }); }); it('should handle nested objects and arrays correctly', () => { const jsonSchema = convertZodToJsonSchema(InstallPackagesSchema); // Check array items schema const packagesProperty = jsonSchema.properties.packages; expect(packagesProperty.type).toBe('array'); expect(packagesProperty.items).toBeDefined(); expect(packagesProperty.items.type).toBe('string'); }); it('should preserve descriptions in schema conversion', () => { const jsonSchema = convertZodToJsonSchema(InstallPackagesSchema); // The converter should preserve Zod descriptions Object.values(jsonSchema.properties).forEach((property: any) => { if (property.description) { expect(typeof property.description).toBe('string'); expect(property.description.length).toBeGreaterThan(0); } }); }); }); describe('MCP Response Format Validation', () => { it('should validate successful response format', () => { const successResponse = { content: [{ type: 'text', text: 'Sample response text' }] }; expect(successResponse).toMatchObject({ content: expect.arrayContaining([ expect.objectContaining({ type: expect.any(String), text: expect.any(String) }) ]) }); }); it('should validate error response format', () => { const errorResponse = { isError: true, content: [{ type: 'text', text: 'Error message' }] }; expect(errorResponse).toMatchObject({ isError: true, content: expect.arrayContaining([ expect.objectContaining({ type: 'text', text: expect.any(String) }) ]) }); }); it('should validate content type requirements', () => { const responses = [ { content: [{ type: 'text', text: 'Valid response' }] }, { isError: true, content: [{ type: 'text', text: 'Error response' }] } ]; responses.forEach(response => { expect(response.content).toBeDefined(); expect(Array.isArray(response.content)).toBe(true); response.content.forEach(content => { expect(content).toHaveProperty('type'); expect(content).toHaveProperty('text'); expect(typeof content.type).toBe('string'); expect(typeof content.text).toBe('string'); }); }); }); }); describe('JSON Serialization Safety', () => { it('should ensure all schemas are JSON serializable', () => { const schemas = [InstallPackagesSchema, SearchPackagesSchema]; schemas.forEach(schema => { const jsonSchema = convertZodToJsonSchema(schema); // Should be able to stringify and parse without errors expect(() => { const serialized = JSON.stringify(jsonSchema); const parsed = JSON.parse(serialized); expect(parsed).toEqual(jsonSchema); }).not.toThrow(); }); }); it('should not contain circular references', () => { const jsonSchema = convertZodToJsonSchema(InstallPackagesSchema); // JSON.stringify will throw if there are circular references expect(() => JSON.stringify(jsonSchema)).not.toThrow(); }); it('should handle special characters in schema properly', () => { const jsonSchema = convertZodToJsonSchema(SearchPackagesSchema); const serialized = JSON.stringify(jsonSchema); // Should be valid JSON expect(() => JSON.parse(serialized)).not.toThrow(); // Should not contain undefined values expect(serialized).not.toContain('undefined'); expect(serialized).not.toContain('null'); }); }); });