Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/plugin-ext/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"main": "lib/common/index.js",
"typings": "lib/common/index.d.ts",
"dependencies": {
"@theia/ai-mcp": "1.62.0",
"@theia/bulk-edit": "1.62.0",
"@theia/callhierarchy": "1.62.0",
"@theia/console": "1.62.0",
Expand Down
137 changes: 137 additions & 0 deletions packages/plugin-ext/src/common/lm-protocol.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// *****************************************************************************
// Copyright (C) 2025 EclipseSource.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************

import { UriComponents } from './uri-components';

/**
* Protocol interfaces for MCP server definition providers.
*/

export interface McpStdioServerDefinitionDto {
/**
* The human-readable name of the server.
*/
readonly label: string;

/**
* The working directory used to start the server.
*/
cwd?: UriComponents;

/**
* The command used to start the server. Node.js-based servers may use
* `process.execPath` to use the editor's version of Node.js to run the script.
*/
command: string;

/**
* Additional command-line arguments passed to the server.
*/
args?: string[];

/**
* Optional additional environment information for the server. Variables
* in this environment will overwrite or remove (if null) the default
* environment variables of the editor's extension host.
*/
env?: Record<string, string | number | null>;

/**
* Optional version identification for the server. If this changes, the
* editor will indicate that tools have changed and prompt to refresh them.
*/
version?: string;

}

/**
* McpHttpServerDefinition represents an MCP server available using the
* Streamable HTTP transport.
*/
export interface McpHttpServerDefinitionDto {
/**
* The human-readable name of the server.
*/
readonly label: string;

/**
* The URI of the server. The editor will make a POST request to this URI
* to begin each session.
*/
uri: UriComponents;

/**
* Optional additional heads included with each request to the server.
*/
headers?: Record<string, string>;

/**
* Optional version identification for the server. If this changes, the
* editor will indicate that tools have changed and prompt to refresh them.
*/
version?: string;
}

/**
* Definitions that describe different types of Model Context Protocol servers,
* which can be returned from the {@link McpServerDefinitionProvider}.
*/
export type McpServerDefinitionDto = McpStdioServerDefinitionDto | McpHttpServerDefinitionDto;
export const isMcpHttpServerDefinitionDto = (definition: McpServerDefinitionDto): definition is McpHttpServerDefinitionDto => 'uri' in definition;
/**
* Main side of the MCP server definition registry.
*/
export interface McpServerDefinitionRegistryMain {
/**
* Register an MCP server definition provider.
*/
$registerMcpServerDefinitionProvider(handle: number, name: string): void;

/**
* Unregister an MCP server definition provider.
*/
$unregisterMcpServerDefinitionProvider(handle: number): void;

/**
* Notify that server definitions have changed.
*/
$onDidChangeMcpServerDefinitions(handle: number): void;

/**
* Get server definitions from a provider.
*/
$getServerDefinitions(handle: number): Promise<McpServerDefinitionDto[]>;

/**
* Resolve a server definition.
*/
$resolveServerDefinition(handle: number, server: McpServerDefinitionDto): Promise<McpServerDefinitionDto | undefined>;
}

/**
* Extension side of the MCP server definition registry.
*/
export interface McpServerDefinitionRegistryExt {
/**
* Request server definitions from a provider.
*/
$provideServerDefinitions(handle: number): Promise<McpServerDefinitionDto[]>;

/**
* Resolve a server definition from a provider.
*/
$resolveServerDefinition(handle: number, server: McpServerDefinitionDto): Promise<McpServerDefinitionDto | undefined>;
}
7 changes: 5 additions & 2 deletions packages/plugin-ext/src/common/plugin-api-rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ import { AccessibilityInformation } from '@theia/core/lib/common/accessibility';
import { TreeDelta } from '@theia/test/lib/common/tree-delta';
import { TestItemDTO, TestOutputDTO, TestRunDTO, TestRunProfileDTO, TestRunRequestDTO, TestStateChangeDTO } from './test-types';
import { ArgumentProcessor } from './commands';
import { McpServerDefinitionRegistryMain, McpServerDefinitionRegistryExt } from './lm-protocol';

export interface PreferenceData {
[scope: number]: any;
Expand Down Expand Up @@ -2346,7 +2347,8 @@ export const PLUGIN_RPC_CONTEXT = {
TELEMETRY_MAIN: createProxyIdentifier<TelemetryMain>('TelemetryMain'),
LOCALIZATION_MAIN: createProxyIdentifier<LocalizationMain>('LocalizationMain'),
TESTING_MAIN: createProxyIdentifier<TestingMain>('TestingMain'),
URI_MAIN: createProxyIdentifier<UriMain>('UriMain')
URI_MAIN: createProxyIdentifier<UriMain>('UriMain'),
MCP_SERVER_DEFINITION_REGISTRY_MAIN: createProxyIdentifier<McpServerDefinitionRegistryMain>('McpServerDefinitionRegistryMain')
};

export const MAIN_RPC_CONTEXT = {
Expand Down Expand Up @@ -2389,7 +2391,8 @@ export const MAIN_RPC_CONTEXT = {
TABS_EXT: createProxyIdentifier<TabsExt>('TabsExt'),
TELEMETRY_EXT: createProxyIdentifier<TelemetryExt>('TelemetryExt)'),
TESTING_EXT: createProxyIdentifier<TestingExt>('TestingExt'),
URI_EXT: createProxyIdentifier<UriExt>('UriExt')
URI_EXT: createProxyIdentifier<UriExt>('UriExt'),
MCP_SERVER_DEFINITION_REGISTRY_EXT: createProxyIdentifier<McpServerDefinitionRegistryExt>('McpServerDefinitionRegistryExt')
};

export interface TasksExt {
Expand Down
7 changes: 7 additions & 0 deletions packages/plugin-ext/src/common/plugin-protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ export interface PluginPackageContribution {
notebooks?: PluginPackageNotebook[];
notebookRenderer?: PluginNotebookRendererContribution[];
notebookPreload?: PluginPackageNotebookPreload[];
mcpServerDefinitionProviders?: PluginPackageMcpServerDefinitionProviderContribution[];
}

export interface PluginPackageNotebook {
Expand All @@ -126,6 +127,12 @@ export interface PluginPackageNotebookPreload {
entrypoint: string;
}

export interface PluginPackageMcpServerDefinitionProviderContribution {
id: string;
label: string;
description?: string;
}

export interface PluginPackageAuthenticationProvider {
id: string;
label: string;
Expand Down
2 changes: 2 additions & 0 deletions packages/plugin-ext/src/hosted/node/plugin-host-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import { WebviewsExtImpl } from '../../plugin/webviews';
import { TerminalServiceExtImpl } from '../../plugin/terminal-ext';
import { InternalSecretsExt, SecretsExtImpl } from '../../plugin/secrets-ext';
import { setupPluginHostLogger } from './plugin-host-logger';
import { LmExtImpl } from '../../plugin/lm-ext';

export default new ContainerModule(bind => {
const channel = new IPCChannel();
Expand Down Expand Up @@ -63,6 +64,7 @@ export default new ContainerModule(bind => {
bind(SecretsExtImpl).toSelf().inSingletonScope();
bind(PreferenceRegistryExtImpl).toSelf().inSingletonScope();
bind(DebugExtImpl).toSelf().inSingletonScope();
bind(LmExtImpl).toSelf().inSingletonScope();
bind(EditorsAndDocumentsExtImpl).toSelf().inSingletonScope();
bind(WorkspaceExtImpl).toSelf().inSingletonScope();
bind(MessageRegistryExt).toSelf().inSingletonScope();
Expand Down
137 changes: 137 additions & 0 deletions packages/plugin-ext/src/main/browser/lm-main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// *****************************************************************************
// Copyright (C) 2025 EclipseSource
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************

import { interfaces } from '@theia/core/shared/inversify';
import { RPCProtocol } from '../../common/rpc-protocol';
import {
McpServerDefinitionRegistryMain,
McpServerDefinitionRegistryExt,
McpServerDefinitionDto,
isMcpHttpServerDefinitionDto,
} from '../../common/lm-protocol';
import { MAIN_RPC_CONTEXT } from '../../common/plugin-api-rpc';
import { MCPServerManager, MCPServerDescription } from '@theia/ai-mcp/lib/common';

export class McpServerDefinitionRegistryMainImpl implements McpServerDefinitionRegistryMain {
private readonly proxy: McpServerDefinitionRegistryExt;
private readonly providers = new Map<number, string>();
private readonly mcpServerManager: MCPServerManager | undefined;

constructor(
rpc: RPCProtocol,
container: interfaces.Container
) {
this.proxy = rpc.getProxy(MAIN_RPC_CONTEXT.MCP_SERVER_DEFINITION_REGISTRY_EXT);
try {
this.mcpServerManager = container.get(MCPServerManager);
} catch {
// MCP Server Manager is optional
this.mcpServerManager = undefined;
}
}

$registerMcpServerDefinitionProvider(handle: number, name: string): void {
this.providers.set(handle, name);
// Trigger initial load of server definitions
this.loadServerDefinitions(handle);
}

$unregisterMcpServerDefinitionProvider(handle: number): void {
if (!this.mcpServerManager) {
console.warn('MCP Server Manager not available - MCP server definitions will not be loaded');
return;
}
const provider = this.providers.get(handle);
if (!provider) {
console.warn(`No MCP Server provider found for handle '${handle}' - MCP server definitions will not be loaded`);
return;
}
this.mcpServerManager.removeServer(provider);
this.providers.delete(handle);
}

$onDidChangeMcpServerDefinitions(handle: number): void {
// Reload server definitions when provider reports changes
this.loadServerDefinitions(handle);
}

async $getServerDefinitions(handle: number): Promise<McpServerDefinitionDto[]> {
try {
return await this.proxy.$provideServerDefinitions(handle);
} catch (error) {
console.error('Error getting MCP server definitions:', error);
return [];
}
}

async $resolveServerDefinition(handle: number, server: McpServerDefinitionDto): Promise<McpServerDefinitionDto | undefined> {
try {
return await this.proxy.$resolveServerDefinition(handle, server);
} catch (error) {
console.error('Error resolving MCP server definition:', error);
return server;
}
}

private async loadServerDefinitions(handle: number): Promise<void> {
if (!this.mcpServerManager) {
console.warn('MCP Server Manager not available - MCP server definitions will not be loaded');
return;
}

try {
const definitions = await this.$getServerDefinitions(handle);

for (const definition of definitions) {
const resolved = await this.$resolveServerDefinition(handle, definition);
if (resolved) {
const mcpServerDescription = this.convertToMcpServerDescription(resolved);
this.mcpServerManager.addOrUpdateServer(mcpServerDescription);
}
}
} catch (error) {
console.error('Error loading MCP server definitions:', error);
}
}

private convertToMcpServerDescription(definition: McpServerDefinitionDto): MCPServerDescription {
if (isMcpHttpServerDefinitionDto(definition)) {
// For HTTP servers, we would need to create a bridge or adapter
// For now, we'll create a placeholder stdio server that could proxy to HTTP
console.warn(`HTTP transport not yet supported for MCP server '${definition.label}'. Skipping.`);
throw new Error(`HTTP transport not yet supported for MCP server '${definition.label}'`);
}

// Convert env values to strings, filtering out null values
let convertedEnv: Record<string, string> | undefined;
if (definition.env) {
convertedEnv = {};
for (const [key, value] of Object.entries(definition.env)) {
if (value !== null) {
convertedEnv[key] = String(value);
}
}
}

return {
name: definition.label,
command: definition.command!,
args: definition.args,
env: convertedEnv,
autostart: false, // Extensions should manage their own server lifecycle
};
}
}
4 changes: 4 additions & 0 deletions packages/plugin-ext/src/main/browser/main-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ import { TestingMainImpl } from './test-main';
import { UriMainImpl } from './uri-main';
import { LoggerMainImpl } from './logger-main';
import { MonacoEditorProvider } from '@theia/monaco/lib/browser/monaco-editor-provider';
import { McpServerDefinitionRegistryMainImpl } from './lm-main';

export function setUpPluginApi(rpc: RPCProtocol, container: interfaces.Container): void {
const loggerMain = new LoggerMainImpl(container);
Expand Down Expand Up @@ -213,4 +214,7 @@ export function setUpPluginApi(rpc: RPCProtocol, container: interfaces.Container

const uriMain = new UriMainImpl(rpc, container);
rpc.set(PLUGIN_RPC_CONTEXT.URI_MAIN, uriMain);

const mcpServerDefinitionRegistryMain = new McpServerDefinitionRegistryMainImpl(rpc, container);
rpc.set(PLUGIN_RPC_CONTEXT.MCP_SERVER_DEFINITION_REGISTRY_MAIN, mcpServerDefinitionRegistryMain);
}
Loading
Loading