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
425 lines (372 loc) • 18.5 kB
text/typescript
/**
* MCP Tool Optimizer Plugin
*
* Learn tool usage patterns and suggest optimal tool sequences.
* Uses @ruvector/wasm for pattern storage and @ruvector/learning-wasm for optimization.
*
* Features:
* - Track tool usage patterns
* - Learn successful tool sequences
* - Suggest optimal tool combinations
* - Identify redundant tool calls
* - Performance optimization recommendations
*
* @example
* ```typescript
* import { mcpToolOptimizerPlugin } from '@claude-flow/plugins/examples/ruvector-plugins';
* await getDefaultRegistry().register(mcpToolOptimizerPlugin);
* ```
*/
import {
PluginBuilder,
MCPToolBuilder,
HookBuilder,
HookEvent,
HookPriority,
Security,
} from '../../src/index.js';
// Import shared vector utilities (consolidated from all plugins)
import {
IVectorDB,
ILoRAEngine,
createVectorDB,
createLoRAEngine,
generateHashEmbedding,
} from './shared/vector-utils.js';
// ============================================================================
// Types
// ============================================================================
export interface ToolUsagePattern {
id: string;
toolName: string;
context: string;
inputPatterns: string[];
outcome: 'success' | 'failure' | 'partial';
duration: number;
followedBy?: string[];
precededBy?: string[];
metadata: {
usageCount: number;
avgDuration: number;
successRate: number;
lastUsed: Date;
};
}
export interface ToolSequence {
id: string;
tools: string[];
context: string;
outcome: 'success' | 'failure';
totalDuration: number;
efficiency: number;
embedding?: Float32Array;
}
export interface OptimizationSuggestion {
type: 'sequence' | 'replacement' | 'parallel' | 'removal';
description: string;
currentTools: string[];
suggestedTools: string[];
expectedImprovement: number;
confidence: number;
}
// ============================================================================
// MCP Tool Optimizer Core
// ============================================================================
export class MCPToolOptimizer {
private vectorDb: IVectorDB | null = null;
private loraEngine: ILoRAEngine | null = null;
private patterns = new Map<string, ToolUsagePattern>();
private sequences = new Map<string, ToolSequence>();
private currentSession: { tools: string[]; startTime: number; context: string } | null = null;
private dimensions = 512;
private nextId = 1;
private initPromise: Promise<void> | null = null;
private toolRelations = new Map<string, { parallelWith: string[]; alternatives: string[]; bestAfter: string[] }>([
['Glob', { parallelWith: ['Grep'], alternatives: [], bestAfter: [] }],
['Grep', { parallelWith: ['Glob'], alternatives: [], bestAfter: ['Glob'] }],
['Read', { parallelWith: ['Read'], alternatives: [], bestAfter: ['Glob', 'Grep'] }],
['Edit', { parallelWith: [], alternatives: ['Write'], bestAfter: ['Read'] }],
['Write', { parallelWith: [], alternatives: ['Edit'], bestAfter: ['Read'] }],
['Bash', { parallelWith: ['Bash'], alternatives: [], bestAfter: [] }],
]);
async initialize(): Promise<void> {
if (this.vectorDb && this.loraEngine) return;
if (this.initPromise) return this.initPromise;
this.initPromise = (async () => {
this.vectorDb = await createVectorDB(this.dimensions);
this.loraEngine = await createLoRAEngine();
})();
return this.initPromise;
}
private async ensureInitialized(): Promise<{ db: IVectorDB; lora: ILoRAEngine }> {
await this.initialize();
return { db: this.vectorDb!, lora: this.loraEngine! };
}
/**
* Record a tool usage.
*/
async recordUsage(
toolName: string,
context: string,
inputSummary: string,
outcome: 'success' | 'failure' | 'partial',
duration: number
): Promise<ToolUsagePattern> {
const safeToolName = Security.validateString(toolName, { maxLength: 100 });
const safeContext = Security.validateString(context, { maxLength: 500 });
const safeInput = Security.validateString(inputSummary, { maxLength: 500 });
const patternKey = `${safeToolName}:${this.hashContext(safeContext)}`;
let pattern = this.patterns.get(patternKey);
if (pattern) {
pattern.metadata.usageCount++;
pattern.metadata.avgDuration = (pattern.metadata.avgDuration * (pattern.metadata.usageCount - 1) + duration) / pattern.metadata.usageCount;
pattern.metadata.successRate = (pattern.metadata.successRate * (pattern.metadata.usageCount - 1) + (outcome === 'success' ? 1 : 0)) / pattern.metadata.usageCount;
pattern.metadata.lastUsed = new Date();
if (!pattern.inputPatterns.includes(safeInput)) {
pattern.inputPatterns.push(safeInput);
if (pattern.inputPatterns.length > 10) pattern.inputPatterns.shift();
}
} else {
const id = `pattern-${this.nextId++}`;
pattern = {
id, toolName: safeToolName, context: safeContext, inputPatterns: [safeInput], outcome, duration,
followedBy: [], precededBy: [],
metadata: { usageCount: 1, avgDuration: duration, successRate: outcome === 'success' ? 1 : 0, lastUsed: new Date() },
};
this.patterns.set(patternKey, pattern);
}
if (this.currentSession) {
const lastTool = this.currentSession.tools[this.currentSession.tools.length - 1];
if (lastTool) {
const lastPatternKey = `${lastTool}:${this.hashContext(this.currentSession.context)}`;
const lastPattern = this.patterns.get(lastPatternKey);
if (lastPattern) {
if (!lastPattern.followedBy) lastPattern.followedBy = [];
if (!lastPattern.followedBy.includes(safeToolName)) lastPattern.followedBy.push(safeToolName);
}
if (!pattern.precededBy) pattern.precededBy = [];
if (!pattern.precededBy.includes(lastTool)) pattern.precededBy.push(lastTool);
}
this.currentSession.tools.push(safeToolName);
}
return pattern;
}
startSession(context: string): void {
this.currentSession = { tools: [], startTime: Date.now(), context: Security.validateString(context, { maxLength: 500 }) };
}
async endSession(outcome: 'success' | 'failure'): Promise<ToolSequence | null> {
const { db } = await this.ensureInitialized();
if (!this.currentSession || this.currentSession.tools.length === 0) {
this.currentSession = null;
return null;
}
const id = `seq-${this.nextId++}`;
const totalDuration = Date.now() - this.currentSession.startTime;
const uniqueTools = new Set(this.currentSession.tools);
const redundancy = 1 - (uniqueTools.size / this.currentSession.tools.length);
const efficiency = outcome === 'success' ? (1 - redundancy * 0.5) : 0.3;
const embedding = this.generateSequenceEmbedding(this.currentSession.tools, this.currentSession.context);
const sequence: ToolSequence = { id, tools: [...this.currentSession.tools], context: this.currentSession.context, outcome, totalDuration, efficiency, embedding };
db.insert(embedding, id, { tools: sequence.tools.join(','), outcome, efficiency });
this.sequences.set(id, sequence);
this.currentSession = null;
return sequence;
}
async optimize(tools: string[], context: string): Promise<OptimizationSuggestion[]> {
const { db } = await this.ensureInitialized();
const suggestions: OptimizationSuggestion[] = [];
const safeTools = tools.map(t => Security.validateString(t, { maxLength: 100 }));
const safeContext = Security.validateString(context, { maxLength: 500 });
const embedding = this.generateSequenceEmbedding(safeTools, safeContext);
const similarSequences = db.search(embedding, 5)
.map(r => this.sequences.get(r.id))
.filter((s): s is ToolSequence => s !== undefined && s.outcome === 'success' && s.efficiency > 0.7);
// Parallel execution suggestions
for (let i = 0; i < safeTools.length - 1; i++) {
const tool = safeTools[i];
const nextTool = safeTools[i + 1];
const relations = this.toolRelations.get(tool);
if (relations?.parallelWith.includes(nextTool)) {
suggestions.push({ type: 'parallel', description: `Run ${tool} and ${nextTool} in parallel`, currentTools: [tool, nextTool], suggestedTools: [`${tool} || ${nextTool}`], expectedImprovement: 0.4, confidence: 0.8 });
}
}
// Redundant tool suggestions
const toolCounts = new Map<string, number>();
for (const tool of safeTools) toolCounts.set(tool, (toolCounts.get(tool) ?? 0) + 1);
for (const [tool, count] of toolCounts) {
if (count > 2 && !['Read', 'Bash'].includes(tool)) {
suggestions.push({ type: 'removal', description: `Combine ${count} ${tool} calls`, currentTools: Array(count).fill(tool), suggestedTools: [tool], expectedImprovement: 0.3 * (count - 1), confidence: 0.7 });
}
}
// Better sequences from history
for (const seq of similarSequences) {
if (seq.tools.length < safeTools.length && seq.efficiency > 0.8) {
suggestions.push({ type: 'sequence', description: `Similar task completed with fewer tools`, currentTools: safeTools, suggestedTools: seq.tools, expectedImprovement: (safeTools.length - seq.tools.length) * 0.1, confidence: 0.6 });
break;
}
}
return suggestions.sort((a, b) => b.expectedImprovement * b.confidence - a.expectedImprovement * a.confidence);
}
async suggestNext(currentTool: string, context: string): Promise<Array<{ tool: string; probability: number; reason: string }>> {
const suggestions: Array<{ tool: string; probability: number; reason: string }> = [];
const safeTool = Security.validateString(currentTool, { maxLength: 100 });
const patternKey = `${safeTool}:${this.hashContext(context)}`;
const pattern = this.patterns.get(patternKey);
if (pattern?.followedBy) {
const counts = new Map<string, number>();
for (const tool of pattern.followedBy) counts.set(tool, (counts.get(tool) ?? 0) + 1);
const total = pattern.followedBy.length;
for (const [tool, count] of counts) {
suggestions.push({ tool, probability: count / total, reason: `Followed ${safeTool} ${count}x` });
}
}
const relations = this.toolRelations.get(safeTool);
if (relations?.bestAfter.length === 0) {
for (const [tool, rel] of this.toolRelations) {
if (rel.bestAfter.includes(safeTool)) {
suggestions.push({ tool, probability: 0.6, reason: `${tool} often follows ${safeTool}` });
}
}
}
return suggestions.sort((a, b) => b.probability - a.probability).slice(0, 5);
}
getStats(): {
totalPatterns: number;
totalSequences: number;
topTools: Array<{ name: string; usageCount: number; successRate: number; avgDuration: number }>;
avgEfficiency: number;
commonSequences: Array<{ tools: string[]; count: number }>;
} {
const toolStats = new Map<string, { usageCount: number; successTotal: number; durationTotal: number }>();
for (const pattern of this.patterns.values()) {
const existing = toolStats.get(pattern.toolName) ?? { usageCount: 0, successTotal: 0, durationTotal: 0 };
existing.usageCount += pattern.metadata.usageCount;
existing.successTotal += pattern.metadata.successRate * pattern.metadata.usageCount;
existing.durationTotal += pattern.metadata.avgDuration * pattern.metadata.usageCount;
toolStats.set(pattern.toolName, existing);
}
const topTools = Array.from(toolStats.entries())
.map(([name, stats]) => ({ name, usageCount: stats.usageCount, successRate: stats.usageCount > 0 ? stats.successTotal / stats.usageCount : 0, avgDuration: stats.usageCount > 0 ? stats.durationTotal / stats.usageCount : 0 }))
.sort((a, b) => b.usageCount - a.usageCount).slice(0, 10);
let totalEfficiency = 0;
const sequenceCounts = new Map<string, number>();
for (const seq of this.sequences.values()) {
totalEfficiency += seq.efficiency;
const key = seq.tools.join(' → ');
sequenceCounts.set(key, (sequenceCounts.get(key) ?? 0) + 1);
}
const commonSequences = Array.from(sequenceCounts.entries())
.map(([tools, count]) => ({ tools: tools.split(' → '), count }))
.sort((a, b) => b.count - a.count).slice(0, 5);
return { totalPatterns: this.patterns.size, totalSequences: this.sequences.size, topTools, avgEfficiency: this.sequences.size > 0 ? totalEfficiency / this.sequences.size : 0, commonSequences };
}
private hashContext(context: string): string {
let hash = 0;
for (let i = 0; i < context.length; i++) { hash = ((hash << 5) - hash) + context.charCodeAt(i); hash = hash & hash; }
return hash.toString(16);
}
private generateSequenceEmbedding(tools: string[], context: string): Float32Array {
const text = `${tools.join(' ')} ${context}`.toLowerCase();
const embedding = new Float32Array(this.dimensions);
let hash = 0;
for (let i = 0; i < text.length; i++) { hash = ((hash << 5) - hash) + text.charCodeAt(i); hash = hash & hash; }
for (let i = 0; i < this.dimensions; i++) { embedding[i] = Math.sin(hash * (i + 1) * 0.001) * 0.5 + 0.5; }
let norm = 0;
for (let i = 0; i < this.dimensions; i++) norm += embedding[i] * embedding[i];
norm = Math.sqrt(norm);
for (let i = 0; i < this.dimensions; i++) embedding[i] /= norm;
return embedding;
}
}
// ============================================================================
// Plugin Definition
// ============================================================================
let optimizerInstance: MCPToolOptimizer | null = null;
async function getOptimizer(): Promise<MCPToolOptimizer> {
if (!optimizerInstance) {
optimizerInstance = new MCPToolOptimizer();
await optimizerInstance.initialize();
}
return optimizerInstance;
}
export const mcpToolOptimizerPlugin = new PluginBuilder('mcp-tool-optimizer', '1.0.0')
.withDescription('Learn tool patterns and suggest optimal sequences using @ruvector/wasm + @ruvector/learning-wasm')
.withAuthor('Claude Flow Team')
.withTags(['optimization', 'tools', 'patterns', 'ruvector', 'learning', 'hnsw'])
.withMCPTools([
new MCPToolBuilder('tool-optimize')
.withDescription('Get optimization suggestions')
.addStringParam('tools', 'Comma-separated tool names', { required: true })
.addStringParam('context', 'Task context', { required: true })
.withHandler(async (params) => {
try {
const optimizer = await getOptimizer();
const tools = (params.tools as string).split(',').map(t => t.trim());
const suggestions = await optimizer.optimize(tools, params.context as string);
if (suggestions.length === 0) return { content: [{ type: 'text', text: '✅ Tool sequence looks optimal!' }] };
const output = suggestions.map((s, i) => `**${i + 1}. ${s.type.toUpperCase()}** (${(s.confidence * 100).toFixed(0)}%)\n ${s.description}`).join('\n\n');
return { content: [{ type: 'text', text: `🔧 **Optimizations:**\n\n${output}` }] };
} catch (error) {
return { content: [{ type: 'text', text: `❌ Error: ${error instanceof Error ? error.message : String(error)}` }], isError: true };
}
})
.build(),
new MCPToolBuilder('tool-suggest-next')
.withDescription('Suggest next tool')
.addStringParam('currentTool', 'Current tool', { required: true })
.addStringParam('context', 'Context')
.withHandler(async (params) => {
try {
const optimizer = await getOptimizer();
const suggestions = await optimizer.suggestNext(params.currentTool as string, (params.context as string) ?? '');
if (suggestions.length === 0) return { content: [{ type: 'text', text: '🤔 No suggestions.' }] };
const output = suggestions.map((s, i) => `${i + 1}. **${s.tool}** (${(s.probability * 100).toFixed(0)}%) - ${s.reason}`).join('\n');
return { content: [{ type: 'text', text: `💡 **Next tools:**\n\n${output}` }] };
} catch (error) {
return { content: [{ type: 'text', text: `❌ Error: ${error instanceof Error ? error.message : String(error)}` }], isError: true };
}
})
.build(),
new MCPToolBuilder('tool-stats')
.withDescription('Get tool statistics')
.withHandler(async () => {
const optimizer = await getOptimizer();
const stats = optimizer.getStats();
const topToolsOutput = stats.topTools.slice(0, 5).map((t, i) => ` ${i + 1}. ${t.name}: ${t.usageCount} uses (${(t.successRate * 100).toFixed(0)}%)`).join('\n');
return { content: [{ type: 'text', text: `📊 **Tool Optimizer:**\n\n**Patterns:** ${stats.totalPatterns}\n**Sequences:** ${stats.totalSequences}\n**Efficiency:** ${(stats.avgEfficiency * 100).toFixed(1)}%\n\n**Top Tools:**\n${topToolsOutput || ' None'}\n\n**Backend:** @ruvector/wasm + @ruvector/learning-wasm` }] };
})
.build(),
])
.withHooks([
new HookBuilder(HookEvent.PostToolCall)
.withName('tool-usage-record')
.withDescription('Record tool usage')
.withPriority(HookPriority.Low)
.handle(async (ctx) => {
const data = ctx.data as { toolName?: string; context?: string; input?: string; success?: boolean; duration?: number } | undefined;
if (!data?.toolName) return { success: true };
try {
const optimizer = await getOptimizer();
await optimizer.recordUsage(data.toolName, data.context ?? 'unknown', data.input ?? '', data.success ? 'success' : 'failure', data.duration ?? 0);
} catch { /* silent */ }
return { success: true };
})
.build(),
new HookBuilder(HookEvent.PostTaskComplete)
.withName('tool-session-end')
.withDescription('End tool session')
.withPriority(HookPriority.Low)
.handle(async (ctx) => {
const data = ctx.data as { success?: boolean } | undefined;
try { await (await getOptimizer()).endSession(data?.success ? 'success' : 'failure'); } catch { /* silent */ }
return { success: true };
})
.build(),
])
.onInitialize(async (ctx) => {
ctx.logger.info('MCP Tool Optimizer initializing with @ruvector/wasm + @ruvector/learning-wasm...');
await getOptimizer();
ctx.logger.info('MCP Tool Optimizer ready - HNSW + LoRA enabled');
})
.build();
export default mcpToolOptimizerPlugin;