c64bridge 1.0.6 → 1.1.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/.github/skills/assembly-program/SKILL.md +37 -0
- package/.github/skills/basic-program/SKILL.md +40 -0
- package/.github/skills/cross-platform-demo/SKILL.md +32 -0
- package/.github/skills/drive-manager/SKILL.md +33 -0
- package/.github/skills/graphics-demo/SKILL.md +30 -0
- package/.github/skills/hello-world/SKILL.md +36 -0
- package/.github/skills/memory-debug/SKILL.md +32 -0
- package/.github/skills/pr-converge/SKILL.md +33 -0
- package/.github/skills/printer-job/SKILL.md +30 -0
- package/.github/skills/sid-music/SKILL.md +35 -0
- package/.github/skills/software-launch/SKILL.md +31 -0
- package/.github/skills/stream-control/SKILL.md +29 -0
- package/.github/skills/system-control/SKILL.md +32 -0
- package/AGENTS.md +28 -7
- package/README.md +70 -15
- package/data/assembly/assembly-spec.md +2 -0
- package/dist/c64Client.js +238 -60
- package/dist/chargen.js +0 -4
- package/dist/config.js +53 -28
- package/dist/device.js +92 -37
- package/dist/diagnostics.js +66 -5
- package/dist/hostPaths.js +128 -0
- package/dist/logger.js +3 -2
- package/dist/loggingHttpClient.js +71 -2
- package/dist/mcp-server.js +416 -291
- package/dist/numericArg.js +55 -0
- package/dist/petscii.js +13 -6
- package/dist/platform.js +12 -3
- package/dist/prompts/registry.js +6 -1
- package/dist/redact.js +166 -0
- package/dist/tools/access.js +66 -0
- package/dist/tools/debug.js +6 -0
- package/dist/tools/developer.js +77 -36
- package/dist/tools/errors.js +54 -3
- package/dist/tools/input.js +25 -8
- package/dist/tools/machineControl.js +113 -21
- package/dist/tools/memory.js +135 -30
- package/dist/tools/meta/artifacts.js +5 -5
- package/dist/tools/meta/background.js +216 -54
- package/dist/tools/meta/compilation.js +6 -5
- package/dist/tools/meta/config.js +20 -10
- package/dist/tools/meta/configInventory.js +25 -2
- package/dist/tools/meta/diagnostics.js +2 -1
- package/dist/tools/meta/filesystem.js +16 -14
- package/dist/tools/meta/graphics.js +48 -20
- package/dist/tools/meta/memory.js +45 -19
- package/dist/tools/meta/program.js +10 -7
- package/dist/tools/meta/screen.js +116 -25
- package/dist/tools/pauseGuard.js +23 -0
- package/dist/tools/pollValidator.js +53 -8
- package/dist/tools/programRunners.js +62 -18
- package/dist/tools/registry/batch.js +17 -11
- package/dist/tools/registry/config.js +9 -2
- package/dist/tools/registry/disk.js +12 -2
- package/dist/tools/registry/drive.js +3 -0
- package/dist/tools/registry/extract.js +3 -0
- package/dist/tools/registry/graphics.js +59 -6
- package/dist/tools/registry/index.js +17 -1
- package/dist/tools/registry/memory.js +11 -1
- package/dist/tools/registry/platform.js +4 -1
- package/dist/tools/registry/printer.js +2 -1
- package/dist/tools/registry/program.js +5 -1
- package/dist/tools/registry/rag.js +1 -0
- package/dist/tools/registry/sound.js +12 -1
- package/dist/tools/registry/stream.js +2 -1
- package/dist/tools/registry/system.js +8 -1
- package/dist/tools/schema.js +7 -7
- package/dist/tools/streaming.js +34 -4
- package/dist/tools/translation/assembler.js +247 -24
- package/dist/tools/translation/basicDetokenizer.js +80 -0
- package/dist/tools/translation/basicTokenizer.js +8 -3
- package/dist/tools/translation/disassembler.js +14 -0
- package/dist/tools/types.js +258 -7
- package/dist/tools/vice.js +5 -16
- package/dist/vicBitmap.js +1 -1
- package/dist/vice/mockServer.js +3 -0
- package/dist/vice/process.js +19 -0
- package/dist/vice/resourcePolicy.js +36 -0
- package/dist/vice/viceClient.js +22 -5
- package/doc/vice/support-matrix.md +17 -11
- package/mcp/schemas/c64_batch.schema.json +2 -1
- package/mcp/schemas/c64_config.schema.json +33 -466
- package/mcp/schemas/c64_debug.schema.json +3 -602
- package/mcp/schemas/c64_disk.schema.json +17 -344
- package/mcp/schemas/c64_drive.schema.json +2 -157
- package/mcp/schemas/c64_extract.schema.json +10 -364
- package/mcp/schemas/c64_graphics.schema.json +19 -426
- package/mcp/schemas/c64_input.schema.json +5 -318
- package/mcp/schemas/c64_memory.schema.json +23 -491
- package/mcp/schemas/c64_printer.schema.json +3 -258
- package/mcp/schemas/c64_program.schema.json +16 -491
- package/mcp/schemas/c64_rag.schema.json +1 -81
- package/mcp/schemas/c64_select_backend.schema.json +2 -36
- package/mcp/schemas/c64_sound.schema.json +20 -822
- package/mcp/schemas/c64_stream.schema.json +4 -75
- package/mcp/schemas/c64_system.schema.json +28 -348
- package/mcp/schemas/c64_vice.schema.json +2 -71
- package/mcp/server.json +1 -1
- package/mcp/tools.json +1990 -6900
- package/mcp.json +21 -1
- package/package.json +19 -8
- package/scripts/mockC64Server.mjs +44 -0
- package/scripts/run-coverage.mjs +27 -1
- package/scripts/smoke-run.sh +3 -2
- package/scripts/start.mjs +23 -1
- package/server.json +53 -3
- package/dist/.tsbuildinfo +0 -1
- package/doc/agent-state-spec.md +0 -243
- package/doc/plans/bun/baseline-metrics.md +0 -16
- package/doc/plans/bun/migration-checklist.md +0 -55
- package/doc/plans/bun/migration.md +0 -83
- package/doc/plans/improve-knowledge/notes.md +0 -43
- package/doc/plans/improve-knowledge/plan.md +0 -121
- package/doc/plans/improve-knowledge/rollout.md +0 -110
- package/doc/plans/meta-tools/README.md +0 -110
- package/doc/plans/meta-tools/analysis.md +0 -272
- package/doc/plans/meta-tools/catalog.md +0 -435
- package/doc/plans/meta-tools/notes.md +0 -192
- package/doc/plans/meta-tools/rollout.md +0 -368
- package/doc/plans/prompts/README.md +0 -96
- package/doc/plans/prompts/user-prompt-ideas.md +0 -147
- package/doc/plans/tool-consolidation/notes.md +0 -28
- package/doc/plans/tool-consolidation/plan.md +0 -169
- package/doc/plans/tool-consolidation/rollout.md +0 -104
- package/doc/plans/vice-backend-switch/plan.md +0 -486
- package/doc/plans/vice-backend-switch-2/plan.md +0 -48
- package/doc/plans/vice-support/notes.md +0 -63
- package/doc/plans/vice-support/plan.md +0 -88
- package/doc/plans/vice-support/rollout.md +0 -99
- package/doc/reviews/audit.md +0 -143
- package/doc/reviews/hardening/01-fable/prompt-handover.md +0 -125
- package/doc/reviews/hardening/01-fable/prompt.md +0 -98
- package/doc/reviews/hardening/01-fable/review-prompt.md +0 -468
- package/doc/reviews/hardening/01-fable/review.md +0 -1234
- package/doc/reviews/vice-support-review.md +0 -185
- package/scripts/compare-rest/rest-compare-2025-11-01T00-25-21-830Z.json +0 -2513
package/dist/mcp-server.js
CHANGED
|
@@ -1,23 +1,29 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import "./bootstrap/stdio-logger.js";
|
|
3
3
|
import { createServer } from "node:http";
|
|
4
|
+
import { timingSafeEqual } from "node:crypto";
|
|
4
5
|
import { dirname, join } from "node:path";
|
|
5
6
|
import { fileURLToPath } from "node:url";
|
|
6
7
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
7
8
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
8
9
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
9
|
-
import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
10
|
+
import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, ErrorCode, McpError, } from "@modelcontextprotocol/sdk/types.js";
|
|
10
11
|
import { loadConfig } from "./config.js";
|
|
11
12
|
import { C64Client } from "./c64Client.js";
|
|
12
13
|
import { listKnowledgeResources, readKnowledgeResource, } from "./rag/knowledgeIndex.js";
|
|
13
14
|
import { PLATFORM_RESOURCE_URI } from "./rag/resourceUris.js";
|
|
14
15
|
import { createLazyRagRetriever, initRag } from "./rag/init.js";
|
|
15
16
|
import { getMcpServerImplementationInfo } from "./mcp/metadata.js";
|
|
17
|
+
import { stopAllBackgroundTasks } from "./tools/meta/background.js";
|
|
18
|
+
import { delegateViceSignalHandling, stopManagedViceProcessesSync } from "./device.js";
|
|
16
19
|
import { toolRegistry } from "./tools/registry/index.js";
|
|
17
|
-
import {
|
|
20
|
+
import { ACCESS_ENV_VAR, getAccessMode } from "./tools/access.js";
|
|
21
|
+
import { ToolValidationError, unknownErrorResult } from "./tools/errors.js";
|
|
22
|
+
import { publishedInputSchema } from "./tools/types.js";
|
|
18
23
|
import { createPromptRegistry } from "./prompts/registry.js";
|
|
19
24
|
import { describePlatformCapabilities, getPlatformStatus, setPlatform } from "./platform.js";
|
|
20
25
|
import axios from "axios";
|
|
26
|
+
import { redactValue, registerSecret } from "./redact.js";
|
|
21
27
|
import { loggerFor, payloadByteLength, formatPayloadForDebug, formatErrorMessage } from "./logger.js";
|
|
22
28
|
import { getDiagnosticsSessionInfo, installProcessDiagnostics, withDiagnosticSpan, writeDiagnosticEvent } from "./diagnostics.js";
|
|
23
29
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -42,6 +48,84 @@ export function parseCliOptions(argv) {
|
|
|
42
48
|
const portCandidate = argv[httpIndex + 1];
|
|
43
49
|
return { mode: "http", port: parsePort(portCandidate) };
|
|
44
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* DNS-rebinding and cross-origin protection for the HTTP transport. Returns
|
|
53
|
+
* the rejection (status and message) or null when the request may proceed.
|
|
54
|
+
* Host must be a loopback name with the listening port, any Origin must be a
|
|
55
|
+
* loopback http origin on that port, and when a token is configured the
|
|
56
|
+
* request must carry the matching bearer token.
|
|
57
|
+
*/
|
|
58
|
+
export function checkHttpRequestAccess(headers, port, token) {
|
|
59
|
+
const allowedHosts = [`127.0.0.1:${port}`, `localhost:${port}`, `[::1]:${port}`];
|
|
60
|
+
const host = headers.host?.toLowerCase();
|
|
61
|
+
if (!host || !allowedHosts.includes(host)) {
|
|
62
|
+
return { status: 403, message: "Forbidden: invalid Host header" };
|
|
63
|
+
}
|
|
64
|
+
const origin = headers.origin;
|
|
65
|
+
if (origin !== undefined) {
|
|
66
|
+
const allowedOrigins = allowedHosts.map((entry) => `http://${entry}`);
|
|
67
|
+
if (typeof origin !== "string" || !allowedOrigins.includes(origin.toLowerCase())) {
|
|
68
|
+
return { status: 403, message: "Forbidden: invalid Origin header" };
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (token) {
|
|
72
|
+
const header = headers.authorization;
|
|
73
|
+
const presented = typeof header === "string" && /^Bearer /i.test(header) ? header.slice(7) : "";
|
|
74
|
+
const expectedBuffer = Buffer.from(token, "utf8");
|
|
75
|
+
const presentedBuffer = Buffer.from(presented, "utf8");
|
|
76
|
+
if (presentedBuffer.length !== expectedBuffer.length || !timingSafeEqual(presentedBuffer, expectedBuffer)) {
|
|
77
|
+
return { status: 401, message: "Unauthorized" };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
const SHUTDOWN_CLEANUP_TIMEOUT_MS = 2000;
|
|
83
|
+
/**
|
|
84
|
+
* Installs the single shutdown path. The first trigger stops background
|
|
85
|
+
* tasks, releases any capture session and stops managed VICE processes, then
|
|
86
|
+
* exits. Later triggers return the same promise. Stdin end/close is a
|
|
87
|
+
* trigger only in stdio mode (the client went away).
|
|
88
|
+
*/
|
|
89
|
+
function installShutdown(client, mode) {
|
|
90
|
+
delegateViceSignalHandling();
|
|
91
|
+
let shutdownPromise = null;
|
|
92
|
+
const shutdown = (reason, exitCode) => {
|
|
93
|
+
if (shutdownPromise)
|
|
94
|
+
return shutdownPromise;
|
|
95
|
+
writeDiagnosticEvent("server_shutdown", { reason });
|
|
96
|
+
console.error(`c64bridge shutting down (${reason})`);
|
|
97
|
+
shutdownPromise = (async () => {
|
|
98
|
+
try {
|
|
99
|
+
const cleanup = Promise.allSettled([
|
|
100
|
+
stopAllBackgroundTasks(),
|
|
101
|
+
client.releaseVideoCapture(),
|
|
102
|
+
]);
|
|
103
|
+
await Promise.race([
|
|
104
|
+
cleanup,
|
|
105
|
+
new Promise((resolve) => setTimeout(resolve, SHUTDOWN_CLEANUP_TIMEOUT_MS).unref()),
|
|
106
|
+
]);
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
// Cleanup is best effort; always continue to exit.
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
stopManagedViceProcessesSync();
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
// ignore
|
|
116
|
+
}
|
|
117
|
+
process.exit(exitCode);
|
|
118
|
+
})();
|
|
119
|
+
return shutdownPromise;
|
|
120
|
+
};
|
|
121
|
+
process.on("SIGTERM", () => void shutdown("SIGTERM", 143));
|
|
122
|
+
process.on("SIGINT", () => void shutdown("SIGINT", 130));
|
|
123
|
+
process.on("SIGHUP", () => void shutdown("SIGHUP", 129));
|
|
124
|
+
if (mode === "stdio") {
|
|
125
|
+
process.stdin.once("end", () => void shutdown("stdin end", 0));
|
|
126
|
+
process.stdin.once("close", () => void shutdown("stdin close", 0));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
45
129
|
function parsePort(raw) {
|
|
46
130
|
if (!raw || raw.startsWith("--")) {
|
|
47
131
|
return undefined;
|
|
@@ -108,344 +192,377 @@ async function main() {
|
|
|
108
192
|
const promptRegistry = createPromptRegistry();
|
|
109
193
|
const getPromptRegistry = createPromptRegistryGetter();
|
|
110
194
|
const implementationInfo = getMcpServerImplementationInfo();
|
|
111
|
-
// Create MCP server
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
if (resourceLogger.isDebugEnabled()) {
|
|
139
|
-
resourceLogger.debug("list resources request", { request: {} });
|
|
140
|
-
resourceLogger.debug("list resources response", { response: formatPayloadForDebug(response) });
|
|
141
|
-
}
|
|
142
|
-
writeDiagnosticEvent("mcp_list_resources_ok", {
|
|
143
|
-
count: response.resources.length,
|
|
144
|
-
latencyMs: latency,
|
|
145
|
-
});
|
|
146
|
-
return response;
|
|
147
|
-
}
|
|
148
|
-
catch (error) {
|
|
149
|
-
const latency = Date.now() - startedAt;
|
|
150
|
-
resourceLogger.error(`list resources failed bytes=0 latencyMs=${latency} error=${formatErrorMessage(error)}`);
|
|
151
|
-
if (resourceLogger.isDebugEnabled()) {
|
|
152
|
-
resourceLogger.debug("list resources request", { request: {} });
|
|
153
|
-
resourceLogger.debug("list resources error", { error: formatErrorMessage(error) });
|
|
154
|
-
}
|
|
155
|
-
writeDiagnosticEvent("mcp_list_resources_failed", { latencyMs: latency, error });
|
|
156
|
-
throw error;
|
|
157
|
-
}
|
|
158
|
-
});
|
|
159
|
-
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
160
|
-
const startedAt = Date.now();
|
|
161
|
-
writeDiagnosticEvent("mcp_read_resource_start", { uri: request.params.uri });
|
|
162
|
-
try {
|
|
163
|
-
let response;
|
|
164
|
-
if (request.params.uri === PLATFORM_RESOURCE_URI) {
|
|
165
|
-
response = {
|
|
166
|
-
contents: [
|
|
167
|
-
{
|
|
168
|
-
uri: PLATFORM_RESOURCE_URI,
|
|
169
|
-
mimeType: "text/markdown",
|
|
170
|
-
text: await renderPlatformStatusMarkdown(client),
|
|
171
|
-
},
|
|
172
|
-
],
|
|
195
|
+
// Create MCP server. Stateless HTTP mode needs a fresh Server and transport
|
|
196
|
+
// per request (the SDK rejects reuse of a stateless transport), so server
|
|
197
|
+
// creation and handler registration live in this factory. The handler bodies
|
|
198
|
+
// below are unchanged and intentionally not re-indented.
|
|
199
|
+
const createMcpServer = () => {
|
|
200
|
+
const server = new Server(implementationInfo, {
|
|
201
|
+
capabilities: {
|
|
202
|
+
resources: {},
|
|
203
|
+
tools: {},
|
|
204
|
+
prompts: {},
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
// RESOURCES: Expose C64 knowledge base
|
|
208
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
209
|
+
const startedAt = Date.now();
|
|
210
|
+
writeDiagnosticEvent("mcp_list_resources_start");
|
|
211
|
+
try {
|
|
212
|
+
const knowledgeResources = listKnowledgeResources().map((resource) => ({
|
|
213
|
+
uri: resource.uri,
|
|
214
|
+
name: resource.name,
|
|
215
|
+
description: resource.description,
|
|
216
|
+
mimeType: resource.mimeType,
|
|
217
|
+
_meta: resource.metadata,
|
|
218
|
+
}));
|
|
219
|
+
const platformResource = createPlatformResourceDescriptor();
|
|
220
|
+
const response = {
|
|
221
|
+
resources: [...knowledgeResources, platformResource],
|
|
173
222
|
};
|
|
223
|
+
const latency = Date.now() - startedAt;
|
|
224
|
+
const bytes = payloadByteLength(response);
|
|
225
|
+
resourceLogger.info(`list resources count=${response.resources.length} bytes=${bytes} latencyMs=${latency}`);
|
|
226
|
+
if (resourceLogger.isDebugEnabled()) {
|
|
227
|
+
resourceLogger.debug("list resources request", { request: {} });
|
|
228
|
+
resourceLogger.debug("list resources response", { response: formatPayloadForDebug(response) });
|
|
229
|
+
}
|
|
230
|
+
writeDiagnosticEvent("mcp_list_resources_ok", {
|
|
231
|
+
count: response.resources.length,
|
|
232
|
+
latencyMs: latency,
|
|
233
|
+
});
|
|
234
|
+
return response;
|
|
174
235
|
}
|
|
175
|
-
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
|
|
236
|
+
catch (error) {
|
|
237
|
+
const latency = Date.now() - startedAt;
|
|
238
|
+
resourceLogger.error(`list resources failed bytes=0 latencyMs=${latency} error=${formatErrorMessage(error)}`);
|
|
239
|
+
if (resourceLogger.isDebugEnabled()) {
|
|
240
|
+
resourceLogger.debug("list resources request", { request: {} });
|
|
241
|
+
resourceLogger.debug("list resources error", { error: formatErrorMessage(error) });
|
|
179
242
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
};
|
|
243
|
+
writeDiagnosticEvent("mcp_list_resources_failed", { latencyMs: latency, error });
|
|
244
|
+
throw error;
|
|
183
245
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
246
|
+
});
|
|
247
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
248
|
+
const startedAt = Date.now();
|
|
249
|
+
writeDiagnosticEvent("mcp_read_resource_start", { uri: request.params.uri });
|
|
250
|
+
try {
|
|
251
|
+
let response;
|
|
252
|
+
if (request.params.uri === PLATFORM_RESOURCE_URI) {
|
|
253
|
+
response = {
|
|
254
|
+
contents: [
|
|
255
|
+
{
|
|
256
|
+
uri: PLATFORM_RESOURCE_URI,
|
|
257
|
+
mimeType: "text/markdown",
|
|
258
|
+
text: await renderPlatformStatusMarkdown(client),
|
|
259
|
+
},
|
|
260
|
+
],
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
const result = readKnowledgeResource(request.params.uri, PROJECT_ROOT);
|
|
265
|
+
if (!result) {
|
|
266
|
+
throw new Error(`Unknown resource: ${request.params.uri}`);
|
|
267
|
+
}
|
|
268
|
+
response = {
|
|
269
|
+
contents: [result],
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
const latency = Date.now() - startedAt;
|
|
273
|
+
const bytes = payloadByteLength(response);
|
|
274
|
+
resourceLogger.info(`read resource uri=${request.params.uri} bytes=${bytes} latencyMs=${latency}`);
|
|
275
|
+
if (resourceLogger.isDebugEnabled()) {
|
|
276
|
+
resourceLogger.debug("read resource request", { request: formatPayloadForDebug(request.params) });
|
|
277
|
+
resourceLogger.debug("read resource response", { response: formatPayloadForDebug(response) });
|
|
278
|
+
}
|
|
279
|
+
writeDiagnosticEvent("mcp_read_resource_ok", {
|
|
280
|
+
uri: request.params.uri,
|
|
281
|
+
latencyMs: latency,
|
|
282
|
+
});
|
|
283
|
+
return response;
|
|
190
284
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
latencyMs
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
285
|
+
catch (error) {
|
|
286
|
+
const latency = Date.now() - startedAt;
|
|
287
|
+
resourceLogger.error(`read resource uri=${request.params.uri} bytes=0 latencyMs=${latency} error=${formatErrorMessage(error)}`);
|
|
288
|
+
if (resourceLogger.isDebugEnabled()) {
|
|
289
|
+
resourceLogger.debug("read resource request", { request: formatPayloadForDebug(request.params) });
|
|
290
|
+
resourceLogger.debug("read resource error", { error: formatErrorMessage(error) });
|
|
291
|
+
}
|
|
292
|
+
writeDiagnosticEvent("mcp_read_resource_failed", {
|
|
293
|
+
uri: request.params.uri,
|
|
294
|
+
latencyMs: latency,
|
|
295
|
+
error,
|
|
296
|
+
});
|
|
297
|
+
throw error;
|
|
203
298
|
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
299
|
+
});
|
|
300
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
301
|
+
const startedAt = Date.now();
|
|
302
|
+
writeDiagnosticEvent("mcp_list_tools_start");
|
|
303
|
+
try {
|
|
304
|
+
const response = {
|
|
305
|
+
tools: toolRegistry.list().map((tool) => ({
|
|
306
|
+
name: tool.name,
|
|
307
|
+
title: tool.metadata.summary,
|
|
308
|
+
description: tool.description,
|
|
309
|
+
inputSchema: publishedInputSchema(tool.inputSchema),
|
|
310
|
+
...(tool.annotations ? { annotations: tool.annotations } : {}),
|
|
311
|
+
_meta: tool.metadata,
|
|
312
|
+
})),
|
|
313
|
+
};
|
|
314
|
+
const latency = Date.now() - startedAt;
|
|
315
|
+
const bytes = payloadByteLength(response);
|
|
316
|
+
toolLogger.info(`list tools count=${response.tools.length} bytes=${bytes} latencyMs=${latency}`);
|
|
317
|
+
if (toolLogger.isDebugEnabled()) {
|
|
318
|
+
toolLogger.debug("list tools request", { request: {} });
|
|
319
|
+
toolLogger.debug("list tools response", { response: formatPayloadForDebug(response) });
|
|
320
|
+
}
|
|
321
|
+
writeDiagnosticEvent("mcp_list_tools_ok", {
|
|
322
|
+
count: response.tools.length,
|
|
323
|
+
latencyMs: latency,
|
|
324
|
+
});
|
|
325
|
+
return response;
|
|
231
326
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
latencyMs
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
if (toolLogger.isDebugEnabled()) {
|
|
242
|
-
toolLogger.debug("list tools request", { request: {} });
|
|
243
|
-
toolLogger.debug("list tools error", { error: formatErrorMessage(error) });
|
|
327
|
+
catch (error) {
|
|
328
|
+
const latency = Date.now() - startedAt;
|
|
329
|
+
toolLogger.error(`list tools failed bytes=0 latencyMs=${latency} error=${formatErrorMessage(error)}`);
|
|
330
|
+
if (toolLogger.isDebugEnabled()) {
|
|
331
|
+
toolLogger.debug("list tools request", { request: {} });
|
|
332
|
+
toolLogger.debug("list tools error", { error: formatErrorMessage(error) });
|
|
333
|
+
}
|
|
334
|
+
writeDiagnosticEvent("mcp_list_tools_failed", { latencyMs: latency, error });
|
|
335
|
+
throw error;
|
|
244
336
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
337
|
+
});
|
|
338
|
+
server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
339
|
+
const startedAt = Date.now();
|
|
340
|
+
writeDiagnosticEvent("mcp_list_prompts_start");
|
|
341
|
+
try {
|
|
342
|
+
const entries = promptRegistry.list();
|
|
343
|
+
const response = {
|
|
344
|
+
prompts: entries.map((entry) => ({
|
|
345
|
+
name: entry.descriptor.name,
|
|
346
|
+
title: entry.descriptor.title,
|
|
347
|
+
description: entry.descriptor.description,
|
|
348
|
+
arguments: entry.arguments?.map((argument) => ({
|
|
349
|
+
name: argument.name,
|
|
350
|
+
description: argument.description,
|
|
351
|
+
required: argument.required,
|
|
352
|
+
options: argument.options,
|
|
353
|
+
})),
|
|
354
|
+
_meta: {
|
|
355
|
+
requiredResources: entry.descriptor.requiredResources,
|
|
356
|
+
optionalResources: entry.descriptor.optionalResources ?? [],
|
|
357
|
+
tools: entry.descriptor.tools,
|
|
358
|
+
tags: entry.descriptor.tags ?? [],
|
|
359
|
+
argumentOptions: Object.fromEntries((entry.arguments ?? [])
|
|
360
|
+
.filter((argument) => Array.isArray(argument.options) && argument.options.length > 0)
|
|
361
|
+
.map((argument) => [argument.name, argument.options])),
|
|
362
|
+
},
|
|
264
363
|
})),
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
promptLogger.info(`list prompts count=${response.prompts.length} bytes=${bytes} latencyMs=${latency}`);
|
|
279
|
-
if (promptLogger.isDebugEnabled()) {
|
|
280
|
-
promptLogger.debug("list prompts request", { request: {} });
|
|
281
|
-
promptLogger.debug("list prompts response", { response: formatPayloadForDebug(response) });
|
|
364
|
+
};
|
|
365
|
+
const latency = Date.now() - startedAt;
|
|
366
|
+
const bytes = payloadByteLength(response);
|
|
367
|
+
promptLogger.info(`list prompts count=${response.prompts.length} bytes=${bytes} latencyMs=${latency}`);
|
|
368
|
+
if (promptLogger.isDebugEnabled()) {
|
|
369
|
+
promptLogger.debug("list prompts request", { request: {} });
|
|
370
|
+
promptLogger.debug("list prompts response", { response: formatPayloadForDebug(response) });
|
|
371
|
+
}
|
|
372
|
+
writeDiagnosticEvent("mcp_list_prompts_ok", {
|
|
373
|
+
count: response.prompts.length,
|
|
374
|
+
latencyMs: latency,
|
|
375
|
+
});
|
|
376
|
+
return response;
|
|
282
377
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
latencyMs
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
if (promptLogger.isDebugEnabled()) {
|
|
293
|
-
promptLogger.debug("list prompts request", { request: {} });
|
|
294
|
-
promptLogger.debug("list prompts error", { error: formatErrorMessage(error) });
|
|
378
|
+
catch (error) {
|
|
379
|
+
const latency = Date.now() - startedAt;
|
|
380
|
+
promptLogger.error(`list prompts failed bytes=0 latencyMs=${latency} error=${formatErrorMessage(error)}`);
|
|
381
|
+
if (promptLogger.isDebugEnabled()) {
|
|
382
|
+
promptLogger.debug("list prompts request", { request: {} });
|
|
383
|
+
promptLogger.debug("list prompts error", { error: formatErrorMessage(error) });
|
|
384
|
+
}
|
|
385
|
+
writeDiagnosticEvent("mcp_list_prompts_failed", { latencyMs: latency, error });
|
|
386
|
+
throw error;
|
|
295
387
|
}
|
|
296
|
-
writeDiagnosticEvent("mcp_list_prompts_failed", { latencyMs: latency, error });
|
|
297
|
-
throw error;
|
|
298
|
-
}
|
|
299
|
-
});
|
|
300
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
301
|
-
const { name } = request.params;
|
|
302
|
-
const args = request.params.arguments ?? {};
|
|
303
|
-
const startedAt = Date.now();
|
|
304
|
-
writeDiagnosticEvent("mcp_call_tool_start", {
|
|
305
|
-
name,
|
|
306
|
-
arguments: formatPayloadForDebug(args),
|
|
307
388
|
});
|
|
308
|
-
|
|
309
|
-
|
|
389
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
390
|
+
const { name } = request.params;
|
|
391
|
+
const args = request.params.arguments ?? {};
|
|
392
|
+
const startedAt = Date.now();
|
|
393
|
+
writeDiagnosticEvent("mcp_call_tool_start", {
|
|
310
394
|
name,
|
|
311
395
|
arguments: formatPayloadForDebug(args),
|
|
312
396
|
});
|
|
313
|
-
}
|
|
314
|
-
try {
|
|
315
|
-
const result = await toolRegistry.invoke(name, args, {
|
|
316
|
-
client,
|
|
317
|
-
rag,
|
|
318
|
-
logger: toolLogger,
|
|
319
|
-
platform: getPlatformStatus(),
|
|
320
|
-
setPlatform,
|
|
321
|
-
});
|
|
322
|
-
const response = toCallToolResult(result);
|
|
323
|
-
const latency = Date.now() - startedAt;
|
|
324
|
-
const bytes = payloadByteLength(response);
|
|
325
|
-
const status = result.isError ? "error" : "ok";
|
|
326
|
-
toolLogger.info(`call tool name=${name} status=${status} bytes=${bytes} latencyMs=${latency}`);
|
|
327
397
|
if (toolLogger.isDebugEnabled()) {
|
|
328
|
-
toolLogger.debug("tool
|
|
398
|
+
toolLogger.debug("tool request", {
|
|
329
399
|
name,
|
|
330
|
-
|
|
400
|
+
arguments: formatPayloadForDebug(args),
|
|
331
401
|
});
|
|
332
402
|
}
|
|
333
|
-
|
|
334
|
-
name,
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
403
|
+
try {
|
|
404
|
+
const result = await toolRegistry.invoke(name, args, {
|
|
405
|
+
client,
|
|
406
|
+
rag,
|
|
407
|
+
logger: toolLogger,
|
|
408
|
+
platform: getPlatformStatus(),
|
|
409
|
+
setPlatform,
|
|
410
|
+
});
|
|
411
|
+
const response = toCallToolResult(result);
|
|
412
|
+
const latency = Date.now() - startedAt;
|
|
413
|
+
const bytes = payloadByteLength(response);
|
|
414
|
+
const status = result.isError ? "error" : "ok";
|
|
415
|
+
toolLogger.info(`call tool name=${name} status=${status} bytes=${bytes} latencyMs=${latency}`);
|
|
416
|
+
if (toolLogger.isDebugEnabled()) {
|
|
417
|
+
toolLogger.debug("tool response", {
|
|
418
|
+
name,
|
|
419
|
+
response: formatPayloadForDebug(response),
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
writeDiagnosticEvent("mcp_call_tool_ok", {
|
|
348
423
|
name,
|
|
349
|
-
|
|
350
|
-
|
|
424
|
+
latencyMs: latency,
|
|
425
|
+
isError: result.isError === true,
|
|
351
426
|
});
|
|
427
|
+
return response;
|
|
352
428
|
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
arguments: formatPayloadForDebug(args),
|
|
368
|
-
});
|
|
369
|
-
if (promptLogger.isDebugEnabled()) {
|
|
370
|
-
promptLogger.debug("prompt request", {
|
|
371
|
-
name,
|
|
372
|
-
arguments: formatPayloadForDebug(args),
|
|
373
|
-
});
|
|
374
|
-
}
|
|
375
|
-
try {
|
|
376
|
-
const resolved = promptRegistry.resolve(name, args);
|
|
377
|
-
const response = {
|
|
378
|
-
description: resolved.description,
|
|
379
|
-
messages: resolved.messages.map(toPromptMessage),
|
|
380
|
-
_meta: {
|
|
381
|
-
arguments: resolved.arguments,
|
|
382
|
-
resources: resolved.resources.map((resource) => ({
|
|
383
|
-
uri: resource.uri,
|
|
384
|
-
name: resource.name,
|
|
385
|
-
description: resource.description,
|
|
386
|
-
mimeType: resource.mimeType,
|
|
387
|
-
metadata: resource.metadata,
|
|
388
|
-
})),
|
|
389
|
-
tools: resolved.tools,
|
|
390
|
-
},
|
|
391
|
-
};
|
|
392
|
-
const latency = Date.now() - startedAt;
|
|
393
|
-
const bytes = payloadByteLength(response);
|
|
394
|
-
promptLogger.info(`get prompt name=${name} bytes=${bytes} latencyMs=${latency}`);
|
|
395
|
-
if (promptLogger.isDebugEnabled()) {
|
|
396
|
-
promptLogger.debug("prompt response", {
|
|
429
|
+
catch (error) {
|
|
430
|
+
const latency = Date.now() - startedAt;
|
|
431
|
+
const fallback = unknownErrorResult(error);
|
|
432
|
+
const response = toCallToolResult(fallback);
|
|
433
|
+
const bytes = payloadByteLength(response);
|
|
434
|
+
toolLogger.error(`call tool name=${name} status=failed bytes=${bytes} latencyMs=${latency} error=${formatErrorMessage(error)}`);
|
|
435
|
+
if (toolLogger.isDebugEnabled()) {
|
|
436
|
+
toolLogger.debug("tool response", {
|
|
437
|
+
name,
|
|
438
|
+
response: formatPayloadForDebug(response),
|
|
439
|
+
error: formatErrorMessage(error),
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
writeDiagnosticEvent("mcp_call_tool_failed", {
|
|
397
443
|
name,
|
|
398
|
-
|
|
444
|
+
latencyMs: latency,
|
|
445
|
+
error,
|
|
399
446
|
});
|
|
447
|
+
return response;
|
|
400
448
|
}
|
|
401
|
-
|
|
449
|
+
});
|
|
450
|
+
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
451
|
+
const { name } = request.params;
|
|
452
|
+
const args = request.params.arguments ?? {};
|
|
453
|
+
const startedAt = Date.now();
|
|
454
|
+
writeDiagnosticEvent("mcp_get_prompt_start", {
|
|
402
455
|
name,
|
|
403
|
-
|
|
456
|
+
arguments: formatPayloadForDebug(args),
|
|
404
457
|
});
|
|
405
|
-
return response;
|
|
406
|
-
}
|
|
407
|
-
catch (error) {
|
|
408
|
-
const latency = Date.now() - startedAt;
|
|
409
|
-
promptLogger.error(`get prompt name=${name} bytes=0 latencyMs=${latency} error=${formatErrorMessage(error)}`);
|
|
410
458
|
if (promptLogger.isDebugEnabled()) {
|
|
411
459
|
promptLogger.debug("prompt request", {
|
|
412
460
|
name,
|
|
413
461
|
arguments: formatPayloadForDebug(args),
|
|
414
462
|
});
|
|
415
|
-
promptLogger.debug("prompt error", { name, error: formatErrorMessage(error) });
|
|
416
463
|
}
|
|
417
|
-
|
|
418
|
-
name,
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
464
|
+
try {
|
|
465
|
+
const resolved = promptRegistry.resolve(name, args);
|
|
466
|
+
const response = {
|
|
467
|
+
description: resolved.description,
|
|
468
|
+
messages: resolved.messages.map(toPromptMessage),
|
|
469
|
+
_meta: {
|
|
470
|
+
arguments: resolved.arguments,
|
|
471
|
+
resources: resolved.resources.map((resource) => ({
|
|
472
|
+
uri: resource.uri,
|
|
473
|
+
name: resource.name,
|
|
474
|
+
description: resource.description,
|
|
475
|
+
mimeType: resource.mimeType,
|
|
476
|
+
metadata: resource.metadata,
|
|
477
|
+
})),
|
|
478
|
+
tools: resolved.tools,
|
|
479
|
+
},
|
|
480
|
+
};
|
|
481
|
+
const latency = Date.now() - startedAt;
|
|
482
|
+
const bytes = payloadByteLength(response);
|
|
483
|
+
promptLogger.info(`get prompt name=${name} bytes=${bytes} latencyMs=${latency}`);
|
|
484
|
+
if (promptLogger.isDebugEnabled()) {
|
|
485
|
+
promptLogger.debug("prompt response", {
|
|
486
|
+
name,
|
|
487
|
+
response: formatPayloadForDebug(response),
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
writeDiagnosticEvent("mcp_get_prompt_ok", {
|
|
491
|
+
name,
|
|
492
|
+
latencyMs: latency,
|
|
493
|
+
});
|
|
494
|
+
return response;
|
|
495
|
+
}
|
|
496
|
+
catch (error) {
|
|
497
|
+
const latency = Date.now() - startedAt;
|
|
498
|
+
promptLogger.error(`get prompt name=${name} bytes=0 latencyMs=${latency} error=${formatErrorMessage(error)}`);
|
|
499
|
+
if (promptLogger.isDebugEnabled()) {
|
|
500
|
+
promptLogger.debug("prompt request", {
|
|
501
|
+
name,
|
|
502
|
+
arguments: formatPayloadForDebug(args),
|
|
503
|
+
});
|
|
504
|
+
promptLogger.debug("prompt error", { name, error: formatErrorMessage(error) });
|
|
505
|
+
}
|
|
506
|
+
writeDiagnosticEvent("mcp_get_prompt_failed", {
|
|
507
|
+
name,
|
|
508
|
+
latencyMs: latency,
|
|
509
|
+
error,
|
|
510
|
+
});
|
|
511
|
+
if (error instanceof ToolValidationError) {
|
|
512
|
+
throw new McpError(ErrorCode.InvalidParams, error.message);
|
|
513
|
+
}
|
|
514
|
+
throw error;
|
|
515
|
+
}
|
|
516
|
+
});
|
|
517
|
+
return server;
|
|
518
|
+
};
|
|
425
519
|
const cli = parseCliOptions(process.argv.slice(2));
|
|
426
520
|
if (cli.mode === "http") {
|
|
427
|
-
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
|
|
428
|
-
await server.connect(transport);
|
|
429
521
|
const port = cli.port ?? 3000;
|
|
522
|
+
const httpToken = process.env.C64BRIDGE_HTTP_TOKEN || undefined;
|
|
523
|
+
if (httpToken) {
|
|
524
|
+
registerSecret(httpToken);
|
|
525
|
+
}
|
|
430
526
|
const httpServer = createServer((request, response) => {
|
|
527
|
+
const rejection = checkHttpRequestAccess(request.headers, port, httpToken);
|
|
528
|
+
if (rejection) {
|
|
529
|
+
response.statusCode = rejection.status;
|
|
530
|
+
if (rejection.status === 401)
|
|
531
|
+
response.setHeader("WWW-Authenticate", "Bearer");
|
|
532
|
+
response.end(rejection.message);
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
431
535
|
if (request.url?.split("?")[0] !== "/mcp") {
|
|
432
536
|
response.statusCode = 404;
|
|
433
537
|
response.end("Not found");
|
|
434
538
|
return;
|
|
435
539
|
}
|
|
436
|
-
|
|
540
|
+
// Stateless mode: one Server and one transport per request.
|
|
541
|
+
const server = createMcpServer();
|
|
542
|
+
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
|
|
543
|
+
response.once("close", () => {
|
|
544
|
+
void transport.close().catch(() => { });
|
|
545
|
+
void server.close().catch(() => { });
|
|
546
|
+
});
|
|
547
|
+
void server
|
|
548
|
+
.connect(transport)
|
|
549
|
+
.then(() => transport.handleRequest(request, response))
|
|
550
|
+
.catch((error) => {
|
|
437
551
|
if (!response.headersSent)
|
|
438
552
|
response.statusCode = 500;
|
|
439
553
|
response.end(error instanceof Error ? error.message : "MCP transport error");
|
|
440
554
|
});
|
|
441
555
|
});
|
|
442
556
|
await new Promise((resolve, reject) => httpServer.listen(port, "127.0.0.1", resolve).once("error", reject));
|
|
557
|
+
installShutdown(client, "http");
|
|
443
558
|
writeDiagnosticEvent("mcp_transport_connected", { mode: "http", port });
|
|
444
559
|
console.error(`c64bridge MCP server running on HTTP at http://127.0.0.1:${port}/mcp`);
|
|
445
560
|
}
|
|
446
561
|
else {
|
|
562
|
+
const server = createMcpServer();
|
|
447
563
|
const transport = new StdioServerTransport();
|
|
448
564
|
await server.connect(transport);
|
|
565
|
+
installShutdown(client, "stdio");
|
|
449
566
|
writeDiagnosticEvent("mcp_transport_connected", { mode: "stdio" });
|
|
450
567
|
console.error("c64bridge MCP server running on stdio");
|
|
451
568
|
}
|
|
@@ -488,6 +605,8 @@ export async function renderPlatformStatusMarkdown(client) {
|
|
|
488
605
|
"",
|
|
489
606
|
`Current platform: \`${status.id}\``,
|
|
490
607
|
"",
|
|
608
|
+
`Access mode: \`${getAccessMode()}\` (\`${ACCESS_ENV_VAR}\`; full allows everything, no-destructive blocks power, flash, drive ROM, image creation and debug register operations, read-only allows only read operations)`,
|
|
609
|
+
"",
|
|
491
610
|
"## Available Backends",
|
|
492
611
|
"",
|
|
493
612
|
...availableBackends.map((backend) => `- \`${backend}\`${backend === status.id ? " (active)" : ""}`),
|
|
@@ -519,6 +638,11 @@ export async function renderPlatformStatusMarkdown(client) {
|
|
|
519
638
|
.map((tool) => `\`${tool}\``)
|
|
520
639
|
.join(", ")}`
|
|
521
640
|
: "- Unsupported tools: _none_");
|
|
641
|
+
lines.push(info.unsupported_operations.length > 0
|
|
642
|
+
? `- Unsupported operations of supported tools (${info.unsupported_operations.length}): ${info.unsupported_operations
|
|
643
|
+
.map((operation) => `\`${operation}\``)
|
|
644
|
+
.join(", ")}`
|
|
645
|
+
: "- Unsupported operations of supported tools: _none_");
|
|
522
646
|
lines.push("");
|
|
523
647
|
}
|
|
524
648
|
lines.push("> Use `c64_select_backend` to switch to another available backend without restarting the MCP server.");
|
|
@@ -558,6 +682,7 @@ async function logConnectivity(client, baseUrl, networkPassword) {
|
|
|
558
682
|
let response = null;
|
|
559
683
|
try {
|
|
560
684
|
// Send the network password like every other request, or a protected device answers 403.
|
|
685
|
+
registerSecret(networkPassword);
|
|
561
686
|
const probeResponse = await axios.get(infoUrl, {
|
|
562
687
|
timeout: 2000,
|
|
563
688
|
...(networkPassword ? { headers: { "X-Password": networkPassword } } : {}),
|
|
@@ -606,13 +731,13 @@ async function logConnectivity(client, baseUrl, networkPassword) {
|
|
|
606
731
|
console.error(`Zero-page probe skipped or failed (may be unsupported on current backend): ${message}`);
|
|
607
732
|
}
|
|
608
733
|
}
|
|
609
|
-
function toCallToolResult(result) {
|
|
610
|
-
const base = { content: result.content };
|
|
734
|
+
export function toCallToolResult(result) {
|
|
735
|
+
const base = { content: redactValue(result.content) };
|
|
611
736
|
if (result.structuredContent !== undefined) {
|
|
612
|
-
base.structuredContent = result.structuredContent;
|
|
737
|
+
base.structuredContent = redactValue(result.structuredContent);
|
|
613
738
|
}
|
|
614
739
|
if (result.metadata !== undefined) {
|
|
615
|
-
base.metadata = result.metadata;
|
|
740
|
+
base.metadata = redactValue(result.metadata);
|
|
616
741
|
}
|
|
617
742
|
if (result.isError !== undefined) {
|
|
618
743
|
base.isError = result.isError;
|
|
@@ -643,7 +768,7 @@ if (process.env.C64BRIDGE_SKIP_AUTO_START !== "1") {
|
|
|
643
768
|
diagnosticsFile: getDiagnosticsSessionInfo()?.filePath,
|
|
644
769
|
error,
|
|
645
770
|
});
|
|
646
|
-
console.error("Fatal error in MCP server:", error);
|
|
771
|
+
console.error("Fatal error in MCP server:", redactValue(error));
|
|
647
772
|
process.exit(1);
|
|
648
773
|
});
|
|
649
774
|
}
|