Skip to content

Commit b75170e

Browse files
Merge pull request #107 from Avtrkrb/fix/mcp-configuration-empty-fields-and-auth
Fix: MCP configuration empty fields and auth issues
2 parents 6d4f73c + 236628b commit b75170e

File tree

6 files changed

+182
-90
lines changed

6 files changed

+182
-90
lines changed

source/lsp/lsp-manager.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ export class LSPManager extends EventEmitter {
7272

7373
// Auto-discover additional servers if enabled (default: true)
7474
if (config.autoDiscover !== false) {
75-
const discovered = discoverLanguageServers();
75+
const discovered = await discoverLanguageServers();
7676

7777
// Only add discovered servers for languages not already covered
7878
const coveredLanguages = new Set<string>();

source/lsp/server-discovery.spec.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,3 +465,41 @@ test('getServerForLanguage - handles empty extension', t => {
465465
const result = getServerForLanguage(servers, '');
466466
t.is(result, undefined);
467467
});
468+
469+
// Tests for verificationMethod functionality
470+
test('getKnownServersStatus - all servers have proper structure', t => {
471+
const result = getKnownServersStatus();
472+
473+
// Check that servers have proper structure
474+
for (const server of result) {
475+
t.truthy(server.name);
476+
t.true(typeof server.available === 'boolean');
477+
t.true(Array.isArray(server.languages));
478+
}
479+
});
480+
481+
test('getKnownServersStatus - key servers are present with correct names', t => {
482+
const result = getKnownServersStatus();
483+
484+
// Check that key servers are present with their correct names
485+
const keyServers = [
486+
'typescript-language-server',
487+
'pyright',
488+
'pylsp',
489+
'rust-analyzer',
490+
'gopls',
491+
'clangd',
492+
'vscode-json-languageserver',
493+
'vscode-html-languageserver',
494+
'vscode-css-languageserver',
495+
'yaml-language-server',
496+
'bash-language-server',
497+
'lua-language-server',
498+
];
499+
500+
for (const serverName of keyServers) {
501+
const server = result.find(s => s.name === serverName);
502+
t.truthy(server, `Server ${serverName} should be present`);
503+
t.is(server!.name, serverName);
504+
}
505+
});

source/lsp/server-discovery.ts

Lines changed: 86 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* Detects installed language servers on the system
44
*/
55

6-
import {execSync} from 'child_process';
6+
import {execSync, spawn} from 'child_process';
77
import {existsSync} from 'fs';
88
import {join} from 'path';
99
import type {LSPServerConfig} from './lsp-client';
@@ -14,6 +14,7 @@ interface LanguageServerDefinition {
1414
args: string[];
1515
languages: string[];
1616
checkCommand?: string; // Command to verify installation
17+
verificationMethod?: 'version' | 'lsp' | 'none'; // New verification method
1718
installHint?: string;
1819
}
1920

@@ -28,6 +29,7 @@ const KNOWN_SERVERS: LanguageServerDefinition[] = [
2829
args: ['--stdio'],
2930
languages: ['ts', 'tsx', 'js', 'jsx', 'mjs', 'cjs'],
3031
checkCommand: 'typescript-language-server --version',
32+
verificationMethod: 'version',
3133
installHint: 'npm install -g typescript-language-server typescript',
3234
},
3335
// Python - Pyright (preferred)
@@ -37,6 +39,7 @@ const KNOWN_SERVERS: LanguageServerDefinition[] = [
3739
args: ['--stdio'],
3840
languages: ['py', 'pyi'],
3941
checkCommand: 'pyright-langserver --version',
42+
verificationMethod: 'lsp',
4043
installHint: 'npm install -g pyright',
4144
},
4245
// Python - pylsp (alternative)
@@ -46,6 +49,7 @@ const KNOWN_SERVERS: LanguageServerDefinition[] = [
4649
args: [],
4750
languages: ['py', 'pyi'],
4851
checkCommand: 'pylsp --version',
52+
verificationMethod: 'version',
4953
installHint: 'pip install python-lsp-server',
5054
},
5155
// Rust
@@ -55,6 +59,7 @@ const KNOWN_SERVERS: LanguageServerDefinition[] = [
5559
args: [],
5660
languages: ['rs'],
5761
checkCommand: 'rust-analyzer --version',
62+
verificationMethod: 'version',
5863
installHint: 'rustup component add rust-analyzer',
5964
},
6065
// Go
@@ -64,6 +69,7 @@ const KNOWN_SERVERS: LanguageServerDefinition[] = [
6469
args: ['serve'],
6570
languages: ['go'],
6671
checkCommand: 'gopls version',
72+
verificationMethod: 'version',
6773
installHint: 'go install golang.org/x/tools/gopls@latest',
6874
},
6975
// C/C++
@@ -73,6 +79,7 @@ const KNOWN_SERVERS: LanguageServerDefinition[] = [
7379
args: ['--background-index'],
7480
languages: ['c', 'cpp', 'cc', 'cxx', 'h', 'hpp', 'hxx'],
7581
checkCommand: 'clangd --version',
82+
verificationMethod: 'version',
7683
installHint: 'Install via system package manager (apt, brew, etc.)',
7784
},
7885
// JSON
@@ -82,6 +89,7 @@ const KNOWN_SERVERS: LanguageServerDefinition[] = [
8289
args: ['--stdio'],
8390
languages: ['json', 'jsonc'],
8491
checkCommand: 'vscode-json-language-server --version',
92+
verificationMethod: 'lsp',
8593
installHint: 'npm install -g vscode-langservers-extracted',
8694
},
8795
// HTML
@@ -91,6 +99,7 @@ const KNOWN_SERVERS: LanguageServerDefinition[] = [
9199
args: ['--stdio'],
92100
languages: ['html', 'htm'],
93101
checkCommand: 'vscode-html-language-server --version',
102+
verificationMethod: 'lsp',
94103
installHint: 'npm install -g vscode-langservers-extracted',
95104
},
96105
// CSS
@@ -100,6 +109,7 @@ const KNOWN_SERVERS: LanguageServerDefinition[] = [
100109
args: ['--stdio'],
101110
languages: ['css', 'scss', 'less'],
102111
checkCommand: 'vscode-css-language-server --version',
112+
verificationMethod: 'lsp',
103113
installHint: 'npm install -g vscode-langservers-extracted',
104114
},
105115
// YAML
@@ -109,6 +119,7 @@ const KNOWN_SERVERS: LanguageServerDefinition[] = [
109119
args: ['--stdio'],
110120
languages: ['yaml', 'yml'],
111121
checkCommand: 'yaml-language-server --version',
122+
verificationMethod: 'lsp',
112123
installHint: 'npm install -g yaml-language-server',
113124
},
114125
// Bash/Shell
@@ -118,6 +129,7 @@ const KNOWN_SERVERS: LanguageServerDefinition[] = [
118129
args: ['start'],
119130
languages: ['sh', 'bash', 'zsh'],
120131
checkCommand: 'bash-language-server --version',
132+
verificationMethod: 'version',
121133
installHint: 'npm install -g bash-language-server',
122134
},
123135
// Lua
@@ -127,6 +139,7 @@ const KNOWN_SERVERS: LanguageServerDefinition[] = [
127139
args: [],
128140
languages: ['lua'],
129141
checkCommand: 'lua-language-server --version',
142+
verificationMethod: 'version',
130143
installHint: 'Install from https://github.com/LuaLS/lua-language-server',
131144
},
132145
];
@@ -165,10 +178,52 @@ function verifyServer(checkCommand: string): boolean {
165178
}
166179
}
167180

181+
/**
182+
* Verify an LSP server by attempting to start it with its required LSP arguments
183+
* and confirming that the process spawns successfully without immediate errors.
184+
*/
185+
function verifyLSPServerWithCommunication(
186+
command: string,
187+
args: string[],
188+
): Promise<boolean> {
189+
return new Promise(resolve => {
190+
const child = spawn(command, args, {stdio: ['pipe', 'pipe', 'pipe']});
191+
192+
// Set a timeout to prevent the process from hanging indefinitely
193+
const timeout = setTimeout(() => {
194+
child.kill();
195+
resolve(false);
196+
}, 2000);
197+
198+
// Listen for errors during startup (e.g., command not found)
199+
child.on('error', () => {
200+
clearTimeout(timeout);
201+
child.kill();
202+
resolve(false);
203+
});
204+
205+
// If the process spawns successfully, we consider it valid.
206+
// We can then kill it immediately.
207+
child.on('spawn', () => {
208+
clearTimeout(timeout);
209+
child.kill(); // Clean up the successfully spawned process
210+
resolve(true);
211+
});
212+
213+
// Handle cases where the process exits very quickly (either success or failure)
214+
child.on('exit', _code => {
215+
clearTimeout(timeout);
216+
// A clean exit can also indicate success for some servers
217+
// However, for LSP servers waiting for input, an immediate exit is often a failure
218+
// The 'spawn' event is a more reliable indicator for our purpose
219+
});
220+
});
221+
}
222+
168223
/**
169224
* Discover all installed language servers
170225
*/
171-
export function discoverLanguageServers(): LSPServerConfig[] {
226+
export async function discoverLanguageServers(): Promise<LSPServerConfig[]> {
172227
const discovered: LSPServerConfig[] = [];
173228
const coveredLanguages = new Set<string>();
174229

@@ -183,13 +238,38 @@ export function discoverLanguageServers(): LSPServerConfig[] {
183238
const commandPath = findCommand(server.command);
184239
if (!commandPath) continue;
185240

186-
// Verify server works if check command provided
241+
// Verify server works based on verification method
187242
// Use the resolved command path for verification
188-
if (server.checkCommand) {
189-
const checkCmd = server.checkCommand.replace(server.command, commandPath);
190-
if (!verifyServer(checkCmd)) continue;
243+
const verificationMethod = server.verificationMethod || 'version';
244+
245+
let verified = true;
246+
switch (verificationMethod) {
247+
case 'version':
248+
// Use the existing check command approach
249+
if (server.checkCommand) {
250+
const checkCmd = server.checkCommand.replace(
251+
server.command,
252+
commandPath,
253+
);
254+
verified = verifyServer(checkCmd);
255+
}
256+
break;
257+
258+
case 'lsp':
259+
// Use the new LSP verification approach
260+
verified = await verifyLSPServerWithCommunication(
261+
commandPath,
262+
server.args,
263+
);
264+
break;
265+
266+
case 'none':
267+
// Skip verification, only check if command exists
268+
break;
191269
}
192270

271+
if (!verified) continue;
272+
193273
// Add to discovered servers with resolved command path
194274
discovered.push({
195275
name: server.name,

source/mcp/transport-factory.spec.ts

Lines changed: 21 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -305,34 +305,19 @@ test('TransportFactory.createTransport: warns about auth config for websocket tr
305305
}
306306
});
307307

308-
test('TransportFactory.createTransport: warns about headers for http transport', t => {
309-
// Capture console.warn calls
310-
const originalWarn = console.warn;
311-
let warningMessage = '';
312-
console.warn = (message: string) => {
313-
warningMessage = message;
308+
test('TransportFactory.createTransport: creates http transport with headers', t => {
309+
const server: MCPServer = {
310+
name: 'test-http-with-headers',
311+
transport: 'http',
312+
url: 'https://example.com/mcp',
313+
headers: {
314+
Authorization: 'Bearer token123',
315+
},
314316
};
315317

316-
try {
317-
const server: MCPServer = {
318-
name: 'test-http-with-headers',
319-
transport: 'http',
320-
url: 'https://example.com/mcp',
321-
headers: {
322-
Authorization: 'Bearer token123',
323-
},
324-
};
325-
326-
const transport = TransportFactory.createTransport(server);
318+
const transport = TransportFactory.createTransport(server);
327319

328-
t.truthy(transport);
329-
t.true(warningMessage.includes('custom headers'));
330-
t.true(warningMessage.includes('HTTP transport'));
331-
t.true(warningMessage.includes('Authorization'));
332-
} finally {
333-
// Restore original console.warn
334-
console.warn = originalWarn;
335-
}
320+
t.truthy(transport);
336321
});
337322

338323
test('TransportFactory.createTransport: warns about auth config for http transport', t => {
@@ -365,33 +350,18 @@ test('TransportFactory.createTransport: warns about auth config for http transpo
365350
}
366351
});
367352

368-
test('TransportFactory.validateServerConfig: warns about headers for http transport', t => {
369-
// Capture console.warn calls
370-
const originalWarn = console.warn;
371-
let warningMessage = '';
372-
console.warn = (message: string) => {
373-
warningMessage = message;
353+
test('TransportFactory.validateServerConfig: validates http config with headers', t => {
354+
const server: MCPServer = {
355+
name: 'test-http-with-headers-validation',
356+
transport: 'http',
357+
url: 'https://example.com/mcp',
358+
headers: {
359+
'Custom-Header': 'value',
360+
},
374361
};
375362

376-
try {
377-
const server: MCPServer = {
378-
name: 'test-http-with-headers-validation',
379-
transport: 'http',
380-
url: 'https://example.com/mcp',
381-
headers: {
382-
'Custom-Header': 'value',
383-
},
384-
};
385-
386-
const result = TransportFactory.validateServerConfig(server);
363+
const result = TransportFactory.validateServerConfig(server);
387364

388-
t.true(result.valid);
389-
t.is(result.errors.length, 0);
390-
t.true(warningMessage.includes('custom headers'));
391-
t.true(warningMessage.includes('HTTP transport'));
392-
t.true(warningMessage.includes('Custom-Header'));
393-
} finally {
394-
// Restore original console.warn
395-
console.warn = originalWarn;
396-
}
365+
t.true(result.valid);
366+
t.is(result.errors.length, 0);
397367
});

0 commit comments

Comments
 (0)