@treedy/lsp-mcp 0.1.7 → 0.1.9

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 (54) hide show
  1. package/dist/bundled/pyright/dist/index.d.ts +2 -0
  2. package/dist/bundled/pyright/dist/index.js +1620 -0
  3. package/dist/bundled/pyright/dist/index.js.map +26 -0
  4. package/dist/bundled/pyright/dist/lsp/connection.d.ts +71 -0
  5. package/dist/bundled/pyright/dist/lsp/document-manager.d.ts +67 -0
  6. package/dist/bundled/pyright/dist/lsp/index.d.ts +3 -0
  7. package/dist/bundled/pyright/dist/lsp/types.d.ts +55 -0
  8. package/dist/bundled/pyright/dist/lsp-client.d.ts +55 -0
  9. package/dist/bundled/pyright/dist/tools/completions.d.ts +18 -0
  10. package/dist/bundled/pyright/dist/tools/definition.d.ts +16 -0
  11. package/dist/bundled/pyright/dist/tools/diagnostics.d.ts +12 -0
  12. package/dist/bundled/pyright/dist/tools/hover.d.ts +16 -0
  13. package/dist/bundled/pyright/dist/tools/references.d.ts +16 -0
  14. package/dist/bundled/pyright/dist/tools/rename.d.ts +18 -0
  15. package/dist/bundled/pyright/dist/tools/search.d.ts +20 -0
  16. package/dist/bundled/pyright/dist/tools/signature-help.d.ts +16 -0
  17. package/dist/bundled/pyright/dist/tools/status.d.ts +14 -0
  18. package/dist/bundled/pyright/dist/tools/symbols.d.ts +17 -0
  19. package/dist/bundled/pyright/dist/tools/update-document.d.ts +14 -0
  20. package/dist/bundled/pyright/dist/utils/position.d.ts +33 -0
  21. package/dist/bundled/pyright/package.json +54 -0
  22. package/dist/bundled/python/README.md +230 -0
  23. package/dist/bundled/python/pyproject.toml +61 -0
  24. package/dist/bundled/python/src/rope_mcp/__init__.py +3 -0
  25. package/dist/bundled/python/src/rope_mcp/config.py +444 -0
  26. package/dist/bundled/python/src/rope_mcp/lsp/__init__.py +15 -0
  27. package/dist/bundled/python/src/rope_mcp/lsp/client.py +863 -0
  28. package/dist/bundled/python/src/rope_mcp/lsp/types.py +83 -0
  29. package/dist/bundled/python/src/rope_mcp/pyright_client.py +147 -0
  30. package/dist/bundled/python/src/rope_mcp/rope_client.py +198 -0
  31. package/dist/bundled/python/src/rope_mcp/server.py +1217 -0
  32. package/dist/bundled/python/src/rope_mcp/tools/__init__.py +24 -0
  33. package/dist/bundled/python/src/rope_mcp/tools/change_signature.py +184 -0
  34. package/dist/bundled/python/src/rope_mcp/tools/completions.py +84 -0
  35. package/dist/bundled/python/src/rope_mcp/tools/definition.py +51 -0
  36. package/dist/bundled/python/src/rope_mcp/tools/diagnostics.py +18 -0
  37. package/dist/bundled/python/src/rope_mcp/tools/hover.py +49 -0
  38. package/dist/bundled/python/src/rope_mcp/tools/move.py +81 -0
  39. package/dist/bundled/python/src/rope_mcp/tools/references.py +60 -0
  40. package/dist/bundled/python/src/rope_mcp/tools/rename.py +61 -0
  41. package/dist/bundled/python/src/rope_mcp/tools/search.py +128 -0
  42. package/dist/bundled/python/src/rope_mcp/tools/symbols.py +118 -0
  43. package/dist/bundled/python/uv.lock +979 -0
  44. package/dist/bundled/typescript/dist/index.js +29772 -0
  45. package/dist/bundled/typescript/dist/index.js.map +211 -0
  46. package/dist/bundled/typescript/package.json +46 -0
  47. package/dist/bundled/vue/dist/index.d.ts +8 -0
  48. package/dist/bundled/vue/dist/index.js +21176 -0
  49. package/dist/bundled/vue/dist/ts-vue-service.d.ts +67 -0
  50. package/dist/bundled/vue/dist/vue-service.d.ts +160 -0
  51. package/dist/bundled/vue/package.json +45 -0
  52. package/dist/index.js +695 -352
  53. package/dist/index.js.map +6 -6
  54. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -6495,8 +6495,8 @@ var require_dist = __commonJS((exports, module) => {
6495
6495
  var require_windows = __commonJS((exports, module) => {
6496
6496
  module.exports = isexe;
6497
6497
  isexe.sync = sync;
6498
- var fs = __require("fs");
6499
- function checkPathExt(path, options) {
6498
+ var fs2 = __require("fs");
6499
+ function checkPathExt(path2, options) {
6500
6500
  var pathext = options.pathExt !== undefined ? options.pathExt : process.env.PATHEXT;
6501
6501
  if (!pathext) {
6502
6502
  return true;
@@ -6507,25 +6507,25 @@ var require_windows = __commonJS((exports, module) => {
6507
6507
  }
6508
6508
  for (var i = 0;i < pathext.length; i++) {
6509
6509
  var p = pathext[i].toLowerCase();
6510
- if (p && path.substr(-p.length).toLowerCase() === p) {
6510
+ if (p && path2.substr(-p.length).toLowerCase() === p) {
6511
6511
  return true;
6512
6512
  }
6513
6513
  }
6514
6514
  return false;
6515
6515
  }
6516
- function checkStat(stat, path, options) {
6516
+ function checkStat(stat, path2, options) {
6517
6517
  if (!stat.isSymbolicLink() && !stat.isFile()) {
6518
6518
  return false;
6519
6519
  }
6520
- return checkPathExt(path, options);
6520
+ return checkPathExt(path2, options);
6521
6521
  }
6522
- function isexe(path, options, cb) {
6523
- fs.stat(path, function(er, stat) {
6524
- cb(er, er ? false : checkStat(stat, path, options));
6522
+ function isexe(path2, options, cb) {
6523
+ fs2.stat(path2, function(er, stat) {
6524
+ cb(er, er ? false : checkStat(stat, path2, options));
6525
6525
  });
6526
6526
  }
6527
- function sync(path, options) {
6528
- return checkStat(fs.statSync(path), path, options);
6527
+ function sync(path2, options) {
6528
+ return checkStat(fs2.statSync(path2), path2, options);
6529
6529
  }
6530
6530
  });
6531
6531
 
@@ -6533,14 +6533,14 @@ var require_windows = __commonJS((exports, module) => {
6533
6533
  var require_mode = __commonJS((exports, module) => {
6534
6534
  module.exports = isexe;
6535
6535
  isexe.sync = sync;
6536
- var fs = __require("fs");
6537
- function isexe(path, options, cb) {
6538
- fs.stat(path, function(er, stat) {
6536
+ var fs2 = __require("fs");
6537
+ function isexe(path2, options, cb) {
6538
+ fs2.stat(path2, function(er, stat) {
6539
6539
  cb(er, er ? false : checkStat(stat, options));
6540
6540
  });
6541
6541
  }
6542
- function sync(path, options) {
6543
- return checkStat(fs.statSync(path), options);
6542
+ function sync(path2, options) {
6543
+ return checkStat(fs2.statSync(path2), options);
6544
6544
  }
6545
6545
  function checkStat(stat, options) {
6546
6546
  return stat.isFile() && checkMode(stat, options);
@@ -6562,7 +6562,7 @@ var require_mode = __commonJS((exports, module) => {
6562
6562
 
6563
6563
  // node_modules/isexe/index.js
6564
6564
  var require_isexe = __commonJS((exports, module) => {
6565
- var fs = __require("fs");
6565
+ var fs2 = __require("fs");
6566
6566
  var core2;
6567
6567
  if (process.platform === "win32" || global.TESTING_WINDOWS) {
6568
6568
  core2 = require_windows();
@@ -6571,7 +6571,7 @@ var require_isexe = __commonJS((exports, module) => {
6571
6571
  }
6572
6572
  module.exports = isexe;
6573
6573
  isexe.sync = sync;
6574
- function isexe(path, options, cb) {
6574
+ function isexe(path2, options, cb) {
6575
6575
  if (typeof options === "function") {
6576
6576
  cb = options;
6577
6577
  options = {};
@@ -6580,17 +6580,17 @@ var require_isexe = __commonJS((exports, module) => {
6580
6580
  if (typeof Promise !== "function") {
6581
6581
  throw new TypeError("callback not provided");
6582
6582
  }
6583
- return new Promise(function(resolve, reject) {
6584
- isexe(path, options || {}, function(er, is) {
6583
+ return new Promise(function(resolve2, reject) {
6584
+ isexe(path2, options || {}, function(er, is) {
6585
6585
  if (er) {
6586
6586
  reject(er);
6587
6587
  } else {
6588
- resolve(is);
6588
+ resolve2(is);
6589
6589
  }
6590
6590
  });
6591
6591
  });
6592
6592
  }
6593
- core2(path, options || {}, function(er, is) {
6593
+ core2(path2, options || {}, function(er, is) {
6594
6594
  if (er) {
6595
6595
  if (er.code === "EACCES" || options && options.ignoreErrors) {
6596
6596
  er = null;
@@ -6600,9 +6600,9 @@ var require_isexe = __commonJS((exports, module) => {
6600
6600
  cb(er, is);
6601
6601
  });
6602
6602
  }
6603
- function sync(path, options) {
6603
+ function sync(path2, options) {
6604
6604
  try {
6605
- return core2.sync(path, options || {});
6605
+ return core2.sync(path2, options || {});
6606
6606
  } catch (er) {
6607
6607
  if (options && options.ignoreErrors || er.code === "EACCES") {
6608
6608
  return false;
@@ -6616,7 +6616,7 @@ var require_isexe = __commonJS((exports, module) => {
6616
6616
  // node_modules/which/which.js
6617
6617
  var require_which = __commonJS((exports, module) => {
6618
6618
  var isWindows = process.platform === "win32" || process.env.OSTYPE === "cygwin" || process.env.OSTYPE === "msys";
6619
- var path = __require("path");
6619
+ var path2 = __require("path");
6620
6620
  var COLON = isWindows ? ";" : ":";
6621
6621
  var isexe = require_isexe();
6622
6622
  var getNotFoundError = (cmd) => Object.assign(new Error(`not found: ${cmd}`), { code: "ENOENT" });
@@ -6647,27 +6647,27 @@ var require_which = __commonJS((exports, module) => {
6647
6647
  opt = {};
6648
6648
  const { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt);
6649
6649
  const found = [];
6650
- const step = (i) => new Promise((resolve, reject) => {
6650
+ const step = (i) => new Promise((resolve2, reject) => {
6651
6651
  if (i === pathEnv.length)
6652
- return opt.all && found.length ? resolve(found) : reject(getNotFoundError(cmd));
6652
+ return opt.all && found.length ? resolve2(found) : reject(getNotFoundError(cmd));
6653
6653
  const ppRaw = pathEnv[i];
6654
6654
  const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
6655
- const pCmd = path.join(pathPart, cmd);
6655
+ const pCmd = path2.join(pathPart, cmd);
6656
6656
  const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
6657
- resolve(subStep(p, i, 0));
6657
+ resolve2(subStep(p, i, 0));
6658
6658
  });
6659
- const subStep = (p, i, ii) => new Promise((resolve, reject) => {
6659
+ const subStep = (p, i, ii) => new Promise((resolve2, reject) => {
6660
6660
  if (ii === pathExt.length)
6661
- return resolve(step(i + 1));
6661
+ return resolve2(step(i + 1));
6662
6662
  const ext = pathExt[ii];
6663
6663
  isexe(p + ext, { pathExt: pathExtExe }, (er, is) => {
6664
6664
  if (!er && is) {
6665
6665
  if (opt.all)
6666
6666
  found.push(p + ext);
6667
6667
  else
6668
- return resolve(p + ext);
6668
+ return resolve2(p + ext);
6669
6669
  }
6670
- return resolve(subStep(p, i, ii + 1));
6670
+ return resolve2(subStep(p, i, ii + 1));
6671
6671
  });
6672
6672
  });
6673
6673
  return cb ? step(0).then((res) => cb(null, res), cb) : step(0);
@@ -6679,7 +6679,7 @@ var require_which = __commonJS((exports, module) => {
6679
6679
  for (let i = 0;i < pathEnv.length; i++) {
6680
6680
  const ppRaw = pathEnv[i];
6681
6681
  const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
6682
- const pCmd = path.join(pathPart, cmd);
6682
+ const pCmd = path2.join(pathPart, cmd);
6683
6683
  const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
6684
6684
  for (let j = 0;j < pathExt.length; j++) {
6685
6685
  const cur = p + pathExt[j];
@@ -6720,7 +6720,7 @@ var require_path_key = __commonJS((exports, module) => {
6720
6720
 
6721
6721
  // node_modules/cross-spawn/lib/util/resolveCommand.js
6722
6722
  var require_resolveCommand = __commonJS((exports, module) => {
6723
- var path = __require("path");
6723
+ var path2 = __require("path");
6724
6724
  var which = require_which();
6725
6725
  var getPathKey = require_path_key();
6726
6726
  function resolveCommandAttempt(parsed, withoutPathExt) {
@@ -6737,7 +6737,7 @@ var require_resolveCommand = __commonJS((exports, module) => {
6737
6737
  try {
6738
6738
  resolved = which.sync(parsed.command, {
6739
6739
  path: env[getPathKey({ env })],
6740
- pathExt: withoutPathExt ? path.delimiter : undefined
6740
+ pathExt: withoutPathExt ? path2.delimiter : undefined
6741
6741
  });
6742
6742
  } catch (e) {} finally {
6743
6743
  if (shouldSwitchCwd) {
@@ -6745,7 +6745,7 @@ var require_resolveCommand = __commonJS((exports, module) => {
6745
6745
  }
6746
6746
  }
6747
6747
  if (resolved) {
6748
- resolved = path.resolve(hasCustomCwd ? parsed.options.cwd : "", resolved);
6748
+ resolved = path2.resolve(hasCustomCwd ? parsed.options.cwd : "", resolved);
6749
6749
  }
6750
6750
  return resolved;
6751
6751
  }
@@ -6790,8 +6790,8 @@ var require_shebang_command = __commonJS((exports, module) => {
6790
6790
  if (!match) {
6791
6791
  return null;
6792
6792
  }
6793
- const [path, argument] = match[0].replace(/#! ?/, "").split(" ");
6794
- const binary = path.split("/").pop();
6793
+ const [path2, argument] = match[0].replace(/#! ?/, "").split(" ");
6794
+ const binary = path2.split("/").pop();
6795
6795
  if (binary === "env") {
6796
6796
  return argument;
6797
6797
  }
@@ -6801,16 +6801,16 @@ var require_shebang_command = __commonJS((exports, module) => {
6801
6801
 
6802
6802
  // node_modules/cross-spawn/lib/util/readShebang.js
6803
6803
  var require_readShebang = __commonJS((exports, module) => {
6804
- var fs = __require("fs");
6804
+ var fs2 = __require("fs");
6805
6805
  var shebangCommand = require_shebang_command();
6806
6806
  function readShebang(command) {
6807
6807
  const size = 150;
6808
6808
  const buffer = Buffer.alloc(size);
6809
6809
  let fd;
6810
6810
  try {
6811
- fd = fs.openSync(command, "r");
6812
- fs.readSync(fd, buffer, 0, size, 0);
6813
- fs.closeSync(fd);
6811
+ fd = fs2.openSync(command, "r");
6812
+ fs2.readSync(fd, buffer, 0, size, 0);
6813
+ fs2.closeSync(fd);
6814
6814
  } catch (e) {}
6815
6815
  return shebangCommand(buffer.toString());
6816
6816
  }
@@ -6819,7 +6819,7 @@ var require_readShebang = __commonJS((exports, module) => {
6819
6819
 
6820
6820
  // node_modules/cross-spawn/lib/parse.js
6821
6821
  var require_parse = __commonJS((exports, module) => {
6822
- var path = __require("path");
6822
+ var path2 = __require("path");
6823
6823
  var resolveCommand = require_resolveCommand();
6824
6824
  var escape2 = require_escape();
6825
6825
  var readShebang = require_readShebang();
@@ -6844,7 +6844,7 @@ var require_parse = __commonJS((exports, module) => {
6844
6844
  const needsShell = !isExecutableRegExp.test(commandFile);
6845
6845
  if (parsed.options.forceShell || needsShell) {
6846
6846
  const needsDoubleEscapeMetaChars = isCmdShimRegExp.test(commandFile);
6847
- parsed.command = path.normalize(parsed.command);
6847
+ parsed.command = path2.normalize(parsed.command);
6848
6848
  parsed.command = escape2.command(parsed.command);
6849
6849
  parsed.args = parsed.args.map((arg) => escape2.argument(arg, needsDoubleEscapeMetaChars));
6850
6850
  const shellCommand = [parsed.command].concat(parsed.args).join(" ");
@@ -19837,29 +19837,116 @@ class StdioServerTransport {
19837
19837
 
19838
19838
  // src/index.ts
19839
19839
  import { createRequire as createRequire2 } from "module";
19840
+ import * as fs2 from "fs";
19841
+ import * as path2 from "path";
19840
19842
 
19841
19843
  // src/config.ts
19842
- function loadConfig() {
19843
- const pythonEnabled = getEnvBool("LSP_MCP_PYTHON_ENABLED", true);
19844
- const pythonProvider = getEnvString("LSP_MCP_PYTHON_PROVIDER", "python-lsp-mcp");
19845
- const typescriptEnabled = getEnvBool("LSP_MCP_TYPESCRIPT_ENABLED", true);
19846
- const vueEnabled = getEnvBool("LSP_MCP_VUE_ENABLED", true);
19847
- const autoUpdate = getEnvBool("LSP_MCP_AUTO_UPDATE", true);
19848
- const eagerStart = getEnvBool("LSP_MCP_EAGER_START", false);
19849
- return {
19844
+ import * as path from "path";
19845
+ import * as fs from "fs";
19846
+ import * as os from "os";
19847
+ import { fileURLToPath } from "url";
19848
+ var DEFAULT_EXTENSIONS = {
19849
+ python: [".py", ".pyi", ".pyw"],
19850
+ typescript: [".ts", ".tsx", ".js", ".jsx", ".mts", ".mjs", ".cts", ".cjs"],
19851
+ vue: [".vue"]
19852
+ };
19853
+ var DEFAULT_CONFIG = {
19854
+ languages: {
19850
19855
  python: {
19851
- enabled: pythonEnabled,
19852
- provider: pythonProvider
19856
+ enabled: true,
19857
+ extensions: DEFAULT_EXTENSIONS.python
19853
19858
  },
19854
19859
  typescript: {
19855
- enabled: typescriptEnabled
19860
+ enabled: true,
19861
+ extensions: DEFAULT_EXTENSIONS.typescript
19856
19862
  },
19857
19863
  vue: {
19858
- enabled: vueEnabled
19859
- },
19860
- autoUpdate,
19861
- eagerStart
19864
+ enabled: true,
19865
+ extensions: DEFAULT_EXTENSIONS.vue
19866
+ }
19867
+ },
19868
+ autoUpdate: true,
19869
+ eagerStart: false,
19870
+ idleTimeout: 600
19871
+ };
19872
+ function loadConfig() {
19873
+ const fileConfig = loadConfigFile();
19874
+ const envConfig = loadEnvConfig();
19875
+ const merged = {
19876
+ ...DEFAULT_CONFIG,
19877
+ ...fileConfig,
19878
+ ...envConfig,
19879
+ languages: {
19880
+ ...DEFAULT_CONFIG.languages,
19881
+ ...fileConfig?.languages || {},
19882
+ ...Object.keys(DEFAULT_CONFIG.languages).reduce((acc, lang) => {
19883
+ if (envConfig.languages?.[lang]) {
19884
+ acc[lang] = {
19885
+ ...fileConfig?.languages?.[lang] || DEFAULT_CONFIG.languages[lang],
19886
+ ...envConfig.languages[lang]
19887
+ };
19888
+ }
19889
+ return acc;
19890
+ }, {})
19891
+ }
19862
19892
  };
19893
+ for (const [lang, cfg] of Object.entries(merged.languages)) {
19894
+ if (!cfg.extensions && DEFAULT_EXTENSIONS[lang]) {
19895
+ cfg.extensions = DEFAULT_EXTENSIONS[lang];
19896
+ }
19897
+ }
19898
+ return merged;
19899
+ }
19900
+ function loadConfigFile() {
19901
+ const locations = [
19902
+ path.resolve(process.cwd(), ".lsp-mcp.json"),
19903
+ path.join(os.homedir(), ".config", "lsp-mcp", "config.json")
19904
+ ];
19905
+ for (const loc of locations) {
19906
+ if (fs.existsSync(loc)) {
19907
+ try {
19908
+ console.error(`[Config] Loading configuration from ${loc}`);
19909
+ const content = fs.readFileSync(loc, "utf-8");
19910
+ return JSON.parse(content);
19911
+ } catch (e) {
19912
+ console.error(`[Config] Failed to parse config file ${loc}: ${e}`);
19913
+ }
19914
+ }
19915
+ }
19916
+ return null;
19917
+ }
19918
+ function loadEnvConfig() {
19919
+ const pythonEnabled = getEnvBool("LSP_MCP_PYTHON_ENABLED");
19920
+ const pythonProvider = getEnvString("LSP_MCP_PYTHON_PROVIDER");
19921
+ const typescriptEnabled = getEnvBool("LSP_MCP_TYPESCRIPT_ENABLED");
19922
+ const vueEnabled = getEnvBool("LSP_MCP_VUE_ENABLED");
19923
+ const autoUpdate = getEnvBool("LSP_MCP_AUTO_UPDATE");
19924
+ const eagerStart = getEnvBool("LSP_MCP_EAGER_START");
19925
+ const idleTimeoutStr = getEnvString("LSP_MCP_IDLE_TIMEOUT");
19926
+ const idleTimeout = idleTimeoutStr ? parseInt(idleTimeoutStr, 10) : undefined;
19927
+ const config2 = {};
19928
+ const languages = {};
19929
+ if (pythonEnabled !== undefined)
19930
+ languages.python = { enabled: pythonEnabled };
19931
+ if (typescriptEnabled !== undefined)
19932
+ languages.typescript = { enabled: typescriptEnabled };
19933
+ if (vueEnabled !== undefined)
19934
+ languages.vue = { enabled: vueEnabled };
19935
+ if (Object.keys(languages).length > 0)
19936
+ config2.languages = languages;
19937
+ if (autoUpdate !== undefined)
19938
+ config2.autoUpdate = autoUpdate;
19939
+ if (eagerStart !== undefined)
19940
+ config2.eagerStart = eagerStart;
19941
+ if (idleTimeout !== undefined)
19942
+ config2.idleTimeout = idleTimeout;
19943
+ if (pythonEnabled !== undefined || pythonProvider) {
19944
+ config2.python = {
19945
+ enabled: pythonEnabled ?? true,
19946
+ provider: pythonProvider ?? "python-lsp-mcp"
19947
+ };
19948
+ }
19949
+ return config2;
19863
19950
  }
19864
19951
  function getEnvBool(name, defaultValue) {
19865
19952
  const value = process.env[name];
@@ -19870,18 +19957,66 @@ function getEnvBool(name, defaultValue) {
19870
19957
  function getEnvString(name, defaultValue) {
19871
19958
  return process.env[name] ?? defaultValue;
19872
19959
  }
19960
+ function inferLanguageFromPath(filePath, config2) {
19961
+ const ext = filePath.substring(filePath.lastIndexOf("."));
19962
+ for (const [lang, langConfig] of Object.entries(config2.languages)) {
19963
+ if (langConfig.enabled && langConfig.extensions.includes(ext)) {
19964
+ return lang;
19965
+ }
19966
+ }
19967
+ return null;
19968
+ }
19969
+ function resolveBundledBackend(name) {
19970
+ try {
19971
+ const currentDir = path.dirname(fileURLToPath(import.meta.url));
19972
+ const isInDist = currentDir.endsWith("dist") || currentDir.endsWith("dist/");
19973
+ const bundledDir = isInDist ? path.resolve(currentDir, "bundled", name) : path.resolve(currentDir, "..", "dist", "bundled", name);
19974
+ if (fs.existsSync(bundledDir)) {
19975
+ return bundledDir;
19976
+ }
19977
+ } catch (error2) {}
19978
+ return null;
19979
+ }
19873
19980
  function getBackendCommand(language, config2) {
19981
+ const langConfig = config2.languages[language];
19982
+ if (!langConfig || !langConfig.enabled)
19983
+ return null;
19984
+ if (langConfig.command) {
19985
+ return {
19986
+ enabled: true,
19987
+ command: langConfig.command,
19988
+ args: langConfig.args || [],
19989
+ env: langConfig.env
19990
+ };
19991
+ }
19874
19992
  const { autoUpdate } = config2;
19875
19993
  if (language === "python") {
19876
- if (!config2.python.enabled)
19877
- return null;
19878
- if (config2.python.provider === "pyright-mcp") {
19994
+ const provider = config2.python?.provider || "python-lsp-mcp";
19995
+ if (provider === "pyright-mcp") {
19996
+ const bundledPath = resolveBundledBackend("pyright");
19997
+ if (bundledPath) {
19998
+ console.error(`[Config] Using bundled pyright backend from ${bundledPath}`);
19999
+ return {
20000
+ enabled: true,
20001
+ command: "node",
20002
+ args: [path.join(bundledPath, "dist", "index.js")]
20003
+ };
20004
+ }
19879
20005
  return {
19880
20006
  enabled: true,
19881
20007
  command: "npx",
19882
20008
  args: autoUpdate ? ["--yes", "@treedy/pyright-mcp@latest"] : ["@treedy/pyright-mcp@latest"]
19883
20009
  };
19884
20010
  } else {
20011
+ const bundledPath = resolveBundledBackend("python");
20012
+ if (bundledPath) {
20013
+ console.error(`[Config] Using bundled python backend from ${bundledPath}`);
20014
+ return {
20015
+ enabled: true,
20016
+ command: "uv",
20017
+ args: ["run", "--directory", bundledPath, "python-lsp-mcp"]
20018
+ };
20019
+ }
19885
20020
  return {
19886
20021
  enabled: true,
19887
20022
  command: "uvx",
@@ -19889,16 +20024,30 @@ function getBackendCommand(language, config2) {
19889
20024
  };
19890
20025
  }
19891
20026
  } else if (language === "typescript") {
19892
- if (!config2.typescript.enabled)
19893
- return null;
20027
+ const bundledPath = resolveBundledBackend("typescript");
20028
+ if (bundledPath) {
20029
+ console.error(`[Config] Using bundled typescript backend from ${bundledPath}`);
20030
+ return {
20031
+ enabled: true,
20032
+ command: "node",
20033
+ args: [path.join(bundledPath, "dist", "index.js")]
20034
+ };
20035
+ }
19894
20036
  return {
19895
20037
  enabled: true,
19896
20038
  command: "npx",
19897
20039
  args: autoUpdate ? ["--yes", "@treedy/typescript-lsp-mcp@latest"] : ["@treedy/typescript-lsp-mcp@latest"]
19898
20040
  };
19899
20041
  } else if (language === "vue") {
19900
- if (!config2.vue.enabled)
19901
- return null;
20042
+ const bundledPath = resolveBundledBackend("vue");
20043
+ if (bundledPath) {
20044
+ console.error(`[Config] Using bundled vue backend from ${bundledPath}`);
20045
+ return {
20046
+ enabled: true,
20047
+ command: "node",
20048
+ args: [path.join(bundledPath, "dist", "index.js")]
20049
+ };
20050
+ }
19902
20051
  return {
19903
20052
  enabled: true,
19904
20053
  command: "npx",
@@ -20487,7 +20636,7 @@ class StdioClientTransport {
20487
20636
  if (this._process) {
20488
20637
  throw new Error("StdioClientTransport already started! If using Client class, note that connect() calls start() automatically.");
20489
20638
  }
20490
- return new Promise((resolve, reject) => {
20639
+ return new Promise((resolve2, reject) => {
20491
20640
  this._process = import_cross_spawn.default(this._serverParams.command, this._serverParams.args ?? [], {
20492
20641
  env: {
20493
20642
  ...getDefaultEnvironment(),
@@ -20503,7 +20652,7 @@ class StdioClientTransport {
20503
20652
  this.onerror?.(error2);
20504
20653
  });
20505
20654
  this._process.on("spawn", () => {
20506
- resolve();
20655
+ resolve2();
20507
20656
  });
20508
20657
  this._process.on("close", (_code) => {
20509
20658
  this._process = undefined;
@@ -20550,20 +20699,20 @@ class StdioClientTransport {
20550
20699
  if (this._process) {
20551
20700
  const processToClose = this._process;
20552
20701
  this._process = undefined;
20553
- const closePromise = new Promise((resolve) => {
20702
+ const closePromise = new Promise((resolve2) => {
20554
20703
  processToClose.once("close", () => {
20555
- resolve();
20704
+ resolve2();
20556
20705
  });
20557
20706
  });
20558
20707
  try {
20559
20708
  processToClose.stdin?.end();
20560
20709
  } catch {}
20561
- await Promise.race([closePromise, new Promise((resolve) => setTimeout(resolve, 2000).unref())]);
20710
+ await Promise.race([closePromise, new Promise((resolve2) => setTimeout(resolve2, 2000).unref())]);
20562
20711
  if (processToClose.exitCode === null) {
20563
20712
  try {
20564
20713
  processToClose.kill("SIGTERM");
20565
20714
  } catch {}
20566
- await Promise.race([closePromise, new Promise((resolve) => setTimeout(resolve, 2000).unref())]);
20715
+ await Promise.race([closePromise, new Promise((resolve2) => setTimeout(resolve2, 2000).unref())]);
20567
20716
  }
20568
20717
  if (processToClose.exitCode === null) {
20569
20718
  try {
@@ -20574,15 +20723,15 @@ class StdioClientTransport {
20574
20723
  this._readBuffer.clear();
20575
20724
  }
20576
20725
  send(message) {
20577
- return new Promise((resolve) => {
20726
+ return new Promise((resolve2) => {
20578
20727
  if (!this._process?.stdin) {
20579
20728
  throw new Error("Not connected");
20580
20729
  }
20581
20730
  const json = serializeMessage(message);
20582
20731
  if (this._process.stdin.write(json)) {
20583
- resolve();
20732
+ resolve2();
20584
20733
  } else {
20585
- this._process.stdin.once("drain", resolve);
20734
+ this._process.stdin.once("drain", resolve2);
20586
20735
  }
20587
20736
  });
20588
20737
  }
@@ -20596,12 +20745,43 @@ class BackendManager {
20596
20745
  backends = new Map;
20597
20746
  startPromises = new Map;
20598
20747
  config;
20748
+ idleCheckInterval = null;
20599
20749
  constructor(config2) {
20600
20750
  this.config = config2;
20751
+ if (this.config.idleTimeout > 0) {
20752
+ this.idleCheckInterval = setInterval(() => this.checkIdle(), 60 * 1000);
20753
+ if (this.idleCheckInterval.unref) {
20754
+ this.idleCheckInterval.unref();
20755
+ }
20756
+ }
20757
+ }
20758
+ async checkIdle() {
20759
+ const now = Date.now();
20760
+ const timeoutMs = this.config.idleTimeout * 1000;
20761
+ for (const [lang, state] of this.backends.entries()) {
20762
+ if (state.status === "ready" && now - state.lastUsed > timeoutMs) {
20763
+ console.error(`[BackendManager] ${lang} backend idle for ${this.config.idleTimeout}s, shutting down...`);
20764
+ await this.shutdownBackend(lang);
20765
+ }
20766
+ }
20767
+ }
20768
+ updateConfig(newConfig) {
20769
+ this.config = newConfig;
20770
+ if (this.idleCheckInterval) {
20771
+ clearInterval(this.idleCheckInterval);
20772
+ this.idleCheckInterval = null;
20773
+ }
20774
+ if (this.config.idleTimeout > 0) {
20775
+ this.idleCheckInterval = setInterval(() => this.checkIdle(), 60 * 1000);
20776
+ if (this.idleCheckInterval.unref) {
20777
+ this.idleCheckInterval.unref();
20778
+ }
20779
+ }
20601
20780
  }
20602
20781
  async getBackend(language) {
20603
20782
  const existing = this.backends.get(language);
20604
20783
  if (existing && existing.status === "ready") {
20784
+ existing.lastUsed = Date.now();
20605
20785
  return existing;
20606
20786
  }
20607
20787
  const pending = this.startPromises.get(language);
@@ -20617,6 +20797,62 @@ class BackendManager {
20617
20797
  this.startPromises.delete(language);
20618
20798
  }
20619
20799
  }
20800
+ monitorBackend(language, transport) {
20801
+ transport.onclose = async () => {
20802
+ const state = this.backends.get(language);
20803
+ if (!state || state.status === "stopped")
20804
+ return;
20805
+ console.error(`[BackendManager] ${language} backend connection closed unexpectedly.`);
20806
+ state.status = "error";
20807
+ state.lastError = "Connection closed unexpectedly";
20808
+ await this.handleCrash(language);
20809
+ };
20810
+ transport.onerror = async (error2) => {
20811
+ const state = this.backends.get(language);
20812
+ if (!state || state.status === "stopped")
20813
+ return;
20814
+ console.error(`[BackendManager] ${language} backend transport error:`, error2);
20815
+ };
20816
+ }
20817
+ async handleCrash(language) {
20818
+ const state = this.backends.get(language);
20819
+ if (!state)
20820
+ return;
20821
+ const now = Date.now();
20822
+ if (now - state.lastCrashTime > 3600 * 1000) {
20823
+ state.retryCount = 0;
20824
+ }
20825
+ state.retryCount++;
20826
+ state.lastCrashTime = now;
20827
+ const maxRetries = 5;
20828
+ if (state.retryCount > maxRetries) {
20829
+ console.error(`[BackendManager] ${language} crashed too many times (${state.retryCount}). Giving up.`);
20830
+ state.status = "error";
20831
+ state.lastError = `Crashed ${state.retryCount} times. Manual restart required.`;
20832
+ return;
20833
+ }
20834
+ const backoffMs = Math.min(1000 * Math.pow(2, state.retryCount - 1), 30000);
20835
+ console.error(`[BackendManager] Restarting ${language} in ${backoffMs}ms (Attempt ${state.retryCount}/${maxRetries})...`);
20836
+ await new Promise((resolve2) => setTimeout(resolve2, backoffMs));
20837
+ const currentState = this.backends.get(language);
20838
+ if (!currentState || currentState.status === "stopped")
20839
+ return;
20840
+ try {
20841
+ this.backends.delete(language);
20842
+ const startPromise = this.startBackend(language);
20843
+ this.startPromises.set(language, startPromise);
20844
+ await startPromise;
20845
+ this.startPromises.delete(language);
20846
+ const newState = this.backends.get(language);
20847
+ if (newState) {
20848
+ newState.retryCount = state.retryCount;
20849
+ newState.lastCrashTime = state.lastCrashTime;
20850
+ }
20851
+ console.error(`[BackendManager] ${language} recovered successfully.`);
20852
+ } catch (error2) {
20853
+ console.error(`[BackendManager] Failed to recover ${language}:`, error2);
20854
+ }
20855
+ }
20620
20856
  async startBackend(language) {
20621
20857
  const backendConfig = getBackendCommand(language, this.config);
20622
20858
  if (!backendConfig) {
@@ -20641,9 +20877,13 @@ class BackendManager {
20641
20877
  transport,
20642
20878
  tools: [],
20643
20879
  status: "starting",
20644
- restartCount: 0
20880
+ restartCount: 0,
20881
+ lastUsed: Date.now(),
20882
+ retryCount: 0,
20883
+ lastCrashTime: 0
20645
20884
  };
20646
20885
  this.backends.set(language, state);
20886
+ this.monitorBackend(language, transport);
20647
20887
  try {
20648
20888
  await client.connect(transport);
20649
20889
  const serverInfo = client.getServerVersion();
@@ -20671,6 +20911,7 @@ class BackendManager {
20671
20911
  throw new Error(`${language} backend is not ready: ${state.lastError}`);
20672
20912
  }
20673
20913
  try {
20914
+ state.lastUsed = Date.now();
20674
20915
  const result = await state.client.callTool({
20675
20916
  name: toolName,
20676
20917
  arguments: args
@@ -20686,6 +20927,7 @@ class BackendManager {
20686
20927
  if (!restarted || restarted.status !== "ready") {
20687
20928
  throw new Error(`${language} backend failed to restart`);
20688
20929
  }
20930
+ restarted.lastUsed = Date.now();
20689
20931
  const result = await restarted.client.callTool({
20690
20932
  name: toolName,
20691
20933
  arguments: args
@@ -20702,13 +20944,7 @@ class BackendManager {
20702
20944
  }
20703
20945
  async getAllTools() {
20704
20946
  const result = new Map;
20705
- const languages = [];
20706
- if (this.config.python.enabled)
20707
- languages.push("python");
20708
- if (this.config.typescript.enabled)
20709
- languages.push("typescript");
20710
- if (this.config.vue.enabled)
20711
- languages.push("vue");
20947
+ const languages = Object.keys(this.config.languages).filter((lang) => this.config.languages[lang].enabled);
20712
20948
  await Promise.all(languages.map(async (lang) => {
20713
20949
  try {
20714
20950
  const tools = await this.getTools(lang);
@@ -20720,6 +20956,20 @@ class BackendManager {
20720
20956
  }));
20721
20957
  return result;
20722
20958
  }
20959
+ async shutdownBackend(language) {
20960
+ const state = this.backends.get(language);
20961
+ if (!state)
20962
+ return;
20963
+ try {
20964
+ console.error(`[BackendManager] Shutting down ${language}...`);
20965
+ await state.transport.close();
20966
+ await state.client.close();
20967
+ } catch (error2) {
20968
+ console.error(`[BackendManager] Error closing ${language}:`, error2);
20969
+ } finally {
20970
+ this.backends.delete(language);
20971
+ }
20972
+ }
20723
20973
  getStatus() {
20724
20974
  const status = {};
20725
20975
  for (const [lang, state] of this.backends) {
@@ -20732,26 +20982,16 @@ class BackendManager {
20732
20982
  serverName: state.serverInfo?.name
20733
20983
  };
20734
20984
  }
20735
- if (this.config.python.enabled && !this.backends.has("python")) {
20736
- status["python"] = { status: "not_started", tools: 0, restartCount: 0 };
20737
- }
20738
- if (this.config.typescript.enabled && !this.backends.has("typescript")) {
20739
- status["typescript"] = { status: "not_started", tools: 0, restartCount: 0 };
20740
- }
20741
- if (this.config.vue.enabled && !this.backends.has("vue")) {
20742
- status["vue"] = { status: "not_started", tools: 0, restartCount: 0 };
20985
+ for (const [lang, config2] of Object.entries(this.config.languages)) {
20986
+ if (config2.enabled && !this.backends.has(lang)) {
20987
+ status[lang] = { status: "not_started", tools: 0, restartCount: 0 };
20988
+ }
20743
20989
  }
20744
20990
  return status;
20745
20991
  }
20746
20992
  getVersions() {
20747
20993
  const versions2 = [];
20748
- const languages = [];
20749
- if (this.config.python.enabled)
20750
- languages.push("python");
20751
- if (this.config.typescript.enabled)
20752
- languages.push("typescript");
20753
- if (this.config.vue.enabled)
20754
- languages.push("vue");
20994
+ const languages = Object.keys(this.config.languages).filter((lang) => this.config.languages[lang].enabled);
20755
20995
  for (const lang of languages) {
20756
20996
  const backendConfig = getBackendCommand(lang, this.config);
20757
20997
  const state = this.backends.get(lang);
@@ -20787,6 +21027,10 @@ class BackendManager {
20787
21027
  }
20788
21028
  async shutdown() {
20789
21029
  console.error("[BackendManager] Shutting down all backends...");
21030
+ if (this.idleCheckInterval) {
21031
+ clearInterval(this.idleCheckInterval);
21032
+ this.idleCheckInterval = null;
21033
+ }
20790
21034
  const shutdownPromises = Array.from(this.backends.entries()).map(async ([lang, state]) => {
20791
21035
  try {
20792
21036
  console.error(`[BackendManager] Closing ${lang}...`);
@@ -20804,13 +21048,13 @@ class BackendManager {
20804
21048
  }
20805
21049
 
20806
21050
  // src/tools/meta.ts
20807
- import { readFileSync } from "fs";
20808
- import { dirname, join } from "path";
20809
- import { fileURLToPath } from "url";
20810
- var __dirname2 = dirname(fileURLToPath(import.meta.url));
21051
+ import { readFileSync as readFileSync2 } from "fs";
21052
+ import { dirname as dirname2, join as join2 } from "path";
21053
+ import { fileURLToPath as fileURLToPath2 } from "url";
21054
+ var __dirname2 = dirname2(fileURLToPath2(import.meta.url));
20811
21055
  var serverVersion = "0.1.0";
20812
21056
  try {
20813
- const packageJson = JSON.parse(readFileSync(join(__dirname2, "../../package.json"), "utf-8"));
21057
+ const packageJson = JSON.parse(readFileSync2(join2(__dirname2, "../../package.json"), "utf-8"));
20814
21058
  serverVersion = packageJson.version;
20815
21059
  } catch {}
20816
21060
  async function status(backendManager, config2) {
@@ -21525,6 +21769,46 @@ function registerPrompts(server) {
21525
21769
  }
21526
21770
  ]
21527
21771
  }));
21772
+ server.registerPrompt("explore-project", {
21773
+ description: "Analyze the current project structure and key files using semantic summaries",
21774
+ args: {
21775
+ path: exports_external.string().optional().describe("Project root path (optional, defaults to active workspace)")
21776
+ }
21777
+ }, async ({ path: path2 }) => ({
21778
+ messages: [{
21779
+ role: "user",
21780
+ content: {
21781
+ type: "text",
21782
+ text: `Please explore the project at '${path2 || "current workspace"}'.
21783
+
21784
+ Recommended Workflow:
21785
+ 1. List files to understand the directory structure (use 'ls' or 'glob').
21786
+ 2. Identify key entry points (e.g., main.py, index.ts, App.vue, pyproject.toml, package.json).
21787
+ 3. Use 'summarize_file' on these entry points to extract high-level symbols (classes/functions) without reading full content.
21788
+ 4. Report back with a structural summary of the project.`
21789
+ }
21790
+ }]
21791
+ }));
21792
+ server.registerPrompt("debug-file", {
21793
+ description: "Deeply analyze a file for errors using diagnostics and inlay hints",
21794
+ args: {
21795
+ file: exports_external.string().describe("Path to the file to debug")
21796
+ }
21797
+ }, async ({ file }) => ({
21798
+ messages: [{
21799
+ role: "user",
21800
+ content: {
21801
+ type: "text",
21802
+ text: `Please debug and analyze the file '${file}'.
21803
+
21804
+ Recommended Workflow:
21805
+ 1. Run 'diagnostics' on the file to identify syntax errors, type mismatches, or unused imports.
21806
+ 2. Use 'read_file_with_hints' to read the content. This will reveal inferred types and parameter names, making it easier to spot logic errors.
21807
+ 3. If errors are found, check specific locations with 'code_action' to see if auto-fixes (like 'Organize Imports') are available.
21808
+ 4. Explain the findings and propose fixes.`
21809
+ }
21810
+ }]
21811
+ }));
21528
21812
  server.registerPrompt("lsp-quick-start", {
21529
21813
  description: "Quick start guide - essential LSP workflow patterns"
21530
21814
  }, async () => ({
@@ -21537,10 +21821,14 @@ function registerPrompts(server) {
21537
21821
 
21538
21822
  ## Key Workflows
21539
21823
 
21540
- ### 1. Search → LSP Tools
21541
-
21542
- search() returns positions (file, line, column) that work directly with other LSP tools:
21824
+ ### 1. Smart Exploration
21825
+ Instead of reading raw code, use:
21826
+ \`\`\`
21827
+ summarize_file(file) → Get outline of classes/functions
21828
+ read_file_with_hints(file) → Read code with type/param annotations
21829
+ \`\`\`
21543
21830
 
21831
+ ### 2. Search → LSP Tools
21544
21832
  \`\`\`
21545
21833
  search("ClassName") → get positions
21546
21834
  hover(file, line, column) → get type info
@@ -21548,53 +21836,15 @@ definition(...) → jump to definition
21548
21836
  references(...) → find usages
21549
21837
  \`\`\`
21550
21838
 
21551
- ### 2. Learn API Before Using
21552
-
21553
- Before using unfamiliar methods/classes:
21554
-
21555
- \`\`\`
21556
- hover(file, line, column) → get documentation
21557
- signature_help(...) → get parameter details
21558
- → Then write correct code
21559
- \`\`\`
21560
-
21561
- ### 3. Always Verify with Diagnostics
21562
-
21563
- After any code changes:
21564
-
21839
+ ### 3. Debug & Fix
21565
21840
  \`\`\`
21566
- Edit code
21567
- diagnostics(path) → check for errors
21568
- Fix issues
21569
- Repeat until clean
21570
- \`\`\`
21571
-
21572
- ## Quick Reference
21573
-
21574
- \`\`\`
21575
- # Navigation (use python/ or typescript/ prefix)
21576
- hover(file, line, column) → Get type info and docs
21577
- definition(file, line, column) → Jump to definition
21578
- references(file, line, column) → Find all usages
21579
-
21580
- # Analysis
21581
- symbols(file) → List all symbols in file
21582
- diagnostics(path) → Type errors and warnings
21583
- search(pattern, path) → Search code, returns LSP positions
21584
-
21585
- # Refactoring (Python only, modifies files)
21586
- rename(file, line, column, new_name) → Rename symbol
21587
- move(file, line, column, destination) → Move to another module
21588
- change_signature(file, line, column, ...) → Modify function params
21841
+ diagnostics(path) → Check for errors
21842
+ code_action(file, line, col) → Get quick fixes (e.g. Organize Imports)
21843
+ run_code_action(...) → Apply fix
21589
21844
  \`\`\`
21590
21845
 
21591
21846
  ## Tool Namespacing
21592
-
21593
- Tools are namespaced by language:
21594
- - \`python/hover\`, \`python/definition\`, etc.
21595
- - \`typescript/hover\`, \`typescript/definition\`, etc.
21596
-
21597
- Or use file extension auto-detection by providing a file path.
21847
+ Tools are unified! Just provide the file path, and the server auto-detects the language (Python/TS/Vue).
21598
21848
  `
21599
21849
  }
21600
21850
  }
@@ -21609,132 +21859,12 @@ var config2 = loadConfig();
21609
21859
  var backendManager = new BackendManager(config2);
21610
21860
  var startedBackends = new Set;
21611
21861
  var registeredTools = new Set;
21862
+ var activeWorkspacePath = null;
21612
21863
  var server = new McpServer({
21613
21864
  name: "lsp-mcp",
21614
21865
  version: packageJson.version
21615
21866
  });
21616
21867
  registerPrompts(server);
21617
- function jsonSchemaToZod(schema) {
21618
- const result = {};
21619
- if (!schema || !schema.properties) {
21620
- return result;
21621
- }
21622
- const required2 = new Set(schema.required || []);
21623
- for (const [key, prop] of Object.entries(schema.properties)) {
21624
- let zodType = schemaToZod(prop);
21625
- if (prop.description) {
21626
- zodType = zodType.describe(prop.description);
21627
- }
21628
- if (prop.default !== undefined) {
21629
- zodType = zodType.default(prop.default);
21630
- }
21631
- if (!required2.has(key)) {
21632
- zodType = zodType.optional();
21633
- }
21634
- result[key] = zodType;
21635
- }
21636
- return result;
21637
- }
21638
- function schemaToZod(schema) {
21639
- if (!schema)
21640
- return exports_external.any();
21641
- if (schema.oneOf || schema.anyOf) {
21642
- const variants = schema.oneOf ?? schema.anyOf;
21643
- const mapped = variants.map((variant) => schemaToZod(variant));
21644
- if (mapped.length === 1)
21645
- return mapped[0];
21646
- if (mapped.length > 1)
21647
- return exports_external.union(mapped);
21648
- return exports_external.any();
21649
- }
21650
- if (schema.allOf) {
21651
- const variants = schema.allOf;
21652
- if (variants.length === 0)
21653
- return exports_external.any();
21654
- return variants.map((variant) => schemaToZod(variant)).reduce((acc, next) => exports_external.intersection(acc, next));
21655
- }
21656
- if (schema.enum && schema.type === "string") {
21657
- return exports_external.enum(schema.enum);
21658
- }
21659
- switch (schema.type) {
21660
- case "string": {
21661
- let zodType = exports_external.string();
21662
- if (schema.minLength !== undefined)
21663
- zodType = zodType.min(schema.minLength);
21664
- if (schema.maxLength !== undefined)
21665
- zodType = zodType.max(schema.maxLength);
21666
- if (schema.pattern) {
21667
- try {
21668
- zodType = zodType.regex(new RegExp(schema.pattern));
21669
- } catch {}
21670
- }
21671
- return zodType;
21672
- }
21673
- case "number":
21674
- case "integer": {
21675
- let zodType = exports_external.number();
21676
- if (schema.type === "integer") {
21677
- zodType = zodType.int();
21678
- }
21679
- if (schema.exclusiveMinimum !== undefined) {
21680
- zodType = zodType.gt(schema.exclusiveMinimum);
21681
- }
21682
- if (schema.minimum !== undefined) {
21683
- zodType = zodType.gte(schema.minimum);
21684
- }
21685
- if (schema.maximum !== undefined) {
21686
- zodType = zodType.lte(schema.maximum);
21687
- }
21688
- return zodType;
21689
- }
21690
- case "boolean":
21691
- return exports_external.boolean();
21692
- case "array":
21693
- return exports_external.array(schemaToZod(schema.items ?? {}));
21694
- case "object": {
21695
- if (schema.properties) {
21696
- const shape = {};
21697
- const required2 = new Set(schema.required || []);
21698
- for (const [key, prop] of Object.entries(schema.properties)) {
21699
- let propSchema = schemaToZod(prop);
21700
- if (prop.description) {
21701
- propSchema = propSchema.describe(prop.description);
21702
- }
21703
- if (prop.default !== undefined) {
21704
- propSchema = propSchema.default(prop.default);
21705
- }
21706
- if (!required2.has(key)) {
21707
- propSchema = propSchema.optional();
21708
- }
21709
- shape[key] = propSchema;
21710
- }
21711
- return exports_external.object(shape).passthrough();
21712
- }
21713
- return exports_external.record(exports_external.any());
21714
- }
21715
- default:
21716
- return exports_external.any();
21717
- }
21718
- }
21719
- function registerBackendTools(language, tools) {
21720
- let count = 0;
21721
- for (const tool of tools) {
21722
- const namespacedName = `${language}_${tool.name}`;
21723
- if (registeredTools.has(namespacedName)) {
21724
- continue;
21725
- }
21726
- const zodSchema = jsonSchemaToZod(tool.inputSchema);
21727
- server.registerTool(namespacedName, {
21728
- description: tool.description || `${language} ${tool.name} tool`,
21729
- inputSchema: zodSchema
21730
- }, async (args) => backendManager.callTool(language, tool.name, args));
21731
- console.error(`[lsp-mcp] Registered ${namespacedName}`);
21732
- registeredTools.add(namespacedName);
21733
- count++;
21734
- }
21735
- server.sendToolListChanged();
21736
- return count;
21737
- }
21738
21868
  async function startAndRegisterBackend(language) {
21739
21869
  if (startedBackends.has(language)) {
21740
21870
  const status2 = backendManager.getStatus()[language];
@@ -21743,11 +21873,10 @@ async function startAndRegisterBackend(language) {
21743
21873
  }
21744
21874
  console.error(`[lsp-mcp] Starting ${language} backend...`);
21745
21875
  try {
21746
- const tools = await backendManager.getTools(language);
21747
- const count = registerBackendTools(language, tools);
21876
+ await backendManager.getBackend(language);
21748
21877
  startedBackends.add(language);
21749
- console.error(`[lsp-mcp] ${language}: ${count} new tools registered`);
21750
- return count;
21878
+ console.error(`[lsp-mcp] ${language} backend started`);
21879
+ return 0;
21751
21880
  } catch (error2) {
21752
21881
  console.error(`[lsp-mcp] Failed to start ${language} backend:`, error2);
21753
21882
  throw error2;
@@ -21756,14 +21885,18 @@ async function startAndRegisterBackend(language) {
21756
21885
  async function updateAndRestartBackend(language) {
21757
21886
  console.error(`[lsp-mcp] Updating ${language} backend...`);
21758
21887
  const result = await backendManager.restartBackend(language);
21759
- const tools = await backendManager.getTools(language);
21760
- const newlyRegistered = registerBackendTools(language, tools);
21761
21888
  startedBackends.add(language);
21762
- console.error(`[lsp-mcp] ${language} backend updated (${newlyRegistered} new tools registered)`);
21763
21889
  return result;
21764
21890
  }
21765
21891
  server.registerTool("status", { description: "Get status of all LSP backends and server configuration" }, async () => status(backendManager, config2));
21766
21892
  server.registerTool("check_versions", { description: "Check versions of all backends and server. Shows installed versions and how to check for updates." }, async () => checkVersions(backendManager, config2));
21893
+ server.registerTool("reload_config", { description: "Reload configuration from environment variables. Useful for changing settings without restarting the server." }, async () => {
21894
+ const newConfig = loadConfig();
21895
+ backendManager.updateConfig(newConfig);
21896
+ return {
21897
+ content: [{ type: "text", text: JSON.stringify({ success: true, message: "Configuration reloaded", config: newConfig }) }]
21898
+ };
21899
+ });
21767
21900
  server.registerTool("switch_python_backend", {
21768
21901
  description: "Switch the Python backend provider (requires restart)",
21769
21902
  inputSchema: switchPythonBackendSchema
@@ -21779,62 +21912,280 @@ server.registerTool("update_backend", {
21779
21912
  description: "Update a backend to the latest version. This will restart the backend with the newest version available.",
21780
21913
  inputSchema: updateBackendSchema
21781
21914
  }, async ({ language }) => updateBackend(language, backendManager, config2, updateAndRestartBackend));
21782
- var KNOWN_TOOLS = {
21915
+ var UNIFIED_TOOLS = [
21916
+ { name: "hover", description: "Get type information and documentation at a specific position", schema: { file: exports_external.string(), line: exports_external.number().int().positive(), column: exports_external.number().int().positive() } },
21917
+ { name: "definition", description: "Go to definition of a symbol at a specific position", schema: { file: exports_external.string(), line: exports_external.number().int().positive(), column: exports_external.number().int().positive() } },
21918
+ { name: "references", description: "Find all references to a symbol at a specific position", schema: { file: exports_external.string(), line: exports_external.number().int().positive(), column: exports_external.number().int().positive() } },
21919
+ { name: "completions", description: "Get code completion suggestions at a specific position", schema: { file: exports_external.string(), line: exports_external.number().int().positive(), column: exports_external.number().int().positive(), limit: exports_external.number().int().positive().default(20).optional() } },
21920
+ { name: "signature_help", description: "Get function signature help at a specific position", schema: { file: exports_external.string(), line: exports_external.number().int().positive(), column: exports_external.number().int().positive() } },
21921
+ { name: "symbols", description: "Extract symbols (classes, functions, methods, variables) from a file", schema: { file: exports_external.string(), query: exports_external.string().optional() } },
21922
+ { name: "diagnostics", description: "Get type errors and warnings for a file or directory", schema: { path: exports_external.string() } },
21923
+ { name: "rename", description: "Preview renaming a symbol at a specific position", schema: { file: exports_external.string(), line: exports_external.number().int().positive(), column: exports_external.number().int().positive(), newName: exports_external.string() } },
21924
+ { name: "update_document", description: "Update file content for incremental analysis without writing to disk", schema: { file: exports_external.string(), content: exports_external.string() } },
21925
+ { name: "search", description: "Search for a pattern in files using ripgrep. Uses active workspace if path is omitted.", schema: { pattern: exports_external.string(), path: exports_external.string().optional(), glob: exports_external.string().optional() } },
21926
+ { name: "summarize_file", description: "Get a high-level outline of a file (classes, functions, methods) to understand its structure without reading the full content.", schema: { file: exports_external.string() } },
21927
+ { name: "read_file_with_hints", description: "Read file content with inlay hints (type annotations, parameter names) inserted as comments. Useful for understanding complex code.", schema: { file: exports_external.string() } },
21928
+ { name: "code_action", description: "Get available code actions (refactors and quick fixes) at a specific position", schema: { file: exports_external.string(), line: exports_external.number().int().positive(), column: exports_external.number().int().positive() } },
21929
+ { name: "run_code_action", description: "Apply a code action (refactor or quick fix)", schema: { file: exports_external.string(), line: exports_external.number().int().positive(), column: exports_external.number().int().positive(), kind: exports_external.enum(["refactor", "quickfix"]), name: exports_external.string(), actionName: exports_external.string().optional(), preview: exports_external.boolean().default(false).optional() } }
21930
+ ];
21931
+ function applyInlayHints(content, hints, language) {
21932
+ const lines = content.split(`
21933
+ `);
21934
+ const resultLines = [...lines];
21935
+ const normalizedHints = hints.map((h) => {
21936
+ let line, char;
21937
+ let label = "";
21938
+ if (typeof h.label === "string")
21939
+ label = h.label;
21940
+ else if (Array.isArray(h.label))
21941
+ label = h.label.map((p) => p.value).join("");
21942
+ if (language === "typescript") {
21943
+ line = h.position.line - 1;
21944
+ char = h.position.column - 1;
21945
+ } else {
21946
+ line = h.position.line;
21947
+ char = h.position.character;
21948
+ }
21949
+ return { line, char, label, kind: h.kind, paddingLeft: h.paddingLeft, paddingRight: h.paddingRight };
21950
+ }).sort((a, b) => {
21951
+ if (a.line !== b.line)
21952
+ return b.line - a.line;
21953
+ return b.char - a.char;
21954
+ });
21955
+ for (const hint of normalizedHints) {
21956
+ if (hint.line < 0 || hint.line >= resultLines.length)
21957
+ continue;
21958
+ const lineContent = resultLines[hint.line];
21959
+ if (hint.char < 0)
21960
+ continue;
21961
+ const prefix = lineContent.substring(0, hint.char);
21962
+ const suffix = lineContent.substring(hint.char);
21963
+ let hintText = hint.label;
21964
+ let formatted = "";
21965
+ if (hint.kind === 1) {
21966
+ formatted = `/*: ${hintText.trim()}*/`;
21967
+ if (!hint.paddingLeft && prefix.length > 0 && !prefix.endsWith(" "))
21968
+ formatted = " " + formatted;
21969
+ } else if (hint.kind === 2) {
21970
+ formatted = `/*${hintText.trim()}:*/`;
21971
+ if (!hint.paddingRight)
21972
+ formatted = formatted + " ";
21973
+ } else {
21974
+ formatted = `/*${hintText}*/`;
21975
+ }
21976
+ resultLines[hint.line] = prefix + formatted + suffix;
21977
+ }
21978
+ return resultLines.join(`
21979
+ `);
21980
+ }
21981
+ function formatSymbolsToMarkdown(symbols, depth = 0) {
21982
+ let output = "";
21983
+ const indent = " ".repeat(depth);
21984
+ for (const symbol of symbols) {
21985
+ const kind = symbol.kind ? `[${symbol.kind.toLowerCase()}]` : "";
21986
+ const line = symbol.range?.start?.line ?? symbol.line ?? "?";
21987
+ output += `${indent}- ${kind} ${symbol.name} (line ${line})
21988
+ `;
21989
+ if (symbol.children && symbol.children.length > 0) {
21990
+ output += formatSymbolsToMarkdown(symbol.children, depth + 1);
21991
+ }
21992
+ }
21993
+ return output;
21994
+ }
21995
+ var LANGUAGE_SPECIFIC_TOOLS = {
21783
21996
  python: [
21784
- { name: "hover", description: "Get documentation for the symbol at the given position", schema: { file: exports_external.string(), line: exports_external.number().int(), column: exports_external.number().int() } },
21785
- { name: "definition", description: "Get the definition location for the symbol at the given position", schema: { file: exports_external.string(), line: exports_external.number().int(), column: exports_external.number().int() } },
21786
- { name: "references", description: "Find all references to the symbol at the given position", schema: { file: exports_external.string(), line: exports_external.number().int(), column: exports_external.number().int() } },
21787
- { name: "completions", description: "Get code completion suggestions at the given position", schema: { file: exports_external.string(), line: exports_external.number().int(), column: exports_external.number().int() } },
21788
- { name: "symbols", description: "Get symbols from a Python file", schema: { file: exports_external.string(), query: exports_external.string().optional() } },
21789
- { name: "diagnostics", description: "Get type errors and warnings for a Python file or directory", schema: { path: exports_external.string() } },
21790
- { name: "rename", description: "Rename the symbol at the given position", schema: { file: exports_external.string(), line: exports_external.number().int(), column: exports_external.number().int(), new_name: exports_external.string() } },
21791
- { name: "signature_help", description: "Get function signature information at the given position", schema: { file: exports_external.string(), line: exports_external.number().int(), column: exports_external.number().int() } },
21792
- { name: "update_document", description: "Update file content for incremental analysis without writing to disk", schema: { file: exports_external.string(), content: exports_external.string() } },
21793
- { name: "search", description: "Search for a regex pattern in files using ripgrep", schema: { pattern: exports_external.string(), path: exports_external.string().optional(), glob: exports_external.string().optional() } },
21794
- { name: "status", description: "Get the status of the Python MCP server", schema: {} }
21997
+ { name: "move", description: "Move a function or class to another module", schema: { file: exports_external.string(), line: exports_external.number().int(), column: exports_external.number().int(), destination: exports_external.string() } },
21998
+ { name: "change_signature", description: "Change the signature of a function", schema: { file: exports_external.string(), line: exports_external.number().int(), column: exports_external.number().int(), new_params: exports_external.array(exports_external.string()).optional() } },
21999
+ { name: "function_signature", description: "Get current signature of a function", schema: { file: exports_external.string(), line: exports_external.number().int(), column: exports_external.number().int() } }
21795
22000
  ],
21796
22001
  typescript: [
21797
- { name: "hover", description: "Get type information and documentation at a specific position", schema: { file: exports_external.string(), line: exports_external.number().int().positive(), column: exports_external.number().int().positive() } },
21798
- { name: "definition", description: "Go to definition of a symbol at a specific position", schema: { file: exports_external.string(), line: exports_external.number().int().positive(), column: exports_external.number().int().positive() } },
21799
- { name: "references", description: "Find all references to a symbol at a specific position", schema: { file: exports_external.string(), line: exports_external.number().int().positive(), column: exports_external.number().int().positive() } },
21800
- { name: "completions", description: "Get code completion suggestions at a specific position", schema: { file: exports_external.string(), line: exports_external.number().int().positive(), column: exports_external.number().int().positive(), limit: exports_external.number().int().positive().default(20).optional() } },
21801
- { name: "signature_help", description: "Get function signature help at a specific position", schema: { file: exports_external.string(), line: exports_external.number().int().positive(), column: exports_external.number().int().positive() } },
21802
- { name: "symbols", description: "Extract symbols (classes, functions, methods, variables) from a file", schema: { file: exports_external.string(), query: exports_external.string().optional() } },
21803
- { name: "diagnostics", description: "Get type errors and warnings for a TypeScript/JavaScript file", schema: { path: exports_external.string() } },
21804
- { name: "rename", description: "Preview renaming a symbol at a specific position", schema: { file: exports_external.string(), line: exports_external.number().int().positive(), column: exports_external.number().int().positive(), newName: exports_external.string() } },
21805
- { name: "update_document", description: "Update file content for incremental analysis without writing to disk", schema: { file: exports_external.string(), content: exports_external.string() } },
21806
- { name: "status", description: "Check TypeScript environment status for a project", schema: { file: exports_external.string() } },
21807
- { name: "search", description: "Search for a pattern in files using ripgrep", schema: { pattern: exports_external.string(), path: exports_external.string().optional(), glob: exports_external.string().optional() } },
21808
22002
  { name: "move", description: "Move a function, class, or variable to a new file", schema: { file: exports_external.string(), line: exports_external.number().int().positive(), column: exports_external.number().int().positive(), destination: exports_external.string().optional(), preview: exports_external.boolean().default(false).optional() } },
21809
- { name: "function_signature", description: "Get the current signature of a function at a specific position", schema: { file: exports_external.string(), line: exports_external.number().int().positive(), column: exports_external.number().int().positive() } },
21810
- { name: "available_refactors", description: "Get available refactoring actions at a specific position", schema: { file: exports_external.string(), line: exports_external.number().int().positive(), column: exports_external.number().int().positive() } },
21811
- { name: "apply_refactor", description: "Apply a specific refactoring action at a position", schema: { file: exports_external.string(), line: exports_external.number().int().positive(), column: exports_external.number().int().positive(), refactorName: exports_external.string(), actionName: exports_external.string(), preview: exports_external.boolean().default(false).optional() } }
22003
+ { name: "function_signature", description: "Get current signature of a function", schema: { file: exports_external.string(), line: exports_external.number().int().positive(), column: exports_external.number().int().positive() } }
21812
22004
  ],
21813
- vue: [
21814
- { name: "hover", description: "Get type information and documentation at a specific position in a Vue SFC file", schema: { file: exports_external.string(), line: exports_external.number().int().positive(), column: exports_external.number().int().positive() } },
21815
- { name: "definition", description: "Go to definition of a symbol at a specific position in a Vue SFC file", schema: { file: exports_external.string(), line: exports_external.number().int().positive(), column: exports_external.number().int().positive() } },
21816
- { name: "references", description: "Find all references to a symbol at a specific position in a Vue SFC file", schema: { file: exports_external.string(), line: exports_external.number().int().positive(), column: exports_external.number().int().positive() } },
21817
- { name: "completions", description: "Get code completion suggestions at a specific position in a Vue SFC file", schema: { file: exports_external.string(), line: exports_external.number().int().positive(), column: exports_external.number().int().positive(), limit: exports_external.number().int().positive().default(20).optional() } },
21818
- { name: "signature_help", description: "Get function signature help at a specific position in a Vue SFC file", schema: { file: exports_external.string(), line: exports_external.number().int().positive(), column: exports_external.number().int().positive() } },
21819
- { name: "diagnostics", description: "Get type errors and warnings for Vue SFC files", schema: { path: exports_external.string() } },
21820
- { name: "update_document", description: "Update Vue file content for incremental analysis without writing to disk", schema: { file: exports_external.string(), content: exports_external.string() } },
21821
- { name: "symbols", description: "Extract symbols (variables, functions, components) from a Vue SFC file", schema: { file: exports_external.string(), query: exports_external.string().optional() } },
21822
- { name: "rename", description: "Preview renaming a symbol at a specific position", schema: { file: exports_external.string(), line: exports_external.number().int().positive(), column: exports_external.number().int().positive(), newName: exports_external.string() } },
21823
- { name: "search", description: "Search for a pattern in Vue files using ripgrep", schema: { pattern: exports_external.string(), path: exports_external.string().optional(), glob: exports_external.string().optional() } },
21824
- { name: "status", description: "Check Vue Language Server status for a project", schema: { file: exports_external.string() } }
21825
- ]
22005
+ vue: []
21826
22006
  };
21827
- function preRegisterKnownTools() {
21828
- const languages = [];
21829
- if (config2.python.enabled)
21830
- languages.push("python");
21831
- if (config2.typescript.enabled)
21832
- languages.push("typescript");
21833
- if (config2.vue.enabled)
21834
- languages.push("vue");
21835
- let totalCount = 0;
21836
- for (const language of languages) {
21837
- const tools = KNOWN_TOOLS[language];
22007
+ server.registerTool("switch_workspace", {
22008
+ description: "Switch the active workspace for ALL backends simultaneously. This clears caches and refocuses code intelligence on the new project root.",
22009
+ inputSchema: {
22010
+ path: exports_external.string().describe("Absolute path to the new project root directory")
22011
+ }
22012
+ }, async ({ path: workspacePath }) => {
22013
+ activeWorkspacePath = workspacePath;
22014
+ const results = {};
22015
+ const languages = Object.keys(config2.languages).filter((lang) => config2.languages[lang].enabled);
22016
+ await Promise.all(languages.map(async (lang) => {
22017
+ try {
22018
+ if (startedBackends.has(lang)) {
22019
+ const result = await backendManager.callTool(lang, "switch_workspace", { path: workspacePath });
22020
+ results[lang] = JSON.parse(result.content[0].text);
22021
+ } else {
22022
+ results[lang] = { status: "not_started", message: "Workspace will be set when backend starts" };
22023
+ }
22024
+ } catch (error2) {
22025
+ results[lang] = { error: String(error2) };
22026
+ }
22027
+ }));
22028
+ return {
22029
+ content: [
22030
+ {
22031
+ type: "text",
22032
+ text: JSON.stringify({
22033
+ success: true,
22034
+ workspace: workspacePath,
22035
+ results
22036
+ }, null, 2)
22037
+ }
22038
+ ]
22039
+ };
22040
+ });
22041
+ function preRegisterTools() {
22042
+ for (const tool of UNIFIED_TOOLS) {
22043
+ server.registerTool(tool.name, {
22044
+ description: `${tool.description} (unified tool, routes automatically by file extension)`,
22045
+ inputSchema: tool.schema
22046
+ }, async (args) => {
22047
+ const filePath = args.file || args.path;
22048
+ if (tool.name === "search" && !filePath) {
22049
+ const languages = Object.keys(config2.languages).filter((lang) => config2.languages[lang].enabled);
22050
+ const results = [];
22051
+ for (const lang of languages) {
22052
+ if (startedBackends.has(lang)) {
22053
+ try {
22054
+ const res = await backendManager.callTool(lang, "search", args);
22055
+ results.push(JSON.parse(res.content[0].text));
22056
+ } catch (e) {}
22057
+ }
22058
+ }
22059
+ if (results.length === 0) {
22060
+ return { content: [{ type: "text", text: JSON.stringify({ matches: [], count: 0, message: "No active backends to search in. Please specify a file path to auto-start a backend." }) }] };
22061
+ }
22062
+ const allMatches = results.flatMap((r) => r.matches || []);
22063
+ return { content: [{ type: "text", text: JSON.stringify({ matches: allMatches, count: allMatches.length }) }] };
22064
+ }
22065
+ if (!filePath) {
22066
+ return {
22067
+ content: [{ type: "text", text: JSON.stringify({ error: "Missing 'file' or 'path' argument required for unified routing" }) }]
22068
+ };
22069
+ }
22070
+ const language = inferLanguageFromPath(filePath, config2);
22071
+ if (!language) {
22072
+ return {
22073
+ content: [
22074
+ {
22075
+ type: "text",
22076
+ text: JSON.stringify({
22077
+ error: "Unsupported File Type",
22078
+ message: `Cannot determine language for file '${filePath}'. Check configuration for supported extensions.`
22079
+ })
22080
+ }
22081
+ ]
22082
+ };
22083
+ }
22084
+ if (!startedBackends.has(language)) {
22085
+ console.error(`[lsp-mcp] Auto-starting ${language} backend for unified ${tool.name}...`);
22086
+ try {
22087
+ await backendManager.getBackend(language);
22088
+ startedBackends.add(language);
22089
+ if (activeWorkspacePath) {
22090
+ console.error(`[lsp-mcp] Syncing active workspace to ${language}: ${activeWorkspacePath}`);
22091
+ try {
22092
+ await backendManager.callTool(language, "switch_workspace", { path: activeWorkspacePath });
22093
+ } catch (syncError) {
22094
+ console.error(`[lsp-mcp] Failed to sync workspace to ${language}:`, syncError);
22095
+ }
22096
+ }
22097
+ } catch (error2) {
22098
+ return {
22099
+ content: [{ type: "text", text: JSON.stringify({ error: `Failed to start ${language} backend: ${error2}` }) }]
22100
+ };
22101
+ }
22102
+ }
22103
+ if (tool.name !== "summarize_file" && tool.name !== "read_file_with_hints") {
22104
+ const availableTools = await backendManager.getTools(language);
22105
+ const supportsTool = availableTools.some((t) => t.name === tool.name);
22106
+ if (!supportsTool) {
22107
+ return {
22108
+ content: [
22109
+ {
22110
+ type: "text",
22111
+ text: JSON.stringify({
22112
+ error: "Not Implemented",
22113
+ message: `The '${language}' backend does not support the '${tool.name}' feature yet.`,
22114
+ available_tools: availableTools.map((t) => t.name)
22115
+ })
22116
+ }
22117
+ ]
22118
+ };
22119
+ }
22120
+ }
22121
+ if (tool.name === "summarize_file") {
22122
+ try {
22123
+ const result = await backendManager.callTool(language, "symbols", args);
22124
+ const parsed = JSON.parse(result.content[0].text);
22125
+ if (parsed.error) {
22126
+ return { content: [{ type: "text", text: JSON.stringify(parsed) }] };
22127
+ }
22128
+ const symbols = parsed.symbols || [];
22129
+ const summary = formatSymbolsToMarkdown(symbols);
22130
+ return {
22131
+ content: [{
22132
+ type: "text",
22133
+ text: `File Summary for ${filePath}:
22134
+
22135
+ ${summary || "(No symbols found)"}`
22136
+ }]
22137
+ };
22138
+ } catch (error2) {
22139
+ return {
22140
+ content: [{ type: "text", text: JSON.stringify({ error: `Failed to summarize file: ${error2}` }) }]
22141
+ };
22142
+ }
22143
+ }
22144
+ if (tool.name === "read_file_with_hints") {
22145
+ try {
22146
+ let absPath = filePath;
22147
+ if (!path2.isAbsolute(filePath) && activeWorkspacePath) {
22148
+ absPath = path2.join(activeWorkspacePath, filePath);
22149
+ }
22150
+ if (!fs2.existsSync(absPath)) {
22151
+ return { content: [{ type: "text", text: JSON.stringify({ error: `File not found: ${absPath}` }) }] };
22152
+ }
22153
+ const content = fs2.readFileSync(absPath, "utf-8");
22154
+ const result = await backendManager.callTool(language, "inlay_hints", args);
22155
+ const parsed = JSON.parse(result.content[0].text);
22156
+ if (parsed.error) {
22157
+ return { content: [{ type: "text", text: JSON.stringify(parsed) }] };
22158
+ }
22159
+ const hints = parsed.hints || [];
22160
+ const contentWithHints = applyInlayHints(content, hints, language);
22161
+ return {
22162
+ content: [{
22163
+ type: "text",
22164
+ text: contentWithHints
22165
+ }]
22166
+ };
22167
+ } catch (error2) {
22168
+ return {
22169
+ content: [{ type: "text", text: JSON.stringify({ error: `Failed to read file with hints: ${error2}` }) }]
22170
+ };
22171
+ }
22172
+ }
22173
+ const backendArgs = { ...args };
22174
+ if (tool.name === "rename") {
22175
+ if (language === "python") {
22176
+ backendArgs.new_name = args.newName || args.new_name;
22177
+ } else {
22178
+ backendArgs.newName = args.newName || args.new_name;
22179
+ }
22180
+ }
22181
+ return backendManager.callTool(language, tool.name, backendArgs);
22182
+ });
22183
+ registeredTools.add(tool.name);
22184
+ }
22185
+ for (const [language, langConfig] of Object.entries(config2.languages)) {
22186
+ if (!langConfig.enabled)
22187
+ continue;
22188
+ const tools = LANGUAGE_SPECIFIC_TOOLS[language];
21838
22189
  if (!tools)
21839
22190
  continue;
21840
22191
  for (const tool of tools) {
@@ -21844,27 +22195,25 @@ function preRegisterKnownTools() {
21844
22195
  inputSchema: tool.schema
21845
22196
  }, async (args) => {
21846
22197
  if (!startedBackends.has(language)) {
21847
- console.error(`[lsp-mcp] Auto-starting ${language} backend for ${tool.name}...`);
21848
- try {
21849
- await backendManager.getBackend(language);
21850
- startedBackends.add(language);
21851
- console.error(`[lsp-mcp] ${language} backend started`);
21852
- } catch (error2) {
21853
- return {
21854
- content: [{ type: "text", text: JSON.stringify({ error: `Failed to start ${language} backend: ${error2}` }) }]
21855
- };
22198
+ await backendManager.getBackend(language);
22199
+ startedBackends.add(language);
22200
+ if (activeWorkspacePath) {
22201
+ console.error(`[lsp-mcp] Syncing active workspace to ${language}: ${activeWorkspacePath}`);
22202
+ try {
22203
+ await backendManager.callTool(language, "switch_workspace", { path: activeWorkspacePath });
22204
+ } catch (syncError) {
22205
+ console.error(`[lsp-mcp] Failed to sync workspace to ${language}:`, syncError);
22206
+ }
21856
22207
  }
21857
22208
  }
21858
22209
  return backendManager.callTool(language, tool.name, args);
21859
22210
  });
21860
22211
  registeredTools.add(namespacedName);
21861
- totalCount++;
21862
22212
  }
21863
- console.error(`[lsp-mcp] Pre-registered ${tools.length} ${language} tools`);
21864
22213
  }
21865
- console.error(`[lsp-mcp] Total: ${totalCount} tools pre-registered (backends start on first use)`);
22214
+ console.error(`[lsp-mcp] Unified and language-specific tools registered`);
21866
22215
  }
21867
- preRegisterKnownTools();
22216
+ preRegisterTools();
21868
22217
  async function gracefulShutdown(signal) {
21869
22218
  console.error(`
21870
22219
  [lsp-mcp] Received ${signal}, shutting down gracefully...`);
@@ -21883,22 +22232,16 @@ process.on("SIGINT", () => gracefulShutdown("SIGINT"));
21883
22232
  async function main() {
21884
22233
  console.error("LSP MCP Server - Unified Multi-Language Code Intelligence");
21885
22234
  console.error(` Version: ${packageJson.version}`);
21886
- console.error(" Python:", config2.python.enabled ? `enabled (${config2.python.provider})` : "disabled");
21887
- console.error(" TypeScript:", config2.typescript.enabled ? "enabled" : "disabled");
21888
- console.error(" Vue:", config2.vue.enabled ? "enabled" : "disabled");
22235
+ console.error(" Python:", config2.languages.python?.enabled ? `enabled` : "disabled");
22236
+ console.error(" TypeScript:", config2.languages.typescript?.enabled ? "enabled" : "disabled");
22237
+ console.error(" Vue:", config2.languages.vue?.enabled ? "enabled" : "disabled");
21889
22238
  console.error("");
21890
22239
  const transport = new StdioServerTransport;
21891
22240
  await server.connect(transport);
21892
22241
  if (config2.eagerStart) {
21893
22242
  console.error("Eager start enabled - starting all backends now...");
21894
- const languages = [];
21895
- if (config2.python.enabled)
21896
- languages.push("python");
21897
- if (config2.typescript.enabled)
21898
- languages.push("typescript");
21899
- if (config2.vue.enabled)
21900
- languages.push("vue");
21901
- await Promise.allSettled(languages.map(async (lang) => {
22243
+ const enabledLanguages = Object.keys(config2.languages).filter((l) => config2.languages[l].enabled);
22244
+ await Promise.allSettled(enabledLanguages.map(async (lang) => {
21902
22245
  try {
21903
22246
  await backendManager.getBackend(lang);
21904
22247
  startedBackends.add(lang);
@@ -21918,4 +22261,4 @@ main().catch((error2) => {
21918
22261
  process.exit(1);
21919
22262
  });
21920
22263
 
21921
- //# debugId=55BC37C22EC14E0664756E2164756E21
22264
+ //# debugId=52D8C4766CA64C6B64756E2164756E21