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