@williamzujkowski/live-coding-music-mcp 1.0.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,654 +0,0 @@
1
- import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
- import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
4
- import { StrudelController } from '../StrudelController.js';
5
- import { PatternStore } from '../PatternStore.js';
6
- import { MusicTheory } from '../services/MusicTheory.js';
7
- import { PatternGenerator } from '../services/PatternGenerator.js';
8
- import { readFileSync, existsSync } from 'fs';
9
- import { Logger } from '../utils/Logger.js';
10
- const configPath = './config.json';
11
- const config = existsSync(configPath)
12
- ? JSON.parse(readFileSync(configPath, 'utf-8'))
13
- : { headless: false };
14
- export class EnhancedMCPServer {
15
- server;
16
- controller;
17
- store;
18
- theory;
19
- generator;
20
- logger;
21
- sessionHistory = [];
22
- undoStack = [];
23
- redoStack = [];
24
- constructor() {
25
- this.server = new Server({
26
- name: 'strudel-mcp-enhanced',
27
- version: '2.0.0',
28
- }, {
29
- capabilities: {
30
- tools: {},
31
- },
32
- });
33
- this.controller = new StrudelController(config.headless);
34
- this.store = new PatternStore('./patterns');
35
- this.theory = new MusicTheory();
36
- this.generator = new PatternGenerator();
37
- this.logger = new Logger();
38
- this.setupHandlers();
39
- }
40
- getTools() {
41
- return [
42
- // Core Control Tools (10)
43
- {
44
- name: 'init',
45
- description: 'Initialize Strudel in browser',
46
- inputSchema: { type: 'object', properties: {} }
47
- },
48
- {
49
- name: 'write',
50
- description: 'Write pattern to editor',
51
- inputSchema: {
52
- type: 'object',
53
- properties: {
54
- pattern: { type: 'string', description: 'Pattern code' }
55
- },
56
- required: ['pattern']
57
- }
58
- },
59
- {
60
- name: 'append',
61
- description: 'Append code to current pattern',
62
- inputSchema: {
63
- type: 'object',
64
- properties: {
65
- code: { type: 'string', description: 'Code to append' }
66
- },
67
- required: ['code']
68
- }
69
- },
70
- {
71
- name: 'insert',
72
- description: 'Insert code at specific line',
73
- inputSchema: {
74
- type: 'object',
75
- properties: {
76
- position: { type: 'number', description: 'Line number' },
77
- code: { type: 'string', description: 'Code to insert' }
78
- },
79
- required: ['position', 'code']
80
- }
81
- },
82
- {
83
- name: 'replace',
84
- description: 'Replace pattern section',
85
- inputSchema: {
86
- type: 'object',
87
- properties: {
88
- search: { type: 'string', description: 'Text to replace' },
89
- replace: { type: 'string', description: 'Replacement text' }
90
- },
91
- required: ['search', 'replace']
92
- }
93
- },
94
- {
95
- name: 'play',
96
- description: 'Start playing pattern',
97
- inputSchema: { type: 'object', properties: {} }
98
- },
99
- {
100
- name: 'pause',
101
- description: 'Pause playback',
102
- inputSchema: { type: 'object', properties: {} }
103
- },
104
- {
105
- name: 'stop',
106
- description: 'Stop playback',
107
- inputSchema: { type: 'object', properties: {} }
108
- },
109
- {
110
- name: 'clear',
111
- description: 'Clear the editor',
112
- inputSchema: { type: 'object', properties: {} }
113
- },
114
- {
115
- name: 'get_pattern',
116
- description: 'Get current pattern code',
117
- inputSchema: { type: 'object', properties: {} }
118
- },
119
- // Pattern Manipulation (10)
120
- {
121
- name: 'transpose',
122
- description: 'Transpose notes by semitones',
123
- inputSchema: {
124
- type: 'object',
125
- properties: {
126
- semitones: { type: 'number', description: 'Semitones to transpose' }
127
- },
128
- required: ['semitones']
129
- }
130
- },
131
- {
132
- name: 'reverse',
133
- description: 'Reverse pattern',
134
- inputSchema: { type: 'object', properties: {} }
135
- },
136
- {
137
- name: 'stretch',
138
- description: 'Time stretch pattern',
139
- inputSchema: {
140
- type: 'object',
141
- properties: {
142
- factor: { type: 'number', description: 'Stretch factor' }
143
- },
144
- required: ['factor']
145
- }
146
- },
147
- {
148
- name: 'quantize',
149
- description: 'Quantize to grid',
150
- inputSchema: {
151
- type: 'object',
152
- properties: {
153
- grid: { type: 'string', description: 'Grid size (e.g., "1/16")' }
154
- },
155
- required: ['grid']
156
- }
157
- },
158
- {
159
- name: 'humanize',
160
- description: 'Add human timing variation',
161
- inputSchema: {
162
- type: 'object',
163
- properties: {
164
- amount: { type: 'number', description: 'Humanization amount (0-1)' }
165
- }
166
- }
167
- },
168
- {
169
- name: 'generate_variation',
170
- description: 'Create pattern variations',
171
- inputSchema: {
172
- type: 'object',
173
- properties: {
174
- type: { type: 'string', description: 'Variation type (subtle/moderate/extreme/glitch/evolving)' }
175
- }
176
- }
177
- },
178
- {
179
- name: 'generate_pattern',
180
- description: 'Generate complete pattern from style',
181
- inputSchema: {
182
- type: 'object',
183
- properties: {
184
- style: { type: 'string', description: 'Music style (techno/house/dnb/ambient/etc)' },
185
- key: { type: 'string', description: 'Musical key' },
186
- bpm: { type: 'number', description: 'Tempo in BPM' }
187
- },
188
- required: ['style']
189
- }
190
- },
191
- {
192
- name: 'generate_drums',
193
- description: 'Generate drum pattern',
194
- inputSchema: {
195
- type: 'object',
196
- properties: {
197
- style: { type: 'string', description: 'Drum style' },
198
- complexity: { type: 'number', description: 'Complexity (0-1)' }
199
- },
200
- required: ['style']
201
- }
202
- },
203
- {
204
- name: 'generate_bassline',
205
- description: 'Generate bassline',
206
- inputSchema: {
207
- type: 'object',
208
- properties: {
209
- key: { type: 'string', description: 'Musical key' },
210
- style: { type: 'string', description: 'Bass style' }
211
- },
212
- required: ['key', 'style']
213
- }
214
- },
215
- {
216
- name: 'generate_melody',
217
- description: 'Generate melody from scale',
218
- inputSchema: {
219
- type: 'object',
220
- properties: {
221
- scale: { type: 'string', description: 'Scale name' },
222
- root: { type: 'string', description: 'Root note' },
223
- length: { type: 'number', description: 'Number of notes' }
224
- },
225
- required: ['scale', 'root']
226
- }
227
- },
228
- // Audio Analysis (5)
229
- {
230
- name: 'analyze',
231
- description: 'Complete audio analysis',
232
- inputSchema: { type: 'object', properties: {} }
233
- },
234
- {
235
- name: 'analyze_spectrum',
236
- description: 'FFT spectrum analysis',
237
- inputSchema: { type: 'object', properties: {} }
238
- },
239
- {
240
- name: 'analyze_rhythm',
241
- description: 'Rhythm analysis',
242
- inputSchema: { type: 'object', properties: {} }
243
- },
244
- {
245
- name: 'detect_tempo',
246
- description: 'BPM detection',
247
- inputSchema: { type: 'object', properties: {} }
248
- },
249
- {
250
- name: 'detect_key',
251
- description: 'Key detection',
252
- inputSchema: { type: 'object', properties: {} }
253
- },
254
- // Effects & Processing (5)
255
- {
256
- name: 'add_effect',
257
- description: 'Add effect to pattern',
258
- inputSchema: {
259
- type: 'object',
260
- properties: {
261
- effect: { type: 'string', description: 'Effect name' },
262
- params: { type: 'string', description: 'Effect parameters' }
263
- },
264
- required: ['effect']
265
- }
266
- },
267
- {
268
- name: 'remove_effect',
269
- description: 'Remove effect',
270
- inputSchema: {
271
- type: 'object',
272
- properties: {
273
- effect: { type: 'string', description: 'Effect to remove' }
274
- },
275
- required: ['effect']
276
- }
277
- },
278
- {
279
- name: 'set_tempo',
280
- description: 'Set BPM',
281
- inputSchema: {
282
- type: 'object',
283
- properties: {
284
- bpm: { type: 'number', description: 'Tempo in BPM' }
285
- },
286
- required: ['bpm']
287
- }
288
- },
289
- {
290
- name: 'add_swing',
291
- description: 'Add swing to pattern',
292
- inputSchema: {
293
- type: 'object',
294
- properties: {
295
- amount: { type: 'number', description: 'Swing amount (0-1)' }
296
- },
297
- required: ['amount']
298
- }
299
- },
300
- {
301
- name: 'apply_scale',
302
- description: 'Apply scale to notes',
303
- inputSchema: {
304
- type: 'object',
305
- properties: {
306
- scale: { type: 'string', description: 'Scale name' },
307
- root: { type: 'string', description: 'Root note' }
308
- },
309
- required: ['scale', 'root']
310
- }
311
- },
312
- // Session Management (5)
313
- {
314
- name: 'save',
315
- description: 'Save pattern with metadata',
316
- inputSchema: {
317
- type: 'object',
318
- properties: {
319
- name: { type: 'string', description: 'Pattern name' },
320
- tags: { type: 'array', items: { type: 'string' } }
321
- },
322
- required: ['name']
323
- }
324
- },
325
- {
326
- name: 'load',
327
- description: 'Load saved pattern',
328
- inputSchema: {
329
- type: 'object',
330
- properties: {
331
- name: { type: 'string', description: 'Pattern name' }
332
- },
333
- required: ['name']
334
- }
335
- },
336
- {
337
- name: 'list',
338
- description: 'List saved patterns',
339
- inputSchema: {
340
- type: 'object',
341
- properties: {
342
- tag: { type: 'string', description: 'Filter by tag' }
343
- }
344
- }
345
- },
346
- {
347
- name: 'undo',
348
- description: 'Undo last action',
349
- inputSchema: { type: 'object', properties: {} }
350
- },
351
- {
352
- name: 'redo',
353
- description: 'Redo action',
354
- inputSchema: { type: 'object', properties: {} }
355
- },
356
- // Additional Music Theory Tools (5)
357
- {
358
- name: 'generate_scale',
359
- description: 'Generate scale notes',
360
- inputSchema: {
361
- type: 'object',
362
- properties: {
363
- root: { type: 'string', description: 'Root note' },
364
- scale: { type: 'string', description: 'Scale type' }
365
- },
366
- required: ['root', 'scale']
367
- }
368
- },
369
- {
370
- name: 'generate_chord_progression',
371
- description: 'Generate chord progression',
372
- inputSchema: {
373
- type: 'object',
374
- properties: {
375
- key: { type: 'string', description: 'Key' },
376
- style: { type: 'string', description: 'Style (pop/jazz/blues/etc)' }
377
- },
378
- required: ['key', 'style']
379
- }
380
- },
381
- {
382
- name: 'generate_euclidean',
383
- description: 'Generate Euclidean rhythm',
384
- inputSchema: {
385
- type: 'object',
386
- properties: {
387
- hits: { type: 'number', description: 'Number of hits' },
388
- steps: { type: 'number', description: 'Total steps' },
389
- sound: { type: 'string', description: 'Sound to use' }
390
- },
391
- required: ['hits', 'steps']
392
- }
393
- },
394
- {
395
- name: 'generate_polyrhythm',
396
- description: 'Generate polyrhythm',
397
- inputSchema: {
398
- type: 'object',
399
- properties: {
400
- sounds: { type: 'array', items: { type: 'string' }, description: 'Sounds to use' },
401
- patterns: { type: 'array', items: { type: 'number' }, description: 'Pattern numbers' }
402
- },
403
- required: ['sounds', 'patterns']
404
- }
405
- },
406
- {
407
- name: 'generate_fill',
408
- description: 'Generate drum fill',
409
- inputSchema: {
410
- type: 'object',
411
- properties: {
412
- style: { type: 'string', description: 'Fill style' },
413
- bars: { type: 'number', description: 'Number of bars' }
414
- },
415
- required: ['style']
416
- }
417
- }
418
- ];
419
- }
420
- setupHandlers() {
421
- this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
422
- tools: this.getTools()
423
- }));
424
- this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
425
- const { name, arguments: args } = request.params;
426
- try {
427
- this.logger.info(`Executing tool: ${name}`, args);
428
- let result = await this.executeTool(name, args);
429
- return {
430
- content: [{
431
- type: 'text',
432
- text: typeof result === 'string' ? result : JSON.stringify(result, null, 2)
433
- }],
434
- };
435
- }
436
- catch (error) {
437
- this.logger.error(`Tool execution failed: ${name}`, error);
438
- return {
439
- content: [{
440
- type: 'text',
441
- text: `Error: ${error.message}`
442
- }],
443
- };
444
- }
445
- });
446
- }
447
- async executeTool(name, args) {
448
- // Save current state for undo
449
- if (['write', 'append', 'insert', 'replace', 'clear'].includes(name)) {
450
- try {
451
- const current = await this.controller.getCurrentPattern();
452
- this.undoStack.push(current);
453
- this.redoStack = [];
454
- }
455
- catch (e) {
456
- // Controller might not be initialized yet
457
- }
458
- }
459
- switch (name) {
460
- // Core Control
461
- case 'init':
462
- return await this.controller.initialize();
463
- case 'write':
464
- return await this.controller.writePattern(args.pattern);
465
- case 'append':
466
- const current = await this.controller.getCurrentPattern();
467
- return await this.controller.writePattern(current + '\n' + args.code);
468
- case 'insert':
469
- const lines = (await this.controller.getCurrentPattern()).split('\n');
470
- lines.splice(args.position, 0, args.code);
471
- return await this.controller.writePattern(lines.join('\n'));
472
- case 'replace':
473
- const pattern = await this.controller.getCurrentPattern();
474
- const replaced = pattern.replace(args.search, args.replace);
475
- return await this.controller.writePattern(replaced);
476
- case 'play':
477
- return await this.controller.play();
478
- case 'pause':
479
- case 'stop':
480
- return await this.controller.stop();
481
- case 'clear':
482
- return await this.controller.writePattern('');
483
- case 'get_pattern':
484
- return await this.controller.getCurrentPattern();
485
- // Pattern Generation
486
- case 'generate_pattern':
487
- const generated = this.generator.generateCompletePattern(args.style, args.key || 'C', args.bpm || 120);
488
- await this.controller.writePattern(generated);
489
- return `Generated ${args.style} pattern`;
490
- case 'generate_drums':
491
- const drums = this.generator.generateDrumPattern(args.style, args.complexity || 0.5);
492
- const currentDrum = await this.controller.getCurrentPattern();
493
- await this.controller.writePattern(currentDrum + '\n' + drums);
494
- return `Added ${args.style} drums`;
495
- case 'generate_bassline':
496
- const bass = this.generator.generateBassline(args.key, args.style);
497
- const currentBass = await this.controller.getCurrentPattern();
498
- await this.controller.writePattern(currentBass + '\n' + bass);
499
- return `Added ${args.style} bassline in ${args.key}`;
500
- case 'generate_melody':
501
- const scale = this.theory.generateScale(args.root, args.scale);
502
- const melody = this.generator.generateMelody(scale, args.length || 8);
503
- const currentMelody = await this.controller.getCurrentPattern();
504
- await this.controller.writePattern(currentMelody + '\n' + melody);
505
- return `Added melody in ${args.root} ${args.scale}`;
506
- // Music Theory
507
- case 'generate_scale':
508
- const scaleNotes = this.theory.generateScale(args.root, args.scale);
509
- return `${args.root} ${args.scale} scale: ${scaleNotes.join(', ')}`;
510
- case 'generate_chord_progression':
511
- const progression = this.theory.generateChordProgression(args.key, args.style);
512
- const chordPattern = this.generator.generateChords(progression);
513
- const currentChords = await this.controller.getCurrentPattern();
514
- await this.controller.writePattern(currentChords + '\n' + chordPattern);
515
- return `Added ${args.style} progression in ${args.key}`;
516
- case 'generate_euclidean':
517
- const euclidean = this.generator.generateEuclideanPattern(args.hits, args.steps, args.sound || 'bd');
518
- const currentEuc = await this.controller.getCurrentPattern();
519
- await this.controller.writePattern(currentEuc + '\n' + euclidean);
520
- return `Added Euclidean rhythm (${args.hits}/${args.steps})`;
521
- case 'generate_polyrhythm':
522
- const poly = this.generator.generatePolyrhythm(args.sounds, args.patterns);
523
- const currentPoly = await this.controller.getCurrentPattern();
524
- await this.controller.writePattern(currentPoly + '\n' + poly);
525
- return `Added polyrhythm`;
526
- case 'generate_fill':
527
- const fill = this.generator.generateFill(args.style, args.bars || 1);
528
- const currentFill = await this.controller.getCurrentPattern();
529
- await this.controller.writePattern(currentFill + '\n' + fill);
530
- return `Added ${args.bars || 1} bar fill`;
531
- // Pattern Manipulation
532
- case 'transpose':
533
- const toTranspose = await this.controller.getCurrentPattern();
534
- const transposed = this.transposePattern(toTranspose, args.semitones);
535
- await this.controller.writePattern(transposed);
536
- return `Transposed ${args.semitones} semitones`;
537
- case 'reverse':
538
- const toReverse = await this.controller.getCurrentPattern();
539
- const reversed = toReverse + '.rev';
540
- await this.controller.writePattern(reversed);
541
- return 'Pattern reversed';
542
- case 'stretch':
543
- const toStretch = await this.controller.getCurrentPattern();
544
- const stretched = toStretch + `.slow(${args.factor})`;
545
- await this.controller.writePattern(stretched);
546
- return `Stretched by factor of ${args.factor}`;
547
- case 'humanize':
548
- const toHumanize = await this.controller.getCurrentPattern();
549
- const humanized = toHumanize + `.nudge(rand.range(-${args.amount || 0.01}, ${args.amount || 0.01}))`;
550
- await this.controller.writePattern(humanized);
551
- return 'Added human timing';
552
- case 'generate_variation':
553
- const toVary = await this.controller.getCurrentPattern();
554
- const varied = this.generator.generateVariation(toVary, args.type || 'subtle');
555
- await this.controller.writePattern(varied);
556
- return `Added ${args.type || 'subtle'} variation`;
557
- // Effects
558
- case 'add_effect':
559
- const currentEffect = await this.controller.getCurrentPattern();
560
- const withEffect = args.params
561
- ? currentEffect + `.${args.effect}(${args.params})`
562
- : currentEffect + `.${args.effect}()`;
563
- await this.controller.writePattern(withEffect);
564
- return `Added ${args.effect} effect`;
565
- case 'add_swing':
566
- const currentSwing = await this.controller.getCurrentPattern();
567
- const withSwing = currentSwing + `.swing(${args.amount})`;
568
- await this.controller.writePattern(withSwing);
569
- return `Added swing: ${args.amount}`;
570
- case 'set_tempo':
571
- const currentTempo = await this.controller.getCurrentPattern();
572
- const withTempo = `setcpm(${args.bpm})\n${currentTempo}`;
573
- await this.controller.writePattern(withTempo);
574
- return `Set tempo to ${args.bpm} BPM`;
575
- // Audio Analysis
576
- case 'analyze':
577
- return await this.controller.analyzeAudio();
578
- case 'analyze_spectrum':
579
- const spectrum = await this.controller.analyzeAudio();
580
- return spectrum.features || spectrum;
581
- case 'analyze_rhythm':
582
- const analysis = await this.controller.analyzeAudio();
583
- return {
584
- isPlaying: analysis.features?.isPlaying,
585
- tempo: 'Analysis pending implementation',
586
- pattern: 'Rhythm pattern analysis'
587
- };
588
- case 'detect_tempo':
589
- return 'Tempo detection: Coming soon';
590
- case 'detect_key':
591
- return 'Key detection: Coming soon';
592
- // Session Management
593
- case 'save':
594
- const toSave = await this.controller.getCurrentPattern();
595
- await this.store.save(args.name, toSave, args.tags || []);
596
- return `Pattern saved as "${args.name}"`;
597
- case 'load':
598
- const saved = await this.store.load(args.name);
599
- if (saved) {
600
- await this.controller.writePattern(saved.content);
601
- return `Loaded pattern "${args.name}"`;
602
- }
603
- return `Pattern "${args.name}" not found`;
604
- case 'list':
605
- const patterns = await this.store.list(args?.tag);
606
- return patterns.map(p => `• ${p.name} [${p.tags.join(', ')}] - ${p.timestamp}`).join('\n') || 'No patterns found';
607
- case 'undo':
608
- if (this.undoStack.length > 0) {
609
- const currentUndo = await this.controller.getCurrentPattern();
610
- this.redoStack.push(currentUndo);
611
- const previous = this.undoStack.pop();
612
- await this.controller.writePattern(previous);
613
- return 'Undone';
614
- }
615
- return 'Nothing to undo';
616
- case 'redo':
617
- if (this.redoStack.length > 0) {
618
- const currentRedo = await this.controller.getCurrentPattern();
619
- this.undoStack.push(currentRedo);
620
- const next = this.redoStack.pop();
621
- await this.controller.writePattern(next);
622
- return 'Redone';
623
- }
624
- return 'Nothing to redo';
625
- default:
626
- throw new Error(`Unknown tool: ${name}`);
627
- }
628
- }
629
- transposePattern(pattern, semitones) {
630
- // Simple transpose implementation - would need more sophisticated parsing
631
- return pattern.replace(/([a-g][#b]?)(\d)/gi, (match, note, octave) => {
632
- const noteMap = {
633
- 'c': 0, 'c#': 1, 'd': 2, 'd#': 3, 'e': 4, 'f': 5,
634
- 'f#': 6, 'g': 7, 'g#': 8, 'a': 9, 'a#': 10, 'b': 11
635
- };
636
- const currentNote = note.toLowerCase();
637
- const noteValue = noteMap[currentNote] || 0;
638
- const newNoteValue = (noteValue + semitones + 12) % 12;
639
- const noteNames = ['c', 'c#', 'd', 'd#', 'e', 'f', 'f#', 'g', 'g#', 'a', 'a#', 'b'];
640
- const newOctave = parseInt(octave) + Math.floor((noteValue + semitones) / 12);
641
- return noteNames[newNoteValue] + newOctave;
642
- });
643
- }
644
- async run() {
645
- const transport = new StdioServerTransport();
646
- await this.server.connect(transport);
647
- this.logger.info('Enhanced Strudel MCP server v2.0 running');
648
- process.on('SIGINT', async () => {
649
- this.logger.info('Shutting down...');
650
- await this.controller.cleanup();
651
- process.exit(0);
652
- });
653
- }
654
- }