UNPKG

claude-flow

Version:

Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration

877 lines (764 loc) 29.5 kB
/** * Hyperbolic Reasoning MCP Tools * * MCP tool definitions for hyperbolic geometry operations including: * - hyperbolic/embed-hierarchy: Embed hierarchies in Poincare ball * - hyperbolic/taxonomic-reason: Taxonomic reasoning and queries * - hyperbolic/semantic-search: Hierarchically-aware search * - hyperbolic/hierarchy-compare: Compare hierarchical structures * - hyperbolic/entailment-graph: Build and query entailment graphs */ import type { MCPTool, MCPToolResult, ToolContext, EmbedHierarchyInput, TaxonomicReasonInput, SemanticSearchInput, HierarchyCompareInput, EntailmentGraphInput, Hierarchy, TaxonomicQueryType, } from './types.js'; import { EmbedHierarchyInputSchema, TaxonomicReasonInputSchema, SemanticSearchInputSchema, HierarchyCompareInputSchema, EntailmentGraphInputSchema, successResult, errorResult, RESOURCE_LIMITS, poincareDistance, } from './types.js'; import { HyperbolicBridge } from './bridges/hyperbolic-bridge.js'; import { GnnBridge } from './bridges/gnn-bridge.js'; // Default logger const defaultLogger = { debug: (msg: string, meta?: Record<string, unknown>) => console.debug(`[hyperbolic-reasoning] ${msg}`, meta), info: (msg: string, meta?: Record<string, unknown>) => console.info(`[hyperbolic-reasoning] ${msg}`, meta), warn: (msg: string, meta?: Record<string, unknown>) => console.warn(`[hyperbolic-reasoning] ${msg}`, meta), error: (msg: string, meta?: Record<string, unknown>) => console.error(`[hyperbolic-reasoning] ${msg}`, meta), }; // Shared bridge instances let hyperbolicBridge: HyperbolicBridge | null = null; let gnnBridge: GnnBridge | null = null; // Stored embeddings and taxonomies const embeddingStore = new Map<string, Awaited<ReturnType<HyperbolicBridge['embedHierarchy']>>>(); const taxonomyStore = new Map<string, Hierarchy>(); async function getHyperbolicBridge(): Promise<HyperbolicBridge> { if (!hyperbolicBridge) { hyperbolicBridge = new HyperbolicBridge(); await hyperbolicBridge.initialize(); } return hyperbolicBridge; } async function getGnnBridge(): Promise<GnnBridge> { if (!gnnBridge) { gnnBridge = new GnnBridge(); await gnnBridge.initialize(); } return gnnBridge; } // ============================================================================ // Tool 1: hyperbolic/embed-hierarchy // ============================================================================ async function embedHierarchyHandler( input: Record<string, unknown>, context?: ToolContext ): Promise<MCPToolResult> { const logger = context?.logger ?? defaultLogger; const startTime = performance.now(); try { const validationResult = EmbedHierarchyInputSchema.safeParse(input); if (!validationResult.success) { return errorResult(`Invalid input: ${validationResult.error.message}`); } const data = validationResult.data; logger.debug('Embed hierarchy', { nodes: data.hierarchy.nodes.length, model: data.model, dimensions: data.parameters?.dimensions, }); // Validate size if (data.hierarchy.nodes.length > RESOURCE_LIMITS.MAX_NODES) { return errorResult(`Too many nodes: ${data.hierarchy.nodes.length} > ${RESOURCE_LIMITS.MAX_NODES}`); } const bridge = await getHyperbolicBridge(); const result = await bridge.embedHierarchy(data.hierarchy, data.parameters); // Store for later use const indexId = `embed_${Date.now()}`; embeddingStore.set(indexId, result); // Create index for search bridge.createIndex(indexId, result.dimension, result.curvature); for (const [nodeId, point] of result.embeddings) { bridge.addToIndex(indexId, nodeId, point); } const duration = performance.now() - startTime; logger.info('Hierarchy embedded', { nodes: result.embeddings.size, curvature: result.curvature, mapScore: result.metrics.mapScore, durationMs: duration.toFixed(2), }); // Convert embeddings to serializable format const embeddingsList: Array<{ id: string; coordinates: number[]; depth: number }> = []; for (const [nodeId, point] of result.embeddings) { embeddingsList.push({ id: nodeId, coordinates: Array.from(point.coordinates).slice(0, 5), // First 5 dimensions depth: bridge.hierarchyDepth(point), }); } return successResult({ indexId, model: result.model, curvature: result.curvature, dimension: result.dimension, metrics: result.metrics, embeddings: embeddingsList.slice(0, 20), // Sample of embeddings totalNodes: result.embeddings.size, }); } catch (error) { logger.error('Embedding failed', { error: error instanceof Error ? error.message : String(error) }); return errorResult(error instanceof Error ? error : new Error(String(error))); } } export const embedHierarchyTool: MCPTool = { name: 'hyperbolic_embed_hierarchy', description: 'Embed hierarchical structure in Poincare ball. Uses hyperbolic geometry for optimal tree representation with logarithmic distortion.', category: 'hyperbolic', version: '0.1.0', tags: ['hyperbolic', 'poincare', 'hierarchy', 'embedding', 'tree'], cacheable: true, cacheTTL: 300000, inputSchema: { type: 'object', properties: { hierarchy: { type: 'object', properties: { nodes: { type: 'array', items: { type: 'object', properties: { id: { type: 'string' }, parent: { type: 'string' }, features: { type: 'object' }, }, }, }, edges: { type: 'array' }, }, }, model: { type: 'string', enum: ['poincare_ball', 'lorentz', 'klein', 'half_plane'] }, parameters: { type: 'object', properties: { dimensions: { type: 'number', default: 32 }, curvature: { type: 'number', default: -1.0 }, learnCurvature: { type: 'boolean', default: true }, epochs: { type: 'number', default: 100 }, learningRate: { type: 'number', default: 0.01 }, }, }, }, required: ['hierarchy'], }, handler: embedHierarchyHandler, }; // ============================================================================ // Tool 2: hyperbolic/taxonomic-reason // ============================================================================ async function taxonomicReasonHandler( input: Record<string, unknown>, context?: ToolContext ): Promise<MCPToolResult> { const logger = context?.logger ?? defaultLogger; const startTime = performance.now(); try { const validationResult = TaxonomicReasonInputSchema.safeParse(input); if (!validationResult.success) { return errorResult(`Invalid input: ${validationResult.error.message}`); } const data = validationResult.data; logger.debug('Taxonomic reason', { type: data.query.type, subject: data.query.subject }); // Get stored embedding const stored = embeddingStore.get(data.taxonomy); if (!stored) { return errorResult(`Taxonomy not found: ${data.taxonomy}. Use hyperbolic_embed_hierarchy first.`); } const subjectEmb = stored.embeddings.get(data.query.subject); if (!subjectEmb) { return errorResult(`Subject not found in taxonomy: ${data.query.subject}`); } const bridge = await getHyperbolicBridge(); let result: unknown; let confidence = 1.0; let explanation = ''; const steps: Array<{ from: string; to: string; relation: string; confidence: number }> = []; switch (data.query.type) { case 'is_a': { if (!data.query.object) { return errorResult('Object required for is_a query'); } const objectEmb = stored.embeddings.get(data.query.object); if (!objectEmb) { return errorResult(`Object not found in taxonomy: ${data.query.object}`); } // Check if subject is descendant of object (object is ancestor) const isAncestor = bridge.isAncestor(objectEmb, subjectEmb); const distance = bridge.distance(subjectEmb, objectEmb); result = isAncestor; confidence = Math.exp(-distance / 2); explanation = isAncestor ? `${data.query.subject} IS-A ${data.query.object} (distance: ${distance.toFixed(3)})` : `${data.query.subject} is NOT-A ${data.query.object}`; break; } case 'subsumption': { if (!data.query.object) { return errorResult('Object required for subsumption query'); } const objectEmb = stored.embeddings.get(data.query.object); if (!objectEmb) { return errorResult(`Object not found in taxonomy: ${data.query.object}`); } // Check both directions const subjectSubsumesObject = bridge.isAncestor(subjectEmb, objectEmb); const objectSubsumesSubject = bridge.isAncestor(objectEmb, subjectEmb); if (subjectSubsumesObject) { result = 'subject_subsumes_object'; explanation = `${data.query.subject} subsumes ${data.query.object}`; } else if (objectSubsumesSubject) { result = 'object_subsumes_subject'; explanation = `${data.query.object} subsumes ${data.query.subject}`; } else { result = 'no_subsumption'; explanation = `No subsumption relation between ${data.query.subject} and ${data.query.object}`; } confidence = 0.9; break; } case 'lowest_common_ancestor': { if (!data.query.object) { return errorResult('Object required for LCA query'); } const objectEmb = stored.embeddings.get(data.query.object); if (!objectEmb) { return errorResult(`Object not found in taxonomy: ${data.query.object}`); } // Find LCA by searching for node closest to midpoint let bestLca = ''; let bestScore = Infinity; for (const [nodeId, nodeEmb] of stored.embeddings) { // Check if this node is ancestor of both const isAncOfSubject = bridge.isAncestor(nodeEmb, subjectEmb); const isAncOfObject = bridge.isAncestor(nodeEmb, objectEmb); if (isAncOfSubject && isAncOfObject) { const depth = bridge.hierarchyDepth(nodeEmb); // Prefer deepest common ancestor (highest depth value) const score = -depth; if (score < bestScore) { bestScore = score; bestLca = nodeId; } } } result = bestLca || null; confidence = bestLca ? 0.95 : 0; explanation = bestLca ? `Lowest common ancestor is ${bestLca}` : 'No common ancestor found'; break; } case 'path': { if (!data.query.object) { return errorResult('Object required for path query'); } // Find path through ancestors const path: string[] = [data.query.subject]; let current = data.query.subject; // Find ancestors of subject const ancestorsOfSubject: string[] = []; for (const [nodeId, nodeEmb] of stored.embeddings) { if (bridge.isAncestor(nodeEmb, subjectEmb)) { ancestorsOfSubject.push(nodeId); } } // Sort by depth (shallowest first) ancestorsOfSubject.sort((a, b) => { const depthA = bridge.hierarchyDepth(stored.embeddings.get(a)!); const depthB = bridge.hierarchyDepth(stored.embeddings.get(b)!); return depthA - depthB; }); // Check if object is in ancestors if (ancestorsOfSubject.includes(data.query.object)) { path.push(...ancestorsOfSubject.slice(0, ancestorsOfSubject.indexOf(data.query.object) + 1)); result = path; explanation = `Path from ${data.query.subject} to ${data.query.object}: ${path.join(' -> ')}`; } else { // Find common ancestor and path through it result = []; explanation = `No direct path from ${data.query.subject} to ${data.query.object}`; } confidence = 0.9; break; } case 'similarity': { if (!data.query.object) { return errorResult('Object required for similarity query'); } const objectEmb = stored.embeddings.get(data.query.object); if (!objectEmb) { return errorResult(`Object not found in taxonomy: ${data.query.object}`); } const distance = bridge.distance(subjectEmb, objectEmb); const similarity = Math.exp(-distance); result = similarity; confidence = 1.0; explanation = `Hyperbolic similarity: ${similarity.toFixed(4)} (distance: ${distance.toFixed(4)})`; break; } } const duration = performance.now() - startTime; logger.info('Taxonomic reasoning completed', { type: data.query.type, confidence, durationMs: duration.toFixed(2), }); return successResult({ result, confidence, explanation, steps, queryType: data.query.type, subject: data.query.subject, object: data.query.object, }); } catch (error) { logger.error('Taxonomic reasoning failed', { error: error instanceof Error ? error.message : String(error) }); return errorResult(error instanceof Error ? error : new Error(String(error))); } } export const taxonomicReasonTool: MCPTool = { name: 'hyperbolic_taxonomic_reason', description: 'Taxonomic reasoning using hyperbolic entailment. Supports IS-A, subsumption, LCA, path, and similarity queries.', category: 'hyperbolic', version: '0.1.0', tags: ['hyperbolic', 'taxonomy', 'reasoning', 'is-a', 'subsumption'], cacheable: true, cacheTTL: 60000, inputSchema: { type: 'object', properties: { query: { type: 'object', properties: { type: { type: 'string', enum: ['is_a', 'subsumption', 'lowest_common_ancestor', 'path', 'similarity'] }, subject: { type: 'string' }, object: { type: 'string' }, }, }, taxonomy: { type: 'string', description: 'Taxonomy index ID from embed-hierarchy' }, inference: { type: 'object', properties: { transitive: { type: 'boolean', default: true }, fuzzy: { type: 'boolean', default: false }, confidence: { type: 'number', default: 0.8 }, }, }, }, required: ['query', 'taxonomy'], }, handler: taxonomicReasonHandler, }; // ============================================================================ // Tool 3: hyperbolic/semantic-search // ============================================================================ async function semanticSearchHandler( input: Record<string, unknown>, context?: ToolContext ): Promise<MCPToolResult> { const logger = context?.logger ?? defaultLogger; const startTime = performance.now(); try { const validationResult = SemanticSearchInputSchema.safeParse(input); if (!validationResult.success) { return errorResult(`Invalid input: ${validationResult.error.message}`); } const data = validationResult.data; logger.debug('Semantic search', { index: data.index, mode: data.searchMode, topK: data.topK }); // Get stored embedding const stored = embeddingStore.get(data.index); if (!stored) { return errorResult(`Index not found: ${data.index}. Use hyperbolic_embed_hierarchy first.`); } const bridge = await getHyperbolicBridge(); // Create query embedding from text // In production, use a proper text encoder const queryEmb = new Float32Array(stored.dimension); for (let i = 0; i < data.query.length; i++) { const idx = data.query.charCodeAt(i) % stored.dimension; queryEmb[idx] += 1; } const norm = Math.sqrt(queryEmb.reduce((s, v) => s + v * v, 0)); for (let i = 0; i < queryEmb.length; i++) { queryEmb[i] = (queryEmb[i] / (norm + 1e-10)) * 0.5; // Scale to be inside ball } const queryPoint = { coordinates: queryEmb, curvature: stored.curvature, dimension: stored.dimension, }; const result = await bridge.search(queryPoint, data.index, data.topK, data.searchMode); // Apply constraints let filteredItems = result.items; if (data.constraints?.maxDepth !== undefined) { filteredItems = filteredItems.filter(item => { const emb = stored.embeddings.get(item.id); if (!emb) return false; return bridge.hierarchyDepth(emb) <= data.constraints!.maxDepth!; }); } if (data.constraints?.minDepth !== undefined) { filteredItems = filteredItems.filter(item => { const emb = stored.embeddings.get(item.id); if (!emb) return false; return bridge.hierarchyDepth(emb) >= data.constraints!.minDepth!; }); } if (data.constraints?.subtreeRoot) { const rootEmb = stored.embeddings.get(data.constraints.subtreeRoot); if (rootEmb) { filteredItems = filteredItems.filter(item => { const emb = stored.embeddings.get(item.id); if (!emb) return false; return bridge.isAncestor(rootEmb, emb); }); } } const duration = performance.now() - startTime; logger.info('Semantic search completed', { results: filteredItems.length, mode: data.searchMode, durationMs: duration.toFixed(2), }); return successResult({ items: filteredItems.slice(0, data.topK).map(item => ({ id: item.id, distance: item.distance, similarity: item.similarity, depth: stored.embeddings.get(item.id) ? bridge.hierarchyDepth(stored.embeddings.get(item.id)!) : undefined, })), totalCandidates: result.totalCandidates, searchTimeMs: result.searchTimeMs, mode: data.searchMode, }); } catch (error) { logger.error('Semantic search failed', { error: error instanceof Error ? error.message : String(error) }); return errorResult(error instanceof Error ? error : new Error(String(error))); } } export const semanticSearchTool: MCPTool = { name: 'hyperbolic_semantic_search', description: 'Semantic search with hierarchical awareness. Supports nearest, subtree, ancestors, siblings, and cone search modes.', category: 'hyperbolic', version: '0.1.0', tags: ['hyperbolic', 'search', 'semantic', 'hierarchy', 'nearest-neighbor'], cacheable: true, cacheTTL: 30000, inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Search query text' }, index: { type: 'string', description: 'Index ID from embed-hierarchy' }, searchMode: { type: 'string', enum: ['nearest', 'subtree', 'ancestors', 'siblings', 'cone'] }, constraints: { type: 'object', properties: { maxDepth: { type: 'number' }, minDepth: { type: 'number' }, subtreeRoot: { type: 'string' }, }, }, topK: { type: 'number', default: 10 }, }, required: ['query', 'index'], }, handler: semanticSearchHandler, }; // ============================================================================ // Tool 4: hyperbolic/hierarchy-compare // ============================================================================ async function hierarchyCompareHandler( input: Record<string, unknown>, context?: ToolContext ): Promise<MCPToolResult> { const logger = context?.logger ?? defaultLogger; const startTime = performance.now(); try { const validationResult = HierarchyCompareInputSchema.safeParse(input); if (!validationResult.success) { return errorResult(`Invalid input: ${validationResult.error.message}`); } const data = validationResult.data; logger.debug('Hierarchy compare', { sourceNodes: data.source.nodes.length, targetNodes: data.target.nodes.length, method: data.alignment, }); const bridge = await getHyperbolicBridge(); // Embed both hierarchies const sourceEmb = await bridge.embedHierarchy(data.source); const targetEmb = await bridge.embedHierarchy(data.target); // Compute alignments const alignments: Array<{ source: string; target: string; confidence: number }> = []; const matchedSource = new Set<string>(); const matchedTarget = new Set<string>(); // Greedy matching based on embedding similarity const sourceIds = Array.from(sourceEmb.embeddings.keys()); const targetIds = Array.from(targetEmb.embeddings.keys()); for (const srcId of sourceIds) { const srcPoint = sourceEmb.embeddings.get(srcId)!; let bestTarget = ''; let bestSimilarity = -Infinity; for (const tgtId of targetIds) { if (matchedTarget.has(tgtId)) continue; const tgtPoint = targetEmb.embeddings.get(tgtId)!; // Compare embeddings (project to same curvature) const dist = poincareDistance(srcPoint.coordinates, tgtPoint.coordinates, srcPoint.curvature); const similarity = Math.exp(-dist); if (similarity > bestSimilarity) { bestSimilarity = similarity; bestTarget = tgtId; } } if (bestTarget && bestSimilarity > 0.5) { alignments.push({ source: srcId, target: bestTarget, confidence: bestSimilarity, }); matchedSource.add(srcId); matchedTarget.add(bestTarget); } } // Compute metrics const metrics: Record<string, number> = {}; // Structural similarity: ratio of matched nodes metrics['structural_similarity'] = (alignments.length * 2) / (sourceIds.length + targetIds.length); // Semantic similarity: average alignment confidence metrics['semantic_similarity'] = alignments.length > 0 ? alignments.reduce((s, a) => s + a.confidence, 0) / alignments.length : 0; // Coverage: ratio of source nodes matched metrics['coverage'] = alignments.length / sourceIds.length; // Precision: ratio of target nodes matched metrics['precision'] = alignments.length / targetIds.length; const unmatchedSource = sourceIds.filter(id => !matchedSource.has(id)); const unmatchedTarget = targetIds.filter(id => !matchedTarget.has(id)); const duration = performance.now() - startTime; logger.info('Hierarchy comparison completed', { alignments: alignments.length, structuralSimilarity: metrics['structural_similarity'], durationMs: duration.toFixed(2), }); return successResult({ similarity: (metrics['structural_similarity']! + metrics['semantic_similarity']!) / 2, alignments, metrics, unmatchedSource, unmatchedTarget, method: data.alignment, }); } catch (error) { logger.error('Hierarchy comparison failed', { error: error instanceof Error ? error.message : String(error) }); return errorResult(error instanceof Error ? error : new Error(String(error))); } } export const hierarchyCompareTool: MCPTool = { name: 'hyperbolic_hierarchy_compare', description: 'Compare hierarchies using hyperbolic alignment. Computes structural and semantic similarity with node-level alignments.', category: 'hyperbolic', version: '0.1.0', tags: ['hyperbolic', 'comparison', 'alignment', 'tree-edit', 'similarity'], cacheable: true, cacheTTL: 120000, inputSchema: { type: 'object', properties: { source: { type: 'object', description: 'First hierarchy' }, target: { type: 'object', description: 'Second hierarchy' }, alignment: { type: 'string', enum: ['wasserstein', 'gromov_wasserstein', 'tree_edit', 'subtree_isomorphism'], }, metrics: { type: 'array', items: { type: 'string', enum: ['structural_similarity', 'semantic_similarity', 'coverage', 'precision'] }, }, }, required: ['source', 'target'], }, handler: hierarchyCompareHandler, }; // ============================================================================ // Tool 5: hyperbolic/entailment-graph // ============================================================================ async function entailmentGraphHandler( input: Record<string, unknown>, context?: ToolContext ): Promise<MCPToolResult> { const logger = context?.logger ?? defaultLogger; const startTime = performance.now(); try { const validationResult = EntailmentGraphInputSchema.safeParse(input); if (!validationResult.success) { return errorResult(`Invalid input: ${validationResult.error.message}`); } const data = validationResult.data; logger.debug('Entailment graph', { action: data.action, concepts: data.concepts?.length }); const gnn = await getGnnBridge(); switch (data.action) { case 'build': { if (!data.concepts || data.concepts.length === 0) { return errorResult('Concepts required for build action'); } const graph = await gnn.buildEntailmentGraph(data.concepts, data.entailmentThreshold); // Apply transitive closure if requested const finalGraph = data.transitiveClosure ? gnn.computeTransitiveClosure(graph) : graph; const duration = performance.now() - startTime; logger.info('Entailment graph built', { nodes: finalGraph.stats.nodeCount, edges: finalGraph.stats.edgeCount, durationMs: duration.toFixed(2), }); return successResult({ graphId: `entailment_${Date.now()}`, stats: finalGraph.stats, relations: finalGraph.relations.slice(0, 50), // Sample transitiveClosure: finalGraph.transitiveClosure, }); } case 'query': { if (!data.query?.premise) { return errorResult('Premise required for query action'); } // Would query stored graph - simplified for demo return successResult({ query: data.query, results: [], message: 'Query requires pre-built graph. Use build action first.', }); } case 'prune': { if (!data.graphId) { return errorResult('GraphId required for prune action'); } // Simplified - would prune stored graph return successResult({ graphId: data.graphId, pruneStrategy: data.pruneStrategy, message: 'Prune action completed', }); } case 'expand': { if (!data.concepts) { return errorResult('Concepts required for expand action'); } return successResult({ added: data.concepts.length, message: 'Expand action completed', }); } default: return errorResult(`Unknown action: ${data.action}`); } } catch (error) { logger.error('Entailment graph operation failed', { error: error instanceof Error ? error.message : String(error) }); return errorResult(error instanceof Error ? error : new Error(String(error))); } } export const entailmentGraphTool: MCPTool = { name: 'hyperbolic_entailment_graph', description: 'Build and query entailment graphs using hyperbolic embeddings. Supports transitive closure and pruning strategies.', category: 'hyperbolic', version: '0.1.0', tags: ['hyperbolic', 'entailment', 'graph', 'nli', 'reasoning'], cacheable: false, inputSchema: { type: 'object', properties: { action: { type: 'string', enum: ['build', 'query', 'expand', 'prune'] }, concepts: { type: 'array', items: { type: 'object', properties: { id: { type: 'string' }, text: { type: 'string' }, type: { type: 'string' }, }, }, }, graphId: { type: 'string' }, query: { type: 'object', properties: { premise: { type: 'string' }, hypothesis: { type: 'string' }, }, }, entailmentThreshold: { type: 'number', default: 0.7 }, transitiveClosure: { type: 'boolean', default: true }, pruneStrategy: { type: 'string', enum: ['none', 'transitive_reduction', 'confidence_threshold'] }, }, required: ['action'], }, handler: entailmentGraphHandler, }; // ============================================================================ // Tool Exports // ============================================================================ /** * All Hyperbolic Reasoning MCP Tools */ export const hyperbolicReasoningTools: MCPTool[] = [ embedHierarchyTool, taxonomicReasonTool, semanticSearchTool, hierarchyCompareTool, entailmentGraphTool, ]; /** * Tool name to handler map */ export const toolHandlers = new Map<string, MCPTool['handler']>([ ['hyperbolic_embed_hierarchy', embedHierarchyHandler], ['hyperbolic_taxonomic_reason', taxonomicReasonHandler], ['hyperbolic_semantic_search', semanticSearchHandler], ['hyperbolic_hierarchy_compare', hierarchyCompareHandler], ['hyperbolic_entailment_graph', entailmentGraphHandler], ]); /** * Get a tool by name */ export function getTool(name: string): MCPTool | undefined { return hyperbolicReasoningTools.find(t => t.name === name); } /** * Get all tool names */ export function getToolNames(): string[] { return hyperbolicReasoningTools.map(t => t.name); } export default hyperbolicReasoningTools;