@theupsider/lsp-mcp 0.1.1 → 0.1.4
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/README.md +3 -2
- package/dist/__tests__/index.test.js +5 -7
- package/dist/detection/language-detector.js +7 -8
- package/dist/detection/language-registry.js +38 -0
- package/dist/index.js +1 -16
- package/dist/lsp/__tests__/lifecycle-manager.test.js +10 -8
- package/dist/lsp/diagnostic-store.js +49 -0
- package/dist/lsp/lifecycle-manager.js +42 -194
- package/dist/lsp/lsp-client.js +131 -44
- package/dist/lsp/server-supervisor.js +152 -0
- package/dist/mcp/__tests__/formatters.test.js +1 -1
- package/dist/mcp/__tests__/read-tools.test.js +11 -9
- package/dist/mcp/__tests__/server.test.js +4 -1
- package/dist/mcp/__tests__/write-tools.test.js +10 -6
- package/dist/mcp/formatters.js +1 -1
- package/dist/mcp/server.js +4 -3
- package/dist/mcp/tools/read-tools.js +14 -3
- package/dist/mcp/tools/shared.js +0 -44
- package/dist/mcp/tools/write-tools.js +5 -5
- package/package.json +6 -2
|
@@ -54,6 +54,7 @@ function registerReadTools(registrar, lifecycleManager, options) {
|
|
|
54
54
|
});
|
|
55
55
|
registrar.registerTool('lsp_workspace_symbols', { description: 'Search workspace symbols', inputSchema: zod_1.z.object({ query: zod_1.z.string().default('') }) }, async (args) => {
|
|
56
56
|
const query = typeof args.query === 'string' ? args.query : '';
|
|
57
|
+
await lifecycleManager.ensureSeedFilesOpen();
|
|
57
58
|
const results = await Promise.all(lifecycleManager.getReadyClients().map(async (client) => {
|
|
58
59
|
return await client.request('workspace/symbol', { query }, 30000);
|
|
59
60
|
}));
|
|
@@ -70,14 +71,24 @@ function registerReadTools(registrar, lifecycleManager, options) {
|
|
|
70
71
|
raw: (result) => asCompletionItems(result)?.slice(0, 50) ?? null
|
|
71
72
|
});
|
|
72
73
|
});
|
|
73
|
-
registrar.registerTool('lsp_diagnostics', { description: '
|
|
74
|
+
registrar.registerTool('lsp_diagnostics', { description: 'Get errors and warnings. Pass a file path to trigger analysis of that file and its language server, then return diagnostics for that file (scope: file) or all files seen so far (scope: workspace). Omit file for workspace scope to query whatever has been opened previously.', inputSchema: zod_1.z.object({ file: zod_1.z.string().optional(), scope: zod_1.z.enum(['file', 'workspace']).default('file'), language: zod_1.z.string().optional() }) }, async (args) => {
|
|
75
|
+
const filePath = typeof args.file === 'string' ? args.file : '';
|
|
74
76
|
const scope = args.scope === 'workspace' ? 'workspace' : 'file';
|
|
77
|
+
if (filePath) {
|
|
78
|
+
await lifecycleManager.ensureLanguageForFile(filePath);
|
|
79
|
+
const client = lifecycleManager.getClientForFile(filePath);
|
|
80
|
+
if (client) {
|
|
81
|
+
const waitPromise = client.waitForDiagnosticsPublish(filePath, 10000);
|
|
82
|
+
await client.ensureDidOpen(filePath);
|
|
83
|
+
client.notify('textDocument/didSave', { textDocument: { uri: (0, uri_1.pathToUri)(filePath) } });
|
|
84
|
+
await waitPromise;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
75
87
|
if (scope === 'workspace') {
|
|
76
88
|
const language = typeof args.language === 'string' ? args.language : undefined;
|
|
77
89
|
const diagnostics = lifecycleManager.getWorkspaceDiagnostics(language).slice(0, 200);
|
|
78
90
|
return (0, shared_1.success)((0, formatters_1.formatDiagnostics)(diagnostics, 'workspace'), diagnostics);
|
|
79
91
|
}
|
|
80
|
-
const filePath = typeof args.file === 'string' ? args.file : '';
|
|
81
92
|
const diagnostics = lifecycleManager.getFileDiagnostics(filePath);
|
|
82
93
|
return (0, shared_1.success)((0, formatters_1.formatDiagnostics)(diagnostics, 'file'), diagnostics);
|
|
83
94
|
});
|
|
@@ -168,7 +179,7 @@ async function runFileRequest(options) {
|
|
|
168
179
|
};
|
|
169
180
|
const uri = (0, uri_1.pathToUri)(filePath);
|
|
170
181
|
try {
|
|
171
|
-
await
|
|
182
|
+
await client.ensureDidOpen(filePath);
|
|
172
183
|
const result = await client.request(options.method, options.params?.(uri, position) ?? {
|
|
173
184
|
textDocument: { uri },
|
|
174
185
|
position
|
package/dist/mcp/tools/shared.js
CHANGED
|
@@ -5,41 +5,19 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.success = success;
|
|
7
7
|
exports.failure = failure;
|
|
8
|
-
exports.ensureDidOpen = ensureDidOpen;
|
|
9
|
-
exports.clearOpenedFiles = clearOpenedFiles;
|
|
10
8
|
exports.noServerResult = noServerResult;
|
|
11
9
|
exports.noProjectRootResult = noProjectRootResult;
|
|
12
10
|
exports.mapToolError = mapToolError;
|
|
13
11
|
exports.normalizeLocations = normalizeLocations;
|
|
14
12
|
exports.normalizeSymbols = normalizeSymbols;
|
|
15
|
-
const promises_1 = require("node:fs/promises");
|
|
16
13
|
const node_path_1 = __importDefault(require("node:path"));
|
|
17
14
|
const uri_1 = require("../../utils/uri");
|
|
18
|
-
const openedFiles = new Set();
|
|
19
15
|
function success(text, raw) {
|
|
20
16
|
return { content: [{ type: 'text', text }], raw };
|
|
21
17
|
}
|
|
22
18
|
function failure(text, raw = null) {
|
|
23
19
|
return { content: [{ type: 'text', text }], error: true, raw };
|
|
24
20
|
}
|
|
25
|
-
async function ensureDidOpen(client, filePath) {
|
|
26
|
-
if (openedFiles.has(filePath)) {
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
const text = await (0, promises_1.readFile)(filePath, 'utf8');
|
|
30
|
-
client.notify('textDocument/didOpen', {
|
|
31
|
-
textDocument: {
|
|
32
|
-
uri: (0, uri_1.pathToUri)(filePath),
|
|
33
|
-
languageId: languageIdForFile(filePath),
|
|
34
|
-
version: 1,
|
|
35
|
-
text
|
|
36
|
-
}
|
|
37
|
-
});
|
|
38
|
-
openedFiles.add(filePath);
|
|
39
|
-
}
|
|
40
|
-
function clearOpenedFiles() {
|
|
41
|
-
openedFiles.clear();
|
|
42
|
-
}
|
|
43
21
|
function noServerResult(filePath) {
|
|
44
22
|
return failure(`No language server available for ${node_path_1.default.extname(filePath) || 'unknown'} files. Run lsp_health for details.`);
|
|
45
23
|
}
|
|
@@ -82,25 +60,3 @@ function normalizeSymbols(symbols) {
|
|
|
82
60
|
}
|
|
83
61
|
return normalized;
|
|
84
62
|
}
|
|
85
|
-
function languageIdForFile(filePath) {
|
|
86
|
-
const ext = node_path_1.default.extname(filePath).toLowerCase();
|
|
87
|
-
const languageMap = {
|
|
88
|
-
'.ts': 'typescript',
|
|
89
|
-
'.tsx': 'typescriptreact',
|
|
90
|
-
'.js': 'javascript',
|
|
91
|
-
'.jsx': 'javascriptreact',
|
|
92
|
-
'.py': 'python',
|
|
93
|
-
'.go': 'go',
|
|
94
|
-
'.rs': 'rust',
|
|
95
|
-
'.java': 'java',
|
|
96
|
-
'.cs': 'csharp',
|
|
97
|
-
'.php': 'php',
|
|
98
|
-
'.rb': 'ruby',
|
|
99
|
-
'.kt': 'kotlin',
|
|
100
|
-
'.swift': 'swift',
|
|
101
|
-
'.c': 'c',
|
|
102
|
-
'.cpp': 'cpp',
|
|
103
|
-
'.h': 'c'
|
|
104
|
-
};
|
|
105
|
-
return languageMap[ext] ?? 'plaintext';
|
|
106
|
-
}
|
|
@@ -21,7 +21,7 @@ function registerWriteTools(registrar, lifecycleManager) {
|
|
|
21
21
|
return (0, shared_1.failure)('Rename is not supported by the active language server.');
|
|
22
22
|
}
|
|
23
23
|
try {
|
|
24
|
-
await
|
|
24
|
+
await client.ensureDidOpen(filePath);
|
|
25
25
|
const edit = await client.request('textDocument/rename', {
|
|
26
26
|
textDocument: { uri: (0, uri_1.pathToUri)(filePath) },
|
|
27
27
|
position: getPosition(args),
|
|
@@ -41,7 +41,7 @@ function registerWriteTools(registrar, lifecycleManager) {
|
|
|
41
41
|
return (0, shared_1.noServerResult)(filePath);
|
|
42
42
|
}
|
|
43
43
|
try {
|
|
44
|
-
await
|
|
44
|
+
await client.ensureDidOpen(filePath);
|
|
45
45
|
const range = isRange(args.range)
|
|
46
46
|
? args.range
|
|
47
47
|
: { start: getPosition(args), end: getPosition(args) };
|
|
@@ -108,7 +108,7 @@ async function runFormattingRequest(_method, args, lifecycleManager, requestEdit
|
|
|
108
108
|
return (0, shared_1.noServerResult)(filePath);
|
|
109
109
|
}
|
|
110
110
|
try {
|
|
111
|
-
await
|
|
111
|
+
await client.ensureDidOpen(filePath);
|
|
112
112
|
const options = await resolveFormattingOptions(filePath, args.options);
|
|
113
113
|
const edits = await requestEdits(client, filePath, options);
|
|
114
114
|
return await applyTextEdits(edits, [filePath], lifecycleManager, client);
|
|
@@ -126,7 +126,7 @@ async function applyWorkspaceEdit(edit, lifecycleManager, fallbackClient, verb)
|
|
|
126
126
|
for (const [uri, edits] of changeEntries) {
|
|
127
127
|
const filePath = (0, uri_1.uriToPath)(uri);
|
|
128
128
|
const client = lifecycleManager.getClientForFile(filePath) ?? fallbackClient;
|
|
129
|
-
await
|
|
129
|
+
await client.ensureDidOpen(filePath);
|
|
130
130
|
await applyEditsToFile(filePath, edits ?? []);
|
|
131
131
|
client.notify('textDocument/didSave', { textDocument: { uri } });
|
|
132
132
|
changedFiles.push(filePath);
|
|
@@ -135,7 +135,7 @@ async function applyWorkspaceEdit(edit, lifecycleManager, fallbackClient, verb)
|
|
|
135
135
|
if ('textDocument' in change && 'edits' in change) {
|
|
136
136
|
const filePath = (0, uri_1.uriToPath)(change.textDocument.uri);
|
|
137
137
|
const client = lifecycleManager.getClientForFile(filePath) ?? fallbackClient;
|
|
138
|
-
await
|
|
138
|
+
await client.ensureDidOpen(filePath);
|
|
139
139
|
await applyEditsToFile(filePath, change.edits);
|
|
140
140
|
client.notify('textDocument/didSave', { textDocument: { uri: change.textDocument.uri } });
|
|
141
141
|
changedFiles.push(filePath);
|
package/package.json
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@theupsider/lsp-mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Universal LSP MCP server",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "commonjs",
|
|
7
|
+
"author": {
|
|
8
|
+
"name": "David Fischer",
|
|
9
|
+
"url": "https://davidfischer.dev"
|
|
10
|
+
},
|
|
7
11
|
"main": "dist/index.js",
|
|
8
12
|
"bin": {
|
|
9
13
|
"lsp-mcp": "dist/index.js"
|
|
@@ -29,4 +33,4 @@
|
|
|
29
33
|
"ts-jest": "latest",
|
|
30
34
|
"typescript": "latest"
|
|
31
35
|
}
|
|
32
|
-
}
|
|
36
|
+
}
|