Skip to content

Commit 8c638f2

Browse files
authored
Register tool functions of mcp servers again after restart of the frontend (#14723)
fixed #14701
1 parent 52d9949 commit 8c638f2

4 files changed

+103
-46
lines changed

packages/ai-mcp/src/browser/mcp-command-contribution.ts

+9-46
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,7 @@ import { AICommandHandlerFactory } from '@theia/ai-core/lib/browser/ai-command-h
1717
import { CommandContribution, CommandRegistry, MessageService } from '@theia/core';
1818
import { QuickInputService } from '@theia/core/lib/browser';
1919
import { inject, injectable } from '@theia/core/shared/inversify';
20-
import { MCPServerManager } from '../common/mcp-server-manager';
21-
import { ToolInvocationRegistry, ToolRequest } from '@theia/ai-core';
22-
23-
type MCPTool = Awaited<ReturnType<MCPServerManager['getTools']>>['tools'][number];
20+
import { MCPFrontendService } from './mcp-frontend-service';
2421

2522
export const StartMCPServer = {
2623
id: 'mcp.startserver',
@@ -42,11 +39,8 @@ export class MCPCommandContribution implements CommandContribution {
4239
@inject(MessageService)
4340
protected messageService: MessageService;
4441

45-
@inject(MCPServerManager)
46-
protected readonly mcpServerManager: MCPServerManager;
47-
48-
@inject(ToolInvocationRegistry)
49-
protected readonly toolInvocationRegistry: ToolInvocationRegistry;
42+
@inject(MCPFrontendService)
43+
protected readonly mcpFrontendService: MCPFrontendService;
5044

5145
private async getMCPServerSelection(serverNames: string[]): Promise<string | undefined> {
5246
if (!serverNames || serverNames.length === 0) {
@@ -61,7 +55,7 @@ export class MCPCommandContribution implements CommandContribution {
6155
commandRegistry.registerCommand(StopMCPServer, this.commandHandlerFactory({
6256
execute: async () => {
6357
try {
64-
const startedServers = await this.mcpServerManager.getStartedServers();
58+
const startedServers = await this.mcpFrontendService.getStartedServers();
6559
if (!startedServers || startedServers.length === 0) {
6660
this.messageService.error('No MCP servers running.');
6761
return;
@@ -70,8 +64,7 @@ export class MCPCommandContribution implements CommandContribution {
7064
if (!selection) {
7165
return;
7266
}
73-
this.toolInvocationRegistry.unregisterAllTools(`mcp_${selection}`);
74-
this.mcpServerManager.stopServer(selection);
67+
await this.mcpFrontendService.stopServer(selection);
7568
} catch (error) {
7669
console.error('Error while stopping MCP server:', error);
7770
}
@@ -81,8 +74,8 @@ export class MCPCommandContribution implements CommandContribution {
8174
commandRegistry.registerCommand(StartMCPServer, this.commandHandlerFactory({
8275
execute: async () => {
8376
try {
84-
const servers = await this.mcpServerManager.getServerNames();
85-
const startedServers = await this.mcpServerManager.getStartedServers();
77+
const servers = await this.mcpFrontendService.getServerNames();
78+
const startedServers = await this.mcpFrontendService.getStartedServers();
8679
const startableServers = servers.filter(server => !startedServers.includes(server));
8780
if (!startableServers || startableServers.length === 0) {
8881
if (startedServers && startedServers.length > 0) {
@@ -97,13 +90,8 @@ export class MCPCommandContribution implements CommandContribution {
9790
if (!selection) {
9891
return;
9992
}
100-
this.mcpServerManager.startServer(selection);
101-
const { tools } = await this.mcpServerManager.getTools(selection);
102-
const toolRequests: ToolRequest[] = tools.map(tool => this.convertToToolRequest(tool, selection));
103-
104-
for (const toolRequest of toolRequests) {
105-
this.toolInvocationRegistry.registerTool(toolRequest);
106-
}
93+
await this.mcpFrontendService.startServer(selection);
94+
const { tools } = await this.mcpFrontendService.getTools(selection);
10795
const toolNames = tools.map(tool => tool.name || 'Unnamed Tool').join(', ');
10896
this.messageService.info(
10997
`MCP server "${selection}" successfully started. Registered tools: ${toolNames || 'No tools available.'}`
@@ -115,29 +103,4 @@ export class MCPCommandContribution implements CommandContribution {
115103
}
116104
}));
117105
}
118-
119-
convertToToolRequest(tool: MCPTool, serverName: string): ToolRequest {
120-
const id = `mcp_${serverName}_${tool.name}`;
121-
122-
return {
123-
id: id,
124-
name: id,
125-
providerName: `mcp_${serverName}`,
126-
parameters: ToolRequest.isToolRequestParameters(tool.inputSchema) ? {
127-
type: tool.inputSchema.type,
128-
properties: tool.inputSchema.properties,
129-
required: tool.inputSchema.required
130-
} : undefined,
131-
description: tool.description,
132-
handler: async (arg_string: string) => {
133-
try {
134-
return await this.mcpServerManager.callTool(serverName, tool.name, arg_string);
135-
} catch (error) {
136-
console.error(`Error in tool handler for ${tool.name} on MCP server ${serverName}:`, error);
137-
throw error;
138-
}
139-
},
140-
};
141-
}
142-
143106
}

packages/ai-mcp/src/browser/mcp-frontend-application-contribution.ts

+5
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { inject, injectable } from '@theia/core/shared/inversify';
1818
import { MCPServerDescription, MCPServerManager } from '../common';
1919
import { MCP_SERVERS_PREF } from './mcp-preferences';
2020
import { JSONObject } from '@theia/core/shared/@phosphor/coreutils';
21+
import { MCPFrontendService } from './mcp-frontend-service';
2122

2223
interface MCPServersPreferenceValue {
2324
command: string;
@@ -60,6 +61,9 @@ export class McpFrontendApplicationContribution implements FrontendApplicationCo
6061
@inject(MCPServerManager)
6162
protected manager: MCPServerManager;
6263

64+
@inject(MCPFrontendService)
65+
protected frontendMCPService: MCPFrontendService;
66+
6367
protected prevServers: Map<string, MCPServerDescription> = new Map();
6468

6569
onStart(): void {
@@ -77,6 +81,7 @@ export class McpFrontendApplicationContribution implements FrontendApplicationCo
7781
}
7882
});
7983
});
84+
this.frontendMCPService.registerToolsForAllStartedServers();
8085
}
8186

8287
protected handleServerChanges(newServers: MCPServersPreference): void {

packages/ai-mcp/src/browser/mcp-frontend-module.ts

+2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { FrontendApplicationContribution, PreferenceContribution, RemoteConnecti
2121
import { MCPServerManager, MCPServerManagerPath } from '../common/mcp-server-manager';
2222
import { McpServersPreferenceSchema } from './mcp-preferences';
2323
import { McpFrontendApplicationContribution } from './mcp-frontend-application-contribution';
24+
import { MCPFrontendService } from './mcp-frontend-service';
2425

2526
export default new ContainerModule(bind => {
2627
bind(PreferenceContribution).toConstantValue({ schema: McpServersPreferenceSchema });
@@ -30,4 +31,5 @@ export default new ContainerModule(bind => {
3031
const connection = ctx.container.get<ServiceConnectionProvider>(RemoteConnectionProvider);
3132
return connection.createProxy<MCPServerManager>(MCPServerManagerPath);
3233
}).inSingletonScope();
34+
bind(MCPFrontendService).toSelf().inSingletonScope();
3335
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// *****************************************************************************
2+
// Copyright (C) 2024 EclipseSource GmbH.
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-only WITH Classpath-exception-2.0
15+
// *****************************************************************************
16+
import { injectable, inject } from '@theia/core/shared/inversify';
17+
import { MCPServer, MCPServerManager } from '../common/mcp-server-manager';
18+
import { ToolInvocationRegistry, ToolRequest } from '@theia/ai-core';
19+
20+
@injectable()
21+
export class MCPFrontendService {
22+
@inject(MCPServerManager)
23+
protected readonly mcpServerManager: MCPServerManager;
24+
25+
@inject(ToolInvocationRegistry)
26+
protected readonly toolInvocationRegistry: ToolInvocationRegistry;
27+
28+
async startServer(serverName: string): Promise<void> {
29+
await this.mcpServerManager.startServer(serverName);
30+
this.registerTools(serverName);
31+
}
32+
33+
async registerToolsForAllStartedServers(): Promise<void> {
34+
const startedServers = await this.getStartedServers();
35+
for (const serverName of startedServers) {
36+
await this.registerTools(serverName);
37+
}
38+
}
39+
40+
async registerTools(serverName: string): Promise<void> {
41+
const { tools } = await this.getTools(serverName);
42+
const toolRequests: ToolRequest[] = tools.map(tool => this.convertToToolRequest(tool, serverName));
43+
toolRequests.forEach(toolRequest =>
44+
this.toolInvocationRegistry.registerTool(toolRequest)
45+
);
46+
}
47+
48+
async stopServer(serverName: string): Promise<void> {
49+
this.toolInvocationRegistry.unregisterAllTools(`mcp_${serverName}`);
50+
await this.mcpServerManager.stopServer(serverName);
51+
}
52+
53+
getStartedServers(): Promise<string[]> {
54+
return this.mcpServerManager.getStartedServers();
55+
}
56+
57+
getServerNames(): Promise<string[]> {
58+
return this.mcpServerManager.getServerNames();
59+
}
60+
61+
getTools(serverName: string): ReturnType<MCPServer['getTools']> {
62+
return this.mcpServerManager.getTools(serverName);
63+
}
64+
65+
private convertToToolRequest(tool: Awaited<ReturnType<MCPServerManager['getTools']>>['tools'][number], serverName: string): ToolRequest {
66+
const id = `mcp_${serverName}_${tool.name}`;
67+
return {
68+
id: id,
69+
name: id,
70+
providerName: `mcp_${serverName}`,
71+
parameters: ToolRequest.isToolRequestParameters(tool.inputSchema) ? {
72+
type: tool.inputSchema.type,
73+
properties: tool.inputSchema.properties,
74+
required: tool.inputSchema.required
75+
} : undefined,
76+
description: tool.description,
77+
handler: async (arg_string: string) => {
78+
try {
79+
return await this.mcpServerManager.callTool(serverName, tool.name, arg_string);
80+
} catch (error) {
81+
console.error(`Error in tool handler for ${tool.name} on MCP server ${serverName}:`, error);
82+
throw error;
83+
}
84+
},
85+
};
86+
}
87+
}

0 commit comments

Comments
 (0)