@williamzujkowski/live-coding-music-mcp 2.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.
- package/README.md +222 -231
- package/dist/AudioAnalyzer.js +0 -1
- package/dist/PatternStore.js +0 -1
- package/dist/index.js +0 -0
- package/dist/server/resources.js +175 -0
- package/dist/server/server.js +226 -348
- package/dist/server/tools/ai.js +78 -22
- package/dist/server/tools/analysis.js +266 -73
- package/dist/server/tools/capture.js +67 -20
- package/dist/server/tools/compose.js +159 -0
- package/dist/server/tools/diagnostics.js +112 -60
- package/dist/server/tools/editor.js +130 -66
- package/dist/server/tools/generate.js +190 -74
- package/dist/server/tools/history.js +149 -88
- package/dist/server/tools/playback.js +49 -18
- package/dist/server/tools/session.js +108 -70
- package/dist/server/tools/storage.js +86 -30
- package/dist/server/tools/transform.js +250 -116
- package/dist/server/tools/types.js +66 -1
- package/dist/services/AudioCaptureService.js +4 -3
- package/dist/services/GeminiService.js +1 -1
- package/dist/services/MIDIExportService.js +2 -4
- package/dist/services/SessionManager.js +4 -2
- package/dist/services/StrudelEngine.js +24 -126
- package/dist/services/StrudelEngineHelpers.js +151 -0
- package/package.json +1 -1
- package/dist/__tests__/MusicTheory.test.js +0 -245
- package/dist/__tests__/PatternGenerator.test.js +0 -337
- package/dist/__tests__/PatternStore.test.js +0 -153
- package/dist/__tests__/utils/MockPlaywright.js +0 -130
- package/dist/server/EnhancedMCPServer.js +0 -654
- package/dist/server/EnhancedMCPServerFixed.js +0 -2506
package/dist/AudioAnalyzer.js
CHANGED
|
@@ -625,7 +625,6 @@ export class AudioAnalyzer {
|
|
|
625
625
|
return 0;
|
|
626
626
|
let syncopationScore = 0;
|
|
627
627
|
for (let i = 1; i < onsets.length; i++) {
|
|
628
|
-
const interval = onsets[i] - onsets[i - 1];
|
|
629
628
|
const phase = (onsets[i] % (meanInterval * 4)) / meanInterval;
|
|
630
629
|
// Check if onset is on an off-beat (not on 0, 1, 2, 3)
|
|
631
630
|
const nearestBeat = Math.round(phase);
|
package/dist/PatternStore.js
CHANGED
package/dist/index.js
CHANGED
|
File without changes
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP resources — read-only catalogs the server exposes alongside tools.
|
|
3
|
+
*
|
|
4
|
+
* From #131 (originated as finding #6 in @nareto's audit #127): an agent
|
|
5
|
+
* shouldn't have to spend tool calls just to discover what bundled
|
|
6
|
+
* patterns exist or which styles the generator accepts. Surface those
|
|
7
|
+
* catalogs as MCP resources so they can be listed once and reasoned
|
|
8
|
+
* about without round-trips.
|
|
9
|
+
*
|
|
10
|
+
* Four resources:
|
|
11
|
+
* strudel://examples — bundled patterns under patterns/examples
|
|
12
|
+
* strudel://patterns — user-saved patterns from PatternStore
|
|
13
|
+
* strudel://styles — supported genres + default BPM
|
|
14
|
+
* strudel://docs/tools — tool quick-reference, one line each
|
|
15
|
+
*
|
|
16
|
+
* All payloads are JSON text. URIs are stable; content for examples and
|
|
17
|
+
* styles is bundled with the build and effectively immutable per release.
|
|
18
|
+
* Patterns is the only one that mutates at runtime.
|
|
19
|
+
*/
|
|
20
|
+
import { promises as fs } from 'node:fs';
|
|
21
|
+
import { join, basename } from 'node:path';
|
|
22
|
+
import { diagnosticsModule } from './tools/diagnostics.js';
|
|
23
|
+
import { playbackModule } from './tools/playback.js';
|
|
24
|
+
import { storageModule } from './tools/storage.js';
|
|
25
|
+
import { historyModule } from './tools/history.js';
|
|
26
|
+
import { analysisModule } from './tools/analysis.js';
|
|
27
|
+
import { editorModule } from './tools/editor.js';
|
|
28
|
+
import { transformModule } from './tools/transform.js';
|
|
29
|
+
import { generateModule } from './tools/generate.js';
|
|
30
|
+
import { sessionModule } from './tools/session.js';
|
|
31
|
+
import { captureModule } from './tools/capture.js';
|
|
32
|
+
import { aiModule } from './tools/ai.js';
|
|
33
|
+
import { composeModule } from './tools/compose.js';
|
|
34
|
+
export const resources = [
|
|
35
|
+
{
|
|
36
|
+
uri: 'strudel://examples',
|
|
37
|
+
name: 'Bundled example patterns',
|
|
38
|
+
description: 'Curated example patterns shipped with the server, organised by genre',
|
|
39
|
+
mimeType: 'application/json',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
uri: 'strudel://patterns',
|
|
43
|
+
name: 'Saved patterns',
|
|
44
|
+
description: 'Patterns saved by the user via the `save` tool, with tags and timestamps',
|
|
45
|
+
mimeType: 'application/json',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
uri: 'strudel://styles',
|
|
49
|
+
name: 'Supported styles',
|
|
50
|
+
description: 'Genres the generator accepts, with their default tempos',
|
|
51
|
+
mimeType: 'application/json',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
uri: 'strudel://docs/tools',
|
|
55
|
+
name: 'Tool quick-reference',
|
|
56
|
+
description: 'Compact list of every registered MCP tool with a one-line description',
|
|
57
|
+
mimeType: 'application/json',
|
|
58
|
+
},
|
|
59
|
+
];
|
|
60
|
+
const resourceUris = new Set(resources.map(r => r.uri));
|
|
61
|
+
export function isResourceUri(uri) {
|
|
62
|
+
return resourceUris.has(uri);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Default tempos for the supported style set. Kept in sync with
|
|
66
|
+
* compose.ts:TEMPO_BY_STYLE — both grow together when a new genre
|
|
67
|
+
* lands. Future cleanup (#120 aftermath): single source of truth.
|
|
68
|
+
*/
|
|
69
|
+
const STYLE_DEFAULTS = {
|
|
70
|
+
techno: 130,
|
|
71
|
+
house: 125,
|
|
72
|
+
dnb: 174,
|
|
73
|
+
ambient: 80,
|
|
74
|
+
trap: 140,
|
|
75
|
+
jungle: 160,
|
|
76
|
+
jazz: 110,
|
|
77
|
+
experimental: 120,
|
|
78
|
+
dubstep: 140,
|
|
79
|
+
trance: 138,
|
|
80
|
+
breakbeat: 130,
|
|
81
|
+
garage: 130,
|
|
82
|
+
electro: 128,
|
|
83
|
+
downtempo: 90,
|
|
84
|
+
idm: 115,
|
|
85
|
+
};
|
|
86
|
+
const TOOL_MODULES = [
|
|
87
|
+
editorModule,
|
|
88
|
+
playbackModule,
|
|
89
|
+
transformModule,
|
|
90
|
+
generateModule,
|
|
91
|
+
analysisModule,
|
|
92
|
+
storageModule,
|
|
93
|
+
historyModule,
|
|
94
|
+
diagnosticsModule,
|
|
95
|
+
composeModule,
|
|
96
|
+
aiModule,
|
|
97
|
+
captureModule,
|
|
98
|
+
sessionModule,
|
|
99
|
+
];
|
|
100
|
+
async function readExamples(examplesDir) {
|
|
101
|
+
const entries = await fs.readdir(examplesDir, { withFileTypes: true });
|
|
102
|
+
const genres = entries.filter(e => e.isDirectory()).map(e => e.name);
|
|
103
|
+
const items = [];
|
|
104
|
+
for (const genre of genres) {
|
|
105
|
+
const dir = join(examplesDir, genre);
|
|
106
|
+
const files = await fs.readdir(dir);
|
|
107
|
+
for (const file of files) {
|
|
108
|
+
if (!file.endsWith('.json'))
|
|
109
|
+
continue;
|
|
110
|
+
try {
|
|
111
|
+
const raw = await fs.readFile(join(dir, file), 'utf-8');
|
|
112
|
+
const data = JSON.parse(raw);
|
|
113
|
+
items.push({
|
|
114
|
+
name: data.name ?? basename(file, '.json'),
|
|
115
|
+
genre: data.genre ?? genre,
|
|
116
|
+
bpm: data.bpm,
|
|
117
|
+
key: data.key,
|
|
118
|
+
tags: data.tags ?? [],
|
|
119
|
+
description: data.description,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
// Skip malformed example files rather than failing the whole list.
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
items.sort((a, b) => a.genre.localeCompare(b.genre) || a.name.localeCompare(b.name));
|
|
128
|
+
return { count: items.length, examples: items };
|
|
129
|
+
}
|
|
130
|
+
async function readSavedPatterns(store) {
|
|
131
|
+
const patterns = await store.list();
|
|
132
|
+
const items = patterns.map(p => ({
|
|
133
|
+
name: p.name,
|
|
134
|
+
tags: p.tags,
|
|
135
|
+
timestamp: p.timestamp,
|
|
136
|
+
contentPreview: p.content.length > 200 ? p.content.slice(0, 200) + '...' : p.content,
|
|
137
|
+
}));
|
|
138
|
+
return { count: items.length, patterns: items };
|
|
139
|
+
}
|
|
140
|
+
function readStyles() {
|
|
141
|
+
const items = Object.entries(STYLE_DEFAULTS)
|
|
142
|
+
.map(([name, defaultBpm]) => ({ name, defaultBpm }))
|
|
143
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
144
|
+
return { count: items.length, styles: items };
|
|
145
|
+
}
|
|
146
|
+
function readToolDocs() {
|
|
147
|
+
const items = TOOL_MODULES.flatMap(m => m.tools.map(t => ({ name: t.name, description: t.description })));
|
|
148
|
+
items.unshift({ name: 'init', description: 'Initialize Strudel in browser' });
|
|
149
|
+
items.sort((a, b) => a.name.localeCompare(b.name));
|
|
150
|
+
return { count: items.length, tools: items };
|
|
151
|
+
}
|
|
152
|
+
export async function readResource(uri, ctx) {
|
|
153
|
+
let data;
|
|
154
|
+
switch (uri) {
|
|
155
|
+
case 'strudel://examples':
|
|
156
|
+
data = await readExamples(ctx.examplesDir);
|
|
157
|
+
break;
|
|
158
|
+
case 'strudel://patterns':
|
|
159
|
+
data = await readSavedPatterns(ctx.store);
|
|
160
|
+
break;
|
|
161
|
+
case 'strudel://styles':
|
|
162
|
+
data = readStyles();
|
|
163
|
+
break;
|
|
164
|
+
case 'strudel://docs/tools':
|
|
165
|
+
data = readToolDocs();
|
|
166
|
+
break;
|
|
167
|
+
default:
|
|
168
|
+
throw new Error(`Unknown resource URI: ${uri}`);
|
|
169
|
+
}
|
|
170
|
+
return {
|
|
171
|
+
uri,
|
|
172
|
+
mimeType: 'application/json',
|
|
173
|
+
text: JSON.stringify(data, null, 2),
|
|
174
|
+
};
|
|
175
|
+
}
|