@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.
- package/README.md +3 -4
- package/dist/index.js +114 -66
- 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 `
|
|
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 `
|
|
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
|
-
|
|
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 = "
|
|
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 (
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
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
|
-
|
|
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(
|
|
349
|
+
import_node_process.default.stdout.write(`${JSON.stringify(resultPayload2)}
|
|
286
350
|
`);
|
|
287
351
|
return;
|
|
288
352
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
|
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);
|