Skip to main content
Glama
server-tools.test.js12.5 kB
/** * Server Tools Validation Tests for Gravity MCP * Tests all 24 MCP tools registration, schemas, and handlers */ import { TestRunner, TestAssert, setupTestEnvironment } from './helpers.js'; const suite = new TestRunner('Server Tools Validation Tests'); // Tool definitions that should be registered const EXPECTED_TOOLS = [ // Forms Management (6) 'gf_list_forms', 'gf_get_form', 'gf_create_form', 'gf_update_form', 'gf_delete_form', 'gf_validate_form', // Entries Management (5) 'gf_list_entries', 'gf_get_entry', 'gf_create_entry', 'gf_update_entry', 'gf_delete_entry', // Form Submissions (2) 'gf_submit_form_data', 'gf_validate_submission', // Notifications (1) 'gf_send_notifications', // Add-on Feeds (7) 'gf_list_feeds', 'gf_get_feed', 'gf_list_form_feeds', 'gf_create_feed', 'gf_update_feed', 'gf_patch_feed', 'gf_delete_feed', // Utilities (2) 'gf_get_field_filters', 'gf_get_results' ]; // Tool categories for organization validation const TOOL_CATEGORIES = { forms: ['gf_list_forms', 'gf_get_form', 'gf_create_form', 'gf_update_form', 'gf_delete_form', 'gf_validate_form'], entries: ['gf_list_entries', 'gf_get_entry', 'gf_create_entry', 'gf_update_entry', 'gf_delete_entry'], submissions: ['gf_submit_form_data', 'gf_validate_submission'], notifications: ['gf_send_notifications'], feeds: ['gf_list_feeds', 'gf_get_feed', 'gf_list_form_feeds', 'gf_create_feed', 'gf_update_feed', 'gf_patch_feed', 'gf_delete_feed'], utilities: ['gf_get_field_filters', 'gf_get_results'] }; // ================================= // TOOL REGISTRATION TESTS // ================================= suite.test('Tool Registration: Should have exactly 23 tools registered', () => { TestAssert.equal(EXPECTED_TOOLS.length, 23, 'Should have 23 tools defined'); // Count by category let totalTools = 0; Object.values(TOOL_CATEGORIES).forEach(tools => { totalTools += tools.length; }); TestAssert.equal(totalTools, 23, 'Category tools should sum to 23'); }); suite.test('Tool Registration: Should have all expected tool names', () => { const toolSet = new Set(EXPECTED_TOOLS); TestAssert.equal(toolSet.size, 23, 'Should have 23 unique tool names'); // Verify naming convention EXPECTED_TOOLS.forEach(tool => { TestAssert.isTrue(tool.startsWith('gf_'), `Tool ${tool} should start with gf_`); TestAssert.isTrue(/^gf_[a-z_]+$/.test(tool), `Tool ${tool} should follow naming convention`); }); }); suite.test('Tool Registration: Should have correct category distribution', () => { TestAssert.equal(TOOL_CATEGORIES.forms.length, 6, 'Forms should have 6 tools'); TestAssert.equal(TOOL_CATEGORIES.entries.length, 5, 'Entries should have 5 tools (no submit)'); TestAssert.equal(TOOL_CATEGORIES.submissions.length, 2, 'Submissions should have 2 tools'); TestAssert.equal(TOOL_CATEGORIES.notifications.length, 1, 'Notifications should have 1 tool'); TestAssert.equal(TOOL_CATEGORIES.feeds.length, 7, 'Feeds should have 7 tools'); TestAssert.equal(TOOL_CATEGORIES.utilities.length, 2, 'Utilities should have 2 tools'); }); // ================================= // INPUT SCHEMA VALIDATION // ================================= suite.test('Schema Validation: Forms tools should have proper schemas', () => { // Validate list_forms schema - Only supports 'include' parameter per API documentation const listFormsSchema = { type: 'object', properties: { include: { type: 'array', items: { type: 'number' } } } }; TestAssert.equal(listFormsSchema.type, 'object'); TestAssert.isNotNull(listFormsSchema.properties); // Validate get_form schema const getFormSchema = { type: 'object', properties: { id: { type: 'number' } }, required: ['id'] }; TestAssert.includes(getFormSchema.required, 'id'); // Validate create_form schema const createFormSchema = { type: 'object', properties: { title: { type: 'string' }, description: { type: 'string' }, fields: { type: 'array' } }, required: ['title'] }; TestAssert.includes(createFormSchema.required, 'title'); }); suite.test('Schema Validation: Entries tools should have search schema', () => { // Validate complex search schema const searchProperties = { search: { type: 'object', properties: { field_filters: { type: 'array', items: { type: 'object', properties: { key: { type: 'string' }, value: { type: 'string' }, operator: { type: 'string', enum: ['=', 'IS', 'CONTAINS', 'IS NOT', 'ISNOT', '<>', 'LIKE', 'NOT IN', 'NOTIN', 'IN', '>', '<', '>=', '<='] } } } }, mode: { type: 'string', enum: ['any', 'all'] } } } }; TestAssert.equal(searchProperties.search.type, 'object'); TestAssert.lengthOf(searchProperties.search.properties.field_filters.items.properties.operator.enum, 14); }); suite.test('Schema Validation: Feeds tools should have addon_slug validation', () => { const feedSchema = { type: 'object', properties: { addon_slug: { type: 'string' }, form_id: { type: 'number' }, meta: { type: 'object' } }, required: ['addon_slug', 'form_id', 'meta'] }; TestAssert.includes(feedSchema.required, 'addon_slug'); TestAssert.includes(feedSchema.required, 'form_id'); TestAssert.includes(feedSchema.required, 'meta'); }); // ================================= // HANDLER VALIDATION // ================================= suite.test('Handler Validation: All tools should have handlers', () => { // Simulate handler mapping const handlers = {}; EXPECTED_TOOLS.forEach(tool => { handlers[tool] = true; // Would be actual handler function }); TestAssert.equal(Object.keys(handlers).length, 23, 'Should have 23 handlers'); EXPECTED_TOOLS.forEach(tool => { TestAssert.isTrue(handlers[tool], `Should have handler for ${tool}`); }); }); suite.test('Handler Validation: Delete operations should check permissions', () => { const deleteTools = ['gf_delete_form', 'gf_delete_entry', 'gf_delete_feed']; deleteTools.forEach(tool => { TestAssert.includes(EXPECTED_TOOLS, tool, `${tool} should be registered`); }); // These tools should have delete protection logic TestAssert.lengthOf(deleteTools, 3, 'Should have 3 delete tools'); }); // ================================= // PARAMETER VALIDATION // ================================= suite.test('Parameter Validation: ID parameters should be numbers', () => { const toolsWithId = [ 'gf_get_form', 'gf_update_form', 'gf_delete_form', 'gf_get_entry', 'gf_update_entry', 'gf_delete_entry', 'gf_get_feed', 'gf_update_feed', 'gf_patch_feed', 'gf_delete_feed' ]; toolsWithId.forEach(tool => { TestAssert.includes(EXPECTED_TOOLS, tool, `${tool} should be registered`); }); TestAssert.lengthOf(toolsWithId, 10, 'Should have 10 tools with ID parameter'); }); suite.test('Parameter Validation: form_id should be required where needed', () => { const toolsWithFormId = [ 'gf_validate_form', 'gf_submit_form_data', 'gf_validate_submission', 'gf_list_form_feeds', 'gf_create_feed', 'gf_get_field_filters', 'gf_get_results' ]; toolsWithFormId.forEach(tool => { TestAssert.includes(EXPECTED_TOOLS, tool, `${tool} should be registered`); }); }); suite.test('Parameter Validation: Pagination parameters should be consistent', () => { // Only entries endpoint supports pagination, forms endpoint does not const toolsWithPagination = ['gf_list_entries']; toolsWithPagination.forEach(tool => { TestAssert.includes(EXPECTED_TOOLS, tool, `${tool} should support pagination`); }); // Verify forms endpoint does NOT have pagination (returns all forms as object) TestAssert.includes(EXPECTED_TOOLS, 'gf_list_forms', 'gf_list_forms should exist but not support pagination'); }); // ================================= // ERROR HANDLING VALIDATION // ================================= suite.test('Error Handling: All tools should handle missing required params', () => { const toolsWithRequired = { 'gf_get_form': ['id'], 'gf_update_form': ['id'], 'gf_delete_form': ['id'], 'gf_create_form': ['title'], 'gf_get_entry': ['id'], 'gf_create_entry': ['form_id'], 'gf_update_entry': ['id'], 'gf_delete_entry': ['id'], 'gf_submit_form_data': ['form_id'], 'gf_validate_submission': ['form_id'], 'gf_send_notifications': ['entry_id'], 'gf_get_feed': ['id'], 'gf_list_form_feeds': ['form_id'], 'gf_create_feed': ['addon_slug', 'form_id', 'meta'], 'gf_update_feed': ['id'], 'gf_patch_feed': ['id'], 'gf_delete_feed': ['id'], 'gf_get_field_filters': ['form_id'], 'gf_get_results': ['form_id'] }; Object.keys(toolsWithRequired).forEach(tool => { TestAssert.includes(EXPECTED_TOOLS, tool, `${tool} should be registered`); TestAssert.isTrue(toolsWithRequired[tool].length > 0, `${tool} should have required params`); }); }); suite.test('Error Handling: Tools should wrap errors consistently', () => { // Verify error response structure const errorResponse = { error: 'Error message', details: 'Detailed error information', success: false }; TestAssert.isFalse(errorResponse.success); TestAssert.isNotNull(errorResponse.error); TestAssert.isNotNull(errorResponse.details); }); // ================================= // TOOL COVERAGE VALIDATION // ================================= suite.test('Coverage: Should cover all REST API v2 endpoints', () => { const apiEndpoints = { '/forms': ['list', 'get', 'create', 'update', 'delete'], '/entries': ['list', 'get', 'create', 'update', 'delete'], '/forms/{id}/submissions': ['submit', 'validate'], '/entries/{id}/notifications': ['send'], '/feeds': ['list', 'get', 'create', 'update', 'patch', 'delete'], '/forms/{id}/feeds': ['list'], '/forms/{id}/field-filters': ['get'], '/forms/{id}/results': ['get'] }; let totalOperations = 0; Object.values(apiEndpoints).forEach(ops => { totalOperations += ops.length; }); // We have 24 tools covering all operations TestAssert.isTrue(totalOperations >= 20, 'Should cover at least 20 operations'); }); suite.test('Coverage: Should have complete CRUD operations where applicable', () => { const crudEntities = { forms: ['list', 'get', 'create', 'update', 'delete'], entries: ['list', 'get', 'create', 'update', 'delete'], feeds: ['list', 'get', 'create', 'update', 'patch', 'delete'] }; TestAssert.lengthOf(crudEntities.forms, 5); TestAssert.lengthOf(crudEntities.entries, 5); TestAssert.lengthOf(crudEntities.feeds, 6); // Includes patch }); // ================================= // TOOL DESCRIPTION VALIDATION // ================================= suite.test('Descriptions: All tools should have clear descriptions', () => { const toolDescriptions = { 'gf_list_forms': 'List all forms (returns all forms as object keyed by ID)', 'gf_get_form': 'Get a specific form by ID with complete schema', 'gf_create_form': 'Create a new form with fields and settings', 'gf_update_form': 'Update an existing form', 'gf_delete_form': 'Delete or trash a form (requires ALLOW_DELETE=true)', 'gf_validate_form': 'Validate form submission data' }; Object.keys(toolDescriptions).forEach(tool => { TestAssert.isTrue(toolDescriptions[tool].length > 10, `${tool} should have meaningful description`); }); // Verify list_forms description clarifies the object response format TestAssert.includes( toolDescriptions['gf_list_forms'], 'object keyed by ID', 'gf_list_forms description should clarify object response format' ); }); suite.test('Descriptions: Delete tools should mention permission requirement', () => { const deleteDescriptions = { 'gf_delete_form': 'Delete or trash a form (requires ALLOW_DELETE=true)', 'gf_delete_entry': 'Delete or trash an entry (requires ALLOW_DELETE=true)', 'gf_delete_feed': 'Delete an add-on feed' }; TestAssert.includes(deleteDescriptions['gf_delete_form'], 'ALLOW_DELETE'); TestAssert.includes(deleteDescriptions['gf_delete_entry'], 'ALLOW_DELETE'); }); // Run tests suite.run().then(results => { process.exit(results.failed > 0 ? 1 : 0); }); export default suite;

Latest Blog Posts

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/GravityKit/gravity-mcp'

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