Skip to main content
Glama
EnhancedMCPServer.tsโ€ข23.7 kB
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 { readFileSync, existsSync } from 'fs'; import { Logger } from '../utils/Logger.js'; const configPath = './config.json'; const config = existsSync(configPath) ? JSON.parse(readFileSync(configPath, 'utf-8')) : { headless: false }; export class EnhancedMCPServer { private server: Server; private controller: StrudelController; private store: PatternStore; private theory: MusicTheory; private generator: PatternGenerator; private logger: Logger; private sessionHistory: string[] = []; private undoStack: string[] = []; private redoStack: string[] = []; constructor() { this.server = new Server( { name: 'strudel-mcp-enhanced', version: '2.0.0', }, { capabilities: { tools: {}, }, } ); this.controller = new StrudelController(config.headless); this.store = new PatternStore('./patterns'); this.theory = new MusicTheory(); this.generator = new PatternGenerator(); this.logger = new Logger(); this.setupHandlers(); } private getTools(): Tool[] { 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: {} } }, // 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'] } } ]; } 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); let result = await 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 async executeTool(name: string, args: any): Promise<any> { // Save current state for undo if (['write', 'append', 'insert', 'replace', 'clear'].includes(name)) { 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': return await this.controller.initialize(); case 'write': return await this.controller.writePattern(args.pattern); case 'append': const current = await this.controller.getCurrentPattern(); return await this.controller.writePattern(current + '\n' + args.code); case 'insert': const lines = (await this.controller.getCurrentPattern()).split('\n'); lines.splice(args.position, 0, args.code); return await this.controller.writePattern(lines.join('\n')); case 'replace': const pattern = await this.controller.getCurrentPattern(); const replaced = pattern.replace(args.search, args.replace); return await this.controller.writePattern(replaced); case 'play': return await this.controller.play(); case 'pause': case 'stop': return await this.controller.stop(); case 'clear': return await this.controller.writePattern(''); case 'get_pattern': return await this.controller.getCurrentPattern(); // Pattern Generation case 'generate_pattern': const generated = this.generator.generateCompletePattern( args.style, args.key || 'C', args.bpm || 120 ); await this.controller.writePattern(generated); return `Generated ${args.style} pattern`; case 'generate_drums': const drums = this.generator.generateDrumPattern(args.style, args.complexity || 0.5); const currentDrum = await this.controller.getCurrentPattern(); await this.controller.writePattern(currentDrum + '\n' + drums); return `Added ${args.style} drums`; case 'generate_bassline': const bass = this.generator.generateBassline(args.key, args.style); const currentBass = await this.controller.getCurrentPattern(); await this.controller.writePattern(currentBass + '\n' + bass); return `Added ${args.style} bassline in ${args.key}`; case 'generate_melody': const scale = this.theory.generateScale(args.root, args.scale); const melody = this.generator.generateMelody(scale, args.length || 8); const currentMelody = await this.controller.getCurrentPattern(); await this.controller.writePattern(currentMelody + '\n' + melody); return `Added melody in ${args.root} ${args.scale}`; // Music Theory case 'generate_scale': const scaleNotes = this.theory.generateScale(args.root, args.scale); return `${args.root} ${args.scale} scale: ${scaleNotes.join(', ')}`; case 'generate_chord_progression': const progression = this.theory.generateChordProgression(args.key, args.style); const chordPattern = this.generator.generateChords(progression); const currentChords = await this.controller.getCurrentPattern(); await this.controller.writePattern(currentChords + '\n' + chordPattern); return `Added ${args.style} progression in ${args.key}`; case 'generate_euclidean': const euclidean = this.generator.generateEuclideanPattern( args.hits, args.steps, args.sound || 'bd' ); const currentEuc = await this.controller.getCurrentPattern(); await this.controller.writePattern(currentEuc + '\n' + euclidean); return `Added Euclidean rhythm (${args.hits}/${args.steps})`; case 'generate_polyrhythm': const poly = this.generator.generatePolyrhythm(args.sounds, args.patterns); const currentPoly = await this.controller.getCurrentPattern(); await this.controller.writePattern(currentPoly + '\n' + poly); return `Added polyrhythm`; case 'generate_fill': const fill = this.generator.generateFill(args.style, args.bars || 1); const currentFill = await this.controller.getCurrentPattern(); await this.controller.writePattern(currentFill + '\n' + fill); return `Added ${args.bars || 1} bar fill`; // Pattern Manipulation case 'transpose': const toTranspose = await this.controller.getCurrentPattern(); const transposed = this.transposePattern(toTranspose, args.semitones); await this.controller.writePattern(transposed); return `Transposed ${args.semitones} semitones`; case 'reverse': const toReverse = await this.controller.getCurrentPattern(); const reversed = toReverse + '.rev'; await this.controller.writePattern(reversed); return 'Pattern reversed'; case 'stretch': const toStretch = await this.controller.getCurrentPattern(); const stretched = toStretch + `.slow(${args.factor})`; await this.controller.writePattern(stretched); return `Stretched by factor of ${args.factor}`; case 'humanize': const toHumanize = await this.controller.getCurrentPattern(); const humanized = toHumanize + `.nudge(rand.range(-${args.amount || 0.01}, ${args.amount || 0.01}))`; await this.controller.writePattern(humanized); return 'Added human timing'; case 'generate_variation': const toVary = await this.controller.getCurrentPattern(); const varied = this.generator.generateVariation(toVary, args.type || 'subtle'); await this.controller.writePattern(varied); return `Added ${args.type || 'subtle'} variation`; // Effects case 'add_effect': const currentEffect = await this.controller.getCurrentPattern(); const withEffect = args.params ? currentEffect + `.${args.effect}(${args.params})` : currentEffect + `.${args.effect}()`; await this.controller.writePattern(withEffect); return `Added ${args.effect} effect`; case 'add_swing': const currentSwing = await this.controller.getCurrentPattern(); const withSwing = currentSwing + `.swing(${args.amount})`; await this.controller.writePattern(withSwing); return `Added swing: ${args.amount}`; case 'set_tempo': const currentTempo = await this.controller.getCurrentPattern(); const withTempo = `setcpm(${args.bpm})\n${currentTempo}`; await this.controller.writePattern(withTempo); return `Set tempo to ${args.bpm} BPM`; // Audio Analysis case 'analyze': return await this.controller.analyzeAudio(); case 'analyze_spectrum': const spectrum = await this.controller.analyzeAudio(); return spectrum.features || spectrum; case 'analyze_rhythm': const analysis = await this.controller.analyzeAudio(); return { isPlaying: analysis.features?.isPlaying, tempo: 'Analysis pending implementation', pattern: 'Rhythm pattern analysis' }; case 'detect_tempo': return 'Tempo detection: Coming soon'; case 'detect_key': return 'Key detection: Coming soon'; // Session Management case 'save': const toSave = await this.controller.getCurrentPattern(); await this.store.save(args.name, toSave, args.tags || []); return `Pattern saved as "${args.name}"`; case 'load': const saved = await this.store.load(args.name); if (saved) { await this.controller.writePattern(saved.content); return `Loaded pattern "${args.name}"`; } return `Pattern "${args.name}" not found`; case 'list': 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.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.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'; 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 running'); process.on('SIGINT', async () => { this.logger.info('Shutting down...'); await this.controller.cleanup(); process.exit(0); }); } }

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/williamzujkowski/strudel-mcp-server'

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