diff --git a/src/services/__tests__/schema-cleanup.test.ts b/src/services/__tests__/schema-cleanup.test.ts new file mode 100644 index 0000000..2fe116a --- /dev/null +++ b/src/services/__tests__/schema-cleanup.test.ts @@ -0,0 +1,102 @@ +describe('Schema Cleanup Tests', () => { + describe('cleanInputSchema functionality', () => { + // Helper function to simulate the cleanInputSchema behavior + const cleanInputSchema = (schema: any): any => { + if (!schema || typeof schema !== 'object') { + return schema; + } + + const cleanedSchema = { ...schema }; + delete cleanedSchema.$schema; + + return cleanedSchema; + }; + + test('should remove $schema field from inputSchema', () => { + const schemaWithDollarSchema = { + $schema: 'http://json-schema.org/draft-07/schema#', + type: 'object', + properties: { + name: { + type: 'string', + description: 'Test property', + }, + }, + required: ['name'], + }; + + const cleanedSchema = cleanInputSchema(schemaWithDollarSchema); + + expect(cleanedSchema).not.toHaveProperty('$schema'); + expect(cleanedSchema.type).toBe('object'); + expect(cleanedSchema.properties).toEqual({ + name: { + type: 'string', + description: 'Test property', + }, + }); + expect(cleanedSchema.required).toEqual(['name']); + }); + + test('should handle null and undefined schemas', () => { + expect(cleanInputSchema(null)).toBe(null); + expect(cleanInputSchema(undefined)).toBe(undefined); + }); + + test('should handle non-object schemas', () => { + expect(cleanInputSchema('string')).toBe('string'); + expect(cleanInputSchema(42)).toBe(42); + expect(cleanInputSchema(true)).toBe(true); + }); + + test('should preserve other properties while removing $schema', () => { + const complexSchema = { + $schema: 'http://json-schema.org/draft-07/schema#', + type: 'object', + title: 'Test Schema', + description: 'A test schema', + properties: { + name: { type: 'string' }, + age: { type: 'number' }, + }, + required: ['name'], + additionalProperties: false, + }; + + const cleanedSchema = cleanInputSchema(complexSchema); + + expect(cleanedSchema).not.toHaveProperty('$schema'); + expect(cleanedSchema.type).toBe('object'); + expect(cleanedSchema.title).toBe('Test Schema'); + expect(cleanedSchema.description).toBe('A test schema'); + expect(cleanedSchema.properties).toEqual({ + name: { type: 'string' }, + age: { type: 'number' }, + }); + expect(cleanedSchema.required).toEqual(['name']); + expect(cleanedSchema.additionalProperties).toBe(false); + }); + + test('should handle schemas without $schema field', () => { + const schemaWithoutDollarSchema = { + type: 'object', + properties: { + name: { type: 'string' }, + }, + }; + + const cleanedSchema = cleanInputSchema(schemaWithoutDollarSchema); + + expect(cleanedSchema).toEqual(schemaWithoutDollarSchema); + expect(cleanedSchema).not.toHaveProperty('$schema'); + }); + + test('should handle empty objects', () => { + const emptySchema = {}; + const cleanedSchema = cleanInputSchema(emptySchema); + + expect(cleanedSchema).toEqual({}); + expect(cleanedSchema).not.toHaveProperty('$schema'); + }); + }); +}); diff --git a/src/services/mcpService.ts b/src/services/mcpService.ts index 5e0e603..2c225e0 100644 --- a/src/services/mcpService.ts +++ b/src/services/mcpService.ts @@ -99,6 +99,18 @@ export const syncToolEmbedding = async (serverName: string, toolName: string) => saveToolsAsVectorEmbeddings(serverName, [tool]); }; +// Helper function to clean $schema field from inputSchema +const cleanInputSchema = (schema: any): any => { + if (!schema || typeof schema !== 'object') { + return schema; + } + + const cleanedSchema = { ...schema }; + delete cleanedSchema.$schema; + + return cleanedSchema; +}; + // Store all server information let serverInfos: ServerInfo[] = []; @@ -272,7 +284,7 @@ const callToolWithReconnect = async ( serverInfo.tools = tools.tools.map((tool) => ({ name: `${serverInfo.name}-${tool.name}`, description: tool.description || '', - inputSchema: tool.inputSchema || {}, + inputSchema: cleanInputSchema(tool.inputSchema || {}), })); // Save tools as vector embeddings for search @@ -391,7 +403,7 @@ export const initializeClientsFromSettings = async (isInit: boolean): Promise ({ name: `${name}-${tool.name}`, description: tool.description, - inputSchema: tool.inputSchema, + inputSchema: cleanInputSchema(tool.inputSchema), })); // Update server info with successful initialization @@ -472,7 +484,7 @@ export const initializeClientsFromSettings = async (isInit: boolean): Promise ({ name: `${name}-${tool.name}`, description: tool.description || '', - inputSchema: tool.inputSchema || {}, + inputSchema: cleanInputSchema(tool.inputSchema || {}), })); serverInfo.status = 'connected'; serverInfo.error = null; @@ -924,7 +936,7 @@ export const handleCallToolRequest = async (request: any, extra: any) => { return { name: result.toolName, description: result.description || '', - inputSchema: result.inputSchema || {}, + inputSchema: cleanInputSchema(result.inputSchema || {}), }; }) .filter((tool) => {