c64bridge 1.0.5 → 1.0.7
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/AGENTS.md +1 -1
- package/README.md +3 -3
- package/data/sound/sidwave.md +113 -36
- package/data/sound/sidwave.schema.json +138 -28
- package/dist/.tsbuildinfo +1 -1
- package/dist/c64Client.js +83 -93
- package/dist/chargen.js +0 -4
- package/dist/config.js +26 -6
- package/dist/device.js +130 -24
- package/dist/diagnostics.js +14 -5
- package/dist/logger.js +3 -2
- package/dist/loggingHttpClient.js +12 -1
- package/dist/mcp-server.js +19 -9
- package/dist/petscii.js +0 -5
- package/dist/redact.js +166 -0
- package/dist/sidwave.js +453 -61
- package/dist/sidwaveCompiler.js +1424 -269
- package/dist/tools/audio.js +70 -12
- package/dist/tools/developer.js +17 -9
- package/dist/tools/errors.js +7 -0
- package/dist/tools/graphics.js +4 -4
- package/dist/tools/input.js +10 -13
- package/dist/tools/machineControl.js +7 -7
- package/dist/tools/memory.js +6 -6
- package/dist/tools/meta/audio.js +8 -2
- package/dist/tools/meta/config.js +5 -4
- package/dist/tools/meta/configInventory.js +25 -2
- package/dist/tools/meta/diagnostics.js +2 -1
- package/dist/tools/meta/graphics.js +1 -1
- package/dist/tools/meta/memory.js +4 -4
- package/dist/tools/meta/program.js +12 -8
- package/dist/tools/printer.js +4 -4
- package/dist/tools/programRunners.js +8 -8
- package/dist/tools/registry/batch.js +1 -7
- package/dist/tools/registry/disk.js +7 -5
- package/dist/tools/storage.js +21 -17
- package/dist/tools/streaming.js +2 -2
- package/dist/tools/translation/assembler.js +44 -12
- package/dist/tools/translation/basicTokenizer.js +5 -1
- package/dist/tools/translation/opcodeInventory.js +1 -1
- package/dist/tools/vice.js +1 -1
- package/dist/vice/mockServer.js +92 -5
- package/dist/vice/readiness.js +4 -9
- package/dist/vice/viceClient.js +18 -0
- package/doc/audit/secret-handling.md +61 -0
- package/mcp/schemas/c64_config.schema.json +19 -2
- package/mcp/schemas/c64_disk.schema.json +2 -2
- package/mcp/schemas/c64_input.schema.json +3 -3
- package/mcp/schemas/c64_program.schema.json +2 -2
- package/mcp/schemas/c64_vice.schema.json +3 -3
- package/mcp/server.json +1 -1
- package/mcp/tools.json +33 -12
- package/mcp.json +1 -1
- package/package.json +5 -2
- package/scripts/mockC64Server.mjs +15 -1
- package/scripts/run-coverage.mjs +31 -1
- package/scripts/update-readme.ts +3 -2
- package/server.json +20 -3
package/dist/vice/mockServer.js
CHANGED
|
@@ -40,10 +40,16 @@ export class ViceMockServer {
|
|
|
40
40
|
this.registerMetadata = buildDefaultRegisterMetadata();
|
|
41
41
|
this.registerValues = new Map();
|
|
42
42
|
this.resources = new Map();
|
|
43
|
+
/** Output lines of VICE's I/O simulation joyport device per port (0 = port 1). */
|
|
44
|
+
this.joyportOutput = [0, 0];
|
|
43
45
|
this.sockets = new Set();
|
|
46
|
+
/** Images attached with autostart, and drive CPU resets, as observed by tests. */
|
|
47
|
+
this.autostarts = [];
|
|
48
|
+
this.driveResets = [];
|
|
44
49
|
this.host = options?.host ?? "127.0.0.1";
|
|
45
50
|
this.requestedPort = options?.port;
|
|
46
51
|
this.resetState();
|
|
52
|
+
this.resetResources();
|
|
47
53
|
}
|
|
48
54
|
async start() {
|
|
49
55
|
if (this.server)
|
|
@@ -114,9 +120,26 @@ export class ViceMockServer {
|
|
|
114
120
|
switch (cmd) {
|
|
115
121
|
case 0x85: // info
|
|
116
122
|
return buildResponse(cmd, reqId);
|
|
117
|
-
case 0xCC: // reset
|
|
118
|
-
|
|
123
|
+
case 0xCC: { // reset: types 8-11 reset only that drive's CPU
|
|
124
|
+
const type = body[0] ?? 0;
|
|
125
|
+
if (type >= 8 && type <= 11) {
|
|
126
|
+
this.driveResets.push(type);
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
this.resetState();
|
|
130
|
+
}
|
|
131
|
+
return buildResponse(cmd, reqId);
|
|
132
|
+
}
|
|
133
|
+
case 0xDD: { // autostart: power-cycles, attaches the image to drive 8 and loads (run=1 also runs)
|
|
134
|
+
const nameLength = body[3] ?? 0;
|
|
135
|
+
this.autostarts.push({
|
|
136
|
+
run: body[0] === 1,
|
|
137
|
+
fileIndex: body.readUInt16LE(1),
|
|
138
|
+
filename: body.subarray(4, 4 + nameLength).toString("utf8"),
|
|
139
|
+
});
|
|
140
|
+
this.renderAutoload(body[0] === 1);
|
|
119
141
|
return buildResponse(cmd, reqId);
|
|
142
|
+
}
|
|
120
143
|
case 0x11: { // checkpoint get
|
|
121
144
|
const id = body.readUInt32LE(0);
|
|
122
145
|
const checkpoint = this.checkpoints.get(id);
|
|
@@ -185,13 +208,17 @@ export class ViceMockServer {
|
|
|
185
208
|
case 0x01: { // mem get
|
|
186
209
|
const start = body.readUInt16LE(1);
|
|
187
210
|
const end = body.readUInt16LE(3);
|
|
188
|
-
|
|
211
|
+
if (start > end) {
|
|
212
|
+
return buildResponse(cmd, reqId, Buffer.alloc(0), 0x81); // VICE rejects start > end with INVALID_PARAMETER.
|
|
213
|
+
}
|
|
214
|
+
const length = end - start + 1;
|
|
189
215
|
const payload = Buffer.alloc(2 + length);
|
|
190
216
|
payload.writeUInt16LE(length & 0xffff, 0); // Like VICE: a full 64 KB read reports ML = 0.
|
|
191
217
|
for (let i = 0; i < length; i += 1) {
|
|
192
218
|
payload[2 + i] = this.memory[(start + i) & 0xffff];
|
|
193
219
|
}
|
|
194
220
|
this.applyHeartbeatOverlay(start, length, payload);
|
|
221
|
+
this.applyJoyportOverlay(start, length, payload);
|
|
195
222
|
return buildResponse(cmd, reqId, payload);
|
|
196
223
|
}
|
|
197
224
|
case 0x02: { // mem set
|
|
@@ -246,7 +273,7 @@ export class ViceMockServer {
|
|
|
246
273
|
const name = body.subarray(1, 1 + nameLen).toString("utf8");
|
|
247
274
|
const value = this.resources.get(name);
|
|
248
275
|
if (value === undefined) {
|
|
249
|
-
return buildResponse(cmd, reqId, Buffer.
|
|
276
|
+
return buildResponse(cmd, reqId, Buffer.alloc(0), 0x01); // VICE: OBJECT_MISSING for an unknown resource.
|
|
250
277
|
}
|
|
251
278
|
if (typeof value === "number") {
|
|
252
279
|
const buffer = Buffer.alloc(2 + 4);
|
|
@@ -266,12 +293,20 @@ export class ViceMockServer {
|
|
|
266
293
|
const isNumber = body[0] === 1;
|
|
267
294
|
const nameLen = body[1] ?? 0;
|
|
268
295
|
const name = body.subarray(2, 2 + nameLen).toString("utf8");
|
|
296
|
+
if (!this.resources.has(name)) {
|
|
297
|
+
// VICE: an unknown integer resource fails resources_set (CMD_FAILURE); an unknown string one is OBJECT_MISSING.
|
|
298
|
+
return buildResponse(cmd, reqId, Buffer.alloc(0), isNumber ? 0x8f : 0x01);
|
|
299
|
+
}
|
|
269
300
|
if (isNumber) {
|
|
270
301
|
const valueLen = body[2 + nameLen] ?? 0;
|
|
271
302
|
const valueBytes = body.subarray(3 + nameLen, 3 + nameLen + valueLen);
|
|
272
303
|
const padded = Buffer.alloc(4);
|
|
273
304
|
valueBytes.copy(padded, 0, 0, Math.min(4, valueBytes.length));
|
|
274
305
|
const value = padded.readInt32LE(0);
|
|
306
|
+
// VICE rejects joyport device ids it does not have (JOYPORT_MAX_DEVICES is 44 in 3.10).
|
|
307
|
+
if (/^JoyPort\dDevice$/.test(name) && (value < 0 || value >= 44)) {
|
|
308
|
+
return buildResponse(cmd, reqId, Buffer.alloc(0), 0x8f);
|
|
309
|
+
}
|
|
275
310
|
this.resources.set(name, value);
|
|
276
311
|
}
|
|
277
312
|
else {
|
|
@@ -281,6 +316,18 @@ export class ViceMockServer {
|
|
|
281
316
|
}
|
|
282
317
|
return buildResponse(cmd, reqId);
|
|
283
318
|
}
|
|
319
|
+
case 0xA2: { // joyport set: like VICE, it only stores the I/O simulation device's output lines
|
|
320
|
+
const port = body.readUInt16LE(0);
|
|
321
|
+
const value = body.readUInt16LE(2);
|
|
322
|
+
if (port > 1) {
|
|
323
|
+
return buildResponse(cmd, reqId, Buffer.alloc(0), 0x01);
|
|
324
|
+
}
|
|
325
|
+
if (value > 0xff) {
|
|
326
|
+
return buildResponse(cmd, reqId, Buffer.alloc(0), 0x81);
|
|
327
|
+
}
|
|
328
|
+
this.joyportOutput[port] = value & 0x1f;
|
|
329
|
+
return buildResponse(cmd, reqId);
|
|
330
|
+
}
|
|
284
331
|
case 0xAA: // exit monitor
|
|
285
332
|
return buildResponse(cmd, reqId);
|
|
286
333
|
case 0xBB: // quit
|
|
@@ -434,11 +481,51 @@ export class ViceMockServer {
|
|
|
434
481
|
for (const meta of this.registerMetadata) {
|
|
435
482
|
this.registerValues.set(meta.id, 0);
|
|
436
483
|
}
|
|
484
|
+
this.joyportOutput = [0, 0];
|
|
485
|
+
}
|
|
486
|
+
// Resources survive a machine reset in VICE. Only resources that exist in
|
|
487
|
+
// VICE 3.10 are known; others fail the way VICE fails (see below).
|
|
488
|
+
resetResources() {
|
|
437
489
|
this.resources = new Map([
|
|
438
|
-
["C64Model", 2],
|
|
439
490
|
["SidEngine", 1],
|
|
491
|
+
["JoyPort1Device", 1],
|
|
492
|
+
["JoyPort2Device", 1],
|
|
493
|
+
["Drive8Type", 1541],
|
|
494
|
+
["Drive9Type", 0],
|
|
495
|
+
["Drive10Type", 0],
|
|
496
|
+
["Drive11Type", 0],
|
|
497
|
+
["VICIIDoubleSize", 0],
|
|
498
|
+
["VICIIBorderMode", 0],
|
|
499
|
+
["VICIIFullscreen", 0],
|
|
500
|
+
["SoundVolume", 100],
|
|
501
|
+
["SidModel", 0],
|
|
502
|
+
["MachineVideoStandard", 1],
|
|
503
|
+
["KernalName", "kernal-901227-03.bin"],
|
|
440
504
|
]);
|
|
441
505
|
}
|
|
506
|
+
/** Lines a joyport device drives, as CIA1 sees them (active low); only the I/O simulation device (37) is modelled. */
|
|
507
|
+
joyportLines(port) {
|
|
508
|
+
return this.resources.get(`JoyPort${port}Device`) === 37 ? this.joyportOutput[port - 1] : 0xff;
|
|
509
|
+
}
|
|
510
|
+
// CIA1 port A ($DC00) reads joyport 2 and port B ($DC01) joyport 1, ANDed with the keyboard lines.
|
|
511
|
+
applyJoyportOverlay(start, length, payload) {
|
|
512
|
+
for (const [address, port] of [[0xdc00, 2], [0xdc01, 1]]) {
|
|
513
|
+
if (address >= start && address < start + length) {
|
|
514
|
+
payload[2 + address - start] = 0xff & this.joyportLines(port);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
// What VICE's autoload leaves on screen after its power cycle: the typed LOAD and READY.
|
|
519
|
+
renderAutoload(run) {
|
|
520
|
+
// The power cycle's KERNAL reset clears $0200-$03FF (RAMTAS).
|
|
521
|
+
this.memory.fill(0x00, 0x0200, 0x0400);
|
|
522
|
+
this.memory.fill(0x20, SCREEN_BASE, SCREEN_BASE + 1000);
|
|
523
|
+
const lines = ['LOAD"*",8,1', "", "SEARCHING FOR *", "LOADING", "READY.", ...(run ? ["RUN"] : [])];
|
|
524
|
+
lines.forEach((line, row) => {
|
|
525
|
+
const codes = asciiToScreenCodes(line);
|
|
526
|
+
codes.copy(Buffer.from(this.memory.buffer, SCREEN_BASE + row * 40, codes.length));
|
|
527
|
+
});
|
|
528
|
+
}
|
|
442
529
|
renderHello() {
|
|
443
530
|
if (this.helloReady)
|
|
444
531
|
return;
|
package/dist/vice/readiness.js
CHANGED
|
@@ -126,15 +126,10 @@ export function asciiToScreenCodes(text) {
|
|
|
126
126
|
out.push(ch.charCodeAt(0) - 64);
|
|
127
127
|
else if (ch >= '0' && ch <= '9')
|
|
128
128
|
out.push(0x30 + (ch.charCodeAt(0) - 48));
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
else if (ch === ':')
|
|
134
|
-
out.push(0x3A);
|
|
135
|
-
else if (ch === '-')
|
|
136
|
-
out.push(0x2D);
|
|
137
|
-
// extend as needed; ignore others
|
|
129
|
+
// Space, digits and punctuation ($20-$3F) have the same screen codes as ASCII.
|
|
130
|
+
else if (ch >= ' ' && ch <= '?')
|
|
131
|
+
out.push(ch.charCodeAt(0));
|
|
132
|
+
// ignore others
|
|
138
133
|
}
|
|
139
134
|
return Buffer.from(out);
|
|
140
135
|
}
|
package/dist/vice/viceClient.js
CHANGED
|
@@ -159,6 +159,24 @@ export class ViceClient {
|
|
|
159
159
|
async resetSoft() { await this.send(0xCC, Buffer.from([0x00])); }
|
|
160
160
|
async resetHard() { await this.send(0xCC, Buffer.from([0x01])); }
|
|
161
161
|
async reset(type = 0) { await this.send(0xCC, Buffer.from([type])); }
|
|
162
|
+
/** Reset only the drive CPU of unit 8-11 (BM 0xCC reset types 8-11). */
|
|
163
|
+
async resetDrive(unit) { await this.send(0xCC, Buffer.from([unit])); }
|
|
164
|
+
/**
|
|
165
|
+
* Attach an image and load from it (BM 0xDD Autostart). With run=false VICE
|
|
166
|
+
* uses its autoload mode, which attaches to drive 8 and LOADs without RUN.
|
|
167
|
+
*/
|
|
168
|
+
async autostart(filename, options) {
|
|
169
|
+
const name = Buffer.from(filename, "utf8");
|
|
170
|
+
if (name.length === 0 || name.length > 255) {
|
|
171
|
+
throw new Error("Autostart file names must be 1-255 bytes");
|
|
172
|
+
}
|
|
173
|
+
const body = Buffer.alloc(4 + name.length);
|
|
174
|
+
body[0] = options.run ? 1 : 0;
|
|
175
|
+
body.writeUInt16LE(options.fileIndex ?? 0, 1);
|
|
176
|
+
body[3] = name.length;
|
|
177
|
+
name.copy(body, 4);
|
|
178
|
+
await this.send(0xDD, body);
|
|
179
|
+
}
|
|
162
180
|
/**
|
|
163
181
|
* Exit the emulator process (BM 0xBB Quit). The socket will be closed by VICE.
|
|
164
182
|
* Consumers should still call close() to dispose any local resources.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Ultimate network password handling audit
|
|
2
|
+
|
|
3
|
+
**Summary: yes, a leak exists.** An Ultimate request failure can leave the password in an Axios error's request configuration, which reaches stderr when a screen read fails during BASIC execution or polling. This is a source audit, supplemented by a local synthetic Axios serialization/console check; it does not establish that a real password was exposed. Diagnostics usually reduce actual `Error` objects to selected fields, and ordinary MCP error conversion usually uses only the message, but neither is a general secret boundary. Firmware-returned data and arbitrary error messages are not redacted.
|
|
4
|
+
|
|
5
|
+
## Paths checked
|
|
6
|
+
|
|
7
|
+
- **Password source and transport — leak (error object).** `src/config.ts:139`–`146` and `src/config.ts:201`–`205` resolve the environment or configuration password; `src/c64Client.ts:351`–`354`, `src/device.ts:221`–`230`, `src/device.ts:1550`–`1551`, and `src/mcp-server.ts:642`–`645` attach it to `X-Password`. Generated `HttpClient.request` returns the Axios request directly (`generated/c64/index.ts:299`–`334`). The HTTP interceptor rejects the original error (`src/loggingHttpClient.ts:49`–`57`), preserving `error.config.headers`. The installed Axios implementation serializes config in `AxiosError.toJSON` unless opt-in redaction is configured (`node_modules/axios/lib/core/AxiosError.js:171`–`196`); a synthetic `JSON.stringify` of an Axios error with this header printed the password. This is an in-process disclosure to error consumers, and makes downstream serialization dangerous.
|
|
8
|
+
|
|
9
|
+
- **HTTP logging — safe for the configured header on the normal interceptor path.** `src/loggingHttpClient.ts:17`–`23` redacts sensitive header names case-insensitively; request metadata stores the redacted copy (`:32`–`:45`), and success/error debug records use it (`:63`–`:115`). The error summary uses only the message (`src/logger.ts:191`–`199`). Request bodies, parameters, response bodies and messages are not redacted, so a server echo or a password entered separately as data is outside this safe verdict.
|
|
10
|
+
|
|
11
|
+
- **General logger and console — leak.** `src/logger.ts:37`–`47` passes details to console unchanged; `src/bootstrap/stdio-logger.ts:25`–`33` directs console output to stderr. `C64Client.readScreen` calls `readMemoryRaw` (`src/c64Client.ts:900`–`904`), which rethrows a non-unsupported facade error (`:2169`–`:2177`). After BASIC upload, `src/tools/programRunners.ts:381`–`385` logs that raw error as details (`:34`–`:41`); Node console inspection prints Axios `config.headers`, including the password. With debug logging enabled, `src/tools/pollValidator.ts:169`–`170` and `:192`–`:193` likewise log the raw error (also `:255`–`:256`, `:279`–`:280`). Other console sites were inspected by search: startup connectivity logs a message rather than the Axios object (`src/mcp-server.ts:650`–`660`), while fatal logging prints an arbitrary raw error (`:738`–`:745`), so it has the same unsafe sink if such an error reaches it. `src/config.ts:62`–`69` prints JSON parser error messages; malformed configuration can put a fragment of the file in that message, so this is another potential credential disclosure, dependent on the malformed input and Node version.
|
|
12
|
+
|
|
13
|
+
- **Diagnostic event files — safe for ordinary Axios Error configuration; otherwise unclear.** `writeDiagnosticEvent` calls `sanitize` before appending NDJSON (`src/diagnostics.ts:112`–`128`); actual `Error` instances are reduced to name, message, stack, code and sanitized cause, omitting Axios config (`:444`–`:453`). This applies to the raw connectivity error (`src/mcp-server.ts:650`–`659`), span failures (`src/diagnostics.ts:198`–`211`), MCP failure events (`src/mcp-server.ts:395`–`415`), and process handlers (`src/diagnostics.ts:410`–`422`). However, plain objects are traversed without key redaction (`:483`–`:488`), and strings, messages and stacks are retained (`:445`–`:456`); call-tool arguments are recorded (`src/mcp-server.ts:350`–`357`). Thus a serialized error, echoed credential, or user-supplied password can be persisted; no automatic flow from the configured header into those plain-object events was established. Diagnostics are enabled by default outside tests unless explicitly disabled (`src/diagnostics.ts:434`–`441`).
|
|
14
|
+
|
|
15
|
+
- **MCP results and errors — safe for ordinary Axios config; unclear for returned firmware data.** `C64Client.normaliseError` selects message, status and response data for Axios errors (`src/c64Client.ts:2426`–`2439`); for example `readMemory` returns those details (`:927`–`:949`). `unknownErrorResult` uses an Error's message rather than serializing the Error (`src/tools/errors.ts:104`–`121`), while `toolErrorResult` includes supplied details (`:78`–`:93`) and `toCallToolResult` passes result fields through (`src/mcp-server.ts:690`–`713`). `version`, `info` and `configGet` can propagate raw Axios errors (`src/device.ts:357`–`358`, `:418`–`:422`; `src/c64Client.ts:1145`–`1152`, `:1416`–`:1423`), but their ordinary tool catches use the message. No direct serialization of one of these raw Axios errors into a client result was established. Response data is unfiltered, however: `config_get` returns firmware values (`src/tools/developer.ts:219`–`237`); whether firmware exposes its password there or echoes it on error is not established. User-supplied config values can also be returned (`:255`–`:280`).
|
|
16
|
+
|
|
17
|
+
- **MCP resources and diagnostic reports — unclear for indirect content.** Platform status constructs backend and capability text, not endpoint credentials (`src/mcp-server.ts:197`–`219`, `:543`–`:604`); resource failures log messages, record sanitized errors and rethrow (`:236`–`:249`). `performance_report` reads diagnostic records and returns a summary (`src/tools/meta/diagnostics.ts:75`–`99`; `src/diagnostics.ts:235`–`249`, `:256` onward), so any sensitive content retained in the diagnostic timeline may reach a client. No direct configured-header-to-resource leak was established.
|
|
18
|
+
|
|
19
|
+
- **Thrown and rethrown errors — leak across internal error boundary.** The original Axios error is rethrown by `readMemoryRaw` (`src/c64Client.ts:2169`–`2177`), by `withDiagnosticSpan` (`src/diagnostics.ts:198`–`211`), and by stream retry and capture paths (`src/c64Client.ts:1963`–`1971`, `:1978`–`:1983`, `:1633`–`:1638`, `:1883`–`:1895`). It still contains the request config; `JSON.stringify` invokes Axios's `toJSON` and exposes the header without opt-in redaction. `ensureStreamSuccess` embeds serialized result details in a new error (`:2023`–`:2027`), but no proven route from a raw Axios config into those details was found. Raw errors reaching generic console sinks remain a concrete risk.
|
|
20
|
+
|
|
21
|
+
- **Other files — unclear for firmware-returned secrets.** Config snapshot collection stores returned values (`src/tools/meta/configInventory.ts:86`–`101`) and snapshot writes them to a file (`src/tools/meta/config.ts:40`–`54`). Whether the firmware returns the network password is unverified; the bridge does not insert its configured header value into the snapshot itself.
|
|
22
|
+
|
|
23
|
+
## Recommended fixes
|
|
24
|
+
|
|
25
|
+
1. In `createLoggingHttpClient`, replace or sanitize rejected Axios errors before they leave the transport, removing sensitive headers from config and request objects; apply the same boundary to direct Axios calls in `logConnectivity` and `C64uBackend.ping`. Preserve useful status and message fields without retaining credential-bearing causes.
|
|
26
|
+
2. In `createPrefixedLogger` and the error logging in `pollBasicOutcome`, the ASM polling function, and BASIC program execution, log safe error summaries rather than raw objects; in `loadConfig`, avoid printing parser excerpts from configuration files. Review the fatal `main().catch` console sink as well.
|
|
27
|
+
3. In `sanitize` and `writeDiagnosticEvent`, redact sensitive keys recursively and handle serialized Axios errors, arguments and error strings deliberately. In `normaliseError`, `unknownErrorResult`, `toolErrorResult`, and `toCallToolResult`, enforce a safe output boundary for errors and echoed data.
|
|
28
|
+
4. In `config_get`'s `execute` and `captureConfigSnapshot`, decide how sensitive firmware configuration values should be handled and redact them if returned. Add focused tests with a synthetic password covering stderr, diagnostics, serialized errors, tool responses and snapshots.
|
|
29
|
+
|
|
30
|
+
## Outcome of the fix
|
|
31
|
+
|
|
32
|
+
The audit findings above describe the code before this change. This change adds one shared helper, `src/redact.ts`, and applies it at each sink.
|
|
33
|
+
|
|
34
|
+
- `registerSecret` records the password. It is called from `resolveUltimateEndpoint` (`src/config.ts`), from `createLoggingHttpClient` (which reads the `X-Password` header it is configured with), and before the direct connectivity probe in `src/mcp-server.ts`.
|
|
35
|
+
- `scrubRequestError` is applied to every rejected request in `createLoggingHttpClient`. It replaces sensitive values in `error.config.headers` and `error.response.config.headers`, clears `error.request` and `error.response.request` (the raw client request keeps the serialised header block), and redacts the password in a string response body. The status, code, message and response data otherwise remain available to callers. This closes the "thrown and rethrown errors" and `JSON.stringify` leaks.
|
|
36
|
+
- `redactValue` and `redactText` are applied in `createPrefixedLogger` (`src/logger.ts`) to the message and details, in `sanitize` (`src/diagnostics.ts`) before the existing size limiting, in `toCallToolResult` (`src/mcp-server.ts`) to `content`, `structuredContent` and `metadata`, and to the fatal error in `main().catch`. `redactValue` reduces `Error` objects to name, message, stack, code, status and cause, redacts credential header names at any depth, and replaces registered secrets in all strings.
|
|
37
|
+
- `loadConfig` now logs only the error name for an unreadable configuration file, because the JSON parser message can quote part of the file.
|
|
38
|
+
|
|
39
|
+
Remaining limitation: redaction replaces the exact registered password string. A transformed form of the password (for example base64-encoded) is not detected. Firmware configuration values are handled separately, see below.
|
|
40
|
+
|
|
41
|
+
Regression test: `test/secretRedaction.test.mjs` configures a distinctive password through `C64U_PASSWORD`, then drives HTTP 401 (with the password echoed in the body), HTTP 403, a client timeout and a refused connection against local servers. It asserts that the password is absent from captured console output, diagnostics files and records, `util.inspect`, `JSON.stringify`, `toJSON` and `stack` of the errors, and from `toCallToolResult` output for `toolErrorResult` and `unknownErrorResult`. With `src/redact.ts` replaced by no-op functions and the other `src/` changes reverted, the test fails with "password leaked in: AxiosError: Request failed with status code 401". With the fix it passes. `npm test` passes.
|
|
42
|
+
|
|
43
|
+
## Firmware-returned configuration values
|
|
44
|
+
|
|
45
|
+
**Evidence in the repository.** The repository does not list individual configuration item names for the credential settings. What it records:
|
|
46
|
+
|
|
47
|
+
- `doc/c64u/c64-rest-api.md:17`: firmware 3.12 has an optional "Network Password".
|
|
48
|
+
- `doc/c64u/c64-rest-api.md:93`–`95` (and the captured `scripts/compare-rest/rest-compare-2025-11-01T00-25-21-830Z.json:371`–`376`): the category list contains "Network settings", "WiFi settings" and "Modem Settings". These are the categories that can hold network, Wi-Fi and service credentials (network password, FTP/Telnet passwords, Wi-Fi key).
|
|
49
|
+
- `doc/c64u/c64-rest-api.md:108`–`143`: category reads return `{ "<Category>": { "<Item>": value } }`, and item reads return `{ "<Category>": { "<Item>": { current, min, max, ... } } }`.
|
|
50
|
+
- `doc/c64u/c64-openapi.yaml` and `doc/u2/u2-openapi.yaml` describe `X-Password` only; they define no password item.
|
|
51
|
+
|
|
52
|
+
Whether the firmware returns the password through `/v1/configs` is therefore not established by repository data. The fix assumes that it can.
|
|
53
|
+
|
|
54
|
+
**Behaviour.** `config_get` and `config_snapshot_and_restore` (snapshot and diff) now replace the value of credential items with `***` and keep the item name. An item is treated as a credential when its name contains `password`, `passwd`, `passphrase`, `passcode`, `psk` or `secret`, or is a bare Wi-Fi `key`. Matching is by item name (`isSensitiveConfigItem` in `src/redact.ts`), not by category, so it also applies to items in categories not listed above. For item reads, the whole item object (including `min`/`max`/`default`) is replaced by `***`.
|
|
55
|
+
|
|
56
|
+
- Opt-in: `includeSecretValues: true` on `config_get` and on `config_snapshot_and_restore` returns or stores the values in plain text. Without the flag the values are never returned or written. The configured network password is still replaced by the registered-secret redaction in `toCallToolResult`, even with the flag.
|
|
57
|
+
- Restore: a default snapshot contains `***`. `stripRedactedSecrets` removes those placeholders from the restore payload, so a restore never sets a password to the literal `***`. Credentials in a default snapshot are therefore not restored. Snapshots made with `includeSecretValues: true` restore them.
|
|
58
|
+
- Diff: both sides are redacted by default, so a changed password is not reported unless the flag is set when the snapshot is taken and when diffing.
|
|
59
|
+
- Limitation: detection is by item name. A credential item with a name that does not match the patterns above is not redacted.
|
|
60
|
+
|
|
61
|
+
**Test.** `test/configSecretRedaction.test.mjs` uses a mocked firmware with "Network Password", "FTP Password", "Telnet Password" and a Wi-Fi "Password". It asserts redaction in the `config_get` result and in the written snapshot file, that other values (Hostname, Volume, access point name) are unchanged, that restore does not write `***`, and that the opt-in flag returns plain values. With `isSensitiveConfigItem` changed to always return false, 3 of its 4 tests fail.
|
|
@@ -35,6 +35,14 @@
|
|
|
35
35
|
"description": "Specific configuration item inside the selected category (e.g. 'Volume').",
|
|
36
36
|
"minLength": 1
|
|
37
37
|
},
|
|
38
|
+
"includeSecretValues": {
|
|
39
|
+
"type": [
|
|
40
|
+
"boolean",
|
|
41
|
+
"null"
|
|
42
|
+
],
|
|
43
|
+
"description": "Return credential values (network password, FTP/Telnet passwords, Wi-Fi keys) in plain text. Default false: those values are replaced with ***.",
|
|
44
|
+
"default": false
|
|
45
|
+
},
|
|
38
46
|
"value": {
|
|
39
47
|
"description": "Primitive configuration value (string, number, or boolean).",
|
|
40
48
|
"type": [
|
|
@@ -128,7 +136,7 @@
|
|
|
128
136
|
"required": [
|
|
129
137
|
"op"
|
|
130
138
|
],
|
|
131
|
-
"description": "Configuration operations available via the c64_config tool.\n\nOperations:\n- list: List configuration categories reported by the firmware. Required inputs: none.\n- get: Read a configuration category or specific item. Required inputs: category. Optional inputs: item.\n- set: Write a configuration value in the selected category. Required inputs: category, item, value.\n- batch_update: Apply multiple configuration updates in a single request. Required inputs: none.\n- load_flash: Load configuration from flash storage. Required inputs: none.\n- save_flash: Persist the current configuration to flash storage. Required inputs: none.\n- reset_defaults: Reset firmware configuration to factory defaults. Required inputs: none.\n- read_debugreg: Read the Ultimate debug register ($D7FF). Required inputs: none.\n- write_debugreg: Write a hex value to the Ultimate debug register ($D7FF). Required inputs: value.\n- info: Retrieve Ultimate hardware information and status. Required inputs: none.\n- version: Fetch firmware version details. Required inputs: none.\n- snapshot: Snapshot configuration to disk for later restore or diff. Required inputs: path.\n- restore: Restore configuration from a snapshot file. Required inputs: path. Optional inputs: applyToFlash.\n- diff: Compare the current configuration with a snapshot. Required inputs: path.\n- shuffle: Discover PRG/CRT files and run each with optional screen capture. Required inputs: none. Optional inputs: root, extensions, durationMs, captureScreen, maxPrograms, outputPath, resetDelayMs.",
|
|
139
|
+
"description": "Configuration operations available via the c64_config tool.\n\nOperations:\n- list: List configuration categories reported by the firmware. Required inputs: none.\n- get: Read a configuration category or specific item. Required inputs: category. Optional inputs: item, includeSecretValues.\n- set: Write a configuration value in the selected category. Required inputs: category, item, value.\n- batch_update: Apply multiple configuration updates in a single request. Required inputs: none.\n- load_flash: Load configuration from flash storage. Required inputs: none.\n- save_flash: Persist the current configuration to flash storage. Required inputs: none.\n- reset_defaults: Reset firmware configuration to factory defaults. Required inputs: none.\n- read_debugreg: Read the Ultimate debug register ($D7FF). Required inputs: none.\n- write_debugreg: Write a hex value to the Ultimate debug register ($D7FF). Required inputs: value.\n- info: Retrieve Ultimate hardware information and status. Required inputs: none.\n- version: Fetch firmware version details. Required inputs: none.\n- snapshot: Snapshot configuration to disk for later restore or diff. Required inputs: path.\n- restore: Restore configuration from a snapshot file. Required inputs: path. Optional inputs: applyToFlash.\n- diff: Compare the current configuration with a snapshot. Required inputs: path.\n- shuffle: Discover PRG/CRT files and run each with optional screen capture. Required inputs: none. Optional inputs: root, extensions, durationMs, captureScreen, maxPrograms, outputPath, resetDelayMs.",
|
|
132
140
|
"additionalProperties": false,
|
|
133
141
|
"x-c64bridge-operations": [
|
|
134
142
|
{
|
|
@@ -157,7 +165,8 @@
|
|
|
157
165
|
"category"
|
|
158
166
|
],
|
|
159
167
|
"optional": [
|
|
160
|
-
"item"
|
|
168
|
+
"item",
|
|
169
|
+
"includeSecretValues"
|
|
161
170
|
],
|
|
162
171
|
"inputSchema": {
|
|
163
172
|
"type": "object",
|
|
@@ -176,6 +185,14 @@
|
|
|
176
185
|
"description": "Specific configuration item inside the selected category (e.g. 'Volume').",
|
|
177
186
|
"minLength": 1
|
|
178
187
|
},
|
|
188
|
+
"includeSecretValues": {
|
|
189
|
+
"type": [
|
|
190
|
+
"boolean",
|
|
191
|
+
"null"
|
|
192
|
+
],
|
|
193
|
+
"description": "Return credential values (network password, FTP/Telnet passwords, Wi-Fi keys) in plain text. Default false: those values are replaced with ***.",
|
|
194
|
+
"default": false
|
|
195
|
+
},
|
|
179
196
|
"op": {
|
|
180
197
|
"const": "get"
|
|
181
198
|
}
|
|
@@ -119,7 +119,7 @@
|
|
|
119
119
|
"integer",
|
|
120
120
|
"null"
|
|
121
121
|
],
|
|
122
|
-
"description": "Track count (D64 supports 35
|
|
122
|
+
"description": "Track count (D64 supports 35 to 41; DNP requires explicit tracks).",
|
|
123
123
|
"minimum": 1,
|
|
124
124
|
"maximum": 255
|
|
125
125
|
},
|
|
@@ -421,7 +421,7 @@
|
|
|
421
421
|
"integer",
|
|
422
422
|
"null"
|
|
423
423
|
],
|
|
424
|
-
"description": "Track count (D64 supports 35
|
|
424
|
+
"description": "Track count (D64 supports 35 to 41; DNP requires explicit tracks).",
|
|
425
425
|
"minimum": 1,
|
|
426
426
|
"maximum": 255
|
|
427
427
|
},
|
|
@@ -178,7 +178,7 @@
|
|
|
178
178
|
"required": [
|
|
179
179
|
"op"
|
|
180
180
|
],
|
|
181
|
-
"description": "Input operations: write_text, key, keyboard, joystick, release_all, state.\n\nOperations:\n- write_text: Send a text string to the keyboard buffer, with PETSCII token expansion. Required inputs: text. Optional inputs: delayMs.\n- key: Tap a single key or hold it for a duration. Required inputs: key. Optional inputs: durationMs, count.\n- joystick: Simulate joystick input. On C64U/U64 this uses machine:input
|
|
181
|
+
"description": "Input operations: write_text, key, keyboard, joystick, release_all, state.\n\nOperations:\n- write_text: Send a text string to the keyboard buffer, with PETSCII token expansion. Required inputs: text. Optional inputs: delayMs.\n- key: Tap a single key or hold it for a duration. Required inputs: key. Optional inputs: durationMs, count.\n- joystick: Simulate joystick input. On C64U/U64 this uses machine:input. On VICE it attaches VICE's I/O simulation joyport device while pressed; that device also pulls CIA lines 5-7 low, so port 1 produces phantom keys (e.g. SHIFT+C=) and port 2 reads $1E for up instead of $7E. Prefer port 2 on VICE and test bits 0-4. Required inputs: port, controls, action. Optional inputs: durationMs.\n- keyboard: Send physical C64 keyboard matrix events through machine:input (a C64U firmware version that provides it, or U64 3.15+). Required inputs: inputs, transition.\n- release_all: Release every key and joystick control injected through machine:input (a C64U firmware version that provides it, or U64 3.15+). Required inputs: none.\n- state: Read the keys and joystick controls held through machine:input (a C64U firmware version that provides it, or U64 3.15+). Required inputs: none.",
|
|
182
182
|
"additionalProperties": false,
|
|
183
183
|
"x-c64bridge-operations": [
|
|
184
184
|
{
|
|
@@ -274,7 +274,7 @@
|
|
|
274
274
|
},
|
|
275
275
|
{
|
|
276
276
|
"op": "joystick",
|
|
277
|
-
"description": "Simulate joystick input. On C64U/U64 this uses machine:input
|
|
277
|
+
"description": "Simulate joystick input. On C64U/U64 this uses machine:input. On VICE it attaches VICE's I/O simulation joyport device while pressed; that device also pulls CIA lines 5-7 low, so port 1 produces phantom keys (e.g. SHIFT+C=) and port 2 reads $1E for up instead of $7E. Prefer port 2 on VICE and test bits 0-4.",
|
|
278
278
|
"required": [
|
|
279
279
|
"port",
|
|
280
280
|
"controls",
|
|
@@ -285,7 +285,7 @@
|
|
|
285
285
|
],
|
|
286
286
|
"inputSchema": {
|
|
287
287
|
"type": "object",
|
|
288
|
-
"description": "Simulate joystick input. On C64U/U64 this uses machine:input
|
|
288
|
+
"description": "Simulate joystick input. On C64U/U64 this uses machine:input. On VICE it attaches VICE's I/O simulation joyport device while pressed; that device also pulls CIA lines 5-7 low, so port 1 produces phantom keys (e.g. SHIFT+C=) and port 2 reads $1E for up instead of $7E. Prefer port 2 on VICE and test bits 0-4.",
|
|
289
289
|
"properties": {
|
|
290
290
|
"op": {
|
|
291
291
|
"const": "joystick"
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"properties": {
|
|
61
61
|
"type": {
|
|
62
62
|
"type": "string",
|
|
63
|
-
"description": "Assertion type",
|
|
63
|
+
"description": "Assertion type; sid_silent needs the C64 Ultimate audio stream (c64u backend).",
|
|
64
64
|
"enum": [
|
|
65
65
|
"screen_contains",
|
|
66
66
|
"memory_equals",
|
|
@@ -462,7 +462,7 @@
|
|
|
462
462
|
"properties": {
|
|
463
463
|
"type": {
|
|
464
464
|
"type": "string",
|
|
465
|
-
"description": "Assertion type",
|
|
465
|
+
"description": "Assertion type; sid_silent needs the C64 Ultimate audio stream (c64u backend).",
|
|
466
466
|
"enum": [
|
|
467
467
|
"screen_contains",
|
|
468
468
|
"memory_equals",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
},
|
|
12
12
|
"name": {
|
|
13
13
|
"type": "string",
|
|
14
|
-
"description": "VICE resource name (example: SidEngine,
|
|
14
|
+
"description": "VICE resource name (example: SidEngine, SidModel).",
|
|
15
15
|
"minLength": 1,
|
|
16
16
|
"maxLength": 64,
|
|
17
17
|
"pattern": "^[A-Z][A-Za-z0-9_.-]{0,63}$"
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
},
|
|
47
47
|
"name": {
|
|
48
48
|
"type": "string",
|
|
49
|
-
"description": "VICE resource name (example: SidEngine,
|
|
49
|
+
"description": "VICE resource name (example: SidEngine, SidModel).",
|
|
50
50
|
"minLength": 1,
|
|
51
51
|
"maxLength": 64,
|
|
52
52
|
"pattern": "^[A-Z][A-Za-z0-9_.-]{0,63}$"
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
},
|
|
77
77
|
"name": {
|
|
78
78
|
"type": "string",
|
|
79
|
-
"description": "VICE resource name (example: SidEngine,
|
|
79
|
+
"description": "VICE resource name (example: SidEngine, SidModel).",
|
|
80
80
|
"minLength": 1,
|
|
81
81
|
"maxLength": 64,
|
|
82
82
|
"pattern": "^[A-Z][A-Za-z0-9_.-]{0,63}$"
|
package/mcp/server.json
CHANGED
package/mcp/tools.json
CHANGED
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
"properties": {
|
|
67
67
|
"type": {
|
|
68
68
|
"type": "string",
|
|
69
|
-
"description": "Assertion type",
|
|
69
|
+
"description": "Assertion type; sid_silent needs the C64 Ultimate audio stream (c64u backend).",
|
|
70
70
|
"enum": [
|
|
71
71
|
"screen_contains",
|
|
72
72
|
"memory_equals",
|
|
@@ -468,7 +468,7 @@
|
|
|
468
468
|
"properties": {
|
|
469
469
|
"type": {
|
|
470
470
|
"type": "string",
|
|
471
|
-
"description": "Assertion type",
|
|
471
|
+
"description": "Assertion type; sid_silent needs the C64 Ultimate audio stream (c64u backend).",
|
|
472
472
|
"enum": [
|
|
473
473
|
"screen_contains",
|
|
474
474
|
"memory_equals",
|
|
@@ -4483,7 +4483,7 @@
|
|
|
4483
4483
|
"integer",
|
|
4484
4484
|
"null"
|
|
4485
4485
|
],
|
|
4486
|
-
"description": "Track count (D64 supports 35
|
|
4486
|
+
"description": "Track count (D64 supports 35 to 41; DNP requires explicit tracks).",
|
|
4487
4487
|
"minimum": 1,
|
|
4488
4488
|
"maximum": 255
|
|
4489
4489
|
},
|
|
@@ -4785,7 +4785,7 @@
|
|
|
4785
4785
|
"integer",
|
|
4786
4786
|
"null"
|
|
4787
4787
|
],
|
|
4788
|
-
"description": "Track count (D64 supports 35
|
|
4788
|
+
"description": "Track count (D64 supports 35 to 41; DNP requires explicit tracks).",
|
|
4789
4789
|
"minimum": 1,
|
|
4790
4790
|
"maximum": 255
|
|
4791
4791
|
},
|
|
@@ -4949,6 +4949,10 @@
|
|
|
4949
4949
|
"vice"
|
|
4950
4950
|
],
|
|
4951
4951
|
"operationPlatforms": {
|
|
4952
|
+
"unmount": [
|
|
4953
|
+
"c64u",
|
|
4954
|
+
"u2"
|
|
4955
|
+
],
|
|
4952
4956
|
"file_info": [
|
|
4953
4957
|
"c64u",
|
|
4954
4958
|
"u2"
|
|
@@ -5721,6 +5725,14 @@
|
|
|
5721
5725
|
"description": "Specific configuration item inside the selected category (e.g. 'Volume').",
|
|
5722
5726
|
"minLength": 1
|
|
5723
5727
|
},
|
|
5728
|
+
"includeSecretValues": {
|
|
5729
|
+
"type": [
|
|
5730
|
+
"boolean",
|
|
5731
|
+
"null"
|
|
5732
|
+
],
|
|
5733
|
+
"description": "Return credential values (network password, FTP/Telnet passwords, Wi-Fi keys) in plain text. Default false: those values are replaced with ***.",
|
|
5734
|
+
"default": false
|
|
5735
|
+
},
|
|
5724
5736
|
"value": {
|
|
5725
5737
|
"description": "Primitive configuration value (string, number, or boolean).",
|
|
5726
5738
|
"type": [
|
|
@@ -5814,7 +5826,7 @@
|
|
|
5814
5826
|
"required": [
|
|
5815
5827
|
"op"
|
|
5816
5828
|
],
|
|
5817
|
-
"description": "Configuration operations available via the c64_config tool.\n\nOperations:\n- list: List configuration categories reported by the firmware. Required inputs: none.\n- get: Read a configuration category or specific item. Required inputs: category. Optional inputs: item.\n- set: Write a configuration value in the selected category. Required inputs: category, item, value.\n- batch_update: Apply multiple configuration updates in a single request. Required inputs: none.\n- load_flash: Load configuration from flash storage. Required inputs: none.\n- save_flash: Persist the current configuration to flash storage. Required inputs: none.\n- reset_defaults: Reset firmware configuration to factory defaults. Required inputs: none.\n- read_debugreg: Read the Ultimate debug register ($D7FF). Required inputs: none.\n- write_debugreg: Write a hex value to the Ultimate debug register ($D7FF). Required inputs: value.\n- info: Retrieve Ultimate hardware information and status. Required inputs: none.\n- version: Fetch firmware version details. Required inputs: none.\n- snapshot: Snapshot configuration to disk for later restore or diff. Required inputs: path.\n- restore: Restore configuration from a snapshot file. Required inputs: path. Optional inputs: applyToFlash.\n- diff: Compare the current configuration with a snapshot. Required inputs: path.\n- shuffle: Discover PRG/CRT files and run each with optional screen capture. Required inputs: none. Optional inputs: root, extensions, durationMs, captureScreen, maxPrograms, outputPath, resetDelayMs.",
|
|
5829
|
+
"description": "Configuration operations available via the c64_config tool.\n\nOperations:\n- list: List configuration categories reported by the firmware. Required inputs: none.\n- get: Read a configuration category or specific item. Required inputs: category. Optional inputs: item, includeSecretValues.\n- set: Write a configuration value in the selected category. Required inputs: category, item, value.\n- batch_update: Apply multiple configuration updates in a single request. Required inputs: none.\n- load_flash: Load configuration from flash storage. Required inputs: none.\n- save_flash: Persist the current configuration to flash storage. Required inputs: none.\n- reset_defaults: Reset firmware configuration to factory defaults. Required inputs: none.\n- read_debugreg: Read the Ultimate debug register ($D7FF). Required inputs: none.\n- write_debugreg: Write a hex value to the Ultimate debug register ($D7FF). Required inputs: value.\n- info: Retrieve Ultimate hardware information and status. Required inputs: none.\n- version: Fetch firmware version details. Required inputs: none.\n- snapshot: Snapshot configuration to disk for later restore or diff. Required inputs: path.\n- restore: Restore configuration from a snapshot file. Required inputs: path. Optional inputs: applyToFlash.\n- diff: Compare the current configuration with a snapshot. Required inputs: path.\n- shuffle: Discover PRG/CRT files and run each with optional screen capture. Required inputs: none. Optional inputs: root, extensions, durationMs, captureScreen, maxPrograms, outputPath, resetDelayMs.",
|
|
5818
5830
|
"additionalProperties": false,
|
|
5819
5831
|
"x-c64bridge-operations": [
|
|
5820
5832
|
{
|
|
@@ -5843,7 +5855,8 @@
|
|
|
5843
5855
|
"category"
|
|
5844
5856
|
],
|
|
5845
5857
|
"optional": [
|
|
5846
|
-
"item"
|
|
5858
|
+
"item",
|
|
5859
|
+
"includeSecretValues"
|
|
5847
5860
|
],
|
|
5848
5861
|
"inputSchema": {
|
|
5849
5862
|
"type": "object",
|
|
@@ -5862,6 +5875,14 @@
|
|
|
5862
5875
|
"description": "Specific configuration item inside the selected category (e.g. 'Volume').",
|
|
5863
5876
|
"minLength": 1
|
|
5864
5877
|
},
|
|
5878
|
+
"includeSecretValues": {
|
|
5879
|
+
"type": [
|
|
5880
|
+
"boolean",
|
|
5881
|
+
"null"
|
|
5882
|
+
],
|
|
5883
|
+
"description": "Return credential values (network password, FTP/Telnet passwords, Wi-Fi keys) in plain text. Default false: those values are replaced with ***.",
|
|
5884
|
+
"default": false
|
|
5885
|
+
},
|
|
5865
5886
|
"op": {
|
|
5866
5887
|
"const": "get"
|
|
5867
5888
|
}
|
|
@@ -8075,7 +8096,7 @@
|
|
|
8075
8096
|
},
|
|
8076
8097
|
"name": {
|
|
8077
8098
|
"type": "string",
|
|
8078
|
-
"description": "VICE resource name (example: SidEngine,
|
|
8099
|
+
"description": "VICE resource name (example: SidEngine, SidModel).",
|
|
8079
8100
|
"minLength": 1,
|
|
8080
8101
|
"maxLength": 64,
|
|
8081
8102
|
"pattern": "^[A-Z][A-Za-z0-9_.-]{0,63}$"
|
|
@@ -8110,7 +8131,7 @@
|
|
|
8110
8131
|
},
|
|
8111
8132
|
"name": {
|
|
8112
8133
|
"type": "string",
|
|
8113
|
-
"description": "VICE resource name (example: SidEngine,
|
|
8134
|
+
"description": "VICE resource name (example: SidEngine, SidModel).",
|
|
8114
8135
|
"minLength": 1,
|
|
8115
8136
|
"maxLength": 64,
|
|
8116
8137
|
"pattern": "^[A-Z][A-Za-z0-9_.-]{0,63}$"
|
|
@@ -8140,7 +8161,7 @@
|
|
|
8140
8161
|
},
|
|
8141
8162
|
"name": {
|
|
8142
8163
|
"type": "string",
|
|
8143
|
-
"description": "VICE resource name (example: SidEngine,
|
|
8164
|
+
"description": "VICE resource name (example: SidEngine, SidModel).",
|
|
8144
8165
|
"minLength": 1,
|
|
8145
8166
|
"maxLength": 64,
|
|
8146
8167
|
"pattern": "^[A-Z][A-Za-z0-9_.-]{0,63}$"
|
|
@@ -8392,7 +8413,7 @@
|
|
|
8392
8413
|
"required": [
|
|
8393
8414
|
"op"
|
|
8394
8415
|
],
|
|
8395
|
-
"description": "Input operations: write_text, key, keyboard, joystick, release_all, state.\n\nOperations:\n- write_text: Send a text string to the keyboard buffer, with PETSCII token expansion. Required inputs: text. Optional inputs: delayMs.\n- key: Tap a single key or hold it for a duration. Required inputs: key. Optional inputs: durationMs, count.\n- joystick: Simulate joystick input. On C64U/U64 this uses machine:input
|
|
8416
|
+
"description": "Input operations: write_text, key, keyboard, joystick, release_all, state.\n\nOperations:\n- write_text: Send a text string to the keyboard buffer, with PETSCII token expansion. Required inputs: text. Optional inputs: delayMs.\n- key: Tap a single key or hold it for a duration. Required inputs: key. Optional inputs: durationMs, count.\n- joystick: Simulate joystick input. On C64U/U64 this uses machine:input. On VICE it attaches VICE's I/O simulation joyport device while pressed; that device also pulls CIA lines 5-7 low, so port 1 produces phantom keys (e.g. SHIFT+C=) and port 2 reads $1E for up instead of $7E. Prefer port 2 on VICE and test bits 0-4. Required inputs: port, controls, action. Optional inputs: durationMs.\n- keyboard: Send physical C64 keyboard matrix events through machine:input (a C64U firmware version that provides it, or U64 3.15+). Required inputs: inputs, transition.\n- release_all: Release every key and joystick control injected through machine:input (a C64U firmware version that provides it, or U64 3.15+). Required inputs: none.\n- state: Read the keys and joystick controls held through machine:input (a C64U firmware version that provides it, or U64 3.15+). Required inputs: none.",
|
|
8396
8417
|
"additionalProperties": false,
|
|
8397
8418
|
"x-c64bridge-operations": [
|
|
8398
8419
|
{
|
|
@@ -8488,7 +8509,7 @@
|
|
|
8488
8509
|
},
|
|
8489
8510
|
{
|
|
8490
8511
|
"op": "joystick",
|
|
8491
|
-
"description": "Simulate joystick input. On C64U/U64 this uses machine:input
|
|
8512
|
+
"description": "Simulate joystick input. On C64U/U64 this uses machine:input. On VICE it attaches VICE's I/O simulation joyport device while pressed; that device also pulls CIA lines 5-7 low, so port 1 produces phantom keys (e.g. SHIFT+C=) and port 2 reads $1E for up instead of $7E. Prefer port 2 on VICE and test bits 0-4.",
|
|
8492
8513
|
"required": [
|
|
8493
8514
|
"port",
|
|
8494
8515
|
"controls",
|
|
@@ -8499,7 +8520,7 @@
|
|
|
8499
8520
|
],
|
|
8500
8521
|
"inputSchema": {
|
|
8501
8522
|
"type": "object",
|
|
8502
|
-
"description": "Simulate joystick input. On C64U/U64 this uses machine:input
|
|
8523
|
+
"description": "Simulate joystick input. On C64U/U64 this uses machine:input. On VICE it attaches VICE's I/O simulation joyport device while pressed; that device also pulls CIA lines 5-7 low, so port 1 produces phantom keys (e.g. SHIFT+C=) and port 2 reads $1E for up instead of $7E. Prefer port 2 on VICE and test bits 0-4.",
|
|
8503
8524
|
"properties": {
|
|
8504
8525
|
"op": {
|
|
8505
8526
|
"const": "joystick"
|
package/mcp.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "c64bridge",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
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": {
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
"date-fns": "^4.4.0",
|
|
74
74
|
"fastify": "^5.12.5",
|
|
75
75
|
"jimp": "^1.6.1",
|
|
76
|
-
"meyda": "
|
|
76
|
+
"meyda": "5.6.2",
|
|
77
77
|
"octokit": "^5.0.5",
|
|
78
78
|
"pitchfinder": "^2.3.4",
|
|
79
79
|
"reflect-metadata": "^0.2.2",
|
|
@@ -82,6 +82,9 @@
|
|
|
82
82
|
"optionalDependencies": {
|
|
83
83
|
"naudiodon": "^2.3.6"
|
|
84
84
|
},
|
|
85
|
+
"overrides": {
|
|
86
|
+
"zod": "^4.6.6"
|
|
87
|
+
},
|
|
85
88
|
"devDependencies": {
|
|
86
89
|
"@types/node": "^25.9.5",
|
|
87
90
|
"tsx": "^4.23.11",
|