@treedy/vue-lsp-mcp 0.2.0 → 0.2.2

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
@@ -19589,6 +19589,61 @@ ${responseNotification}`;
19589
19589
  }
19590
19590
  }, 30000);
19591
19591
  }
19592
+ var activeWorkspace = null;
19593
+ function setActiveWorkspace(workspace) {
19594
+ activeWorkspace = path.resolve(workspace);
19595
+ return activeWorkspace;
19596
+ }
19597
+ function resolveFilePath(filePath) {
19598
+ const pathObj = path.parse(filePath);
19599
+ let absPath;
19600
+ if (!path.isAbsolute(filePath)) {
19601
+ if (activeWorkspace) {
19602
+ absPath = path.resolve(activeWorkspace, filePath);
19603
+ } else {
19604
+ absPath = path.resolve(filePath);
19605
+ }
19606
+ } else {
19607
+ absPath = filePath;
19608
+ }
19609
+ absPath = path.resolve(absPath);
19610
+ if (activeWorkspace && !absPath.startsWith(activeWorkspace)) {
19611
+ return {
19612
+ absPath: null,
19613
+ error: JSON.stringify({
19614
+ error: "Context Mismatch",
19615
+ message: `The file '${filePath}' resolves to '${absPath}', which is outside the active workspace '${activeWorkspace}'.
19616
+
19617
+ Current Logic:
19618
+ 1. I only analyze files from the active project to ensure accuracy and save resources.
19619
+ 2. You must explicitly switch the workspace if you want to work on a different project.
19620
+
19621
+ Action Required:
19622
+ Please call 'switch_workspace(path="...")' with the new project root before retrying.`,
19623
+ currentWorkspace: activeWorkspace,
19624
+ resolvedPath: absPath
19625
+ })
19626
+ };
19627
+ }
19628
+ return { absPath, error: null };
19629
+ }
19630
+ function validateFileWorkspace(filePath) {
19631
+ const { error: error2 } = resolveFilePath(filePath);
19632
+ return error2;
19633
+ }
19634
+ function clearAllConnections() {
19635
+ for (const conn of activeConnections) {
19636
+ try {
19637
+ conn.process.kill();
19638
+ if (conn.tsserver)
19639
+ conn.tsserver.kill();
19640
+ } catch (e) {}
19641
+ }
19642
+ activeConnections.clear();
19643
+ connectionCache.clear();
19644
+ documentContents.clear();
19645
+ documentVersions.clear();
19646
+ }
19592
19647
  function findProjectRoot(filePath) {
19593
19648
  let dir = path.dirname(path.resolve(filePath));
19594
19649
  while (dir !== path.dirname(dir)) {
@@ -19611,6 +19666,17 @@ function getFileContent(filePath) {
19611
19666
  }
19612
19667
  return "";
19613
19668
  }
19669
+ function positionToOffset(content, line, character) {
19670
+ const lines = content.split(`
19671
+ `);
19672
+ const safeLine = Math.max(0, Math.min(line, lines.length - 1));
19673
+ let offset = 0;
19674
+ for (let i = 0;i < safeLine; i++) {
19675
+ offset += lines[i].length + 1;
19676
+ }
19677
+ const safeChar = Math.max(0, Math.min(character, lines[safeLine].length));
19678
+ return offset + safeChar;
19679
+ }
19614
19680
  function toUri(filePath) {
19615
19681
  return `file://${path.resolve(filePath)}`;
19616
19682
  }
@@ -19820,7 +19886,7 @@ async function getConnection(projectRoot) {
19820
19886
  }
19821
19887
  }
19822
19888
  }
19823
- await sendMessage(conn, "initialize", {
19889
+ const initResult = await sendMessage(conn, "initialize", {
19824
19890
  processId: process.pid,
19825
19891
  rootUri: toUri(projectRoot),
19826
19892
  rootPath: projectRoot,
@@ -19830,7 +19896,23 @@ async function getConnection(projectRoot) {
19830
19896
  completion: { completionItem: { snippetSupport: true } },
19831
19897
  signatureHelp: {},
19832
19898
  definition: {},
19899
+ implementation: {},
19900
+ typeDefinition: {},
19833
19901
  references: {},
19902
+ documentHighlight: {},
19903
+ selectionRange: {},
19904
+ foldingRange: {},
19905
+ documentLink: {},
19906
+ callHierarchy: {},
19907
+ codeAction: {
19908
+ dynamicRegistration: false,
19909
+ codeActionLiteralSupport: {
19910
+ codeActionKind: {
19911
+ valueSet: ["quickfix", "refactor", "source", "source.organizeImports"]
19912
+ }
19913
+ }
19914
+ },
19915
+ rename: { prepareSupport: true },
19834
19916
  publishDiagnostics: {},
19835
19917
  synchronization: {
19836
19918
  didOpen: true,
@@ -19849,7 +19931,7 @@ async function getConnection(projectRoot) {
19849
19931
  tsdk: tsSdkPath || ""
19850
19932
  },
19851
19933
  vue: {
19852
- hybridMode: false
19934
+ hybridMode: true
19853
19935
  }
19854
19936
  },
19855
19937
  workspaceFolders: [
@@ -19859,6 +19941,7 @@ async function getConnection(projectRoot) {
19859
19941
  }
19860
19942
  ]
19861
19943
  });
19944
+ conn.serverCapabilities = initResult?.capabilities || {};
19862
19945
  sendNotification(conn, "initialized", {});
19863
19946
  conn.initialized = true;
19864
19947
  return conn;
@@ -19956,6 +20039,52 @@ async function getDefinition(filePath, line, column) {
19956
20039
  return [];
19957
20040
  }
19958
20041
  }
20042
+ async function getImplementation(filePath, line, column) {
20043
+ const projectRoot = findProjectRoot(filePath);
20044
+ const conn = await getConnection(projectRoot);
20045
+ await ensureDocumentOpen(conn, filePath);
20046
+ const result = await sendMessage(conn, "textDocument/implementation", {
20047
+ textDocument: { uri: toUri(filePath) },
20048
+ position: { line: line - 1, character: column - 1 }
20049
+ });
20050
+ if (!result)
20051
+ return [];
20052
+ const locations = Array.isArray(result) ? result : [result];
20053
+ return locations.map((loc) => {
20054
+ const uri = loc.targetUri || loc.uri;
20055
+ const range = loc.targetRange || loc.range;
20056
+ return {
20057
+ file: fromUri(uri),
20058
+ line: range.start.line + 1,
20059
+ column: range.start.character + 1,
20060
+ endLine: range.end.line + 1,
20061
+ endColumn: range.end.character + 1
20062
+ };
20063
+ });
20064
+ }
20065
+ async function getTypeDefinition(filePath, line, column) {
20066
+ const projectRoot = findProjectRoot(filePath);
20067
+ const conn = await getConnection(projectRoot);
20068
+ await ensureDocumentOpen(conn, filePath);
20069
+ const result = await sendMessage(conn, "textDocument/typeDefinition", {
20070
+ textDocument: { uri: toUri(filePath) },
20071
+ position: { line: line - 1, character: column - 1 }
20072
+ });
20073
+ if (!result)
20074
+ return [];
20075
+ const locations = Array.isArray(result) ? result : [result];
20076
+ return locations.map((loc) => {
20077
+ const uri = loc.targetUri || loc.uri;
20078
+ const range = loc.targetRange || loc.range;
20079
+ return {
20080
+ file: fromUri(uri),
20081
+ line: range.start.line + 1,
20082
+ column: range.start.character + 1,
20083
+ endLine: range.end.line + 1,
20084
+ endColumn: range.end.character + 1
20085
+ };
20086
+ });
20087
+ }
19959
20088
  async function getReferences(filePath, line, column) {
19960
20089
  const projectRoot = findProjectRoot(filePath);
19961
20090
  const conn = await getConnection(projectRoot);
@@ -19979,6 +20108,25 @@ async function getReferences(filePath, line, column) {
19979
20108
  return [];
19980
20109
  }
19981
20110
  }
20111
+ async function getDocumentHighlights(filePath, line, column) {
20112
+ const projectRoot = findProjectRoot(filePath);
20113
+ const conn = await getConnection(projectRoot);
20114
+ await ensureDocumentOpen(conn, filePath);
20115
+ const result = await sendMessage(conn, "textDocument/documentHighlight", {
20116
+ textDocument: { uri: toUri(filePath) },
20117
+ position: { line: line - 1, character: column - 1 }
20118
+ });
20119
+ if (!result || !Array.isArray(result))
20120
+ return [];
20121
+ return result.map((item) => ({
20122
+ file: path.resolve(filePath),
20123
+ line: item.range.start.line + 1,
20124
+ column: item.range.start.character + 1,
20125
+ endLine: item.range.end.line + 1,
20126
+ endColumn: item.range.end.character + 1,
20127
+ kind: item.kind ?? 1
20128
+ }));
20129
+ }
19982
20130
  async function getCompletions(filePath, line, column, limit = 20) {
19983
20131
  const projectRoot = findProjectRoot(filePath);
19984
20132
  const conn = await getConnection(projectRoot);
@@ -20059,6 +20207,275 @@ async function getSignatureHelp(filePath, line, column) {
20059
20207
  return null;
20060
20208
  }
20061
20209
  }
20210
+ async function getPrepareRename(filePath, line, column) {
20211
+ const projectRoot = findProjectRoot(filePath);
20212
+ const conn = await getConnection(projectRoot);
20213
+ await ensureDocumentOpen(conn, filePath);
20214
+ const result = await sendMessage(conn, "textDocument/prepareRename", {
20215
+ textDocument: { uri: toUri(filePath) },
20216
+ position: { line: line - 1, character: column - 1 }
20217
+ });
20218
+ if (!result)
20219
+ return { canRename: false };
20220
+ const range = "range" in result ? result.range : result;
20221
+ const placeholder = "placeholder" in result ? result.placeholder : undefined;
20222
+ return {
20223
+ canRename: true,
20224
+ placeholder,
20225
+ range: {
20226
+ line: range.start.line + 1,
20227
+ column: range.start.character + 1,
20228
+ endLine: range.end.line + 1,
20229
+ endColumn: range.end.character + 1
20230
+ }
20231
+ };
20232
+ }
20233
+ async function getInlayHints(filePath) {
20234
+ const projectRoot = findProjectRoot(filePath);
20235
+ const conn = await getConnection(projectRoot);
20236
+ await ensureDocumentOpen(conn, filePath);
20237
+ try {
20238
+ const content = getFileContent(filePath);
20239
+ const lines = content.split(`
20240
+ `);
20241
+ const result = await sendMessage(conn, "textDocument/inlayHint", {
20242
+ textDocument: { uri: toUri(filePath) },
20243
+ range: {
20244
+ start: { line: 0, character: 0 },
20245
+ end: { line: lines.length, character: lines[lines.length - 1].length }
20246
+ }
20247
+ });
20248
+ if (!result) {
20249
+ return [];
20250
+ }
20251
+ return Array.isArray(result) ? result : [];
20252
+ } catch (error2) {
20253
+ console.error("Inlay hints error:", error2);
20254
+ return [];
20255
+ }
20256
+ }
20257
+ async function getSemanticTokens(filePath) {
20258
+ const projectRoot = findProjectRoot(filePath);
20259
+ const conn = await getConnection(projectRoot);
20260
+ await ensureDocumentOpen(conn, filePath);
20261
+ const result = await sendMessage(conn, "textDocument/semanticTokens/full", {
20262
+ textDocument: { uri: toUri(filePath) }
20263
+ });
20264
+ if (!result || !Array.isArray(result.data)) {
20265
+ return { tokens: [], count: 0 };
20266
+ }
20267
+ const legend = conn.serverCapabilities?.semanticTokensProvider?.legend || {};
20268
+ const tokenTypes = Array.isArray(legend.tokenTypes) ? legend.tokenTypes : [
20269
+ "namespace",
20270
+ "type",
20271
+ "class",
20272
+ "enum",
20273
+ "interface",
20274
+ "struct",
20275
+ "typeParameter",
20276
+ "parameter",
20277
+ "variable",
20278
+ "property",
20279
+ "enumMember",
20280
+ "event",
20281
+ "function",
20282
+ "method",
20283
+ "macro",
20284
+ "keyword",
20285
+ "modifier",
20286
+ "comment",
20287
+ "string",
20288
+ "number",
20289
+ "regexp",
20290
+ "operator",
20291
+ "decorator"
20292
+ ];
20293
+ const tokenModifiers = Array.isArray(legend.tokenModifiers) ? legend.tokenModifiers : [];
20294
+ let line = 0;
20295
+ let character = 0;
20296
+ const decoded = [];
20297
+ for (let i = 0;i < result.data.length; i += 5) {
20298
+ const deltaLine = result.data[i];
20299
+ const deltaStart = result.data[i + 1];
20300
+ const length = result.data[i + 2];
20301
+ const tokenType = result.data[i + 3];
20302
+ const modifierBits = result.data[i + 4];
20303
+ line = line + deltaLine;
20304
+ character = deltaLine > 0 ? deltaStart : character + deltaStart;
20305
+ const modifiers = tokenModifiers.filter((_, idx) => (modifierBits & 1 << idx) !== 0);
20306
+ decoded.push({
20307
+ line: line + 1,
20308
+ column: character + 1,
20309
+ endLine: line + 1,
20310
+ endColumn: character + length + 1,
20311
+ token_type: tokenTypes[tokenType] || String(tokenType),
20312
+ token_modifiers: modifiers
20313
+ });
20314
+ }
20315
+ return { tokens: decoded, count: decoded.length };
20316
+ }
20317
+ async function getSelectionRanges(filePath, line, column) {
20318
+ const projectRoot = findProjectRoot(filePath);
20319
+ const conn = await getConnection(projectRoot);
20320
+ await ensureDocumentOpen(conn, filePath);
20321
+ const result = await sendMessage(conn, "textDocument/selectionRange", {
20322
+ textDocument: { uri: toUri(filePath) },
20323
+ positions: [{ line: line - 1, character: column - 1 }]
20324
+ });
20325
+ if (!Array.isArray(result) || result.length === 0)
20326
+ return [];
20327
+ const ranges = [];
20328
+ let current = result[0];
20329
+ while (current) {
20330
+ const itemRange = current.range;
20331
+ ranges.push({
20332
+ line: itemRange.start.line + 1,
20333
+ column: itemRange.start.character + 1,
20334
+ endLine: itemRange.end.line + 1,
20335
+ endColumn: itemRange.end.character + 1
20336
+ });
20337
+ current = current.parent;
20338
+ }
20339
+ return ranges;
20340
+ }
20341
+ async function getFoldingRanges(filePath) {
20342
+ const projectRoot = findProjectRoot(filePath);
20343
+ const conn = await getConnection(projectRoot);
20344
+ await ensureDocumentOpen(conn, filePath);
20345
+ const result = await sendMessage(conn, "textDocument/foldingRange", {
20346
+ textDocument: { uri: toUri(filePath) }
20347
+ });
20348
+ if (!Array.isArray(result))
20349
+ return [];
20350
+ return result.map((item) => ({
20351
+ kind: item.kind,
20352
+ line: (item.startLine ?? 0) + 1,
20353
+ column: (item.startCharacter ?? 0) + 1,
20354
+ endLine: (item.endLine ?? 0) + 1,
20355
+ endColumn: (item.endCharacter ?? 0) + 1
20356
+ }));
20357
+ }
20358
+ async function getDocumentLinks(filePath) {
20359
+ const projectRoot = findProjectRoot(filePath);
20360
+ const conn = await getConnection(projectRoot);
20361
+ await ensureDocumentOpen(conn, filePath);
20362
+ const result = await sendMessage(conn, "textDocument/documentLink", {
20363
+ textDocument: { uri: toUri(filePath) }
20364
+ });
20365
+ if (!Array.isArray(result))
20366
+ return [];
20367
+ return result.map((item) => ({
20368
+ target: item.target,
20369
+ line: item.range.start.line + 1,
20370
+ column: item.range.start.character + 1,
20371
+ endLine: item.range.end.line + 1,
20372
+ endColumn: item.range.end.character + 1
20373
+ }));
20374
+ }
20375
+ async function getCallHierarchy(filePath, line, column, direction = "both") {
20376
+ const projectRoot = findProjectRoot(filePath);
20377
+ const conn = await getConnection(projectRoot);
20378
+ await ensureDocumentOpen(conn, filePath);
20379
+ const prepared = await sendMessage(conn, "textDocument/prepareCallHierarchy", {
20380
+ textDocument: { uri: toUri(filePath) },
20381
+ position: { line: line - 1, character: column - 1 }
20382
+ });
20383
+ if (!prepared || Array.isArray(prepared) && prepared.length === 0) {
20384
+ return { items: [], count: 0, direction };
20385
+ }
20386
+ const baseItems = Array.isArray(prepared) ? prepared : [prepared];
20387
+ const includeIncoming = direction === "both" || direction === "incoming";
20388
+ const includeOutgoing = direction === "both" || direction === "outgoing";
20389
+ const items = await Promise.all(baseItems.map(async (item) => {
20390
+ const normalizeItem = (node) => ({
20391
+ name: node.name,
20392
+ kind: node.kind,
20393
+ containerName: node.containerName,
20394
+ file: fromUri(node.uri),
20395
+ line: (node.selectionRange?.start?.line ?? node.range.start.line) + 1,
20396
+ column: (node.selectionRange?.start?.character ?? node.range.start.character) + 1
20397
+ });
20398
+ const incoming = includeIncoming ? await sendMessage(conn, "callHierarchy/incomingCalls", { item }) || [] : [];
20399
+ const outgoing = includeOutgoing ? await sendMessage(conn, "callHierarchy/outgoingCalls", { item }) || [] : [];
20400
+ return {
20401
+ symbol: normalizeItem(item),
20402
+ incoming: incoming.map((call) => ({
20403
+ from: normalizeItem(call.from),
20404
+ call_count: Array.isArray(call.fromRanges) ? call.fromRanges.length : 0
20405
+ })),
20406
+ outgoing: outgoing.map((call) => ({
20407
+ to: normalizeItem(call.to),
20408
+ call_count: Array.isArray(call.fromRanges) ? call.fromRanges.length : 0
20409
+ }))
20410
+ };
20411
+ }));
20412
+ return { items, count: items.length, direction };
20413
+ }
20414
+ async function getCodeActions(filePath, line, column) {
20415
+ const projectRoot = findProjectRoot(filePath);
20416
+ const conn = await getConnection(projectRoot);
20417
+ await ensureDocumentOpen(conn, filePath);
20418
+ const result = await sendMessage(conn, "textDocument/codeAction", {
20419
+ textDocument: { uri: toUri(filePath) },
20420
+ range: {
20421
+ start: { line: line - 1, character: column - 1 },
20422
+ end: { line: line - 1, character: column - 1 }
20423
+ },
20424
+ context: { diagnostics: [] }
20425
+ });
20426
+ return Array.isArray(result) ? result : [];
20427
+ }
20428
+ async function executeCommand(filePath, command, args = []) {
20429
+ const projectRoot = findProjectRoot(filePath);
20430
+ const conn = await getConnection(projectRoot);
20431
+ return sendMessage(conn, "workspace/executeCommand", { command, arguments: args });
20432
+ }
20433
+ async function applyWorkspaceEdit(edit) {
20434
+ const allEdits = new Map;
20435
+ if (edit?.changes && typeof edit.changes === "object") {
20436
+ for (const [uri, edits] of Object.entries(edit.changes)) {
20437
+ allEdits.set(uri, Array.isArray(edits) ? edits : []);
20438
+ }
20439
+ }
20440
+ if (Array.isArray(edit?.documentChanges)) {
20441
+ for (const change of edit.documentChanges) {
20442
+ if (change?.textDocument?.uri && Array.isArray(change.edits)) {
20443
+ const uri = change.textDocument.uri;
20444
+ const existing = allEdits.get(uri) || [];
20445
+ existing.push(...change.edits);
20446
+ allEdits.set(uri, existing);
20447
+ }
20448
+ }
20449
+ }
20450
+ let filesChanged = 0;
20451
+ let editsApplied = 0;
20452
+ for (const [uri, edits] of allEdits) {
20453
+ const filePath = fromUri(uri);
20454
+ if (!fs.existsSync(filePath))
20455
+ continue;
20456
+ const original = getFileContent(filePath);
20457
+ let content = original;
20458
+ const sorted = [...edits].sort((a, b) => {
20459
+ if (a.range.start.line !== b.range.start.line)
20460
+ return b.range.start.line - a.range.start.line;
20461
+ return b.range.start.character - a.range.start.character;
20462
+ });
20463
+ for (const textEdit of sorted) {
20464
+ const start = textEdit.range.start;
20465
+ const end = textEdit.range.end;
20466
+ const startOffset = positionToOffset(content, start.line, start.character);
20467
+ const endOffset = positionToOffset(content, end.line, end.character);
20468
+ content = content.slice(0, startOffset) + (textEdit.newText || "") + content.slice(endOffset);
20469
+ editsApplied += 1;
20470
+ }
20471
+ if (content !== original) {
20472
+ fs.writeFileSync(filePath, content, "utf-8");
20473
+ await updateDocument(filePath, content);
20474
+ filesChanged += 1;
20475
+ }
20476
+ }
20477
+ return { filesChanged, editsApplied };
20478
+ }
20062
20479
  async function getDiagnostics(filePath) {
20063
20480
  const projectRoot = findProjectRoot(filePath);
20064
20481
  const absPath = path.resolve(filePath);
@@ -20312,7 +20729,7 @@ async function ensureFileOpen(conn, filePath) {
20312
20729
  await sendCommand(conn, "open", {
20313
20730
  file: absPath,
20314
20731
  fileContent: content,
20315
- scriptKindName: "TS",
20732
+ scriptKindName: "Unknown",
20316
20733
  projectRootPath: conn.projectRoot
20317
20734
  });
20318
20735
  conn.openedFiles.add(absPath);
@@ -20589,39 +21006,105 @@ async function getRenameLocations(filePath, line, column) {
20589
21006
 
20590
21007
  // src/index.ts
20591
21008
  async function getQuickInfo3(file, line, column) {
20592
- const lspResult = await getQuickInfo(file, line, column);
21009
+ const { absPath, error: error2 } = resolveFilePath(file);
21010
+ if (error2 || !absPath)
21011
+ throw new Error(error2 || "Invalid path");
21012
+ const lspResult = await getQuickInfo(absPath, line, column);
20593
21013
  if (lspResult && lspResult.contents) {
20594
21014
  return lspResult;
20595
21015
  }
20596
- return getQuickInfo2(file, line, column);
21016
+ return getQuickInfo2(absPath, line, column);
20597
21017
  }
20598
21018
  async function getDefinition3(file, line, column) {
20599
- const lspResult = await getDefinition(file, line, column);
21019
+ const { absPath, error: error2 } = resolveFilePath(file);
21020
+ if (error2 || !absPath)
21021
+ throw new Error(error2 || "Invalid path");
21022
+ const lspResult = await getDefinition(absPath, line, column);
20600
21023
  if (lspResult && lspResult.length > 0) {
20601
21024
  return lspResult;
20602
21025
  }
20603
- return getDefinition2(file, line, column);
21026
+ return getDefinition2(absPath, line, column);
21027
+ }
21028
+ async function getImplementation2(file, line, column) {
21029
+ const { absPath, error: error2 } = resolveFilePath(file);
21030
+ if (error2 || !absPath)
21031
+ throw new Error(error2 || "Invalid path");
21032
+ return getImplementation(absPath, line, column);
21033
+ }
21034
+ async function getTypeDefinition2(file, line, column) {
21035
+ const { absPath, error: error2 } = resolveFilePath(file);
21036
+ if (error2 || !absPath)
21037
+ throw new Error(error2 || "Invalid path");
21038
+ return getTypeDefinition(absPath, line, column);
20604
21039
  }
20605
21040
  async function getReferences3(file, line, column) {
20606
- const lspResult = await getReferences(file, line, column);
21041
+ const { absPath, error: error2 } = resolveFilePath(file);
21042
+ if (error2 || !absPath)
21043
+ throw new Error(error2 || "Invalid path");
21044
+ const lspResult = await getReferences(absPath, line, column);
20607
21045
  if (lspResult && lspResult.length > 0) {
20608
21046
  return lspResult;
20609
21047
  }
20610
- return getReferences2(file, line, column);
21048
+ return getReferences2(absPath, line, column);
21049
+ }
21050
+ async function getDocumentHighlights2(file, line, column) {
21051
+ const { absPath, error: error2 } = resolveFilePath(file);
21052
+ if (error2 || !absPath)
21053
+ throw new Error(error2 || "Invalid path");
21054
+ return getDocumentHighlights(absPath, line, column);
20611
21055
  }
20612
21056
  async function getCompletions3(file, line, column, limit = 20) {
20613
- const lspResult = await getCompletions(file, line, column, limit);
21057
+ const { absPath, error: error2 } = resolveFilePath(file);
21058
+ if (error2 || !absPath)
21059
+ throw new Error(error2 || "Invalid path");
21060
+ const lspResult = await getCompletions(absPath, line, column, limit);
20614
21061
  if (lspResult && lspResult.items && lspResult.items.length > 0) {
20615
21062
  return lspResult;
20616
21063
  }
20617
- return getCompletions2(file, line, column, limit);
21064
+ return getCompletions2(absPath, line, column, limit);
20618
21065
  }
20619
21066
  async function getSignatureHelp3(file, line, column) {
20620
- const lspResult = await getSignatureHelp(file, line, column);
21067
+ const { absPath, error: error2 } = resolveFilePath(file);
21068
+ if (error2 || !absPath)
21069
+ throw new Error(error2 || "Invalid path");
21070
+ const lspResult = await getSignatureHelp(absPath, line, column);
20621
21071
  if (lspResult && lspResult.signatures && lspResult.signatures.length > 0) {
20622
21072
  return lspResult;
20623
21073
  }
20624
- return getSignatureHelp2(file, line, column);
21074
+ return getSignatureHelp2(absPath, line, column);
21075
+ }
21076
+ function symbolAtPosition(content, line, column) {
21077
+ const lines = content.split(`
21078
+ `);
21079
+ if (line < 1 || line > lines.length)
21080
+ return "";
21081
+ const text = lines[line - 1] || "";
21082
+ if (!text.length)
21083
+ return "";
21084
+ const idx = Math.max(0, Math.min(text.length - 1, column - 1));
21085
+ const re = /[A-Za-z_][A-Za-z0-9_]*/g;
21086
+ let match;
21087
+ while ((match = re.exec(text)) !== null) {
21088
+ if (match.index <= idx && idx < match.index + match[0].length) {
21089
+ return match[0];
21090
+ }
21091
+ }
21092
+ return "";
21093
+ }
21094
+ function packageNameFromPath(filePath) {
21095
+ const normalized = filePath.replace(/\\/g, "/");
21096
+ const marker = "/node_modules/";
21097
+ const idx = normalized.lastIndexOf(marker);
21098
+ if (idx < 0)
21099
+ return null;
21100
+ const rest = normalized.slice(idx + marker.length);
21101
+ const parts = rest.split("/");
21102
+ if (!parts.length)
21103
+ return null;
21104
+ if (parts[0].startsWith("@") && parts.length > 1) {
21105
+ return `${parts[0]}/${parts[1]}`;
21106
+ }
21107
+ return parts[0] || null;
20625
21108
  }
20626
21109
  var require2 = createRequire3(import.meta.url);
20627
21110
  var packageJson = require2("../package.json");
@@ -20629,6 +21112,35 @@ var server = new McpServer({
20629
21112
  name: "vue-lsp-mcp",
20630
21113
  version: packageJson.version
20631
21114
  });
21115
+ server.tool("switch_workspace", "Switch the active workspace to a new project directory", {
21116
+ path: exports_external.string().describe("Absolute path to the new project root directory")
21117
+ }, async ({ path: inputPath }) => {
21118
+ try {
21119
+ const absPath = path3.resolve(inputPath);
21120
+ if (!fs3.existsSync(absPath) || !fs3.statSync(absPath).isDirectory()) {
21121
+ return {
21122
+ content: [{ type: "text", text: JSON.stringify({ error: "Invalid Path", message: `'${inputPath}' is not a directory.` }) }]
21123
+ };
21124
+ }
21125
+ clearAllConnections();
21126
+ const newWorkspace = setActiveWorkspace(absPath);
21127
+ return {
21128
+ content: [{
21129
+ type: "text",
21130
+ text: JSON.stringify({
21131
+ success: true,
21132
+ message: `Switched active workspace to: ${newWorkspace}`,
21133
+ workspace: newWorkspace,
21134
+ info: "All previous Vue language server connections have been closed."
21135
+ })
21136
+ }]
21137
+ };
21138
+ } catch (error2) {
21139
+ return {
21140
+ content: [{ type: "text", text: JSON.stringify({ error: String(error2) }) }]
21141
+ };
21142
+ }
21143
+ });
20632
21144
  server.tool("hover", "Get type information and documentation at a specific position in a Vue SFC file", {
20633
21145
  file: exports_external.string().describe("Absolute path to the .vue file"),
20634
21146
  line: exports_external.number().int().positive().describe("Line number (1-based)"),
@@ -20677,6 +21189,88 @@ server.tool("definition", "Go to definition of a symbol at a specific position i
20677
21189
  };
20678
21190
  }
20679
21191
  });
21192
+ server.tool("implementation", "Go to implementation of a symbol at a specific position in a Vue SFC file", {
21193
+ file: exports_external.string().describe("Path to the .vue file (absolute or relative to active workspace)"),
21194
+ line: exports_external.number().int().positive().describe("Line number (1-based)"),
21195
+ column: exports_external.number().int().positive().describe("Column number (1-based)")
21196
+ }, async ({ file, line, column }) => {
21197
+ try {
21198
+ const implementations = await getImplementation2(file, line, column);
21199
+ if (!implementations || implementations.length === 0) {
21200
+ return {
21201
+ content: [{ type: "text", text: JSON.stringify({ error: "No implementation found" }) }]
21202
+ };
21203
+ }
21204
+ return {
21205
+ content: [{
21206
+ type: "text",
21207
+ text: JSON.stringify(implementations.length === 1 ? implementations[0] : implementations)
21208
+ }]
21209
+ };
21210
+ } catch (error2) {
21211
+ const message = String(error2);
21212
+ if (message.includes("Method not found") || message.includes("implementation")) {
21213
+ return {
21214
+ content: [{
21215
+ type: "text",
21216
+ text: JSON.stringify({
21217
+ error: "NOT_IMPLEMENTED",
21218
+ error_code: "NOT_IMPLEMENTED",
21219
+ message: "Vue backend does not expose implementation in this environment.",
21220
+ next_step: "Use definition/references or switch to TypeScript backend for this feature.",
21221
+ install_commands: [],
21222
+ missing_packages: [],
21223
+ strict_mode: true
21224
+ })
21225
+ }]
21226
+ };
21227
+ }
21228
+ return {
21229
+ content: [{ type: "text", text: JSON.stringify({ error: message }) }]
21230
+ };
21231
+ }
21232
+ });
21233
+ server.tool("type_definition", "Go to type definition of a symbol at a specific position in a Vue SFC file", {
21234
+ file: exports_external.string().describe("Path to the .vue file (absolute or relative to active workspace)"),
21235
+ line: exports_external.number().int().positive().describe("Line number (1-based)"),
21236
+ column: exports_external.number().int().positive().describe("Column number (1-based)")
21237
+ }, async ({ file, line, column }) => {
21238
+ try {
21239
+ const typeDefinitions = await getTypeDefinition2(file, line, column);
21240
+ if (!typeDefinitions || typeDefinitions.length === 0) {
21241
+ return {
21242
+ content: [{ type: "text", text: JSON.stringify({ error: "No type definition found" }) }]
21243
+ };
21244
+ }
21245
+ return {
21246
+ content: [{
21247
+ type: "text",
21248
+ text: JSON.stringify(typeDefinitions.length === 1 ? typeDefinitions[0] : typeDefinitions)
21249
+ }]
21250
+ };
21251
+ } catch (error2) {
21252
+ const message = String(error2);
21253
+ if (message.includes("Method not found") || message.includes("typeDefinition")) {
21254
+ return {
21255
+ content: [{
21256
+ type: "text",
21257
+ text: JSON.stringify({
21258
+ error: "NOT_IMPLEMENTED",
21259
+ error_code: "NOT_IMPLEMENTED",
21260
+ message: "Vue backend does not expose type_definition in this environment.",
21261
+ next_step: "Use hover/definition or switch to TypeScript backend for this feature.",
21262
+ install_commands: [],
21263
+ missing_packages: [],
21264
+ strict_mode: true
21265
+ })
21266
+ }]
21267
+ };
21268
+ }
21269
+ return {
21270
+ content: [{ type: "text", text: JSON.stringify({ error: message }) }]
21271
+ };
21272
+ }
21273
+ });
20680
21274
  server.tool("references", "Find all references to a symbol at a specific position in a Vue SFC file", {
20681
21275
  file: exports_external.string().describe("Absolute path to the .vue file"),
20682
21276
  line: exports_external.number().int().positive().describe("Line number (1-based)"),
@@ -20696,6 +21290,85 @@ server.tool("references", "Find all references to a symbol at a specific positio
20696
21290
  };
20697
21291
  }
20698
21292
  });
21293
+ server.tool("call_hierarchy", "Get incoming/outgoing call hierarchy for symbol at a specific position in a Vue SFC file", {
21294
+ file: exports_external.string().describe("Path to the .vue file (absolute or relative to active workspace)"),
21295
+ line: exports_external.number().int().positive().describe("Line number (1-based)"),
21296
+ column: exports_external.number().int().positive().describe("Column number (1-based)"),
21297
+ direction: exports_external.enum(["incoming", "outgoing", "both"]).default("both").optional().describe("Call hierarchy direction to include")
21298
+ }, async ({ file, line, column, direction }) => {
21299
+ try {
21300
+ const { absPath, error: error2 } = resolveFilePath(file);
21301
+ if (error2 || !absPath) {
21302
+ return { content: [{ type: "text", text: error2 || "Invalid path" }] };
21303
+ }
21304
+ const result = await getCallHierarchy(absPath, line, column, direction || "both");
21305
+ if (!result.count) {
21306
+ return {
21307
+ content: [{ type: "text", text: JSON.stringify({ error: "No call hierarchy found", ...result }) }]
21308
+ };
21309
+ }
21310
+ return {
21311
+ content: [{ type: "text", text: JSON.stringify(result) }]
21312
+ };
21313
+ } catch (error2) {
21314
+ const message = String(error2);
21315
+ if (message.includes("Method not found") || message.includes("callHierarchy")) {
21316
+ return {
21317
+ content: [{
21318
+ type: "text",
21319
+ text: JSON.stringify({
21320
+ error: "NOT_IMPLEMENTED",
21321
+ error_code: "NOT_IMPLEMENTED",
21322
+ message: "Vue backend does not expose call_hierarchy in this environment.",
21323
+ next_step: "Use references/definition or switch to TypeScript backend for call_hierarchy.",
21324
+ install_commands: [],
21325
+ missing_packages: [],
21326
+ strict_mode: true
21327
+ })
21328
+ }]
21329
+ };
21330
+ }
21331
+ return {
21332
+ content: [{ type: "text", text: JSON.stringify({ error: message }) }]
21333
+ };
21334
+ }
21335
+ });
21336
+ server.tool("document_highlight", "Find symbol highlights in current Vue document at a specific position", {
21337
+ file: exports_external.string().describe("Path to the .vue file (absolute or relative to active workspace)"),
21338
+ line: exports_external.number().int().positive().describe("Line number (1-based)"),
21339
+ column: exports_external.number().int().positive().describe("Column number (1-based)")
21340
+ }, async ({ file, line, column }) => {
21341
+ try {
21342
+ const highlights = await getDocumentHighlights2(file, line, column);
21343
+ return {
21344
+ content: [{
21345
+ type: "text",
21346
+ text: JSON.stringify({ highlights, count: highlights.length })
21347
+ }]
21348
+ };
21349
+ } catch (error2) {
21350
+ const message = String(error2);
21351
+ if (message.includes("Method not found") || message.includes("documentHighlight")) {
21352
+ return {
21353
+ content: [{
21354
+ type: "text",
21355
+ text: JSON.stringify({
21356
+ error: "NOT_IMPLEMENTED",
21357
+ error_code: "NOT_IMPLEMENTED",
21358
+ message: "Vue backend does not expose document_highlight in this environment.",
21359
+ next_step: "Use references as fallback for symbol occurrences.",
21360
+ install_commands: [],
21361
+ missing_packages: [],
21362
+ strict_mode: true
21363
+ })
21364
+ }]
21365
+ };
21366
+ }
21367
+ return {
21368
+ content: [{ type: "text", text: JSON.stringify({ error: message }) }]
21369
+ };
21370
+ }
21371
+ });
20699
21372
  server.tool("completions", "Get code completion suggestions at a specific position in a Vue SFC file", {
20700
21373
  file: exports_external.string().describe("Absolute path to the .vue file"),
20701
21374
  line: exports_external.number().int().positive().describe("Line number (1-based)"),
@@ -20744,11 +21417,106 @@ server.tool("signature_help", "Get function signature help at a specific positio
20744
21417
  };
20745
21418
  }
20746
21419
  });
21420
+ server.tool("inlay_hints", "Get inlay hints (type annotations, parameter names) for a Vue SFC file", {
21421
+ file: exports_external.string().describe("Path to the .vue file (absolute or relative to active workspace)")
21422
+ }, async ({ file }) => {
21423
+ try {
21424
+ const { absPath, error: error2 } = resolveFilePath(file);
21425
+ if (error2 || !absPath) {
21426
+ return { content: [{ type: "text", text: error2 || "Invalid path" }] };
21427
+ }
21428
+ const hints = await getInlayHints(absPath);
21429
+ return {
21430
+ content: [{
21431
+ type: "text",
21432
+ text: JSON.stringify({ hints, count: hints.length })
21433
+ }]
21434
+ };
21435
+ } catch (error2) {
21436
+ return {
21437
+ content: [{ type: "text", text: JSON.stringify({ error: String(error2) }) }]
21438
+ };
21439
+ }
21440
+ });
21441
+ server.tool("semantic_tokens", "Get semantic tokens for a Vue SFC file", {
21442
+ file: exports_external.string().describe("Path to the .vue file (absolute or relative to active workspace)")
21443
+ }, async ({ file }) => {
21444
+ try {
21445
+ const { absPath, error: error2 } = resolveFilePath(file);
21446
+ if (error2 || !absPath) {
21447
+ return { content: [{ type: "text", text: error2 || "Invalid path" }] };
21448
+ }
21449
+ const result = await getSemanticTokens(absPath);
21450
+ return {
21451
+ content: [{
21452
+ type: "text",
21453
+ text: JSON.stringify(result)
21454
+ }]
21455
+ };
21456
+ } catch (error2) {
21457
+ const message = String(error2);
21458
+ if (message.includes("Method not found") || message.includes("semanticTokens")) {
21459
+ return {
21460
+ content: [{
21461
+ type: "text",
21462
+ text: JSON.stringify({
21463
+ error: "NOT_IMPLEMENTED",
21464
+ error_code: "NOT_IMPLEMENTED",
21465
+ message: "Vue backend does not expose semantic tokens in this environment.",
21466
+ next_step: "Use hover/definition/references or switch to TypeScript/Python where semantic_tokens is available.",
21467
+ install_commands: [],
21468
+ missing_packages: [],
21469
+ strict_mode: true
21470
+ })
21471
+ }]
21472
+ };
21473
+ }
21474
+ return {
21475
+ content: [{ type: "text", text: JSON.stringify({ error: message }) }]
21476
+ };
21477
+ }
21478
+ });
21479
+ server.tool("moniker", "Get moniker-like symbol identity for cross-package tracking in Vue files", {
21480
+ file: exports_external.string().describe("Path to the .vue file (absolute or relative to active workspace)"),
21481
+ line: exports_external.number().int().positive().describe("Line number (1-based)"),
21482
+ column: exports_external.number().int().positive().describe("Column number (1-based)")
21483
+ }, async ({ file, line, column }) => {
21484
+ try {
21485
+ const { absPath, error: error2 } = resolveFilePath(file);
21486
+ if (error2 || !absPath) {
21487
+ return { content: [{ type: "text", text: error2 || "Invalid path" }] };
21488
+ }
21489
+ const definitions = await getDefinition3(absPath, line, column);
21490
+ const sourceFile = definitions.length > 0 ? definitions[0].file : absPath;
21491
+ const content = getFileContent(absPath);
21492
+ const symbol = symbolAtPosition(content, line, column);
21493
+ const packageName = packageNameFromPath(sourceFile);
21494
+ const identifier = packageName ? `npm:${packageName}:${symbol || `${line}:${column}`}` : `workspace:${sourceFile}:${symbol || `${line}:${column}`}`;
21495
+ return {
21496
+ content: [{
21497
+ type: "text",
21498
+ text: JSON.stringify({
21499
+ symbol,
21500
+ identifier,
21501
+ package_name: packageName,
21502
+ source_file: sourceFile
21503
+ })
21504
+ }]
21505
+ };
21506
+ } catch (error2) {
21507
+ return {
21508
+ content: [{ type: "text", text: JSON.stringify({ error: String(error2) }) }]
21509
+ };
21510
+ }
21511
+ });
20747
21512
  server.tool("diagnostics", "Get type errors and warnings for Vue SFC files", {
20748
- path: exports_external.string().describe("Path to a .vue file or directory to check")
21513
+ path: exports_external.string().describe("Path to a .vue file or directory to check (absolute or relative to active workspace)")
20749
21514
  }, async ({ path: inputPath }) => {
20750
21515
  try {
20751
- const absPath = path3.resolve(inputPath);
21516
+ const { absPath, error: error2 } = resolveFilePath(inputPath);
21517
+ if (error2 || !absPath) {
21518
+ return { content: [{ type: "text", text: error2 || "Invalid path" }] };
21519
+ }
20752
21520
  const stats = fs3.statSync(absPath);
20753
21521
  let files = [];
20754
21522
  if (stats.isDirectory()) {
@@ -20793,15 +21561,19 @@ server.tool("diagnostics", "Get type errors and warnings for Vue SFC files", {
20793
21561
  }
20794
21562
  });
20795
21563
  server.tool("update_document", "Update Vue file content for incremental analysis without writing to disk", {
20796
- file: exports_external.string().describe("Absolute path to the .vue file"),
21564
+ file: exports_external.string().describe("Path to the .vue file (absolute or relative to active workspace)"),
20797
21565
  content: exports_external.string().describe("New content for the file")
20798
21566
  }, async ({ file, content }) => {
20799
21567
  try {
20800
- await updateDocument(file, content);
21568
+ const { absPath, error: error2 } = resolveFilePath(file);
21569
+ if (error2 || !absPath) {
21570
+ return { content: [{ type: "text", text: error2 || "Invalid path" }] };
21571
+ }
21572
+ await updateDocument(absPath, content);
20801
21573
  return {
20802
21574
  content: [{
20803
21575
  type: "text",
20804
- text: JSON.stringify({ success: true, file })
21576
+ text: JSON.stringify({ success: true, file: absPath })
20805
21577
  }]
20806
21578
  };
20807
21579
  } catch (error2) {
@@ -20811,7 +21583,7 @@ server.tool("update_document", "Update Vue file content for incremental analysis
20811
21583
  }
20812
21584
  });
20813
21585
  server.tool("symbols", "Extract symbols (variables, functions, components) from a Vue SFC file", {
20814
- file: exports_external.string().describe("Absolute path to the .vue file"),
21586
+ file: exports_external.string().describe("Path to the .vue file (absolute or relative to active workspace)"),
20815
21587
  query: exports_external.string().optional().describe("Optional filter query for symbol names")
20816
21588
  }, async ({ file, query }) => {
20817
21589
  try {
@@ -20846,7 +21618,11 @@ server.tool("symbols", "Extract symbols (variables, functions, components) from
20846
21618
  }
20847
21619
  }
20848
21620
  };
20849
- const tree = await getDocumentSymbols(file);
21621
+ const { absPath, error: error2 } = resolveFilePath(file);
21622
+ if (error2 || !absPath) {
21623
+ return { content: [{ type: "text", text: error2 || "Invalid path" }] };
21624
+ }
21625
+ const tree = await getDocumentSymbols(absPath);
20850
21626
  if (!tree) {
20851
21627
  return {
20852
21628
  content: [{ type: "text", text: JSON.stringify({ error: "Failed to get symbols" }) }]
@@ -20870,14 +21646,162 @@ server.tool("symbols", "Extract symbols (variables, functions, components) from
20870
21646
  };
20871
21647
  }
20872
21648
  });
21649
+ server.tool("code_action", "Get available code actions (quick fixes/refactors) at a specific position in a Vue SFC file", {
21650
+ file: exports_external.string().describe("Path to the .vue file (absolute or relative to active workspace)"),
21651
+ line: exports_external.number().int().positive().describe("Line number (1-based)"),
21652
+ column: exports_external.number().int().positive().describe("Column number (1-based)")
21653
+ }, async ({ file, line, column }) => {
21654
+ try {
21655
+ const { absPath, error: error2 } = resolveFilePath(file);
21656
+ if (error2 || !absPath) {
21657
+ return { content: [{ type: "text", text: error2 || "Invalid path" }] };
21658
+ }
21659
+ const actions = await getCodeActions(absPath, line, column);
21660
+ const formatted = actions.map((action) => ({
21661
+ title: action.title,
21662
+ kind: action.kind || "quickfix",
21663
+ hasEdit: Boolean(action.edit),
21664
+ hasCommand: Boolean(action.command),
21665
+ data: action.data
21666
+ }));
21667
+ return {
21668
+ content: [{ type: "text", text: JSON.stringify({ actions: formatted, count: formatted.length }) }]
21669
+ };
21670
+ } catch (error2) {
21671
+ const message = String(error2);
21672
+ if (message.includes("Method not found") || message.includes("codeAction")) {
21673
+ return {
21674
+ content: [{
21675
+ type: "text",
21676
+ text: JSON.stringify({
21677
+ error: "NOT_IMPLEMENTED",
21678
+ error_code: "NOT_IMPLEMENTED",
21679
+ message: "Vue backend does not expose code_action in this environment.",
21680
+ next_step: "Use diagnostics + manual edits as fallback.",
21681
+ install_commands: [],
21682
+ missing_packages: [],
21683
+ strict_mode: true
21684
+ })
21685
+ }]
21686
+ };
21687
+ }
21688
+ return {
21689
+ content: [{ type: "text", text: JSON.stringify({ error: message }) }]
21690
+ };
21691
+ }
21692
+ });
21693
+ server.tool("run_code_action", "Run a specific code action at a position in a Vue SFC file", {
21694
+ file: exports_external.string().describe("Path to the .vue file (absolute or relative to active workspace)"),
21695
+ line: exports_external.number().int().positive().describe("Line number (1-based)"),
21696
+ column: exports_external.number().int().positive().describe("Column number (1-based)"),
21697
+ title: exports_external.string().describe("Code action title (exact match from code_action output)")
21698
+ }, async ({ file, line, column, title }) => {
21699
+ try {
21700
+ const { absPath, error: error2 } = resolveFilePath(file);
21701
+ if (error2 || !absPath) {
21702
+ return { content: [{ type: "text", text: error2 || "Invalid path" }] };
21703
+ }
21704
+ const actions = await getCodeActions(absPath, line, column);
21705
+ const action = actions.find((a) => a.title === title);
21706
+ if (!action) {
21707
+ return {
21708
+ content: [{ type: "text", text: JSON.stringify({ error: `Action '${title}' not found` }) }]
21709
+ };
21710
+ }
21711
+ let editResult = null;
21712
+ if (action.edit) {
21713
+ editResult = await applyWorkspaceEdit(action.edit);
21714
+ }
21715
+ if (action.command) {
21716
+ const commandName = typeof action.command === "string" ? action.command : action.command.command;
21717
+ const args = typeof action.command === "string" ? [] : action.command.arguments || [];
21718
+ await executeCommand(absPath, commandName, args);
21719
+ }
21720
+ return {
21721
+ content: [{
21722
+ type: "text",
21723
+ text: JSON.stringify({
21724
+ success: true,
21725
+ title: action.title,
21726
+ kind: action.kind || "quickfix",
21727
+ appliedEdit: Boolean(action.edit),
21728
+ executedCommand: Boolean(action.command),
21729
+ editResult
21730
+ })
21731
+ }]
21732
+ };
21733
+ } catch (error2) {
21734
+ const message = String(error2);
21735
+ if (message.includes("Method not found") || message.includes("codeAction") || message.includes("executeCommand")) {
21736
+ return {
21737
+ content: [{
21738
+ type: "text",
21739
+ text: JSON.stringify({
21740
+ error: "NOT_IMPLEMENTED",
21741
+ error_code: "NOT_IMPLEMENTED",
21742
+ message: "Vue backend cannot run code actions in this environment.",
21743
+ next_step: "Use code_action for hints and apply edits manually.",
21744
+ install_commands: [],
21745
+ missing_packages: [],
21746
+ strict_mode: true
21747
+ })
21748
+ }]
21749
+ };
21750
+ }
21751
+ return {
21752
+ content: [{ type: "text", text: JSON.stringify({ error: message }) }]
21753
+ };
21754
+ }
21755
+ });
21756
+ server.tool("prepare_rename", "Check whether a symbol can be safely renamed at a specific position in a Vue SFC file", {
21757
+ file: exports_external.string().describe("Path to the .vue file (absolute or relative to active workspace)"),
21758
+ line: exports_external.number().int().positive().describe("Line number (1-based)"),
21759
+ column: exports_external.number().int().positive().describe("Column number (1-based)")
21760
+ }, async ({ file, line, column }) => {
21761
+ try {
21762
+ const { absPath, error: error2 } = resolveFilePath(file);
21763
+ if (error2 || !absPath) {
21764
+ return { content: [{ type: "text", text: error2 || "Invalid path" }] };
21765
+ }
21766
+ const result = await getPrepareRename(absPath, line, column);
21767
+ return {
21768
+ content: [{ type: "text", text: JSON.stringify(result) }]
21769
+ };
21770
+ } catch (error2) {
21771
+ const message = String(error2);
21772
+ if (message.includes("Method not found") || message.includes("prepareRename")) {
21773
+ return {
21774
+ content: [{
21775
+ type: "text",
21776
+ text: JSON.stringify({
21777
+ error: "NOT_IMPLEMENTED",
21778
+ error_code: "NOT_IMPLEMENTED",
21779
+ message: "Vue backend does not expose prepare_rename in this environment.",
21780
+ next_step: "Use rename preview and verify affected ranges manually.",
21781
+ install_commands: [],
21782
+ missing_packages: [],
21783
+ strict_mode: true
21784
+ })
21785
+ }]
21786
+ };
21787
+ }
21788
+ return {
21789
+ content: [{ type: "text", text: JSON.stringify({ error: message }) }]
21790
+ };
21791
+ }
21792
+ });
20873
21793
  server.tool("rename", "Preview renaming a symbol at a specific position (shows all locations that would be renamed)", {
20874
- file: exports_external.string().describe("Absolute path to the .vue file"),
21794
+ file: exports_external.string().describe("Path to the .vue file (absolute or relative to active workspace)"),
20875
21795
  line: exports_external.number().int().positive().describe("Line number (1-based)"),
20876
21796
  column: exports_external.number().int().positive().describe("Column number (1-based)"),
20877
21797
  newName: exports_external.string().describe("New name for the symbol")
20878
21798
  }, async ({ file, line, column, newName }) => {
20879
21799
  try {
20880
- const locations = await getRenameLocations(file, line, column);
21800
+ const { absPath, error: error2 } = resolveFilePath(file);
21801
+ if (error2 || !absPath) {
21802
+ return { content: [{ type: "text", text: error2 || "Invalid path" }] };
21803
+ }
21804
+ const locations = await getRenameLocations(absPath, line, column);
20881
21805
  if (!locations || locations.length === 0) {
20882
21806
  return {
20883
21807
  content: [{ type: "text", text: JSON.stringify({ error: "Cannot rename symbol at this position" }) }]
@@ -20912,14 +21836,174 @@ server.tool("rename", "Preview renaming a symbol at a specific position (shows a
20912
21836
  };
20913
21837
  }
20914
21838
  });
21839
+ server.tool("linked_editing_range", "Get linked editing ranges at a specific position in a Vue SFC file", {
21840
+ file: exports_external.string().describe("Path to the .vue file (absolute or relative to active workspace)"),
21841
+ line: exports_external.number().int().positive().describe("Line number (1-based)"),
21842
+ column: exports_external.number().int().positive().describe("Column number (1-based)")
21843
+ }, async ({ file, line, column }) => {
21844
+ try {
21845
+ const { absPath, error: error2 } = resolveFilePath(file);
21846
+ if (error2 || !absPath) {
21847
+ return { content: [{ type: "text", text: error2 || "Invalid path" }] };
21848
+ }
21849
+ const locations = await getRenameLocations(absPath, line, column);
21850
+ if (!locations || locations.length === 0) {
21851
+ return {
21852
+ content: [{ type: "text", text: JSON.stringify({ ranges: [], count: 0 }) }]
21853
+ };
21854
+ }
21855
+ const current = path3.resolve(absPath);
21856
+ const ranges = locations.filter((loc) => path3.resolve(loc.file) === current).map((loc) => ({
21857
+ line: loc.line,
21858
+ column: loc.column,
21859
+ endLine: loc.line,
21860
+ endColumn: loc.column + loc.length
21861
+ }));
21862
+ return {
21863
+ content: [{
21864
+ type: "text",
21865
+ text: JSON.stringify({ ranges, count: ranges.length })
21866
+ }]
21867
+ };
21868
+ } catch (error2) {
21869
+ return {
21870
+ content: [{ type: "text", text: JSON.stringify({ error: String(error2) }) }]
21871
+ };
21872
+ }
21873
+ });
21874
+ server.tool("selection_range", "Get nested smart selection ranges at a specific position in a Vue SFC file", {
21875
+ file: exports_external.string().describe("Path to the .vue file (absolute or relative to active workspace)"),
21876
+ line: exports_external.number().int().positive().describe("Line number (1-based)"),
21877
+ column: exports_external.number().int().positive().describe("Column number (1-based)")
21878
+ }, async ({ file, line, column }) => {
21879
+ try {
21880
+ const { absPath, error: error2 } = resolveFilePath(file);
21881
+ if (error2 || !absPath) {
21882
+ return { content: [{ type: "text", text: error2 || "Invalid path" }] };
21883
+ }
21884
+ const ranges = await getSelectionRanges(absPath, line, column);
21885
+ if (!ranges.length) {
21886
+ return {
21887
+ content: [{ type: "text", text: JSON.stringify({ error: "No selection range found" }) }]
21888
+ };
21889
+ }
21890
+ return {
21891
+ content: [{ type: "text", text: JSON.stringify({ ranges, count: ranges.length }) }]
21892
+ };
21893
+ } catch (error2) {
21894
+ const message = String(error2);
21895
+ if (message.includes("Method not found") || message.includes("selectionRange")) {
21896
+ return {
21897
+ content: [{
21898
+ type: "text",
21899
+ text: JSON.stringify({
21900
+ error: "NOT_IMPLEMENTED",
21901
+ error_code: "NOT_IMPLEMENTED",
21902
+ message: "Vue backend does not expose selection_range in this environment.",
21903
+ next_step: "Use read_file_with_hints/symbols and explicit ranges as fallback.",
21904
+ install_commands: [],
21905
+ missing_packages: [],
21906
+ strict_mode: true
21907
+ })
21908
+ }]
21909
+ };
21910
+ }
21911
+ return {
21912
+ content: [{ type: "text", text: JSON.stringify({ error: message }) }]
21913
+ };
21914
+ }
21915
+ });
21916
+ server.tool("folding_range", "Get foldable ranges in a Vue SFC file", {
21917
+ file: exports_external.string().describe("Path to the .vue file (absolute or relative to active workspace)")
21918
+ }, async ({ file }) => {
21919
+ try {
21920
+ const { absPath, error: error2 } = resolveFilePath(file);
21921
+ if (error2 || !absPath) {
21922
+ return { content: [{ type: "text", text: error2 || "Invalid path" }] };
21923
+ }
21924
+ const ranges = await getFoldingRanges(absPath);
21925
+ return {
21926
+ content: [{ type: "text", text: JSON.stringify({ ranges, count: ranges.length }) }]
21927
+ };
21928
+ } catch (error2) {
21929
+ const message = String(error2);
21930
+ if (message.includes("Method not found") || message.includes("foldingRange")) {
21931
+ return {
21932
+ content: [{
21933
+ type: "text",
21934
+ text: JSON.stringify({
21935
+ error: "NOT_IMPLEMENTED",
21936
+ error_code: "NOT_IMPLEMENTED",
21937
+ message: "Vue backend does not expose folding_range in this environment.",
21938
+ next_step: "Use symbols-based sections for manual folding.",
21939
+ install_commands: [],
21940
+ missing_packages: [],
21941
+ strict_mode: true
21942
+ })
21943
+ }]
21944
+ };
21945
+ }
21946
+ return {
21947
+ content: [{ type: "text", text: JSON.stringify({ error: message }) }]
21948
+ };
21949
+ }
21950
+ });
21951
+ server.tool("document_link", "Extract links (imports/URLs) from a Vue SFC file", {
21952
+ file: exports_external.string().describe("Path to the .vue file (absolute or relative to active workspace)")
21953
+ }, async ({ file }) => {
21954
+ try {
21955
+ const { absPath, error: error2 } = resolveFilePath(file);
21956
+ if (error2 || !absPath) {
21957
+ return { content: [{ type: "text", text: error2 || "Invalid path" }] };
21958
+ }
21959
+ const links = await getDocumentLinks(absPath);
21960
+ return {
21961
+ content: [{ type: "text", text: JSON.stringify({ links, count: links.length }) }]
21962
+ };
21963
+ } catch (error2) {
21964
+ const message = String(error2);
21965
+ if (message.includes("Method not found") || message.includes("documentLink")) {
21966
+ return {
21967
+ content: [{
21968
+ type: "text",
21969
+ text: JSON.stringify({
21970
+ error: "NOT_IMPLEMENTED",
21971
+ error_code: "NOT_IMPLEMENTED",
21972
+ message: "Vue backend does not expose document_link in this environment.",
21973
+ next_step: "Use search over import/url patterns as fallback.",
21974
+ install_commands: [],
21975
+ missing_packages: [],
21976
+ strict_mode: true
21977
+ })
21978
+ }]
21979
+ };
21980
+ }
21981
+ return {
21982
+ content: [{ type: "text", text: JSON.stringify({ error: message }) }]
21983
+ };
21984
+ }
21985
+ });
20915
21986
  server.tool("search", "Search for a pattern in Vue files using ripgrep", {
20916
21987
  pattern: exports_external.string().describe("The regex pattern to search for"),
20917
- path: exports_external.string().optional().describe("Directory or file to search in"),
21988
+ path: exports_external.string().optional().describe("Directory or file to search in (absolute or relative to active workspace)"),
20918
21989
  glob: exports_external.string().optional().describe("Glob pattern to filter files (e.g., '*.vue')"),
20919
21990
  caseSensitive: exports_external.boolean().default(true).describe("Whether the search is case sensitive"),
20920
21991
  maxResults: exports_external.number().int().positive().default(50).describe("Maximum number of results")
20921
21992
  }, async ({ pattern, path: searchPath, glob, caseSensitive, maxResults }) => {
20922
21993
  try {
21994
+ let absSearchPath;
21995
+ if (searchPath) {
21996
+ const { absPath, error: error2 } = validateFileWorkspace(searchPath) ? { absPath: null, error: validateFileWorkspace(searchPath) } : { absPath: path3.resolve(searchPath), error: null };
21997
+ const result2 = resolveFilePath(searchPath);
21998
+ if (result2.error || !result2.absPath) {
21999
+ return { content: [{ type: "text", text: result2.error || "Invalid path" }] };
22000
+ }
22001
+ absSearchPath = result2.absPath;
22002
+ } else {
22003
+ const { absPath } = resolveFilePath(".");
22004
+ if (absPath)
22005
+ absSearchPath = absPath;
22006
+ }
20923
22007
  const { execSync } = await import("child_process");
20924
22008
  const args = ["rg", "--json", "-n"];
20925
22009
  if (!caseSensitive)
@@ -20931,8 +22015,8 @@ server.tool("search", "Search for a pattern in Vue files using ripgrep", {
20931
22015
  }
20932
22016
  args.push("--max-count", maxResults.toString());
20933
22017
  args.push(pattern);
20934
- if (searchPath)
20935
- args.push(searchPath);
22018
+ if (absSearchPath)
22019
+ args.push(absSearchPath);
20936
22020
  const result = execSync(args.join(" "), {
20937
22021
  encoding: "utf-8",
20938
22022
  maxBuffer: 10485760,
@@ -20973,10 +22057,14 @@ server.tool("search", "Search for a pattern in Vue files using ripgrep", {
20973
22057
  }
20974
22058
  });
20975
22059
  server.tool("status", "Check Vue Language Server status for a project", {
20976
- file: exports_external.string().describe("A .vue file path to check the project status for")
22060
+ file: exports_external.string().describe("A .vue file path to check the project status for (absolute or relative to active workspace)")
20977
22061
  }, async ({ file }) => {
20978
22062
  try {
20979
- const status = await getProjectStatus(file);
22063
+ const { absPath, error: error2 } = resolveFilePath(file);
22064
+ if (error2 || !absPath) {
22065
+ return { content: [{ type: "text", text: error2 || "Invalid path" }] };
22066
+ }
22067
+ const status = await getProjectStatus(absPath);
20980
22068
  return {
20981
22069
  content: [{
20982
22070
  type: "text",