@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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/mcpServer.js +106 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@context-engine-bridge/context-engine-mcp-bridge",
3
- "version": "0.0.30",
3
+ "version": "0.0.31",
4
4
  "description": "Context Engine MCP bridge (http/stdio proxy combining indexer + memory servers)",
5
5
  "bin": {
6
6
  "ctxce": "bin/ctxce.js",
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
- res.on("data", chunk => { data += chunk; });
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
- return { content: [{ type: "text", text: JSON.stringify({ ok: false, error: `LSP proxy error: ${err.message || String(err)}` }) }] };
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
- return maybeRemapToolResult(name, result, workspace);
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