iflow-mcp_ghchen99_mcp-musescore 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,282 @@
1
+ Metadata-Version: 2.4
2
+ Name: iflow-mcp_ghchen99_mcp-musescore
3
+ Version: 0.1.0
4
+ Summary: MCP server for MuseScore control through WebSocket
5
+ License-File: LICENSE
6
+ Requires-Python: >=3.8
7
+ Requires-Dist: mcp>=1.0.0
8
+ Requires-Dist: websockets>=13.0
9
+ Description-Content-Type: text/markdown
10
+
11
+ # MuseScore MCP Server
12
+
13
+ A Model Context Protocol (MCP) server that provides programmatic control over MuseScore through a WebSocket-based plugin system. This allows AI assistants like Claude to compose music, add lyrics, navigate scores, and control MuseScore directly.
14
+
15
+ ![Demo GIF](./assets/mcp-muse.gif)
16
+
17
+ ## Prerequisites
18
+
19
+ - MuseScore 3.x or 4.x
20
+ - Python 3.8+
21
+ - Claude Desktop or compatible MCP client
22
+
23
+ ## Setup
24
+
25
+ ### 1. Install the MuseScore Plugin
26
+
27
+ First, save the QML plugin code to your MuseScore plugins directory:
28
+
29
+ **macOS**: `~/Documents/MuseScore4/Plugins/musescore-mcp-websocket.qml`
30
+ **Windows**: `%USERPROFILE%\Documents\MuseScore4\Plugins\musescore-mcp-websocket.qml`
31
+ **Linux**: `~/Documents/MuseScore4/Plugins/musescore-mcp-websocket.qml`
32
+
33
+ ### 2. Enable the Plugin in MuseScore
34
+
35
+ 1. Open MuseScore
36
+ 2. Go to **Plugins → Plugin Manager**
37
+ 3. Find "MuseScore API Server" and check the box to enable it
38
+ 4. Click **OK**
39
+
40
+ ### 3. Setup Python Environment
41
+
42
+ ```bash
43
+ git clone <your-repo>
44
+ cd mcp-agents-demo
45
+ python -m venv .venv
46
+ source .venv/bin/activate # On Windows: .venv\Scripts\activate
47
+ pip install fastmcp websockets
48
+ ```
49
+
50
+ ### 4. Configure Claude Desktop
51
+
52
+ Add to your Claude Desktop configuration file:
53
+
54
+ **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
55
+ **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
56
+
57
+ ```json
58
+ {
59
+ "mcpServers": {
60
+ "musescore": {
61
+ "command": "/path/to/your/project/.venv/bin/python",
62
+ "args": [
63
+ "/path/to/your/project/server.py"
64
+ ]
65
+ }
66
+ }
67
+ }
68
+ ```
69
+
70
+ **Note**: Update the paths to match your actual project location.
71
+
72
+ ## Running the System
73
+
74
+ ### Order of Operations (Important!)
75
+
76
+ 1. **Start MuseScore first** with a score open
77
+ 2. **Run the MuseScore plugin**: Go to **Plugins → MuseScore API Server**
78
+ - You should see console output: `"Starting MuseScore API Server on port 8765"`
79
+ 3. **Then start the Python MCP server** or restart Claude Desktop
80
+
81
+ [insert screenshot of different functionality, harmonisation, melodywriting, as zoomed in GIFs]
82
+
83
+ ### Development and Testing
84
+
85
+ For development, use the MCP development tools:
86
+
87
+ ```bash
88
+ # Install MCP dev tools
89
+ pip install mcp
90
+
91
+ # Test your server
92
+ mcp dev server.py
93
+
94
+ # Check connection status
95
+ mcp dev server.py --inspect
96
+ ```
97
+
98
+ ### Viewing Console Output
99
+
100
+ To see MuseScore plugin console output, run MuseScore from terminal:
101
+
102
+ **macOS**:
103
+ ```bash
104
+ /Applications/MuseScore\ 4.app/Contents/MacOS/mscore
105
+ ```
106
+
107
+ **Windows**:
108
+ ```cmd
109
+ cd "C:\Program Files\MuseScore 4\bin"
110
+ MuseScore.exe
111
+ ```
112
+
113
+ **Linux**:
114
+ ```bash
115
+ musescore4
116
+ ```
117
+
118
+ ## Features
119
+
120
+ This MCP server provides comprehensive MuseScore control:
121
+
122
+ ### **Navigation & Cursor Control**
123
+ - `get_cursor_info()` - Get current cursor position and selection info
124
+ - `go_to_measure(measure)` - Navigate to specific measure
125
+ - `go_to_beginning_of_score()` / `go_to_final_measure()` - Navigate to start/end
126
+ - `next_element()` / `prev_element()` - Move cursor element by element
127
+ - `next_staff()` / `prev_staff()` - Move between staves
128
+ - `select_current_measure()` - Select entire current measure
129
+
130
+ ### **Note & Rest Creation**
131
+ - `add_note(pitch, duration, advance_cursor_after_action)` - Add notes with MIDI pitch
132
+ - `add_rest(duration, advance_cursor_after_action)` - Add rests
133
+ - `add_tuplet(duration, ratio, advance_cursor_after_action)` - Add tuplets (triplets, etc.)
134
+
135
+ ### **Measure Management**
136
+ - `insert_measure()` - Insert measure at current position
137
+ - `append_measure(count)` - Add measures to end of score
138
+ - `delete_selection(measure)` - Delete current selection or specific measure
139
+
140
+ ### **Lyrics & Text**
141
+ - `add_lyrics_to_current_note(text)` - Add lyrics to current note
142
+ - `add_lyrics(lyrics_list)` - Batch add lyrics to multiple notes
143
+ - `set_title(title)` - Set score title
144
+
145
+ ### **Score Information**
146
+ - `get_score()` - Get complete score analysis and structure
147
+ - `ping_musescore()` - Test connection to MuseScore
148
+ - `connect_to_musescore()` - Establish WebSocket connection
149
+
150
+ ### **Utilities**
151
+ - `undo()` - Undo last action
152
+ - `set_time_signature(numerator, denominator)` - Change time signature
153
+ - `processSequence(sequence)` - Execute multiple commands in batch
154
+
155
+ ## Sample Music
156
+
157
+ Check out the `/examples` folder for sample MuseScore files demonstrating various musical styles:
158
+
159
+ - **Asian Instrumental** - Traditional Asian-inspired instrumental piece
160
+ - **String Quartet** - Classical string quartet arrangement
161
+
162
+ Each example includes:
163
+ - `.mscz` - MuseScore file (editable)
164
+ - `.pdf` - Sheet music
165
+ - `.mp3` - Audio preview
166
+
167
+ ## Usage Examples
168
+
169
+ ### Creating a Simple Melody
170
+
171
+ ```python
172
+ # Set up the score
173
+ await set_title("My First Song")
174
+ await go_to_beginning_of_score()
175
+
176
+ # Add notes (MIDI pitch: 60=C, 62=D, 64=E, etc.)
177
+ await add_note(60, {"numerator": 1, "denominator": 4}, True) # Quarter note C
178
+ await add_note(64, {"numerator": 1, "denominator": 4}, True) # Quarter note E
179
+ await add_note(67, {"numerator": 1, "denominator": 4}, True) # Quarter note G
180
+ await add_note(72, {"numerator": 1, "denominator": 2}, True) # Half note C
181
+
182
+ # Add lyrics
183
+ await go_to_beginning_of_score()
184
+ await add_lyrics_to_current_note("Do")
185
+ await next_element()
186
+ await add_lyrics_to_current_note("Mi")
187
+ await next_element()
188
+ await add_lyrics_to_current_note("Sol")
189
+ await next_element()
190
+ await add_lyrics_to_current_note("Do")
191
+ ```
192
+
193
+ ### Batch Operations
194
+
195
+ ```python
196
+ # Add multiple lyrics at once
197
+ await add_lyrics(["Twin-", "kle", "twin-", "kle", "lit-", "tle", "star"])
198
+
199
+ # Use sequence processing for complex operations
200
+ sequence = [
201
+ {"action": "goToBeginningOfScore", "params": {}},
202
+ {"action": "addNote", "params": {"pitch": 60, "duration": {"numerator": 1, "denominator": 4}, "advanceCursorAfterAction": True}},
203
+ {"action": "addNote", "params": {"pitch": 64, "duration": {"numerator": 1, "denominator": 4}, "advanceCursorAfterAction": True}},
204
+ {"action": "addRest", "params": {"duration": {"numerator": 1, "denominator": 4}, "advanceCursorAfterAction": True}}
205
+ ]
206
+ await processSequence(sequence)
207
+ ```
208
+
209
+ ## Troubleshooting
210
+
211
+ ### Connection Issues
212
+ - **"Not connected to MuseScore"**:
213
+ - Ensure MuseScore is running with a score open
214
+ - Run the MuseScore plugin (Plugins → MuseScore API Server)
215
+ - Check that port 8765 isn't blocked by firewall
216
+
217
+ ### Plugin Issues
218
+ - **Plugin not appearing**: Check the `.qml` file is in the correct plugins directory
219
+ - **Plugin won't enable**: Restart MuseScore after placing the plugin file
220
+ - **No console output**: Run MuseScore from terminal to see debug messages
221
+
222
+ ### Python Server Issues
223
+ - **"No server object found"**: The server object must be named `mcp`, `server`, or `app` at module level
224
+ - **WebSocket errors**: Make sure MuseScore plugin is running before starting Python server
225
+ - **Connection timeout**: The MuseScore plugin must be actively running, not just enabled
226
+
227
+ ### API Limitations
228
+ - **Lyrics**: Only first verse supported in MuseScore 3.x plugin API
229
+ - **Title setting**: Uses multiple fallback methods due to frame access limitations
230
+ - **Selection persistence**: Some operations may affect current selection
231
+
232
+ ## File Structure
233
+
234
+ ```
235
+ mcp-agents-demo/
236
+ ├── .venv/
237
+ ├── server.py # Python MCP server entry point
238
+ ├── musescore-mcp-websocket.qml # MuseScore plugin
239
+ ├── requirements.txt
240
+ ├── README.md
241
+ └── src/ # Source code modules
242
+ ├── __init__.py
243
+ ├── client/ # WebSocket client functionality
244
+ │ ├── __init__.py
245
+ │ └── websocket_client.py
246
+ ├── tools/ # MCP tool implementations
247
+ │ ├── __init__.py
248
+ │ ├── connection.py # Connection management tools
249
+ │ ├── navigation.py # Score navigation tools
250
+ │ ├── notes_measures.py # Note and measure manipulation
251
+ │ ├── sequences.py # Batch operation tools
252
+ │ ├── staff_instruments.py # Staff and instrument tools
253
+ │ └── time_tempo.py # Timing and tempo tools
254
+ └── types/ # Type definitions
255
+ ├── __init__.py
256
+ └── action_types.py # WebSocket action type definitions
257
+ ```
258
+
259
+ ## Requirements
260
+
261
+ Create a `requirements.txt` file with:
262
+
263
+ ```
264
+ fastmcp
265
+ websockets
266
+ ```
267
+
268
+ ## MIDI Pitch Reference
269
+
270
+ Common MIDI pitch values for reference:
271
+ - **Middle C**: 60
272
+ - **C Major Scale**: 60, 62, 64, 65, 67, 69, 71, 72
273
+ - **Chromatic**: C=60, C#=61, D=62, D#=63, E=64, F=65, F#=66, G=67, G#=68, A=69, A#=70, B=71
274
+
275
+ ## Duration Reference
276
+
277
+ Duration format: `{"numerator": int, "denominator": int}`
278
+ - **Whole note**: `{"numerator": 1, "denominator": 1}`
279
+ - **Half note**: `{"numerator": 1, "denominator": 2}`
280
+ - **Quarter note**: `{"numerator": 1, "denominator": 4}`
281
+ - **Eighth note**: `{"numerator": 1, "denominator": 8}`
282
+ - **Dotted quarter**: `{"numerator": 3, "denominator": 8}`
@@ -0,0 +1,18 @@
1
+ server.py,sha256=9dIrI669T4c8VCAF4iP7h_5v_o-NX1H2AG9K6mBt3uo,1130
2
+ src/__init__.py,sha256=-jib-i1HuFU7ElFWBWohFghvPOE7AveBCFjeie62eGE,110
3
+ src/client/__init__.py,sha256=MNyf4k-4MdWouGG0P1ConsLs6UnNvOGjXCEWmOTWsD8,129
4
+ src/client/websocket_client.py,sha256=3FzE9dbrDMWb1LZ3-uG1FFWPAyIYXk6KMfvHRtC1vSI,1944
5
+ src/tools/__init__.py,sha256=bJbvf3pA5bFypW0MIC-QeaS822n5LrO8af0eAY2KYoI,547
6
+ src/tools/connection.py,sha256=0HrzZJDb0er9t-roIP5za_8C7uv4Gxg0RBYAom8-J4g,703
7
+ src/tools/navigation.py,sha256=R4GI4Oal97y6EZbFa6NEwzsTdgDevAIFCfhjEEWjIVE,1667
8
+ src/tools/notes_measures.py,sha256=HzjBcX-JaWAr1FwJI61WPVWTtsHDXn6GE84FuerPwIM,3676
9
+ src/tools/sequences.py,sha256=QEeCVc6D9gqSzTN8DnoEg0yhjC5pIhXAI1muNnBwQp4,491
10
+ src/tools/staff_instruments.py,sha256=3dv6ab2lDt7jYYBANZ2YTMhp4K7YzKEnHf7T7UucXyM,1303
11
+ src/tools/time_tempo.py,sha256=mHpyits3jIfjNJAMypcNHkR39-8-F1hHT7cqT41eloQ,678
12
+ src/types/__init__.py,sha256=zu6KFs8c4Ex6dw_Re1xok3EjUmVF7KPoY4LVKEStFxY,679
13
+ src/types/action_types.py,sha256=t-24oOqlQan3UA0-VLT1D3Gy_rpZaguumh8JTYWbnEo,277
14
+ iflow_mcp_ghchen99_mcp_musescore-0.1.0.dist-info/METADATA,sha256=9Ns82Sb6z7lnQJcAOlNKyeztdd-XtH8tOhbGmWCg5Ac,9196
15
+ iflow_mcp_ghchen99_mcp_musescore-0.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
16
+ iflow_mcp_ghchen99_mcp_musescore-0.1.0.dist-info/entry_points.txt,sha256=jbMwCNQtTWK1AFnvDssHEwY1a99FqLTWxSxvYCWMAzo,46
17
+ iflow_mcp_ghchen99_mcp_musescore-0.1.0.dist-info/licenses/LICENSE,sha256=mc8W3ZAoSXw3qToh-9CfxDqZ8XRyykACU5kBlqdlBuI,1068
18
+ iflow_mcp_ghchen99_mcp_musescore-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.28.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ musescore-mcp = server:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 George Chen
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
server.py ADDED
@@ -0,0 +1,44 @@
1
+ from mcp.server.fastmcp import FastMCP
2
+ import sys
3
+ import logging
4
+
5
+ # Import modular components
6
+ from src.client import MuseScoreClient
7
+ from src.tools import (
8
+ setup_connection_tools,
9
+ setup_navigation_tools,
10
+ setup_notes_measures_tools,
11
+ setup_staff_instruments_tools,
12
+ setup_time_tempo_tools,
13
+ setup_sequence_tools
14
+ )
15
+
16
+ # Set up logging
17
+ logging.basicConfig(
18
+ level=logging.INFO,
19
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
20
+ handlers=[logging.StreamHandler(sys.stderr)]
21
+ )
22
+ logger = logging.getLogger("MuseScoreMCP")
23
+
24
+ # Create the MCP app and client
25
+ mcp = FastMCP("MuseScore Assistant")
26
+ client = MuseScoreClient()
27
+
28
+ # Setup all tool categories
29
+ setup_connection_tools(mcp, client)
30
+ setup_navigation_tools(mcp, client)
31
+ setup_notes_measures_tools(mcp, client)
32
+ setup_staff_instruments_tools(mcp, client)
33
+ setup_time_tempo_tools(mcp, client)
34
+ setup_sequence_tools(mcp, client)
35
+
36
+ # Main entry point
37
+ def main():
38
+ sys.stderr.write("MuseScore MCP Server starting up...\n")
39
+ sys.stderr.flush()
40
+ logger.info("MuseScore MCP Server is running")
41
+ mcp.run()
42
+
43
+ if __name__ == "__main__":
44
+ main()
src/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """MuseScore MCP Server - A Model Context Protocol server for MuseScore integration."""
2
+
3
+ __version__ = "1.0.0"
src/client/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ """WebSocket client for MuseScore communication."""
2
+
3
+ from .websocket_client import MuseScoreClient
4
+
5
+ __all__ = ["MuseScoreClient"]
@@ -0,0 +1,55 @@
1
+ """WebSocket client for communicating with MuseScore."""
2
+
3
+ import websockets
4
+ import json
5
+ import logging
6
+ from typing import Dict, Any, Optional
7
+
8
+ logger = logging.getLogger("MuseScoreMCP.Client")
9
+
10
+
11
+ class MuseScoreClient:
12
+ """Client to communicate with MuseScore WebSocket API."""
13
+
14
+ def __init__(self, host: str = "localhost", port: int = 8765):
15
+ self.uri = f"ws://{host}:{port}"
16
+ self.websocket = None
17
+
18
+ async def connect(self):
19
+ """Connect to the MuseScore WebSocket API."""
20
+ try:
21
+ self.websocket = await websockets.connect(self.uri)
22
+ logger.info(f"Connected to MuseScore API at {self.uri}")
23
+ return True
24
+ except Exception as e:
25
+ logger.error(f"Failed to connect to MuseScore API: {str(e)}")
26
+ return False
27
+
28
+ async def send_command(self, action: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
29
+ """Send a command to MuseScore and wait for response."""
30
+ if not self.websocket:
31
+ connected = await self.connect()
32
+ if not connected:
33
+ return {"error": "Not connected to MuseScore"}
34
+
35
+ if params is None:
36
+ params = {}
37
+
38
+ command = {"action": action, "params": params}
39
+
40
+ try:
41
+ logger.info(f"Sending command: {json.dumps(command)}")
42
+ await self.websocket.send(json.dumps(command))
43
+ response = await self.websocket.recv()
44
+ logger.info(f"Received response: {response}")
45
+ return json.loads(response)
46
+ except Exception as e:
47
+ logger.error(f"Error sending command: {str(e)}")
48
+ return {"error": str(e)}
49
+
50
+ async def close(self):
51
+ """Close the WebSocket connection."""
52
+ if self.websocket:
53
+ await self.websocket.close()
54
+ self.websocket = None
55
+ logger.info("Disconnected from MuseScore API")
src/tools/__init__.py ADDED
@@ -0,0 +1,17 @@
1
+ """MCP tools for MuseScore operations."""
2
+
3
+ from .connection import setup_connection_tools
4
+ from .navigation import setup_navigation_tools
5
+ from .notes_measures import setup_notes_measures_tools
6
+ from .staff_instruments import setup_staff_instruments_tools
7
+ from .time_tempo import setup_time_tempo_tools
8
+ from .sequences import setup_sequence_tools
9
+
10
+ __all__ = [
11
+ "setup_connection_tools",
12
+ "setup_navigation_tools",
13
+ "setup_notes_measures_tools",
14
+ "setup_staff_instruments_tools",
15
+ "setup_time_tempo_tools",
16
+ "setup_sequence_tools"
17
+ ]
@@ -0,0 +1,23 @@
1
+ """Connection and utility tools for MuseScore MCP."""
2
+
3
+ from ..client import MuseScoreClient
4
+
5
+
6
+ def setup_connection_tools(mcp, client: MuseScoreClient):
7
+ """Setup connection and utility tools."""
8
+
9
+ @mcp.tool()
10
+ async def connect_to_musescore():
11
+ """Connect to the MuseScore WebSocket API."""
12
+ result = await client.connect()
13
+ return {"success": result}
14
+
15
+ @mcp.tool()
16
+ async def ping_musescore():
17
+ """Ping the MuseScore WebSocket API to check connection."""
18
+ return await client.send_command("ping")
19
+
20
+ @mcp.tool()
21
+ async def get_score():
22
+ """Get information about the current score."""
23
+ return await client.send_command("getScore")
@@ -0,0 +1,52 @@
1
+ """Cursor and navigation tools for MuseScore MCP."""
2
+
3
+ from ..client import MuseScoreClient
4
+
5
+
6
+ def setup_navigation_tools(mcp, client: MuseScoreClient):
7
+ """Setup cursor and navigation tools."""
8
+
9
+ @mcp.tool()
10
+ async def get_cursor_info():
11
+ """Get information about the current cursor position."""
12
+ return await client.send_command("getCursorInfo")
13
+
14
+ @mcp.tool()
15
+ async def go_to_measure(measure: int):
16
+ """Navigate to a specific measure."""
17
+ return await client.send_command("goToMeasure", {"measure": measure})
18
+
19
+ @mcp.tool()
20
+ async def go_to_final_measure():
21
+ """Navigate to the final measure of the score."""
22
+ return await client.send_command("goToFinalMeasure")
23
+
24
+ @mcp.tool()
25
+ async def go_to_beginning_of_score():
26
+ """Navigate to the beginning of the score."""
27
+ return await client.send_command("goToBeginningOfScore")
28
+
29
+ @mcp.tool()
30
+ async def next_element():
31
+ """Move cursor to the next element."""
32
+ return await client.send_command("nextElement")
33
+
34
+ @mcp.tool()
35
+ async def prev_element():
36
+ """Move cursor to the previous element."""
37
+ return await client.send_command("prevElement")
38
+
39
+ @mcp.tool()
40
+ async def next_staff():
41
+ """Move cursor to the next staff."""
42
+ return await client.send_command("nextStaff")
43
+
44
+ @mcp.tool()
45
+ async def prev_staff():
46
+ """Move cursor to the previous staff."""
47
+ return await client.send_command("prevStaff")
48
+
49
+ @mcp.tool()
50
+ async def select_current_measure():
51
+ """Select the current measure."""
52
+ return await client.send_command("selectCurrentMeasure")
@@ -0,0 +1,87 @@
1
+ """Notes and measures tools for MuseScore MCP."""
2
+
3
+ from typing import List, Optional
4
+ from ..client import MuseScoreClient
5
+
6
+
7
+ def setup_notes_measures_tools(mcp, client: MuseScoreClient):
8
+ """Setup notes and measures tools."""
9
+
10
+ @mcp.tool()
11
+ async def add_note(pitch: int = 64, duration: dict = {"numerator": 1, "denominator": 4}, advance_cursor_after_action: bool = True):
12
+ """Add a note at the current cursor position with the specified pitch and duration.
13
+
14
+ Args:
15
+ pitch: MIDI pitch value (0-127, where 60 is middle C)
16
+ duration: Duration as {"numerator": int, "denominator": int} (e.g., {"numerator": 1, "denominator": 4} for quarter note)
17
+ advance_cursor_after_action: Whether to move cursor to next position after adding note
18
+ """
19
+ return await client.send_command("addNote", {
20
+ "pitch": pitch,
21
+ "duration": duration,
22
+ "advanceCursorAfterAction": advance_cursor_after_action
23
+ })
24
+
25
+ @mcp.tool()
26
+ async def add_rest(duration: dict = {"numerator": 1, "denominator": 4}, advance_cursor_after_action: bool = True):
27
+ """Add a rest at the current cursor position.
28
+
29
+ Args:
30
+ duration: Duration as {"numerator": int, "denominator": int} (e.g., {"numerator": 1, "denominator": 4} for quarter rest)
31
+ advance_cursor_after_action: Whether to move cursor to next position after adding rest
32
+ """
33
+ return await client.send_command("addRest", {
34
+ "duration": duration,
35
+ "advanceCursorAfterAction": advance_cursor_after_action
36
+ })
37
+
38
+ @mcp.tool()
39
+ async def add_tuplet(duration: dict = {"numerator": 1, "denominator": 4}, ratio: dict = {"numerator": 3, "denominator": 2}, advance_cursor_after_action: bool = True):
40
+ """Add a tuplet at the current cursor position.
41
+
42
+ Args:
43
+ duration: Base duration as {"numerator": int, "denominator": int}
44
+ ratio: Tuplet ratio as {"numerator": int, "denominator": int} (e.g., {"numerator": 3, "denominator": 2} for triplet)
45
+ advance_cursor_after_action: Whether to move cursor to next position after adding tuplet
46
+ """
47
+ return await client.send_command("addTuplet", {
48
+ "duration": duration,
49
+ "ratio": ratio,
50
+ "advanceCursorAfterAction": advance_cursor_after_action
51
+ })
52
+
53
+ @mcp.tool()
54
+ async def add_lyrics(lyrics: List[str], verse: int = 0):
55
+ """Add lyrics to consecutive notes starting from the current cursor position.
56
+
57
+ Args:
58
+ lyrics: List of lyric syllables to add (e.g., ["Hel", "lo", "world"])
59
+ verse: Verse number (0-based, default is 0 for first verse)
60
+ """
61
+ return await client.send_command("addLyrics", {
62
+ "lyrics": lyrics,
63
+ "verse": verse
64
+ })
65
+
66
+ @mcp.tool()
67
+ async def insert_measure():
68
+ """Insert a measure at the current position."""
69
+ return await client.send_command("insertMeasure")
70
+
71
+ @mcp.tool()
72
+ async def append_measure(count: int = 1):
73
+ """Append measures to the end of the score."""
74
+ return await client.send_command("appendMeasure", {"count": count})
75
+
76
+ @mcp.tool()
77
+ async def delete_selection(measure: Optional[int] = None):
78
+ """Delete the current selection or specified measure."""
79
+ params = {}
80
+ if measure is not None:
81
+ params["measure"] = measure
82
+ return await client.send_command("deleteSelection", params)
83
+
84
+ @mcp.tool()
85
+ async def undo():
86
+ """Undo the last action."""
87
+ return await client.send_command("undo")
src/tools/sequences.py ADDED
@@ -0,0 +1,16 @@
1
+ """Sequence processing tools for MuseScore MCP."""
2
+
3
+ from ..client import MuseScoreClient
4
+
5
+
6
+ def setup_sequence_tools(mcp, client: MuseScoreClient):
7
+ """Setup sequence processing tools."""
8
+
9
+ @mcp.tool()
10
+ async def processSequence(sequence: list):
11
+ """Process a sequence of commands.
12
+
13
+ Args:
14
+ sequence: A list of action dictionaries with 'action' and 'params' keys
15
+ """
16
+ return await client.send_command("processSequence", {"sequence": sequence})
@@ -0,0 +1,44 @@
1
+ """Staff and instrument tools for MuseScore MCP."""
2
+
3
+ from ..client import MuseScoreClient
4
+
5
+
6
+ def setup_staff_instruments_tools(mcp, client: MuseScoreClient):
7
+ """Setup staff and instrument tools."""
8
+
9
+ @mcp.tool()
10
+ async def add_instrument(instrument_id: str):
11
+ """Add a new staff/instrument to the score.
12
+
13
+ Args:
14
+ instrument_id: ID of the instrument to add
15
+ """
16
+ return await client.send_command("addInstrument", {
17
+ "instrumentId": instrument_id
18
+ })
19
+
20
+ @mcp.tool()
21
+ async def set_staff_mute(staff: int, mute: bool):
22
+ """Mute or unmute a staff.
23
+
24
+ Args:
25
+ staff: Staff number (0-based)
26
+ mute: True to mute, False to unmute
27
+ """
28
+ return await client.send_command("setStaffMute", {
29
+ "staff": staff,
30
+ "mute": mute
31
+ })
32
+
33
+ @mcp.tool()
34
+ async def set_instrument_sound(staff: int, instrument_id: str):
35
+ """Change the sound of an instrument on a staff.
36
+
37
+ Args:
38
+ staff: Staff number (0-based)
39
+ instrument_id: ID of the new instrument sound
40
+ """
41
+ return await client.send_command("setInstrumentSound", {
42
+ "staff": staff,
43
+ "instrumentId": instrument_id
44
+ })
@@ -0,0 +1,20 @@
1
+ """Time signature and tempo tools for MuseScore MCP."""
2
+
3
+ from ..client import MuseScoreClient
4
+
5
+
6
+ def setup_time_tempo_tools(mcp, client: MuseScoreClient):
7
+ """Setup time signature and tempo tools."""
8
+
9
+ @mcp.tool()
10
+ async def set_time_signature(numerator: int = 4, denominator: int = 4):
11
+ """Set the time signature.
12
+
13
+ Args:
14
+ numerator: Top number of time signature (beats per measure)
15
+ denominator: Bottom number of time signature (note value that gets the beat)
16
+ """
17
+ return await client.send_command("setTimeSignature", {
18
+ "numerator": numerator,
19
+ "denominator": denominator
20
+ })
src/types/__init__.py ADDED
@@ -0,0 +1,29 @@
1
+ """Type definitions for MuseScore MCP."""
2
+
3
+ from .action_types import *
4
+
5
+ __all__ = [
6
+ "ActionSequence",
7
+ "getScoreAction",
8
+ "addNoteAction",
9
+ "addRestAction",
10
+ "addTupletAction",
11
+ "addLyricsAction",
12
+ "addInstrumentAction",
13
+ "setStaffMuteAction",
14
+ "setInstrumentSoundAction",
15
+ "appendMeasureAction",
16
+ "deleteSelectionAction",
17
+ "getCursorInfoAction",
18
+ "goToMeasureAction",
19
+ "nextElementAction",
20
+ "prevElementAction",
21
+ "selectCurrentMeasureAction",
22
+ "insertMeasureAction",
23
+ "goToFinalMeasureAction",
24
+ "goToBeginningOfScoreAction",
25
+ "setTimeSignatureAction",
26
+ "undoAction",
27
+ "nextStaffAction",
28
+ "prevStaffAction"
29
+ ]
@@ -0,0 +1,12 @@
1
+ """TypedDict definitions for MuseScore MCP action sequences."""
2
+
3
+ from typing import Dict, Any, List, TypedDict
4
+
5
+
6
+ class ActionSequenceItem(TypedDict):
7
+ """A single action in a sequence."""
8
+ action: str
9
+ params: Dict[str, Any]
10
+
11
+
12
+ ActionSequence = List[ActionSequenceItem]