Skip to content

Commit 3c44416

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

6 files changed

Lines changed: 37 additions & 17 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 MCP (Model Context Protocol) server mode. When enabled, the XML language server runs in socket mode and registers itself in `~/.languagetools/instances.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 { Executable, 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/binary/binaryServerStarter.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import * as os from 'os';
66
import * as path from 'path';
77
import { Readable } from 'stream';
88
import { commands, ExtensionContext, extensions, ProgressLocation, ProgressOptions, window, WorkspaceConfiguration } from "vscode";
9-
import { Executable } from "vscode-languageclient/node";
9+
import { Executable, ServerOptions } from "vscode-languageclient/node";
1010
import * as yauzl from 'yauzl';
1111
import * as ClientCommandConstants from '../../commands/clientCommandConstants';
1212
import { getProxySettings, getProxySettingsAsEnvironmentVariables, ProxySettings } from '../../settings/proxySettings';
@@ -21,13 +21,13 @@ const JAR_ZIP_AND_HASH_REJECTOR = /(?:\.jar)|(?:\.zip)|(?:\.sha256)$/;
2121
export const ABORTED_ERROR: Error = new Error('XML Language Server download cancelled by user');
2222

2323
/**
24-
* Returns the executable to launch LemMinX (the XML Language Server) as a binary
24+
* Returns the server options to launch LemMinX (the XML Language Server) as a binary
2525
*
2626
* @param context the extension context
2727
* @throws if the binary doesn't exist and can't be downloaded, or if the binary is not trusted
28-
* @returns Returns the executable to launch LemMinX (the XML Language Server) as a binary
28+
* @returns Returns the server options to launch LemMinX (the XML Language Server) as a binary
2929
*/
30-
export async function prepareBinaryExecutable(context: ExtensionContext): Promise<Executable> {
30+
export async function prepareBinaryExecutable(context: ExtensionContext): Promise<ServerOptions> {
3131
const binaryArgs: string = getXMLConfiguration().get("server.binary.args");
3232
let binaryExecutable: Executable;
3333
return getServerBinaryPath()

src/server/java/javaServerStarter.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as os from 'os';
22
import * as path from 'path';
33
import { ExtensionContext, window, workspace } from 'vscode';
4-
import { Executable } from 'vscode-languageclient/node';
4+
import { Executable, ServerOptions } 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';
77
import { RequirementsData } from '../requirements';
@@ -17,15 +17,19 @@ export async function prepareJavaExecutable(
1717
context: ExtensionContext,
1818
requirements: RequirementsData,
1919
xmlJavaExtensions: string[]
20-
): Promise<Executable> {
20+
): Promise<ServerOptions> {
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,15 @@ 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+
}
100112
} else {
101113
return null;
102114
}
@@ -135,3 +147,5 @@ export function parseVMargs(params: any[], vmargsLine: string) {
135147
}
136148
});
137149
}
150+
151+

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)