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 +93 -116
- package/dist/mcp-app.html +1 -1
- package/dist/server.js +1 -1
- package/dist/strudel-app.html +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,27 +1,59 @@
|
|
|
1
1
|
# MCP Music Studio
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://smithery.ai/server/linxule/mcp-music-studio)
|
|
4
4
|
|
|
5
|
-
|
|
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
|
-
##
|
|
7
|
+
## Quick Start — No Install Required
|
|
8
8
|
|
|
9
|
-
|
|
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
|
-
|
|
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
|
-
|
|
47
|
+
### Shared
|
|
48
|
+
- **`search-music-docs`** — semantic search over strudel.cc and ABCJS documentation
|
|
23
49
|
|
|
24
|
-
|
|
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
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
Add to `.vscode/mcp.json` (project) or user settings:
|
|
95
|
+
<details>
|
|
96
|
+
<summary>VS Code / Trae / PearAI</summary>
|
|
61
97
|
|
|
62
|
-
|
|
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
|
-
|
|
112
|
+
<details>
|
|
113
|
+
<summary>Cursor</summary>
|
|
76
114
|
|
|
77
|
-
Add to `~/.cursor/mcp.json
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
|
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"
|
|
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
|
|
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
|
-
|
|
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.
|
|
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.
|
|
54185
|
+
version: "0.2.1"
|
|
54186
54186
|
});
|
|
54187
54187
|
const resourceUri = "ui://sheet-music/mcp-app.html";
|
|
54188
54188
|
const playInputSchema = exports_external.object({
|
package/dist/strudel-app.html
CHANGED
|
@@ -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.
|
|
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.
|
|
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": [
|