@treedy/lsp-mcp 0.1.5 → 0.1.7

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/index.js CHANGED
@@ -20670,11 +20670,31 @@ class BackendManager {
20670
20670
  if (state.status !== "ready") {
20671
20671
  throw new Error(`${language} backend is not ready: ${state.lastError}`);
20672
20672
  }
20673
- const result = await state.client.callTool({
20674
- name: toolName,
20675
- arguments: args
20676
- });
20677
- return result;
20673
+ try {
20674
+ const result = await state.client.callTool({
20675
+ name: toolName,
20676
+ arguments: args
20677
+ });
20678
+ return result;
20679
+ } catch (error2) {
20680
+ state.status = "error";
20681
+ state.lastError = String(error2);
20682
+ console.error(`[BackendManager] ${language} tool call failed, attempting restart:`, error2);
20683
+ try {
20684
+ await this.restartBackend(language);
20685
+ const restarted = this.backends.get(language);
20686
+ if (!restarted || restarted.status !== "ready") {
20687
+ throw new Error(`${language} backend failed to restart`);
20688
+ }
20689
+ const result = await restarted.client.callTool({
20690
+ name: toolName,
20691
+ arguments: args
20692
+ });
20693
+ return result;
20694
+ } catch (restartError) {
20695
+ throw new Error(`${language} backend error after restart: ${restartError}`);
20696
+ }
20697
+ }
20678
20698
  }
20679
20699
  async getTools(language) {
20680
20700
  const state = await this.getBackend(language);
@@ -20748,6 +20768,7 @@ class BackendManager {
20748
20768
  async restartBackend(language) {
20749
20769
  const existing = this.backends.get(language);
20750
20770
  const oldVersion = existing?.serverInfo?.version ?? null;
20771
+ const restartCount = (existing?.restartCount ?? 0) + 1;
20751
20772
  if (existing) {
20752
20773
  console.error(`[BackendManager] Stopping ${language} for update...`);
20753
20774
  try {
@@ -20760,6 +20781,7 @@ class BackendManager {
20760
20781
  }
20761
20782
  console.error(`[BackendManager] Starting fresh ${language} backend...`);
20762
20783
  const state = await this.startBackend(language);
20784
+ state.restartCount = restartCount;
20763
20785
  const newVersion = state.serverInfo?.version ?? null;
20764
20786
  return { oldVersion, newVersion };
20765
20787
  }
@@ -21586,6 +21608,7 @@ var packageJson = require2("../package.json");
21586
21608
  var config2 = loadConfig();
21587
21609
  var backendManager = new BackendManager(config2);
21588
21610
  var startedBackends = new Set;
21611
+ var registeredTools = new Set;
21589
21612
  var server = new McpServer({
21590
21613
  name: "lsp-mcp",
21591
21614
  version: packageJson.version
@@ -21598,46 +21621,7 @@ function jsonSchemaToZod(schema) {
21598
21621
  }
21599
21622
  const required2 = new Set(schema.required || []);
21600
21623
  for (const [key, prop] of Object.entries(schema.properties)) {
21601
- let zodType;
21602
- switch (prop.type) {
21603
- case "string":
21604
- zodType = exports_external.string();
21605
- if (prop.enum) {
21606
- zodType = exports_external.enum(prop.enum);
21607
- }
21608
- break;
21609
- case "number":
21610
- case "integer":
21611
- zodType = exports_external.number();
21612
- if (prop.type === "integer") {
21613
- zodType = zodType.int();
21614
- }
21615
- if (prop.exclusiveMinimum !== undefined) {
21616
- zodType = zodType.gt(prop.exclusiveMinimum);
21617
- }
21618
- if (prop.minimum !== undefined) {
21619
- zodType = zodType.gte(prop.minimum);
21620
- }
21621
- if (prop.maximum !== undefined) {
21622
- zodType = zodType.lte(prop.maximum);
21623
- }
21624
- break;
21625
- case "boolean":
21626
- zodType = exports_external.boolean();
21627
- break;
21628
- case "array":
21629
- if (prop.items?.type === "string") {
21630
- zodType = exports_external.array(exports_external.string());
21631
- } else {
21632
- zodType = exports_external.array(exports_external.any());
21633
- }
21634
- break;
21635
- case "object":
21636
- zodType = exports_external.record(exports_external.any());
21637
- break;
21638
- default:
21639
- zodType = exports_external.any();
21640
- }
21624
+ let zodType = schemaToZod(prop);
21641
21625
  if (prop.description) {
21642
21626
  zodType = zodType.describe(prop.description);
21643
21627
  }
@@ -21651,16 +21635,101 @@ function jsonSchemaToZod(schema) {
21651
21635
  }
21652
21636
  return result;
21653
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
+ }
21654
21719
  function registerBackendTools(language, tools) {
21655
21720
  let count = 0;
21656
21721
  for (const tool of tools) {
21657
21722
  const namespacedName = `${language}_${tool.name}`;
21723
+ if (registeredTools.has(namespacedName)) {
21724
+ continue;
21725
+ }
21658
21726
  const zodSchema = jsonSchemaToZod(tool.inputSchema);
21659
21727
  server.registerTool(namespacedName, {
21660
21728
  description: tool.description || `${language} ${tool.name} tool`,
21661
21729
  inputSchema: zodSchema
21662
21730
  }, async (args) => backendManager.callTool(language, tool.name, args));
21663
21731
  console.error(`[lsp-mcp] Registered ${namespacedName}`);
21732
+ registeredTools.add(namespacedName);
21664
21733
  count++;
21665
21734
  }
21666
21735
  server.sendToolListChanged();
@@ -21677,7 +21746,7 @@ async function startAndRegisterBackend(language) {
21677
21746
  const tools = await backendManager.getTools(language);
21678
21747
  const count = registerBackendTools(language, tools);
21679
21748
  startedBackends.add(language);
21680
- console.error(`[lsp-mcp] ${language}: ${count} tools registered`);
21749
+ console.error(`[lsp-mcp] ${language}: ${count} new tools registered`);
21681
21750
  return count;
21682
21751
  } catch (error2) {
21683
21752
  console.error(`[lsp-mcp] Failed to start ${language} backend:`, error2);
@@ -21687,14 +21756,10 @@ async function startAndRegisterBackend(language) {
21687
21756
  async function updateAndRestartBackend(language) {
21688
21757
  console.error(`[lsp-mcp] Updating ${language} backend...`);
21689
21758
  const result = await backendManager.restartBackend(language);
21690
- if (startedBackends.has(language)) {
21691
- console.error(`[lsp-mcp] ${language} backend updated, tools still available`);
21692
- } else {
21693
- const tools = await backendManager.getTools(language);
21694
- registerBackendTools(language, tools);
21695
- startedBackends.add(language);
21696
- console.error(`[lsp-mcp] ${language} backend updated and ${tools.length} tools registered`);
21697
- }
21759
+ const tools = await backendManager.getTools(language);
21760
+ const newlyRegistered = registerBackendTools(language, tools);
21761
+ startedBackends.add(language);
21762
+ console.error(`[lsp-mcp] ${language} backend updated (${newlyRegistered} new tools registered)`);
21698
21763
  return result;
21699
21764
  }
21700
21765
  server.registerTool("status", { description: "Get status of all LSP backends and server configuration" }, async () => status(backendManager, config2));
@@ -21714,6 +21779,92 @@ server.registerTool("update_backend", {
21714
21779
  description: "Update a backend to the latest version. This will restart the backend with the newest version available.",
21715
21780
  inputSchema: updateBackendSchema
21716
21781
  }, async ({ language }) => updateBackend(language, backendManager, config2, updateAndRestartBackend));
21782
+ var KNOWN_TOOLS = {
21783
+ python: [
21784
+ { name: "hover", description: "Get documentation for the symbol at the given position", schema: { file: exports_external.string(), line: exports_external.number().int(), column: exports_external.number().int() } },
21785
+ { name: "definition", description: "Get the definition location for the symbol at the given position", schema: { file: exports_external.string(), line: exports_external.number().int(), column: exports_external.number().int() } },
21786
+ { name: "references", description: "Find all references to the symbol at the given position", schema: { file: exports_external.string(), line: exports_external.number().int(), column: exports_external.number().int() } },
21787
+ { name: "completions", description: "Get code completion suggestions at the given position", schema: { file: exports_external.string(), line: exports_external.number().int(), column: exports_external.number().int() } },
21788
+ { name: "symbols", description: "Get symbols from a Python file", schema: { file: exports_external.string(), query: exports_external.string().optional() } },
21789
+ { name: "diagnostics", description: "Get type errors and warnings for a Python file or directory", schema: { path: exports_external.string() } },
21790
+ { name: "rename", description: "Rename the symbol at the given position", schema: { file: exports_external.string(), line: exports_external.number().int(), column: exports_external.number().int(), new_name: exports_external.string() } },
21791
+ { name: "signature_help", description: "Get function signature information at the given position", schema: { file: exports_external.string(), line: exports_external.number().int(), column: exports_external.number().int() } },
21792
+ { name: "update_document", description: "Update file content for incremental analysis without writing to disk", schema: { file: exports_external.string(), content: exports_external.string() } },
21793
+ { name: "search", description: "Search for a regex pattern in files using ripgrep", schema: { pattern: exports_external.string(), path: exports_external.string().optional(), glob: exports_external.string().optional() } },
21794
+ { name: "status", description: "Get the status of the Python MCP server", schema: {} }
21795
+ ],
21796
+ 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
+ { name: "move", description: "Move a function, class, or variable to a new file", schema: { file: exports_external.string(), line: exports_external.number().int().positive(), column: exports_external.number().int().positive(), destination: exports_external.string().optional(), preview: exports_external.boolean().default(false).optional() } },
21809
+ { name: "function_signature", description: "Get the current signature of a function at a specific position", schema: { file: exports_external.string(), line: exports_external.number().int().positive(), column: exports_external.number().int().positive() } },
21810
+ { name: "available_refactors", description: "Get available refactoring actions at a specific position", schema: { file: exports_external.string(), line: exports_external.number().int().positive(), column: exports_external.number().int().positive() } },
21811
+ { name: "apply_refactor", description: "Apply a specific refactoring action at a position", schema: { file: exports_external.string(), line: exports_external.number().int().positive(), column: exports_external.number().int().positive(), refactorName: exports_external.string(), actionName: exports_external.string(), preview: exports_external.boolean().default(false).optional() } }
21812
+ ],
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
+ ]
21826
+ };
21827
+ function preRegisterKnownTools() {
21828
+ const languages = [];
21829
+ if (config2.python.enabled)
21830
+ languages.push("python");
21831
+ if (config2.typescript.enabled)
21832
+ languages.push("typescript");
21833
+ if (config2.vue.enabled)
21834
+ languages.push("vue");
21835
+ let totalCount = 0;
21836
+ for (const language of languages) {
21837
+ const tools = KNOWN_TOOLS[language];
21838
+ if (!tools)
21839
+ continue;
21840
+ for (const tool of tools) {
21841
+ const namespacedName = `${language}_${tool.name}`;
21842
+ server.registerTool(namespacedName, {
21843
+ description: tool.description,
21844
+ inputSchema: tool.schema
21845
+ }, async (args) => {
21846
+ if (!startedBackends.has(language)) {
21847
+ console.error(`[lsp-mcp] Auto-starting ${language} backend for ${tool.name}...`);
21848
+ try {
21849
+ await backendManager.getBackend(language);
21850
+ startedBackends.add(language);
21851
+ console.error(`[lsp-mcp] ${language} backend started`);
21852
+ } catch (error2) {
21853
+ return {
21854
+ content: [{ type: "text", text: JSON.stringify({ error: `Failed to start ${language} backend: ${error2}` }) }]
21855
+ };
21856
+ }
21857
+ }
21858
+ return backendManager.callTool(language, tool.name, args);
21859
+ });
21860
+ registeredTools.add(namespacedName);
21861
+ totalCount++;
21862
+ }
21863
+ console.error(`[lsp-mcp] Pre-registered ${tools.length} ${language} tools`);
21864
+ }
21865
+ console.error(`[lsp-mcp] Total: ${totalCount} tools pre-registered (backends start on first use)`);
21866
+ }
21867
+ preRegisterKnownTools();
21717
21868
  async function gracefulShutdown(signal) {
21718
21869
  console.error(`
21719
21870
  [lsp-mcp] Received ${signal}, shutting down gracefully...`);
@@ -21735,12 +21886,11 @@ async function main() {
21735
21886
  console.error(" Python:", config2.python.enabled ? `enabled (${config2.python.provider})` : "disabled");
21736
21887
  console.error(" TypeScript:", config2.typescript.enabled ? "enabled" : "disabled");
21737
21888
  console.error(" Vue:", config2.vue.enabled ? "enabled" : "disabled");
21738
- console.error(" Eager Start:", config2.eagerStart ? "enabled" : "disabled");
21739
21889
  console.error("");
21740
21890
  const transport = new StdioServerTransport;
21741
21891
  await server.connect(transport);
21742
21892
  if (config2.eagerStart) {
21743
- console.error("Starting all enabled backends...");
21893
+ console.error("Eager start enabled - starting all backends now...");
21744
21894
  const languages = [];
21745
21895
  if (config2.python.enabled)
21746
21896
  languages.push("python");
@@ -21748,26 +21898,17 @@ async function main() {
21748
21898
  languages.push("typescript");
21749
21899
  if (config2.vue.enabled)
21750
21900
  languages.push("vue");
21751
- const results = await Promise.allSettled(languages.map(async (lang) => {
21901
+ await Promise.allSettled(languages.map(async (lang) => {
21752
21902
  try {
21753
- const count = await startAndRegisterBackend(lang);
21754
- return { lang, count };
21903
+ await backendManager.getBackend(lang);
21904
+ startedBackends.add(lang);
21905
+ console.error(` ${lang}: backend started`);
21755
21906
  } catch (error2) {
21756
- console.error(`[lsp-mcp] Failed to start ${lang}:`, error2);
21757
- return { lang, error: error2 };
21907
+ console.error(` ${lang}: failed to start - ${error2}`);
21758
21908
  }
21759
21909
  }));
21760
- for (const result of results) {
21761
- if (result.status === "fulfilled" && "count" in result.value) {
21762
- console.error(` ${result.value.lang}: ${result.value.count} tools registered`);
21763
- } else if (result.status === "fulfilled" && "error" in result.value) {
21764
- console.error(` ${result.value.lang}: failed to start`);
21765
- }
21766
- }
21767
21910
  } else {
21768
- console.error("Backends are loaded on-demand. Use these tools to get started:");
21769
- console.error(" - list_backends: See available backends and their status");
21770
- console.error(" - start_backend: Install and start a backend (e.g., start_backend language=python)");
21911
+ console.error("Tools are pre-registered. Backends start automatically on first use.");
21771
21912
  }
21772
21913
  console.error("");
21773
21914
  console.error("Ready");
@@ -21777,4 +21918,4 @@ main().catch((error2) => {
21777
21918
  process.exit(1);
21778
21919
  });
21779
21920
 
21780
- //# debugId=93725E95ED75445864756E2164756E21
21921
+ //# debugId=55BC37C22EC14E0664756E2164756E21