import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
Tool,
} from '@modelcontextprotocol/sdk/types.js';
import { StrudelController } from '../StrudelController.js';
import { PatternStore } from '../PatternStore.js';
import { MusicTheory } from '../services/MusicTheory.js';
import { PatternGenerator } from '../services/PatternGenerator.js';
import { DeepSeekService } from '../services/DeepSeekService.js';
import { readFileSync, existsSync } from 'fs';
import { Logger } from '../utils/Logger.js';
import { PerformanceMonitor } from '../utils/PerformanceMonitor.js';
import { InputValidator } from '../utils/InputValidator.js';
const configPath = './config.json';
const config = existsSync(configPath)
? JSON.parse(readFileSync(configPath, 'utf-8'))
: { headless: false };
export class EnhancedMCPServerFixed {
private server: Server;
private controller: StrudelController;
private store: PatternStore;
private theory: MusicTheory;
private generator: PatternGenerator;
private deepseek: DeepSeekService;
private logger: Logger;
private perfMonitor: PerformanceMonitor;
private sessionHistory: string[] = [];
private undoStack: string[] = [];
private redoStack: string[] = [];
private isInitialized: boolean = false;
private generatedPatterns: Map<string, string> = new Map();
constructor() {
this.server = new Server(
{
name: 'strudel-mcp-enhanced',
version: '2.1.0',
},
{
capabilities: {
tools: {},
},
}
);
this.controller = new StrudelController(config.headless);
this.store = new PatternStore('./patterns');
this.theory = new MusicTheory();
this.generator = new PatternGenerator();
this.deepseek = new DeepSeekService(config.deepseek);
this.logger = new Logger();
this.perfMonitor = new PerformanceMonitor();
this.setupHandlers();
}
private getTools(): Tool[] {
// Same tools as before - keeping the same structure
return [
// Core Control Tools (10)
{
name: 'init',
description: 'Initialize Strudel in browser',
inputSchema: { type: 'object', properties: {} }
},
{
name: 'write',
description: 'Write pattern to editor',
inputSchema: {
type: 'object',
properties: {
pattern: { type: 'string', description: 'Pattern code' }
},
required: ['pattern']
}
},
{
name: 'append',
description: 'Append code to current pattern',
inputSchema: {
type: 'object',
properties: {
code: { type: 'string', description: 'Code to append' }
},
required: ['code']
}
},
{
name: 'insert',
description: 'Insert code at specific line',
inputSchema: {
type: 'object',
properties: {
position: { type: 'number', description: 'Line number' },
code: { type: 'string', description: 'Code to insert' }
},
required: ['position', 'code']
}
},
{
name: 'replace',
description: 'Replace pattern section',
inputSchema: {
type: 'object',
properties: {
search: { type: 'string', description: 'Text to replace' },
replace: { type: 'string', description: 'Replacement text' }
},
required: ['search', 'replace']
}
},
{
name: 'play',
description: 'Start playing pattern',
inputSchema: { type: 'object', properties: {} }
},
{
name: 'pause',
description: 'Pause playback',
inputSchema: { type: 'object', properties: {} }
},
{
name: 'stop',
description: 'Stop playback',
inputSchema: { type: 'object', properties: {} }
},
{
name: 'clear',
description: 'Clear the editor',
inputSchema: { type: 'object', properties: {} }
},
{
name: 'get_pattern',
description: 'Get current pattern code',
inputSchema: { type: 'object', properties: {} }
},
// Pattern Manipulation (10)
{
name: 'transpose',
description: 'Transpose notes by semitones',
inputSchema: {
type: 'object',
properties: {
semitones: { type: 'number', description: 'Semitones to transpose' }
},
required: ['semitones']
}
},
{
name: 'reverse',
description: 'Reverse pattern',
inputSchema: { type: 'object', properties: {} }
},
{
name: 'stretch',
description: 'Time stretch pattern',
inputSchema: {
type: 'object',
properties: {
factor: { type: 'number', description: 'Stretch factor' }
},
required: ['factor']
}
},
{
name: 'quantize',
description: 'Quantize to grid',
inputSchema: {
type: 'object',
properties: {
grid: { type: 'string', description: 'Grid size (e.g., "1/16")' }
},
required: ['grid']
}
},
{
name: 'humanize',
description: 'Add human timing variation',
inputSchema: {
type: 'object',
properties: {
amount: { type: 'number', description: 'Humanization amount (0-1)' }
}
}
},
{
name: 'generate_variation',
description: 'Create pattern variations',
inputSchema: {
type: 'object',
properties: {
type: { type: 'string', description: 'Variation type (subtle/moderate/extreme/glitch/evolving)' }
}
}
},
{
name: 'generate_pattern',
description: 'Generate complete pattern from style',
inputSchema: {
type: 'object',
properties: {
style: { type: 'string', description: 'Music style (techno/house/dnb/ambient/etc)' },
key: { type: 'string', description: 'Musical key' },
bpm: { type: 'number', description: 'Tempo in BPM' }
},
required: ['style']
}
},
{
name: 'generate_drums',
description: 'Generate drum pattern',
inputSchema: {
type: 'object',
properties: {
style: { type: 'string', description: 'Drum style' },
complexity: { type: 'number', description: 'Complexity (0-1)' }
},
required: ['style']
}
},
{
name: 'generate_bassline',
description: 'Generate bassline',
inputSchema: {
type: 'object',
properties: {
key: { type: 'string', description: 'Musical key' },
style: { type: 'string', description: 'Bass style' }
},
required: ['key', 'style']
}
},
{
name: 'generate_melody',
description: 'Generate melody from scale',
inputSchema: {
type: 'object',
properties: {
scale: { type: 'string', description: 'Scale name' },
root: { type: 'string', description: 'Root note' },
length: { type: 'number', description: 'Number of notes' }
},
required: ['scale', 'root']
}
},
// Audio Analysis (5)
{
name: 'analyze',
description: 'Complete audio analysis',
inputSchema: { type: 'object', properties: {} }
},
{
name: 'analyze_spectrum',
description: 'FFT spectrum analysis',
inputSchema: { type: 'object', properties: {} }
},
{
name: 'analyze_rhythm',
description: 'Rhythm analysis',
inputSchema: { type: 'object', properties: {} }
},
{
name: 'detect_tempo',
description: 'BPM detection',
inputSchema: { type: 'object', properties: {} }
},
{
name: 'detect_key',
description: 'Key detection',
inputSchema: { type: 'object', properties: {} }
},
{
name: 'validate_pattern_runtime',
description: 'Validate pattern with runtime error checking (monitors Strudel console for errors)',
inputSchema: {
type: 'object',
properties: {
pattern: { type: 'string', description: 'Pattern code to validate' },
waitMs: { type: 'number', description: 'How long to wait for errors (default 500ms)' }
},
required: ['pattern']
}
},
// Effects & Processing (5)
{
name: 'add_effect',
description: 'Add effect to pattern',
inputSchema: {
type: 'object',
properties: {
effect: { type: 'string', description: 'Effect name' },
params: { type: 'string', description: 'Effect parameters' }
},
required: ['effect']
}
},
{
name: 'remove_effect',
description: 'Remove effect',
inputSchema: {
type: 'object',
properties: {
effect: { type: 'string', description: 'Effect to remove' }
},
required: ['effect']
}
},
{
name: 'set_tempo',
description: 'Set BPM',
inputSchema: {
type: 'object',
properties: {
bpm: { type: 'number', description: 'Tempo in BPM' }
},
required: ['bpm']
}
},
{
name: 'add_swing',
description: 'Add swing to pattern',
inputSchema: {
type: 'object',
properties: {
amount: { type: 'number', description: 'Swing amount (0-1)' }
},
required: ['amount']
}
},
{
name: 'apply_scale',
description: 'Apply scale to notes',
inputSchema: {
type: 'object',
properties: {
scale: { type: 'string', description: 'Scale name' },
root: { type: 'string', description: 'Root note' }
},
required: ['scale', 'root']
}
},
// Session Management (5)
{
name: 'save',
description: 'Save pattern with metadata',
inputSchema: {
type: 'object',
properties: {
name: { type: 'string', description: 'Pattern name' },
tags: { type: 'array', items: { type: 'string' } }
},
required: ['name']
}
},
{
name: 'load',
description: 'Load saved pattern',
inputSchema: {
type: 'object',
properties: {
name: { type: 'string', description: 'Pattern name' }
},
required: ['name']
}
},
{
name: 'list',
description: 'List saved patterns',
inputSchema: {
type: 'object',
properties: {
tag: { type: 'string', description: 'Filter by tag' }
}
}
},
{
name: 'undo',
description: 'Undo last action',
inputSchema: { type: 'object', properties: {} }
},
{
name: 'redo',
description: 'Redo action',
inputSchema: { type: 'object', properties: {} }
},
// Additional Music Theory Tools (5)
{
name: 'generate_scale',
description: 'Generate scale notes',
inputSchema: {
type: 'object',
properties: {
root: { type: 'string', description: 'Root note' },
scale: { type: 'string', description: 'Scale type' }
},
required: ['root', 'scale']
}
},
{
name: 'generate_chord_progression',
description: 'Generate chord progression',
inputSchema: {
type: 'object',
properties: {
key: { type: 'string', description: 'Key' },
style: { type: 'string', description: 'Style (pop/jazz/blues/etc)' }
},
required: ['key', 'style']
}
},
{
name: 'generate_euclidean',
description: 'Generate Euclidean rhythm',
inputSchema: {
type: 'object',
properties: {
hits: { type: 'number', description: 'Number of hits' },
steps: { type: 'number', description: 'Total steps' },
sound: { type: 'string', description: 'Sound to use' }
},
required: ['hits', 'steps']
}
},
{
name: 'generate_polyrhythm',
description: 'Generate polyrhythm',
inputSchema: {
type: 'object',
properties: {
sounds: { type: 'array', items: { type: 'string' }, description: 'Sounds to use' },
patterns: { type: 'array', items: { type: 'number' }, description: 'Pattern numbers' }
},
required: ['sounds', 'patterns']
}
},
{
name: 'generate_fill',
description: 'Generate drum fill',
inputSchema: {
type: 'object',
properties: {
style: { type: 'string', description: 'Fill style' },
bars: { type: 'number', description: 'Number of bars' }
},
required: ['style']
}
},
// Performance Monitoring (2)
{
name: 'performance_report',
description: 'Get performance metrics and bottlenecks',
inputSchema: { type: 'object', properties: {} }
},
{
name: 'memory_usage',
description: 'Get current memory usage statistics',
inputSchema: { type: 'object', properties: {} }
},
// AI-Powered Tools (5) - Requires DEEPSEEK_API_KEY environment variable
{
name: 'ai_generate_pattern',
description: 'Generate a Strudel pattern from natural language description using DeepSeek AI',
inputSchema: {
type: 'object',
properties: {
prompt: { type: 'string', description: 'Natural language description of the desired pattern (e.g., "create a dark ambient drone with slow evolving textures")' },
style: { type: 'string', description: 'Optional music style hint (techno, house, dnb, ambient, jazz, etc.)' },
key: { type: 'string', description: 'Optional musical key (C, Am, F#, etc.)' },
bpm: { type: 'number', description: 'Optional tempo in BPM' }
},
required: ['prompt']
}
},
{
name: 'ai_enhance_pattern',
description: 'Enhance or modify the current pattern using AI suggestions',
inputSchema: {
type: 'object',
properties: {
enhancement_type: { type: 'string', description: 'Type of enhancement: variation, complexity, simplify, style-transfer, fill' },
target_style: { type: 'string', description: 'Target style for style-transfer enhancement' },
intensity: { type: 'number', description: 'Intensity of the enhancement (0-1)' }
},
required: ['enhancement_type']
}
},
{
name: 'ai_explain_pattern',
description: 'Get an AI-generated explanation of what the current pattern does',
inputSchema: { type: 'object', properties: {} }
},
{
name: 'ai_analyze_pattern',
description: 'Get AI analysis of the pattern including style detection, complexity assessment, and improvement suggestions',
inputSchema: { type: 'object', properties: {} }
},
{
name: 'ai_suggest_variations',
description: 'Generate AI-suggested variations of the current pattern',
inputSchema: {
type: 'object',
properties: {
count: { type: 'number', description: 'Number of variations to generate (default: 3, max: 5)' }
}
}
},
{
name: 'ai_status',
description: 'Check if DeepSeek AI features are available and configured',
inputSchema: { type: 'object', properties: {} }
},
{
name: 'ai_test_connection',
description: 'Test DeepSeek API connection with a simple request to diagnose connection issues',
inputSchema: { type: 'object', properties: {} }
}
];
}
private setupHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: this.getTools()
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
this.logger.info(`Executing tool: ${name}`, args);
// Measure performance
const result = await this.perfMonitor.measureAsync(
name,
() => this.executeTool(name, args)
);
return {
content: [{
type: 'text',
text: typeof result === 'string' ? result : JSON.stringify(result, null, 2)
}],
};
} catch (error: any) {
this.logger.error(`Tool execution failed: ${name}`, error);
return {
content: [{
type: 'text',
text: `Error: ${error.message}`
}],
};
}
});
}
private requiresInitialization(toolName: string): boolean {
const toolsRequiringInit = [
'write', 'append', 'insert', 'replace', 'play', 'pause', 'stop',
'clear', 'get_pattern', 'analyze', 'analyze_spectrum', 'analyze_rhythm',
'transpose', 'reverse', 'stretch', 'humanize', 'generate_variation',
'add_effect', 'add_swing', 'set_tempo', 'save', 'undo', 'redo',
'validate_pattern_runtime'
];
const toolsRequiringWrite = [
'generate_pattern', 'generate_drums', 'generate_bassline', 'generate_melody',
'generate_chord_progression', 'generate_euclidean', 'generate_polyrhythm',
'generate_fill'
];
return toolsRequiringInit.includes(toolName) || toolsRequiringWrite.includes(toolName);
}
private async getCurrentPatternSafe(): Promise<string> {
if (!this.isInitialized) {
// Return the last generated pattern if available
const lastPattern = Array.from(this.generatedPatterns.values()).pop();
return lastPattern || '';
}
try {
return await this.controller.getCurrentPattern();
} catch (e) {
return '';
}
}
private async writePatternSafe(pattern: string): Promise<string> {
if (!this.isInitialized) {
// Store the pattern for later use
const id = `pattern_${Date.now()}`;
this.generatedPatterns.set(id, pattern);
return `Pattern generated (initialize Strudel to use it): ${pattern.substring(0, 50)}...`;
}
return await this.controller.writePattern(pattern);
}
private async executeTool(name: string, args: any): Promise<any> {
// Check if tool needs initialization
if (this.requiresInitialization(name) && !this.isInitialized && name !== 'init') {
// For generation tools that don't require browser, handle them specially
const generationTools = [
'generate_pattern', 'generate_drums', 'generate_bassline', 'generate_melody',
'generate_chord_progression', 'generate_euclidean', 'generate_polyrhythm', 'generate_fill'
];
if (!generationTools.includes(name)) {
return `Browser not initialized. Run 'init' first to use ${name}.`;
}
}
// Save current state for undo (only if initialized)
if (['write', 'append', 'insert', 'replace', 'clear'].includes(name) && this.isInitialized) {
try {
const current = await this.controller.getCurrentPattern();
this.undoStack.push(current);
this.redoStack = [];
} catch (e) {
// Controller might not be initialized yet
}
}
switch (name) {
// Core Control
case 'init':
const initResult = await this.controller.initialize();
this.isInitialized = true;
// Write any pending patterns
if (this.generatedPatterns.size > 0) {
const lastPattern = Array.from(this.generatedPatterns.values()).pop();
if (lastPattern) {
await this.controller.writePattern(lastPattern);
return `${initResult}. Loaded generated pattern.`;
}
}
return initResult;
case 'write':
InputValidator.validateStringLength(args.pattern, 'pattern', 10000, true);
return await this.writePatternSafe(args.pattern);
case 'append':
InputValidator.validateStringLength(args.code, 'code', 10000, true);
const current = await this.getCurrentPatternSafe();
return await this.writePatternSafe(current + '\n' + args.code);
case 'insert':
InputValidator.validatePositiveInteger(args.position, 'position');
InputValidator.validateStringLength(args.code, 'code', 10000, true);
const lines = (await this.getCurrentPatternSafe()).split('\n');
lines.splice(args.position, 0, args.code);
return await this.writePatternSafe(lines.join('\n'));
case 'replace':
InputValidator.validateStringLength(args.search, 'search', 1000, true);
InputValidator.validateStringLength(args.replace, 'replace', 10000, true);
const pattern = await this.getCurrentPatternSafe();
const replaced = pattern.replace(args.search, args.replace);
return await this.writePatternSafe(replaced);
case 'play':
return await this.controller.play();
case 'pause':
case 'stop':
return await this.controller.stop();
case 'clear':
return await this.writePatternSafe('');
case 'get_pattern':
return await this.getCurrentPatternSafe();
// Pattern Generation - These can work without browser
case 'generate_pattern':
InputValidator.validateStringLength(args.style, 'style', 100, false);
if (args.key) {
InputValidator.validateRootNote(args.key);
}
if (args.bpm !== undefined) {
InputValidator.validateBPM(args.bpm);
}
const generated = this.generator.generateCompletePattern(
args.style,
args.key || 'C',
args.bpm || 120
);
await this.writePatternSafe(generated);
return `Generated ${args.style} pattern`;
case 'generate_drums':
InputValidator.validateStringLength(args.style, 'style', 100, false);
if (args.complexity !== undefined) {
InputValidator.validateNormalizedValue(args.complexity, 'complexity');
}
const drums = this.generator.generateDrumPattern(args.style, args.complexity || 0.5);
const currentDrum = await this.getCurrentPatternSafe();
const newDrumPattern = currentDrum ? currentDrum + '\n' + drums : drums;
await this.writePatternSafe(newDrumPattern);
return `Generated ${args.style} drums`;
case 'generate_bassline':
InputValidator.validateRootNote(args.key);
InputValidator.validateStringLength(args.style, 'style', 100, false);
const bass = this.generator.generateBassline(args.key, args.style);
const currentBass = await this.getCurrentPatternSafe();
const newBassPattern = currentBass ? currentBass + '\n' + bass : bass;
await this.writePatternSafe(newBassPattern);
return `Generated ${args.style} bassline in ${args.key}`;
case 'generate_melody':
InputValidator.validateRootNote(args.root);
InputValidator.validateScaleName(args.scale);
if (args.length !== undefined) {
InputValidator.validatePositiveInteger(args.length, 'length');
}
const scale = this.theory.generateScale(args.root, args.scale);
const melody = this.generator.generateMelody(scale, args.length || 8);
const currentMelody = await this.getCurrentPatternSafe();
const newMelodyPattern = currentMelody ? currentMelody + '\n' + melody : melody;
await this.writePatternSafe(newMelodyPattern);
return `Generated melody in ${args.root} ${args.scale}`;
// Music Theory - These don't require browser
case 'generate_scale':
InputValidator.validateRootNote(args.root);
InputValidator.validateScaleName(args.scale);
const scaleNotes = this.theory.generateScale(args.root, args.scale);
return `${args.root} ${args.scale} scale: ${scaleNotes.join(', ')}`;
case 'generate_chord_progression':
InputValidator.validateRootNote(args.key);
InputValidator.validateChordStyle(args.style);
const progression = this.theory.generateChordProgression(args.key, args.style);
const chordPattern = this.generator.generateChords(progression);
const currentChords = await this.getCurrentPatternSafe();
const newChordPattern = currentChords ? currentChords + '\n' + chordPattern : chordPattern;
await this.writePatternSafe(newChordPattern);
return `Generated ${args.style} progression in ${args.key}: ${progression}`;
case 'generate_euclidean':
InputValidator.validateEuclidean(args.hits, args.steps);
if (args.sound) {
InputValidator.validateStringLength(args.sound, 'sound', 100, false);
}
const euclidean = this.generator.generateEuclideanPattern(
args.hits,
args.steps,
args.sound || 'bd'
);
const currentEuc = await this.getCurrentPatternSafe();
const newEucPattern = currentEuc ? currentEuc + '\n' + euclidean : euclidean;
await this.writePatternSafe(newEucPattern);
return `Generated Euclidean rhythm (${args.hits}/${args.steps})`;
case 'generate_polyrhythm':
args.sounds.forEach((sound: string) => {
InputValidator.validateStringLength(sound, 'sound', 100, false);
});
args.patterns.forEach((pattern: number) => {
InputValidator.validatePositiveInteger(pattern, 'pattern');
});
const poly = this.generator.generatePolyrhythm(args.sounds, args.patterns);
const currentPoly = await this.getCurrentPatternSafe();
const newPolyPattern = currentPoly ? currentPoly + '\n' + poly : poly;
await this.writePatternSafe(newPolyPattern);
return `Generated polyrhythm`;
case 'generate_fill':
InputValidator.validateStringLength(args.style, 'style', 100, false);
if (args.bars !== undefined) {
InputValidator.validatePositiveInteger(args.bars, 'bars');
}
const fill = this.generator.generateFill(args.style, args.bars || 1);
const currentFill = await this.getCurrentPatternSafe();
const newFillPattern = currentFill ? currentFill + '\n' + fill : fill;
await this.writePatternSafe(newFillPattern);
return `Generated ${args.bars || 1} bar fill`;
// Pattern Manipulation - These require browser
case 'transpose':
// Semitones can be positive or negative, just validate it's a number
if (typeof args.semitones !== 'number' || !Number.isInteger(args.semitones)) {
throw new Error('Semitones must be an integer');
}
const toTranspose = await this.getCurrentPatternSafe();
const transposed = this.transposePattern(toTranspose, args.semitones);
await this.writePatternSafe(transposed);
return `Transposed ${args.semitones} semitones`;
case 'reverse':
const toReverse = await this.getCurrentPatternSafe();
const reversed = toReverse + '.rev';
await this.writePatternSafe(reversed);
return 'Pattern reversed';
case 'stretch':
InputValidator.validateGain(args.factor); // Positive number, use gain validator for simplicity
const toStretch = await this.getCurrentPatternSafe();
const stretched = toStretch + `.slow(${args.factor})`;
await this.writePatternSafe(stretched);
return `Stretched by factor of ${args.factor}`;
case 'humanize':
if (args.amount !== undefined) {
InputValidator.validateNormalizedValue(args.amount, 'amount');
}
const toHumanize = await this.getCurrentPatternSafe();
const humanized = toHumanize + `.nudge(rand.range(-${args.amount || 0.01}, ${args.amount || 0.01}))`;
await this.writePatternSafe(humanized);
return 'Added human timing';
case 'generate_variation':
const toVary = await this.getCurrentPatternSafe();
const varied = this.generator.generateVariation(toVary, args.type || 'subtle');
await this.writePatternSafe(varied);
return `Added ${args.type || 'subtle'} variation`;
// Effects - These require browser
case 'add_effect':
InputValidator.validateStringLength(args.effect, 'effect', 100, false);
if (args.params) {
InputValidator.validateStringLength(args.params, 'params', 1000, true);
}
const currentEffect = await this.getCurrentPatternSafe();
const withEffect = args.params
? currentEffect + `.${args.effect}(${args.params})`
: currentEffect + `.${args.effect}()`;
await this.writePatternSafe(withEffect);
return `Added ${args.effect} effect`;
case 'add_swing':
InputValidator.validateNormalizedValue(args.amount, 'amount');
const currentSwing = await this.getCurrentPatternSafe();
const withSwing = currentSwing + `.swing(${args.amount})`;
await this.writePatternSafe(withSwing);
return `Added swing: ${args.amount}`;
case 'set_tempo':
InputValidator.validateBPM(args.bpm);
const currentTempo = await this.getCurrentPatternSafe();
const withTempo = `setcpm(${args.bpm})\n${currentTempo}`;
await this.writePatternSafe(withTempo);
return `Set tempo to ${args.bpm} BPM`;
// Audio Analysis - Requires browser
case 'analyze':
if (!this.isInitialized) {
return 'Browser not initialized. Run init first.';
}
return await this.controller.analyzeAudio();
case 'analyze_spectrum':
if (!this.isInitialized) {
return 'Browser not initialized. Run init first.';
}
const spectrum = await this.controller.analyzeAudio();
return spectrum.features || spectrum;
case 'analyze_rhythm':
if (!this.isInitialized) {
return 'Browser not initialized. Run init first.';
}
const analysis = await this.controller.analyzeAudio();
return {
isPlaying: analysis.features?.isPlaying,
tempo: 'Analysis pending implementation',
pattern: 'Rhythm pattern analysis'
};
case 'detect_tempo':
if (!this.isInitialized) {
return 'Browser not initialized. Run init first.';
}
try {
const tempoAnalysis = await this.controller.detectTempo();
if (!tempoAnalysis || tempoAnalysis.bpm === 0) {
return {
bpm: 0,
confidence: 0,
message: 'No tempo detected. Ensure audio is playing and has a clear rhythmic pattern.'
};
}
return {
bpm: tempoAnalysis.bpm,
confidence: Math.round(tempoAnalysis.confidence * 100) / 100,
method: tempoAnalysis.method,
message: `Detected ${tempoAnalysis.bpm} BPM with ${Math.round(tempoAnalysis.confidence * 100)}% confidence`
};
} catch (error: any) {
return {
bpm: 0,
confidence: 0,
error: error.message || 'Tempo detection failed'
};
}
case 'detect_key':
if (!this.isInitialized) {
return 'Browser not initialized. Run init first.';
}
try {
const keyAnalysis = await this.controller.detectKey();
if (!keyAnalysis || keyAnalysis.confidence < 0.1) {
return {
key: 'Unknown',
scale: 'unknown',
confidence: 0,
message: 'No clear key detected. Ensure audio is playing and has tonal content.'
};
}
const result: any = {
key: keyAnalysis.key,
scale: keyAnalysis.scale,
confidence: Math.round(keyAnalysis.confidence * 100) / 100,
message: `Detected ${keyAnalysis.key} ${keyAnalysis.scale} with ${Math.round(keyAnalysis.confidence * 100)}% confidence`
};
// Include alternatives if available and confidence is moderate
if (keyAnalysis.alternatives && keyAnalysis.alternatives.length > 0) {
result.alternatives = keyAnalysis.alternatives.map((alt: any) => ({
key: alt.key,
scale: alt.scale,
confidence: Math.round(alt.confidence * 100) / 100
}));
}
return result;
} catch (error: any) {
return {
key: 'Unknown',
scale: 'unknown',
confidence: 0,
error: error.message || 'Key detection failed'
};
}
case 'validate_pattern_runtime':
if (!this.isInitialized) {
return 'Browser not initialized. Run init first.';
}
InputValidator.validateStringLength(args.pattern, 'pattern', 10000, false);
const validation = await this.controller.validatePatternRuntime(
args.pattern,
args.waitMs || 500
);
if (validation.valid) {
return `✅ Pattern valid - no runtime errors detected`;
} else {
return `❌ Pattern has runtime errors:\n${validation.errors.join('\n')}\n` +
(validation.warnings.length > 0 ? `\nWarnings:\n${validation.warnings.join('\n')}` : '');
}
// Session Management
case 'save':
InputValidator.validateStringLength(args.name, 'name', 255, false);
const toSave = await this.getCurrentPatternSafe();
if (!toSave) {
return 'No pattern to save';
}
await this.store.save(args.name, toSave, args.tags || []);
return `Pattern saved as "${args.name}"`;
case 'load':
InputValidator.validateStringLength(args.name, 'name', 255, false);
const saved = await this.store.load(args.name);
if (saved) {
await this.writePatternSafe(saved.content);
return `Loaded pattern "${args.name}"`;
}
return `Pattern "${args.name}" not found`;
case 'list':
if (args?.tag) {
InputValidator.validateStringLength(args.tag, 'tag', 100, false);
}
const patterns = await this.store.list(args?.tag);
return patterns.map(p =>
`• ${p.name} [${p.tags.join(', ')}] - ${p.timestamp}`
).join('\n') || 'No patterns found';
case 'undo':
if (!this.isInitialized) {
return 'Browser not initialized. Run init first.';
}
if (this.undoStack.length > 0) {
const currentUndo = await this.controller.getCurrentPattern();
this.redoStack.push(currentUndo);
const previous = this.undoStack.pop()!;
await this.controller.writePattern(previous);
return 'Undone';
}
return 'Nothing to undo';
case 'redo':
if (!this.isInitialized) {
return 'Browser not initialized. Run init first.';
}
if (this.redoStack.length > 0) {
const currentRedo = await this.controller.getCurrentPattern();
this.undoStack.push(currentRedo);
const next = this.redoStack.pop()!;
await this.controller.writePattern(next);
return 'Redone';
}
return 'Nothing to redo';
// Performance Monitoring
case 'performance_report':
const report = this.perfMonitor.getReport();
const bottlenecks = this.perfMonitor.getBottlenecks(5);
return `${report}\n\nTop 5 Bottlenecks:\n${JSON.stringify(bottlenecks, null, 2)}`;
case 'memory_usage':
const memory = this.perfMonitor.getMemoryUsage();
return memory ? JSON.stringify(memory, null, 2) : 'Memory usage not available';
// AI-Powered Tools
case 'ai_generate_pattern':
InputValidator.validateStringLength(args.prompt, 'prompt', 2000, true);
if (args.style) {
InputValidator.validateStringLength(args.style, 'style', 100, false);
}
if (args.key) {
InputValidator.validateRootNote(args.key);
}
if (args.bpm !== undefined) {
InputValidator.validateBPM(args.bpm);
}
const aiResult = await this.deepseek.generatePatternFromPrompt({
prompt: args.prompt,
style: args.style,
key: args.key,
bpm: args.bpm
});
if (aiResult.success && aiResult.pattern) {
await this.writePatternSafe(aiResult.pattern);
if (aiResult.isFallback && aiResult.error) {
return `⚠️ AI generation failed, using fallback:\n\nError: ${aiResult.error}\n\nFallback pattern:\n${aiResult.pattern}`;
}
return `Generated pattern:\n${aiResult.pattern}`;
}
return `Pattern generation failed: ${aiResult.error || 'Unknown error'}`;
case 'ai_enhance_pattern':
const patternToEnhance = await this.getCurrentPatternSafe();
if (!patternToEnhance) {
return 'No pattern to enhance. Write or generate a pattern first.';
}
const enhanceResult = await this.deepseek.enhancePattern({
pattern: patternToEnhance,
enhancementType: args.enhancement_type as any,
targetStyle: args.target_style,
intensity: args.intensity
});
if (enhanceResult.success && enhanceResult.pattern) {
await this.writePatternSafe(enhanceResult.pattern);
if (enhanceResult.isFallback && enhanceResult.error) {
return `⚠️ AI enhancement failed, using fallback:\n\nError: ${enhanceResult.error}\n\nFallback pattern:\n${enhanceResult.pattern}`;
}
return `Pattern enhanced:\n${enhanceResult.pattern}`;
}
return `Enhancement failed: ${enhanceResult.error || 'Unknown error'}`;
case 'ai_explain_pattern':
const patternToExplain = await this.getCurrentPatternSafe();
if (!patternToExplain) {
return 'No pattern to explain. Write or generate a pattern first.';
}
const explanation = await this.deepseek.explainPattern(patternToExplain);
return explanation;
case 'ai_analyze_pattern':
const patternToAnalyze = await this.getCurrentPatternSafe();
if (!patternToAnalyze) {
return 'No pattern to analyze. Write or generate a pattern first.';
}
const analysisResult = await this.deepseek.analyzePattern(patternToAnalyze);
if (analysisResult.success && analysisResult.analysis) {
return JSON.stringify(analysisResult.analysis, null, 2);
}
return `Analysis failed: ${analysisResult.error || 'Unknown error'}`;
case 'ai_suggest_variations':
const patternForVariations = await this.getCurrentPatternSafe();
if (!patternForVariations) {
return 'No pattern to create variations from. Write or generate a pattern first.';
}
const count = Math.min(args.count || 3, 5);
const variations = await this.deepseek.generateVariations(patternForVariations, count);
const validVariations = variations.filter(v => v.success && v.pattern);
if (validVariations.length === 0) {
const errors = variations.filter(v => v.error).map(v => v.error).join('\n');
return `Failed to generate variations${errors ? ':\n' + errors : ''}`;
}
const fallbackVariations = validVariations.filter(v => v.isFallback);
const varOutput = validVariations.map((v, i) =>
`--- Variation ${i + 1}${v.isFallback ? ' (fallback)' : ''} ---\n${v.pattern}`
).join('\n\n');
const warning = fallbackVariations.length > 0
? `⚠️ Some variations used fallback generation due to API errors.\n\n`
: '';
return `${warning}Generated ${validVariations.length} variations:\n\n${varOutput}`;
case 'ai_status':
const isAvailable = this.deepseek.isAvailable();
const aiConfig = this.deepseek.getConfig();
return {
available: isAvailable,
message: isAvailable
? 'DeepSeek AI is configured and ready'
: 'DeepSeek AI not configured. Set DEEPSEEK_API_KEY environment variable.',
model: aiConfig.model,
fallbackMode: !isAvailable
};
case 'ai_test_connection':
if (!this.deepseek.isAvailable()) {
return {
success: false,
error: 'DeepSeek API not configured. Set DEEPSEEK_API_KEY environment variable.',
details: {
apiKeyPresent: !!process.env.DEEPSEEK_API_KEY,
apiKeyLength: process.env.DEEPSEEK_API_KEY?.length || 0
}
};
}
try {
// Make a simple test API call
const testResult = await this.deepseek.generatePatternFromPrompt({
prompt: 'Generate a simple test pattern: just a kick drum on every beat',
style: 'techno'
});
if (testResult.success && !testResult.isFallback) {
return {
success: true,
message: 'DeepSeek API connection successful!',
details: {
model: testResult.model,
tokensUsed: testResult.usage?.totalTokens,
responseTime: 'OK'
}
};
} else {
return {
success: false,
error: testResult.error || 'API call returned fallback result',
message: 'API connection test failed - using fallback generation',
details: {
isFallback: testResult.isFallback,
errorDetails: testResult.error
}
};
}
} catch (error: any) {
let errorMessage = error.message || 'Unknown error';
if (error.status) errorMessage += ` (HTTP ${error.status})`;
if (error.code) errorMessage += ` [${error.code}]`;
if (error.response?.data?.error?.message) {
errorMessage = error.response.data.error.message;
if (error.status) errorMessage += ` (HTTP ${error.status})`;
}
return {
success: false,
error: errorMessage,
message: 'API connection test failed',
details: {
errorType: error.type || 'unknown',
statusCode: error.status,
errorCode: error.code
}
};
}
default:
throw new Error(`Unknown tool: ${name}`);
}
}
private transposePattern(pattern: string, semitones: number): string {
// Simple transpose implementation - would need more sophisticated parsing
return pattern.replace(/([a-g][#b]?)(\d)/gi, (match, note, octave) => {
const noteMap: Record<string, number> = {
'c': 0, 'c#': 1, 'd': 2, 'd#': 3, 'e': 4, 'f': 5,
'f#': 6, 'g': 7, 'g#': 8, 'a': 9, 'a#': 10, 'b': 11
};
const currentNote = note.toLowerCase();
const noteValue = noteMap[currentNote] || 0;
const newNoteValue = (noteValue + semitones + 12) % 12;
const noteNames = ['c', 'c#', 'd', 'd#', 'e', 'f', 'f#', 'g', 'g#', 'a', 'a#', 'b'];
const newOctave = parseInt(octave) + Math.floor((noteValue + semitones) / 12);
return noteNames[newNoteValue] + newOctave;
});
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
this.logger.info('Enhanced Strudel MCP server v2.0.1 running (fixed)');
process.on('SIGINT', async () => {
this.logger.info('Shutting down...');
await this.controller.cleanup();
process.exit(0);
});
}
}