Skip to main content
Glama
discoveryHandlers.ts11.5 kB
/** * Discovery tool handlers * * This module implements handlers for MCP discovery and information tools * including search, registry operations, and server information. */ import { AdapterFactory } from '@src/core/tools/internal/adapters/index.js'; import logger, { debugIf } from '@src/logger/logger.js'; import { type McpInfoOutput, McpInfoOutputSchema, type McpInfoToolArgs, type McpRegistryInfoOutput, McpRegistryInfoOutputSchema, type McpRegistryInfoToolArgs, type McpRegistryListOutput, McpRegistryListOutputSchema, type McpRegistryListToolArgs, type McpRegistryStatusOutput, McpRegistryStatusOutputSchema, type McpRegistryStatusToolArgs, type McpSearchOutput, McpSearchOutputSchema, type McpSearchToolArgs, } from './schemas/index.js'; /** * Internal tool handler for searching MCP registry */ export async function handleMcpSearch(args: McpSearchToolArgs): Promise<McpSearchOutput> { try { debugIf(() => ({ message: 'Executing mcp_search tool', meta: { args }, })); const adapter = AdapterFactory.getDiscoveryAdapter(); const servers = await adapter.searchServers(args.query || '', { limit: args.limit, cursor: args.cursor, transport: args.transport as 'stdio' | 'sse' | 'webhook' | undefined, status: args.status as 'active' | 'archived' | 'deprecated' | 'all', registry_type: args.type as 'npm' | 'pypi' | 'docker', }); // Transform to match expected output schema with installation method info const result = { results: servers.map((server) => { // Determine installation method const packages = server.packages || []; const remotes = server.remotes || []; const hasPackages = packages.length > 0; const hasRemotes = remotes.length > 0; let installationMethod = 'unknown'; let installationHint = ''; if (hasPackages) { installationMethod = 'package'; const pkgTypes = packages.map((p) => p.registryType).filter(Boolean); installationHint = `Installable via ${pkgTypes.join(', ')} packages`; } else if (hasRemotes) { installationMethod = 'remote'; const remoteTypes = remotes.map((r) => r.type).filter(Boolean); installationHint = `Installable via ${remoteTypes.join(', ')} remotes`; } // Extract prerequisite information const firstPackage = packages[0]; const envVars = firstPackage?.environmentVariables?.length || 0; const packageArgs = firstPackage?.packageArguments?.length || 0; const runtimeArgs = firstPackage?.runtimeArguments?.length || 0; let prerequisiteHint = ''; if (envVars > 0 || packageArgs > 0 || runtimeArgs > 0) { const requirements = []; if (envVars > 0) requirements.push(`${envVars} env vars`); if (packageArgs > 0) requirements.push(`${packageArgs} package args`); if (runtimeArgs > 0) requirements.push(`${runtimeArgs} runtime args`); prerequisiteHint = `Requires: ${requirements.join(', ')}`; } return { name: server.name, version: server.version, description: server.description, author: (server._meta as { author?: string })?.author || 'Unknown', tags: (server._meta as { tags?: string[] })?.tags || [], transport: server.packages?.map((pkg) => pkg.transport?.type).filter(Boolean) || [], registry: 'official', downloads: (server._meta as { downloads?: number })?.downloads, // Enhanced installation information installationMethod, installationHint, prerequisiteHint, hasEnvironmentVariables: envVars > 0, hasPackageArguments: packageArgs > 0, hasRuntimeArguments: runtimeArgs > 0, installable: hasPackages || hasRemotes, }; }), total: servers.length, query: args.query || '', registry: 'official', }; return McpSearchOutputSchema.parse(result); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; logger.error('Error in mcp_search tool handler', { error: errorMessage }); throw new Error(`Search failed: ${errorMessage}`); } } /** * Internal tool handler for getting MCP registry status */ export async function handleMcpRegistryStatus(args: McpRegistryStatusToolArgs): Promise<McpRegistryStatusOutput> { try { debugIf(() => ({ message: 'Executing mcp_registry_status tool', meta: { args }, })); const adapter = AdapterFactory.getDiscoveryAdapter(); const status = await adapter.getRegistryStatus(args.includeStats); // Transform to match expected output schema const { total_servers, ...restStats } = status.stats || {}; const result = { registry: args.registry || 'official', status: status.available ? ('online' as const) : ('offline' as const), responseTime: status.response_time_ms, lastCheck: status.last_updated, error: status.available ? undefined : 'Registry unavailable', metadata: { version: '1.0.0', supportedFormats: ['json', 'table'], totalServers: total_servers || 0, ...restStats, }, }; return McpRegistryStatusOutputSchema.parse(result); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; logger.error('Error in mcp_registry_status tool handler', { error: errorMessage }); throw new Error(`Registry status check failed: ${errorMessage}`); } } /** * Internal tool handler for getting MCP registry information */ export async function handleMcpRegistryInfo(args: McpRegistryInfoToolArgs): Promise<McpRegistryInfoOutput> { try { debugIf(() => ({ message: 'Executing mcp_registry_info tool', meta: { args }, })); // Mock registry info - in real implementation, this would get detailed registry information const registryInfo = { name: args.registry || 'official', url: 'https://registry.modelcontextprotocol.io', description: 'The official Model Context Protocol server registry', version: '1.0.0', supportedFormats: ['json', 'table'], features: ['search', 'get', 'list'], statistics: { totalPackages: 150, lastUpdated: new Date().toISOString(), }, }; return McpRegistryInfoOutputSchema.parse(registryInfo); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; logger.error('Error in mcp_registry_info tool handler', { error: errorMessage }); throw new Error(`Registry info check failed: ${errorMessage}`); } } /** * Internal tool handler for listing MCP registry contents */ export async function handleMcpRegistryList(args: McpRegistryListToolArgs): Promise<McpRegistryListOutput> { try { debugIf(() => ({ message: 'Executing mcp_registry_list tool', meta: { args }, })); // Mock registry list - in real implementation, this would query the actual registry const registryList = { registries: [ { name: 'Official MCP Registry', url: 'https://registry.modelcontextprotocol.io', status: 'online' as const, description: 'The official Model Context Protocol server registry', packageCount: args.includeStats ? 150 : undefined, }, { name: 'Community Registry', url: 'https://community-registry.modelcontextprotocol.io', status: 'online' as const, description: 'Community-contributed MCP servers', packageCount: args.includeStats ? 75 : undefined, }, { name: 'Experimental Registry', url: 'https://experimental-registry.modelcontextprotocol.io', status: 'unknown' as const, // 'beta' mapped to 'unknown' for schema compliance description: 'Experimental and cutting-edge MCP servers', packageCount: args.includeStats ? 25 : undefined, }, ], total: 3, }; return McpRegistryListOutputSchema.parse(registryList); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; logger.error('Error in mcp_registry_list tool handler', { error: errorMessage }); throw new Error(`Registry list failed: ${errorMessage}`); } } /** * Internal tool handler for getting information about specific MCP servers */ export async function handleMcpInfo(args: McpInfoToolArgs): Promise<McpInfoOutput> { try { debugIf(() => ({ message: 'Executing mcp_info tool', meta: { args }, })); const adapter = AdapterFactory.getDiscoveryAdapter(); const server = await adapter.getServerById(args.name, args.version); if (!server) { const result = { server: { name: args.name || 'unknown', status: 'unknown' as const, transport: 'stdio' as const, }, }; return McpInfoOutputSchema.parse(result); } // Transform to match expected output schema const serverMeta = server._meta as { capabilities?: { tools?: { list?: unknown[]; count?: number; }; resources?: { list?: unknown[]; count?: number; }; prompts?: { list?: unknown[]; count?: number; }; }; tags?: string[]; }; const capabilities = serverMeta?.capabilities || {}; const result = { server: { name: server.name, status: 'unknown' as const, // Would need ServerManager integration for real status transport: 'stdio' as const, // Determine from server.packages }, configuration: { command: server.packages?.[0]?.identifier, tags: (serverMeta?.tags as string[]) || [], autoRestart: false, // Default value enabled: true, // Default value }, capabilities: { tools: capabilities.tools?.list ? Array(capabilities.tools.count || 0) .fill(null) .map((_, i) => ({ name: `tool_${i}`, description: `Tool ${i}`, })) : [], resources: capabilities.resources?.list ? Array(capabilities.resources.count || 0) .fill(null) .map((_, i) => ({ uri: `resource://${i}`, name: `Resource ${i}`, })) : [], prompts: capabilities.prompts?.list || capabilities.prompts?.count ? Array(capabilities.prompts.count || 0) .fill(null) .map((_, i) => ({ name: `prompt_${i}`, description: `Prompt ${i}`, })) : [], }, health: { status: 'unknown' as const, lastCheck: new Date().toISOString(), }, }; return McpInfoOutputSchema.parse(result); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; logger.error('Error in mcp_info tool handler', { error: errorMessage }); throw new Error(`Server info check failed: ${errorMessage}`); } } /** * Cleanup function for discovery handlers */ export function cleanupDiscoveryHandlers(): void { AdapterFactory.cleanup(); }

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/1mcp-app/agent'

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