Skip to content

Commit 95dfe2a

Browse files
feat(vscode-integration): add Model Context Protocol integration
Bridges the GLSP-announced MCP server into the IDE's built-in MCP definition provider API, surfacing the URL in the IDE's MCP server list. MCP integration - Node-side provider registers the announced endpoint on startup and deregisters on extension dispose. - Plumbs an opt-in MCP server configuration through the connector base classes. - Raises the minimum supported IDE version to the release shipping the MCP definition provider API. Adopters - Enable MCP by setting the configuration on the connector. - The workflow example demonstrates the end-to-end wiring with a transient connect notification and a copy-URL action. Misc - Webview content-security policy allows blob URLs as image sources, enabling client-side PNG export inside the webview. - Adapts the webview shutdown bridge to the widened asynchronous shutdown contract. Part of eclipse-glsp/glsp#1546
1 parent 57bf2bb commit 95dfe2a

12 files changed

Lines changed: 150 additions & 19 deletions

File tree

example/workflow/extension/package.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,12 @@
191191
"id": "workflow.editor.title",
192192
"label": "Diagram"
193193
}
194+
],
195+
"mcpServerDefinitionProviders": [
196+
{
197+
"id": "glsp-workflow",
198+
"label": "GLSP Workflow"
199+
}
194200
]
195201
},
196202
"activationEvents": [
@@ -209,6 +215,6 @@
209215
"workflow-glsp-webview": "2.7.0-next"
210216
},
211217
"engines": {
212-
"vscode": "^1.54.0"
218+
"vscode": "^1.99.0"
213219
}
214220
}

example/workflow/extension/src/workflow-editor-provider.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/********************************************************************************
2-
* Copyright (c) 2021-2023 EclipseSource and others.
2+
* Copyright (c) 2021-2026 EclipseSource and others.
33
*
44
* This program and the accompanying materials are made available under the
55
* terms of the Eclipse Public License v. 2.0 which is available at
@@ -48,6 +48,7 @@ export default class WorkflowEditorProvider extends GlspEditorProvider {
4848
<meta name="viewport" content="width=device-width, height=device-height">
4949
<meta http-equiv="Content-Security-Policy" content="
5050
default-src http://*.fontawesome.com ${webview.cspSource} 'unsafe-inline' 'unsafe-eval';
51+
img-src ${webview.cspSource} blob:;
5152
">
5253
5354
</head>

example/workflow/extension/src/workflow-extension.ts

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/********************************************************************************
2-
* Copyright (c) 2021-2024 EclipseSource and others.
2+
* Copyright (c) 2021-2026 EclipseSource and others.
33
*
44
* This program and the accompanying materials are made available under the
55
* terms of the Eclipse Public License v. 2.0 which is available at
@@ -19,6 +19,7 @@ import { WorkflowDiagramModule, WorkflowLayoutConfigurator, WorkflowServerModule
1919
import { configureELKLayoutModule } from '@eclipse-glsp/layout-elk';
2020
import { GModelStorage, LogLevel, createAppModule } from '@eclipse-glsp/server/node';
2121
import {
22+
GlspMcpServerProvider,
2223
GlspSocketServerLauncher,
2324
GlspVscodeConnector,
2425
NavigateAction,
@@ -58,21 +59,44 @@ export async function activate(context: vscode.ExtensionContext): Promise<void>
5859
context.subscriptions.push(serverProcess);
5960
await serverProcess.start();
6061
}
62+
// Presence of `mcpServer` (even an empty `{}`) opts the GLSP server into starting an
63+
// embedded MCP HTTP server. The custom `name` groups multiple GLSP-based MCP servers
64+
// in the IDE's MCP server list and matches the `mcpServerDefinitionProviders` id below.
65+
const mcpServer = { name: 'glsp-workflow' };
6166
// Wrap server with quickstart component
6267
const workflowServer = useIntegratedServer
6368
? new NodeGlspVscodeServer({
6469
clientId: 'glsp.workflow',
6570
clientName: 'workflow',
66-
serverModules: createServerModules()
71+
serverModules: createServerModules(),
72+
mcpServer
6773
})
6874
: new SocketGlspVscodeServer({
6975
clientId: 'glsp.workflow',
7076
clientName: 'workflow',
7177
connectionOptions: {
7278
port: serverProcess?.getPort() || JSON.parse(process.env.GLSP_SERVER_PORT || DEFAULT_SERVER_PORT),
7379
path: process.env.GLSP_WEBSOCKET_PATH
74-
}
80+
},
81+
mcpServer
7582
});
83+
84+
// Bridge the embedded MCP server's announced URL into VS Code's built-in MCP host. The
85+
// `glsp-workflow` provider id matches the `mcpServerDefinitionProviders` contribution in
86+
// this extension's `package.json` and the `mcpServer.name` above.
87+
const mcpProvider = new GlspMcpServerProvider();
88+
context.subscriptions.push(mcpProvider, vscode.lm.registerMcpServerDefinitionProvider(mcpServer.name, mcpProvider));
89+
workflowServer.initializeResult.then(
90+
result => {
91+
const server = mcpProvider.addServer(result);
92+
if (server) {
93+
notifyMcpConnected(server.name, server.url);
94+
}
95+
},
96+
() => {
97+
/* GLSP startup error already surfaced by the connector; no MCP server to register. */
98+
}
99+
);
76100
// Initialize GLSP-VSCode connector with server wrapper
77101
const glspVscodeConnector = new GlspVscodeConnector({
78102
server: workflowServer,
@@ -106,6 +130,16 @@ export async function activate(context: vscode.ExtensionContext): Promise<void>
106130
);
107131
}
108132

133+
/** Info notification with a `Copy URL` action; stays until the user dismisses it. */
134+
function notifyMcpConnected(name: string, url: string): void {
135+
const COPY_URL_ACTION = 'Copy URL';
136+
vscode.window.showInformationMessage(`MCP server '${name}' auto-registered at ${url}`, COPY_URL_ACTION).then(action => {
137+
if (action === COPY_URL_ACTION) {
138+
vscode.env.clipboard.writeText(url);
139+
}
140+
});
141+
}
142+
109143
function createServerModules(): ContainerModule[] {
110144
const appModule = createAppModule({ logLevel: LogLevel.info, logDir: LOG_DIR, fileLog: true, consoleLog: false });
111145
const elkLayoutModule = configureELKLayoutModule({ algorithms: ['layered'], layoutConfigurator: WorkflowLayoutConfigurator });

example/workflow/web-extension/src/workflow-editor-provider.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/********************************************************************************
2-
* Copyright (c) 2023 EclipseSource and others.
2+
* Copyright (c) 2023-2026 EclipseSource and others.
33
*
44
* This program and the accompanying materials are made available under the
55
* terms of the Eclipse Public License v. 2.0 which is available at
@@ -48,6 +48,7 @@ export default class WorkflowEditorProvider extends GlspEditorProvider {
4848
<meta name="viewport" content="width=device-width, height=device-height">
4949
<meta http-equiv="Content-Security-Policy" content="
5050
default-src http://*.fontawesome.com ${webview.cspSource} 'unsafe-inline' 'unsafe-eval';
51+
img-src ${webview.cspSource} blob:;
5152
">
5253
5354
</head>

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
"devDependencies": {
3333
"@eclipse-glsp/dev": "next",
3434
"@types/node": "22.x",
35-
"@types/vscode": "^1.54.0",
35+
"@types/vscode": "^1.99.0",
3636
"concurrently": "^8.2.2",
3737
"lerna": "^9.0.0",
3838
"mocha-ctrf-json-reporter": "0.0.9",

packages/vscode-integration-webview/src/webview-glsp-client.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,9 @@ export class WebviewGlspClient implements GLSPClient, Disposable {
118118
return this.messenger.sendRequest(DisposeClientSessionRequest, HOST_EXTENSION, params);
119119
}
120120

121-
shutdownServer(): void {
122-
this.messenger.sendNotification(ShutdownServerNotification, HOST_EXTENSION);
121+
async shutdownServer(): Promise<void> {
122+
// Await the send so callers can dispose without racing the wire flush.
123+
await this.messenger.sendNotification(ShutdownServerNotification, HOST_EXTENSION);
123124
}
124125

125126
async stop(): Promise<void> {

packages/vscode-integration/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,11 @@
5252
"ws": "^8.13.0"
5353
},
5454
"devDependencies": {
55-
"@types/vscode": "^1.54.0",
55+
"@types/vscode": "^1.99.0",
5656
"@types/ws": "^8.5.4"
5757
},
5858
"engines": {
59-
"vscode": "^1.54.0"
59+
"vscode": "^1.99.0"
6060
},
6161
"publishConfig": {
6262
"access": "public"

packages/vscode-integration/src/common/quickstart-components/base-glsp-vscode-server.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/********************************************************************************
2-
* Copyright (c) 2023 EclipseSource and others.
2+
* Copyright (c) 2023-2026 EclipseSource and others.
33
*
44
* This program and the accompanying materials are made available under the
55
* terms of the Eclipse Public License v. 2.0 which is available at
@@ -21,7 +21,8 @@ import {
2121
GLSPClient,
2222
InitializeParameters,
2323
InitializeResult,
24-
MaybePromise
24+
MaybePromise,
25+
McpServerConfiguration
2526
} from '@eclipse-glsp/protocol';
2627
import * as vscode from 'vscode';
2728
import { GlspVscodeServer } from '../types';
@@ -31,6 +32,8 @@ export interface GlspVscodeServerOptions {
3132
readonly clientId: string;
3233
/** Name to register the client with on the server. */
3334
readonly clientName: string;
35+
/** MCP-server configuration sent on the GLSP `initialize` request; presence opts in. */
36+
readonly mcpServer?: McpServerConfiguration;
3437
}
3538

3639
/**
@@ -85,7 +88,8 @@ export abstract class BaseGlspVscodeServer<C extends GLSPClient = GLSPClient> im
8588
protected async createInitializeParameters(): Promise<InitializeParameters> {
8689
return {
8790
applicationId: ApplicationIdProvider.get(),
88-
protocolVersion: GLSPClient.protocolVersion
91+
protocolVersion: GLSPClient.protocolVersion,
92+
...(this.options.mcpServer ? { mcpServer: this.options.mcpServer } : {})
8993
};
9094
}
9195

packages/vscode-integration/src/common/quickstart-components/webview-endpoint.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ export class WebviewEndpoint implements Disposable {
157157
this.messenger.onRequest(DisposeClientSessionRequest, params => glspClient.disposeClientSession(params), {
158158
sender: this.messageParticipant
159159
}),
160-
this.messenger.onRequest(ShutdownServerNotification, () => glspClient.shutdownServer(), {
160+
this.messenger.onRequest(ShutdownServerNotification, () => Promise.resolve(glspClient.shutdownServer()), {
161161
sender: this.messageParticipant
162162
}),
163163
this.messenger.onRequest(StopRequest, () => glspClient.stop(), {
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/********************************************************************************
2+
* Copyright (c) 2026 EclipseSource and others.
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License v. 2.0 which is available at
6+
* http://www.eclipse.org/legal/epl-2.0.
7+
*
8+
* This Source Code may also be made available under the following Secondary
9+
* Licenses when the conditions for such availability set forth in the Eclipse
10+
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
11+
* with the GNU Classpath Exception which is available at
12+
* https://www.gnu.org/software/classpath/license.html.
13+
*
14+
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15+
********************************************************************************/
16+
import { InitializeResult, McpInitializeResult, McpServerResult } from '@eclipse-glsp/protocol';
17+
import * as vscode from 'vscode';
18+
19+
/**
20+
* Bridges the embedded GLSP MCP server's announced URL into VS Code's built-in MCP host
21+
* via {@link vscode.lm.registerMcpServerDefinitionProvider}. Adopters create one provider
22+
* per extension and feed it the {@link InitializeResult} they get back from the GLSP server's
23+
* `initialize` call (or an explicit {@link McpServerResult}); each entry then surfaces as a
24+
* dynamic MCP server definition in VS Code.
25+
*
26+
* Companion contribution point in the consuming extension's `package.json`:
27+
* ```json
28+
* "contributes": {
29+
* "mcpServerDefinitionProviders": [
30+
* { "id": "glsp", "label": "GLSP" }
31+
* ]
32+
* }
33+
* ```
34+
*
35+
* @example
36+
* ```ts
37+
* const provider = new GlspMcpServerProvider();
38+
* context.subscriptions.push(vscode.lm.registerMcpServerDefinitionProvider('glsp', provider));
39+
* const initializeResult = await glspVscodeServer.initializeResult;
40+
* provider.addServer(initializeResult);
41+
* ```
42+
*/
43+
export class GlspMcpServerProvider implements vscode.McpServerDefinitionProvider, vscode.Disposable {
44+
protected readonly servers = new Map<string, McpServerResult>();
45+
protected readonly didChangeEmitter = new vscode.EventEmitter<void>();
46+
47+
readonly onDidChangeMcpServerDefinitions: vscode.Event<void> = this.didChangeEmitter.event;
48+
49+
/**
50+
* Adds (or replaces by `name`) an MCP server entry. Accepts either the raw
51+
* {@link InitializeResult} from a GLSP `initialize` call (a no-op if the response carries no
52+
* `mcpServer` field) or an explicit {@link McpServerResult}. Returns the entry that was
53+
* registered, or `undefined` if nothing was added.
54+
*/
55+
addServer(serverOrResult: InitializeResult | McpServerResult): McpServerResult | undefined {
56+
const server = McpServerResult.is(serverOrResult) ? serverOrResult : McpInitializeResult.getServer(serverOrResult);
57+
if (!server) {
58+
return undefined;
59+
}
60+
this.servers.set(server.name, server);
61+
this.didChangeEmitter.fire();
62+
return server;
63+
}
64+
65+
/** Removes a previously-added entry by `name`. No-op if the entry is unknown. */
66+
removeServer(name: string): void {
67+
if (this.servers.delete(name)) {
68+
this.didChangeEmitter.fire();
69+
}
70+
}
71+
72+
provideMcpServerDefinitions(): vscode.McpServerDefinition[] {
73+
return [...this.servers.values()].map(
74+
server => new vscode.McpHttpServerDefinition(server.name, vscode.Uri.parse(server.url), server.headers)
75+
);
76+
}
77+
78+
dispose(): void {
79+
this.servers.clear();
80+
this.didChangeEmitter.dispose();
81+
}
82+
}
83+

0 commit comments

Comments
 (0)