@williamzujkowski/live-coding-music-mcp 1.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.
- package/LICENSE +21 -0
- package/README.md +1735 -0
- package/dist/AudioAnalyzer.js +678 -0
- package/dist/PatternStore.js +169 -0
- package/dist/StrudelController.js +676 -0
- package/dist/__tests__/MusicTheory.test.js +245 -0
- package/dist/__tests__/PatternGenerator.test.js +337 -0
- package/dist/__tests__/PatternStore.test.js +153 -0
- package/dist/__tests__/utils/MockPlaywright.js +130 -0
- package/dist/index.js +21 -0
- package/dist/server/EnhancedMCPServer.js +654 -0
- package/dist/server/EnhancedMCPServerFixed.js +2506 -0
- package/dist/server/server.js +580 -0
- package/dist/server/tools/ai.js +530 -0
- package/dist/server/tools/analysis.js +132 -0
- package/dist/server/tools/capture.js +184 -0
- package/dist/server/tools/diagnostics.js +96 -0
- package/dist/server/tools/editor.js +128 -0
- package/dist/server/tools/generate.js +218 -0
- package/dist/server/tools/history.js +196 -0
- package/dist/server/tools/playback.js +41 -0
- package/dist/server/tools/session.js +115 -0
- package/dist/server/tools/storage.js +77 -0
- package/dist/server/tools/transform.js +399 -0
- package/dist/server/tools/types.js +9 -0
- package/dist/services/AudioCaptureService.js +279 -0
- package/dist/services/GeminiService.js +790 -0
- package/dist/services/MIDIExportService.js +412 -0
- package/dist/services/MusicTheory.js +186 -0
- package/dist/services/PatternGenerator.js +647 -0
- package/dist/services/SessionManager.js +316 -0
- package/dist/services/StrudelEngine.js +374 -0
- package/dist/services/__mocks__/StrudelEngine.js +157 -0
- package/dist/types/AudioAnalysis.js +4 -0
- package/dist/types/index.js +1 -0
- package/dist/utils/ErrorRecovery.js +248 -0
- package/dist/utils/InputValidator.js +281 -0
- package/dist/utils/Logger.js +19 -0
- package/dist/utils/PatternValidator.js +241 -0
- package/dist/utils/PerformanceMonitor.js +128 -0
- package/package.json +86 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export class Logger {
|
|
2
|
+
getTimestamp() {
|
|
3
|
+
return new Date().toISOString();
|
|
4
|
+
}
|
|
5
|
+
info(message, data) {
|
|
6
|
+
console.error(`[${this.getTimestamp()}] INFO: ${message}`, data ? JSON.stringify(data) : '');
|
|
7
|
+
}
|
|
8
|
+
error(message, error) {
|
|
9
|
+
console.error(`[${this.getTimestamp()}] ERROR: ${message}`, error?.message || error);
|
|
10
|
+
}
|
|
11
|
+
warn(message, data) {
|
|
12
|
+
console.error(`[${this.getTimestamp()}] WARN: ${message}`, data ? JSON.stringify(data) : '');
|
|
13
|
+
}
|
|
14
|
+
debug(message, data) {
|
|
15
|
+
if (process.env.DEBUG) {
|
|
16
|
+
console.error(`[${this.getTimestamp()}] DEBUG: ${message}`, data ? JSON.stringify(data) : '');
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PatternValidator - Validates Strudel patterns before execution
|
|
3
|
+
* Provides syntax checking and error recovery for generated patterns
|
|
4
|
+
*/
|
|
5
|
+
export class PatternValidator {
|
|
6
|
+
/**
|
|
7
|
+
* Validates a Strudel pattern for syntax errors
|
|
8
|
+
* @param pattern - The pattern code to validate
|
|
9
|
+
* @returns ValidationResult with errors and suggestions
|
|
10
|
+
*/
|
|
11
|
+
validate(pattern) {
|
|
12
|
+
const errors = [];
|
|
13
|
+
const warnings = [];
|
|
14
|
+
const suggestions = [];
|
|
15
|
+
// Check for empty pattern
|
|
16
|
+
if (!pattern || pattern.trim().length === 0) {
|
|
17
|
+
errors.push('Pattern is empty');
|
|
18
|
+
suggestions.push('Add a simple pattern like: s("bd*4")');
|
|
19
|
+
return { valid: false, errors, warnings, suggestions };
|
|
20
|
+
}
|
|
21
|
+
// Check for balanced parentheses
|
|
22
|
+
const parenCheck = this.checkBalancedParentheses(pattern);
|
|
23
|
+
if (!parenCheck.valid) {
|
|
24
|
+
errors.push(`Unbalanced parentheses: ${parenCheck.message}`);
|
|
25
|
+
suggestions.push('Check that all ( ) [ ] { } are properly matched');
|
|
26
|
+
}
|
|
27
|
+
// Check for balanced quotes
|
|
28
|
+
const quoteCheck = this.checkBalancedQuotes(pattern);
|
|
29
|
+
if (!quoteCheck.valid) {
|
|
30
|
+
errors.push(`Unbalanced quotes: ${quoteCheck.message}`);
|
|
31
|
+
suggestions.push('Ensure all strings are properly quoted');
|
|
32
|
+
}
|
|
33
|
+
// Check for common Strudel syntax patterns
|
|
34
|
+
const syntaxCheck = this.checkStrudelSyntax(pattern);
|
|
35
|
+
warnings.push(...syntaxCheck.warnings);
|
|
36
|
+
suggestions.push(...syntaxCheck.suggestions);
|
|
37
|
+
// Check for dangerous operations
|
|
38
|
+
const safetyCheck = this.checkSafety(pattern);
|
|
39
|
+
if (!safetyCheck.safe) {
|
|
40
|
+
errors.push(...safetyCheck.errors);
|
|
41
|
+
}
|
|
42
|
+
warnings.push(...safetyCheck.warnings);
|
|
43
|
+
return {
|
|
44
|
+
valid: errors.length === 0,
|
|
45
|
+
errors,
|
|
46
|
+
warnings,
|
|
47
|
+
suggestions
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Checks if parentheses, brackets, and braces are balanced
|
|
52
|
+
*/
|
|
53
|
+
checkBalancedParentheses(pattern) {
|
|
54
|
+
const stack = [];
|
|
55
|
+
const pairs = { '(': ')', '[': ']', '{': '}' };
|
|
56
|
+
const opening = Object.keys(pairs);
|
|
57
|
+
const closing = Object.values(pairs);
|
|
58
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
59
|
+
const char = pattern[i];
|
|
60
|
+
if (opening.includes(char)) {
|
|
61
|
+
stack.push(char);
|
|
62
|
+
}
|
|
63
|
+
else if (closing.includes(char)) {
|
|
64
|
+
if (stack.length === 0) {
|
|
65
|
+
return { valid: false, message: `Unexpected closing '${char}' at position ${i}` };
|
|
66
|
+
}
|
|
67
|
+
const last = stack.pop();
|
|
68
|
+
if (pairs[last] !== char) {
|
|
69
|
+
return { valid: false, message: `Mismatched '${last}' and '${char}' at position ${i}` };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (stack.length > 0) {
|
|
74
|
+
return { valid: false, message: `Unclosed '${stack[stack.length - 1]}'` };
|
|
75
|
+
}
|
|
76
|
+
return { valid: true, message: '' };
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Checks if quotes are balanced
|
|
80
|
+
*/
|
|
81
|
+
checkBalancedQuotes(pattern) {
|
|
82
|
+
let inSingleQuote = false;
|
|
83
|
+
let inDoubleQuote = false;
|
|
84
|
+
let inBacktick = false;
|
|
85
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
86
|
+
const char = pattern[i];
|
|
87
|
+
const prevChar = i > 0 ? pattern[i - 1] : '';
|
|
88
|
+
// Skip escaped quotes
|
|
89
|
+
if (prevChar === '\\') {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (char === "'" && !inDoubleQuote && !inBacktick) {
|
|
93
|
+
inSingleQuote = !inSingleQuote;
|
|
94
|
+
}
|
|
95
|
+
else if (char === '"' && !inSingleQuote && !inBacktick) {
|
|
96
|
+
inDoubleQuote = !inDoubleQuote;
|
|
97
|
+
}
|
|
98
|
+
else if (char === '`' && !inSingleQuote && !inDoubleQuote) {
|
|
99
|
+
inBacktick = !inBacktick;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (inSingleQuote) {
|
|
103
|
+
return { valid: false, message: 'Unclosed single quote' };
|
|
104
|
+
}
|
|
105
|
+
if (inDoubleQuote) {
|
|
106
|
+
return { valid: false, message: 'Unclosed double quote' };
|
|
107
|
+
}
|
|
108
|
+
if (inBacktick) {
|
|
109
|
+
return { valid: false, message: 'Unclosed backtick' };
|
|
110
|
+
}
|
|
111
|
+
return { valid: true, message: '' };
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Checks for common Strudel syntax patterns and potential issues
|
|
115
|
+
*/
|
|
116
|
+
checkStrudelSyntax(pattern) {
|
|
117
|
+
const warnings = [];
|
|
118
|
+
const suggestions = [];
|
|
119
|
+
// Check for common Strudel functions
|
|
120
|
+
const hasSound = /s\(/.test(pattern);
|
|
121
|
+
const hasNote = /note\(/.test(pattern);
|
|
122
|
+
const hasStack = /stack\(/.test(pattern);
|
|
123
|
+
if (!hasSound && !hasNote && !hasStack) {
|
|
124
|
+
warnings.push('Pattern may not produce sound - no s(), note(), or stack() found');
|
|
125
|
+
suggestions.push('Try adding s("bd*4") for a basic kick drum pattern');
|
|
126
|
+
}
|
|
127
|
+
// Check for common mistakes
|
|
128
|
+
if (/s\([^"']/.test(pattern)) {
|
|
129
|
+
warnings.push('Sound patterns should be in quotes: s("bd") not s(bd)');
|
|
130
|
+
}
|
|
131
|
+
// Check for undefined variables
|
|
132
|
+
const functionCalls = pattern.match(/\b([a-z_][a-z0-9_]*)\s*\(/gi);
|
|
133
|
+
if (functionCalls) {
|
|
134
|
+
const knownFunctions = [
|
|
135
|
+
's', 'note', 'stack', 'setcpm', 'sound', 'n', 'room', 'delay',
|
|
136
|
+
'reverb', 'fast', 'slow', 'rev', 'iter', 'jux', 'every', 'sometimes',
|
|
137
|
+
'rarely', 'often', 'gain', 'pan', 'cutoff', 'resonance', 'attack',
|
|
138
|
+
'release', 'sustain', 'decay', 'lpf', 'hpf', 'bpf', 'struct', 'euclid',
|
|
139
|
+
'euclidLegacy', 'choose', 'chooseWith', 'range', 'sine', 'saw', 'square',
|
|
140
|
+
'tri', 'rand', 'perlin', 'add', 'sub', 'mul', 'div', 'mod', 'pow',
|
|
141
|
+
'min', 'max', 'floor', 'ceil', 'round'
|
|
142
|
+
];
|
|
143
|
+
functionCalls.forEach(call => {
|
|
144
|
+
const funcName = call.replace(/\s*\($/, '').toLowerCase();
|
|
145
|
+
if (!knownFunctions.includes(funcName)) {
|
|
146
|
+
warnings.push(`Unknown function: ${funcName}`);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
// Check for tempo setting
|
|
151
|
+
if (!pattern.includes('setcpm') && !pattern.includes('cpm')) {
|
|
152
|
+
suggestions.push('Consider adding setcpm(120) to set tempo');
|
|
153
|
+
}
|
|
154
|
+
return { warnings, suggestions };
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Checks for potentially dangerous operations
|
|
158
|
+
*/
|
|
159
|
+
checkSafety(pattern) {
|
|
160
|
+
const errors = [];
|
|
161
|
+
const warnings = [];
|
|
162
|
+
// Check for excessive gain that could damage speakers
|
|
163
|
+
const gainMatches = pattern.match(/\.gain\(([0-9.]+)\)/g);
|
|
164
|
+
if (gainMatches) {
|
|
165
|
+
gainMatches.forEach(match => {
|
|
166
|
+
const gainValue = parseFloat(match.match(/([0-9.]+)/)?.[0] || '0');
|
|
167
|
+
if (gainValue > 2) {
|
|
168
|
+
warnings.push(`High gain value detected: ${gainValue} - may be too loud`);
|
|
169
|
+
}
|
|
170
|
+
if (gainValue > 5) {
|
|
171
|
+
errors.push(`Dangerous gain value: ${gainValue} - reduced to 2.0 for safety`);
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
// Check for potentially infinite loops
|
|
176
|
+
if (/while\s*\(true\)/.test(pattern) || /for\s*\(.*;;.*\)/.test(pattern)) {
|
|
177
|
+
errors.push('Potential infinite loop detected');
|
|
178
|
+
}
|
|
179
|
+
// Check for eval or similar dangerous functions
|
|
180
|
+
if (/\beval\(/.test(pattern) || /Function\(/.test(pattern)) {
|
|
181
|
+
errors.push('Use of eval() or Function() is not allowed');
|
|
182
|
+
}
|
|
183
|
+
return {
|
|
184
|
+
safe: errors.length === 0,
|
|
185
|
+
errors,
|
|
186
|
+
warnings
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Attempts to auto-fix common pattern errors
|
|
191
|
+
* @param pattern - The pattern to fix
|
|
192
|
+
* @returns Fixed pattern with applied corrections
|
|
193
|
+
*/
|
|
194
|
+
autoFix(pattern) {
|
|
195
|
+
const fixes = [];
|
|
196
|
+
let fixed = pattern;
|
|
197
|
+
// Fix excessive gain values
|
|
198
|
+
fixed = fixed.replace(/\.gain\(([0-9.]+)\)/g, (match, value) => {
|
|
199
|
+
const gainValue = parseFloat(value);
|
|
200
|
+
if (gainValue > 2) {
|
|
201
|
+
fixes.push(`Reduced gain from ${gainValue} to 2.0`);
|
|
202
|
+
return '.gain(2.0)';
|
|
203
|
+
}
|
|
204
|
+
return match;
|
|
205
|
+
});
|
|
206
|
+
// Add missing quotes around sound patterns
|
|
207
|
+
fixed = fixed.replace(/s\(([a-z0-9*]+)\)/gi, (match, sound) => {
|
|
208
|
+
if (!sound.includes('"') && !sound.includes("'")) {
|
|
209
|
+
fixes.push(`Added quotes around sound: ${sound}`);
|
|
210
|
+
return `s("${sound}")`;
|
|
211
|
+
}
|
|
212
|
+
return match;
|
|
213
|
+
});
|
|
214
|
+
return { pattern: fixed, fixes };
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Provides helpful suggestions based on pattern analysis
|
|
218
|
+
* @param pattern - The pattern to analyze
|
|
219
|
+
* @returns Array of suggestions for improvement
|
|
220
|
+
*/
|
|
221
|
+
suggest(pattern) {
|
|
222
|
+
const suggestions = [];
|
|
223
|
+
// Suggest adding effects if pattern is plain
|
|
224
|
+
if (!pattern.includes('room') && !pattern.includes('delay') && !pattern.includes('reverb')) {
|
|
225
|
+
suggestions.push('Consider adding spatial effects: .room(0.5) or .delay(0.25)');
|
|
226
|
+
}
|
|
227
|
+
// Suggest adding variation
|
|
228
|
+
if (!pattern.includes('sometimes') && !pattern.includes('every')) {
|
|
229
|
+
suggestions.push('Add variation with .sometimes() or .every()');
|
|
230
|
+
}
|
|
231
|
+
// Suggest structure if pattern is very simple
|
|
232
|
+
if (pattern.length < 30 && pattern.includes('s(')) {
|
|
233
|
+
suggestions.push('Try stacking multiple patterns with stack()');
|
|
234
|
+
}
|
|
235
|
+
// Suggest tempo control
|
|
236
|
+
if (!pattern.includes('setcpm') && !pattern.includes('cpm')) {
|
|
237
|
+
suggestions.push('Set tempo with setcpm(120) at the beginning');
|
|
238
|
+
}
|
|
239
|
+
return suggestions;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
export class PerformanceMonitor {
|
|
2
|
+
metrics = new Map();
|
|
3
|
+
startTimes = new Map();
|
|
4
|
+
getTime;
|
|
5
|
+
constructor(timeProvider) {
|
|
6
|
+
this.getTime = timeProvider || (() => performance.now());
|
|
7
|
+
}
|
|
8
|
+
startOperation(operationName) {
|
|
9
|
+
this.startTimes.set(operationName, this.getTime());
|
|
10
|
+
}
|
|
11
|
+
endOperation(operationName, success = true) {
|
|
12
|
+
const startTime = this.startTimes.get(operationName);
|
|
13
|
+
if (startTime === undefined)
|
|
14
|
+
return 0;
|
|
15
|
+
const duration = this.getTime() - startTime;
|
|
16
|
+
this.startTimes.delete(operationName);
|
|
17
|
+
const metric = this.metrics.get(operationName) || {
|
|
18
|
+
count: 0,
|
|
19
|
+
totalTime: 0,
|
|
20
|
+
minTime: Infinity,
|
|
21
|
+
maxTime: 0,
|
|
22
|
+
errors: 0
|
|
23
|
+
};
|
|
24
|
+
metric.count++;
|
|
25
|
+
metric.totalTime += duration;
|
|
26
|
+
metric.minTime = Math.min(metric.minTime, duration);
|
|
27
|
+
metric.maxTime = Math.max(metric.maxTime, duration);
|
|
28
|
+
if (!success)
|
|
29
|
+
metric.errors++;
|
|
30
|
+
this.metrics.set(operationName, metric);
|
|
31
|
+
return duration;
|
|
32
|
+
}
|
|
33
|
+
async measureAsync(operationName, operation) {
|
|
34
|
+
this.startOperation(operationName);
|
|
35
|
+
try {
|
|
36
|
+
const result = await operation();
|
|
37
|
+
this.endOperation(operationName, true);
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
this.endOperation(operationName, false);
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
getMetrics(operationName) {
|
|
46
|
+
if (operationName !== undefined) {
|
|
47
|
+
const metric = this.metrics.get(operationName);
|
|
48
|
+
if (!metric)
|
|
49
|
+
return null;
|
|
50
|
+
return {
|
|
51
|
+
operation: operationName,
|
|
52
|
+
calls: metric.count,
|
|
53
|
+
averageTime: metric.totalTime / metric.count,
|
|
54
|
+
minTime: metric.minTime,
|
|
55
|
+
maxTime: metric.maxTime,
|
|
56
|
+
totalTime: metric.totalTime,
|
|
57
|
+
errorRate: (metric.errors / metric.count) * 100
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
const allMetrics = [];
|
|
61
|
+
this.metrics.forEach((metric, name) => {
|
|
62
|
+
allMetrics.push({
|
|
63
|
+
operation: name,
|
|
64
|
+
calls: metric.count,
|
|
65
|
+
averageTime: metric.totalTime / metric.count,
|
|
66
|
+
minTime: metric.minTime,
|
|
67
|
+
maxTime: metric.maxTime,
|
|
68
|
+
totalTime: metric.totalTime,
|
|
69
|
+
errorRate: (metric.errors / metric.count) * 100
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
return allMetrics.sort((a, b) => b.totalTime - a.totalTime);
|
|
73
|
+
}
|
|
74
|
+
getReport() {
|
|
75
|
+
const metrics = this.getMetrics();
|
|
76
|
+
if (!metrics || metrics.length === 0) {
|
|
77
|
+
return 'No performance metrics collected';
|
|
78
|
+
}
|
|
79
|
+
let report = '\n=== PERFORMANCE REPORT ===\n\n';
|
|
80
|
+
report += 'Operation'.padEnd(30) + 'Calls'.padEnd(10) + 'Avg(ms)'.padEnd(12) + 'Min(ms)'.padEnd(12) + 'Max(ms)'.padEnd(12) + 'Errors\n';
|
|
81
|
+
report += '-'.repeat(86) + '\n';
|
|
82
|
+
metrics.forEach(m => {
|
|
83
|
+
report += m.operation.padEnd(30) +
|
|
84
|
+
m.calls.toString().padEnd(10) +
|
|
85
|
+
m.averageTime.toFixed(2).padEnd(12) +
|
|
86
|
+
m.minTime.toFixed(2).padEnd(12) +
|
|
87
|
+
m.maxTime.toFixed(2).padEnd(12) +
|
|
88
|
+
`${m.errorRate.toFixed(1)}%\n`;
|
|
89
|
+
});
|
|
90
|
+
const totalCalls = metrics.reduce((sum, m) => sum + m.calls, 0);
|
|
91
|
+
const totalTime = metrics.reduce((sum, m) => sum + m.totalTime, 0);
|
|
92
|
+
report += '\n';
|
|
93
|
+
report += `Total Operations: ${totalCalls}\n`;
|
|
94
|
+
report += `Total Time: ${totalTime.toFixed(2)}ms\n`;
|
|
95
|
+
report += `Average per Operation: ${(totalTime / totalCalls).toFixed(2)}ms\n`;
|
|
96
|
+
return report;
|
|
97
|
+
}
|
|
98
|
+
reset() {
|
|
99
|
+
this.metrics.clear();
|
|
100
|
+
this.startTimes.clear();
|
|
101
|
+
}
|
|
102
|
+
// Memory usage tracking
|
|
103
|
+
getMemoryUsage() {
|
|
104
|
+
if (typeof process !== 'undefined' && process.memoryUsage) {
|
|
105
|
+
const usage = process.memoryUsage();
|
|
106
|
+
return {
|
|
107
|
+
heapUsed: (usage.heapUsed / 1024 / 1024).toFixed(2) + ' MB',
|
|
108
|
+
heapTotal: (usage.heapTotal / 1024 / 1024).toFixed(2) + ' MB',
|
|
109
|
+
rss: (usage.rss / 1024 / 1024).toFixed(2) + ' MB',
|
|
110
|
+
external: (usage.external / 1024 / 1024).toFixed(2) + ' MB'
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
// Get top slowest operations
|
|
116
|
+
getBottlenecks(limit = 5) {
|
|
117
|
+
const metrics = this.getMetrics();
|
|
118
|
+
return metrics
|
|
119
|
+
.sort((a, b) => b.averageTime - a.averageTime)
|
|
120
|
+
.slice(0, limit)
|
|
121
|
+
.map(m => ({
|
|
122
|
+
operation: m.operation,
|
|
123
|
+
averageTime: m.averageTime.toFixed(2) + 'ms',
|
|
124
|
+
maxTime: m.maxTime.toFixed(2) + 'ms',
|
|
125
|
+
calls: m.calls
|
|
126
|
+
}));
|
|
127
|
+
}
|
|
128
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@williamzujkowski/live-coding-music-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server (unofficial, fan project) for AI-assisted live-coding music via strudel.cc. Lets Claude drive pattern generation, audio analysis, and MIDI export through the Strudel browser REPL.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"live-coding-music-mcp": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist/",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE",
|
|
14
|
+
"package.json"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc && npx tsx scripts/generate-tool-docs.ts",
|
|
18
|
+
"dev": "tsx watch src/index.ts",
|
|
19
|
+
"start": "node dist/index.js",
|
|
20
|
+
"test": "jest --coverage",
|
|
21
|
+
"test:nocov": "jest",
|
|
22
|
+
"test:watch": "jest --watch",
|
|
23
|
+
"test:integration": "node tests/strudel-integration.js",
|
|
24
|
+
"test:browser": "HEADLESS=true jest --testPathPattern=browser",
|
|
25
|
+
"lint": "eslint src --ext .ts",
|
|
26
|
+
"format": "prettier --write \"src/**/*.ts\"",
|
|
27
|
+
"docker:build": "docker build -t live-coding-music-mcp .",
|
|
28
|
+
"docker:run": "docker-compose up",
|
|
29
|
+
"prepublishOnly": "npm run build && npm run test:nocov -- --testPathIgnorePatterns=browser",
|
|
30
|
+
"prepare": "npm run build",
|
|
31
|
+
"validate": "echo '{\"jsonrpc\":\"2.0\",\"method\":\"tools/list\",\"id\":1}' | node dist/index.js",
|
|
32
|
+
"benchmark": "tsx src/__tests__/benchmarks/latency.benchmark.ts",
|
|
33
|
+
"benchmark:gate": "tsx src/__tests__/benchmarks/latency.benchmark.ts --gate",
|
|
34
|
+
"clean": "rm -rf dist coverage"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@google/generative-ai": "^0.24.1",
|
|
38
|
+
"@modelcontextprotocol/sdk": "^1.24.3",
|
|
39
|
+
"@strudel/core": "1.2.5",
|
|
40
|
+
"@strudel/mini": "1.2.5",
|
|
41
|
+
"@strudel/tonal": "1.2.5",
|
|
42
|
+
"@strudel/transpiler": "1.2.5",
|
|
43
|
+
"@tonejs/midi": "^2.0.28",
|
|
44
|
+
"google-auth-library": "^10.5.0",
|
|
45
|
+
"playwright": "^1.52.0"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@types/jest": "^29.5.0",
|
|
49
|
+
"@types/node": "^22",
|
|
50
|
+
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
|
51
|
+
"@typescript-eslint/parser": "^7.0.0",
|
|
52
|
+
"eslint": "^8.57.0",
|
|
53
|
+
"jest": "^29.7.0",
|
|
54
|
+
"prettier": "^3.2.0",
|
|
55
|
+
"ts-jest": "^29.1.0",
|
|
56
|
+
"tsx": "^4.0.0",
|
|
57
|
+
"typescript": "^5.0.0"
|
|
58
|
+
},
|
|
59
|
+
"repository": {
|
|
60
|
+
"type": "git",
|
|
61
|
+
"url": "git+https://github.com/williamzujkowski/live-coding-music-mcp.git"
|
|
62
|
+
},
|
|
63
|
+
"keywords": [
|
|
64
|
+
"mcp",
|
|
65
|
+
"model-context-protocol",
|
|
66
|
+
"strudel",
|
|
67
|
+
"tidalcycles",
|
|
68
|
+
"music",
|
|
69
|
+
"ai",
|
|
70
|
+
"claude",
|
|
71
|
+
"live-coding",
|
|
72
|
+
"algorithmic-composition",
|
|
73
|
+
"web-audio",
|
|
74
|
+
"music-generation"
|
|
75
|
+
],
|
|
76
|
+
"author": "William Zujkowski <william.zujkowski@gmail.com>",
|
|
77
|
+
"license": "MIT",
|
|
78
|
+
"bugs": {
|
|
79
|
+
"url": "https://github.com/williamzujkowski/live-coding-music-mcp/issues"
|
|
80
|
+
},
|
|
81
|
+
"homepage": "https://github.com/williamzujkowski/live-coding-music-mcp#readme",
|
|
82
|
+
"engines": {
|
|
83
|
+
"node": ">=22.0.0",
|
|
84
|
+
"npm": ">=10.0.0"
|
|
85
|
+
}
|
|
86
|
+
}
|