import { isEqual, omit } from 'es-toolkit' import type { McpToolSchema, StoredMcpToolReference } from '@/lib/mcp/types' export type McpToolIssueType = | 'server_not_found' | 'server_error' | 'tool_not_found' | 'schema_changed' | 'url_changed' export interface McpToolIssue { type: McpToolIssueType message: string } export interface ServerState { id: string url?: string connectionStatus?: 'connected' | 'disconnected' | 'error' lastError?: string } export interface DiscoveredTool { serverId: string name: string inputSchema?: McpToolSchema } export function hasSchemaChanged( storedSchema: McpToolSchema | undefined, serverSchema: McpToolSchema | undefined ): boolean { if (!storedSchema || !serverSchema) return false const storedWithoutDesc = omit(storedSchema, ['description']) const serverWithoutDesc = omit(serverSchema, ['description']) return !isEqual(storedWithoutDesc, serverWithoutDesc) } /** * Validates server-level connectivity for an MCP server. * Checks: server existence, connection status, URL changes. */ export function getMcpServerIssue( serverId: string, serverUrl: string | undefined, servers: ServerState[] ): McpToolIssue | null { const server = servers.find((s) => s.id === serverId) if (!server) { return { type: 'server_not_found', message: 'Server not found' } } if (server.connectionStatus === 'error') { return { type: 'server_error', message: server.lastError || 'Server connection error' } } if (server.connectionStatus !== 'connected') { return { type: 'server_error', message: 'Server not connected' } } if (serverUrl && server.url && serverUrl !== server.url) { return { type: 'url_changed', message: 'Server URL changed' } } return null } export function getMcpToolIssue( storedTool: StoredMcpToolReference, servers: ServerState[], discoveredTools: DiscoveredTool[] ): McpToolIssue | null { const { serverId, serverUrl, toolName, schema } = storedTool const serverIssue = getMcpServerIssue(serverId, serverUrl, servers) if (serverIssue) return serverIssue const serverTool = discoveredTools.find((t) => t.serverId === serverId && t.name === toolName) if (!serverTool) { return { type: 'tool_not_found', message: 'Tool not found on server' } } if (schema && serverTool.inputSchema) { if (hasSchemaChanged(schema, serverTool.inputSchema)) { return { type: 'schema_changed', message: 'Tool schema changed' } } } return null } export function getIssueBadgeLabel(issue: McpToolIssue): string { switch (issue.type) { case 'schema_changed': case 'url_changed': return 'stale' default: return 'unavailable' } } export function getIssueBadgeVariant(issue: McpToolIssue): 'amber' | 'red' { switch (issue.type) { case 'schema_changed': case 'url_changed': return 'amber' default: return 'red' } } export function isToolUnavailable(issue: McpToolIssue | null): boolean { if (!issue) return false return ( issue.type === 'server_not_found' || issue.type === 'server_error' || issue.type === 'tool_not_found' ) }