c64-debug-mcp 1.0.6 → 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -14,6 +14,101 @@ Debug Commodore 64 programs through conversation - AI-powered control of the VIC
14
14
  - 📸 **Display Capture**: Capture screen state and text content
15
15
  - ⌨️ **Input Control**: Send keyboard and joystick input
16
16
  - 📝 **Program Loading**: Load PRG files and manage execution
17
+ - 🔢 **Flexible Formats**: Use C64 notation ($D000, $FF, %11111111) or standard formats (0xD000, 255, 0b11111111)
18
+
19
+ ## Address Formats
20
+
21
+ The MCP server accepts C64 addresses in multiple formats for natural interaction:
22
+
23
+ - **C64 style**: `$D000` (dollar sign prefix - classic 6502 assembler notation)
24
+ - **C style**: `0xD000` (0x prefix - standard programming hex notation)
25
+ - **Decimal**: `53248` (traditional decimal format)
26
+
27
+ **Note**: Bare hex without prefix (e.g., `D000`) is NOT supported to avoid ambiguity with 4-digit decimals.
28
+
29
+ ### Range Support
30
+
31
+ Tools that read memory or set breakpoints support both **address+length** and **start+end** formats:
32
+
33
+ **Address + Length format** (original):
34
+ ```javascript
35
+ memory_read(address="$D000", length=256) // Read 256 bytes starting at $D000
36
+ breakpoint_set(kind="exec", address="$1000", length=1) // Breakpoint at $1000
37
+ ```
38
+
39
+ **Start + End format** (more intuitive for ranges):
40
+ ```javascript
41
+ memory_read(start="$D000", end="$D0FF") // Read from $D000 to $D0FF inclusive
42
+ memory_read(start=198, end=199) // Read addresses 198-199 (2 bytes)
43
+ breakpoint_set(kind="exec", start="$1000", end="$1000") // Breakpoint at $1000
44
+ ```
45
+
46
+ Both formats work with all address representations (decimal, $, 0x).
47
+
48
+ **Common C64 Addresses**:
49
+ - `$D000` - SID chip registers (sound)
50
+ - `$D020` - Border color register
51
+ - `$D021` - Background color register
52
+ - `$0400` - Default screen memory
53
+ - `$0800` - Common program start
54
+ - `$C000` - BASIC ROM start
55
+
56
+ ## Address Ranges
57
+
58
+ For operations that work with memory ranges (like `memory_read` and `breakpoint_set`), you can specify ranges in two ways:
59
+
60
+ **Option 1: Address + Length**
61
+ ```javascript
62
+ memory_read(address="$0400", length=256) // Read 256 bytes from $0400
63
+ breakpoint_set(kind="exec", address="$1000", length=16) // Break on $1000-$100F
64
+ ```
65
+
66
+ **Option 2: Start + End (inclusive)**
67
+ ```javascript
68
+ memory_read(start="$0400", end="$04FF") // Read from $0400 to $04FF (256 bytes)
69
+ breakpoint_set(kind="exec", start="$1000", end="$100F") // Break on $1000-$100F
70
+ ```
71
+
72
+ Both formats work identically and support all address formats (decimal, hex).
73
+
74
+ ## Byte Value Formats
75
+
76
+ The MCP server accepts byte values (0-255) in multiple formats for natural C64-style input:
77
+
78
+ - **C64 hex**: `$FF` (dollar sign prefix - classic 6502 notation)
79
+ - **C hex**: `0xFF` (0x prefix - standard programming notation)
80
+ - **C64 binary**: `%11111111` (percent prefix - classic 6502 bit notation)
81
+ - **C binary**: `0b11111111` (0b prefix - standard programming notation)
82
+ - **Decimal**: `255` (traditional decimal format)
83
+
84
+ **Note**: Bare hex/binary without prefix (e.g., `FF`, `11111111`) is NOT supported to avoid ambiguity.
85
+
86
+ **Mixed formats in arrays**:
87
+ ```json
88
+ [255, "$FF", "0xFF", "%11111111", "0b11111111"]
89
+ ```
90
+
91
+ **Common C64 Byte Values**:
92
+ - `$00`-`$0F` - Color values (0-15)
93
+ - `$20` - PETSCII space character
94
+ - `$41` - PETSCII 'A' character
95
+ - `%00011011` - VIC-II D011 control register (text mode, 25 rows, screen on)
96
+ - `%11111111` - All bits set (enable all sprites, etc.)
97
+
98
+ **Use Cases**:
99
+ ```javascript
100
+ // Set border to light blue
101
+ memory_write(address="$D020", data=["$0E"])
102
+
103
+ // Enable all 8 sprites
104
+ memory_write(address="$D015", data=["%11111111"])
105
+
106
+ // Write " AB" to screen (PETSCII)
107
+ memory_write(address="$0400", data=["$20", "$41", "$42"])
108
+
109
+ // Set VIC-II control register with bit pattern
110
+ memory_write(address="$D011", data=["%00011011"])
111
+ ```
17
112
 
18
113
  ## Requirements
19
114
 
@@ -24,7 +119,7 @@ Debug Commodore 64 programs through conversation - AI-powered control of the VIC
24
119
  ## Installation
25
120
 
26
121
  ```bash
27
- claude mcp add c64debug -- npx -y c64-debug-mcp
122
+ claude mcp add c64-dev-tools -- npx -y c64-debug-mcp@latest
28
123
  ```
29
124
 
30
125
  Or add manually to your MCP client config:
@@ -32,9 +127,9 @@ Or add manually to your MCP client config:
32
127
  ```json
33
128
  {
34
129
  "mcpServers": {
35
- "c64debug": {
130
+ "c64-dev-tools": {
36
131
  "command": "npx",
37
- "args": ["-y", "c64-debug-mcp"]
132
+ "args": ["-y", "c64-debug-mcp@latest"]
38
133
  }
39
134
  }
40
135
  }
package/dist/http.cjs CHANGED
@@ -152,9 +152,211 @@ var import_zod3 = require("zod");
152
152
  // src/schemas.ts
153
153
  var import_zod2 = require("zod");
154
154
  var warningSchema = warningItemSchema;
155
- var address16Schema = import_zod2.z.number().int().min(0).max(65535);
156
- var byteValueSchema = import_zod2.z.number().int().min(0).max(255).describe("Single raw byte value");
157
- var byteArraySchema = import_zod2.z.array(byteValueSchema).describe("Raw bytes as a JSON array");
155
+ function parseAddress16(input) {
156
+ if (input === void 0) {
157
+ throw new import_zod2.z.ZodError([
158
+ {
159
+ code: "custom",
160
+ message: "Address is required",
161
+ path: []
162
+ }
163
+ ]);
164
+ }
165
+ if (typeof input === "number") {
166
+ if (!Number.isInteger(input) || input < 0 || input > 65535) {
167
+ throw new import_zod2.z.ZodError([
168
+ {
169
+ code: "custom",
170
+ message: `Address must be an integer between 0 and 65535 (0xFFFF), got ${input}`,
171
+ path: []
172
+ }
173
+ ]);
174
+ }
175
+ return input;
176
+ }
177
+ if (typeof input !== "string") {
178
+ throw new import_zod2.z.ZodError([
179
+ {
180
+ code: "custom",
181
+ message: `Address must be a number or string, got ${typeof input}`,
182
+ path: []
183
+ }
184
+ ]);
185
+ }
186
+ const trimmed = input.trim();
187
+ let hexString;
188
+ let format;
189
+ if (trimmed.startsWith("$")) {
190
+ hexString = trimmed.slice(1);
191
+ format = "C64 hex ($)";
192
+ } else if (trimmed.toLowerCase().startsWith("0x")) {
193
+ hexString = trimmed.slice(2);
194
+ format = "C hex (0x)";
195
+ } else {
196
+ throw new import_zod2.z.ZodError([
197
+ {
198
+ code: "custom",
199
+ message: `Invalid address format: "${input}". Expected formats: decimal number (53248), hex with $ ($D000), or hex with 0x (0xD000). Bare hex not supported to avoid ambiguity.`,
200
+ path: []
201
+ }
202
+ ]);
203
+ }
204
+ if (!/^[0-9A-Fa-f]{1,4}$/.test(hexString)) {
205
+ throw new import_zod2.z.ZodError([
206
+ {
207
+ code: "custom",
208
+ message: `Invalid ${format} address: "${input}". Hex portion must be 1-4 hex digits (0-9, A-F)`,
209
+ path: []
210
+ }
211
+ ]);
212
+ }
213
+ const parsed = parseInt(hexString, 16);
214
+ if (isNaN(parsed) || parsed < 0 || parsed > 65535) {
215
+ throw new import_zod2.z.ZodError([
216
+ {
217
+ code: "custom",
218
+ message: `Address out of range: "${input}" (${parsed}). Must be 0x0000-0xFFFF (0-65535)`,
219
+ path: []
220
+ }
221
+ ]);
222
+ }
223
+ return parsed;
224
+ }
225
+ var address16Schema = import_zod2.z.preprocess(parseAddress16, import_zod2.z.number().int().min(0).max(65535)).describe("16-bit C64 address: decimal (53248) or hex string with prefix ($D000, 0xD000)");
226
+ function parseByte(input) {
227
+ if (input === void 0) {
228
+ throw new import_zod2.z.ZodError([
229
+ {
230
+ code: "custom",
231
+ message: "Byte value is required",
232
+ path: []
233
+ }
234
+ ]);
235
+ }
236
+ if (typeof input === "number") {
237
+ if (!Number.isInteger(input) || input < 0 || input > 255) {
238
+ throw new import_zod2.z.ZodError([
239
+ {
240
+ code: "custom",
241
+ message: `Byte value must be an integer between 0 and 255 (0xFF), got ${input}`,
242
+ path: []
243
+ }
244
+ ]);
245
+ }
246
+ return input;
247
+ }
248
+ if (typeof input !== "string") {
249
+ throw new import_zod2.z.ZodError([
250
+ {
251
+ code: "custom",
252
+ message: `Byte value must be a number or string, got ${typeof input}`,
253
+ path: []
254
+ }
255
+ ]);
256
+ }
257
+ const trimmed = input.trim();
258
+ if (trimmed.startsWith("$")) {
259
+ const hexString = trimmed.slice(1);
260
+ if (!/^[0-9A-Fa-f]{1,2}$/.test(hexString)) {
261
+ throw new import_zod2.z.ZodError([
262
+ {
263
+ code: "custom",
264
+ message: `Invalid C64 hex ($) byte value: "${input}". Hex portion must be 1-2 hex digits (0-9, A-F)`,
265
+ path: []
266
+ }
267
+ ]);
268
+ }
269
+ const parsed = parseInt(hexString, 16);
270
+ if (isNaN(parsed) || parsed < 0 || parsed > 255) {
271
+ throw new import_zod2.z.ZodError([
272
+ {
273
+ code: "custom",
274
+ message: `Byte value out of range: "${input}" (${parsed}). Must be 0x00-0xFF (0-255)`,
275
+ path: []
276
+ }
277
+ ]);
278
+ }
279
+ return parsed;
280
+ }
281
+ if (trimmed.toLowerCase().startsWith("0x")) {
282
+ const hexString = trimmed.slice(2);
283
+ if (!/^[0-9A-Fa-f]{1,2}$/.test(hexString)) {
284
+ throw new import_zod2.z.ZodError([
285
+ {
286
+ code: "custom",
287
+ message: `Invalid C hex (0x) byte value: "${input}". Hex portion must be 1-2 hex digits (0-9, A-F)`,
288
+ path: []
289
+ }
290
+ ]);
291
+ }
292
+ const parsed = parseInt(hexString, 16);
293
+ if (isNaN(parsed) || parsed < 0 || parsed > 255) {
294
+ throw new import_zod2.z.ZodError([
295
+ {
296
+ code: "custom",
297
+ message: `Byte value out of range: "${input}" (${parsed}). Must be 0x00-0xFF (0-255)`,
298
+ path: []
299
+ }
300
+ ]);
301
+ }
302
+ return parsed;
303
+ }
304
+ if (trimmed.startsWith("%")) {
305
+ const binString = trimmed.slice(1);
306
+ if (!/^[01]{1,8}$/.test(binString)) {
307
+ throw new import_zod2.z.ZodError([
308
+ {
309
+ code: "custom",
310
+ message: `Invalid C64 binary (%) byte value: "${input}". Binary portion must be 1-8 binary digits (0-1)`,
311
+ path: []
312
+ }
313
+ ]);
314
+ }
315
+ const parsed = parseInt(binString, 2);
316
+ if (isNaN(parsed) || parsed < 0 || parsed > 255) {
317
+ throw new import_zod2.z.ZodError([
318
+ {
319
+ code: "custom",
320
+ message: `Byte value out of range: "${input}" (${parsed}). Must be 0b00000000-0b11111111 (0-255)`,
321
+ path: []
322
+ }
323
+ ]);
324
+ }
325
+ return parsed;
326
+ }
327
+ if (trimmed.toLowerCase().startsWith("0b")) {
328
+ const binString = trimmed.slice(2);
329
+ if (!/^[01]{1,8}$/.test(binString)) {
330
+ throw new import_zod2.z.ZodError([
331
+ {
332
+ code: "custom",
333
+ message: `Invalid C binary (0b) byte value: "${input}". Binary portion must be 1-8 binary digits (0-1)`,
334
+ path: []
335
+ }
336
+ ]);
337
+ }
338
+ const parsed = parseInt(binString, 2);
339
+ if (isNaN(parsed) || parsed < 0 || parsed > 255) {
340
+ throw new import_zod2.z.ZodError([
341
+ {
342
+ code: "custom",
343
+ message: `Byte value out of range: "${input}" (${parsed}). Must be 0b00000000-0b11111111 (0-255)`,
344
+ path: []
345
+ }
346
+ ]);
347
+ }
348
+ return parsed;
349
+ }
350
+ throw new import_zod2.z.ZodError([
351
+ {
352
+ code: "custom",
353
+ message: `Invalid byte format: "${input}". Expected formats: decimal (255), hex with prefix ($FF, 0xFF), or binary with prefix (%11111111, 0b11111111). Bare hex/binary not supported to avoid ambiguity.`,
354
+ path: []
355
+ }
356
+ ]);
357
+ }
358
+ var byteValueSchema = import_zod2.z.preprocess(parseByte, import_zod2.z.number().int().min(0).max(255)).describe("8-bit byte value: decimal (255), hex with prefix ($FF, 0xFF), or binary with prefix (%11111111, 0b11111111)");
359
+ var byteArraySchema = import_zod2.z.array(byteValueSchema).describe('Array of byte values in mixed formats: [255, "$FF", "%11111111", 42]');
158
360
  var c64RegisterValueSchema = import_zod2.z.object(
159
361
  Object.fromEntries(
160
362
  C64_REGISTER_DEFINITIONS.map((register) => [
@@ -2437,9 +2639,21 @@ var ViceSession = class {
2437
2639
  async #withAutoResumeForInput(commandName, operation) {
2438
2640
  await this.#ensureReady();
2439
2641
  this.#syncMonitorRuntimeState();
2642
+ if (this.#executionState === "unknown") {
2643
+ throw new ViceMcpError(
2644
+ "emulator_state_unknown",
2645
+ `${commandName} requires a known execution state (running or stopped)`,
2646
+ "session_state",
2647
+ false,
2648
+ {
2649
+ executionState: this.#executionState,
2650
+ lastStopReason: this.#lastStopReason
2651
+ }
2652
+ );
2653
+ }
2440
2654
  const wasRunning = this.#executionState === "running";
2441
2655
  const wasPaused = this.#explicitPauseActive;
2442
- if (!wasRunning) {
2656
+ if (this.#executionState === "stopped") {
2443
2657
  this.#writeProcessLogLine(`[${commandName}] auto-resuming for input operation`);
2444
2658
  await this.#client.continueExecution();
2445
2659
  await this.waitForState("running", 5e3, INPUT_RUNNING_STABLE_MS);
@@ -3440,10 +3654,27 @@ var setRegistersTool = createViceTool({
3440
3654
  });
3441
3655
  var readMemoryTool = createViceTool({
3442
3656
  id: "memory_read",
3443
- description: "Reads a memory chunk by start address and length and returns raw bytes as a JSON array.",
3444
- inputSchema: import_zod4.z.object({
3445
- address: address16Schema.describe("Start address in the 16-bit C64 address space"),
3446
- length: import_zod4.z.number().int().positive().max(65535).describe("Size of the data chunk to read in bytes")
3657
+ description: "Reads a memory chunk. Use either (address, length) or (start, end) format. Addresses can be decimal (53248) or hex string with prefix ($D000, 0xD000). Returns byte values as decimal numbers.",
3658
+ inputSchema: import_zod4.z.union([
3659
+ import_zod4.z.object({
3660
+ address: address16Schema.describe("Start address: decimal (53248) or hex string with prefix ($D000, 0xD000)"),
3661
+ length: import_zod4.z.number().int().positive().max(65535).describe("Number of bytes to read")
3662
+ }),
3663
+ import_zod4.z.object({
3664
+ start: address16Schema.describe("Start address (inclusive): decimal (53248) or hex string with prefix ($D000, 0xD000)"),
3665
+ end: address16Schema.describe("End address (inclusive): decimal (53248) or hex string with prefix ($D000, 0xD000)")
3666
+ })
3667
+ ]).transform((input) => {
3668
+ if ("start" in input && "end" in input) {
3669
+ if (input.end < input.start) {
3670
+ throw new Error("End address must be greater than or equal to start address");
3671
+ }
3672
+ return {
3673
+ address: input.start,
3674
+ length: input.end - input.start + 1
3675
+ };
3676
+ }
3677
+ return input;
3447
3678
  }).refine((input) => input.address + input.length <= 65536, {
3448
3679
  message: "address + length must stay within the 64K address space",
3449
3680
  path: ["length"]
@@ -3464,10 +3695,10 @@ var readMemoryTool = createViceTool({
3464
3695
  });
3465
3696
  var writeMemoryTool = createViceTool({
3466
3697
  id: "memory_write",
3467
- description: 'Writes raw byte values into the active C64 memory space. Requires emulator to be stopped - call execute(action="pause") first if running.',
3698
+ description: "Writes raw byte values into C64 memory. Address and byte values support decimal, hex ($FF, 0xFF), and binary (%11111111, 0b11111111) formats. Requires emulator to be stopped.",
3468
3699
  inputSchema: import_zod4.z.object({
3469
- address: address16Schema.describe("Start address in the 16-bit C64 address space"),
3470
- data: byteArraySchema.min(1).describe("Raw bytes to write into memory")
3700
+ address: address16Schema.describe("Start address: decimal (53248) or hex string with prefix ($D000, 0xD000)"),
3701
+ data: byteArraySchema.min(1).describe("Bytes to write: decimal (255), hex ($FF, 0xFF), or binary (%11111111, 0b11111111). Mixed formats allowed.")
3471
3702
  }).refine((input) => input.address + input.data.length - 1 <= 65535, {
3472
3703
  message: "address + data.length must stay within the 16-bit address space",
3473
3704
  path: ["data"]
@@ -3523,15 +3754,36 @@ var listBreakpointsTool = createViceTool({
3523
3754
  });
3524
3755
  var breakpointSetTool = createViceTool({
3525
3756
  id: "breakpoint_set",
3526
- description: "Creates an execution breakpoint or read/write watchpoint.",
3757
+ description: "Creates an execution breakpoint or read/write watchpoint. Use either (address, length) or (start, end) format. Addresses can be decimal (53248) or hex string with prefix ($D000, 0xD000).",
3527
3758
  inputSchema: import_zod4.z.object({
3528
3759
  kind: breakpointKindSchema,
3529
- address: address16Schema.describe("Start address of the breakpoint range"),
3530
- length: import_zod4.z.number().int().positive().default(1).describe("Size of the breakpoint range in bytes"),
3531
3760
  condition: import_zod4.z.string().optional(),
3532
3761
  label: import_zod4.z.string().optional(),
3533
3762
  temporary: import_zod4.z.boolean().default(false),
3534
3763
  enabled: import_zod4.z.boolean().default(true)
3764
+ }).and(
3765
+ import_zod4.z.union([
3766
+ import_zod4.z.object({
3767
+ address: address16Schema.describe("Start address: decimal (53248) or hex string with prefix ($D000, 0xD000)"),
3768
+ length: import_zod4.z.number().int().positive().default(1).describe("Size of the breakpoint range in bytes")
3769
+ }),
3770
+ import_zod4.z.object({
3771
+ start: address16Schema.describe("Start address (inclusive): decimal (53248) or hex string with prefix ($D000, 0xD000)"),
3772
+ end: address16Schema.describe("End address (inclusive): decimal (53248) or hex string with prefix ($D000, 0xD000)")
3773
+ })
3774
+ ])
3775
+ ).transform((input) => {
3776
+ if ("start" in input && "end" in input) {
3777
+ if (input.end < input.start) {
3778
+ throw new Error("End address must be greater than or equal to start address");
3779
+ }
3780
+ return {
3781
+ ...input,
3782
+ address: input.start,
3783
+ length: input.end - input.start + 1
3784
+ };
3785
+ }
3786
+ return input;
3535
3787
  }),
3536
3788
  dataSchema: import_zod4.z.object({
3537
3789
  breakpoint: breakpointSchema,