Skip to content

Commit 0541346

Browse files
committed
Multiple language client support to support MCP Server
Signed-off-by: azerr <azerr@redhat.com>
1 parent ac784ed commit 0541346

5 files changed

Lines changed: 34 additions & 12 deletions

File tree

package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,12 @@
190190
"description": "The XML Language server allows other VSCode extensions to extend its functionality. It requires Java-specific features in order to do this. If extensions to the XML language server are detected, but a binary XML language server is run, a warning will appear. This setting can be used to disable this warning.",
191191
"scope": "window"
192192
},
193+
"xml.mcp.enabled": {
194+
"type": "boolean",
195+
"default": false,
196+
"markdownDescription": "Enable multi-client mode. When enabled, the XML language server listens on a TCP socket and registers itself in `${workspace}/.lsp-servers/lemminx.json`, allowing MCP servers (like languagetools) to connect and share the same server instance. Requires restart to take effect.",
197+
"scope": "window"
198+
},
193199
"xml.server.binary.path": {
194200
"type": "string",
195201
"description": "Specify the path of a custom binary version of the XML server to use. A binary will be downloaded if this is not set.",

src/client/xmlClient.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { TelemetryEvent } from '@redhat-developer/vscode-redhat-telemetry/lib';
22
import { commands, ExtensionContext, extensions, Position, TextDocument, TextEditor, Uri, window, workspace } from 'vscode';
33
import { Command, ConfigurationParams, ConfigurationRequest, DidChangeConfigurationNotification, DocumentFilter, DocumentSelector, ExecuteCommandParams, LanguageClientOptions, MessageType, NotificationType, RequestType, RevealOutputChannelOn, State, TextDocumentPositionParams } from "vscode-languageclient";
4-
import { Executable, LanguageClient } from 'vscode-languageclient/node';
4+
import { LanguageClient, ServerOptions } from 'vscode-languageclient/node';
55
import { XMLFileAssociation } from '../api/xmlExtensionApi';
66
import { registerClientServerCommands } from '../commands/registerCommands';
77
import * as ServerCommandConstants from '../commands/serverCommandConstants';
@@ -41,10 +41,10 @@ const ActionableNotification = new NotificationType<ActionableMessage>('xml/acti
4141

4242
let languageClient: LanguageClient;
4343

44-
export async function startLanguageClient(context: ExtensionContext, executable: Executable, logfile: string, externalXmlSettings: ExternalXmlSettings, requirementsData: RequirementsData): Promise<LanguageClient> {
44+
export async function startLanguageClient(context: ExtensionContext, serverOptions: ServerOptions, logfile: string, externalXmlSettings: ExternalXmlSettings, requirementsData: RequirementsData): Promise<LanguageClient> {
4545

4646
const languageClientOptions: LanguageClientOptions = getLanguageClientOptions(logfile, externalXmlSettings, requirementsData, context);
47-
languageClient = new LanguageClient('xml', 'XML Support', executable, languageClientOptions);
47+
languageClient = new LanguageClient('xml', 'XML Support', serverOptions, languageClientOptions);
4848
//In vscode-languageclient version 9.0.0, inline completion (textDocument/inlineCompletion) is a proposed feature
4949
// TODO remove registerProposedFeatures once upgraded to 10.x
5050
languageClient.registerProposedFeatures();

src/extension.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
import * as fs from 'fs-extra';
1414
import { ConfigurationTarget, ExtensionContext, Uri, commands, extensions, languages, window, workspace } from "vscode";
15-
import { Executable, LanguageClient } from 'vscode-languageclient/node';
15+
import { Executable, LanguageClient, ServerOptions } from 'vscode-languageclient/node';
1616
import { XMLExtensionApi } from './api/xmlExtensionApi';
1717
import { getXmlExtensionApiImplementation } from './api/xmlExtensionApiImplementation';
1818
import { cleanUpHeapDumps } from './client/clientErrorHandler';
@@ -67,7 +67,7 @@ export async function activate(context: ExtensionContext): Promise<XMLExtensionA
6767

6868
const externalXmlSettings: ExternalXmlSettings = new ExternalXmlSettings();
6969

70-
const serverOptions: Executable = await prepareExecutable(
70+
const serverOptions: ServerOptions = await prepareExecutable(
7171
requirementsData, collectXmlJavaExtensions(extensions.all, getXMLConfiguration().get("extension.jars", [])), context);
7272

7373
languageClient = await startLanguageClient(context, serverOptions, logfile, externalXmlSettings, requirementsData);

src/server/java/javaServerStarter.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as os from 'os';
22
import * as path from 'path';
3-
import { ExtensionContext, window, workspace } from 'vscode';
3+
import { env, ExtensionContext, version as vscodeVersion, window, workspace } from 'vscode';
44
import { Executable } from 'vscode-languageclient/node';
55
import { getProxySettings, getProxySettingsAsJVMArgs, jvmArgsContainsProxySettings, ProxySettings } from '../../settings/proxySettings';
66
import { getJavaagentFlag, getKey, getXMLConfiguration, IS_WORKSPACE_VMARGS_XML_ALLOWED, xmlServerVmargs } from '../../settings/settings';
@@ -19,13 +19,17 @@ export async function prepareJavaExecutable(
1919
xmlJavaExtensions: string[]
2020
): Promise<Executable> {
2121

22+
const mcpEnabled = getXMLConfiguration().get('mcp.enabled', false);
23+
const workspacePath = workspace.workspaceFolders?.[0]?.uri.fsPath || '';
24+
25+
// Always use stdio mode, but pass MCP parameters if enabled
2226
return {
2327
command: path.resolve(requirements.java_home + '/bin/java'),
24-
args: prepareParams(requirements, xmlJavaExtensions, context)
28+
args: prepareParams(requirements, xmlJavaExtensions, context, mcpEnabled, workspacePath)
2529
} as Executable;
2630
}
2731

28-
function prepareParams(requirements: RequirementsData, xmlJavaExtensions: string[], context: ExtensionContext): string[] {
32+
function prepareParams(requirements: RequirementsData, xmlJavaExtensions: string[], context: ExtensionContext, mcpEnabled?: boolean, workspacePath?: string): string[] {
2933
const params: string[] = [];
3034
if (DEBUG) {
3135
if (process.env['SUSPEND_SERVER'] === 'true') {
@@ -96,7 +100,17 @@ function prepareParams(requirements: RequirementsData, xmlJavaExtensions: string
96100
xmlJavaExtensionsClasspath = pathSeparator + xmlJavaExtensions.join(pathSeparator);
97101
}
98102
params.push('-cp'); params.push(path.resolve(server_home, launchersFound[0]) + xmlJavaExtensionsClasspath);
103+
104+
// Always use stdio launcher
99105
params.push('org.eclipse.lemminx.XMLServerLauncher');
106+
107+
// Add MCP arguments if enabled
108+
if (mcpEnabled && workspacePath) {
109+
params.push('--mcp-enabled');
110+
params.push('--workspace'); params.push(workspacePath);
111+
params.push('--client-name'); params.push(env.appName); // e.g., "Visual Studio Code", "VSCodium"
112+
params.push('--client-version'); params.push(vscodeVersion);
113+
}
100114
} else {
101115
return null;
102116
}
@@ -135,3 +149,5 @@ export function parseVMargs(params: any[], vmargsLine: string) {
135149
}
136150
});
137151
}
152+
153+

src/server/serverStarter.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
11
import { commands, ConfigurationTarget, ExtensionContext, window } from "vscode";
2-
import { Executable } from "vscode-languageclient/node";
2+
import { Executable, ServerOptions } from "vscode-languageclient/node";
33
import { getXMLConfiguration } from "../settings/settings";
44
import * as Telemetry from "../telemetry";
55
import { ABORTED_ERROR, prepareBinaryExecutable } from "./binary/binaryServerStarter";
66
import { prepareJavaExecutable } from "./java/javaServerStarter";
77
import { getOpenJDKDownloadLink, RequirementsData } from "./requirements";
88

99
/**
10-
* Returns the executable to use to launch LemMinX (the XML Language Server)
10+
* Returns the server options to use to launch LemMinX (the XML Language Server)
1111
*
1212
* @param requirements the java information, or an empty object if there is no java
1313
* @param xmlJavaExtensions a list of all the java extension jars
1414
* @param context the extensions context
1515
* @throws if neither the binary nor the java version of the extension can be launched
16-
* @returns the executable to launch LemMinX with (the XML language server)
16+
* @returns the server options to launch LemMinX with (the XML language server)
1717
*/
1818
export async function prepareExecutable(
1919
requirements: RequirementsData,
2020
xmlJavaExtensions: string[],
21-
context: ExtensionContext): Promise<Executable> {
21+
context: ExtensionContext): Promise<ServerOptions> {
2222

2323
const hasJava: boolean = requirements.java_home !== undefined;
2424
const hasExtensions: boolean = xmlJavaExtensions.length !== 0;

0 commit comments

Comments
 (0)