@treedy/lsp-mcp 0.1.8 → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bundled/python/src/rope_mcp/config.py +50 -14
- package/dist/bundled/python/src/rope_mcp/lsp/client.py +243 -4
- package/dist/bundled/python/src/rope_mcp/lsp/types.py +1 -0
- package/dist/bundled/python/src/rope_mcp/server.py +331 -77
- package/dist/bundled/python/src/rope_mcp/tools/__init__.py +0 -2
- package/dist/bundled/typescript/dist/index.js +6129 -5891
- package/dist/bundled/typescript/dist/index.js.map +5 -5
- package/dist/bundled/vue/dist/index.js +136 -71
- package/dist/bundled/vue/dist/vue-service.d.ts +16 -0
- package/dist/index.js +595 -328
- package/dist/index.js.map +7 -7
- package/package.json +1 -1
- package/dist/bundled/python/src/rope_mcp/__pycache__/__init__.cpython-312.pyc +0 -0
- package/dist/bundled/python/src/rope_mcp/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/bundled/python/src/rope_mcp/__pycache__/config.cpython-312.pyc +0 -0
- package/dist/bundled/python/src/rope_mcp/__pycache__/config.cpython-313.pyc +0 -0
- package/dist/bundled/python/src/rope_mcp/__pycache__/pyright_client.cpython-313.pyc +0 -0
- package/dist/bundled/python/src/rope_mcp/__pycache__/rope_client.cpython-313.pyc +0 -0
- package/dist/bundled/python/src/rope_mcp/__pycache__/server.cpython-312.pyc +0 -0
- package/dist/bundled/python/src/rope_mcp/__pycache__/server.cpython-313.pyc +0 -0
- package/dist/bundled/python/src/rope_mcp/lsp/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/bundled/python/src/rope_mcp/lsp/__pycache__/client.cpython-313.pyc +0 -0
- package/dist/bundled/python/src/rope_mcp/lsp/__pycache__/types.cpython-313.pyc +0 -0
- package/dist/bundled/python/src/rope_mcp/tools/__pycache__/__init__.cpython-313.pyc +0 -0
- package/dist/bundled/python/src/rope_mcp/tools/__pycache__/change_signature.cpython-313.pyc +0 -0
- package/dist/bundled/python/src/rope_mcp/tools/__pycache__/completions.cpython-313.pyc +0 -0
- package/dist/bundled/python/src/rope_mcp/tools/__pycache__/definition.cpython-313.pyc +0 -0
- package/dist/bundled/python/src/rope_mcp/tools/__pycache__/diagnostics.cpython-313.pyc +0 -0
- package/dist/bundled/python/src/rope_mcp/tools/__pycache__/hover.cpython-313.pyc +0 -0
- package/dist/bundled/python/src/rope_mcp/tools/__pycache__/move.cpython-313.pyc +0 -0
- package/dist/bundled/python/src/rope_mcp/tools/__pycache__/references.cpython-313.pyc +0 -0
- package/dist/bundled/python/src/rope_mcp/tools/__pycache__/rename.cpython-313.pyc +0 -0
- package/dist/bundled/python/src/rope_mcp/tools/__pycache__/search.cpython-313.pyc +0 -0
- 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
|
-
|
|
19846
|
-
|
|
19847
|
-
|
|
19848
|
-
|
|
19849
|
-
|
|
19850
|
-
|
|
19851
|
-
|
|
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:
|
|
19855
|
-
|
|
19856
|
+
enabled: true,
|
|
19857
|
+
extensions: DEFAULT_EXTENSIONS.python
|
|
19856
19858
|
},
|
|
19857
19859
|
typescript: {
|
|
19858
|
-
enabled:
|
|
19860
|
+
enabled: true,
|
|
19861
|
+
extensions: DEFAULT_EXTENSIONS.typescript
|
|
19859
19862
|
},
|
|
19860
19863
|
vue: {
|
|
19861
|
-
enabled:
|
|
19862
|
-
|
|
19863
|
-
|
|
19864
|
-
|
|
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 && !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
|
-
|
|
19891
|
-
|
|
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
|
-
|
|
20786
|
-
|
|
20787
|
-
|
|
20788
|
-
|
|
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 && 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(
|
|
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) {
|
|
@@ -20872,14 +21066,14 @@ async function status(backendManager, config2) {
|
|
|
20872
21066
|
description: "Unified MCP server for multi-language code intelligence",
|
|
20873
21067
|
config: {
|
|
20874
21068
|
python: {
|
|
20875
|
-
enabled: config2.python
|
|
20876
|
-
provider: config2.python
|
|
21069
|
+
enabled: config2.languages.python?.enabled ?? false,
|
|
21070
|
+
provider: config2.python?.provider
|
|
20877
21071
|
},
|
|
20878
21072
|
typescript: {
|
|
20879
|
-
enabled: config2.typescript
|
|
21073
|
+
enabled: config2.languages.typescript?.enabled ?? false
|
|
20880
21074
|
},
|
|
20881
21075
|
vue: {
|
|
20882
|
-
enabled: config2.vue
|
|
21076
|
+
enabled: config2.languages.vue?.enabled ?? false
|
|
20883
21077
|
},
|
|
20884
21078
|
autoUpdate: config2.autoUpdate
|
|
20885
21079
|
},
|
|
@@ -20973,8 +21167,8 @@ async function listBackends(backendManager, config2) {
|
|
|
20973
21167
|
const backends = [
|
|
20974
21168
|
{
|
|
20975
21169
|
name: "python",
|
|
20976
|
-
enabled: config2.python
|
|
20977
|
-
provider: config2.python
|
|
21170
|
+
enabled: config2.languages.python?.enabled ?? false,
|
|
21171
|
+
provider: config2.python?.provider,
|
|
20978
21172
|
status: backendStatus.python?.status || "not_started",
|
|
20979
21173
|
tools: backendStatus.python?.tools || 0,
|
|
20980
21174
|
description: "Python code intelligence (hover, definition, references, refactoring)",
|
|
@@ -20982,7 +21176,7 @@ async function listBackends(backendManager, config2) {
|
|
|
20982
21176
|
},
|
|
20983
21177
|
{
|
|
20984
21178
|
name: "typescript",
|
|
20985
|
-
enabled: config2.typescript
|
|
21179
|
+
enabled: config2.languages.typescript?.enabled ?? false,
|
|
20986
21180
|
status: backendStatus.typescript?.status || "not_started",
|
|
20987
21181
|
tools: backendStatus.typescript?.tools || 0,
|
|
20988
21182
|
description: "TypeScript/JavaScript code intelligence",
|
|
@@ -20990,7 +21184,7 @@ async function listBackends(backendManager, config2) {
|
|
|
20990
21184
|
},
|
|
20991
21185
|
{
|
|
20992
21186
|
name: "vue",
|
|
20993
|
-
enabled: config2.vue
|
|
21187
|
+
enabled: config2.languages.vue?.enabled ?? false,
|
|
20994
21188
|
status: backendStatus.vue?.status || "not_started",
|
|
20995
21189
|
tools: backendStatus.vue?.tools || 0,
|
|
20996
21190
|
description: "Vue Single File Component (.vue) code intelligence via Volar",
|
|
@@ -21012,7 +21206,7 @@ var startBackendSchema = {
|
|
|
21012
21206
|
language: exports_external.enum(["python", "typescript", "vue"]).describe("The backend to start")
|
|
21013
21207
|
};
|
|
21014
21208
|
async function startBackend(language, backendManager, config2, registerToolsCallback) {
|
|
21015
|
-
if (language === "python" && !config2.python
|
|
21209
|
+
if (language === "python" && !config2.languages.python?.enabled) {
|
|
21016
21210
|
return {
|
|
21017
21211
|
content: [{
|
|
21018
21212
|
type: "text",
|
|
@@ -21024,7 +21218,7 @@ async function startBackend(language, backendManager, config2, registerToolsCall
|
|
|
21024
21218
|
}]
|
|
21025
21219
|
};
|
|
21026
21220
|
}
|
|
21027
|
-
if (language === "typescript" && !config2.typescript
|
|
21221
|
+
if (language === "typescript" && !config2.languages.typescript?.enabled) {
|
|
21028
21222
|
return {
|
|
21029
21223
|
content: [{
|
|
21030
21224
|
type: "text",
|
|
@@ -21036,7 +21230,7 @@ async function startBackend(language, backendManager, config2, registerToolsCall
|
|
|
21036
21230
|
}]
|
|
21037
21231
|
};
|
|
21038
21232
|
}
|
|
21039
|
-
if (language === "vue" && !config2.vue
|
|
21233
|
+
if (language === "vue" && !config2.languages.vue?.enabled) {
|
|
21040
21234
|
return {
|
|
21041
21235
|
content: [{
|
|
21042
21236
|
type: "text",
|
|
@@ -21085,7 +21279,7 @@ var updateBackendSchema = {
|
|
|
21085
21279
|
language: exports_external.enum(["python", "typescript", "vue"]).describe("The backend to update")
|
|
21086
21280
|
};
|
|
21087
21281
|
async function updateBackend(language, backendManager, config2, updateCallback) {
|
|
21088
|
-
if (language === "python" && !config2.python
|
|
21282
|
+
if (language === "python" && !config2.languages.python?.enabled) {
|
|
21089
21283
|
return {
|
|
21090
21284
|
content: [{
|
|
21091
21285
|
type: "text",
|
|
@@ -21097,7 +21291,7 @@ async function updateBackend(language, backendManager, config2, updateCallback)
|
|
|
21097
21291
|
}]
|
|
21098
21292
|
};
|
|
21099
21293
|
}
|
|
21100
|
-
if (language === "typescript" && !config2.typescript
|
|
21294
|
+
if (language === "typescript" && !config2.languages.typescript?.enabled) {
|
|
21101
21295
|
return {
|
|
21102
21296
|
content: [{
|
|
21103
21297
|
type: "text",
|
|
@@ -21109,7 +21303,7 @@ async function updateBackend(language, backendManager, config2, updateCallback)
|
|
|
21109
21303
|
}]
|
|
21110
21304
|
};
|
|
21111
21305
|
}
|
|
21112
|
-
if (language === "vue" && !config2.vue
|
|
21306
|
+
if (language === "vue" && !config2.languages.vue?.enabled) {
|
|
21113
21307
|
return {
|
|
21114
21308
|
content: [{
|
|
21115
21309
|
type: "text",
|
|
@@ -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.
|
|
21591
|
-
|
|
21592
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
21626
|
-
|
|
21627
|
-
|
|
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
|
-
|
|
21797
|
-
const count = registerBackendTools(language, tools);
|
|
21876
|
+
await backendManager.getBackend(language);
|
|
21798
21877
|
startedBackends.add(language);
|
|
21799
|
-
console.error(`[lsp-mcp] ${language}
|
|
21800
|
-
return
|
|
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
|
|
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: "
|
|
21835
|
-
{ name: "
|
|
21836
|
-
{ name: "
|
|
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
|
|
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,180 @@ server.registerTool("switch_workspace", {
|
|
|
21904
22026
|
}
|
|
21905
22027
|
}));
|
|
21906
22028
|
return {
|
|
21907
|
-
content: [
|
|
21908
|
-
|
|
21909
|
-
|
|
21910
|
-
|
|
21911
|
-
|
|
21912
|
-
|
|
21913
|
-
|
|
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
|
|
21918
|
-
const
|
|
21919
|
-
|
|
21920
|
-
|
|
21921
|
-
|
|
21922
|
-
|
|
21923
|
-
|
|
21924
|
-
|
|
21925
|
-
|
|
21926
|
-
|
|
21927
|
-
|
|
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
|
+
const msg = String(error2);
|
|
22099
|
+
let hint = "";
|
|
22100
|
+
if (msg.includes("ENOENT")) {
|
|
22101
|
+
if (language === "python")
|
|
22102
|
+
hint = "Make sure 'uv' (recommended) or 'python' is installed and in your PATH.";
|
|
22103
|
+
else
|
|
22104
|
+
hint = "Make sure 'node' and 'npm' are installed and in your PATH.";
|
|
22105
|
+
} else {
|
|
22106
|
+
hint = "Check server logs for details. You may need to install the backend manually.";
|
|
22107
|
+
}
|
|
22108
|
+
return {
|
|
22109
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
22110
|
+
error: `Failed to start ${language} backend`,
|
|
22111
|
+
details: msg,
|
|
22112
|
+
hint
|
|
22113
|
+
}, null, 2) }]
|
|
22114
|
+
};
|
|
22115
|
+
}
|
|
22116
|
+
}
|
|
22117
|
+
if (tool.name !== "summarize_file" && tool.name !== "read_file_with_hints") {
|
|
22118
|
+
const availableTools = await backendManager.getTools(language);
|
|
22119
|
+
const supportsTool = availableTools.some((t) => t.name === tool.name);
|
|
22120
|
+
if (!supportsTool) {
|
|
22121
|
+
return {
|
|
22122
|
+
content: [
|
|
22123
|
+
{
|
|
22124
|
+
type: "text",
|
|
22125
|
+
text: JSON.stringify({
|
|
22126
|
+
error: "Not Implemented",
|
|
22127
|
+
message: `The '${language}' backend does not support the '${tool.name}' feature yet.`,
|
|
22128
|
+
available_tools: availableTools.map((t) => t.name)
|
|
22129
|
+
})
|
|
22130
|
+
}
|
|
22131
|
+
]
|
|
22132
|
+
};
|
|
22133
|
+
}
|
|
22134
|
+
}
|
|
22135
|
+
if (tool.name === "summarize_file") {
|
|
22136
|
+
try {
|
|
22137
|
+
const result = await backendManager.callTool(language, "symbols", args);
|
|
22138
|
+
const parsed = JSON.parse(result.content[0].text);
|
|
22139
|
+
if (parsed.error) {
|
|
22140
|
+
return { content: [{ type: "text", text: JSON.stringify(parsed) }] };
|
|
22141
|
+
}
|
|
22142
|
+
const symbols = parsed.symbols || [];
|
|
22143
|
+
const summary = formatSymbolsToMarkdown(symbols);
|
|
22144
|
+
return {
|
|
22145
|
+
content: [{
|
|
22146
|
+
type: "text",
|
|
22147
|
+
text: `File Summary for ${filePath}:
|
|
22148
|
+
|
|
22149
|
+
${summary || "(No symbols found)"}`
|
|
22150
|
+
}]
|
|
22151
|
+
};
|
|
22152
|
+
} catch (error2) {
|
|
22153
|
+
return {
|
|
22154
|
+
content: [{ type: "text", text: JSON.stringify({ error: `Failed to summarize file: ${error2}` }) }]
|
|
22155
|
+
};
|
|
22156
|
+
}
|
|
22157
|
+
}
|
|
22158
|
+
if (tool.name === "read_file_with_hints") {
|
|
22159
|
+
try {
|
|
22160
|
+
let absPath = filePath;
|
|
22161
|
+
if (!path2.isAbsolute(filePath) && activeWorkspacePath) {
|
|
22162
|
+
absPath = path2.join(activeWorkspacePath, filePath);
|
|
22163
|
+
}
|
|
22164
|
+
if (!fs2.existsSync(absPath)) {
|
|
22165
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: `File not found: ${absPath}` }) }] };
|
|
22166
|
+
}
|
|
22167
|
+
const content = fs2.readFileSync(absPath, "utf-8");
|
|
22168
|
+
const result = await backendManager.callTool(language, "inlay_hints", args);
|
|
22169
|
+
const parsed = JSON.parse(result.content[0].text);
|
|
22170
|
+
if (parsed.error) {
|
|
22171
|
+
return { content: [{ type: "text", text: JSON.stringify(parsed) }] };
|
|
22172
|
+
}
|
|
22173
|
+
const hints = parsed.hints || [];
|
|
22174
|
+
const contentWithHints = applyInlayHints(content, hints, language);
|
|
22175
|
+
return {
|
|
22176
|
+
content: [{
|
|
22177
|
+
type: "text",
|
|
22178
|
+
text: contentWithHints
|
|
22179
|
+
}]
|
|
22180
|
+
};
|
|
22181
|
+
} catch (error2) {
|
|
22182
|
+
return {
|
|
22183
|
+
content: [{ type: "text", text: JSON.stringify({ error: `Failed to read file with hints: ${error2}` }) }]
|
|
22184
|
+
};
|
|
22185
|
+
}
|
|
22186
|
+
}
|
|
22187
|
+
const backendArgs = { ...args };
|
|
22188
|
+
if (tool.name === "rename") {
|
|
22189
|
+
if (language === "python") {
|
|
22190
|
+
backendArgs.new_name = args.newName || args.new_name;
|
|
22191
|
+
} else {
|
|
22192
|
+
backendArgs.newName = args.newName || args.new_name;
|
|
22193
|
+
}
|
|
22194
|
+
}
|
|
22195
|
+
return backendManager.callTool(language, tool.name, backendArgs);
|
|
22196
|
+
});
|
|
22197
|
+
registeredTools.add(tool.name);
|
|
22198
|
+
}
|
|
22199
|
+
for (const [language, langConfig] of Object.entries(config2.languages)) {
|
|
22200
|
+
if (!langConfig?.enabled)
|
|
22201
|
+
continue;
|
|
22202
|
+
const tools = LANGUAGE_SPECIFIC_TOOLS[language];
|
|
21928
22203
|
if (!tools)
|
|
21929
22204
|
continue;
|
|
21930
22205
|
for (const tool of tools) {
|
|
@@ -21934,27 +22209,25 @@ function preRegisterKnownTools() {
|
|
|
21934
22209
|
inputSchema: tool.schema
|
|
21935
22210
|
}, async (args) => {
|
|
21936
22211
|
if (!startedBackends.has(language)) {
|
|
21937
|
-
|
|
21938
|
-
|
|
21939
|
-
|
|
21940
|
-
|
|
21941
|
-
|
|
21942
|
-
|
|
21943
|
-
|
|
21944
|
-
|
|
21945
|
-
}
|
|
22212
|
+
await backendManager.getBackend(language);
|
|
22213
|
+
startedBackends.add(language);
|
|
22214
|
+
if (activeWorkspacePath) {
|
|
22215
|
+
console.error(`[lsp-mcp] Syncing active workspace to ${language}: ${activeWorkspacePath}`);
|
|
22216
|
+
try {
|
|
22217
|
+
await backendManager.callTool(language, "switch_workspace", { path: activeWorkspacePath });
|
|
22218
|
+
} catch (syncError) {
|
|
22219
|
+
console.error(`[lsp-mcp] Failed to sync workspace to ${language}:`, syncError);
|
|
22220
|
+
}
|
|
21946
22221
|
}
|
|
21947
22222
|
}
|
|
21948
22223
|
return backendManager.callTool(language, tool.name, args);
|
|
21949
22224
|
});
|
|
21950
22225
|
registeredTools.add(namespacedName);
|
|
21951
|
-
totalCount++;
|
|
21952
22226
|
}
|
|
21953
|
-
console.error(`[lsp-mcp] Pre-registered ${tools.length} ${language} tools`);
|
|
21954
22227
|
}
|
|
21955
|
-
console.error(`[lsp-mcp]
|
|
22228
|
+
console.error(`[lsp-mcp] Unified and language-specific tools registered`);
|
|
21956
22229
|
}
|
|
21957
|
-
|
|
22230
|
+
preRegisterTools();
|
|
21958
22231
|
async function gracefulShutdown(signal) {
|
|
21959
22232
|
console.error(`
|
|
21960
22233
|
[lsp-mcp] Received ${signal}, shutting down gracefully...`);
|
|
@@ -21973,22 +22246,16 @@ process.on("SIGINT", () => gracefulShutdown("SIGINT"));
|
|
|
21973
22246
|
async function main() {
|
|
21974
22247
|
console.error("LSP MCP Server - Unified Multi-Language Code Intelligence");
|
|
21975
22248
|
console.error(` Version: ${packageJson.version}`);
|
|
21976
|
-
console.error(" Python:", config2.python
|
|
21977
|
-
console.error(" TypeScript:", config2.typescript
|
|
21978
|
-
console.error(" Vue:", config2.vue
|
|
22249
|
+
console.error(" Python:", config2.languages.python?.enabled ? `enabled` : "disabled");
|
|
22250
|
+
console.error(" TypeScript:", config2.languages.typescript?.enabled ? "enabled" : "disabled");
|
|
22251
|
+
console.error(" Vue:", config2.languages.vue?.enabled ? "enabled" : "disabled");
|
|
21979
22252
|
console.error("");
|
|
21980
22253
|
const transport = new StdioServerTransport;
|
|
21981
22254
|
await server.connect(transport);
|
|
21982
22255
|
if (config2.eagerStart) {
|
|
21983
22256
|
console.error("Eager start enabled - starting all backends now...");
|
|
21984
|
-
const
|
|
21985
|
-
|
|
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) => {
|
|
22257
|
+
const enabledLanguages = Object.keys(config2.languages).filter((l) => config2.languages[l].enabled);
|
|
22258
|
+
await Promise.allSettled(enabledLanguages.map(async (lang) => {
|
|
21992
22259
|
try {
|
|
21993
22260
|
await backendManager.getBackend(lang);
|
|
21994
22261
|
startedBackends.add(lang);
|
|
@@ -22008,4 +22275,4 @@ main().catch((error2) => {
|
|
|
22008
22275
|
process.exit(1);
|
|
22009
22276
|
});
|
|
22010
22277
|
|
|
22011
|
-
//# debugId=
|
|
22278
|
+
//# debugId=4446D59062B5AFD964756E2164756E21
|