@jiangxiaoxu/lm-tools-bridge-proxy 0.1.18 → 0.1.19

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.
Files changed (3) hide show
  1. package/README.md +3 -4
  2. package/dist/index.js +114 -66
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -15,15 +15,14 @@ The proxy uses the current working directory (`cwd`) to resolve the VS Code inst
15
15
 
16
16
  Codex does not pass `cwd` into `npx` MCP services, so the proxy requires an explicit workspace handshake before it forwards MCP requests.
17
17
 
18
- 1. Call `lmTools/requestWorkspaceMCPServer` with `params.cwd`.
18
+ 1. Call `lmToolsBridgeProxy.requestWorkspaceMCPServer` with `params.cwd`.
19
19
  2. Wait for `ok: true` and a resolved target.
20
- 3. Optionally call `lmTools/status` to confirm the target is still online.
21
20
 
22
- Until `lmTools/requestWorkspaceMCPServer` succeeds, the proxy rejects all MCP requests (including `roots/list`) with a workspace-not-ready error.
21
+ Until `lmToolsBridgeProxy.requestWorkspaceMCPServer` succeeds, the proxy rejects all MCP requests (including `roots/list`) with a workspace-not-ready error.
23
22
 
24
23
  If the target MCP goes offline, the proxy marks itself disconnected and attempts auto-reconnect every second. `lmTools/status` returns `offlineDurationSec` to show how long it has been offline.
25
24
 
26
- When the proxy is not ready, it exposes a minimal MCP resource (`lm-tools-bridge-proxy://handshake`) via `resources/list` and also returns `lmTools/requestWorkspaceMCPServer` and `lmTools/status` from `tools/list` so clients can discover the exact method names.
25
+ The proxy always exposes a minimal MCP resource (`lm-tools-bridge-proxy://handshake`) via `resources/list`, and it is pinned to the top of the list. It also returns `lmToolsBridgeProxy.requestWorkspaceMCPServer` from `tools/list` before handshake so clients can discover the exact method name.
27
26
 
28
27
  ## Logging
29
28
 
package/dist/index.js CHANGED
@@ -41,8 +41,7 @@ var ERROR_NO_MATCH = -32004;
41
41
  var ERROR_WORKSPACE_NOT_SET = -32005;
42
42
  var ERROR_MCP_OFFLINE = -32006;
43
43
  var STARTUP_TIME = Date.now();
44
- var REQUEST_WORKSPACE_METHOD = "lmTools/requestWorkspaceMCPServer";
45
- var STATUS_METHOD = "lmTools/status";
44
+ var REQUEST_WORKSPACE_METHOD = "lmToolsBridgeProxy.requestWorkspaceMCPServer";
46
45
  var TOOLS_LIST_METHOD = "tools/list";
47
46
  var TOOLS_CALL_METHOD = "tools/call";
48
47
  var INITIALIZE_METHOD = "initialize";
@@ -259,60 +258,137 @@ function getProxyVersion() {
259
258
  }
260
259
  return "unknown";
261
260
  }
261
+ async function requestTargetJson(target, payload) {
262
+ return new Promise((resolve) => {
263
+ const body = JSON.stringify(payload);
264
+ const request = import_node_http.default.request(
265
+ {
266
+ hostname: target.host,
267
+ port: Number(target.port),
268
+ path: "/mcp",
269
+ method: "POST",
270
+ headers: {
271
+ Accept: "application/json",
272
+ "Content-Type": "application/json",
273
+ "Content-Length": Buffer.byteLength(body)
274
+ }
275
+ },
276
+ (response) => {
277
+ const chunks = [];
278
+ response.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
279
+ response.on("end", () => {
280
+ try {
281
+ const text = Buffer.concat(chunks).toString("utf8");
282
+ const parsed = JSON.parse(text);
283
+ resolve({ ok: true, data: parsed });
284
+ } catch {
285
+ resolve({ ok: false });
286
+ }
287
+ });
288
+ }
289
+ );
290
+ request.on("error", () => {
291
+ resolve({ ok: false });
292
+ });
293
+ request.write(body);
294
+ request.end();
295
+ });
296
+ }
262
297
  function createStdioMessageHandler(targetGetter, targetRefresher, stateGetter) {
263
298
  return async (message) => {
264
299
  const state = stateGetter();
265
- if (!state.workspaceMatched) {
266
- if (message?.method === "resources/list") {
267
- logDebug("resources.list.handshake", { id: message.id ?? null });
268
- if (message.id === void 0 || message.id === null) {
269
- return;
300
+ if (message?.method === "resources/read" && message?.params?.uri === HANDSHAKE_RESOURCE_URI) {
301
+ logDebug("resources.read.handshake", { id: message.id ?? null });
302
+ if (message.id === void 0 || message.id === null) {
303
+ return;
304
+ }
305
+ const statusResult = await buildStatusPayload();
306
+ const content = [
307
+ "This MCP proxy requires an explicit workspace handshake.",
308
+ "Call lmToolsBridgeProxy.requestWorkspaceMCPServer with params.cwd, wait for ok:true.",
309
+ "",
310
+ "Status snapshot:",
311
+ JSON.stringify(statusResult.payload, null, 2)
312
+ ].join("\n");
313
+ const resultPayload = {
314
+ jsonrpc: "2.0",
315
+ id: message.id,
316
+ result: {
317
+ contents: [
318
+ {
319
+ uri: HANDSHAKE_RESOURCE_URI,
320
+ mimeType: "text/plain",
321
+ text: content
322
+ }
323
+ ]
270
324
  }
271
- const resultPayload = {
325
+ };
326
+ import_node_process.default.stdout.write(`${JSON.stringify(resultPayload)}
327
+ `);
328
+ return;
329
+ }
330
+ if (message?.method === "resources/list") {
331
+ logDebug("resources.list.handshake", { id: message.id ?? null });
332
+ if (message.id === void 0 || message.id === null) {
333
+ return;
334
+ }
335
+ const proxyResource = {
336
+ uri: HANDSHAKE_RESOURCE_URI,
337
+ name: "MCP proxy handshake",
338
+ description: "Call lmToolsBridgeProxy.requestWorkspaceMCPServer with params.cwd before using MCP.",
339
+ mimeType: "text/plain"
340
+ };
341
+ if (!state.workspaceMatched) {
342
+ const resultPayload2 = {
272
343
  jsonrpc: "2.0",
273
344
  id: message.id,
274
345
  result: {
275
- resources: [
276
- {
277
- uri: HANDSHAKE_RESOURCE_URI,
278
- name: "MCP proxy handshake",
279
- description: "Call lmTools/requestWorkspaceMCPServer with params.cwd before using MCP.",
280
- mimeType: "text/plain"
281
- }
282
- ]
346
+ resources: [proxyResource]
283
347
  }
284
348
  };
285
- import_node_process.default.stdout.write(`${JSON.stringify(resultPayload)}
349
+ import_node_process.default.stdout.write(`${JSON.stringify(resultPayload2)}
286
350
  `);
287
351
  return;
288
352
  }
289
- if (message?.method === "resources/read" && message?.params?.uri === HANDSHAKE_RESOURCE_URI) {
290
- logDebug("resources.read.handshake", { id: message.id ?? null });
291
- if (message.id === void 0 || message.id === null) {
292
- return;
293
- }
294
- const content = [
295
- "This MCP proxy requires an explicit workspace handshake.",
296
- "Call lmTools/requestWorkspaceMCPServer with params.cwd, wait for ok:true.",
297
- "Then call lmTools/status to confirm online status."
298
- ].join("\n");
299
- const resultPayload = {
353
+ const target2 = targetGetter();
354
+ if (!target2) {
355
+ const resultPayload2 = {
300
356
  jsonrpc: "2.0",
301
357
  id: message.id,
302
358
  result: {
303
- contents: [
304
- {
305
- uri: HANDSHAKE_RESOURCE_URI,
306
- mimeType: "text/plain",
307
- text: content
308
- }
309
- ]
359
+ resources: [proxyResource]
310
360
  }
311
361
  };
312
- import_node_process.default.stdout.write(`${JSON.stringify(resultPayload)}
362
+ import_node_process.default.stdout.write(`${JSON.stringify(resultPayload2)}
313
363
  `);
314
364
  return;
315
365
  }
366
+ const remote = await requestTargetJson(target2, {
367
+ jsonrpc: "2.0",
368
+ id: message.id,
369
+ method: "resources/list",
370
+ params: message?.params ?? {}
371
+ });
372
+ const remoteResources = remote.ok && remote.data?.result?.resources ? remote.data.result.resources : [];
373
+ const merged = [
374
+ proxyResource,
375
+ ...remoteResources.filter((entry) => {
376
+ const uri = entry?.uri;
377
+ return uri !== HANDSHAKE_RESOURCE_URI;
378
+ })
379
+ ];
380
+ const resultPayload = {
381
+ jsonrpc: "2.0",
382
+ id: message.id,
383
+ result: {
384
+ resources: merged
385
+ }
386
+ };
387
+ import_node_process.default.stdout.write(`${JSON.stringify(resultPayload)}
388
+ `);
389
+ return;
390
+ }
391
+ if (!state.workspaceMatched) {
316
392
  if (message.id === void 0 || message.id === null) {
317
393
  return;
318
394
  }
@@ -321,7 +397,7 @@ function createStdioMessageHandler(targetGetter, targetRefresher, stateGetter) {
321
397
  id: message.id,
322
398
  error: {
323
399
  code: state.workspaceSetExplicitly ? ERROR_NO_MATCH : ERROR_WORKSPACE_NOT_SET,
324
- message: state.workspaceSetExplicitly ? "Workspace not matched. Call lmTools/requestWorkspaceMCPServer with params.cwd and wait for success." : "Workspace not set. Call lmTools/requestWorkspaceMCPServer with params.cwd before using MCP."
400
+ message: state.workspaceSetExplicitly ? "Workspace not matched. Call lmToolsBridgeProxy.requestWorkspaceMCPServer with params.cwd and wait for success." : "Workspace not set. Call lmToolsBridgeProxy.requestWorkspaceMCPServer with params.cwd before using MCP."
325
401
  }
326
402
  };
327
403
  if (message?.method === "roots/list") {
@@ -669,14 +745,6 @@ async function main() {
669
745
  },
670
746
  required: ["cwd"]
671
747
  }
672
- },
673
- {
674
- name: STATUS_METHOD,
675
- description: "Get proxy status and online state for the current target.",
676
- inputSchema: {
677
- type: "object",
678
- properties: {}
679
- }
680
748
  }
681
749
  ];
682
750
  const respondToolCall = (id, payload) => {
@@ -714,7 +782,7 @@ async function main() {
714
782
  import_node_process.default.stdout.write(`${JSON.stringify(resultPayload)}
715
783
  `);
716
784
  };
717
- const buildStatusPayload = async () => {
785
+ const buildStatusPayload2 = async () => {
718
786
  let resolveResult;
719
787
  if (workspaceSetExplicitly && !workspaceMatched2) {
720
788
  const deadline = Date.now() + RESOLVE_RETRY_DELAY_MS;
@@ -908,31 +976,11 @@ async function main() {
908
976
  respondToolCall(message.id, result.payload);
909
977
  return;
910
978
  }
911
- if (name === STATUS_METHOD) {
912
- const statusResult = await buildStatusPayload();
913
- respondToolCall(message.id, statusResult.payload);
914
- return;
915
- }
916
979
  if (!workspaceMatched2) {
917
980
  respondToolError(message.id, -32602, `Unknown tool: ${String(name)}`);
918
981
  return;
919
982
  }
920
983
  }
921
- if (message?.method === STATUS_METHOD) {
922
- logDebug("status.request", { id: message.id ?? null });
923
- const statusResult = await buildStatusPayload();
924
- logDebug("status.state", statusResult.debug);
925
- if (message.id !== void 0 && message.id !== null) {
926
- const resultPayload = {
927
- jsonrpc: "2.0",
928
- id: message.id,
929
- result: statusResult.payload
930
- };
931
- import_node_process.default.stdout.write(`${JSON.stringify(resultPayload)}
932
- `);
933
- }
934
- return;
935
- }
936
984
  if (message?.method === REQUEST_WORKSPACE_METHOD) {
937
985
  logDebug("requestWorkspaceMCPServer.request", { id: message.id ?? null, cwd: message?.params?.cwd ?? null });
938
986
  const result = await handleRequestWorkspace(message?.params?.cwd);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jiangxiaoxu/lm-tools-bridge-proxy",
3
- "version": "0.1.18",
3
+ "version": "0.1.19",
4
4
  "description": "Stdio MCP proxy for LM Tools Bridge (Windows Named Pipe resolve).",
5
5
  "license": "MIT",
6
6
  "bin": {