@context-engine-bridge/context-engine-mcp-bridge 0.0.30 → 0.0.31
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/package.json +1 -1
- package/src/mcpServer.js +106 -4
package/package.json
CHANGED
package/src/mcpServer.js
CHANGED
|
@@ -51,6 +51,80 @@ function _readLspConnection() {
|
|
|
51
51
|
return _cacheNull();
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
const _LSP_ENRICHABLE_TOOLS = new Set([
|
|
55
|
+
"batch_search", "batch_symbol_graph", "batch_graph_query",
|
|
56
|
+
"repo_search", "repo_search_compat", "symbol_graph", "search",
|
|
57
|
+
"code_search", "context_search", "search_tests_for",
|
|
58
|
+
"search_config_for", "search_callers_for", "search_importers_for",
|
|
59
|
+
]);
|
|
60
|
+
|
|
61
|
+
function _extractPaths(obj, paths, depth = 0) {
|
|
62
|
+
if (!obj || typeof obj !== "object" || depth > 20) return;
|
|
63
|
+
if (Array.isArray(obj)) {
|
|
64
|
+
for (const item of obj) _extractPaths(item, paths, depth + 1);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (typeof obj.path === "string" && (obj.path.startsWith("/") || /^[a-zA-Z]:[/\\]/.test(obj.path))) paths.add(obj.path);
|
|
68
|
+
for (const [key, val] of Object.entries(obj)) {
|
|
69
|
+
if (key === "__proto__" || key === "constructor" || key === "prototype") continue;
|
|
70
|
+
if (val && typeof val === "object") _extractPaths(val, paths, depth + 1);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function _callLspHandler(port, secret, operation, params) {
|
|
75
|
+
const postData = JSON.stringify(params);
|
|
76
|
+
return new Promise((resolve) => {
|
|
77
|
+
const req = http.request({
|
|
78
|
+
hostname: "127.0.0.1",
|
|
79
|
+
port: parseInt(port, 10),
|
|
80
|
+
path: `/lsp/${operation}`,
|
|
81
|
+
method: "POST",
|
|
82
|
+
headers: {
|
|
83
|
+
"Content-Type": "application/json",
|
|
84
|
+
"Content-Length": Buffer.byteLength(postData),
|
|
85
|
+
...(secret ? { "x-lsp-handler-token": secret } : {}),
|
|
86
|
+
},
|
|
87
|
+
timeout: 3000,
|
|
88
|
+
}, (res) => {
|
|
89
|
+
let data = "";
|
|
90
|
+
let exceeded = false;
|
|
91
|
+
const MAX_RESP = 5 * 1024 * 1024;
|
|
92
|
+
res.on("data", chunk => {
|
|
93
|
+
if (exceeded) return;
|
|
94
|
+
data += chunk;
|
|
95
|
+
if (data.length > MAX_RESP) { exceeded = true; req.destroy(); resolve(null); }
|
|
96
|
+
});
|
|
97
|
+
res.on("end", () => { if (!exceeded) { try { resolve(JSON.parse(data)); } catch { resolve(null); } } });
|
|
98
|
+
});
|
|
99
|
+
req.on("error", () => resolve(null));
|
|
100
|
+
req.on("timeout", () => { req.destroy(); resolve(null); });
|
|
101
|
+
req.write(postData);
|
|
102
|
+
req.end();
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function _enrichWithLsp(result, lspConn) {
|
|
107
|
+
try {
|
|
108
|
+
if (!Array.isArray(result?.content)) return result;
|
|
109
|
+
const textBlock = result.content.find(c => c.type === "text");
|
|
110
|
+
if (!textBlock?.text) return result;
|
|
111
|
+
let parsed;
|
|
112
|
+
try { parsed = JSON.parse(textBlock.text); } catch { return result; }
|
|
113
|
+
if (!parsed.ok) return result;
|
|
114
|
+
const paths = new Set();
|
|
115
|
+
_extractPaths(parsed, paths);
|
|
116
|
+
if (paths.size === 0) return result;
|
|
117
|
+
const diag = await _callLspHandler(lspConn.port, lspConn.secret, "diagnostics_recent", { paths: [...paths] });
|
|
118
|
+
if (!diag?.ok || !diag.files || diag.total === 0) return result;
|
|
119
|
+
parsed._lsp = { diagnostics: diag.files, total: diag.total };
|
|
120
|
+
textBlock.text = JSON.stringify(parsed);
|
|
121
|
+
return result;
|
|
122
|
+
} catch (err) {
|
|
123
|
+
debugLog("[ctxce] LSP enrichment failed: " + String(err));
|
|
124
|
+
return result;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
54
128
|
function debugLog(message) {
|
|
55
129
|
try {
|
|
56
130
|
const text = typeof message === "string" ? message : String(message);
|
|
@@ -839,6 +913,16 @@ async function createBridgeServer(options) {
|
|
|
839
913
|
|
|
840
914
|
const lspAvailable = !!_readLspConnection();
|
|
841
915
|
if (lspAvailable) {
|
|
916
|
+
const lspProp = {
|
|
917
|
+
type: "boolean",
|
|
918
|
+
description: "When true, auto-enrich results with live LSP diagnostics (errors/warnings) for files in the response. Zero extra tool calls needed.",
|
|
919
|
+
};
|
|
920
|
+
for (const tool of tools) {
|
|
921
|
+
if (_LSP_ENRICHABLE_TOOLS.has(tool.name) && tool.inputSchema?.properties) {
|
|
922
|
+
tool.inputSchema = { ...tool.inputSchema, properties: { ...tool.inputSchema.properties, include_lsp: lspProp } };
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
|
|
842
926
|
const pathProp = { type: "string", description: "Absolute file path" };
|
|
843
927
|
const lineProp = { type: "integer", description: "0-based line" };
|
|
844
928
|
const charProp = { type: "integer", description: "0-based column" };
|
|
@@ -920,6 +1004,13 @@ async function createBridgeServer(options) {
|
|
|
920
1004
|
const name = params.name;
|
|
921
1005
|
let args = params.arguments;
|
|
922
1006
|
|
|
1007
|
+
let includeLsp = false;
|
|
1008
|
+
if (args && typeof args === "object" && args.include_lsp === true && _LSP_ENRICHABLE_TOOLS.has(name)) {
|
|
1009
|
+
includeLsp = true;
|
|
1010
|
+
const { include_lsp: _stripped, ...rest } = args;
|
|
1011
|
+
args = rest;
|
|
1012
|
+
}
|
|
1013
|
+
|
|
923
1014
|
debugLog(`[ctxce] tools/call: ${name || "<no-name>"}`);
|
|
924
1015
|
|
|
925
1016
|
// Refresh session before each call; re-init clients if session changes.
|
|
@@ -985,9 +1076,15 @@ async function createBridgeServer(options) {
|
|
|
985
1076
|
timeout: 15000,
|
|
986
1077
|
}, (res) => {
|
|
987
1078
|
let data = "";
|
|
988
|
-
|
|
1079
|
+
let exceeded = false;
|
|
1080
|
+
const MAX_RESP = 5 * 1024 * 1024;
|
|
1081
|
+
res.on("data", chunk => {
|
|
1082
|
+
if (exceeded) return;
|
|
1083
|
+
data += chunk;
|
|
1084
|
+
if (data.length > MAX_RESP) { exceeded = true; req.destroy(); resolve({ ok: false, error: "Response too large" }); }
|
|
1085
|
+
});
|
|
989
1086
|
res.on("end", () => {
|
|
990
|
-
try { resolve(JSON.parse(data)); } catch { resolve({ ok: false, error: "Invalid response from LSP handler" }); }
|
|
1087
|
+
if (!exceeded) { try { resolve(JSON.parse(data)); } catch { resolve({ ok: false, error: "Invalid response from LSP handler" }); } }
|
|
991
1088
|
});
|
|
992
1089
|
});
|
|
993
1090
|
req.on("error", reject);
|
|
@@ -997,7 +1094,9 @@ async function createBridgeServer(options) {
|
|
|
997
1094
|
});
|
|
998
1095
|
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
999
1096
|
} catch (err) {
|
|
1000
|
-
|
|
1097
|
+
debugLog("[ctxce] LSP proxy error: " + String(err));
|
|
1098
|
+
const safeMsg = (err && err.code) ? `LSP proxy error: ${err.code}` : "LSP proxy error: request failed";
|
|
1099
|
+
return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: safeMsg }) }] };
|
|
1001
1100
|
}
|
|
1002
1101
|
}
|
|
1003
1102
|
|
|
@@ -1029,7 +1128,10 @@ async function createBridgeServer(options) {
|
|
|
1029
1128
|
undefined,
|
|
1030
1129
|
{ timeout: timeoutMs },
|
|
1031
1130
|
);
|
|
1032
|
-
|
|
1131
|
+
let finalResult = maybeRemapToolResult(name, result, workspace);
|
|
1132
|
+
const lspConn = includeLsp && _readLspConnection();
|
|
1133
|
+
if (lspConn) finalResult = await _enrichWithLsp(finalResult, lspConn);
|
|
1134
|
+
return finalResult;
|
|
1033
1135
|
} catch (err) {
|
|
1034
1136
|
lastError = err;
|
|
1035
1137
|
|