mcp-music-studio 0.1.1 → 0.1.2
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 +26 -2
- package/dist/index.js +34 -7
- package/dist/mcp-app.html +1 -1
- package/dist/server.d.ts +6 -1
- package/dist/server.js +95 -39
- package/dist/src/browser-fallback.d.ts +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ Forked from [`@modelcontextprotocol/server-sheet-music`](https://github.com/mode
|
|
|
8
8
|
|
|
9
9
|
- **8 style presets** — rock, jazz, bossa, waltz, march, reggae, folk, classical. One parameter adds drums + bass + chord accompaniment.
|
|
10
10
|
- **30 instruments** — selectable from UI or via tool parameter, with fuzzy matching
|
|
11
|
-
- **
|
|
11
|
+
- **Three rendering modes** — ext-apps inline UI for Claude Desktop/VS Code, browser fallback for CLI environments, configurable via `--render-mode`
|
|
12
12
|
- **`get-music-guide` tool** — on-demand reference for AI systems (instruments, drums, ABC syntax, arrangements, genre templates, MIDI directives)
|
|
13
13
|
- **7 `music://guide/*` resources** — same content for resource-capable clients
|
|
14
14
|
- **Note highlighting** — currently playing notes light up during playback
|
|
@@ -148,7 +148,6 @@ Creates and plays music with visual sheet music and multi-instrument audio.
|
|
|
148
148
|
| `tempo` | number? | BPM (40-240) |
|
|
149
149
|
| `swing` | number? | Swing feel (0-100) |
|
|
150
150
|
| `transpose` | number? | Semitones (-12 to 12) |
|
|
151
|
-
| `openInBrowser` | boolean? | Open standalone browser player (for CLI environments without UI) |
|
|
152
151
|
|
|
153
152
|
**Example — jazz arrangement:**
|
|
154
153
|
```json
|
|
@@ -174,6 +173,31 @@ Returns reference material for composition. Topics:
|
|
|
174
173
|
| `styles` | What each style preset does and when to use it |
|
|
175
174
|
| `midi-directives` | Full `%%MIDI` reference for ABCJS |
|
|
176
175
|
|
|
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:**
|
|
189
|
+
|
|
190
|
+
```json
|
|
191
|
+
{
|
|
192
|
+
"mcpServers": {
|
|
193
|
+
"music-studio": {
|
|
194
|
+
"command": "npx",
|
|
195
|
+
"args": ["-y", "mcp-music-studio", "--stdio", "--render-mode", "browser", "--output-dir", "/path/to/output"]
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
177
201
|
## Development
|
|
178
202
|
|
|
179
203
|
```bash
|
package/dist/index.js
CHANGED
|
@@ -30624,13 +30624,13 @@ async function startStreamableHTTPServer(createServer2) {
|
|
|
30624
30624
|
}
|
|
30625
30625
|
}
|
|
30626
30626
|
});
|
|
30627
|
-
const httpServer = app.listen(port, (
|
|
30628
|
-
if (err) {
|
|
30629
|
-
console.error("Failed to start server:", err);
|
|
30630
|
-
process.exit(1);
|
|
30631
|
-
}
|
|
30627
|
+
const httpServer = app.listen(port, () => {
|
|
30632
30628
|
console.log(`MCP server listening on http://localhost:${port}/mcp`);
|
|
30633
30629
|
});
|
|
30630
|
+
httpServer.on("error", (err) => {
|
|
30631
|
+
console.error("Failed to start server:", err);
|
|
30632
|
+
process.exit(1);
|
|
30633
|
+
});
|
|
30634
30634
|
const shutdown = () => {
|
|
30635
30635
|
console.log(`
|
|
30636
30636
|
Shutting down...`);
|
|
@@ -30642,11 +30642,38 @@ Shutting down...`);
|
|
|
30642
30642
|
async function startStdioServer(createServer2) {
|
|
30643
30643
|
await createServer2().connect(new StdioServerTransport);
|
|
30644
30644
|
}
|
|
30645
|
+
function parseArg(name) {
|
|
30646
|
+
for (let i = 0;i < process.argv.length; i++) {
|
|
30647
|
+
const arg = process.argv[i].trim();
|
|
30648
|
+
if (arg === name && i + 1 < process.argv.length)
|
|
30649
|
+
return process.argv[i + 1].trim();
|
|
30650
|
+
if (arg.startsWith(name + "="))
|
|
30651
|
+
return arg.slice(name.length + 1).trim();
|
|
30652
|
+
if (arg.startsWith(name + " "))
|
|
30653
|
+
return arg.slice(name.length).trim();
|
|
30654
|
+
}
|
|
30655
|
+
return;
|
|
30656
|
+
}
|
|
30657
|
+
function parseRenderMode() {
|
|
30658
|
+
const val = parseArg("--render-mode");
|
|
30659
|
+
if (!val)
|
|
30660
|
+
return "auto";
|
|
30661
|
+
if (val === "html" || val === "browser" || val === "auto")
|
|
30662
|
+
return val;
|
|
30663
|
+
console.error(`Unknown render mode "${val}", using "auto"`);
|
|
30664
|
+
return "auto";
|
|
30665
|
+
}
|
|
30666
|
+
function parseOutputDir() {
|
|
30667
|
+
return parseArg("--output-dir");
|
|
30668
|
+
}
|
|
30645
30669
|
async function main() {
|
|
30670
|
+
const defaultRenderMode = parseRenderMode();
|
|
30671
|
+
const outputDir = parseOutputDir();
|
|
30672
|
+
const factory = () => createServer({ defaultRenderMode, outputDir });
|
|
30646
30673
|
if (process.argv.includes("--stdio")) {
|
|
30647
|
-
await startStdioServer(
|
|
30674
|
+
await startStdioServer(factory);
|
|
30648
30675
|
} else {
|
|
30649
|
-
await startStreamableHTTPServer(
|
|
30676
|
+
await startStreamableHTTPServer(factory);
|
|
30650
30677
|
}
|
|
30651
30678
|
}
|
|
30652
30679
|
main().catch((e) => {
|
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 ye={visualObj:null,synthControl:null,currentInstrument:ou,currentStyle:"",currentAbc:null,highlightedEls:[]},nr=document.querySelector(".main"),Tc=document.getElementById("status"),At=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(ye.highlightedEls.forEach(t=>t.classList.remove("note-playing")),ye.highlightedEls=[],!!e.elements){for(const t of e.elements)for(const n of t)n.classList.add("note-playing"),ye.highlightedEls.push(n);ye.highlightedEls.length>0&&ye.highlightedEls[0].scrollIntoView({behavior:"smooth",block:"nearest"})}},onFinished(){ye.highlightedEls.forEach(e=>e.classList.remove("note-playing")),ye.highlightedEls=[]}},_t=document.createElement("select");_t.id="instrument-select";_t.className="control-select";for(const e of Object.keys($t)){const t=document.createElement("option");t.value=e,t.textContent=e,e===ye.currentInstrument&&(t.selected=!0),_t.appendChild(t)}_t.addEventListener("change",()=>{ye.currentInstrument=_t.value,ye.visualObj&&ye.synthControl&&p3()});async function p3(){var e;if(!(!ye.synthControl||!((e=ye.visualObj)!=null&&e[0])))try{const n={program:$t[ye.currentInstrument]??0};await ye.synthControl.setTune(ye.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(_t);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",()=>{ye.currentStyle=ot.value,ye.currentAbc&&fu(ye.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 It(e,t=!1){Tc.textContent=e,Tc.classList.toggle("error",t)}async function fu(e,t){try{It("Rendering..."),ye.synthControl&&ye.synthControl.pause(),ye.currentAbc=e,At.innerHTML="",pa.innerHTML="";const n=cu(e,ye.currentStyle);if(ye.visualObj=ar.renderAbc(At,n,{responsive:"resize",add_classes:!0}),!ye.visualObj||ye.visualObj.length===0)throw new Error("Failed to parse music notation");if(!ar.synth.supportsAudio())throw new Error("Audio not supported in this browser");ye.synthControl=new ar.synth.SynthController,ye.synthControl.load(pa,d3,{displayLoop:!0,displayPlay:!0,displayProgress:!0,displayWarp:!0});const r={program:$t[ye.currentInstrument]??0,...t};await ye.synthControl.setTune(ye.visualObj[0],!1,r),hu.classList.add("visible"),It("Ready to play!")}catch(n){console.error("Render error:",n),It(`Error: ${n.message}`,!0),pa.innerHTML=""}}const kt=new yg({name:"Music Studio",version:"0.1.
|
|
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 ye={visualObj:null,synthControl:null,currentInstrument:ou,currentStyle:"",currentAbc:null,highlightedEls:[]},nr=document.querySelector(".main"),Tc=document.getElementById("status"),At=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(ye.highlightedEls.forEach(t=>t.classList.remove("note-playing")),ye.highlightedEls=[],!!e.elements){for(const t of e.elements)for(const n of t)n.classList.add("note-playing"),ye.highlightedEls.push(n);ye.highlightedEls.length>0&&ye.highlightedEls[0].scrollIntoView({behavior:"smooth",block:"nearest"})}},onFinished(){ye.highlightedEls.forEach(e=>e.classList.remove("note-playing")),ye.highlightedEls=[]}},_t=document.createElement("select");_t.id="instrument-select";_t.className="control-select";for(const e of Object.keys($t)){const t=document.createElement("option");t.value=e,t.textContent=e,e===ye.currentInstrument&&(t.selected=!0),_t.appendChild(t)}_t.addEventListener("change",()=>{ye.currentInstrument=_t.value,ye.visualObj&&ye.synthControl&&p3()});async function p3(){var e;if(!(!ye.synthControl||!((e=ye.visualObj)!=null&&e[0])))try{const n={program:$t[ye.currentInstrument]??0};await ye.synthControl.setTune(ye.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(_t);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",()=>{ye.currentStyle=ot.value,ye.currentAbc&&fu(ye.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 It(e,t=!1){Tc.textContent=e,Tc.classList.toggle("error",t)}async function fu(e,t){try{It("Rendering..."),ye.synthControl&&ye.synthControl.pause(),ye.currentAbc=e,At.innerHTML="",pa.innerHTML="";const n=cu(e,ye.currentStyle);if(ye.visualObj=ar.renderAbc(At,n,{responsive:"resize",add_classes:!0}),!ye.visualObj||ye.visualObj.length===0)throw new Error("Failed to parse music notation");if(!ar.synth.supportsAudio())throw new Error("Audio not supported in this browser");ye.synthControl=new ar.synth.SynthController,ye.synthControl.load(pa,d3,{displayLoop:!0,displayPlay:!0,displayProgress:!0,displayWarp:!0});const r={program:$t[ye.currentInstrument]??0,...t};await ye.synthControl.setTune(ye.visualObj[0],!1,r),hu.classList.add("visible"),It("Ready to play!")}catch(n){console.error("Render error:",n),It(`Error: ${n.message}`,!0),pa.innerHTML=""}}const kt=new yg({name:"Music Studio",version:"0.1.2"});ir=kt;kt.ontoolinput=e=>{console.info("Received tool input:",e);const t=f3(e.arguments??{});ye.currentInstrument=t.instrument,_t.value=t.instrument,ye.currentStyle=t.style,ot.value=t.style,t.abcNotation?fu(t.abcNotation,t.synthOptions).catch(console.error):It("No ABC notation provided",!0)};let ga=null,Ec="";kt.ontoolinputpartial=e=>{var a,r;const t=(a=e.arguments)==null?void 0:a.abcNotation;if(!t)return;ye.currentAbc=t;const n=(r=e.arguments)==null?void 0:r.style;if(n&&ui(n)&&ye.currentStyle!==n&&(ye.currentStyle=n,ot.value=n),!t.match(/K:[^\n]+/)){It("Composing...");return}t!==Ec&&(Ec=t,ga&&clearTimeout(ga),ga=setTimeout(()=>{try{const s=cu(t,ye.currentStyle);ar.renderAbc(At,s,{responsive:"resize",add_classes:!0}),At.scrollTop=At.scrollHeight;const o=At.closest(".sheet-section");o&&(o.scrollTop=o.scrollHeight),It("Composing...")}catch{}},150))};kt.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`)}kt.onhostcontextchanged=du;kt.connect().then(()=>{const e=kt.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.d.ts
CHANGED
|
@@ -8,5 +8,10 @@ export declare function handlePlaySheetMusic({ abcNotation, }: {
|
|
|
8
8
|
export declare function handleGetMusicGuide({ topic, }: {
|
|
9
9
|
topic: (typeof GUIDE_TOPICS)[number];
|
|
10
10
|
}): Promise<CallToolResult>;
|
|
11
|
-
export
|
|
11
|
+
export type RenderMode = "auto" | "html" | "browser";
|
|
12
|
+
export interface ServerOptions {
|
|
13
|
+
defaultRenderMode?: RenderMode;
|
|
14
|
+
outputDir?: string;
|
|
15
|
+
}
|
|
16
|
+
export declare function createServer(options?: ServerOptions): McpServer;
|
|
12
17
|
export {};
|
package/dist/server.js
CHANGED
|
@@ -52118,6 +52118,12 @@ function uZ(Z, $, J, X) {
|
|
|
52118
52118
|
function fZ(Z, $, J, X, D) {
|
|
52119
52119
|
return Z.registerResource($, J, { mimeType: d, ...X }, D);
|
|
52120
52120
|
}
|
|
52121
|
+
var OQ = "io.modelcontextprotocol/ui";
|
|
52122
|
+
function dZ(Z) {
|
|
52123
|
+
if (!Z)
|
|
52124
|
+
return;
|
|
52125
|
+
return Z.extensions?.[OQ];
|
|
52126
|
+
}
|
|
52121
52127
|
|
|
52122
52128
|
// src/music-logic.ts
|
|
52123
52129
|
var INSTRUMENTS = {
|
|
@@ -52343,8 +52349,11 @@ function formatKey(raw) {
|
|
|
52343
52349
|
return "";
|
|
52344
52350
|
return raw.replace(/([A-G])b/g, "$1♭").replace(/([A-G])#/g, "$1♯");
|
|
52345
52351
|
}
|
|
52352
|
+
function stripVoiceNameQuotes(abc) {
|
|
52353
|
+
return abc.replace(/^(V:\S+.*?\bname=)"([^"]*)"(.*)$/gm, "$1$2$3");
|
|
52354
|
+
}
|
|
52346
52355
|
function generatePlayerHtml(options) {
|
|
52347
|
-
let abc = options.abcNotation;
|
|
52356
|
+
let abc = stripVoiceNameQuotes(options.abcNotation);
|
|
52348
52357
|
if (options.tempo !== undefined || options.transpose !== undefined) {
|
|
52349
52358
|
abc = injectTempoAndTranspose(abc, {
|
|
52350
52359
|
tempo: options.tempo,
|
|
@@ -52673,12 +52682,12 @@ function generatePlayerHtml(options) {
|
|
|
52673
52682
|
</body>
|
|
52674
52683
|
</html>`;
|
|
52675
52684
|
}
|
|
52676
|
-
async function openPlayerInBrowser(options) {
|
|
52685
|
+
async function openPlayerInBrowser(options, outputDir) {
|
|
52677
52686
|
const html = generatePlayerHtml(options);
|
|
52678
|
-
const
|
|
52679
|
-
await fs.mkdir(
|
|
52687
|
+
const outDir = outputDir ?? path.join(os.homedir(), "Desktop", "mcp-music-studio");
|
|
52688
|
+
await fs.mkdir(outDir, { recursive: true });
|
|
52680
52689
|
const filename = `player-${Date.now()}.html`;
|
|
52681
|
-
const filepath = path.join(
|
|
52690
|
+
const filepath = path.join(outDir, filename);
|
|
52682
52691
|
await fs.writeFile(filepath, html, "utf-8");
|
|
52683
52692
|
const platform = process.platform;
|
|
52684
52693
|
let cmd;
|
|
@@ -53180,62 +53189,90 @@ async function handleGetMusicGuide({
|
|
|
53180
53189
|
content: [{ type: "text", text: GUIDES[topic] }]
|
|
53181
53190
|
};
|
|
53182
53191
|
}
|
|
53183
|
-
function createServer() {
|
|
53192
|
+
function createServer(options) {
|
|
53193
|
+
const defaultRenderMode = options?.defaultRenderMode ?? "auto";
|
|
53194
|
+
const outputDir = options?.outputDir;
|
|
53195
|
+
let clientSupportsExtApps = false;
|
|
53184
53196
|
const server = new McpServer({
|
|
53185
53197
|
name: "Music Studio",
|
|
53186
|
-
version: "0.1.
|
|
53198
|
+
version: "0.1.2"
|
|
53187
53199
|
});
|
|
53188
53200
|
const resourceUri = "ui://sheet-music/mcp-app.html";
|
|
53189
|
-
|
|
53190
|
-
|
|
53191
|
-
|
|
53201
|
+
const playInputSchema = exports_external.object({
|
|
53202
|
+
abcNotation: exports_external.string().default(DEFAULT_ABC_NOTATION_INPUT).describe('ABC notation string. Include chord symbols ("C", "Am7") above notes for auto-accompaniment with style presets.'),
|
|
53203
|
+
instrument: exports_external.string().optional().describe("Default instrument. Options: Acoustic Grand Piano, Electric Piano, Harpsichord, " + "Music Box, Vibraphone, Marimba, Xylophone, Church Organ, Accordion, Harmonica, " + "Acoustic Guitar (Nylon), Acoustic Guitar (Steel), Electric Guitar (Clean), " + "Acoustic Bass, Violin, Viola, Cello, String Ensemble, Trumpet, Trombone, " + "French Horn, Alto Sax, Tenor Sax, Oboe, Clarinet, Flute, Pan Flute, Steel Drums."),
|
|
53204
|
+
style: exports_external.enum(STYLE_NAMES).optional().describe("Accompaniment style. Adds drums, bass, and chord patterns automatically. " + 'Your ABC needs chord symbols ("C", "Am") for accompaniment to work. ' + "Options: rock, jazz, bossa, waltz, march, reggae, folk, classical."),
|
|
53205
|
+
tempo: exports_external.number().min(40).max(240).optional().describe("Tempo in BPM (40-240). Overrides Q: in ABC notation."),
|
|
53206
|
+
swing: exports_external.number().min(0).max(100).optional().describe("Swing percentage (0-100). 0=straight, 33=light swing, 66=heavy swing. Great for jazz and blues."),
|
|
53207
|
+
transpose: exports_external.number().min(-12).max(12).optional().describe("Transpose by semitones (-12 to 12). Positive=higher, negative=lower.")
|
|
53208
|
+
});
|
|
53209
|
+
const BASE_DESCRIPTION = "Creates and plays music with visual sheet music and multi-instrument audio. " + "Use this to compose original songs, demonstrate music theory, set a mood, " + `celebrate occasions, or play well-known tunes.
|
|
53192
53210
|
|
|
53193
|
-
` + `
|
|
53194
|
-
` + `- Original compositions for any occasion (birthdays, lullabies, celebrations)
|
|
53195
|
-
` + `- Genre pieces: set the style parameter for automatic drums + bass + chord accompaniment
|
|
53196
|
-
` + `- Educational demos: scales, modes, chord progressions, rhythms
|
|
53197
|
-
` + `- Multi-voice arrangements with different instruments per voice
|
|
53211
|
+
` + "BEFORE COMPOSING: Call get-music-guide first to learn available instruments, " + "styles, drum patterns, and ABC syntax. Start with topic 'genres' for complete " + `examples, 'styles' for preset details, or 'instruments' for the full list.
|
|
53198
53212
|
|
|
53199
53213
|
` + `HOW IT WORKS:
|
|
53200
|
-
` + 'Write ABC notation with chord symbols ("C", "Am7", "G7") above the melody. ' + "Set a style to get automatic accompaniment. For known tunes, look up ABC from
|
|
53214
|
+
` + 'Write ABC notation with chord symbols ("C", "Am7", "G7") above the melody. ' + "Set a style to get automatic drums + bass + chord accompaniment. " + `For known tunes, look up ABC from abcnotation.com or thesession.org.
|
|
53201
53215
|
|
|
53202
53216
|
` + `QUICK ABC REFERENCE:
|
|
53203
53217
|
` + `Notes: C D E F G A B c d e (lowercase=octave up) | Rests: z z2 z4
|
|
53204
53218
|
` + `Lengths: C2(half) C/2(eighth) C3/2(dotted) | Bars: | |: :| [1 [2
|
|
53205
53219
|
` + `Chords: [CEG](simultaneous) "Am"(guitar chord) | Ties: C-C | Slurs: (CDE)
|
|
53206
53220
|
` + `Multi-voice: V:1 ... V:2 ... with %%MIDI program N per voice
|
|
53207
|
-
` +
|
|
53221
|
+
` + "Dynamics: !p! !f! !ff! !mf! | Tempo: Q:1/4=120";
|
|
53222
|
+
const EXT_APPS_SUFFIX = `
|
|
53208
53223
|
|
|
53209
|
-
|
|
53210
|
-
|
|
53211
|
-
|
|
53212
|
-
|
|
53213
|
-
|
|
53214
|
-
tempo: exports_external.number().min(40).max(240).optional().describe("Tempo in BPM (40-240). Overrides Q: in ABC notation."),
|
|
53215
|
-
swing: exports_external.number().min(0).max(100).optional().describe("Swing percentage (0-100). 0=straight, 33=light swing, 66=heavy swing. Great for jazz and blues."),
|
|
53216
|
-
transpose: exports_external.number().min(-12).max(12).optional().describe("Transpose by semitones (-12 to 12). Positive=higher, negative=lower."),
|
|
53217
|
-
openInBrowser: exports_external.boolean().optional().describe("Open a standalone browser player with full playback, note highlighting, and controls. " + "Use this in CLI environments (Claude Code, Codex, Gemini CLI) where the MCP client doesn't render UI.")
|
|
53218
|
-
}),
|
|
53219
|
-
_meta: { ui: { resourceUri } }
|
|
53220
|
-
}, async (args) => {
|
|
53224
|
+
The music player renders inline with interactive playback controls.`;
|
|
53225
|
+
const FALLBACK_SUFFIX = `
|
|
53226
|
+
|
|
53227
|
+
The music player is delivered as HTML or opened in the browser automatically.`;
|
|
53228
|
+
const playHandler = async (args) => {
|
|
53221
53229
|
const result = await handlePlaySheetMusic(args);
|
|
53222
|
-
if (
|
|
53230
|
+
if (result.isError)
|
|
53231
|
+
return result;
|
|
53232
|
+
if (defaultRenderMode === "auto")
|
|
53233
|
+
return result;
|
|
53234
|
+
const playerOpts = {
|
|
53235
|
+
abcNotation: args.abcNotation,
|
|
53236
|
+
style: args.style,
|
|
53237
|
+
instrument: args.instrument,
|
|
53238
|
+
tempo: args.tempo,
|
|
53239
|
+
swing: args.swing,
|
|
53240
|
+
transpose: args.transpose
|
|
53241
|
+
};
|
|
53242
|
+
if (defaultRenderMode === "html") {
|
|
53223
53243
|
try {
|
|
53224
|
-
const
|
|
53225
|
-
|
|
53226
|
-
|
|
53227
|
-
|
|
53228
|
-
|
|
53229
|
-
|
|
53230
|
-
|
|
53244
|
+
const html = generatePlayerHtml(playerOpts);
|
|
53245
|
+
const text = result.content[0]?.type === "text" ? result.content[0].text : "";
|
|
53246
|
+
result.content = [
|
|
53247
|
+
{ type: "text", text },
|
|
53248
|
+
{
|
|
53249
|
+
type: "resource",
|
|
53250
|
+
resource: {
|
|
53251
|
+
uri: `music://player/${Date.now()}.html`,
|
|
53252
|
+
mimeType: "text/html",
|
|
53253
|
+
text: html
|
|
53254
|
+
}
|
|
53255
|
+
}
|
|
53256
|
+
];
|
|
53257
|
+
} catch (err) {
|
|
53258
|
+
result.content.push({
|
|
53259
|
+
type: "text",
|
|
53260
|
+
text: `
|
|
53261
|
+
Failed to generate HTML player: ${err.message}`
|
|
53231
53262
|
});
|
|
53263
|
+
}
|
|
53264
|
+
} else if (defaultRenderMode === "browser") {
|
|
53265
|
+
try {
|
|
53266
|
+
const filepath = await openPlayerInBrowser(playerOpts, outputDir);
|
|
53267
|
+
const fileUrl = `file://${filepath}`;
|
|
53232
53268
|
const text = result.content[0]?.type === "text" ? result.content[0].text : "";
|
|
53233
53269
|
result.content = [
|
|
53234
53270
|
{
|
|
53235
53271
|
type: "text",
|
|
53236
53272
|
text: `${text}
|
|
53237
53273
|
|
|
53238
|
-
|
|
53274
|
+
Music player saved and opening in browser.
|
|
53275
|
+
File: ${fileUrl}`
|
|
53239
53276
|
}
|
|
53240
53277
|
];
|
|
53241
53278
|
} catch (err) {
|
|
@@ -53247,7 +53284,26 @@ Failed to open browser: ${err.message}`
|
|
|
53247
53284
|
}
|
|
53248
53285
|
}
|
|
53249
53286
|
return result;
|
|
53250
|
-
}
|
|
53287
|
+
};
|
|
53288
|
+
uZ(server, "play-sheet-music", {
|
|
53289
|
+
title: "Play Sheet Music",
|
|
53290
|
+
description: BASE_DESCRIPTION + FALLBACK_SUFFIX,
|
|
53291
|
+
inputSchema: playInputSchema,
|
|
53292
|
+
_meta: { ui: { resourceUri } }
|
|
53293
|
+
}, playHandler);
|
|
53294
|
+
server.server.oninitialized = () => {
|
|
53295
|
+
const caps = server.server.getClientCapabilities();
|
|
53296
|
+
const uiCap = dZ(caps);
|
|
53297
|
+
clientSupportsExtApps = !!uiCap?.mimeTypes?.includes(d);
|
|
53298
|
+
if (clientSupportsExtApps) {
|
|
53299
|
+
uZ(server, "play-sheet-music", {
|
|
53300
|
+
title: "Play Sheet Music",
|
|
53301
|
+
description: BASE_DESCRIPTION + EXT_APPS_SUFFIX,
|
|
53302
|
+
inputSchema: playInputSchema,
|
|
53303
|
+
_meta: { ui: { resourceUri } }
|
|
53304
|
+
}, playHandler);
|
|
53305
|
+
}
|
|
53306
|
+
};
|
|
53251
53307
|
server.registerTool("get-music-guide", {
|
|
53252
53308
|
title: "Music Reference Guide",
|
|
53253
53309
|
description: "Returns detailed reference material for music composition. " + "Topics: instruments (GM instrument list + combos), drums (patterns + percussion notes), " + "abc-syntax (notation reference), arrangements (multi-voice patterns), " + "genres (complete ABC templates for jazz/blues/folk/rock/bossa/classical), " + "styles (what each style preset does), midi-directives (%%MIDI reference).",
|
|
@@ -7,4 +7,4 @@ export interface BrowserPlayerOptions {
|
|
|
7
7
|
transpose?: number;
|
|
8
8
|
}
|
|
9
9
|
export declare function generatePlayerHtml(options: BrowserPlayerOptions): string;
|
|
10
|
-
export declare function openPlayerInBrowser(options: BrowserPlayerOptions): Promise<string>;
|
|
10
|
+
export declare function openPlayerInBrowser(options: BrowserPlayerOptions, outputDir?: string): Promise<string>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-music-studio",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "MCP music composition server — multi-instrument playback, style presets, note highlighting, and AI-friendly reference guides. Fork of @modelcontextprotocol/server-sheet-music.",
|
|
6
6
|
"keywords": [
|