c64bridge 1.0.2 → 1.0.4

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.
@@ -223,7 +223,7 @@
223
223
  "string",
224
224
  "null"
225
225
  ],
226
- "description": "Playback target format.",
226
+ "description": "Playback target format. PRG works on all backends; SID playback requires C64U/U64 or U2 (SID export with dryRun works everywhere).",
227
227
  "enum": [
228
228
  "prg",
229
229
  "sid"
@@ -249,7 +249,8 @@
249
249
  "minLength": 1,
250
250
  "enum": [
251
251
  "vice",
252
- "c64u"
252
+ "c64u",
253
+ "u2"
253
254
  ]
254
255
  }
255
256
  },
@@ -363,7 +364,7 @@
363
364
  "required": [
364
365
  "op"
365
366
  ],
366
- "description": "Sound operations available via the c64_sound tool.\n\nOperations:\n- capture_samples: Capture raw stereo PCM samples from the C64 Ultimate audio UDP stream. Required inputs: none. Optional inputs: count, encoding.\n- set_volume: Set the SID master volume register at $D418 (0-15). Required inputs: volume.\n- reset: Soft or hard reset of SID registers to clear glitches. Required inputs: none. Optional inputs: hard.\n- note_on: Trigger a SID voice with configurable waveform, ADSR, and pitch. Required inputs: none. Optional inputs: voice, note, frequencyHz, system, waveform, pulseWidth, attack, decay, sustain, release.\n- note_off: Release a SID voice by clearing its gate bit. Required inputs: voice.\n- silence_all: Silence all SID voices with optional audio verification. Required inputs: none. Optional inputs: verify.\n- play_sid_file: Play a SID file stored on the Ultimate filesystem. Required inputs: path. Optional inputs: songnr.\n- play_mod_file: Play a MOD tracker module via the Ultimate SID player. Required inputs: path.\n- generate: Generate a lightweight SID arpeggio playback sequence. Required inputs: none. Optional inputs: root, pattern, steps, tempoMs, waveform, preset.\n- compile_play: Compile SIDWAVE or CPG source and optionally play it immediately. Required inputs: none. Optional inputs: sidwave, cpg, format, output, dryRun.\n- play_preset: Compile and play a built-in SID preset such as Für Elise by Beethoven. Required inputs: none. Optional inputs: preset, platforms, verify, analysisDurationSeconds, waitBeforeCaptureMs, restoreActiveBackend.\n- pipeline: Compile a SIDWAVE score, play it, and analyze the recording. Required inputs: none. Optional inputs: sidwave, cpg, output, waitBeforeCaptureMs, analysisDurationSeconds, expectedSidwave, verifySilenceBefore, verifySilenceAfter, silenceDurationSeconds, silenceRmsThreshold, postSilenceWaitMs, silenceWaitMs.\n- analyze: Automatically analyze SID playback when verification is requested. Required inputs: request. Optional inputs: durationSeconds, expectedSidwave.\n- record_analyze: Record audio for a fixed duration and return SID analysis metrics. Required inputs: durationSeconds. Optional inputs: expectedSidwave.",
367
+ "description": "Sound operations available via the c64_sound tool.\n\nOperations:\n- capture_samples: Capture raw stereo PCM samples from the C64 Ultimate audio UDP stream. Required inputs: none. Optional inputs: count, encoding.\n- set_volume: Set the SID master volume register at $D418 (0-15). Required inputs: volume.\n- reset: Soft or hard reset of SID registers to clear glitches. Required inputs: none. Optional inputs: hard.\n- note_on: Trigger a SID voice with configurable waveform, ADSR, and pitch. Required inputs: none. Optional inputs: voice, note, frequencyHz, system, waveform, pulseWidth, attack, decay, sustain, release.\n- note_off: Release a SID voice by clearing its gate bit. Required inputs: voice.\n- silence_all: Silence all SID voices with optional audio verification. Required inputs: none. Optional inputs: verify.\n- play_sid_file: Play a SID file stored on the Ultimate filesystem. Required inputs: path. Optional inputs: songnr.\n- play_mod_file: Play a MOD tracker module via the Ultimate SID player. Required inputs: path.\n- generate: Generate a lightweight SID arpeggio playback sequence. Required inputs: none. Optional inputs: root, pattern, steps, tempoMs, waveform, preset.\n- compile_play: Compile SIDWAVE or CPG source and optionally play it immediately. Required inputs: none. Optional inputs: sidwave, cpg, format, output, dryRun.\n- play_preset: Compile and play a built-in SID preset such as Für Elise by Beethoven. Required inputs: none. Optional inputs: preset, platforms, verify, analysisDurationSeconds, waitBeforeCaptureMs, restoreActiveBackend.\n- pipeline: Compile a SIDWAVE score, play it, and analyze the recording. Required inputs: none. Optional inputs: sidwave, cpg, output, waitBeforeCaptureMs, analysisDurationSeconds, expectedSidwave, verifySilenceBefore, verifySilenceAfter, silenceDurationSeconds, silenceRmsThreshold, postSilenceWaitMs, silenceWaitMs.\n- analyze: Automatically analyze SID playback when verification is requested. Required inputs: request. Optional inputs: durationSeconds, expectedSidwave.\n- record_analyze: Record audio for a fixed duration and return SID analysis metrics. Uses the Ultimate audio stream on c64u and the host microphone on other backends. Required inputs: durationSeconds. Optional inputs: expectedSidwave.",
367
368
  "additionalProperties": false,
368
369
  "x-c64bridge-operations": [
369
370
  {
@@ -815,7 +816,7 @@
815
816
  "string",
816
817
  "null"
817
818
  ],
818
- "description": "Playback target format.",
819
+ "description": "Playback target format. PRG works on all backends; SID playback requires C64U/U64 or U2 (SID export with dryRun works everywhere).",
819
820
  "enum": [
820
821
  "prg",
821
822
  "sid"
@@ -876,7 +877,8 @@
876
877
  "minLength": 1,
877
878
  "enum": [
878
879
  "vice",
879
- "c64u"
880
+ "c64u",
881
+ "u2"
880
882
  ]
881
883
  }
882
884
  },
@@ -885,7 +887,7 @@
885
887
  "boolean",
886
888
  "null"
887
889
  ],
888
- "description": "Capture and analyze audio on supported backends after playback starts.",
890
+ "description": "Capture and analyze audio after playback starts: native streaming on C64U/U64, host audio input on U2/VICE (PortAudio required).",
889
891
  "default": true
890
892
  },
891
893
  "analysisDurationSeconds": {
@@ -969,7 +971,7 @@
969
971
  "string",
970
972
  "null"
971
973
  ],
972
- "description": "Playback artifact format.",
974
+ "description": "Playback artifact format. PRG works on all backends; SID attachment playback requires C64U/U64 or U2.",
973
975
  "enum": [
974
976
  "prg",
975
977
  "sid"
@@ -1119,7 +1121,7 @@
1119
1121
  },
1120
1122
  {
1121
1123
  "op": "record_analyze",
1122
- "description": "Record audio for a fixed duration and return SID analysis metrics.",
1124
+ "description": "Record audio for a fixed duration and return SID analysis metrics. Uses the Ultimate audio stream on c64u and the host microphone on other backends.",
1123
1125
  "required": [
1124
1126
  "durationSeconds"
1125
1127
  ],
@@ -1128,7 +1130,7 @@
1128
1130
  ],
1129
1131
  "inputSchema": {
1130
1132
  "type": "object",
1131
- "description": "Record audio for a fixed duration and return SID analysis metrics.",
1133
+ "description": "Record audio for a fixed duration and return SID analysis metrics. Uses the Ultimate audio stream on c64u and the host microphone on other backends.",
1132
1134
  "properties": {
1133
1135
  "durationSeconds": {
1134
1136
  "type": "number",
@@ -91,7 +91,7 @@
91
91
  "required": [
92
92
  "op"
93
93
  ],
94
- "description": "System operations available via the c64_system tool.\n\nOperations:\n- pause: Pause the machine until resumed. Required inputs: none.\n- resume: Resume CPU execution after a pause. Required inputs: none.\n- reset: Issue a soft reset without cutting power. Required inputs: none.\n- reboot: Trigger a firmware reboot to recover from faults. Required inputs: none.\n- poweroff: Request a controlled shutdown via the Ultimate firmware. Required inputs: none.\n- power_cycle: Return the active backend to a fresh state. C64U/U64 Tool Menu verification requires machine:menu_screen (a C64U firmware version that provides it, or U64 3.15+); U2-family reboots through REST. Required inputs: none.\n- menu: Toggle the Ultimate menu button for navigation. Required inputs: none.\n- read_menu_screen: Read the active Ultimate menu's raw character and colour matrix through machine:menu_screen (a C64U firmware version that provides it, or U64/U2 3.15+). Required inputs: none.\n- start_task: Start a named background task that runs on an interval. Required inputs: name, operation. Optional inputs: arguments, intervalMs, maxIterations.\n- stop_task: Stop a specific background task and clear its timer. Required inputs: name.\n- stop_all_tasks: Stop every running background task and persist state. Required inputs: none.\n- list_tasks: List known background tasks with status metadata. Required inputs: none.\n- performance_report: Summarize diagnostics spans and tool latencies from the current or latest MCP session. Required inputs: none. Optional inputs: scope, includeTimeline, maxEntries.",
94
+ "description": "System operations available via the c64_system tool.\n\nOperations:\n- pause: Pause the machine until resumed. Required inputs: none.\n- resume: Resume CPU execution after a pause. Required inputs: none.\n- reset: Issue a soft reset without cutting power. Required inputs: none.\n- reboot: Trigger a firmware reboot to recover from faults. Required inputs: none.\n- poweroff: Shut down C64U/U64 or quit VICE. U2-family hardware cannot power off. Required inputs: none.\n- power_cycle: Return the active backend to a fresh state. C64U/U64 Tool Menu verification requires machine:menu_screen (a C64U firmware version that provides it, or U64 3.15+); U2-family reboots through REST. Required inputs: none.\n- menu: Toggle the Ultimate menu button for navigation. Required inputs: none.\n- read_menu_screen: Read the active Ultimate menu's raw character and colour matrix through machine:menu_screen (a C64U firmware version that provides it, or U64/U2 3.15+). Required inputs: none.\n- start_task: Start a named background task that runs on an interval. Required inputs: name, operation. Optional inputs: arguments, intervalMs, maxIterations.\n- stop_task: Stop a specific background task and clear its timer. Required inputs: name.\n- stop_all_tasks: Stop every running background task and persist state. Required inputs: none.\n- list_tasks: List known background tasks with status metadata. Required inputs: none.\n- performance_report: Summarize diagnostics spans and tool latencies from the current or latest MCP session. Required inputs: none. Optional inputs: scope, includeTimeline, maxEntries.",
95
95
  "additionalProperties": false,
96
96
  "x-c64bridge-operations": [
97
97
  {
@@ -172,12 +172,12 @@
172
172
  },
173
173
  {
174
174
  "op": "poweroff",
175
- "description": "Request a controlled shutdown via the Ultimate firmware.",
175
+ "description": "Shut down C64U/U64 or quit VICE. U2-family hardware cannot power off.",
176
176
  "required": [],
177
177
  "optional": [],
178
178
  "inputSchema": {
179
179
  "type": "object",
180
- "description": "Request a controlled shutdown via the Ultimate firmware.",
180
+ "description": "Shut down C64U/U64 or quit VICE. U2-family hardware cannot power off.",
181
181
  "properties": {
182
182
  "op": {
183
183
  "const": "poweroff"
package/mcp/server.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "c64bridge",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "Stdio-based Model Context Protocol (MCP) server that bridges Commodore 64 Ultimate hardware with AI agents.",
5
5
  "transports": [
6
6
  "stdio"
package/mcp/tools.json CHANGED
@@ -191,8 +191,7 @@
191
191
  "boolean",
192
192
  "null"
193
193
  ],
194
- "description": "Capture debugreg state",
195
- "default": true
194
+ "description": "Capture Ultimate debug registers (C64U/U64 only). Defaults to true on C64U/U64 and false on U2/VICE."
196
195
  },
197
196
  "platforms": {
198
197
  "type": [
@@ -205,13 +204,10 @@
205
204
  "minLength": 1,
206
205
  "enum": [
207
206
  "vice",
208
- "c64u"
207
+ "c64u",
208
+ "u2"
209
209
  ]
210
- },
211
- "default": [
212
- "vice",
213
- "c64u"
214
- ]
210
+ }
215
211
  },
216
212
  "messageTemplate": {
217
213
  "type": [
@@ -227,7 +223,7 @@
227
223
  "boolean",
228
224
  "null"
229
225
  ],
230
- "description": "Capture a framebuffer screenshot and save it as a PNG per backend.",
226
+ "description": "Capture a framebuffer screenshot on C64U/U64 or VICE. U2 skips screenshots by default; explicitly requesting one on U2 fails before execution.",
231
227
  "default": true
232
228
  },
233
229
  "restoreActiveBackend": {
@@ -629,8 +625,7 @@
629
625
  "boolean",
630
626
  "null"
631
627
  ],
632
- "description": "Capture debugreg state",
633
- "default": true
628
+ "description": "Capture Ultimate debug registers (C64U/U64 only). Defaults to true on C64U/U64 and false on U2/VICE."
634
629
  },
635
630
  "op": {
636
631
  "const": "bundle_run"
@@ -673,13 +668,10 @@
673
668
  "minLength": 1,
674
669
  "enum": [
675
670
  "vice",
676
- "c64u"
671
+ "c64u",
672
+ "u2"
677
673
  ]
678
- },
679
- "default": [
680
- "vice",
681
- "c64u"
682
- ]
674
+ }
683
675
  },
684
676
  "messageTemplate": {
685
677
  "type": [
@@ -703,7 +695,7 @@
703
695
  "boolean",
704
696
  "null"
705
697
  ],
706
- "description": "Capture a framebuffer screenshot and save it as a PNG per backend.",
698
+ "description": "Capture a framebuffer screenshot on C64U/U64 or VICE. U2 skips screenshots by default; explicitly requesting one on U2 fails before execution.",
707
699
  "default": true
708
700
  },
709
701
  "outputPath": {
@@ -815,10 +807,6 @@
815
807
  "run_crt": [
816
808
  "c64u",
817
809
  "u2"
818
- ],
819
- "bundle_run": [
820
- "c64u",
821
- "u2"
822
810
  ]
823
811
  }
824
812
  }
@@ -1829,7 +1817,7 @@
1829
1817
  "string",
1830
1818
  "null"
1831
1819
  ],
1832
- "description": "Playback target format.",
1820
+ "description": "Playback target format. PRG works on all backends; SID playback requires C64U/U64 or U2 (SID export with dryRun works everywhere).",
1833
1821
  "enum": [
1834
1822
  "prg",
1835
1823
  "sid"
@@ -1855,7 +1843,8 @@
1855
1843
  "minLength": 1,
1856
1844
  "enum": [
1857
1845
  "vice",
1858
- "c64u"
1846
+ "c64u",
1847
+ "u2"
1859
1848
  ]
1860
1849
  }
1861
1850
  },
@@ -1969,7 +1958,7 @@
1969
1958
  "required": [
1970
1959
  "op"
1971
1960
  ],
1972
- "description": "Sound operations available via the c64_sound tool.\n\nOperations:\n- capture_samples: Capture raw stereo PCM samples from the C64 Ultimate audio UDP stream. Required inputs: none. Optional inputs: count, encoding.\n- set_volume: Set the SID master volume register at $D418 (0-15). Required inputs: volume.\n- reset: Soft or hard reset of SID registers to clear glitches. Required inputs: none. Optional inputs: hard.\n- note_on: Trigger a SID voice with configurable waveform, ADSR, and pitch. Required inputs: none. Optional inputs: voice, note, frequencyHz, system, waveform, pulseWidth, attack, decay, sustain, release.\n- note_off: Release a SID voice by clearing its gate bit. Required inputs: voice.\n- silence_all: Silence all SID voices with optional audio verification. Required inputs: none. Optional inputs: verify.\n- play_sid_file: Play a SID file stored on the Ultimate filesystem. Required inputs: path. Optional inputs: songnr.\n- play_mod_file: Play a MOD tracker module via the Ultimate SID player. Required inputs: path.\n- generate: Generate a lightweight SID arpeggio playback sequence. Required inputs: none. Optional inputs: root, pattern, steps, tempoMs, waveform, preset.\n- compile_play: Compile SIDWAVE or CPG source and optionally play it immediately. Required inputs: none. Optional inputs: sidwave, cpg, format, output, dryRun.\n- play_preset: Compile and play a built-in SID preset such as Für Elise by Beethoven. Required inputs: none. Optional inputs: preset, platforms, verify, analysisDurationSeconds, waitBeforeCaptureMs, restoreActiveBackend.\n- pipeline: Compile a SIDWAVE score, play it, and analyze the recording. Required inputs: none. Optional inputs: sidwave, cpg, output, waitBeforeCaptureMs, analysisDurationSeconds, expectedSidwave, verifySilenceBefore, verifySilenceAfter, silenceDurationSeconds, silenceRmsThreshold, postSilenceWaitMs, silenceWaitMs.\n- analyze: Automatically analyze SID playback when verification is requested. Required inputs: request. Optional inputs: durationSeconds, expectedSidwave.\n- record_analyze: Record audio for a fixed duration and return SID analysis metrics. Required inputs: durationSeconds. Optional inputs: expectedSidwave.",
1961
+ "description": "Sound operations available via the c64_sound tool.\n\nOperations:\n- capture_samples: Capture raw stereo PCM samples from the C64 Ultimate audio UDP stream. Required inputs: none. Optional inputs: count, encoding.\n- set_volume: Set the SID master volume register at $D418 (0-15). Required inputs: volume.\n- reset: Soft or hard reset of SID registers to clear glitches. Required inputs: none. Optional inputs: hard.\n- note_on: Trigger a SID voice with configurable waveform, ADSR, and pitch. Required inputs: none. Optional inputs: voice, note, frequencyHz, system, waveform, pulseWidth, attack, decay, sustain, release.\n- note_off: Release a SID voice by clearing its gate bit. Required inputs: voice.\n- silence_all: Silence all SID voices with optional audio verification. Required inputs: none. Optional inputs: verify.\n- play_sid_file: Play a SID file stored on the Ultimate filesystem. Required inputs: path. Optional inputs: songnr.\n- play_mod_file: Play a MOD tracker module via the Ultimate SID player. Required inputs: path.\n- generate: Generate a lightweight SID arpeggio playback sequence. Required inputs: none. Optional inputs: root, pattern, steps, tempoMs, waveform, preset.\n- compile_play: Compile SIDWAVE or CPG source and optionally play it immediately. Required inputs: none. Optional inputs: sidwave, cpg, format, output, dryRun.\n- play_preset: Compile and play a built-in SID preset such as Für Elise by Beethoven. Required inputs: none. Optional inputs: preset, platforms, verify, analysisDurationSeconds, waitBeforeCaptureMs, restoreActiveBackend.\n- pipeline: Compile a SIDWAVE score, play it, and analyze the recording. Required inputs: none. Optional inputs: sidwave, cpg, output, waitBeforeCaptureMs, analysisDurationSeconds, expectedSidwave, verifySilenceBefore, verifySilenceAfter, silenceDurationSeconds, silenceRmsThreshold, postSilenceWaitMs, silenceWaitMs.\n- analyze: Automatically analyze SID playback when verification is requested. Required inputs: request. Optional inputs: durationSeconds, expectedSidwave.\n- record_analyze: Record audio for a fixed duration and return SID analysis metrics. Uses the Ultimate audio stream on c64u and the host microphone on other backends. Required inputs: durationSeconds. Optional inputs: expectedSidwave.",
1973
1962
  "additionalProperties": false,
1974
1963
  "x-c64bridge-operations": [
1975
1964
  {
@@ -2421,7 +2410,7 @@
2421
2410
  "string",
2422
2411
  "null"
2423
2412
  ],
2424
- "description": "Playback target format.",
2413
+ "description": "Playback target format. PRG works on all backends; SID playback requires C64U/U64 or U2 (SID export with dryRun works everywhere).",
2425
2414
  "enum": [
2426
2415
  "prg",
2427
2416
  "sid"
@@ -2482,7 +2471,8 @@
2482
2471
  "minLength": 1,
2483
2472
  "enum": [
2484
2473
  "vice",
2485
- "c64u"
2474
+ "c64u",
2475
+ "u2"
2486
2476
  ]
2487
2477
  }
2488
2478
  },
@@ -2491,7 +2481,7 @@
2491
2481
  "boolean",
2492
2482
  "null"
2493
2483
  ],
2494
- "description": "Capture and analyze audio on supported backends after playback starts.",
2484
+ "description": "Capture and analyze audio after playback starts: native streaming on C64U/U64, host audio input on U2/VICE (PortAudio required).",
2495
2485
  "default": true
2496
2486
  },
2497
2487
  "analysisDurationSeconds": {
@@ -2575,7 +2565,7 @@
2575
2565
  "string",
2576
2566
  "null"
2577
2567
  ],
2578
- "description": "Playback artifact format.",
2568
+ "description": "Playback artifact format. PRG works on all backends; SID attachment playback requires C64U/U64 or U2.",
2579
2569
  "enum": [
2580
2570
  "prg",
2581
2571
  "sid"
@@ -2725,7 +2715,7 @@
2725
2715
  },
2726
2716
  {
2727
2717
  "op": "record_analyze",
2728
- "description": "Record audio for a fixed duration and return SID analysis metrics.",
2718
+ "description": "Record audio for a fixed duration and return SID analysis metrics. Uses the Ultimate audio stream on c64u and the host microphone on other backends.",
2729
2719
  "required": [
2730
2720
  "durationSeconds"
2731
2721
  ],
@@ -2734,7 +2724,7 @@
2734
2724
  ],
2735
2725
  "inputSchema": {
2736
2726
  "type": "object",
2737
- "description": "Record audio for a fixed duration and return SID analysis metrics.",
2727
+ "description": "Record audio for a fixed duration and return SID analysis metrics. Uses the Ultimate audio stream on c64u and the host microphone on other backends.",
2738
2728
  "properties": {
2739
2729
  "durationSeconds": {
2740
2730
  "type": "number",
@@ -2838,13 +2828,18 @@
2838
2828
  ],
2839
2829
  "pipeline": [
2840
2830
  "c64u",
2841
- "u2"
2831
+ "u2",
2832
+ "vice"
2842
2833
  ],
2843
2834
  "analyze": [
2844
- "c64u"
2835
+ "c64u",
2836
+ "u2",
2837
+ "vice"
2845
2838
  ],
2846
2839
  "record_analyze": [
2847
- "c64u"
2840
+ "c64u",
2841
+ "u2",
2842
+ "vice"
2848
2843
  ]
2849
2844
  }
2850
2845
  }
@@ -2946,7 +2941,7 @@
2946
2941
  "required": [
2947
2942
  "op"
2948
2943
  ],
2949
- "description": "System operations available via the c64_system tool.\n\nOperations:\n- pause: Pause the machine until resumed. Required inputs: none.\n- resume: Resume CPU execution after a pause. Required inputs: none.\n- reset: Issue a soft reset without cutting power. Required inputs: none.\n- reboot: Trigger a firmware reboot to recover from faults. Required inputs: none.\n- poweroff: Request a controlled shutdown via the Ultimate firmware. Required inputs: none.\n- power_cycle: Return the active backend to a fresh state. C64U/U64 Tool Menu verification requires machine:menu_screen (a C64U firmware version that provides it, or U64 3.15+); U2-family reboots through REST. Required inputs: none.\n- menu: Toggle the Ultimate menu button for navigation. Required inputs: none.\n- read_menu_screen: Read the active Ultimate menu's raw character and colour matrix through machine:menu_screen (a C64U firmware version that provides it, or U64/U2 3.15+). Required inputs: none.\n- start_task: Start a named background task that runs on an interval. Required inputs: name, operation. Optional inputs: arguments, intervalMs, maxIterations.\n- stop_task: Stop a specific background task and clear its timer. Required inputs: name.\n- stop_all_tasks: Stop every running background task and persist state. Required inputs: none.\n- list_tasks: List known background tasks with status metadata. Required inputs: none.\n- performance_report: Summarize diagnostics spans and tool latencies from the current or latest MCP session. Required inputs: none. Optional inputs: scope, includeTimeline, maxEntries.",
2944
+ "description": "System operations available via the c64_system tool.\n\nOperations:\n- pause: Pause the machine until resumed. Required inputs: none.\n- resume: Resume CPU execution after a pause. Required inputs: none.\n- reset: Issue a soft reset without cutting power. Required inputs: none.\n- reboot: Trigger a firmware reboot to recover from faults. Required inputs: none.\n- poweroff: Shut down C64U/U64 or quit VICE. U2-family hardware cannot power off. Required inputs: none.\n- power_cycle: Return the active backend to a fresh state. C64U/U64 Tool Menu verification requires machine:menu_screen (a C64U firmware version that provides it, or U64 3.15+); U2-family reboots through REST. Required inputs: none.\n- menu: Toggle the Ultimate menu button for navigation. Required inputs: none.\n- read_menu_screen: Read the active Ultimate menu's raw character and colour matrix through machine:menu_screen (a C64U firmware version that provides it, or U64/U2 3.15+). Required inputs: none.\n- start_task: Start a named background task that runs on an interval. Required inputs: name, operation. Optional inputs: arguments, intervalMs, maxIterations.\n- stop_task: Stop a specific background task and clear its timer. Required inputs: name.\n- stop_all_tasks: Stop every running background task and persist state. Required inputs: none.\n- list_tasks: List known background tasks with status metadata. Required inputs: none.\n- performance_report: Summarize diagnostics spans and tool latencies from the current or latest MCP session. Required inputs: none. Optional inputs: scope, includeTimeline, maxEntries.",
2950
2945
  "additionalProperties": false,
2951
2946
  "x-c64bridge-operations": [
2952
2947
  {
@@ -3027,12 +3022,12 @@
3027
3022
  },
3028
3023
  {
3029
3024
  "op": "poweroff",
3030
- "description": "Request a controlled shutdown via the Ultimate firmware.",
3025
+ "description": "Shut down C64U/U64 or quit VICE. U2-family hardware cannot power off.",
3031
3026
  "required": [],
3032
3027
  "optional": [],
3033
3028
  "inputSchema": {
3034
3029
  "type": "object",
3035
- "description": "Request a controlled shutdown via the Ultimate firmware.",
3030
+ "description": "Shut down C64U/U64 or quit VICE. U2-family hardware cannot power off.",
3036
3031
  "properties": {
3037
3032
  "op": {
3038
3033
  "const": "poweroff"
@@ -3338,6 +3333,10 @@
3338
3333
  "vice"
3339
3334
  ],
3340
3335
  "operationPlatforms": {
3336
+ "poweroff": [
3337
+ "c64u",
3338
+ "vice"
3339
+ ],
3341
3340
  "pause": [
3342
3341
  "c64u",
3343
3342
  "u2"
@@ -4183,7 +4182,13 @@
4183
4182
  "c64u",
4184
4183
  "u2",
4185
4184
  "vice"
4186
- ]
4185
+ ],
4186
+ "operationPlatforms": {
4187
+ "capture_frame": [
4188
+ "c64u",
4189
+ "vice"
4190
+ ]
4191
+ }
4187
4192
  }
4188
4193
  },
4189
4194
  {
@@ -5732,7 +5737,7 @@
5732
5737
  "boolean",
5733
5738
  "null"
5734
5739
  ],
5735
- "description": "When true, save the restored configuration to flash immediately.",
5740
+ "description": "When true, save the restored configuration to flash immediately (C64U/U64 and U2 only).",
5736
5741
  "default": false
5737
5742
  },
5738
5743
  "root": {
@@ -6131,7 +6136,7 @@
6131
6136
  "boolean",
6132
6137
  "null"
6133
6138
  ],
6134
- "description": "When true, save the restored configuration to flash immediately.",
6139
+ "description": "When true, save the restored configuration to flash immediately (C64U/U64 and U2 only).",
6135
6140
  "default": false
6136
6141
  },
6137
6142
  "op": {
@@ -6954,8 +6959,16 @@
6954
6959
  "Summarise output file paths or sample counts so users can inspect artifacts quickly."
6955
6960
  ],
6956
6961
  "platforms": [
6957
- "c64u"
6958
- ]
6962
+ "c64u",
6963
+ "u2",
6964
+ "vice"
6965
+ ],
6966
+ "operationPlatforms": {
6967
+ "fs_stats": [
6968
+ "c64u",
6969
+ "u2"
6970
+ ]
6971
+ }
6959
6972
  }
6960
6973
  },
6961
6974
  {
package/mcp.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "c64bridge",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "Stdio-based Model Context Protocol (MCP) server that bridges Commodore 64 Ultimate hardware with AI agents.",
5
5
  "entry": "dist/index.js",
6
6
  "type": "stdio",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "c64bridge",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "mcpName": "io.github.chrisgleissner/c64bridge",
5
5
  "description": "C64 Bridge MCP server for Commodore 64 Ultimate hardware and VICE emulator control",
6
6
  "repository": {
@@ -68,10 +68,10 @@
68
68
  "node": ">=24"
69
69
  },
70
70
  "dependencies": {
71
- "@modelcontextprotocol/sdk": "^1.30.0",
72
- "axios": "^1.19.0",
71
+ "@modelcontextprotocol/sdk": "^1.32.1",
72
+ "axios": "^1.20.0",
73
73
  "date-fns": "^4.4.0",
74
- "fastify": "^5.11.2",
74
+ "fastify": "^5.12.5",
75
75
  "jimp": "^1.6.1",
76
76
  "meyda": "^5.6.3",
77
77
  "octokit": "^5.0.5",
@@ -44,6 +44,9 @@ function createDefaultConfigs() {
44
44
  Video: {
45
45
  Mode: "PAL",
46
46
  },
47
+ "U64 Specific Settings": {
48
+ "System Mode": "PAL",
49
+ },
47
50
  };
48
51
  }
49
52
 
@@ -127,6 +130,14 @@ function createInitialState() {
127
130
  },
128
131
  lastStreamAction: null,
129
132
  streamActionLog: [],
133
+ // Number of upcoming stream:start requests answered like firmware with an empty ARP entry.
134
+ streamHostResolveFailures: 0,
135
+ // When set, the audio emitter stops after this many packets to imitate UDP packet loss.
136
+ audioPacketLimit: null,
137
+ audioSignal: null,
138
+ streamStartResponseDelayMs: 0,
139
+ // Unlike the one-shot failure counter, this gate stays closed until a test's ping stub primes it.
140
+ streamNeighborReady: true,
130
141
  };
131
142
  }
132
143
 
@@ -329,7 +340,10 @@ export async function startMockC64Server(options = {}) {
329
340
  }
330
341
 
331
342
  const sendAudioPacket = () => {
332
- const packet = buildMockAudioPacket(runtime.sequence, runtime.sampleIndex);
343
+ if (state.audioPacketLimit !== null && state.streams.audio.packetsSent >= state.audioPacketLimit) {
344
+ return;
345
+ }
346
+ const packet = buildMockAudioPacket(runtime.sequence, runtime.sampleIndex, state.audioSignal);
333
347
  runtime.sequence = (runtime.sequence + 1) & 0xffff;
334
348
  runtime.sampleIndex += 192;
335
349
  socket.send(packet, port, host);
@@ -713,7 +727,7 @@ export async function startMockC64Server(options = {}) {
713
727
 
714
728
  if (url.startsWith("/v1/configs/")) {
715
729
  const routeUrl = new URL(url, "http://mock.local");
716
- const segments = routeUrl.pathname.split("/").filter(Boolean).slice(2); // remove v1 + configs
730
+ const segments = routeUrl.pathname.split("/").filter(Boolean).slice(2).map((segment) => decodeURIComponent(segment)); // remove v1 + configs
717
731
 
718
732
  if (segments.length === 1) {
719
733
  const [category] = segments;
@@ -730,7 +744,8 @@ export async function startMockC64Server(options = {}) {
730
744
  if (method === "GET") {
731
745
  const categoryData = state.configs[category] ?? {};
732
746
  const value = categoryData[item];
733
- sendJson(res, { value });
747
+ // Firmware nests the item as { "<category>": { "<item>": { current } } }; `value` is kept for older callers.
748
+ sendJson(res, { value, [category]: { [item]: { current: value } } });
734
749
  return;
735
750
  }
736
751
 
@@ -762,10 +777,20 @@ export async function startMockC64Server(options = {}) {
762
777
  if (action === "start" && method === "PUT") {
763
778
  const body = await readJson(req);
764
779
  const target = routeUrl.searchParams.get("ip") ?? routeUrl.searchParams.get("target") ?? body?.ip ?? body?.target ?? null;
780
+ const neighborReady = typeof state.streamNeighborReady === "function" ? state.streamNeighborReady(target) : state.streamNeighborReady;
781
+ if (!neighborReady || state.streamHostResolveFailures > 0) {
782
+ if (state.streamHostResolveFailures > 0) state.streamHostResolveFailures -= 1;
783
+ state.streamActionLog.push({ action: "start-rejected", stream, target });
784
+ sendJson(res, { errors: ["Network Host Resolve Error"] }, 404);
785
+ return;
786
+ }
765
787
  state.streams[stream] = { active: true, target, packetsSent: 0 };
766
788
  state.lastStreamAction = { action: "start", stream, target };
767
789
  state.streamActionLog.push({ action: "start", stream, target });
768
790
  startStreamEmitter(stream, target);
791
+ if (state.streamStartResponseDelayMs > 0) {
792
+ await new Promise((resolve) => setTimeout(resolve, state.streamStartResponseDelayMs));
793
+ }
769
794
  sendJson(res, { result: "started", stream, target });
770
795
  return;
771
796
  }
@@ -994,15 +1019,17 @@ function buildMockVideoFramePackets(frameNumber, sequenceStart) {
994
1019
  return packets;
995
1020
  }
996
1021
 
997
- function buildMockAudioPacket(sequence, sampleIndex) {
1022
+ function buildMockAudioPacket(sequence, sampleIndex, signal = null) {
998
1023
  const header = Buffer.alloc(2);
999
1024
  header.writeUInt16LE(sequence & 0xffff, 0);
1000
1025
 
1001
1026
  const payload = Buffer.alloc(768);
1002
1027
  for (let index = 0; index < 192; index += 1) {
1003
1028
  const phase = ((sampleIndex + index) % 256) - 128;
1004
- const left = phase * 128;
1005
- const right = -left;
1029
+ // Both channels carry the same sawtooth plus a different constant offset, like the real stream.
1030
+ const wave = signal ? Math.sin(2 * Math.PI * signal.frequency * (sampleIndex + index) / signal.sampleRate) : phase * 128;
1031
+ const left = signal ? Math.round(wave * signal.amplitude + signal.leftOffset) : wave + 8000;
1032
+ const right = signal ? Math.round(wave * signal.amplitude * signal.rightGain + signal.rightOffset) : wave + 5000;
1006
1033
  payload.writeInt16LE(left, index * 4);
1007
1034
  payload.writeInt16LE(right, index * 4 + 2);
1008
1035
  }
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env node
2
+ import assert from 'node:assert/strict';
3
+ import { mkdtempSync, writeFileSync, rmSync } from 'node:fs';
4
+ import os from 'node:os';
5
+ import path from 'node:path';
6
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
7
+ import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
8
+ import { CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js';
9
+ import { validateReleaseMetadata } from './validate-release.mjs';
10
+ import { startMockC64Server } from './mockC64Server.mjs';
11
+
12
+ const packageDir = path.resolve(process.argv[2]);
13
+ const version = process.argv[3];
14
+ validateReleaseMetadata(packageDir, version, { packed: true });
15
+ const temp = mkdtempSync(path.join(os.tmpdir(), 'c64bridge-release-smoke-'));
16
+ const mock = await startMockC64Server();
17
+ const config = path.join(temp, 'config.json');
18
+ writeFileSync(config, JSON.stringify({ c64u: { baseUrl: mock.baseUrl } }));
19
+ mock.state.audioSignal = { frequency: 440, sampleRate: 47982.8869047619, amplitude: 4000, leftOffset: 26900, rightOffset: 20000, rightGain: -1 };
20
+ const client = new Client({ name: 'c64bridge-release-validation', version: '1.0.0' }, { capabilities: {} });
21
+ const transport = new StdioClientTransport({
22
+ command: process.execPath,
23
+ args: [path.join(packageDir, 'scripts/cli.js')],
24
+ // Launch from a foreign directory, as an installed npm consumer would.
25
+ cwd: temp,
26
+ env: { ...process.env, C64BRIDGE_CONFIG: config, C64_MODE: 'c64u', C64BRIDGE_DISABLE_DIAGNOSTICS: '1' },
27
+ stderr: 'pipe',
28
+ });
29
+ let stderr = '';
30
+ transport.stderr?.on('data', (chunk) => { stderr = (stderr + chunk.toString()).slice(-16384); });
31
+ try {
32
+ await client.connect(transport);
33
+ assert.equal(client.getServerVersion().version, version, 'MCP runtime version differs from artifact');
34
+ const tools = await client.listTools();
35
+ for (const name of ['c64_program', 'c64_memory', 'c64_sound', 'c64_graphics']) {
36
+ assert.ok(tools.tools.some((tool) => tool.name === name), `Missing packaged tool ${name}`);
37
+ }
38
+ const call = async (name, args) => {
39
+ const result = await client.request({ method: 'tools/call', params: { name, arguments: args } }, CallToolResultSchema);
40
+ assert.notEqual(result.isError, true, JSON.stringify(result));
41
+ return result;
42
+ };
43
+ mock.state.streamHostResolveFailures = 1;
44
+ const samples = await call('c64_sound', { op: 'capture_samples', count: 256 });
45
+ assert.equal(samples.metadata.samplePairs, 256);
46
+ await call('c64_graphics', { op: 'capture_frame' });
47
+ const recorded = await call('c64_sound', { op: 'record_analyze', durationSeconds: 0.5 });
48
+ const analysis = JSON.parse(recorded.content.find((entry) => entry.type === 'text').text).analysis;
49
+ assert.equal(analysis.source, 'ultimate-stream');
50
+ assert.ok(analysis.global_metrics.average_rms > 0.08, 'Stereo audio cancelled to silence');
51
+ assert.equal(analysis.voices[0].detected_notes.find((note) => note.note)?.note, 'A4');
52
+ assert.equal(mock.state.streams.audio.active, false);
53
+ assert.equal(mock.state.streams.video.active, false);
54
+ console.log(`Packed c64bridge@${version}: CLI, MCP version/discovery, video, ARP retry, and stereo audio verified.`);
55
+ } catch (error) {
56
+ console.error(stderr);
57
+ throw error;
58
+ } finally {
59
+ await client.close();
60
+ await mock.close();
61
+ rmSync(temp, { recursive: true, force: true });
62
+ }
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env node
2
+ import assert from 'node:assert/strict';
3
+ import { readFileSync } from 'node:fs';
4
+ import path from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+
7
+ export function validateReleaseMetadata(root, version, { packed = false } = {}) {
8
+ assert.match(version, /^\d+\.\d+\.\d+$/, 'Expected a stable release version');
9
+ const read = (file) => JSON.parse(readFileSync(path.join(root, file), 'utf8'));
10
+ const pkg = read('package.json');
11
+ assert.equal(pkg.name, 'c64bridge', 'Wrong npm package');
12
+ assert.equal(pkg.version, version, 'package.json version does not match release tag');
13
+ assert.equal(pkg.private, undefined, 'Release package must be public');
14
+ assert.equal(pkg.main, 'dist/index.js', 'Wrong compiled entry point');
15
+ assert.equal(pkg.bin.c64bridge, 'scripts/cli.js', 'Wrong CLI entry point');
16
+ assert.equal(read('mcp.json').version, version, 'mcp.json version mismatch');
17
+ assert.equal(read('mcp/server.json').version, version, 'Generated MCP metadata version mismatch');
18
+ const server = read('server.json');
19
+ assert.equal(server.name, pkg.mcpName, 'MCP Registry server name mismatch');
20
+ assert.equal(server.version, version, 'MCP Registry version mismatch');
21
+ assert.equal(server.packages[0].identifier, pkg.name, 'MCP Registry npm name mismatch');
22
+ assert.equal(server.packages[0].version, version, 'MCP Registry npm version mismatch');
23
+ if (!packed) {
24
+ const lock = read('package-lock.json');
25
+ assert.equal(lock.name, pkg.name, 'Lockfile package name mismatch');
26
+ assert.equal(lock.version, version, 'Lockfile version mismatch');
27
+ assert.equal(lock.packages[''].version, version, 'Lockfile root version mismatch');
28
+ for (const section of ['dependencies', 'devDependencies', 'optionalDependencies']) {
29
+ assert.deepEqual(lock.packages[''][section], pkg[section], `Lockfile ${section} differs from package.json`);
30
+ }
31
+ const changelog = readFileSync(path.join(root, 'CHANGELOG.md'), 'utf8');
32
+ assert.match(changelog, new RegExp(`^## ${version.replaceAll('.', '\\.')} - \\d{4}-\\d{2}-\\d{2}$`, 'm'), 'Missing release changelog entry');
33
+ }
34
+ return pkg;
35
+ }
36
+
37
+ if (process.argv[1] && fileURLToPath(import.meta.url) === path.resolve(process.argv[1])) {
38
+ validateReleaseMetadata(process.cwd(), process.argv[2]);
39
+ console.log(`Release metadata agrees on c64bridge@${process.argv[2]}.`);
40
+ }