@theupsider/lsp-mcp 1.0.1 → 1.0.8

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.
@@ -46,7 +46,7 @@ describe('McpServer', () => {
46
46
  const writeTools = jest.requireMock('../tools/write-tools').registerWriteTools;
47
47
  readTools.mockImplementationOnce((registrar) => {
48
48
  registrar.registerTool('lsp_init', { description: 'init' }, async () => ({ content: [{ type: 'text', text: 'ok' }], raw: null }));
49
- registrar.registerTool('lsp_hover', { description: 'hover' }, async () => ({ content: [{ type: 'text', text: 'hover' }], raw: null }));
49
+ registrar.registerTool('lsp_definition', { description: 'definition' }, async () => ({ content: [{ type: 'text', text: 'definition' }], raw: null }));
50
50
  });
51
51
  writeTools.mockImplementationOnce(() => undefined);
52
52
  const { ListToolsRequestSchema } = jest.requireActual('@modelcontextprotocol/sdk/types.js');
@@ -55,14 +55,14 @@ describe('McpServer', () => {
55
55
  const listHandler = getHandler(ListToolsRequestSchema);
56
56
  const result = await listHandler({});
57
57
  expect(result.tools.map((t) => t.name)).toContain('lsp_init');
58
- expect(result.tools.map((t) => t.name)).toContain('lsp_hover');
58
+ expect(result.tools.map((t) => t.name)).toContain('lsp_definition');
59
59
  });
60
60
  it('lists all tools after initialization too', async () => {
61
61
  const readTools = jest.requireMock('../tools/read-tools').registerReadTools;
62
62
  const writeTools = jest.requireMock('../tools/write-tools').registerWriteTools;
63
63
  readTools.mockImplementationOnce((registrar) => {
64
64
  registrar.registerTool('lsp_init', { description: 'init' }, async () => ({ content: [{ type: 'text', text: 'ok' }], raw: null }));
65
- registrar.registerTool('lsp_hover', { description: 'hover' }, async () => ({ content: [{ type: 'text', text: 'hover' }], raw: null }));
65
+ registrar.registerTool('lsp_definition', { description: 'definition' }, async () => ({ content: [{ type: 'text', text: 'definition' }], raw: null }));
66
66
  });
67
67
  writeTools.mockImplementationOnce(() => undefined);
68
68
  const { ListToolsRequestSchema } = jest.requireActual('@modelcontextprotocol/sdk/types.js');
@@ -72,20 +72,20 @@ describe('McpServer', () => {
72
72
  const listHandler = getHandler(ListToolsRequestSchema);
73
73
  const result = await listHandler({});
74
74
  expect(result.tools.map((t) => t.name)).toContain('lsp_init');
75
- expect(result.tools.map((t) => t.name)).toContain('lsp_hover');
75
+ expect(result.tools.map((t) => t.name)).toContain('lsp_definition');
76
76
  });
77
77
  it('returns no-root error for non-init tools before lsp_init', async () => {
78
78
  const readTools = jest.requireMock('../tools/read-tools').registerReadTools;
79
79
  const writeTools = jest.requireMock('../tools/write-tools').registerWriteTools;
80
80
  readTools.mockImplementationOnce((registrar) => {
81
- registrar.registerTool('lsp_hover', { description: 'hover' }, async () => ({ content: [{ type: 'text', text: 'reachable' }], raw: null }));
81
+ registrar.registerTool('lsp_definition', { description: 'definition' }, async () => ({ content: [{ type: 'text', text: 'reachable' }], raw: null }));
82
82
  });
83
83
  writeTools.mockImplementationOnce(() => undefined);
84
84
  const { CallToolRequestSchema } = jest.requireActual('@modelcontextprotocol/sdk/types.js');
85
85
  const server = new server_1.McpServer('info');
86
86
  await server.start();
87
87
  const callHandler = getHandler(CallToolRequestSchema);
88
- const result = await callHandler({ params: { name: 'lsp_hover', arguments: {} } });
88
+ const result = await callHandler({ params: { name: 'lsp_definition', arguments: {} } });
89
89
  expect(result.content[0].text).toMatch(/No project root set/);
90
90
  });
91
91
  it('re-initializes with new root when lsp_init called again', async () => {
@@ -2,10 +2,10 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const promises_1 = require("node:fs/promises");
4
4
  const write_tools_1 = require("../tools/write-tools");
5
- jest.mock('node:fs/promises', () => ({
5
+ jest.mock("node:fs/promises", () => ({
6
6
  access: jest.fn(),
7
7
  readFile: jest.fn(),
8
- writeFile: jest.fn()
8
+ writeFile: jest.fn(),
9
9
  }));
10
10
  class FakeRegistrar {
11
11
  tools = new Map();
@@ -13,166 +13,203 @@ class FakeRegistrar {
13
13
  this.tools.set(name, { handler });
14
14
  }
15
15
  }
16
- describe('registerWriteTools', () => {
16
+ describe("registerWriteTools", () => {
17
17
  beforeEach(() => {
18
18
  jest.clearAllMocks();
19
19
  promises_1.access.mockResolvedValue(undefined);
20
20
  promises_1.readFile.mockImplementation(async (filePath) => {
21
21
  const pathText = String(filePath);
22
- if (pathText.endsWith('.editorconfig')) {
23
- return 'root = true\n[*]\nindent_size = 2\nindent_style = space\n';
22
+ if (pathText.endsWith(".editorconfig")) {
23
+ return "root = true\n[*]\nindent_size = 2\nindent_style = space\n";
24
24
  }
25
- return 'const foo = oldName\n';
25
+ return "const foo = oldName\n";
26
26
  });
27
27
  promises_1.writeFile.mockResolvedValue(undefined);
28
28
  });
29
- it('renames symbols when the server supports rename and saves the changed file', async () => {
29
+ it("renames symbols when the server supports rename and saves the changed file", async () => {
30
30
  const registrar = new FakeRegistrar();
31
31
  const client = createClient({
32
32
  changes: {
33
- 'file:///workspace/src/index.ts': [{ range: { start: { line: 0, character: 12 }, end: { line: 0, character: 19 } }, newText: 'newName' }]
34
- }
33
+ "file:///workspace/src/index.ts": [
34
+ {
35
+ range: {
36
+ start: { line: 0, character: 12 },
37
+ end: { line: 0, character: 19 },
38
+ },
39
+ newText: "newName",
40
+ },
41
+ ],
42
+ },
35
43
  });
36
44
  (0, write_tools_1.registerWriteTools)(registrar, createLifecycle(client));
37
- const result = await getHandler(registrar, 'lsp_rename')({ file: '/workspace/src/index.ts', line: 0, character: 12, newName: 'newName' });
38
- expect(client.request).toHaveBeenCalledWith('textDocument/rename', {
39
- textDocument: { uri: 'file:///workspace/src/index.ts' },
45
+ const result = await getHandler(registrar, "lsp_rename")({
46
+ file: "/workspace/src/index.ts",
47
+ line: 0,
48
+ character: 12,
49
+ newName: "newName",
50
+ });
51
+ expect(client.request).toHaveBeenCalledWith("textDocument/rename", {
52
+ textDocument: { uri: "file:///workspace/src/index.ts" },
40
53
  position: { line: 0, character: 12 },
41
- newName: 'newName'
54
+ newName: "newName",
42
55
  }, 15000);
43
- expect(promises_1.writeFile).toHaveBeenCalledWith('/workspace/src/index.ts', 'const foo = newName\n', 'utf8');
44
- expect(client.notify).toHaveBeenCalledWith('textDocument/didSave', { textDocument: { uri: 'file:///workspace/src/index.ts' } });
56
+ expect(promises_1.writeFile).toHaveBeenCalledWith("/workspace/src/index.ts", "const foo = newName\n", "utf8");
57
+ expect(client.notify).toHaveBeenCalledWith("textDocument/didSave", {
58
+ textDocument: { uri: "file:///workspace/src/index.ts" },
59
+ });
45
60
  expect(result).toEqual({
46
- content: [{ type: 'text', text: 'Applied workspace edit to 1 file(s)' }],
47
- raw: { changedFiles: ['/workspace/src/index.ts'] }
61
+ content: [{ type: "text", text: "Applied workspace edit to 1 file(s)" }],
62
+ raw: { changedFiles: ["/workspace/src/index.ts"] },
48
63
  });
49
64
  });
50
- it('returns an error when rename is unsupported', async () => {
65
+ it("returns an error when rename is unsupported", async () => {
51
66
  const registrar = new FakeRegistrar();
52
67
  const client = createClient(null, { renameProvider: false });
53
68
  (0, write_tools_1.registerWriteTools)(registrar, createLifecycle(client));
54
- await expect(getHandler(registrar, 'lsp_rename')({ file: '/workspace/src/index.ts', line: 0, character: 0, newName: 'x' })).resolves.toEqual({
55
- content: [{ type: 'text', text: 'Rename is not supported by the active language server.' }],
69
+ await expect(getHandler(registrar, "lsp_rename")({
70
+ file: "/workspace/src/index.ts",
71
+ line: 0,
72
+ character: 0,
73
+ newName: "x",
74
+ })).resolves.toEqual({
75
+ content: [
76
+ {
77
+ type: "text",
78
+ text: "Rename is not supported by the active language server.",
79
+ },
80
+ ],
56
81
  error: true,
57
- raw: null
82
+ raw: null,
58
83
  });
59
84
  });
60
- it('lists code actions without applying them', async () => {
85
+ it("lists code actions without applying them", async () => {
61
86
  const registrar = new FakeRegistrar();
62
- const actions = [{ title: 'Fix import', kind: 'quickfix' }];
87
+ const actions = [{ title: "Fix import", kind: "quickfix" }];
63
88
  const client = createClient(actions);
64
89
  (0, write_tools_1.registerWriteTools)(registrar, createLifecycle(client));
65
- await expect(getHandler(registrar, 'lsp_code_action')({ file: '/workspace/src/index.ts', line: 0, character: 0 })).resolves.toEqual({
66
- content: [{ type: 'text', text: 'Available code actions:\n- [0] Fix import' }],
67
- raw: actions
90
+ await expect(getHandler(registrar, "lsp_code_action")({ file: "/workspace/src/index.ts", line: 0, character: 0 })).resolves.toEqual({
91
+ content: [
92
+ { type: "text", text: "Available code actions:\n- [0] Fix import" },
93
+ ],
94
+ raw: actions,
68
95
  });
69
96
  expect(promises_1.writeFile).not.toHaveBeenCalled();
70
97
  });
71
- it('applies the selected code action edit and command', async () => {
98
+ it("applies the selected code action edit and command", async () => {
72
99
  const registrar = new FakeRegistrar();
73
100
  const client = createClient([
74
101
  {
75
- title: 'Fix import',
102
+ title: "Fix import",
76
103
  edit: {
77
104
  changes: {
78
- 'file:///workspace/src/index.ts': [{ range: { start: { line: 0, character: 0 }, end: { line: 0, character: 5 } }, newText: 'let' }]
79
- }
105
+ "file:///workspace/src/index.ts": [
106
+ {
107
+ range: {
108
+ start: { line: 0, character: 0 },
109
+ end: { line: 0, character: 5 },
110
+ },
111
+ newText: "let",
112
+ },
113
+ ],
114
+ },
80
115
  },
81
- command: { command: 'workspace.applyFix', arguments: ['x'] }
82
- }
116
+ command: { command: "workspace.applyFix", arguments: ["x"] },
117
+ },
83
118
  ]);
84
119
  (0, write_tools_1.registerWriteTools)(registrar, createLifecycle(client));
85
- const result = await getHandler(registrar, 'lsp_code_action')({ file: '/workspace/src/index.ts', line: 0, character: 0, apply: true });
86
- expect(client.request).toHaveBeenCalledWith('workspace/executeCommand', { command: 'workspace.applyFix', arguments: ['x'] }, 15000);
120
+ const result = await getHandler(registrar, "lsp_code_action")({ file: "/workspace/src/index.ts", line: 0, character: 0, apply: true });
121
+ expect(client.request).toHaveBeenCalledWith("workspace/executeCommand", { command: "workspace.applyFix", arguments: ["x"] }, 15000);
87
122
  expect(result).toEqual({
88
- content: [{ type: 'text', text: 'Applied code action: Fix import' }],
89
- raw: { title: 'Fix import', changedFiles: ['/workspace/src/index.ts'] }
123
+ content: [{ type: "text", text: "Applied code action: Fix import" }],
124
+ raw: { title: "Fix import", changedFiles: ["/workspace/src/index.ts"] },
90
125
  });
91
126
  });
92
- it('formats a file using editorconfig defaults', async () => {
127
+ it("formats a file using editorconfig defaults", async () => {
93
128
  const registrar = new FakeRegistrar();
94
- const client = createClient([{ range: { start: { line: 0, character: 0 }, end: { line: 0, character: 21 } }, newText: 'const foo = oldName;\n' }]);
129
+ const client = createClient([
130
+ {
131
+ range: {
132
+ start: { line: 0, character: 0 },
133
+ end: { line: 0, character: 21 },
134
+ },
135
+ newText: "const foo = oldName;\n",
136
+ },
137
+ ]);
95
138
  (0, write_tools_1.registerWriteTools)(registrar, createLifecycle(client));
96
- await getHandler(registrar, 'lsp_formatting')({ file: '/workspace/src/index.ts' });
97
- expect(client.request).toHaveBeenCalledWith('textDocument/formatting', {
98
- textDocument: { uri: 'file:///workspace/src/index.ts' },
99
- options: { tabSize: 2, insertSpaces: true }
139
+ await getHandler(registrar, "lsp_formatting")({ file: "/workspace/src/index.ts" });
140
+ expect(client.request).toHaveBeenCalledWith("textDocument/formatting", {
141
+ textDocument: { uri: "file:///workspace/src/index.ts" },
142
+ options: { tabSize: 2, insertSpaces: true },
100
143
  }, 15000);
101
144
  });
102
- it('formats a range with explicit options', async () => {
145
+ it("formats a range with explicit options", async () => {
103
146
  const registrar = new FakeRegistrar();
104
- const client = createClient([{ range: { start: { line: 0, character: 0 }, end: { line: 0, character: 21 } }, newText: 'const foo = oldName;\n' }]);
147
+ const client = createClient([
148
+ {
149
+ range: {
150
+ start: { line: 0, character: 0 },
151
+ end: { line: 0, character: 21 },
152
+ },
153
+ newText: "const foo = oldName;\n",
154
+ },
155
+ ]);
105
156
  (0, write_tools_1.registerWriteTools)(registrar, createLifecycle(client));
106
- await getHandler(registrar, 'lsp_range_formatting')({
107
- file: '/workspace/src/index.ts',
108
- range: { start: { line: 0, character: 0 }, end: { line: 0, character: 21 } },
109
- options: { tabSize: 4, insertSpaces: false }
157
+ await getHandler(registrar, "lsp_range_formatting")({
158
+ file: "/workspace/src/index.ts",
159
+ range: {
160
+ start: { line: 0, character: 0 },
161
+ end: { line: 0, character: 21 },
162
+ },
163
+ options: { tabSize: 4, insertSpaces: false },
110
164
  });
111
- expect(client.request).toHaveBeenCalledWith('textDocument/rangeFormatting', {
112
- textDocument: { uri: 'file:///workspace/src/index.ts' },
113
- range: { start: { line: 0, character: 0 }, end: { line: 0, character: 21 } },
114
- options: { tabSize: 4, insertSpaces: false }
165
+ expect(client.request).toHaveBeenCalledWith("textDocument/rangeFormatting", {
166
+ textDocument: { uri: "file:///workspace/src/index.ts" },
167
+ range: {
168
+ start: { line: 0, character: 0 },
169
+ end: { line: 0, character: 21 },
170
+ },
171
+ options: { tabSize: 4, insertSpaces: false },
115
172
  }, 15000);
116
173
  });
117
- it('applies a raw workspace edit across files', async () => {
118
- const registrar = new FakeRegistrar();
119
- const client = createClient(null);
120
- (0, write_tools_1.registerWriteTools)(registrar, createLifecycle(client));
121
- const edit = {
122
- changes: {
123
- 'file:///workspace/src/index.ts': [{ range: { start: { line: 0, character: 6 }, end: { line: 0, character: 9 } }, newText: 'bar' }]
124
- }
125
- };
126
- await expect(getHandler(registrar, 'lsp_apply_workspace_edit')({ edit })).resolves.toEqual({
127
- content: [{ type: 'text', text: 'Applied workspace edit to 1 file(s)' }],
128
- raw: { changedFiles: ['/workspace/src/index.ts'] }
129
- });
130
- });
131
- it('supports indexed code actions, empty edits, and document changes', async () => {
174
+ it("supports indexed code actions and document changes", async () => {
132
175
  const registrar = new FakeRegistrar();
133
176
  const client = createClient([
134
- { title: 'Skip me' },
177
+ { title: "Skip me" },
135
178
  {
136
- title: 'Apply me',
179
+ title: "Apply me",
137
180
  edit: {
138
181
  documentChanges: [
139
182
  {
140
- textDocument: { uri: 'file:///workspace/src/index.ts', version: 1 },
141
- edits: [{ range: { start: { line: 0, character: 6 }, end: { line: 0, character: 9 } }, newText: 'bar' }]
142
- }
143
- ]
144
- }
145
- }
183
+ textDocument: {
184
+ uri: "file:///workspace/src/index.ts",
185
+ version: 1,
186
+ },
187
+ edits: [
188
+ {
189
+ range: {
190
+ start: { line: 0, character: 6 },
191
+ end: { line: 0, character: 9 },
192
+ },
193
+ newText: "bar",
194
+ },
195
+ ],
196
+ },
197
+ ],
198
+ },
199
+ },
146
200
  ]);
147
201
  (0, write_tools_1.registerWriteTools)(registrar, createLifecycle(client));
148
- await expect(getHandler(registrar, 'lsp_code_action')({ file: '/workspace/src/index.ts', line: 0, character: 0, apply: { index: 1 } })).resolves.toEqual({
149
- content: [{ type: 'text', text: 'Applied code action: Apply me' }],
150
- raw: { title: 'Apply me', changedFiles: ['/workspace/src/index.ts'] }
151
- });
152
- client.request.mockResolvedValueOnce(null);
153
- await expect(getHandler(registrar, 'lsp_apply_workspace_edit')({ edit: null })).resolves.toEqual({
154
- content: [{ type: 'text', text: 'No result' }],
155
- raw: null
202
+ await expect(getHandler(registrar, "lsp_code_action")({
203
+ file: "/workspace/src/index.ts",
204
+ line: 0,
205
+ character: 0,
206
+ apply: { index: 1 },
207
+ })).resolves.toEqual({
208
+ content: [{ type: "text", text: "Applied code action: Apply me" }],
209
+ raw: { title: "Apply me", changedFiles: ["/workspace/src/index.ts"] },
156
210
  });
157
211
  });
158
- it('returns an error when no write-capable client is ready', async () => {
159
- const registrar = new FakeRegistrar();
160
- (0, write_tools_1.registerWriteTools)(registrar, {
161
- getClientForFile: jest.fn(() => null),
162
- getReadyClients: jest.fn(() => []),
163
- getFileDiagnostics: jest.fn((_) => []),
164
- getWorkspaceDiagnostics: jest.fn(() => []),
165
- getHealth: jest.fn(() => []),
166
- ensureLanguageForFile: jest.fn().mockResolvedValue(undefined),
167
- ensureSeedFilesOpen: jest.fn().mockResolvedValue(undefined)
168
- });
169
- await expect(getHandler(registrar, 'lsp_apply_workspace_edit')({ edit: {} })).resolves.toEqual({
170
- content: [{ type: 'text', text: 'No language servers are ready. Run lsp_health for details.' }],
171
- error: true,
172
- raw: null
173
- });
174
- });
175
- it('handles missing clients, formatter defaults, and empty code actions', async () => {
212
+ it("handles missing clients, formatter defaults, and empty code actions", async () => {
176
213
  const registrar = new FakeRegistrar();
177
214
  const noClientLifecycle = {
178
215
  getClientForFile: jest.fn(() => null),
@@ -181,45 +218,71 @@ describe('registerWriteTools', () => {
181
218
  getWorkspaceDiagnostics: jest.fn(() => []),
182
219
  getHealth: jest.fn(() => []),
183
220
  ensureLanguageForFile: jest.fn().mockResolvedValue(undefined),
184
- ensureSeedFilesOpen: jest.fn().mockResolvedValue(undefined)
221
+ ensureSeedFilesOpen: jest.fn().mockResolvedValue(undefined),
222
+ analyzeWorkspace: jest.fn().mockResolvedValue(undefined),
185
223
  };
186
224
  (0, write_tools_1.registerWriteTools)(registrar, noClientLifecycle);
187
- await expect(getHandler(registrar, 'lsp_rename')({ file: '/workspace/README.md', line: 0, character: 0, newName: 'x' })).resolves.toEqual({
188
- content: [{ type: 'text', text: 'No language server available for .md files. Run lsp_health for details.' }],
225
+ await expect(getHandler(registrar, "lsp_rename")({ file: "/workspace/README.md", line: 0, character: 0, newName: "x" })).resolves.toEqual({
226
+ content: [
227
+ {
228
+ type: "text",
229
+ text: "No language server available for .md files. Run lsp_health for details.",
230
+ },
231
+ ],
189
232
  error: true,
190
- raw: null
233
+ raw: null,
191
234
  });
192
- await expect(getHandler(registrar, 'lsp_code_action')({ file: '/workspace/README.md', line: 0, character: 0 })).resolves.toEqual({
193
- content: [{ type: 'text', text: 'No language server available for .md files. Run lsp_health for details.' }],
235
+ await expect(getHandler(registrar, "lsp_code_action")({ file: "/workspace/README.md", line: 0, character: 0 })).resolves.toEqual({
236
+ content: [
237
+ {
238
+ type: "text",
239
+ text: "No language server available for .md files. Run lsp_health for details.",
240
+ },
241
+ ],
194
242
  error: true,
195
- raw: null
243
+ raw: null,
196
244
  });
197
- await expect(getHandler(registrar, 'lsp_formatting')({ file: '/workspace/README.md' })).resolves.toEqual({
198
- content: [{ type: 'text', text: 'No language server available for .md files. Run lsp_health for details.' }],
245
+ await expect(getHandler(registrar, "lsp_formatting")({ file: "/workspace/README.md" })).resolves.toEqual({
246
+ content: [
247
+ {
248
+ type: "text",
249
+ text: "No language server available for .md files. Run lsp_health for details.",
250
+ },
251
+ ],
199
252
  error: true,
200
- raw: null
253
+ raw: null,
201
254
  });
202
255
  const secondRegistrar = new FakeRegistrar();
203
256
  const client = createClient([]);
204
- promises_1.access.mockRejectedValue(new Error('missing'));
257
+ promises_1.access.mockRejectedValue(new Error("missing"));
205
258
  (0, write_tools_1.registerWriteTools)(secondRegistrar, createLifecycle(client));
206
- await expect(getHandler(secondRegistrar, 'lsp_formatting')({ file: '/workspace/src/index.ts' })).resolves.toEqual({
207
- content: [{ type: 'text', text: 'Applied workspace edit to 0 file(s)' }],
208
- raw: { changedFiles: [] }
259
+ await expect(getHandler(secondRegistrar, "lsp_formatting")({ file: "/workspace/src/index.ts" })).resolves.toEqual({
260
+ content: [{ type: "text", text: "Applied workspace edit to 0 file(s)" }],
261
+ raw: { changedFiles: [] },
209
262
  });
210
- await expect(getHandler(secondRegistrar, 'lsp_code_action')({ file: '/workspace/src/index.ts', line: 0, character: 0 })).resolves.toEqual({
211
- content: [{ type: 'text', text: 'No result' }],
212
- raw: []
263
+ await expect(getHandler(secondRegistrar, "lsp_code_action")({ file: "/workspace/src/index.ts", line: 0, character: 0 })).resolves.toEqual({
264
+ content: [{ type: "text", text: "No result" }],
265
+ raw: [],
213
266
  });
214
267
  });
215
- it('maps write failures to timeout guidance', async () => {
268
+ it("maps write failures to timeout guidance", async () => {
216
269
  const registrar = new FakeRegistrar();
217
- const client = createClient(new Error('LSP request timed out: textDocument/rename'));
270
+ const client = createClient(new Error("LSP request timed out: textDocument/rename"));
218
271
  (0, write_tools_1.registerWriteTools)(registrar, createLifecycle(client));
219
- await expect(getHandler(registrar, 'lsp_rename')({ file: '/workspace/src/index.ts', line: 0, character: 0, newName: 'x' })).resolves.toEqual({
220
- content: [{ type: 'text', text: 'Operation timed out after 15s — try a more specific query or check the LSP server health' }],
272
+ await expect(getHandler(registrar, "lsp_rename")({
273
+ file: "/workspace/src/index.ts",
274
+ line: 0,
275
+ character: 0,
276
+ newName: "x",
277
+ })).resolves.toEqual({
278
+ content: [
279
+ {
280
+ type: "text",
281
+ text: "Operation timed out after 15s — try a more specific query or check the LSP server health",
282
+ },
283
+ ],
221
284
  error: true,
222
- raw: null
285
+ raw: null,
223
286
  });
224
287
  });
225
288
  });
@@ -238,13 +301,14 @@ function createLifecycle(client) {
238
301
  getWorkspaceDiagnostics: jest.fn(() => []),
239
302
  getHealth: jest.fn(() => []),
240
303
  ensureLanguageForFile: jest.fn().mockResolvedValue(undefined),
241
- ensureSeedFilesOpen: jest.fn().mockResolvedValue(undefined)
304
+ ensureSeedFilesOpen: jest.fn().mockResolvedValue(undefined),
305
+ analyzeWorkspace: jest.fn().mockResolvedValue(undefined),
242
306
  };
243
307
  }
244
308
  function createClient(result, capabilities = { renameProvider: true }) {
245
309
  return {
246
310
  request: jest.fn().mockImplementation(async (method) => {
247
- if (method === 'workspace/executeCommand') {
311
+ if (method === "workspace/executeCommand") {
248
312
  return null;
249
313
  }
250
314
  if (result instanceof Error) {
@@ -256,6 +320,6 @@ function createClient(result, capabilities = { renameProvider: true }) {
256
320
  getCapabilities: jest.fn(() => capabilities),
257
321
  ensureDidOpen: jest.fn().mockResolvedValue(undefined),
258
322
  waitForDiagnosticsPublish: jest.fn().mockResolvedValue(undefined),
259
- ensureSeedFileOpen: jest.fn().mockResolvedValue(undefined)
323
+ ensureSeedFileOpen: jest.fn().mockResolvedValue(undefined),
260
324
  };
261
325
  }