machine-bridge-mcp 0.7.1 → 0.8.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.
@@ -1,6 +1,6 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import { randomBytes } from "node:crypto";
3
- import { LocalDaemon } from "./daemon.mjs";
3
+ import { LocalRuntime } from "./runtime.mjs";
4
4
  import { classifyOperationalError, createLogger } from "./log.mjs";
5
5
  import {
6
6
  MCP_INSTRUCTIONS,
@@ -19,7 +19,7 @@ const PACKAGE_VERSION = String(JSON.parse(readFileSync(new URL("../../package.js
19
19
 
20
20
  export async function runStdioServer({ workspace, policy, logLevel = "info", jobRoot = "", resources = {}, resourceStatePath = "" }) {
21
21
  const logger = createLogger({ component: "stdio", level: logLevel, stderrOnly: true, color: false });
22
- const runtime = new LocalDaemon({ workspace, policy, logger, jobRoot, resources, resourceStatePath });
22
+ const runtime = new LocalRuntime({ workspace, policy, logger, jobRoot, resources, resourceStatePath });
23
23
  const pending = new Map();
24
24
  let negotiatedVersion = MCP_PROTOCOL_VERSION;
25
25
  let initialized = false;
@@ -50,100 +50,116 @@ export async function runStdioServer({ workspace, policy, logLevel = "info", job
50
50
  runtime.stop();
51
51
 
52
52
  async function handleLine(line) {
53
- let message;
54
- try { message = JSON.parse(line); } catch {
55
- send(rpcError(null, -32700, "Parse error"));
56
- return;
57
- }
58
- if (!isJsonRpcMessage(message)) {
59
- send(rpcError(null, -32600, "Invalid JSON-RPC message"));
53
+ const message = parseJsonRpcLine(line, send);
54
+ if (!message || typeof message.method !== "string") return;
55
+ if (handleControlMessage(message)) return;
56
+ if (!initialized) {
57
+ send(rpcError(message.id, -32002, "Server is not initialized"));
60
58
  return;
61
59
  }
62
- if (typeof message.method !== "string") return;
60
+ await handleInitializedMessage(message);
61
+ }
63
62
 
63
+ function handleControlMessage(message) {
64
64
  if (message.method === "initialize") {
65
65
  const requested = asObject(message.params).protocolVersion;
66
66
  negotiatedVersion = typeof requested === "string" && MCP_SUPPORTED_PROTOCOL_VERSIONS.includes(requested)
67
67
  ? requested
68
68
  : MCP_PROTOCOL_VERSION;
69
69
  initialized = true;
70
- send(rpcResult(message.id, {
71
- protocolVersion: negotiatedVersion,
72
- capabilities: { tools: { listChanged: false }, logging: {} },
73
- serverInfo: {
74
- name: SERVER_NAME,
75
- title: "Machine Bridge MCP",
76
- version: PACKAGE_VERSION,
77
- description: "Workspace-scoped local coding tools over MCP stdio or authenticated remote relay.",
78
- },
79
- instructions: MCP_INSTRUCTIONS,
80
- }));
81
- return;
70
+ send(rpcResult(message.id, initializationResult(negotiatedVersion)));
71
+ return true;
82
72
  }
83
-
84
- if (message.method === "notifications/initialized") return;
73
+ if (message.method === "notifications/initialized") return true;
85
74
  if (message.method === "notifications/cancelled") {
86
75
  const requestId = asObject(message.params).requestId;
87
- const key = jsonRpcIdKey(requestId);
88
- const callId = pending.get(key);
76
+ const callId = pending.get(jsonRpcIdKey(requestId));
89
77
  if (callId) runtime.cancelCall(callId, "MCP cancellation notification");
90
- return;
78
+ return true;
91
79
  }
92
- if (message.method === "logging/setLevel") {
80
+ if (message.method === "logging/setLevel" || message.method === "ping") {
93
81
  send(rpcResult(message.id, {}));
82
+ return true;
83
+ }
84
+ return false;
85
+ }
86
+
87
+ async function handleInitializedMessage(message) {
88
+ if (message.method === "tools/list") {
89
+ send(rpcResult(message.id, { tools: toolsForPolicy(policy) }));
94
90
  return;
95
91
  }
96
- if (message.method === "ping") {
97
- send(rpcResult(message.id, {}));
92
+ if (message.method === "tools/call") {
93
+ await handleToolCall(message);
98
94
  return;
99
95
  }
100
- if (!initialized) {
101
- send(rpcError(message.id, -32002, "Server is not initialized"));
96
+ send(rpcError(message.id, -32601, `Method not found: ${message.method}`));
97
+ }
98
+
99
+ async function handleToolCall(message) {
100
+ if (!("id" in message) || message.id === null) {
101
+ send(rpcError(null, -32600, "tools/call requires a non-null request id"));
102
102
  return;
103
103
  }
104
- if (message.method === "tools/list") {
105
- send(rpcResult(message.id, { tools: toolsForPolicy(policy) }));
104
+ const params = asObject(message.params);
105
+ const name = typeof params.name === "string" ? params.name : "";
106
+ if (!name) {
107
+ send(rpcError(message.id, -32602, "tools/call requires a tool name"));
106
108
  return;
107
109
  }
108
- if (message.method === "tools/call") {
109
- if (!("id" in message) || message.id === null) {
110
- send(rpcError(null, -32600, "tools/call requires a non-null request id"));
111
- return;
112
- }
113
- const params = asObject(message.params);
114
- const name = typeof params.name === "string" ? params.name : "";
115
- if (!name) {
116
- send(rpcError(message.id, -32602, "tools/call requires a tool name"));
117
- return;
118
- }
119
- const args = asObject(params.arguments);
120
- const callId = `stdio_${randomBytes(16).toString("hex")}`;
121
- const key = jsonRpcIdKey(message.id);
122
- if (key && pending.has(key)) {
123
- send(rpcError(message.id, -32600, "Duplicate in-flight JSON-RPC request id"));
124
- return;
125
- }
126
- if (key) pending.set(key, callId);
127
- const started = Date.now();
128
- logger.debug("tool call started", { call_id: callId.slice(0, 20), tool: name });
129
- try {
130
- const result = await runtime.executeTool(name, args, { callId });
131
- send(rpcResult(message.id, toolResult(result)));
132
- const durationMs = Date.now() - started;
133
- logger.debug(durationMs >= SLOW_TOOL_CALL_MS ? "slow tool call completed" : "tool call completed", { call_id: callId.slice(0, 20), tool: name, duration_ms: durationMs });
134
- } catch (error) {
135
- const safeError = runtime.safeErrorMessage(error, args);
136
- send(rpcResult(message.id, toolResult({ error: safeError }, true)));
137
- const durationMs = Date.now() - started;
138
- logger.debug("tool call failed", { call_id: callId.slice(0, 20), tool: name, duration_ms: durationMs, error_class: classifyOperationalError(error) });
139
- } finally {
140
- if (key) pending.delete(key);
141
- runtime.finishCall(callId);
142
- }
110
+ const args = asObject(params.arguments);
111
+ const callId = `stdio_${randomBytes(16).toString("hex")}`;
112
+ const key = jsonRpcIdKey(message.id);
113
+ if (key && pending.has(key)) {
114
+ send(rpcError(message.id, -32600, "Duplicate in-flight JSON-RPC request id"));
143
115
  return;
144
116
  }
145
- send(rpcError(message.id, -32601, `Method not found: ${message.method}`));
117
+ if (key) pending.set(key, callId);
118
+ const started = Date.now();
119
+ logger.debug("tool call started", { call_id: callId.slice(0, 20), tool: name });
120
+ try {
121
+ const result = await runtime.executeTool(name, args, { callId });
122
+ send(rpcResult(message.id, toolResult(result)));
123
+ const durationMs = Date.now() - started;
124
+ logger.debug(durationMs >= SLOW_TOOL_CALL_MS ? "slow tool call completed" : "tool call completed", { call_id: callId.slice(0, 20), tool: name, duration_ms: durationMs });
125
+ } catch (error) {
126
+ const safeError = runtime.safeErrorMessage(error, args);
127
+ send(rpcResult(message.id, toolResult({ error: safeError }, true)));
128
+ const durationMs = Date.now() - started;
129
+ logger.debug("tool call failed", { call_id: callId.slice(0, 20), tool: name, duration_ms: durationMs, error_class: classifyOperationalError(error) });
130
+ } finally {
131
+ if (key) pending.delete(key);
132
+ runtime.finishCall(callId);
133
+ }
146
134
  }
135
+
136
+ }
137
+
138
+ function parseJsonRpcLine(line, send) {
139
+ let message;
140
+ try { message = JSON.parse(line); } catch {
141
+ send(rpcError(null, -32700, "Parse error"));
142
+ return null;
143
+ }
144
+ if (!isJsonRpcMessage(message)) {
145
+ send(rpcError(null, -32600, "Invalid JSON-RPC message"));
146
+ return null;
147
+ }
148
+ return message;
149
+ }
150
+
151
+ function initializationResult(protocolVersion) {
152
+ return {
153
+ protocolVersion,
154
+ capabilities: { tools: { listChanged: false }, logging: {} },
155
+ serverInfo: {
156
+ name: SERVER_NAME,
157
+ title: "Machine Bridge MCP",
158
+ version: PACKAGE_VERSION,
159
+ description: "Workspace-scoped local coding tools over MCP stdio or authenticated remote relay.",
160
+ },
161
+ instructions: MCP_INSTRUCTIONS,
162
+ };
147
163
  }
148
164
 
149
165
  function consumeBoundedJsonLines(stream, { maxLineBytes, onLine, onOversize }) {
@@ -3,7 +3,7 @@ import toolCatalog from "../shared/tool-catalog.json";
3
3
  import serverMetadata from "../shared/server-metadata.json";
4
4
 
5
5
  const SERVER_NAME = String(serverMetadata.name);
6
- const SERVER_VERSION = "0.7.1";
6
+ const SERVER_VERSION = "0.8.0";
7
7
  const MCP_PROTOCOL_VERSION = String(serverMetadata.protocolVersion);
8
8
  const MCP_SUPPORTED_PROTOCOL_VERSIONS = serverMetadata.supportedProtocolVersions.map((value) => String(value));
9
9
  const JSONRPC_VERSION = "2.0";
@@ -293,13 +293,20 @@ export class BridgeRoom extends DurableObject<BridgeEnv> {
293
293
  }
294
294
 
295
295
  async webSocketClose(ws: WebSocket): Promise<void> {
296
+ await this.cleanupDaemonSocket(ws, "daemon disconnected");
297
+ }
298
+
299
+ async webSocketError(ws: WebSocket, error: unknown): Promise<void> {
300
+ console.error(JSON.stringify({ level: "warn", message: "daemon_websocket_error", error_class: workerErrorClass(error) }));
301
+ await this.cleanupDaemonSocket(ws, "daemon transport error");
302
+ }
303
+
304
+ private async cleanupDaemonSocket(ws: WebSocket, message: string): Promise<void> {
296
305
  await this.scheduleCandidateAlarm();
297
- const attachment = this.daemonAttachment(ws);
298
- if (!attachment) return;
299
306
  for (const [id, pending] of this.pending) {
300
307
  if (pending.socket !== ws) continue;
301
308
  clearTimeout(pending.timeout);
302
- pending.reject(new Error("daemon disconnected"));
309
+ pending.reject(new Error(message));
303
310
  this.pending.delete(id);
304
311
  }
305
312
  }