Skip to main content
Glama
definitions.test.ts15.9 kB
/** * Tests for Tool Definition utilities */ import { ToolDefinitionBuilder, createTool, CommonSchemas, ToolDefinitionValidator, ToolTemplates } from '../../src/tools/definitions.js'; import type { ToolDefinition } from '../../src/types/index.js'; describe('ToolDefinitionBuilder', () => { let builder: ToolDefinitionBuilder; beforeEach(() => { builder = new ToolDefinitionBuilder(); }); describe('Basic Tool Creation', () => { it('should create a simple tool', () => { const tool = builder .name('test-tool') .description('A test tool') .handler(async () => ({ content: [{ type: 'text', text: 'test' }] })) .build(); expect(tool.name).toBe('test-tool'); expect(tool.description).toBe('A test tool'); expect(typeof tool.handler).toBe('function'); expect(tool.inputSchema.type).toBe('object'); expect(tool.inputSchema.properties).toEqual({}); }); it('should create a tool with category and version', () => { const tool = builder .name('categorized-tool') .description('A categorized tool') .category('test') .version('1.0.0') .handler(async () => ({ content: [{ type: 'text', text: 'test' }] })) .build(); expect(tool.category).toBe('test'); expect(tool.version).toBe('1.0.0'); }); it('should throw error for missing name', () => { expect(() => { builder .description('A test tool') .handler(async () => ({ content: [{ type: 'text', text: 'test' }] })) .build(); }).toThrow('Tool name is required'); }); it('should throw error for missing description', () => { expect(() => { builder .name('test-tool') .handler(async () => ({ content: [{ type: 'text', text: 'test' }] })) .build(); }).toThrow('Tool description is required'); }); it('should throw error for missing handler', () => { expect(() => { builder .name('test-tool') .description('A test tool') .build(); }).toThrow('Tool handler is required'); }); }); describe('Parameter Addition', () => { beforeEach(() => { builder .name('param-tool') .description('A tool with parameters') .handler(async () => ({ content: [{ type: 'text', text: 'test' }] })); }); it('should add required string parameter', () => { const tool = builder .requiredString('name', 'User name', { minLength: 1, maxLength: 50 }) .build(); expect(tool.inputSchema.properties!.name).toEqual({ type: 'string', description: 'User name', minLength: 1, maxLength: 50 }); expect(tool.inputSchema.required).toContain('name'); }); it('should add optional string parameter', () => { const tool = builder .optionalString('email', 'User email', { pattern: '^[^@]+@[^@]+\\.[^@]+$' }) .build(); expect(tool.inputSchema.properties!.email).toEqual({ type: 'string', description: 'User email', pattern: '^[^@]+@[^@]+\\.[^@]+$' }); expect(tool.inputSchema.required).toBeUndefined(); }); it('should add required number parameter', () => { const tool = builder .requiredNumber('age', 'User age', { minimum: 0, maximum: 150 }) .build(); expect(tool.inputSchema.properties!.age).toEqual({ type: 'number', description: 'User age', minimum: 0, maximum: 150 }); expect(tool.inputSchema.required).toContain('age'); }); it('should add optional integer parameter', () => { const tool = builder .optionalInteger('count', 'Item count', { minimum: 0 }) .build(); expect(tool.inputSchema.properties!.count).toEqual({ type: 'integer', description: 'Item count', minimum: 0 }); expect(tool.inputSchema.required).toBeUndefined(); }); it('should add boolean parameter', () => { const tool = builder .requiredBoolean('active', 'Is active') .build(); expect(tool.inputSchema.properties!.active).toEqual({ type: 'boolean', description: 'Is active' }); expect(tool.inputSchema.required).toContain('active'); }); it('should add array parameter', () => { const tool = builder .requiredArray('tags', 'Tag list', { type: 'string' }, { minItems: 1, maxItems: 10 }) .build(); expect(tool.inputSchema.properties!.tags).toEqual({ type: 'array', description: 'Tag list', items: { type: 'string' }, minItems: 1, maxItems: 10 }); expect(tool.inputSchema.required).toContain('tags'); }); it('should add object parameter', () => { const tool = builder .requiredObject('metadata', 'Metadata object', { key: { type: 'string' }, value: { type: 'string' } }, ['key']) .build(); expect(tool.inputSchema.properties!.metadata).toEqual({ type: 'object', description: 'Metadata object', properties: { key: { type: 'string' }, value: { type: 'string' } }, required: ['key'] }); expect(tool.inputSchema.required).toContain('metadata'); }); it('should add multiple parameters with mixed requirements', () => { const tool = builder .requiredString('name', 'User name') .optionalString('email', 'User email') .requiredNumber('age', 'User age') .optionalBoolean('active', 'Is active') .build(); expect(Object.keys(tool.inputSchema.properties!)).toHaveLength(4); expect(tool.inputSchema.required).toEqual(['name', 'age']); }); }); describe('Fluent Interface', () => { it('should support method chaining', () => { const tool = builder .name('chained-tool') .description('A tool created with method chaining') .category('test') .version('1.0.0') .requiredString('param1', 'First parameter') .optionalNumber('param2', 'Second parameter') .handler(async () => ({ content: [{ type: 'text', text: 'test' }] })) .build(); expect(tool.name).toBe('chained-tool'); expect(tool.category).toBe('test'); expect(tool.version).toBe('1.0.0'); expect(Object.keys(tool.inputSchema.properties!)).toHaveLength(2); }); }); }); describe('createTool helper', () => { it('should create a new ToolDefinitionBuilder instance', () => { const builder = createTool(); expect(builder).toBeInstanceOf(ToolDefinitionBuilder); }); it('should create different instances', () => { const builder1 = createTool(); const builder2 = createTool(); expect(builder1).not.toBe(builder2); }); }); describe('CommonSchemas', () => { it('should provide URL schema', () => { const schema = CommonSchemas.url('Website URL'); expect(schema).toEqual({ type: 'string', description: 'Website URL', pattern: '^https?://.+', maxLength: 2048 }); }); it('should provide email schema', () => { const schema = CommonSchemas.email('Email address'); expect(schema).toEqual({ type: 'string', description: 'Email address', pattern: '^[^@]+@[^@]+\\.[^@]+$', maxLength: 254 }); }); it('should provide UUID schema', () => { const schema = CommonSchemas.uuid('Unique identifier'); expect(schema).toEqual({ type: 'string', description: 'Unique identifier', pattern: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$' }); }); it('should provide date schema', () => { const schema = CommonSchemas.date('Date value'); expect(schema).toEqual({ type: 'string', description: 'Date value', pattern: '^\\d{4}-\\d{2}-\\d{2}$' }); }); it('should provide datetime schema', () => { const schema = CommonSchemas.datetime('DateTime value'); expect(schema).toEqual({ type: 'string', description: 'DateTime value', pattern: '^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d{3})?Z?$' }); }); it('should provide positive integer schema', () => { const schema = CommonSchemas.positiveInteger('Count'); expect(schema).toEqual({ type: 'integer', description: 'Count', minimum: 1 }); }); it('should provide HTTP method schema', () => { const schema = CommonSchemas.httpMethod('HTTP method'); expect(schema).toEqual({ type: 'string', description: 'HTTP method', enum: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'] }); }); }); describe('ToolDefinitionValidator', () => { describe('Valid Tool Definitions', () => { it('should validate a correct tool definition', () => { const tool: ToolDefinition = { name: 'valid-tool', description: 'A valid tool', inputSchema: { type: 'object', properties: { param1: { type: 'string', description: 'First parameter' } }, required: ['param1'] }, handler: async () => ({ content: [{ type: 'text', text: 'test' }] }) }; const result = ToolDefinitionValidator.validate(tool); expect(result.valid).toBe(true); expect(result.errors).toHaveLength(0); }); it('should validate a tool with optional fields', () => { const tool: ToolDefinition = { name: 'complete-tool', description: 'A complete tool', category: 'test', version: '1.0.0', inputSchema: { type: 'object', properties: {} }, handler: async () => ({ content: [{ type: 'text', text: 'test' }] }) }; const result = ToolDefinitionValidator.validate(tool); expect(result.valid).toBe(true); expect(result.errors).toHaveLength(0); }); }); describe('Invalid Tool Definitions', () => { it('should reject tool without name', () => { const tool = { description: 'A tool without name', inputSchema: { type: 'object', properties: {} }, handler: async () => ({ content: [{ type: 'text', text: 'test' }] }) } as ToolDefinition; const result = ToolDefinitionValidator.validate(tool); expect(result.valid).toBe(false); expect(result.errors).toContain('Tool name is required and must be a string'); }); it('should reject tool without description', () => { const tool = { name: 'no-description-tool', inputSchema: { type: 'object', properties: {} }, handler: async () => ({ content: [{ type: 'text', text: 'test' }] }) } as ToolDefinition; const result = ToolDefinitionValidator.validate(tool); expect(result.valid).toBe(false); expect(result.errors).toContain('Tool description is required and must be a string'); }); it('should reject tool without handler', () => { const tool = { name: 'no-handler-tool', description: 'A tool without handler', inputSchema: { type: 'object', properties: {} } } as ToolDefinition; const result = ToolDefinitionValidator.validate(tool); expect(result.valid).toBe(false); expect(result.errors).toContain('Tool handler is required and must be a function'); }); it('should reject tool with invalid input schema type', () => { const tool = { name: 'invalid-schema-tool', description: 'A tool with invalid schema', inputSchema: { type: 'string', properties: {} }, handler: async () => ({ content: [{ type: 'text', text: 'test' }] }) } as ToolDefinition; const result = ToolDefinitionValidator.validate(tool); expect(result.valid).toBe(false); expect(result.errors).toContain('inputSchema.type must be \'object\''); }); it('should reject tool with invalid property types', () => { const tool = { name: 'invalid-prop-tool', description: 'A tool with invalid property', inputSchema: { type: 'object', properties: { invalidProp: { type: 'invalid-type' } } }, handler: async () => ({ content: [{ type: 'text', text: 'test' }] }) } as ToolDefinition; const result = ToolDefinitionValidator.validate(tool); expect(result.valid).toBe(false); expect(result.errors).toContain('inputSchema.properties.invalidProp.type must be one of: string, number, integer, boolean, array, object'); }); it('should reject tool with invalid required field reference', () => { const tool = { name: 'invalid-required-tool', description: 'A tool with invalid required field', inputSchema: { type: 'object', properties: { existingProp: { type: 'string' } }, required: ['nonExistentProp'] }, handler: async () => ({ content: [{ type: 'text', text: 'test' }] }) } as ToolDefinition; const result = ToolDefinitionValidator.validate(tool); expect(result.valid).toBe(false); expect(result.errors).toContain('inputSchema.required references non-existent property: nonExistentProp'); }); }); }); describe('ToolTemplates', () => { describe('API Call Template', () => { it('should create API call tool template', () => { const toolBuilder = ToolTemplates.apiCall('test-api', 'Test API call', '/test', 'GET'); const tool = toolBuilder.build(); expect(tool.name).toBe('test-api'); expect(tool.description).toBe('Test API call'); expect(tool.category).toBe('api'); expect(tool.version).toBe('1.0.0'); expect(tool.inputSchema.properties!.endpoint).toBeDefined(); expect(tool.inputSchema.properties!.method).toBeDefined(); expect(tool.inputSchema.required).toContain('endpoint'); }); it('should execute API call tool handler', async () => { const mockApiClient = { makeRequest: jest.fn().mockResolvedValue({ success: true }) }; const toolBuilder = ToolTemplates.apiCall('test-api', 'Test API call', '/test', 'GET'); const tool = toolBuilder.build(); const result = await tool.handler({ endpoint: '/test' }, mockApiClient); expect(mockApiClient.makeRequest).toHaveBeenCalledWith('/test', 'GET', undefined); expect(result.content[0].text).toContain('success'); }); }); describe('Data Processor Template', () => { it('should create data processor tool template', () => { const toolBuilder = ToolTemplates.dataProcessor('test-processor', 'Test data processor'); const tool = toolBuilder.build(); expect(tool.name).toBe('test-processor'); expect(tool.description).toBe('Test data processor'); expect(tool.category).toBe('data'); expect(tool.inputSchema.properties!.input).toBeDefined(); expect(tool.inputSchema.properties!.format).toBeDefined(); expect(tool.inputSchema.required).toContain('input'); }); }); describe('Utility Template', () => { it('should create utility tool template', () => { const toolBuilder = ToolTemplates.utility('test-utility', 'Test utility'); const tool = toolBuilder.build(); expect(tool.name).toBe('test-utility'); expect(tool.description).toBe('Test utility'); expect(tool.category).toBe('utility'); }); it('should execute utility tool handler', async () => { const toolBuilder = ToolTemplates.utility('test-utility', 'Test utility'); const tool = toolBuilder.build(); const result = await tool.handler({}, null); expect(result.content[0].text).toBe('Utility tool executed successfully'); }); }); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/celeryhq/simplified-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server