Skip to main content
Glama
index.tsβ€’14.4 kB
/** * Universal MCP tool configurations - Main module * * This module implements the universal tool consolidation strategy from Issue #352 * to reduce tool count from 70 to ~30 tools while maintaining full functionality. * * Universal tools use parameter-based routing with resource_type discrimination * to provide consolidated operations across companies, people, records, and tasks. */ import { coreOperationsToolConfigs, coreOperationsToolDefinitions, } from './core/index.js'; import { advancedOperationsToolConfigs, advancedOperationsToolDefinitions, } from './operations/index.js'; import { batchSearchConfig, batchSearchToolDefinition, } from './batch-search.js'; import { openAiToolConfigs, openAiToolDefinitions } from '../openai/index.js'; import { formatToolDescription } from '@/handlers/tools/standards/index.js'; import { smitheryDiagnosticsToolDefinition, smitheryDiagnosticsConfig, } from './smithery-diagnostics.js'; /** * Simple no-auth health-check tool to support unauthenticated capability scanning * This tool intentionally performs no Attio API calls and always succeeds. */ export const healthCheckToolDefinition = { name: 'aaa-health-check', description: formatToolDescription({ capability: 'Run a lightweight health probe that echoes deployment metadata.', boundaries: 'query Attio APIs, mutate data, or require credentials.', constraints: 'Accepts optional echo text; returns JSON payload as text for MCP clients.', recoveryHint: 'If unavailable, review Smithery sandbox logs or restart the server process.', }), inputSchema: { type: 'object', properties: {}, additionalProperties: true, }, annotations: { readOnlyHint: true, idempotentHint: true, }, }; export const healthCheckConfig = { name: 'aaa-health-check', handler: async (params: { [key: string]: unknown }) => { const payload = { ok: true, name: 'attio-mcp', echo: typeof params?.echo === 'string' ? (params.echo as string) : undefined, timestamp: new Date().toISOString(), environment: process.env.NODE_ENV || 'production', needs_api_key: true, } as const; // Return MCP-compliant text response (not JSON type) // MCP SDK expects content type to be 'text', not 'json' return { content: [ { type: 'text', text: JSON.stringify(payload, null, 2), }, ], isError: false, }; }, formatResult: (res: Record<string, unknown>): string => { const content = res?.content as Array<Record<string, unknown>> | undefined; const textContent = content?.[0]?.text as string | undefined; // Parse JSON from text content if available let data: Record<string, unknown>; if (textContent) { try { data = JSON.parse(textContent) as Record<string, unknown>; } catch { data = res; } } else { data = res; } const parts: string[] = ['βœ… Server healthy']; if (data?.echo) parts.push(`echo: ${String(data.echo)}`); if (data?.environment) parts.push(`env: ${String(data.environment)}`); return parts.join(' | '); }, }; // Re-export individual tool config objects for testing export { coreOperationsToolConfigs, coreOperationsToolDefinitions, advancedOperationsToolConfigs, advancedOperationsToolDefinitions, }; // Re-export types for external use export * from './types.js'; export * from './schemas.js'; export * from './shared-handlers.js'; /** * All universal tool configurations * These replace 40+ resource-specific tools with 14 universal operations */ export const universalToolConfigs = { // Ensure health-check is listed first alphabetically for best-guess scanners 'aaa-health-check': healthCheckConfig, 'smithery-debug-config': smitheryDiagnosticsConfig, ...coreOperationsToolConfigs, ...advancedOperationsToolConfigs, records_search_batch: batchSearchConfig, ...openAiToolConfigs, }; /** * All universal tool definitions for MCP protocol */ export const universalToolDefinitions = { // Ensure health-check is listed first alphabetically for best-guess scanners 'aaa-health-check': healthCheckToolDefinition, 'smithery-debug-config': smitheryDiagnosticsToolDefinition, ...coreOperationsToolDefinitions, ...advancedOperationsToolDefinitions, records_search_batch: batchSearchToolDefinition, ...openAiToolDefinitions, }; /** * Core universal operations (8 tools) * These consolidate the majority of CRUD and basic search operations */ export const coreUniversalTools = [ 'records_search', 'records_get_details', 'create-record', 'update-record', 'delete-record', 'records_get_attributes', 'records_discover_attributes', 'records_get_info', ]; /** * Advanced universal operations (6 tools) * These provide sophisticated search and batch capabilities */ export const advancedUniversalTools = [ 'records_search_advanced', 'records_search_by_relationship', 'records_search_by_content', 'records_search_by_timeframe', 'records_batch', 'records_search_batch', ]; /** * All universal tool names */ export const allUniversalTools = [ ...coreUniversalTools, ...advancedUniversalTools, ]; /** * Tools that will be deprecated and replaced by universal operations * * These mappings help with migration and alias creation */ export const deprecatedToolMappings: Record<string, string> = { // Company tools β†’ Universal equivalents 'search-companies': 'records_search', 'get-company-details': 'records_get_details', 'create-company': 'create-record', 'update-company': 'update-record', 'delete-company': 'delete-record', 'get-company-attributes': 'records_get_attributes', 'discover-company-attributes': 'records_discover_attributes', 'get-company-basic-info': 'records_get_info', 'get-company-contact-info': 'records_get_info', 'get-company-business-info': 'records_get_info', 'get-company-social-info': 'records_get_info', 'advanced-search-companies': 'records_search_advanced', 'search-companies-by-notes': 'records_search_by_content', 'search-companies-by-people': 'records_search_by_relationship', // People tools β†’ Universal equivalents 'search-people': 'records_search', 'get-person-details': 'records_get_details', 'create-person': 'create-record', 'advanced-search-people': 'records_search_advanced', 'search-people-by-company': 'records_search_by_relationship', 'search-people-by-activity': 'records_search_by_content', 'search-people-by-notes': 'records_search_by_content', 'search-people-by-creation-date': 'records_search_by_timeframe', 'search-people-by-modification-date': 'records_search_by_timeframe', 'search-people-by-last-interaction': 'records_search_by_timeframe', // Record tools β†’ Universal equivalents 'create-record': 'create-record', // Already universal 'get-record': 'records_get_details', 'update-record': 'update-record', // Already universal 'delete-record': 'delete-record', // Already universal 'list-records': 'records_search', 'batch-create-records': 'records_batch', 'batch-update-records': 'records_batch', // Task tools β†’ Universal equivalents 'create-task': 'create-record', 'update-task': 'update-record', 'delete-task': 'delete-record', 'list-tasks': 'records_search', // Batch tools β†’ Universal equivalent 'batch-create-companies': 'records_batch', 'batch-update-companies': 'records_batch', 'batch-delete-companies': 'records_batch', 'batch-search-companies': 'records_search_batch', 'batch-get-company-details': 'records_batch', }; /** * Resource type mappings for deprecated tools * Used to automatically set resource_type when migrating from old tools */ export const resourceTypeMappings: Record<string, string> = { // Company tools 'search-companies': 'companies', 'get-company-details': 'companies', 'create-company': 'companies', 'update-company': 'companies', 'delete-company': 'companies', 'get-company-attributes': 'companies', 'discover-company-attributes': 'companies', 'get-company-basic-info': 'companies', 'get-company-contact-info': 'companies', 'get-company-business-info': 'companies', 'get-company-social-info': 'companies', 'advanced-search-companies': 'companies', 'search-companies-by-notes': 'companies', 'search-companies-by-people': 'companies', 'batch-create-companies': 'companies', 'batch-update-companies': 'companies', 'batch-delete-companies': 'companies', 'batch-search-companies': 'companies', 'batch-get-company-details': 'companies', // People tools 'search-people': 'people', 'get-person-details': 'people', 'create-person': 'people', 'advanced-search-people': 'people', 'search-people-by-company': 'people', 'search-people-by-activity': 'people', 'search-people-by-notes': 'people', 'search-people-by-creation-date': 'people', 'search-people-by-modification-date': 'people', 'search-people-by-last-interaction': 'people', // Record tools 'create-record': 'records', 'get-record': 'records', 'update-record': 'records', 'delete-record': 'records', 'list-records': 'records', 'batch-create-records': 'records', 'batch-update-records': 'records', // Task tools 'create-task': 'tasks', 'update-task': 'tasks', 'delete-task': 'tasks', 'list-tasks': 'tasks', }; /** * Info type mappings for get-detailed-info universal tool */ export const infoTypeMappings: Record<string, string> = { 'get-company-basic-info': 'basic', 'get-company-contact-info': 'contact', 'get-company-business-info': 'business', 'get-company-social-info': 'social', }; /** * Content type mappings for search-by-content universal tool */ export const contentTypeMappings: Record<string, string> = { 'search-companies-by-notes': 'notes', 'search-people-by-notes': 'notes', 'search-people-by-activity': 'activity', }; /** * Timeframe type mappings for search-by-timeframe universal tool */ export const timeframeTypeMappings: Record<string, string> = { 'search-people-by-creation-date': 'created', 'search-people-by-modification-date': 'modified', 'search-people-by-last-interaction': 'last_interaction', }; /** * Relationship type mappings for search-by-relationship universal tool */ export const relationshipTypeMappings: Record<string, string> = { 'search-companies-by-people': 'people_to_company', 'search-people-by-company': 'company_to_people', }; /** * Batch operation type mappings for batch-operations universal tool */ export const batchOperationTypeMappings: Record<string, string> = { 'batch-create-companies': 'create', 'batch-update-companies': 'update', 'batch-delete-companies': 'delete', 'batch-search-companies': 'search', 'batch-get-company-details': 'get', 'batch-create-records': 'create', 'batch-update-records': 'update', }; /** * Get the count of tools that will be consolidated */ export function getConsolidationStats() { const deprecatedCount = Object.keys(deprecatedToolMappings).length; const universalCount = allUniversalTools.length; const reductionCount = deprecatedCount - universalCount; const reductionPercentage = Math.round( (reductionCount / deprecatedCount) * 100 ); return { deprecatedCount, universalCount, reductionCount, reductionPercentage, summary: `${deprecatedCount} β†’ ${universalCount} tools (${reductionPercentage}% reduction)`, }; } /** * Utility to check if a tool name is a universal tool */ export function isUniversalTool(toolName: string): boolean { return allUniversalTools.includes(toolName); } /** * Utility to check if a tool name is deprecated and should be migrated */ export function isDeprecatedTool(toolName: string): boolean { return toolName in deprecatedToolMappings; } /** * Get the universal tool equivalent for a deprecated tool */ export function getUniversalEquivalent( deprecatedToolName: string ): string | undefined { return deprecatedToolMappings[deprecatedToolName]; } /** * Get migration parameters for converting a deprecated tool call to universal */ export function getMigrationParams( deprecatedToolName: string, originalParams: Record<string, unknown> ): Record<string, unknown> { const universalTool = getUniversalEquivalent(deprecatedToolName); const resourceType = resourceTypeMappings[deprecatedToolName]; if (!universalTool || !resourceType) { throw new Error( `No migration path found for deprecated tool: ${deprecatedToolName}` ); } // Base parameters for all universal tools - use Record to allow dynamic property assignment const baseParams: Record<string, unknown> = { resource_type: resourceType, ...originalParams, }; // Add specific parameters based on the universal tool type switch (universalTool) { case 'get-detailed-info': { const infoType = infoTypeMappings[deprecatedToolName]; if (infoType) { baseParams.info_type = infoType; } break; } case 'search-by-content': { const contentType = contentTypeMappings[deprecatedToolName]; if (contentType) { baseParams.content_type = contentType; } break; } case 'search-by-timeframe': { const timeframeType = timeframeTypeMappings[deprecatedToolName]; if (timeframeType) { baseParams.timeframe_type = timeframeType; } break; } case 'search-by-relationship': { const relationshipType = relationshipTypeMappings[deprecatedToolName]; if (relationshipType) { baseParams.relationship_type = relationshipType; } break; } case 'batch-operations': { const operationType = batchOperationTypeMappings[deprecatedToolName]; if (operationType) { baseParams.operation_type = operationType; } break; } } return baseParams; } /** * Log consolidation statistics */ export async function logConsolidationStats(): Promise<void> { const stats = getConsolidationStats(); const { createScopedLogger } = await import('../../../utils/logger.js'); const log = createScopedLogger('universal.tools', 'consolidation'); log.info('Universal tool consolidation', { summary: stats.summary }); log.info('Tool count reduction', { reductionCount: stats.reductionCount, reductionPercentage: stats.reductionPercentage, }); }

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/kesslerio/attio-mcp-server'

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