mcp-music-studio 0.2.0 → 0.2.1

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 CHANGED
@@ -1,27 +1,59 @@
1
1
  # MCP Music Studio
2
2
 
3
- A creative music tool for AI systems — compose, arrange, and play music with multi-instrument audio, style presets, and visual sheet music.
3
+ [![smithery badge](https://smithery.ai/badge/linxule/mcp-music-studio)](https://smithery.ai/server/linxule/mcp-music-studio)
4
4
 
5
- Forked from [`@modelcontextprotocol/server-sheet-music`](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/sheet-music-server) and substantially extended.
5
+ Two-mode creative music studio for AI: **scored composition** (ABC notation with sheet music) and **live performance** (Strudel live coding with TidalCycles). Interactive UI renders inline in Claude Desktop, claude.ai, and other MCP clients.
6
6
 
7
- ## Features
7
+ ## Quick Start — No Install Required
8
8
 
9
- - **8 style presets** rock, jazz, bossa, waltz, march, reggae, folk, classical. One parameter adds drums + bass + chord accompaniment.
10
- - **30 instruments** — selectable from UI or via tool parameter, with fuzzy matching
11
- - **Three rendering modes** — ext-apps inline UI for Claude Desktop/VS Code, browser fallback for CLI environments, configurable via `--render-mode`
12
- - **`get-music-guide` tool** — on-demand reference for AI systems (instruments, drums, ABC syntax, arrangements, genre templates, MIDI directives)
13
- - **7 `music://guide/*` resources** — same content for resource-capable clients
14
- - **Note highlighting** — currently playing notes light up during playback
15
- - **Forgiving parser** — warnings don't block playback, only fatal errors do
16
- - **Streaming render** — sheet music appears as the AI types (`ontoolinputpartial`)
17
- - **Tempo slider** — warp control in the UI
18
- - **Fullscreen mode** — via ext-apps `requestDisplayMode`
9
+ Paste this URL into any MCP client that supports remote servers:
19
10
 
20
- ## Install
11
+ ```
12
+ https://mcp-music-studio.linxule.workers.dev/mcp
13
+ ```
14
+
15
+ **Claude Desktop / claude.ai:**
16
+ Settings → Connectors → Add Connector → paste the URL above → done.
17
+
18
+ **Claude Code:**
19
+ ```bash
20
+ claude mcp add --transport http music-studio https://mcp-music-studio.linxule.workers.dev/mcp
21
+ ```
22
+
23
+ That's it — ask Claude to play a song or create a beat.
24
+
25
+ ---
26
+
27
+ ## What You Get
28
+
29
+ ### Scored Composition (ABC Notation)
30
+ Write sheet music → see it rendered → hear it played with multi-instrument audio.
31
+
32
+ - **8 style presets** — rock, jazz, bossa, waltz, march, reggae, folk, classical — one parameter adds drums + bass + chord accompaniment
33
+ - **30 instruments** — piano, strings, brass, woodwinds, synths — selectable by name
34
+ - **Visual sheet music** — notes highlight as they play
35
+ - **Streaming render** — sheet music appears as the AI types
36
+ - **`get-music-guide`** — 7 reference topics (instruments, drums, ABC syntax, arrangements, genres, styles, MIDI directives)
37
+
38
+ ### Live Performance (Strudel)
39
+ Write code → hear it play → edit in a live REPL.
40
+
41
+ - **TidalCycles mini-notation** in JavaScript
42
+ - **72 drum machine banks** + **128 GM instruments** + built-in synths
43
+ - **Full effects chain** — filters, reverb, delay, FM synthesis
44
+ - **Editable REPL** — users can tweak the code and hear changes instantly
45
+ - **`get-strudel-guide`** — 7 reference topics (mini-notation, sounds, effects, patterns, genres, tips, advanced)
21
46
 
22
- Requires Node.js 18+. Supports stdio and HTTP transports.
47
+ ### Shared
48
+ - **`search-music-docs`** — semantic search over strudel.cc and ABCJS documentation
23
49
 
24
- ### CLI Install (one-liner)
50
+ ---
51
+
52
+ ## Local Install (Optional)
53
+
54
+ The remote URL above works without any local setup. If you prefer running locally (offline use, lower latency), install via npm:
55
+
56
+ ### CLI One-Liners
25
57
 
26
58
  ```bash
27
59
  # Claude Code
@@ -32,11 +64,15 @@ codex mcp add -- npx -y mcp-music-studio --stdio
32
64
 
33
65
  # Gemini CLI
34
66
  gemini mcp add -- npx -y mcp-music-studio --stdio
67
+
68
+ # OpenCode
69
+ opencode mcp add music-studio -- npx -y mcp-music-studio --stdio
35
70
  ```
36
71
 
37
- ### Claude Desktop
72
+ ### JSON Config (Claude Desktop, Cursor, Windsurf, etc.)
38
73
 
39
- Config file location:
74
+ <details>
75
+ <summary>Claude Desktop — edit config file</summary>
40
76
 
41
77
  | OS | Path |
42
78
  |----|------|
@@ -54,12 +90,12 @@ Config file location:
54
90
  }
55
91
  }
56
92
  ```
93
+ </details>
57
94
 
58
- ### VS Code
59
-
60
- Add to `.vscode/mcp.json` (project) or user settings:
95
+ <details>
96
+ <summary>VS Code / Trae / PearAI</summary>
61
97
 
62
- > **Note**: VS Code uses `"servers"` not `"mcpServers"`. Also works in Trae, Void, and PearAI.
98
+ Add to `.vscode/mcp.json` note: uses `"servers"` not `"mcpServers"`:
63
99
 
64
100
  ```json
65
101
  {
@@ -71,10 +107,12 @@ Add to `.vscode/mcp.json` (project) or user settings:
71
107
  }
72
108
  }
73
109
  ```
110
+ </details>
74
111
 
75
- ### Cursor
112
+ <details>
113
+ <summary>Cursor</summary>
76
114
 
77
- Add to `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` (project):
115
+ Add to `~/.cursor/mcp.json`:
78
116
 
79
117
  ```json
80
118
  {
@@ -86,8 +124,10 @@ Add to `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` (project):
86
124
  }
87
125
  }
88
126
  ```
127
+ </details>
89
128
 
90
- ### Windsurf
129
+ <details>
130
+ <summary>Windsurf</summary>
91
131
 
92
132
  Add to `~/.codeium/windsurf/mcp_config.json`:
93
133
 
@@ -101,8 +141,10 @@ Add to `~/.codeium/windsurf/mcp_config.json`:
101
141
  }
102
142
  }
103
143
  ```
144
+ </details>
104
145
 
105
- ### Windows
146
+ <details>
147
+ <summary>Windows</summary>
106
148
 
107
149
  On Windows, `npx` is a `.cmd` file and requires a shell wrapper:
108
150
 
@@ -116,87 +158,42 @@ On Windows, `npx` is a `.cmd` file and requires a shell wrapper:
116
158
  }
117
159
  }
118
160
  ```
161
+ </details>
119
162
 
120
- ### ChatGPT
121
-
122
- ChatGPT only supports remote HTTPS MCP servers. Run the HTTP transport and expose via tunnel:
123
-
124
- ```bash
125
- npx mcp-music-studio
126
- # Server starts on http://localhost:3001/mcp
127
- # Use ngrok, Cloudflare Tunnel, etc. to expose publicly
128
- ```
129
-
130
- ### HTTP Transport
131
-
132
- ```bash
133
- npx mcp-music-studio
134
- # Server starts on http://localhost:3001/mcp
135
- ```
136
-
137
- ## Tools
138
-
139
- ### `play-sheet-music`
140
-
141
- Creates and plays music with visual sheet music and multi-instrument audio.
142
-
143
- | Parameter | Type | Description |
144
- |-----------|------|-------------|
145
- | `abcNotation` | string | ABC notation with optional chord symbols |
146
- | `instrument` | string? | Default instrument (e.g., "Violin", "Flute") |
147
- | `style` | enum? | Accompaniment style: rock, jazz, bossa, waltz, march, reggae, folk, classical |
148
- | `tempo` | number? | BPM (40-240) |
149
- | `swing` | number? | Swing feel (0-100) |
150
- | `transpose` | number? | Semitones (-12 to 12) |
151
-
152
- **Example — jazz arrangement:**
153
- ```json
154
- {
155
- "abcNotation": "X:1\nT:Blue Note\nM:4/4\nL:1/8\nK:Bb\n\"Bbmaj7\"d2 f2 d2 Bc | \"Eb7\"_e2 g2 e2 cB | \"Dm7\"d2 f2 a2 fd | \"G7\"g2 f2 e2 dc |",
156
- "style": "jazz",
157
- "instrument": "Alto Sax",
158
- "tempo": 140
159
- }
160
- ```
161
-
162
- ### `get-music-guide`
163
-
164
- Returns reference material for composition. Topics:
163
+ <details>
164
+ <summary>Render modes (for non-ext-apps clients)</summary>
165
165
 
166
- | Topic | Contents |
167
- |-------|----------|
168
- | `instruments` | All 128 GM instruments by family, program numbers, combo suggestions |
169
- | `drums` | Percussion notes, pattern syntax, 8 ready-to-use patterns |
170
- | `abc-syntax` | Notes, rests, chords, repeats, dynamics, multi-voice, lyrics |
171
- | `arrangements` | Multi-voice patterns, volume balancing, accompaniment setup |
172
- | `genres` | Complete ABC examples: jazz, blues, folk, minuet, rock, bossa, lullaby |
173
- | `styles` | What each style preset does and when to use it |
174
- | `midi-directives` | Full `%%MIDI` reference for ABCJS |
166
+ The server auto-detects ext-apps support. For clients that don't support it (Cherry Studio, CLI environments), use `--render-mode`:
175
167
 
176
- ## Render Modes
177
-
178
- The server auto-detects whether the client supports ext-apps UI. For clients that don't, use `--render-mode` to control how music is delivered:
179
-
180
- | Mode | Flag | Behavior |
181
- |------|------|----------|
182
- | `auto` | (default) | Inline ext-apps UI for Claude Desktop, VS Code |
183
- | `browser` | `--render-mode browser` | Saves HTML player and opens in system browser |
184
- | `html` | `--render-mode html` | Returns HTML as embedded resource in response |
185
-
186
- Use `--output-dir` to control where HTML player files are saved (default: `~/Desktop/mcp-music-studio`).
187
-
188
- **Example — Cherry Studio, CLI environments, or other non-ext-apps clients:**
168
+ | Mode | Behavior |
169
+ |------|----------|
170
+ | `auto` (default) | Inline UI for Claude Desktop, VS Code |
171
+ | `browser` | Saves HTML and opens in system browser |
172
+ | `html` | Returns HTML as embedded resource |
189
173
 
190
174
  ```json
191
175
  {
192
176
  "mcpServers": {
193
177
  "music-studio": {
194
178
  "command": "npx",
195
- "args": ["-y", "mcp-music-studio", "--stdio", "--render-mode", "browser", "--output-dir", "/path/to/output"]
179
+ "args": ["-y", "mcp-music-studio", "--stdio", "--render-mode", "browser"]
196
180
  }
197
181
  }
198
182
  }
199
183
  ```
184
+ </details>
185
+
186
+ ---
187
+
188
+ ## Tools
189
+
190
+ | Tool | Description |
191
+ |------|-------------|
192
+ | `play-sheet-music` | ABC notation → visual sheet music + multi-instrument audio |
193
+ | `play-live-pattern` | Strudel code → live-coded patterns with synthesis + effects |
194
+ | `get-music-guide` | ABC reference (7 topics: instruments, drums, syntax, genres...) |
195
+ | `get-strudel-guide` | Strudel reference (7 topics: sounds, effects, patterns, genres...) |
196
+ | `search-music-docs` | Semantic search over strudel.cc and ABCJS docs |
200
197
 
201
198
  ## Development
202
199
 
@@ -204,32 +201,12 @@ Use `--output-dir` to control where HTML player files are saved (default: `~/Des
204
201
  bun install
205
202
  bun run dev # watch + serve (hot reload)
206
203
  bun run build # production build
207
- bun run serve # HTTP server on port 3001
208
- ```
209
-
210
- ## Architecture
211
-
212
- ```
213
- mcp-music-studio/
214
- ├── server.ts # MCP server: tools, resources, guides, forgiving parser
215
- ├── main.ts # Entry point: HTTP + stdio transports
216
- ├── mcp-app.html # HTML shell (Vite inlines everything for ext-apps UI)
217
- ├── src/
218
- │ ├── mcp-app.ts # Ext-apps client: rendering, audio, streaming
219
- │ ├── mcp-app.css # Styles: dark mode, note highlighting, toolbar
220
- │ ├── music-logic.ts # Shared: instruments, presets, ABC processing
221
- │ ├── server-logic.ts # Server: parse validation, result construction
222
- │ ├── browser-fallback.ts # Browser player: HTML generation, auto-open
223
- │ └── global.css # Base reset
224
- ├── tests/ # Vitest tests
225
- ├── vite.config.ts # Single-file HTML bundling
226
- ├── tsconfig.json # Client TypeScript config
227
- └── tsconfig.server.json # Server TypeScript config
204
+ bun run test # run tests
228
205
  ```
229
206
 
230
207
  ## Attribution
231
208
 
232
- This project is a fork of the [Sheet Music Server](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/sheet-music-server) example from the [MCP ext-apps](https://github.com/modelcontextprotocol/ext-apps) repository by Anthropic, licensed under MIT.
209
+ Forked from the [Sheet Music Server](https://github.com/modelcontextprotocol/ext-apps/tree/main/examples/sheet-music-server) example from [MCP ext-apps](https://github.com/modelcontextprotocol/ext-apps) by Anthropic, licensed under MIT.
233
210
 
234
211
  ## License
235
212
 
package/dist/mcp-app.html CHANGED
@@ -228,7 +228,7 @@ X:`),l=o.length;return l===0&&(l=1),l};var r=a.TuneBook=function(s){var o=t(s);t
228
228
  `)}
229
229
  `):`${a.join(`
230
230
  `)}
231
- ${n}`}function f3(e){const t=u3(e),n={};return e.swing!==void 0&&(n.swing=e.swing),{...t,abcNotation:e.abcNotation?h3(e.abcNotation,e):void 0,synthOptions:n}}const me={visualObj:null,synthControl:null,currentInstrument:ou,currentStyle:"",currentAbc:null,highlightedEls:[]},nr=document.querySelector(".main"),Tc=document.getElementById("status"),Mt=document.getElementById("sheet-music"),pa=document.getElementById("audio-controls"),lu=document.getElementById("instrument-selector"),uu=document.getElementById("style-selector"),hu=document.getElementById("toolbar");"audioSession"in navigator&&(navigator.audioSession.type="playback");const d3={onEvent(e){if(me.highlightedEls.forEach(t=>t.classList.remove("note-playing")),me.highlightedEls=[],!!e.elements){for(const t of e.elements)for(const n of t)n.classList.add("note-playing"),me.highlightedEls.push(n);me.highlightedEls.length>0&&me.highlightedEls[0].scrollIntoView({behavior:"smooth",block:"nearest"})}},onFinished(){me.highlightedEls.forEach(e=>e.classList.remove("note-playing")),me.highlightedEls=[]}},kt=document.createElement("select");kt.id="instrument-select";kt.className="control-select";for(const e of Object.keys($t)){const t=document.createElement("option");t.value=e,t.textContent=e,e===me.currentInstrument&&(t.selected=!0),kt.appendChild(t)}kt.addEventListener("change",()=>{me.currentInstrument=kt.value,me.visualObj&&me.synthControl&&p3()});async function p3(){var e;if(!(!me.synthControl||!((e=me.visualObj)!=null&&e[0])))try{const n={program:$t[me.currentInstrument]??0};await me.synthControl.setTune(me.visualObj[0],!1,n)}catch(t){console.error("Failed to apply settings:",t)}}const kr=document.createElement("label");kr.className="control-label";kr.htmlFor="instrument-select";kr.textContent="Instrument";lu.appendChild(kr);lu.appendChild(kt);const ot=document.createElement("select");ot.id="style-select";ot.className="control-select";const hi=document.createElement("option");hi.value="";hi.textContent="No style (melody only)";ot.appendChild(hi);for(const e of Object.keys(li)){const t=document.createElement("option");t.value=e,t.textContent=e.charAt(0).toUpperCase()+e.slice(1),ot.appendChild(t)}ot.addEventListener("change",()=>{me.currentStyle=ot.value,me.currentAbc&&fu(me.currentAbc)});const xr=document.createElement("label");xr.className="control-label";xr.htmlFor="style-select";xr.textContent="Style";uu.appendChild(xr);uu.appendChild(ot);let ir=null;const Jt=document.createElement("button");Jt.className="toolbar-btn";Jt.textContent="⛶";Jt.title="Toggle fullscreen";Jt.addEventListener("click",()=>{ir==null||ir.requestDisplayMode({mode:"fullscreen"})});hu.appendChild(Jt);function _t(e,t=!1){Tc.textContent=e,Tc.classList.toggle("error",t)}async function fu(e,t){try{_t("Rendering..."),me.synthControl&&me.synthControl.pause(),me.currentAbc=e,Mt.innerHTML="",pa.innerHTML="";const n=cu(e,me.currentStyle);if(me.visualObj=ar.renderAbc(Mt,n,{responsive:"resize",add_classes:!0}),!me.visualObj||me.visualObj.length===0)throw new Error("Failed to parse music notation");if(!ar.synth.supportsAudio())throw new Error("Audio not supported in this browser");me.synthControl=new ar.synth.SynthController,me.synthControl.load(pa,d3,{displayLoop:!0,displayPlay:!0,displayProgress:!0,displayWarp:!0});const r={program:$t[me.currentInstrument]??0,...t};await me.synthControl.setTune(me.visualObj[0],!1,r),hu.classList.add("visible");try{me.synthControl.play(),_t("Playing...")}catch{_t("Ready to play!")}}catch(n){console.error("Render error:",n),_t(`Error: ${n.message}`,!0),pa.innerHTML=""}}const xt=new yg({name:"Music Studio",version:"0.2.0"});ir=xt;xt.ontoolinput=e=>{console.info("Received tool input:",e);const t=f3(e.arguments??{});me.currentInstrument=t.instrument,kt.value=t.instrument,me.currentStyle=t.style,ot.value=t.style,t.abcNotation?fu(t.abcNotation,t.synthOptions).catch(console.error):_t("No ABC notation provided",!0)};let ga=null,Ec="";xt.ontoolinputpartial=e=>{var a,r;const t=(a=e.arguments)==null?void 0:a.abcNotation;if(!t)return;me.currentAbc=t;const n=(r=e.arguments)==null?void 0:r.style;if(n&&ui(n)&&me.currentStyle!==n&&(me.currentStyle=n,ot.value=n),!t.match(/K:[^\n]+/)){_t("Composing...");return}t!==Ec&&(Ec=t,ga&&clearTimeout(ga),ga=setTimeout(()=>{try{const s=cu(t,me.currentStyle);ar.renderAbc(Mt,s,{responsive:"resize",add_classes:!0}),Mt.scrollTop=Mt.scrollHeight;const o=Mt.closest(".sheet-section");o&&(o.scrollTop=o.scrollHeight),_t("Composing...")}catch{}},150))};xt.onerror=console.error;function du(e){e.safeAreaInsets&&(nr.style.paddingTop=`${e.safeAreaInsets.top}px`,nr.style.paddingRight=`${e.safeAreaInsets.right}px`,nr.style.paddingBottom=`${e.safeAreaInsets.bottom}px`,nr.style.paddingLeft=`${e.safeAreaInsets.left}px`)}xt.onhostcontextchanged=du;xt.connect().then(()=>{const e=xt.getHostContext();e&&du(e)});</script>
231
+ ${n}`}function f3(e){const t=u3(e),n={};return e.swing!==void 0&&(n.swing=e.swing),{...t,abcNotation:e.abcNotation?h3(e.abcNotation,e):void 0,synthOptions:n}}const me={visualObj:null,synthControl:null,currentInstrument:ou,currentStyle:"",currentAbc:null,highlightedEls:[]},nr=document.querySelector(".main"),Tc=document.getElementById("status"),Mt=document.getElementById("sheet-music"),pa=document.getElementById("audio-controls"),lu=document.getElementById("instrument-selector"),uu=document.getElementById("style-selector"),hu=document.getElementById("toolbar");"audioSession"in navigator&&(navigator.audioSession.type="playback");const d3={onEvent(e){if(me.highlightedEls.forEach(t=>t.classList.remove("note-playing")),me.highlightedEls=[],!!e.elements){for(const t of e.elements)for(const n of t)n.classList.add("note-playing"),me.highlightedEls.push(n);me.highlightedEls.length>0&&me.highlightedEls[0].scrollIntoView({behavior:"smooth",block:"nearest"})}},onFinished(){me.highlightedEls.forEach(e=>e.classList.remove("note-playing")),me.highlightedEls=[]}},kt=document.createElement("select");kt.id="instrument-select";kt.className="control-select";for(const e of Object.keys($t)){const t=document.createElement("option");t.value=e,t.textContent=e,e===me.currentInstrument&&(t.selected=!0),kt.appendChild(t)}kt.addEventListener("change",()=>{me.currentInstrument=kt.value,me.visualObj&&me.synthControl&&p3()});async function p3(){var e;if(!(!me.synthControl||!((e=me.visualObj)!=null&&e[0])))try{const n={program:$t[me.currentInstrument]??0};await me.synthControl.setTune(me.visualObj[0],!1,n)}catch(t){console.error("Failed to apply settings:",t)}}const kr=document.createElement("label");kr.className="control-label";kr.htmlFor="instrument-select";kr.textContent="Instrument";lu.appendChild(kr);lu.appendChild(kt);const ot=document.createElement("select");ot.id="style-select";ot.className="control-select";const hi=document.createElement("option");hi.value="";hi.textContent="No style (melody only)";ot.appendChild(hi);for(const e of Object.keys(li)){const t=document.createElement("option");t.value=e,t.textContent=e.charAt(0).toUpperCase()+e.slice(1),ot.appendChild(t)}ot.addEventListener("change",()=>{me.currentStyle=ot.value,me.currentAbc&&fu(me.currentAbc)});const xr=document.createElement("label");xr.className="control-label";xr.htmlFor="style-select";xr.textContent="Style";uu.appendChild(xr);uu.appendChild(ot);let ir=null;const Jt=document.createElement("button");Jt.className="toolbar-btn";Jt.textContent="⛶";Jt.title="Toggle fullscreen";Jt.addEventListener("click",()=>{ir==null||ir.requestDisplayMode({mode:"fullscreen"})});hu.appendChild(Jt);function _t(e,t=!1){Tc.textContent=e,Tc.classList.toggle("error",t)}async function fu(e,t){try{_t("Rendering..."),me.synthControl&&me.synthControl.pause(),me.currentAbc=e,Mt.innerHTML="",pa.innerHTML="";const n=cu(e,me.currentStyle);if(me.visualObj=ar.renderAbc(Mt,n,{responsive:"resize",add_classes:!0}),!me.visualObj||me.visualObj.length===0)throw new Error("Failed to parse music notation");if(!ar.synth.supportsAudio())throw new Error("Audio not supported in this browser");me.synthControl=new ar.synth.SynthController,me.synthControl.load(pa,d3,{displayLoop:!0,displayPlay:!0,displayProgress:!0,displayWarp:!0});const r={program:$t[me.currentInstrument]??0,...t};await me.synthControl.setTune(me.visualObj[0],!1,r),hu.classList.add("visible");try{me.synthControl.play(),_t("Playing...")}catch{_t("Ready to play!")}}catch(n){console.error("Render error:",n),_t(`Error: ${n.message}`,!0),pa.innerHTML=""}}const xt=new yg({name:"Music Studio",version:"0.2.1"});ir=xt;xt.ontoolinput=e=>{console.info("Received tool input:",e);const t=f3(e.arguments??{});me.currentInstrument=t.instrument,kt.value=t.instrument,me.currentStyle=t.style,ot.value=t.style,t.abcNotation?fu(t.abcNotation,t.synthOptions).catch(console.error):_t("No ABC notation provided",!0)};let ga=null,Ec="";xt.ontoolinputpartial=e=>{var a,r;const t=(a=e.arguments)==null?void 0:a.abcNotation;if(!t)return;me.currentAbc=t;const n=(r=e.arguments)==null?void 0:r.style;if(n&&ui(n)&&me.currentStyle!==n&&(me.currentStyle=n,ot.value=n),!t.match(/K:[^\n]+/)){_t("Composing...");return}t!==Ec&&(Ec=t,ga&&clearTimeout(ga),ga=setTimeout(()=>{try{const s=cu(t,me.currentStyle);ar.renderAbc(Mt,s,{responsive:"resize",add_classes:!0}),Mt.scrollTop=Mt.scrollHeight;const o=Mt.closest(".sheet-section");o&&(o.scrollTop=o.scrollHeight),_t("Composing...")}catch{}},150))};xt.onerror=console.error;function du(e){e.safeAreaInsets&&(nr.style.paddingTop=`${e.safeAreaInsets.top}px`,nr.style.paddingRight=`${e.safeAreaInsets.right}px`,nr.style.paddingBottom=`${e.safeAreaInsets.bottom}px`,nr.style.paddingLeft=`${e.safeAreaInsets.left}px`)}xt.onhostcontextchanged=du;xt.connect().then(()=>{const e=xt.getHostContext();e&&du(e)});</script>
232
232
  <style rel="stylesheet" crossorigin>.abcjs-inline-audio{height:26px;padding:0 5px;border-radius:3px;background-color:#424242;display:flex;align-items:center;box-sizing:border-box}.abcjs-inline-audio.abcjs-disabled{opacity:.5}.abcjs-inline-audio .abcjs-btn{display:block;width:28px;height:34px;margin-right:2px;padding:7px 4px;background:none!important;border:1px solid transparent;box-sizing:border-box;line-height:1}.abcjs-btn g{fill:#f4f4f4;stroke:#f4f4f4}.abcjs-inline-audio .abcjs-btn:hover g{fill:#ccc;stroke:#ccc}.abcjs-inline-audio .abcjs-midi-selection.abcjs-pushed,.abcjs-inline-audio .abcjs-midi-loop.abcjs-pushed,.abcjs-inline-audio .abcjs-midi-reset.abcjs-pushed{border:1px solid #cccccc;background-color:#666;box-sizing:border-box}.abcjs-inline-audio .abcjs-midi-start .abcjs-pause-svg,.abcjs-inline-audio .abcjs-midi-start .abcjs-loading-svg,.abcjs-inline-audio .abcjs-midi-start.abcjs-pushed .abcjs-play-svg,.abcjs-inline-audio .abcjs-midi-start.abcjs-loading .abcjs-play-svg{display:none}.abcjs-inline-audio .abcjs-midi-start.abcjs-pushed .abcjs-pause-svg{display:block}.abcjs-inline-audio .abcjs-midi-progress-background{background-color:#424242;height:10px;border-radius:5px;border:2px solid #cccccc;margin:0 8px 0 15px;position:relative;flex:1;padding:0;box-sizing:border-box}.abcjs-inline-audio .abcjs-midi-progress-indicator{width:20px;margin-left:-10px;height:14px;background-color:#f4f4f4;position:absolute;display:inline-block;border-radius:6px;top:-4px;left:0;box-sizing:border-box}.abcjs-inline-audio .abcjs-midi-clock{margin-left:4px;margin-top:1px;margin-right:2px;display:inline-block;font-family:sans-serif;font-size:16px;box-sizing:border-box;color:#f4f4f4}.abcjs-inline-audio .abcjs-tempo-wrapper{font-size:10px;color:#f4f4f4;box-sizing:border-box;display:flex;align-items:center}.abcjs-inline-audio .abcjs-midi-tempo{border-radius:2px;border:none;margin:0 2px 0 4px;width:42px;padding-left:2px;box-sizing:border-box}.abcjs-inline-audio .abcjs-loading .abcjs-loading-svg{display:inherit}.abcjs-inline-audio .abcjs-loading{outline:none;animation-name:abcjs-spin;animation-duration:1s;animation-iteration-count:infinite;animation-timing-function:linear}.abcjs-inline-audio .abcjs-loading-svg circle{stroke:#f4f4f4}@keyframes abcjs-spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.abcjs-large .abcjs-inline-audio{height:52px}.abcjs-large .abcjs-btn{width:56px;height:52px;font-size:28px;padding:6px 8px}.abcjs-large .abcjs-midi-progress-background{height:20px;border:4px solid #cccccc}.abcjs-large .abcjs-midi-progress-indicator{height:28px;top:-8px;width:40px}.abcjs-large .abcjs-midi-clock{font-size:32px;margin-right:10px;margin-left:10px;margin-top:-1px}.abcjs-large .abcjs-midi-tempo{font-size:20px;width:50px}.abcjs-large .abcjs-tempo-wrapper{font-size:20px}.abcjs-css-warning{display:none}*{box-sizing:border-box}html,body{font-family:system-ui,-apple-system,sans-serif;font-size:1rem}code{font-size:1em}:root{--color-bg: #ffffff;--color-text: #1f2937;--color-text-muted: #6b7280;--color-primary: #2563eb;--color-success: #10b981;--color-danger: #ef4444;--color-card-bg: #f9fafb;--color-border: #e5e7eb}@media(prefers-color-scheme:dark){:root{--color-bg: #111827;--color-text: #f9fafb;--color-text-muted: #9ca3af;--color-primary: #3b82f6;--color-success: #34d399;--color-danger: #f87171;--color-card-bg: #1f2937;--color-border: #374151}}html,body{margin:0;padding:0;background:var(--color-bg);color:var(--color-text)}.main{width:100%;margin:0 auto;padding:8px;display:flex;flex-direction:column;gap:8px}.header{display:flex;flex-direction:column;gap:6px}#audio-controls:empty{display:none}#audio-controls:not(:empty)~#status{display:none}.audio-controls{width:100%}.audio-controls .abcjs-inline-audio{border-radius:8px}.audio-controls .abcjs-inline-audio .abcjs-midi-loop.abcjs-pushed{background-color:var(--color-success)!important;border-color:var(--color-success)!important}.status{font-size:.875rem;color:var(--color-text-muted)}.status.error{color:var(--color-danger)}.toolbar{display:none;flex-wrap:wrap;gap:8px}.toolbar.visible{display:flex}.toolbar-group{display:flex;align-items:center;gap:6px;flex:1;min-width:140px}.control-label{font-size:.75rem;color:var(--color-text-muted);white-space:nowrap;text-transform:uppercase;letter-spacing:.05em}.control-select{flex:1;padding:5px 8px;border:1px solid var(--color-border);border-radius:6px;background:var(--color-card-bg);color:var(--color-text);font-size:.8125rem;cursor:pointer;min-width:0}.control-select:focus{outline:2px solid var(--color-primary);outline-offset:-1px}.toolbar-btn{padding:5px 10px;border:1px solid var(--color-border);border-radius:6px;background:var(--color-card-bg);color:var(--color-text);font-size:1rem;cursor:pointer;line-height:1}.toolbar-btn:hover{background:var(--color-border)}.note-playing path,.note-playing circle,.note-playing ellipse,.note-playing polygon{fill:var(--color-primary)!important;transition:fill .08s ease}.note-playing path[stroke]{stroke:var(--color-primary)!important}.sheet-section{background:var(--color-card-bg);border-radius:8px;padding:16px;border:1px solid var(--color-border);min-height:200px;max-height:80vh;overflow-y:auto;scroll-behavior:smooth}.sheet-section .sheet-music-container{width:100%;overflow-x:auto}</style>
233
233
  </head>
234
234
  <body>
package/dist/server.js CHANGED
@@ -54182,7 +54182,7 @@ function createServer(options) {
54182
54182
  let clientSupportsExtApps = false;
54183
54183
  const server = new McpServer({
54184
54184
  name: "Music Studio",
54185
- version: "0.2.0"
54185
+ version: "0.2.1"
54186
54186
  });
54187
54187
  const resourceUri = "ui://sheet-music/mcp-app.html";
54188
54188
  const playInputSchema = exports_external.object({
@@ -81,7 +81,7 @@ Boolean requesting whether a visible border and background is provided by the ho
81
81
  - omitted: host decides border`)});p({method:u("ui/request-display-mode"),params:p({mode:ze.describe("The display mode being requested.")})});var fl=p({mode:ze.describe("The display mode that was actually set. May differ from requested if not supported.")}).passthrough(),ml=S([u("model"),u("app")]).describe("Tool visibility scope - who can access the tool.");p({resourceUri:l().optional(),visibility:w(ml).optional().describe(`Who can access this tool. Default: ["model", "app"]
82
82
  - "model": Tool visible to and callable by the agent
83
83
  - "app": Tool callable by the app from this server only`)});p({mimeTypes:w(l()).optional().describe('Array of supported MIME types for UI resources.\nMust include `"text/html;profile=mcp-app"` for MCP Apps support.')});p({method:u("ui/download-file"),params:p({contents:w(S([ao,co])).describe("Resource contents to download — embedded (inline data) or linked (host fetches). Uses standard MCP resource types.")})});p({method:u("ui/message"),params:p({role:u("user").describe('Message role, currently only "user" is supported.'),content:w(Ce).describe("Message content blocks (text, image, etc.).")})});p({method:u("ui/notifications/sandbox-resource-ready"),params:p({html:l().describe("HTML content to load into the inner iframe."),sandbox:l().optional().describe("Optional override for the inner iframe's sandbox attribute."),csp:Nt.optional().describe("CSP configuration from resource metadata."),permissions:qt.optional().describe("Sandbox permissions from resource metadata.")})});var gl=p({method:u("ui/notifications/tool-result"),params:Qe.describe("Standard MCP tool execution result.")}),ho=p({toolInfo:p({id:$e.optional().describe("JSON-RPC id of the tools/call request."),tool:xt.describe("Tool definition including name, inputSchema, etc.")}).optional().describe("Metadata of the tool call that instantiated this App."),theme:el.optional().describe("Current color theme preference."),styles:ll.optional().describe("Style configuration for theming the app."),displayMode:ze.optional().describe("How the UI is currently displayed."),availableDisplayModes:w(ze).optional().describe("Display modes the host supports."),containerDimensions:S([p({height:k().describe("Fixed container height in pixels.")}),p({maxHeight:S([k(),rt()]).optional().describe("Maximum container height in pixels.")})]).and(S([p({width:k().describe("Fixed container width in pixels.")}),p({maxWidth:S([k(),rt()]).optional().describe("Maximum container width in pixels.")})])).optional().describe(`Container dimensions. Represents the dimensions of the iframe or other
84
- container holding the app. Specify either width or maxWidth, and either height or maxHeight.`),locale:l().optional().describe("User's language and region preference in BCP 47 format."),timeZone:l().optional().describe("User's timezone in IANA format."),userAgent:l().optional().describe("Host application identifier."),platform:S([u("web"),u("desktop"),u("mobile")]).optional().describe("Platform type for responsive design decisions."),deviceCapabilities:p({touch:I().optional().describe("Whether the device supports touch input."),hover:I().optional().describe("Whether the device supports hover interactions.")}).optional().describe("Device input capabilities."),safeAreaInsets:p({top:k().describe("Top safe area inset in pixels."),right:k().describe("Right safe area inset in pixels."),bottom:k().describe("Bottom safe area inset in pixels."),left:k().describe("Left safe area inset in pixels.")}).optional().describe("Mobile safe area boundaries in pixels.")}).passthrough(),_l=p({method:u("ui/notifications/host-context-changed"),params:ho.describe("Partial context update containing only changed fields.")});p({method:u("ui/update-model-context"),params:p({content:w(Ce).optional().describe("Context content blocks (text, image, etc.)."),structuredContent:R(l(),P().describe("Structured content for machine-readable context data.")).optional().describe("Structured content for machine-readable context data.")})});p({method:u("ui/initialize"),params:p({appInfo:Ke.describe("App identification (name and version)."),appCapabilities:hl.describe("Features and capabilities this app provides."),protocolVersion:l().describe("Protocol version this app supports.")})});var bl=p({protocolVersion:l().describe('Negotiated protocol version string (e.g., "2025-11-21").'),hostInfo:Ke.describe("Host application identification and version."),hostCapabilities:pl.describe("Features and capabilities provided by the host."),hostContext:ho.describe("Rich context about the host environment.")}).passthrough();class vl extends Gu{constructor(n,o={},r={autoResize:!0}){super(r);H(this,"_appInfo");H(this,"_capabilities");H(this,"options");H(this,"_hostCapabilities");H(this,"_hostInfo");H(this,"_hostContext");H(this,"sendOpenLink",this.openLink);this._appInfo=n,this._capabilities=o,this.options=r,this.setRequestHandler(Ge,s=>(console.log("Received ping:",s.params),{})),this.onhostcontextchanged=()=>{}}getHostCapabilities(){return this._hostCapabilities}getHostVersion(){return this._hostInfo}getHostContext(){return this._hostContext}set ontoolinput(n){this.setNotificationHandler(il,o=>n(o.params))}set ontoolinputpartial(n){this.setNotificationHandler(al,o=>n(o.params))}set ontoolresult(n){this.setNotificationHandler(gl,o=>n(o.params))}set ontoolcancelled(n){this.setNotificationHandler(cl,o=>n(o.params))}set onhostcontextchanged(n){this.setNotificationHandler(_l,o=>{this._hostContext={...this._hostContext,...o.params},n(o.params)})}set onteardown(n){this.setRequestHandler(dl,(o,r)=>n(o.params,r))}set oncalltool(n){this.setRequestHandler(lo,(o,r)=>n(o.params,r))}set onlisttools(n){this.setRequestHandler(uo,(o,r)=>n(o.params,r))}assertCapabilityForMethod(n){}assertRequestHandlerCapability(n){switch(n){case"tools/call":case"tools/list":if(!this._capabilities.tools)throw Error(`Client does not support tool capability (required for ${n})`);return;case"ping":case"ui/resource-teardown":return;default:throw Error(`No handler for method ${n} registered`)}}assertNotificationCapability(n){}assertTaskCapability(n){throw Error("Tasks are not supported in MCP Apps")}assertTaskHandlerCapability(n){throw Error("Task handlers are not supported in MCP Apps")}async callServerTool(n,o){if(typeof n=="string")throw Error(`callServerTool() expects an object as its first argument, but received a string ("${n}"). Did you mean: callServerTool({ name: "${n}", arguments: { ... } })?`);return await this.request({method:"tools/call",params:n},Qe,o)}async readServerResource(n,o){return await this.request({method:"resources/read",params:n},io,o)}async listServerResources(n,o){return await this.request({method:"resources/list",params:n},so,o)}sendMessage(n,o){return this.request({method:"ui/message",params:n},sl,o)}sendLog(n){return this.notification({method:"notifications/message",params:n})}updateModelContext(n,o){return this.request({method:"ui/update-model-context",params:n},vt,o)}openLink(n,o){return this.request({method:"ui/open-link",params:n},ol,o)}downloadFile(n,o){return this.request({method:"ui/download-file",params:n},rl,o)}requestDisplayMode(n,o){return this.request({method:"ui/request-display-mode",params:n},fl,o)}sendSizeChanged(n){return this.notification({method:"ui/notifications/size-changed",params:n})}setupSizeChangedNotifications(){let n=!1,o=0,r=0,s=()=>{n||(n=!0,requestAnimationFrame(()=>{n=!1;let a=document.documentElement,c=a.style.width,d=a.style.height;a.style.width="fit-content",a.style.height="max-content";let h=a.getBoundingClientRect();a.style.width=c,a.style.height=d;let m=window.innerWidth-a.clientWidth,g=Math.ceil(h.width+m),_=Math.ceil(h.height);(g!==o||_!==r)&&(o=g,r=_,this.sendSizeChanged({width:g,height:_}))}))};s();let i=new ResizeObserver(s);return i.observe(document.documentElement),i.observe(document.body),()=>i.disconnect()}async connect(n=new Xu(window.parent,window.parent),o){var r;if(this.transport)throw Error("App is already connected. Call close() before connecting again.");await super.connect(n);try{let s=await this.request({method:"ui/initialize",params:{appCapabilities:this._capabilities,appInfo:this._appInfo,protocolVersion:Qu}},bl,o);if(s===void 0)throw Error(`Server sent invalid initialize result: ${s}`);this._hostCapabilities=s.hostCapabilities,this._hostInfo=s.hostInfo,this._hostContext=s.hostContext,await this.notification({method:"ui/notifications/initialized"}),(r=this.options)!=null&&r.autoResize&&this.setupSizeChangedNotifications()}catch(s){throw this.close(),s}}}const yl="https://unpkg.com/@strudel/repl@1.3.0",Ae=new vl({name:"Strudel Live Pattern",version:"0.2.0"}),at=document.getElementById("play-btn"),wl=document.getElementById("stop-btn"),kl=document.getElementById("fullscreen-btn"),an=document.getElementById("status"),cn=document.getElementById("strudel-container");let se=null,fo="",mo=!1,un=!1;"audioSession"in navigator&&(navigator.audioSession.type="playback");function Ye(){return(se==null?void 0:se.editor)??null}async function Sl(){if(!un)return new Promise((e,t)=>{const n=document.createElement("script");n.src=yl,n.onload=async()=>{un=!0;try{const o=window.strudel;o!=null&&o.prebake&&await o.prebake()}catch{}e()},n.onerror=()=>t(new Error("Failed to load Strudel REPL")),document.head.appendChild(n)})}function re(e,t="normal"){an.textContent=e,an.className=`status ${t}`}function He(e){mo=e,at.classList.toggle("playing",e),at.textContent=e?"Playing":"Play",re(e?"Playing...":"Ready",e?"playing":"normal")}function zl(e,t){const n=t/60/4,o=Math.round(n*1e4)/1e4;return/setcps\s*\(/.test(e)?e.replace(/setcps\s*\([^)]*\)/,`setcps(${o})`):`setcps(${o})
84
+ container holding the app. Specify either width or maxWidth, and either height or maxHeight.`),locale:l().optional().describe("User's language and region preference in BCP 47 format."),timeZone:l().optional().describe("User's timezone in IANA format."),userAgent:l().optional().describe("Host application identifier."),platform:S([u("web"),u("desktop"),u("mobile")]).optional().describe("Platform type for responsive design decisions."),deviceCapabilities:p({touch:I().optional().describe("Whether the device supports touch input."),hover:I().optional().describe("Whether the device supports hover interactions.")}).optional().describe("Device input capabilities."),safeAreaInsets:p({top:k().describe("Top safe area inset in pixels."),right:k().describe("Right safe area inset in pixels."),bottom:k().describe("Bottom safe area inset in pixels."),left:k().describe("Left safe area inset in pixels.")}).optional().describe("Mobile safe area boundaries in pixels.")}).passthrough(),_l=p({method:u("ui/notifications/host-context-changed"),params:ho.describe("Partial context update containing only changed fields.")});p({method:u("ui/update-model-context"),params:p({content:w(Ce).optional().describe("Context content blocks (text, image, etc.)."),structuredContent:R(l(),P().describe("Structured content for machine-readable context data.")).optional().describe("Structured content for machine-readable context data.")})});p({method:u("ui/initialize"),params:p({appInfo:Ke.describe("App identification (name and version)."),appCapabilities:hl.describe("Features and capabilities this app provides."),protocolVersion:l().describe("Protocol version this app supports.")})});var bl=p({protocolVersion:l().describe('Negotiated protocol version string (e.g., "2025-11-21").'),hostInfo:Ke.describe("Host application identification and version."),hostCapabilities:pl.describe("Features and capabilities provided by the host."),hostContext:ho.describe("Rich context about the host environment.")}).passthrough();class vl extends Gu{constructor(n,o={},r={autoResize:!0}){super(r);H(this,"_appInfo");H(this,"_capabilities");H(this,"options");H(this,"_hostCapabilities");H(this,"_hostInfo");H(this,"_hostContext");H(this,"sendOpenLink",this.openLink);this._appInfo=n,this._capabilities=o,this.options=r,this.setRequestHandler(Ge,s=>(console.log("Received ping:",s.params),{})),this.onhostcontextchanged=()=>{}}getHostCapabilities(){return this._hostCapabilities}getHostVersion(){return this._hostInfo}getHostContext(){return this._hostContext}set ontoolinput(n){this.setNotificationHandler(il,o=>n(o.params))}set ontoolinputpartial(n){this.setNotificationHandler(al,o=>n(o.params))}set ontoolresult(n){this.setNotificationHandler(gl,o=>n(o.params))}set ontoolcancelled(n){this.setNotificationHandler(cl,o=>n(o.params))}set onhostcontextchanged(n){this.setNotificationHandler(_l,o=>{this._hostContext={...this._hostContext,...o.params},n(o.params)})}set onteardown(n){this.setRequestHandler(dl,(o,r)=>n(o.params,r))}set oncalltool(n){this.setRequestHandler(lo,(o,r)=>n(o.params,r))}set onlisttools(n){this.setRequestHandler(uo,(o,r)=>n(o.params,r))}assertCapabilityForMethod(n){}assertRequestHandlerCapability(n){switch(n){case"tools/call":case"tools/list":if(!this._capabilities.tools)throw Error(`Client does not support tool capability (required for ${n})`);return;case"ping":case"ui/resource-teardown":return;default:throw Error(`No handler for method ${n} registered`)}}assertNotificationCapability(n){}assertTaskCapability(n){throw Error("Tasks are not supported in MCP Apps")}assertTaskHandlerCapability(n){throw Error("Task handlers are not supported in MCP Apps")}async callServerTool(n,o){if(typeof n=="string")throw Error(`callServerTool() expects an object as its first argument, but received a string ("${n}"). Did you mean: callServerTool({ name: "${n}", arguments: { ... } })?`);return await this.request({method:"tools/call",params:n},Qe,o)}async readServerResource(n,o){return await this.request({method:"resources/read",params:n},io,o)}async listServerResources(n,o){return await this.request({method:"resources/list",params:n},so,o)}sendMessage(n,o){return this.request({method:"ui/message",params:n},sl,o)}sendLog(n){return this.notification({method:"notifications/message",params:n})}updateModelContext(n,o){return this.request({method:"ui/update-model-context",params:n},vt,o)}openLink(n,o){return this.request({method:"ui/open-link",params:n},ol,o)}downloadFile(n,o){return this.request({method:"ui/download-file",params:n},rl,o)}requestDisplayMode(n,o){return this.request({method:"ui/request-display-mode",params:n},fl,o)}sendSizeChanged(n){return this.notification({method:"ui/notifications/size-changed",params:n})}setupSizeChangedNotifications(){let n=!1,o=0,r=0,s=()=>{n||(n=!0,requestAnimationFrame(()=>{n=!1;let a=document.documentElement,c=a.style.width,d=a.style.height;a.style.width="fit-content",a.style.height="max-content";let h=a.getBoundingClientRect();a.style.width=c,a.style.height=d;let m=window.innerWidth-a.clientWidth,g=Math.ceil(h.width+m),_=Math.ceil(h.height);(g!==o||_!==r)&&(o=g,r=_,this.sendSizeChanged({width:g,height:_}))}))};s();let i=new ResizeObserver(s);return i.observe(document.documentElement),i.observe(document.body),()=>i.disconnect()}async connect(n=new Xu(window.parent,window.parent),o){var r;if(this.transport)throw Error("App is already connected. Call close() before connecting again.");await super.connect(n);try{let s=await this.request({method:"ui/initialize",params:{appCapabilities:this._capabilities,appInfo:this._appInfo,protocolVersion:Qu}},bl,o);if(s===void 0)throw Error(`Server sent invalid initialize result: ${s}`);this._hostCapabilities=s.hostCapabilities,this._hostInfo=s.hostInfo,this._hostContext=s.hostContext,await this.notification({method:"ui/notifications/initialized"}),(r=this.options)!=null&&r.autoResize&&this.setupSizeChangedNotifications()}catch(s){throw this.close(),s}}}const yl="https://unpkg.com/@strudel/repl@1.3.0",Ae=new vl({name:"Strudel Live Pattern",version:"0.2.1"}),at=document.getElementById("play-btn"),wl=document.getElementById("stop-btn"),kl=document.getElementById("fullscreen-btn"),an=document.getElementById("status"),cn=document.getElementById("strudel-container");let se=null,fo="",mo=!1,un=!1;"audioSession"in navigator&&(navigator.audioSession.type="playback");function Ye(){return(se==null?void 0:se.editor)??null}async function Sl(){if(!un)return new Promise((e,t)=>{const n=document.createElement("script");n.src=yl,n.onload=async()=>{un=!0;try{const o=window.strudel;o!=null&&o.prebake&&await o.prebake()}catch{}e()},n.onerror=()=>t(new Error("Failed to load Strudel REPL")),document.head.appendChild(n)})}function re(e,t="normal"){an.textContent=e,an.className=`status ${t}`}function He(e){mo=e,at.classList.toggle("playing",e),at.textContent=e?"Playing":"Play",re(e?"Playing...":"Ready",e?"playing":"normal")}function zl(e,t){const n=t/60/4,o=Math.round(n*1e4)/1e4;return/setcps\s*\(/.test(e)?e.replace(/setcps\s*\([^)]*\)/,`setcps(${o})`):`setcps(${o})
85
85
  ${e}`}function Tl(e=8e3){return new Promise((t,n)=>{const o=Date.now(),r=()=>{const s=Ye();s!=null&&s.setCode?t(s):Date.now()-o>e?n(new Error("Strudel editor did not initialize")):setTimeout(r,150)};r()})}function tt(){if(document.querySelectorAll("body > canvas").forEach(e=>{const t=e.style;t.position==="fixed"&&(t.display="none")}),se!=null&&se.nextElementSibling){const e=se.nextElementSibling;e.querySelector(".cm-editor")&&(e.style.minHeight="200px",e.style.flex="1")}}async function $l(e){const t=e.code;if(!t)return;const n=e.bpm,o=e.autoplay;try{re("Loading Strudel..."),await Sl();let r=t;n&&(r=zl(r,n)),fo=r,se||(cn.innerHTML=`<strudel-editor>\x3C!--
86
86
  ${r}
87
87
  --></strudel-editor>`,se=cn.querySelector("strudel-editor")),re("Initializing...");const s=await Tl();if(tt(),setTimeout(tt,500),setTimeout(tt,1500),s.setCode(r),o!==!1)try{s.evaluate(r,!0),He(!0)}catch{re("Click Play to start","normal")}else re("Ready — click Play or Ctrl+Enter","normal")}catch(r){re(`Error: ${r.message}`,"error")}}at.addEventListener("click",()=>{const e=Ye();if(e)try{mo?(e.stop(),He(!1)):(e.evaluate(fo,!0),He(!0))}catch(t){re(`Playback error: ${t.message}`,"error")}});wl.addEventListener("click",()=>{const e=Ye();if(e)try{e.stop(),He(!1)}catch(t){re(`Stop error: ${t.message}`,"error")}});kl.addEventListener("click",()=>{Ae.requestDisplayMode({mode:"fullscreen"})});Ae.connect().then(()=>{Ae.ontoolinput=e=>{$l(e.arguments??{})},Ae.ontoolinputpartial=e=>{var o;const t=(o=e.arguments)==null?void 0:o.code;if(!t)return;re("Composing pattern...");const n=Ye();n!=null&&n.setCode&&n.setCode(t)}});</script>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-music-studio",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "type": "module",
5
5
  "description": "Two-mode MCP music studio: scored composition (ABC notation) and live performance (Strudel). Interactive ext-apps UI with sheet music rendering, 30+ instruments, style presets, and live coding REPL.",
6
6
  "keywords": [