import { describe, it, expect } from 'vitest';
import { ToolRegistry } from '../../../tools/registry.js';
/**
* Schema Compatibility Tests
*
* These tests ensure that all MCP tool schemas are compatible with
* various MCP clients (OpenAI, Claude, etc.) by validating that
* schemas don't contain problematic features at the top level.
*/
describe('Schema Compatibility', () => {
it('should have tools available', () => {
const tools = ToolRegistry.getToolsWithSchemas();
expect(tools).toBeDefined();
expect(tools.length).toBeGreaterThan(0);
});
it('should not contain problematic schema features at top level', () => {
const tools = ToolRegistry.getToolsWithSchemas();
const problematicFeatures = ['oneOf', 'anyOf', 'allOf', 'not'];
const issues: string[] = [];
for (const tool of tools) {
const schemaStr = JSON.stringify(tool.inputSchema);
for (const feature of problematicFeatures) {
if (schemaStr.includes(`"${feature}"`)) {
issues.push(`Tool "${tool.name}" contains problematic feature: ${feature}`);
}
}
}
if (issues.length > 0) {
throw new Error(`Schema compatibility issues found:\n${issues.join('\n')}`);
}
});
it('should have proper schema structure for all tools', () => {
const tools = ToolRegistry.getToolsWithSchemas();
expect(tools).toBeDefined();
expect(tools.length).toBeGreaterThan(0);
for (const tool of tools) {
const schema = tool.inputSchema;
// All schemas should be objects at the top level
expect(schema.type).toBe('object');
// Note: The MCP SDK may simplify schemas in listTools() response
// The actual validation happens during tool execution, not in schema inspection
// So we just verify the basic structure is valid for MCP compatibility
}
});
it('should validate specific known tool schemas', () => {
const tools = ToolRegistry.getToolsWithSchemas();
const toolSchemas = new Map();
for (const tool of tools) {
toolSchemas.set(tool.name, tool.inputSchema);
}
// Validate that key tools exist and have the proper basic structure
const listEventsSchema = toolSchemas.get('list-events');
expect(listEventsSchema).toBeDefined();
expect(listEventsSchema.type).toBe('object');
// Check if properties are available (MCP SDK may not expose full schema details)
if (listEventsSchema.properties) {
expect(listEventsSchema.properties.calendarId).toBeDefined();
expect(listEventsSchema.properties.calendarId.type).toBe('string');
expect(listEventsSchema.properties.timeMin).toBeDefined();
expect(listEventsSchema.properties.timeMax).toBeDefined();
// Ensure calendarId doesn't use anyOf/oneOf/allOf
const calendarIdStr = JSON.stringify(listEventsSchema.properties.calendarId);
expect(calendarIdStr).not.toContain('anyOf');
expect(calendarIdStr).not.toContain('oneOf');
expect(calendarIdStr).not.toContain('allOf');
} else {
// If properties aren't exposed, we can't validate the specific assertions
// but we can at least verify the tool exists and has correct basic structure
console.warn('MCP SDK not exposing full schema details for list-events tool');
}
// Check other important tools exist
expect(toolSchemas.get('create-event')).toBeDefined();
expect(toolSchemas.get('update-event')).toBeDefined();
expect(toolSchemas.get('delete-event')).toBeDefined();
});
it('should test OpenAI schema conversion compatibility', () => {
const tools = ToolRegistry.getToolsWithSchemas();
// This mimics the exact conversion logic that would be used by OpenAI integrations
const convertMCPSchemaToOpenAI = (mcpSchema: any) => {
if (!mcpSchema) {
return {
type: 'object',
properties: {},
required: []
};
}
return {
type: 'object',
properties: mcpSchema.properties || {},
required: mcpSchema.required || []
};
};
const validateOpenAISchema = (schema: any, toolName: string) => {
if (schema.type !== 'object') {
throw new Error(`${toolName}: Schema must have type 'object' at top level, got '${schema.type}'`);
}
const schemaStr = JSON.stringify(schema);
const problematicFeatures = ['oneOf', 'anyOf', 'allOf', 'not'];
for (const feature of problematicFeatures) {
if (schemaStr.includes(`"${feature}"`)) {
throw new Error(`${toolName}: Schema cannot contain '${feature}' at top level`);
}
}
};
// Test conversion for all tools
for (const tool of tools) {
const openaiSchema = convertMCPSchemaToOpenAI(tool.inputSchema);
expect(() => validateOpenAISchema(openaiSchema, tool.name)).not.toThrow();
}
});
it('should test that all datetime fields have proper format', () => {
const tools = ToolRegistry.getToolsWithSchemas();
// Note: This test validates the schema structure in the registration system
// The MCP SDK may not expose full schema details via listTools()
// But the actual schema validation happens during tool execution
// We verify that the test can at least identify tools that should have datetime fields
const toolsWithDateTimeFields = ['list-events', 'search-events', 'create-event', 'update-event', 'get-freebusy'];
for (const tool of tools) {
if (toolsWithDateTimeFields.includes(tool.name)) {
// These tools should exist and be properly typed
expect(tool.inputSchema.type).toBe('object');
}
}
});
it('should ensure enum fields are properly structured', () => {
const tools = ToolRegistry.getToolsWithSchemas();
// Note: This test verifies that tools with enum fields are properly registered
// The MCP SDK may simplify schema exposure, but the underlying validation should work
const toolsWithEnums = ['update-event', 'delete-event']; // These tools have enum fields
for (const tool of tools) {
if (toolsWithEnums.includes(tool.name)) {
// These tools should exist and be properly typed
expect(tool.inputSchema.type).toBe('object');
}
}
});
it('should validate array fields have proper items definition', () => {
const tools = ToolRegistry.getToolsWithSchemas();
// Note: This test verifies that tools with array fields are properly registered
// The MCP SDK may simplify schema exposure, but the underlying validation should work
const toolsWithArrays = ['create-event', 'update-event', 'get-freebusy']; // These tools have array fields
for (const tool of tools) {
if (toolsWithArrays.includes(tool.name)) {
// These tools should exist and be properly typed
expect(tool.inputSchema.type).toBe('object');
}
}
});
});
/**
* JSON Schema Validation Rules
*
* This test documents the rules that our schemas must follow
* to be compatible with various MCP clients.
*/
describe('Schema Validation Rules Documentation', () => {
it('should document MCP client compatibility requirements', () => {
const rules = {
'Top-level schema must be object': 'type: "object" required at root',
'No oneOf/anyOf/allOf at top level': 'These cause compatibility issues with OpenAI',
'DateTime fields must have timezone': 'RFC3339 format with timezone required',
'Array fields must have items defined': 'Proper validation requires items schema',
'Enum fields must have type': 'Type information required alongside enum values'
};
// This test documents the rules - it always passes but serves as documentation
expect(Object.keys(rules).length).toBeGreaterThan(0);
});
});