@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.
@@ -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);
@@ -99,7 +99,6 @@ export class PatternStore {
99
99
  }
100
100
  try {
101
101
  const files = await fs.readdir(this.basePath);
102
- const patterns = [];
103
102
  // Parallel file reading for better performance
104
103
  const readPromises = files
105
104
  .filter(file => file.endsWith('.json'))
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
+ }