c64bridge 0.9.2 → 1.0.0
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 +24 -3
- package/README.md +271 -161
- package/data/basic/rom-routines.md +269 -0
- package/data/context/bootstrap.md +1 -1
- package/data/io/cia-spec.md +2 -2
- package/data/{memory/kernal-memory-map.md → kernal/rom-routines.md} +3 -2
- package/data/memory/low-memory-map.md +1 -0
- package/data/memory/memory-map.md +13 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/c64Client.js +127 -12
- package/dist/config.js +34 -8
- package/dist/device.js +75 -45
- package/dist/mcp-server.js +28 -8
- package/dist/platform.js +13 -2
- package/dist/rag/knowledgeIndex.js +54 -9
- package/dist/rag/resourceUris.js +2 -0
- package/dist/tools/audio.js +1 -1
- package/dist/tools/developer.js +6 -6
- package/dist/tools/graphics.js +4 -4
- package/dist/tools/input.js +129 -12
- package/dist/tools/machineControl.js +85 -6
- package/dist/tools/memory.js +1 -1
- package/dist/tools/meta/index.js +1 -1
- package/dist/tools/programRunners.js +5 -5
- package/dist/tools/rag.js +1 -1
- package/dist/tools/registry/batch.js +1 -1
- package/dist/tools/registry/config.js +5 -5
- package/dist/tools/registry/disk.js +2 -2
- package/dist/tools/registry/drive.js +2 -2
- package/dist/tools/registry/graphics.js +2 -2
- package/dist/tools/registry/memory.js +2 -2
- package/dist/tools/registry/platform.js +7 -5
- package/dist/tools/registry/program.js +5 -5
- package/dist/tools/registry/rag.js +1 -1
- package/dist/tools/registry/sound.js +4 -4
- package/dist/tools/registry/system.js +13 -3
- package/dist/tools/storage.js +1 -1
- package/doc/c64u/c64-openapi.yaml +1130 -774
- package/doc/developer.md +3 -2
- package/doc/u2/u2-openapi.yaml +1192 -0
- package/generated/c64/index.js +142 -9
- package/generated/openapi/c64u/index.js +1117 -0
- package/generated/openapi/u2/index.js +929 -0
- package/mcp/resources.json +60 -7
- package/mcp/schemas/c64_graphics.schema.json +3 -3
- package/mcp/schemas/c64_input.schema.json +247 -6
- package/mcp/schemas/c64_memory.schema.json +3 -3
- package/mcp/schemas/c64_program.schema.json +3 -3
- package/mcp/schemas/c64_select_backend.schema.json +2 -0
- package/mcp/schemas/c64_system.schema.json +41 -1
- package/mcp/server.json +1 -1
- package/mcp/tools.json +382 -41
- package/mcp.json +21 -5
- package/package.json +14 -8
- package/scripts/mockC64Server.mjs +40 -0
- package/scripts/prepare-release.mjs +13 -2
- package/scripts/update-readme.ts +19 -30
- package/server.json +2 -2
package/mcp.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "c64bridge",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
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",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"default": "~/.c64bridge.json"
|
|
38
38
|
},
|
|
39
39
|
"C64_MODE": {
|
|
40
|
-
"description": "Select active backend (c64u
|
|
40
|
+
"description": "Select active backend (c64u/U64 hardware, u2/U2-family cartridge, or vice emulator)",
|
|
41
41
|
"default": "c64u"
|
|
42
42
|
},
|
|
43
43
|
"C64U_HOST": {
|
|
@@ -52,6 +52,18 @@
|
|
|
52
52
|
"description": "Override the C64 Ultimate network password sent as X-Password",
|
|
53
53
|
"default": ""
|
|
54
54
|
},
|
|
55
|
+
"U2_HOST": {
|
|
56
|
+
"description": "Override the U2/U2+/U2+L host name or IP address when C64_MODE=u2",
|
|
57
|
+
"default": ""
|
|
58
|
+
},
|
|
59
|
+
"U2_PORT": {
|
|
60
|
+
"description": "Override the U2-family REST port when C64_MODE=u2",
|
|
61
|
+
"default": ""
|
|
62
|
+
},
|
|
63
|
+
"U2_PASSWORD": {
|
|
64
|
+
"description": "Override the U2-family network password sent as X-Password when C64_MODE=u2",
|
|
65
|
+
"default": ""
|
|
66
|
+
},
|
|
55
67
|
"C64BRIDGE_DIAGNOSTICS_DIR": {
|
|
56
68
|
"description": "Override the directory where persistent MCP diagnostics files are written",
|
|
57
69
|
"default": "~/.c64bridge/diagnostics"
|
|
@@ -129,7 +141,7 @@
|
|
|
129
141
|
"default": "6502"
|
|
130
142
|
},
|
|
131
143
|
"VICE_VISIBLE": {
|
|
132
|
-
"description": "Launch VICE
|
|
144
|
+
"description": "Launch VICE as a desktop window when true; use headless/Xvfb managed launch when false",
|
|
133
145
|
"default": "true"
|
|
134
146
|
},
|
|
135
147
|
"VICE_WARP": {
|
|
@@ -137,15 +149,19 @@
|
|
|
137
149
|
"default": "false when visible, true when headless"
|
|
138
150
|
},
|
|
139
151
|
"VICE_ARGS": {
|
|
140
|
-
"description": "Extra command-line arguments forwarded to managed VICE launches",
|
|
152
|
+
"description": "Extra command-line arguments forwarded to managed VICE launches, such as -minimized for a visible window that starts iconified",
|
|
141
153
|
"default": ""
|
|
142
154
|
},
|
|
155
|
+
"VICE_PREWARM": {
|
|
156
|
+
"description": "Set to 1 to start/connect VICE in the background during MCP startup; disabled by default so VICE starts lazily on first use",
|
|
157
|
+
"default": "0"
|
|
158
|
+
},
|
|
143
159
|
"VICE_RUN_TIMEOUT_MS": {
|
|
144
160
|
"description": "Timeout for headless VICE runs in milliseconds",
|
|
145
161
|
"default": "10000"
|
|
146
162
|
},
|
|
147
163
|
"FORCE_XVFB": {
|
|
148
|
-
"description": "Set to 1 to force managed VICE launches to
|
|
164
|
+
"description": "Set to 1 to force managed VICE launches to use Xvfb even when a graphical session is detected",
|
|
149
165
|
"default": "0"
|
|
150
166
|
},
|
|
151
167
|
"DISABLE_XVFB": {
|
package/package.json
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "c64bridge",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"mcpName": "io.github.chrisgleissner/c64bridge",
|
|
5
5
|
"description": "C64 Bridge MCP server for Commodore 64 Ultimate hardware and VICE emulator control",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/chrisgleissner/c64bridge.git"
|
|
9
|
+
},
|
|
6
10
|
"type": "module",
|
|
7
11
|
"main": "dist/index.js",
|
|
8
12
|
"packageManager": "bun@1.3.1",
|
|
@@ -34,7 +38,9 @@
|
|
|
34
38
|
"check": "npm run build && npm test",
|
|
35
39
|
"check:node-compat": "node scripts/check-node-compat.mjs",
|
|
36
40
|
"build:rag": "node scripts/invoke-bun.mjs scripts/rag-rebuild.mjs",
|
|
37
|
-
"api:generate": "
|
|
41
|
+
"api:generate": "npm run api:generate:c64u && npm run api:generate:u2",
|
|
42
|
+
"api:generate:c64u": "npx --yes swagger-typescript-api@12.0.4 -p doc/c64u/c64-openapi.yaml -o generated/openapi/c64u -n index.ts --axios --single-http-client --module-name-index 0",
|
|
43
|
+
"api:generate:u2": "npx --yes swagger-typescript-api@12.0.4 -p doc/u2/u2-openapi.yaml -o generated/openapi/u2 -n index.ts --axios --single-http-client --module-name-index 0",
|
|
38
44
|
"c64:tool": "bash scripts/c64-tool.sh",
|
|
39
45
|
"rag:rebuild": "node scripts/invoke-bun.mjs scripts/rag-rebuild.mjs",
|
|
40
46
|
"rag:fetch": "node scripts/fetch-external-sources.mjs",
|
|
@@ -63,22 +69,22 @@
|
|
|
63
69
|
},
|
|
64
70
|
"dependencies": {
|
|
65
71
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
66
|
-
"axios": "^1.
|
|
67
|
-
"date-fns": "^4.
|
|
68
|
-
"fastify": "^5.
|
|
72
|
+
"axios": "^1.18.1",
|
|
73
|
+
"date-fns": "^4.4.0",
|
|
74
|
+
"fastify": "^5.10.0",
|
|
69
75
|
"jimp": "^1.6.1",
|
|
70
76
|
"meyda": "^5.6.3",
|
|
71
77
|
"octokit": "^5.0.5",
|
|
72
78
|
"pitchfinder": "^2.3.4",
|
|
73
79
|
"reflect-metadata": "^0.2.2",
|
|
74
|
-
"yaml": "^2.
|
|
80
|
+
"yaml": "^2.9.0"
|
|
75
81
|
},
|
|
76
82
|
"optionalDependencies": {
|
|
77
83
|
"naudiodon": "^2.3.6"
|
|
78
84
|
},
|
|
79
85
|
"devDependencies": {
|
|
80
|
-
"@types/node": "^25.
|
|
81
|
-
"tsx": "^4.
|
|
86
|
+
"@types/node": "^25.9.5",
|
|
87
|
+
"tsx": "^4.23.0",
|
|
82
88
|
"typescript": "^6.0.3"
|
|
83
89
|
},
|
|
84
90
|
"files": [
|
|
@@ -112,6 +112,8 @@ function createInitialState() {
|
|
|
112
112
|
lastConfigAction: null,
|
|
113
113
|
menuToggleCount: 0,
|
|
114
114
|
lastMenuTarget: null,
|
|
115
|
+
menuScreen: Buffer.from([0x41, 0x01, 0x42, 0x02]),
|
|
116
|
+
inputState: { keyboard: { inputs: [] }, joysticks: [{ port: 1, inputs: [] }, { port: 2, inputs: [] }] },
|
|
115
117
|
streams: {
|
|
116
118
|
video: { active: false, target: null, packetsSent: 0 },
|
|
117
119
|
audio: { active: false, target: null, packetsSent: 0 },
|
|
@@ -331,6 +333,44 @@ export async function startMockC64Server(options = {}) {
|
|
|
331
333
|
return;
|
|
332
334
|
}
|
|
333
335
|
|
|
336
|
+
if (method === "GET" && url === "/v1/machine:menu_screen") {
|
|
337
|
+
res.setHeader("Content-Type", "application/octet-stream");
|
|
338
|
+
res.end(state.menuScreen);
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (method === "GET" && url === "/v1/machine:input") {
|
|
343
|
+
sendJson(res, state.inputState);
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (method === "POST" && url === "/v1/machine:input") {
|
|
348
|
+
const batch = await readJson(req);
|
|
349
|
+
for (const event of batch.events ?? []) {
|
|
350
|
+
if (event.kind === "release_all") {
|
|
351
|
+
state.inputState.keyboard.inputs = [];
|
|
352
|
+
state.inputState.joysticks.forEach((joystick) => { joystick.inputs = []; });
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
const target = event.kind === "keyboard"
|
|
356
|
+
? state.inputState.keyboard
|
|
357
|
+
: state.inputState.joysticks.find((joystick) => joystick.port === event.port);
|
|
358
|
+
if (!target) continue;
|
|
359
|
+
const inputs = new Set(target.inputs);
|
|
360
|
+
for (const input of event.inputs ?? []) {
|
|
361
|
+
if (event.transition === "release") inputs.delete(input);
|
|
362
|
+
else if (event.transition === "press") inputs.add(input);
|
|
363
|
+
else if (event.transition === "tap") {
|
|
364
|
+
inputs.add(input);
|
|
365
|
+
inputs.delete(input);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
target.inputs = [...inputs];
|
|
369
|
+
}
|
|
370
|
+
sendJson(res, state.inputState);
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
|
|
334
374
|
if (method === "GET" && url === "/v1/machine:debugreg") {
|
|
335
375
|
sendJson(res, { value: state.debugreg ?? "00" });
|
|
336
376
|
return;
|
|
@@ -26,8 +26,19 @@ const fileExists = async (relativePath) => {
|
|
|
26
26
|
};
|
|
27
27
|
|
|
28
28
|
const getRepositoryMetadata = (repository) => {
|
|
29
|
-
const
|
|
30
|
-
if (!
|
|
29
|
+
const rawUrl = typeof repository === 'string' ? repository : repository?.url;
|
|
30
|
+
if (!rawUrl) {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let url = rawUrl.trim();
|
|
35
|
+
if (url.startsWith('git+')) {
|
|
36
|
+
url = url.slice(4);
|
|
37
|
+
}
|
|
38
|
+
if (url.endsWith('.git')) {
|
|
39
|
+
url = url.slice(0, -4);
|
|
40
|
+
}
|
|
41
|
+
if (!/^https?:\/\//.test(url)) {
|
|
31
42
|
return undefined;
|
|
32
43
|
}
|
|
33
44
|
|
package/scripts/update-readme.ts
CHANGED
|
@@ -27,6 +27,7 @@ type ManifestEnvEntry = {
|
|
|
27
27
|
type EnvCategory =
|
|
28
28
|
| "Server Runtime"
|
|
29
29
|
| "C64 Ultimate"
|
|
30
|
+
| "U2-family"
|
|
30
31
|
| "VICE Runtime"
|
|
31
32
|
| "VICE Audio Capture"
|
|
32
33
|
| "SID Playback"
|
|
@@ -55,6 +56,9 @@ function classifyEnvVariable(name: string): EnvCategory {
|
|
|
55
56
|
if (name.startsWith("C64U_")) {
|
|
56
57
|
return "C64 Ultimate";
|
|
57
58
|
}
|
|
59
|
+
if (name.startsWith("U2_")) {
|
|
60
|
+
return "U2-family";
|
|
61
|
+
}
|
|
58
62
|
if (name.startsWith("VICE_") || name === "FORCE_XVFB" || name === "DISABLE_XVFB") {
|
|
59
63
|
if (name === "VICE_MODE" || name === "VICE_LIMIT_CYCLES" || name === "VICE_RUN_TIMEOUT_MS") {
|
|
60
64
|
return "VICE Audio Capture";
|
|
@@ -79,6 +83,9 @@ function resolveJsonConfigKey(name: string): string {
|
|
|
79
83
|
C64U_HOST: "c64u.host",
|
|
80
84
|
C64U_PORT: "c64u.port",
|
|
81
85
|
C64U_PASSWORD: "c64u.networkPassword",
|
|
86
|
+
U2_HOST: "u2.host",
|
|
87
|
+
U2_PORT: "u2.port",
|
|
88
|
+
U2_PASSWORD: "u2.networkPassword",
|
|
82
89
|
VICE_BINARY: "vice.exe",
|
|
83
90
|
VICE_DIRECTORY: "vice.directory",
|
|
84
91
|
VICE_HOST: "vice.host",
|
|
@@ -86,6 +93,7 @@ function resolveJsonConfigKey(name: string): string {
|
|
|
86
93
|
VICE_VISIBLE: "vice.visible",
|
|
87
94
|
VICE_WARP: "vice.warp",
|
|
88
95
|
VICE_ARGS: "vice.args",
|
|
96
|
+
VICE_PREWARM: "vice.prewarm",
|
|
89
97
|
};
|
|
90
98
|
return map[name] ?? "—";
|
|
91
99
|
}
|
|
@@ -103,6 +111,7 @@ export function renderEnvironmentSection(envEntries: Readonly<Record<string, Man
|
|
|
103
111
|
const orderedCategories: readonly EnvCategory[] = [
|
|
104
112
|
"Server Runtime",
|
|
105
113
|
"C64 Ultimate",
|
|
114
|
+
"U2-family",
|
|
106
115
|
"VICE Runtime",
|
|
107
116
|
"VICE Audio Capture",
|
|
108
117
|
"SID Playback",
|
|
@@ -144,7 +153,6 @@ type GroupedOperation = {
|
|
|
144
153
|
readonly required: readonly string[];
|
|
145
154
|
readonly optional: readonly string[];
|
|
146
155
|
readonly properties: Readonly<Record<string, JsonSchema | undefined>>;
|
|
147
|
-
readonly notes: readonly string[];
|
|
148
156
|
};
|
|
149
157
|
|
|
150
158
|
type GroupedToolInfo = {
|
|
@@ -167,6 +175,11 @@ function toTableValue(values: readonly string[]): string {
|
|
|
167
175
|
return values.join(", ");
|
|
168
176
|
}
|
|
169
177
|
|
|
178
|
+
function withVerificationNote(description: string, properties: Readonly<Record<string, JsonSchema | undefined>>): string {
|
|
179
|
+
const supportsVerification = Object.keys(properties).some((name) => name.toLowerCase().startsWith("verify"));
|
|
180
|
+
return supportsVerification ? `${description} Supports verification.` : description;
|
|
181
|
+
}
|
|
182
|
+
|
|
170
183
|
function formatInputName(name: string, schema?: JsonSchema): string {
|
|
171
184
|
const defaultSuffix = schema && Object.prototype.hasOwnProperty.call(schema, "default")
|
|
172
185
|
? `=${JSON.stringify(schema.default)}`
|
|
@@ -228,21 +241,12 @@ function collectFromOperationMetadata(schema: JsonSchema): readonly GroupedOpera
|
|
|
228
241
|
? entry.optional.filter((name): name is string => typeof name === "string" && name !== "op")
|
|
229
242
|
: Object.keys(properties).filter((name) => name !== "op" && !requiredProps.includes(name));
|
|
230
243
|
|
|
231
|
-
const notes: string[] = [];
|
|
232
|
-
const hasVerificationProperty = Object.keys(properties).some((name) =>
|
|
233
|
-
name.toLowerCase().startsWith("verify"),
|
|
234
|
-
);
|
|
235
|
-
if (hasVerificationProperty) {
|
|
236
|
-
notes.push("supports verify");
|
|
237
|
-
}
|
|
238
|
-
|
|
239
244
|
operations.push({
|
|
240
245
|
op: opName,
|
|
241
|
-
description: getString(entry.description, `Operation ${opName}`),
|
|
246
|
+
description: withVerificationNote(getString(entry.description, `Operation ${opName}`), properties),
|
|
242
247
|
required: requiredProps,
|
|
243
248
|
optional: optionalProps,
|
|
244
249
|
properties,
|
|
245
|
-
notes,
|
|
246
250
|
});
|
|
247
251
|
}
|
|
248
252
|
|
|
@@ -281,21 +285,12 @@ function collectFromVariantList(variants: readonly JsonSchema[]): readonly Group
|
|
|
281
285
|
const description = getString(typedVariant.description, getString(opSchema?.description, `Operation ${opName}`));
|
|
282
286
|
const requiredProps = ((typedVariant.required as readonly string[] | undefined) ?? []).filter((name) => name !== "op");
|
|
283
287
|
|
|
284
|
-
const notes: string[] = [];
|
|
285
|
-
const hasVerificationProperty = Object.keys(properties).some((name) =>
|
|
286
|
-
name.toLowerCase().startsWith("verify"),
|
|
287
|
-
);
|
|
288
|
-
if (hasVerificationProperty) {
|
|
289
|
-
notes.push("supports verify");
|
|
290
|
-
}
|
|
291
|
-
|
|
292
288
|
operations.push({
|
|
293
289
|
op: opName,
|
|
294
|
-
description,
|
|
290
|
+
description: withVerificationNote(description, properties),
|
|
295
291
|
required: requiredProps,
|
|
296
292
|
optional: Object.keys(properties).filter((name) => name !== "op" && !requiredProps.includes(name)),
|
|
297
293
|
properties,
|
|
298
|
-
notes,
|
|
299
294
|
});
|
|
300
295
|
}
|
|
301
296
|
|
|
@@ -320,18 +315,12 @@ function collectFromFlattenedSchema(schema: JsonSchema): readonly GroupedOperati
|
|
|
320
315
|
}
|
|
321
316
|
|
|
322
317
|
const descriptionMap = parseOperationDescriptions(getString(schema.description));
|
|
323
|
-
const hasVerificationProperty = Object.keys(properties).some((name) =>
|
|
324
|
-
name !== "op" && name.toLowerCase().startsWith("verify"),
|
|
325
|
-
);
|
|
326
|
-
const notes = hasVerificationProperty ? ["supports verify"] : [];
|
|
327
|
-
|
|
328
318
|
return opNames.map((opName) => ({
|
|
329
319
|
op: opName,
|
|
330
|
-
description: descriptionMap.get(opName) ?? `Operation ${opName}`,
|
|
320
|
+
description: withVerificationNote(descriptionMap.get(opName) ?? `Operation ${opName}`, properties),
|
|
331
321
|
required: [] as readonly string[],
|
|
332
322
|
optional: Object.keys(properties).filter((name) => name !== "op"),
|
|
333
323
|
properties,
|
|
334
|
-
notes,
|
|
335
324
|
}));
|
|
336
325
|
}
|
|
337
326
|
|
|
@@ -390,13 +379,13 @@ export function renderToolsSection(modules: readonly ToolModuleDescriptor[] = de
|
|
|
390
379
|
escapeCell(operation.description),
|
|
391
380
|
escapeCell(formatInputs(operation.required, operation.properties)),
|
|
392
381
|
escapeCell(formatInputs(operation.optional, operation.properties)),
|
|
393
|
-
operation.notes.length ? escapeCell(operation.notes.join(", ")) : "—",
|
|
394
382
|
effectivePlatforms.includes("c64u") ? "✅" : "",
|
|
383
|
+
effectivePlatforms.includes("u2") ? "✅" : "",
|
|
395
384
|
effectivePlatforms.includes("vice") ? "✅" : "",
|
|
396
385
|
];
|
|
397
386
|
});
|
|
398
387
|
|
|
399
|
-
lines.push(renderTable(["Operation", "Description", "Required Inputs", "Optional Inputs", "
|
|
388
|
+
lines.push(renderTable(["Operation", "Description", "Required Inputs", "Optional Inputs", "C64U", "U2", "VICE"], operationRows));
|
|
400
389
|
}
|
|
401
390
|
|
|
402
391
|
lines.push("");
|
package/server.json
CHANGED
|
@@ -8,12 +8,12 @@
|
|
|
8
8
|
"source": "github"
|
|
9
9
|
},
|
|
10
10
|
"websiteUrl": "https://github.com/chrisgleissner/c64bridge",
|
|
11
|
-
"version": "0.
|
|
11
|
+
"version": "1.0.0",
|
|
12
12
|
"packages": [
|
|
13
13
|
{
|
|
14
14
|
"registryType": "npm",
|
|
15
15
|
"identifier": "c64bridge",
|
|
16
|
-
"version": "0.
|
|
16
|
+
"version": "1.0.0",
|
|
17
17
|
"transport": {
|
|
18
18
|
"type": "stdio"
|
|
19
19
|
},
|