@treedy/lsp-mcp 0.1.8 → 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 (34) hide show
  1. package/dist/bundled/python/src/rope_mcp/config.py +50 -14
  2. package/dist/bundled/python/src/rope_mcp/lsp/client.py +243 -4
  3. package/dist/bundled/python/src/rope_mcp/lsp/types.py +1 -0
  4. package/dist/bundled/python/src/rope_mcp/server.py +331 -77
  5. package/dist/bundled/python/src/rope_mcp/tools/__init__.py +0 -2
  6. package/dist/bundled/typescript/dist/index.js +6129 -5891
  7. package/dist/bundled/typescript/dist/index.js.map +5 -5
  8. package/dist/bundled/vue/dist/index.js +136 -71
  9. package/dist/bundled/vue/dist/vue-service.d.ts +16 -0
  10. package/dist/index.js +567 -314
  11. package/dist/index.js.map +6 -6
  12. package/package.json +1 -1
  13. package/dist/bundled/python/src/rope_mcp/__pycache__/__init__.cpython-312.pyc +0 -0
  14. package/dist/bundled/python/src/rope_mcp/__pycache__/__init__.cpython-313.pyc +0 -0
  15. package/dist/bundled/python/src/rope_mcp/__pycache__/config.cpython-312.pyc +0 -0
  16. package/dist/bundled/python/src/rope_mcp/__pycache__/config.cpython-313.pyc +0 -0
  17. package/dist/bundled/python/src/rope_mcp/__pycache__/pyright_client.cpython-313.pyc +0 -0
  18. package/dist/bundled/python/src/rope_mcp/__pycache__/rope_client.cpython-313.pyc +0 -0
  19. package/dist/bundled/python/src/rope_mcp/__pycache__/server.cpython-312.pyc +0 -0
  20. package/dist/bundled/python/src/rope_mcp/__pycache__/server.cpython-313.pyc +0 -0
  21. package/dist/bundled/python/src/rope_mcp/lsp/__pycache__/__init__.cpython-313.pyc +0 -0
  22. package/dist/bundled/python/src/rope_mcp/lsp/__pycache__/client.cpython-313.pyc +0 -0
  23. package/dist/bundled/python/src/rope_mcp/lsp/__pycache__/types.cpython-313.pyc +0 -0
  24. package/dist/bundled/python/src/rope_mcp/tools/__pycache__/__init__.cpython-313.pyc +0 -0
  25. package/dist/bundled/python/src/rope_mcp/tools/__pycache__/change_signature.cpython-313.pyc +0 -0
  26. package/dist/bundled/python/src/rope_mcp/tools/__pycache__/completions.cpython-313.pyc +0 -0
  27. package/dist/bundled/python/src/rope_mcp/tools/__pycache__/definition.cpython-313.pyc +0 -0
  28. package/dist/bundled/python/src/rope_mcp/tools/__pycache__/diagnostics.cpython-313.pyc +0 -0
  29. package/dist/bundled/python/src/rope_mcp/tools/__pycache__/hover.cpython-313.pyc +0 -0
  30. package/dist/bundled/python/src/rope_mcp/tools/__pycache__/move.cpython-313.pyc +0 -0
  31. package/dist/bundled/python/src/rope_mcp/tools/__pycache__/references.cpython-313.pyc +0 -0
  32. package/dist/bundled/python/src/rope_mcp/tools/__pycache__/rename.cpython-313.pyc +0 -0
  33. package/dist/bundled/python/src/rope_mcp/tools/__pycache__/search.cpython-313.pyc +0 -0
  34. package/dist/bundled/python/src/rope_mcp/tools/__pycache__/symbols.cpython-313.pyc +0 -0
package/dist/index.js CHANGED
@@ -19837,32 +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
19844
  import * as path from "path";
19843
19845
  import * as fs from "fs";
19846
+ import * as os from "os";
19844
19847
  import { fileURLToPath } from "url";
19845
- function loadConfig() {
19846
- const pythonEnabled = getEnvBool("LSP_MCP_PYTHON_ENABLED", true);
19847
- const pythonProvider = getEnvString("LSP_MCP_PYTHON_PROVIDER", "python-lsp-mcp");
19848
- const typescriptEnabled = getEnvBool("LSP_MCP_TYPESCRIPT_ENABLED", true);
19849
- const vueEnabled = getEnvBool("LSP_MCP_VUE_ENABLED", true);
19850
- const autoUpdate = getEnvBool("LSP_MCP_AUTO_UPDATE", true);
19851
- const eagerStart = getEnvBool("LSP_MCP_EAGER_START", false);
19852
- return {
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: {
19853
19855
  python: {
19854
- enabled: pythonEnabled,
19855
- provider: pythonProvider
19856
+ enabled: true,
19857
+ extensions: DEFAULT_EXTENSIONS.python
19856
19858
  },
19857
19859
  typescript: {
19858
- enabled: typescriptEnabled
19860
+ enabled: true,
19861
+ extensions: DEFAULT_EXTENSIONS.typescript
19859
19862
  },
19860
19863
  vue: {
19861
- enabled: vueEnabled
19862
- },
19863
- autoUpdate,
19864
- 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
+ }
19865
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;
19866
19950
  }
19867
19951
  function getEnvBool(name, defaultValue) {
19868
19952
  const value = process.env[name];
@@ -19873,6 +19957,15 @@ function getEnvBool(name, defaultValue) {
19873
19957
  function getEnvString(name, defaultValue) {
19874
19958
  return process.env[name] ?? defaultValue;
19875
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
+ }
19876
19969
  function resolveBundledBackend(name) {
19877
19970
  try {
19878
19971
  const currentDir = path.dirname(fileURLToPath(import.meta.url));
@@ -19885,11 +19978,21 @@ function resolveBundledBackend(name) {
19885
19978
  return null;
19886
19979
  }
19887
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
+ }
19888
19992
  const { autoUpdate } = config2;
19889
19993
  if (language === "python") {
19890
- if (!config2.python.enabled)
19891
- return null;
19892
- if (config2.python.provider === "pyright-mcp") {
19994
+ const provider = config2.python?.provider || "python-lsp-mcp";
19995
+ if (provider === "pyright-mcp") {
19893
19996
  const bundledPath = resolveBundledBackend("pyright");
19894
19997
  if (bundledPath) {
19895
19998
  console.error(`[Config] Using bundled pyright backend from ${bundledPath}`);
@@ -19921,8 +20024,6 @@ function getBackendCommand(language, config2) {
19921
20024
  };
19922
20025
  }
19923
20026
  } else if (language === "typescript") {
19924
- if (!config2.typescript.enabled)
19925
- return null;
19926
20027
  const bundledPath = resolveBundledBackend("typescript");
19927
20028
  if (bundledPath) {
19928
20029
  console.error(`[Config] Using bundled typescript backend from ${bundledPath}`);
@@ -19938,8 +20039,6 @@ function getBackendCommand(language, config2) {
19938
20039
  args: autoUpdate ? ["--yes", "@treedy/typescript-lsp-mcp@latest"] : ["@treedy/typescript-lsp-mcp@latest"]
19939
20040
  };
19940
20041
  } else if (language === "vue") {
19941
- if (!config2.vue.enabled)
19942
- return null;
19943
20042
  const bundledPath = resolveBundledBackend("vue");
19944
20043
  if (bundledPath) {
19945
20044
  console.error(`[Config] Using bundled vue backend from ${bundledPath}`);
@@ -20646,12 +20745,43 @@ class BackendManager {
20646
20745
  backends = new Map;
20647
20746
  startPromises = new Map;
20648
20747
  config;
20748
+ idleCheckInterval = null;
20649
20749
  constructor(config2) {
20650
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
+ }
20651
20780
  }
20652
20781
  async getBackend(language) {
20653
20782
  const existing = this.backends.get(language);
20654
20783
  if (existing && existing.status === "ready") {
20784
+ existing.lastUsed = Date.now();
20655
20785
  return existing;
20656
20786
  }
20657
20787
  const pending = this.startPromises.get(language);
@@ -20667,6 +20797,62 @@ class BackendManager {
20667
20797
  this.startPromises.delete(language);
20668
20798
  }
20669
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
+ }
20670
20856
  async startBackend(language) {
20671
20857
  const backendConfig = getBackendCommand(language, this.config);
20672
20858
  if (!backendConfig) {
@@ -20691,9 +20877,13 @@ class BackendManager {
20691
20877
  transport,
20692
20878
  tools: [],
20693
20879
  status: "starting",
20694
- restartCount: 0
20880
+ restartCount: 0,
20881
+ lastUsed: Date.now(),
20882
+ retryCount: 0,
20883
+ lastCrashTime: 0
20695
20884
  };
20696
20885
  this.backends.set(language, state);
20886
+ this.monitorBackend(language, transport);
20697
20887
  try {
20698
20888
  await client.connect(transport);
20699
20889
  const serverInfo = client.getServerVersion();
@@ -20721,6 +20911,7 @@ class BackendManager {
20721
20911
  throw new Error(`${language} backend is not ready: ${state.lastError}`);
20722
20912
  }
20723
20913
  try {
20914
+ state.lastUsed = Date.now();
20724
20915
  const result = await state.client.callTool({
20725
20916
  name: toolName,
20726
20917
  arguments: args
@@ -20736,6 +20927,7 @@ class BackendManager {
20736
20927
  if (!restarted || restarted.status !== "ready") {
20737
20928
  throw new Error(`${language} backend failed to restart`);
20738
20929
  }
20930
+ restarted.lastUsed = Date.now();
20739
20931
  const result = await restarted.client.callTool({
20740
20932
  name: toolName,
20741
20933
  arguments: args
@@ -20752,13 +20944,7 @@ class BackendManager {
20752
20944
  }
20753
20945
  async getAllTools() {
20754
20946
  const result = new Map;
20755
- const languages = [];
20756
- if (this.config.python.enabled)
20757
- languages.push("python");
20758
- if (this.config.typescript.enabled)
20759
- languages.push("typescript");
20760
- if (this.config.vue.enabled)
20761
- languages.push("vue");
20947
+ const languages = Object.keys(this.config.languages).filter((lang) => this.config.languages[lang].enabled);
20762
20948
  await Promise.all(languages.map(async (lang) => {
20763
20949
  try {
20764
20950
  const tools = await this.getTools(lang);
@@ -20770,6 +20956,20 @@ class BackendManager {
20770
20956
  }));
20771
20957
  return result;
20772
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
+ }
20773
20973
  getStatus() {
20774
20974
  const status = {};
20775
20975
  for (const [lang, state] of this.backends) {
@@ -20782,26 +20982,16 @@ class BackendManager {
20782
20982
  serverName: state.serverInfo?.name
20783
20983
  };
20784
20984
  }
20785
- if (this.config.python.enabled && !this.backends.has("python")) {
20786
- status["python"] = { status: "not_started", tools: 0, restartCount: 0 };
20787
- }
20788
- if (this.config.typescript.enabled && !this.backends.has("typescript")) {
20789
- status["typescript"] = { status: "not_started", tools: 0, restartCount: 0 };
20790
- }
20791
- if (this.config.vue.enabled && !this.backends.has("vue")) {
20792
- 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
+ }
20793
20989
  }
20794
20990
  return status;
20795
20991
  }
20796
20992
  getVersions() {
20797
20993
  const versions2 = [];
20798
- const languages = [];
20799
- if (this.config.python.enabled)
20800
- languages.push("python");
20801
- if (this.config.typescript.enabled)
20802
- languages.push("typescript");
20803
- if (this.config.vue.enabled)
20804
- languages.push("vue");
20994
+ const languages = Object.keys(this.config.languages).filter((lang) => this.config.languages[lang].enabled);
20805
20995
  for (const lang of languages) {
20806
20996
  const backendConfig = getBackendCommand(lang, this.config);
20807
20997
  const state = this.backends.get(lang);
@@ -20837,6 +21027,10 @@ class BackendManager {
20837
21027
  }
20838
21028
  async shutdown() {
20839
21029
  console.error("[BackendManager] Shutting down all backends...");
21030
+ if (this.idleCheckInterval) {
21031
+ clearInterval(this.idleCheckInterval);
21032
+ this.idleCheckInterval = null;
21033
+ }
20840
21034
  const shutdownPromises = Array.from(this.backends.entries()).map(async ([lang, state]) => {
20841
21035
  try {
20842
21036
  console.error(`[BackendManager] Closing ${lang}...`);
@@ -20854,13 +21048,13 @@ class BackendManager {
20854
21048
  }
20855
21049
 
20856
21050
  // src/tools/meta.ts
20857
- import { readFileSync } from "fs";
21051
+ import { readFileSync as readFileSync2 } from "fs";
20858
21052
  import { dirname as dirname2, join as join2 } from "path";
20859
21053
  import { fileURLToPath as fileURLToPath2 } from "url";
20860
21054
  var __dirname2 = dirname2(fileURLToPath2(import.meta.url));
20861
21055
  var serverVersion = "0.1.0";
20862
21056
  try {
20863
- const packageJson = JSON.parse(readFileSync(join2(__dirname2, "../../package.json"), "utf-8"));
21057
+ const packageJson = JSON.parse(readFileSync2(join2(__dirname2, "../../package.json"), "utf-8"));
20864
21058
  serverVersion = packageJson.version;
20865
21059
  } catch {}
20866
21060
  async function status(backendManager, config2) {
@@ -21575,6 +21769,46 @@ function registerPrompts(server) {
21575
21769
  }
21576
21770
  ]
21577
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
+ }));
21578
21812
  server.registerPrompt("lsp-quick-start", {
21579
21813
  description: "Quick start guide - essential LSP workflow patterns"
21580
21814
  }, async () => ({
@@ -21587,10 +21821,14 @@ function registerPrompts(server) {
21587
21821
 
21588
21822
  ## Key Workflows
21589
21823
 
21590
- ### 1. Search → LSP Tools
21591
-
21592
- 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
+ \`\`\`
21593
21830
 
21831
+ ### 2. Search → LSP Tools
21594
21832
  \`\`\`
21595
21833
  search("ClassName") → get positions
21596
21834
  hover(file, line, column) → get type info
@@ -21598,53 +21836,15 @@ definition(...) → jump to definition
21598
21836
  references(...) → find usages
21599
21837
  \`\`\`
21600
21838
 
21601
- ### 2. Learn API Before Using
21602
-
21603
- Before using unfamiliar methods/classes:
21604
-
21605
- \`\`\`
21606
- hover(file, line, column) → get documentation
21607
- signature_help(...) → get parameter details
21608
- → Then write correct code
21609
- \`\`\`
21610
-
21611
- ### 3. Always Verify with Diagnostics
21612
-
21613
- After any code changes:
21614
-
21615
- \`\`\`
21616
- Edit code
21617
- diagnostics(path) → check for errors
21618
- Fix issues
21619
- Repeat until clean
21620
- \`\`\`
21621
-
21622
- ## Quick Reference
21623
-
21839
+ ### 3. Debug & Fix
21624
21840
  \`\`\`
21625
- # Navigation (use python/ or typescript/ prefix)
21626
- hover(file, line, column) → Get type info and docs
21627
- definition(file, line, column) Jump to definition
21628
- references(file, line, column) → Find all usages
21629
-
21630
- # Analysis
21631
- symbols(file) → List all symbols in file
21632
- diagnostics(path) → Type errors and warnings
21633
- search(pattern, path) → Search code, returns LSP positions
21634
-
21635
- # Refactoring (Python only, modifies files)
21636
- rename(file, line, column, new_name) → Rename symbol
21637
- move(file, line, column, destination) → Move to another module
21638
- 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
21639
21844
  \`\`\`
21640
21845
 
21641
21846
  ## Tool Namespacing
21642
-
21643
- Tools are namespaced by language:
21644
- - \`python/hover\`, \`python/definition\`, etc.
21645
- - \`typescript/hover\`, \`typescript/definition\`, etc.
21646
-
21647
- 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).
21648
21848
  `
21649
21849
  }
21650
21850
  }
@@ -21659,132 +21859,12 @@ var config2 = loadConfig();
21659
21859
  var backendManager = new BackendManager(config2);
21660
21860
  var startedBackends = new Set;
21661
21861
  var registeredTools = new Set;
21862
+ var activeWorkspacePath = null;
21662
21863
  var server = new McpServer({
21663
21864
  name: "lsp-mcp",
21664
21865
  version: packageJson.version
21665
21866
  });
21666
21867
  registerPrompts(server);
21667
- function jsonSchemaToZod(schema) {
21668
- const result = {};
21669
- if (!schema || !schema.properties) {
21670
- return result;
21671
- }
21672
- const required2 = new Set(schema.required || []);
21673
- for (const [key, prop] of Object.entries(schema.properties)) {
21674
- let zodType = schemaToZod(prop);
21675
- if (prop.description) {
21676
- zodType = zodType.describe(prop.description);
21677
- }
21678
- if (prop.default !== undefined) {
21679
- zodType = zodType.default(prop.default);
21680
- }
21681
- if (!required2.has(key)) {
21682
- zodType = zodType.optional();
21683
- }
21684
- result[key] = zodType;
21685
- }
21686
- return result;
21687
- }
21688
- function schemaToZod(schema) {
21689
- if (!schema)
21690
- return exports_external.any();
21691
- if (schema.oneOf || schema.anyOf) {
21692
- const variants = schema.oneOf ?? schema.anyOf;
21693
- const mapped = variants.map((variant) => schemaToZod(variant));
21694
- if (mapped.length === 1)
21695
- return mapped[0];
21696
- if (mapped.length > 1)
21697
- return exports_external.union(mapped);
21698
- return exports_external.any();
21699
- }
21700
- if (schema.allOf) {
21701
- const variants = schema.allOf;
21702
- if (variants.length === 0)
21703
- return exports_external.any();
21704
- return variants.map((variant) => schemaToZod(variant)).reduce((acc, next) => exports_external.intersection(acc, next));
21705
- }
21706
- if (schema.enum && schema.type === "string") {
21707
- return exports_external.enum(schema.enum);
21708
- }
21709
- switch (schema.type) {
21710
- case "string": {
21711
- let zodType = exports_external.string();
21712
- if (schema.minLength !== undefined)
21713
- zodType = zodType.min(schema.minLength);
21714
- if (schema.maxLength !== undefined)
21715
- zodType = zodType.max(schema.maxLength);
21716
- if (schema.pattern) {
21717
- try {
21718
- zodType = zodType.regex(new RegExp(schema.pattern));
21719
- } catch {}
21720
- }
21721
- return zodType;
21722
- }
21723
- case "number":
21724
- case "integer": {
21725
- let zodType = exports_external.number();
21726
- if (schema.type === "integer") {
21727
- zodType = zodType.int();
21728
- }
21729
- if (schema.exclusiveMinimum !== undefined) {
21730
- zodType = zodType.gt(schema.exclusiveMinimum);
21731
- }
21732
- if (schema.minimum !== undefined) {
21733
- zodType = zodType.gte(schema.minimum);
21734
- }
21735
- if (schema.maximum !== undefined) {
21736
- zodType = zodType.lte(schema.maximum);
21737
- }
21738
- return zodType;
21739
- }
21740
- case "boolean":
21741
- return exports_external.boolean();
21742
- case "array":
21743
- return exports_external.array(schemaToZod(schema.items ?? {}));
21744
- case "object": {
21745
- if (schema.properties) {
21746
- const shape = {};
21747
- const required2 = new Set(schema.required || []);
21748
- for (const [key, prop] of Object.entries(schema.properties)) {
21749
- let propSchema = schemaToZod(prop);
21750
- if (prop.description) {
21751
- propSchema = propSchema.describe(prop.description);
21752
- }
21753
- if (prop.default !== undefined) {
21754
- propSchema = propSchema.default(prop.default);
21755
- }
21756
- if (!required2.has(key)) {
21757
- propSchema = propSchema.optional();
21758
- }
21759
- shape[key] = propSchema;
21760
- }
21761
- return exports_external.object(shape).passthrough();
21762
- }
21763
- return exports_external.record(exports_external.any());
21764
- }
21765
- default:
21766
- return exports_external.any();
21767
- }
21768
- }
21769
- function registerBackendTools(language, tools) {
21770
- let count = 0;
21771
- for (const tool of tools) {
21772
- const namespacedName = `${language}_${tool.name}`;
21773
- if (registeredTools.has(namespacedName)) {
21774
- continue;
21775
- }
21776
- const zodSchema = jsonSchemaToZod(tool.inputSchema);
21777
- server.registerTool(namespacedName, {
21778
- description: tool.description || `${language} ${tool.name} tool`,
21779
- inputSchema: zodSchema
21780
- }, async (args) => backendManager.callTool(language, tool.name, args));
21781
- console.error(`[lsp-mcp] Registered ${namespacedName}`);
21782
- registeredTools.add(namespacedName);
21783
- count++;
21784
- }
21785
- server.sendToolListChanged();
21786
- return count;
21787
- }
21788
21868
  async function startAndRegisterBackend(language) {
21789
21869
  if (startedBackends.has(language)) {
21790
21870
  const status2 = backendManager.getStatus()[language];
@@ -21793,11 +21873,10 @@ async function startAndRegisterBackend(language) {
21793
21873
  }
21794
21874
  console.error(`[lsp-mcp] Starting ${language} backend...`);
21795
21875
  try {
21796
- const tools = await backendManager.getTools(language);
21797
- const count = registerBackendTools(language, tools);
21876
+ await backendManager.getBackend(language);
21798
21877
  startedBackends.add(language);
21799
- console.error(`[lsp-mcp] ${language}: ${count} new tools registered`);
21800
- return count;
21878
+ console.error(`[lsp-mcp] ${language} backend started`);
21879
+ return 0;
21801
21880
  } catch (error2) {
21802
21881
  console.error(`[lsp-mcp] Failed to start ${language} backend:`, error2);
21803
21882
  throw error2;
@@ -21806,14 +21885,18 @@ async function startAndRegisterBackend(language) {
21806
21885
  async function updateAndRestartBackend(language) {
21807
21886
  console.error(`[lsp-mcp] Updating ${language} backend...`);
21808
21887
  const result = await backendManager.restartBackend(language);
21809
- const tools = await backendManager.getTools(language);
21810
- const newlyRegistered = registerBackendTools(language, tools);
21811
21888
  startedBackends.add(language);
21812
- console.error(`[lsp-mcp] ${language} backend updated (${newlyRegistered} new tools registered)`);
21813
21889
  return result;
21814
21890
  }
21815
21891
  server.registerTool("status", { description: "Get status of all LSP backends and server configuration" }, async () => status(backendManager, config2));
21816
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
+ });
21817
21900
  server.registerTool("switch_python_backend", {
21818
21901
  description: "Switch the Python backend provider (requires restart)",
21819
21902
  inputSchema: switchPythonBackendSchema
@@ -21829,53 +21912,97 @@ server.registerTool("update_backend", {
21829
21912
  description: "Update a backend to the latest version. This will restart the backend with the newest version available.",
21830
21913
  inputSchema: updateBackendSchema
21831
21914
  }, async ({ language }) => updateBackend(language, backendManager, config2, updateAndRestartBackend));
21832
- 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 = {
21833
21996
  python: [
21834
- { name: "switch_workspace", description: "Switch the active workspace to a new project directory", schema: { path: exports_external.string() } },
21835
- { 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() } },
21836
- { 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() } },
21837
- { 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() } },
21838
- { 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() } },
21839
- { name: "symbols", description: "Get symbols from a Python file", schema: { file: exports_external.string(), query: exports_external.string().optional() } },
21840
- { name: "diagnostics", description: "Get type errors and warnings for a Python file or directory", schema: { path: exports_external.string() } },
21841
- { 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() } },
21842
- { 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() } },
21843
- { name: "update_document", description: "Update file content for incremental analysis without writing to disk", schema: { file: exports_external.string(), content: exports_external.string() } },
21844
- { 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() } },
21845
- { 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() } }
21846
22000
  ],
21847
22001
  typescript: [
21848
- { name: "switch_workspace", description: "Switch the active workspace to a new project directory", schema: { path: exports_external.string() } },
21849
- { 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() } },
21850
- { 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() } },
21851
- { 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() } },
21852
- { 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() } },
21853
- { 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() } },
21854
- { name: "symbols", description: "Extract symbols (classes, functions, methods, variables) from a file", schema: { file: exports_external.string(), query: exports_external.string().optional() } },
21855
- { name: "diagnostics", description: "Get type errors and warnings for a TypeScript/JavaScript file", schema: { path: exports_external.string() } },
21856
- { 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() } },
21857
- { name: "update_document", description: "Update file content for incremental analysis without writing to disk", schema: { file: exports_external.string(), content: exports_external.string() } },
21858
- { name: "status", description: "Check TypeScript environment status for a project", schema: { file: exports_external.string() } },
21859
- { 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() } },
21860
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() } },
21861
- { 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() } },
21862
- { 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() } },
21863
- { 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() } }
21864
22004
  ],
21865
- vue: [
21866
- { name: "switch_workspace", description: "Switch the active workspace to a new project directory", schema: { path: exports_external.string() } },
21867
- { 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() } },
21868
- { 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() } },
21869
- { 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() } },
21870
- { 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() } },
21871
- { 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() } },
21872
- { name: "diagnostics", description: "Get type errors and warnings for Vue SFC files", schema: { path: exports_external.string() } },
21873
- { name: "update_document", description: "Update Vue file content for incremental analysis without writing to disk", schema: { file: exports_external.string(), content: exports_external.string() } },
21874
- { name: "symbols", description: "Extract symbols (variables, functions, components) from a Vue SFC file", schema: { file: exports_external.string(), query: exports_external.string().optional() } },
21875
- { 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() } },
21876
- { 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() } },
21877
- { name: "status", description: "Check Vue Language Server status for a project", schema: { file: exports_external.string() } }
21878
- ]
22005
+ vue: []
21879
22006
  };
21880
22007
  server.registerTool("switch_workspace", {
21881
22008
  description: "Switch the active workspace for ALL backends simultaneously. This clears caches and refocuses code intelligence on the new project root.",
@@ -21883,14 +22010,9 @@ server.registerTool("switch_workspace", {
21883
22010
  path: exports_external.string().describe("Absolute path to the new project root directory")
21884
22011
  }
21885
22012
  }, async ({ path: workspacePath }) => {
22013
+ activeWorkspacePath = workspacePath;
21886
22014
  const results = {};
21887
- const languages = [];
21888
- if (config2.python.enabled)
21889
- languages.push("python");
21890
- if (config2.typescript.enabled)
21891
- languages.push("typescript");
21892
- if (config2.vue.enabled)
21893
- languages.push("vue");
22015
+ const languages = Object.keys(config2.languages).filter((lang) => config2.languages[lang].enabled);
21894
22016
  await Promise.all(languages.map(async (lang) => {
21895
22017
  try {
21896
22018
  if (startedBackends.has(lang)) {
@@ -21904,27 +22026,166 @@ server.registerTool("switch_workspace", {
21904
22026
  }
21905
22027
  }));
21906
22028
  return {
21907
- content: [{
21908
- type: "text",
21909
- text: JSON.stringify({
21910
- success: true,
21911
- workspace: workspacePath,
21912
- results
21913
- }, null, 2)
21914
- }]
22029
+ content: [
22030
+ {
22031
+ type: "text",
22032
+ text: JSON.stringify({
22033
+ success: true,
22034
+ workspace: workspacePath,
22035
+ results
22036
+ }, null, 2)
22037
+ }
22038
+ ]
21915
22039
  };
21916
22040
  });
21917
- function preRegisterKnownTools() {
21918
- const languages = [];
21919
- if (config2.python.enabled)
21920
- languages.push("python");
21921
- if (config2.typescript.enabled)
21922
- languages.push("typescript");
21923
- if (config2.vue.enabled)
21924
- languages.push("vue");
21925
- let totalCount = 0;
21926
- for (const language of languages) {
21927
- const tools = KNOWN_TOOLS[language];
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];
21928
22189
  if (!tools)
21929
22190
  continue;
21930
22191
  for (const tool of tools) {
@@ -21934,27 +22195,25 @@ function preRegisterKnownTools() {
21934
22195
  inputSchema: tool.schema
21935
22196
  }, async (args) => {
21936
22197
  if (!startedBackends.has(language)) {
21937
- console.error(`[lsp-mcp] Auto-starting ${language} backend for ${tool.name}...`);
21938
- try {
21939
- await backendManager.getBackend(language);
21940
- startedBackends.add(language);
21941
- console.error(`[lsp-mcp] ${language} backend started`);
21942
- } catch (error2) {
21943
- return {
21944
- content: [{ type: "text", text: JSON.stringify({ error: `Failed to start ${language} backend: ${error2}` }) }]
21945
- };
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
+ }
21946
22207
  }
21947
22208
  }
21948
22209
  return backendManager.callTool(language, tool.name, args);
21949
22210
  });
21950
22211
  registeredTools.add(namespacedName);
21951
- totalCount++;
21952
22212
  }
21953
- console.error(`[lsp-mcp] Pre-registered ${tools.length} ${language} tools`);
21954
22213
  }
21955
- 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`);
21956
22215
  }
21957
- preRegisterKnownTools();
22216
+ preRegisterTools();
21958
22217
  async function gracefulShutdown(signal) {
21959
22218
  console.error(`
21960
22219
  [lsp-mcp] Received ${signal}, shutting down gracefully...`);
@@ -21973,22 +22232,16 @@ process.on("SIGINT", () => gracefulShutdown("SIGINT"));
21973
22232
  async function main() {
21974
22233
  console.error("LSP MCP Server - Unified Multi-Language Code Intelligence");
21975
22234
  console.error(` Version: ${packageJson.version}`);
21976
- console.error(" Python:", config2.python.enabled ? `enabled (${config2.python.provider})` : "disabled");
21977
- console.error(" TypeScript:", config2.typescript.enabled ? "enabled" : "disabled");
21978
- 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");
21979
22238
  console.error("");
21980
22239
  const transport = new StdioServerTransport;
21981
22240
  await server.connect(transport);
21982
22241
  if (config2.eagerStart) {
21983
22242
  console.error("Eager start enabled - starting all backends now...");
21984
- const languages = [];
21985
- if (config2.python.enabled)
21986
- languages.push("python");
21987
- if (config2.typescript.enabled)
21988
- languages.push("typescript");
21989
- if (config2.vue.enabled)
21990
- languages.push("vue");
21991
- 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) => {
21992
22245
  try {
21993
22246
  await backendManager.getBackend(lang);
21994
22247
  startedBackends.add(lang);
@@ -22008,4 +22261,4 @@ main().catch((error2) => {
22008
22261
  process.exit(1);
22009
22262
  });
22010
22263
 
22011
- //# debugId=B173E4D0EEF312F264756E2164756E21
22264
+ //# debugId=52D8C4766CA64C6B64756E2164756E21