Skip to content

Commit 051e4c2

Browse files
committed
feat: support node14 and improve MCP Server configuration page
1 parent caf1013 commit 051e4c2

11 files changed

Lines changed: 93 additions & 29 deletions

File tree

packages/ai-native/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
"ansi-regex": "^2.0.0",
5050
"diff": "^7.0.0",
5151
"dom-align": "^1.7.0",
52+
"eventsource": "^3.0.2",
5253
"rc-collapse": "^4.0.0",
5354
"react-chat-elements": "^12.0.10",
5455
"react-highlight": "^0.15.0",

packages/ai-native/src/browser/mcp/config/components/mcp-config.module.less

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,15 @@
162162
align-items: center;
163163
gap: 8px;
164164
flex-wrap: wrap;
165+
&.link {
166+
cursor: pointer;
167+
opacity: 0.6;
168+
transition: all 0.2s ease;
169+
&:hover {
170+
opacity: 1;
171+
text-decoration: underline;
172+
}
173+
}
165174
}
166175

167176
.toolTag {

packages/ai-native/src/browser/mcp/config/components/mcp-config.view.tsx

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1-
import React from 'react';
1+
import cls from 'classnames';
2+
import React, { useCallback } from 'react';
23

4+
import { Badge } from '@opensumi/ide-components';
35
import { AINativeSettingSectionsId, ILogger, useInjectable } from '@opensumi/ide-core-browser';
46
import { PreferenceService } from '@opensumi/ide-core-browser/lib/preferences';
7+
import { localize } from '@opensumi/ide-core-common';
58

69
import { BUILTIN_MCP_SERVER_NAME } from '../../../../common';
710
import { MCPServerDescription } from '../../../../common/mcp-server-manager';
@@ -17,6 +20,7 @@ interface MCPServer {
1720
tools?: string[];
1821
command?: string;
1922
type?: string;
23+
serverHost?: string;
2024
}
2125

2226
export const MCPConfigView: React.FC = () => {
@@ -54,7 +58,6 @@ export const MCPConfigView: React.FC = () => {
5458
// Update enabled state in preferences
5559
const servers = preferenceService.get<MCPServerDescription[]>(AINativeSettingSectionsId.MCPServers, []);
5660
let updatedServers = servers;
57-
5861
// 处理内置服务器的特殊情况
5962
if (serverName === BUILTIN_MCP_SERVER_NAME) {
6063
const builtinServerExists = servers.some((server) => server.name === BUILTIN_MCP_SERVER_NAME);
@@ -133,6 +136,19 @@ export const MCPConfigView: React.FC = () => {
133136
await loadServers();
134137
};
135138

139+
const getReadableServerType = useCallback((type: string) => {
140+
switch (type) {
141+
case MCP_SERVER_TYPE.STDIO:
142+
return localize('ai.native.mcp.type.stdio');
143+
case MCP_SERVER_TYPE.SSE:
144+
return localize('ai.native.mcp.type.sse');
145+
case MCP_SERVER_TYPE.BUILTIN:
146+
return localize('ai.native.mcp.type.builtin');
147+
default:
148+
return type;
149+
}
150+
}, []);
151+
136152
return (
137153
<div className={styles.container}>
138154
<div className={styles.header}>
@@ -152,19 +168,23 @@ export const MCPConfigView: React.FC = () => {
152168
<h3 className={styles.serverName}>{server.name}</h3>
153169
</div>
154170
<div className={styles.serverActions}>
155-
<button className={styles.iconButton} title='Edit' onClick={() => handleEditServer(server)}>
156-
<i className='codicon codicon-edit' />
157-
</button>
171+
{server.name !== BUILTIN_MCP_SERVER_NAME && (
172+
<button className={styles.iconButton} title='Edit' onClick={() => handleEditServer(server)}>
173+
<i className='codicon codicon-edit' />
174+
</button>
175+
)}
158176
<button
159177
className={styles.iconButton}
160178
title={server.isStarted ? 'Stop' : 'Start'}
161179
onClick={() => handleServerControl(server.name, !server.isStarted)}
162180
>
163181
<i className={`codicon ${server.isStarted ? 'codicon-debug-stop' : 'codicon-debug-start'}`} />
164182
</button>
165-
<button className={styles.iconButton} title='Delete' onClick={() => handleDeleteServer(server.name)}>
166-
<i className='codicon codicon-trash' />
167-
</button>
183+
{server.name !== BUILTIN_MCP_SERVER_NAME && (
184+
<button className={styles.iconButton} title='Delete' onClick={() => handleDeleteServer(server.name)}>
185+
<i className='codicon codicon-trash' />
186+
</button>
187+
)}
168188
</div>
169189
</div>
170190
<div className={styles.serverDetail}>
@@ -177,7 +197,7 @@ export const MCPConfigView: React.FC = () => {
177197
{server.type && (
178198
<div className={styles.detailRow}>
179199
<span className={styles.detailLabel}>Type:</span>
180-
<span className={styles.serverType}>{server.type}</span>
200+
<Badge className={styles.serverType}>{getReadableServerType(server.type)}</Badge>
181201
</div>
182202
)}
183203
</div>
@@ -203,6 +223,14 @@ export const MCPConfigView: React.FC = () => {
203223
</div>
204224
</div>
205225
)}
226+
{server.serverHost && (
227+
<div className={styles.serverDetail}>
228+
<div className={styles.detailRow}>
229+
<span className={styles.detailLabel}>Server Link:</span>
230+
<span className={cls(styles.detailContent, styles.link)}>{server.serverHost}</span>
231+
</div>
232+
</div>
233+
)}
206234
</div>
207235
))}
208236
</div>

packages/ai-native/src/browser/mcp/mcp-server-proxy.service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export class MCPServerProxyService implements IMCPServerProxyService {
2828
}
2929

3030
// 获取 OpenSumi 内部注册的 MCP tools
31-
async $getMCPTools() {
31+
async $getBuiltinMCPTools() {
3232
const tools = await this.mcpServerRegistry.getMCPTools().map((tool) =>
3333
// 不要传递 handler
3434
({

packages/ai-native/src/browser/types.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ import { SumiReadableStream } from '@opensumi/ide-utils/lib/stream';
2727
import { IMarker } from '@opensumi/monaco-editor-core/esm/vs/platform/markers/common/markers';
2828

2929
import { IChatWelcomeMessageContent, ISampleQuestions, ITerminalCommandSuggestionDesc } from '../common';
30-
import { SerializedContext } from '../common/llm-context';
3130

3231
import {
3332
ICodeEditsContextBean,

packages/ai-native/src/common/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export interface IMCPServerProxyService {
3131
isError?: boolean;
3232
}>;
3333
// 获取 browser 层注册的 MCP 工具列表 (Browser tab 维度)
34-
$getMCPTools(): Promise<MCPTool[]>;
34+
$getBuiltinMCPTools(): Promise<MCPTool[]>;
3535
// 通知前端 MCP 服务注册表发生了变化
3636
$updateMCPServers(): Promise<void>;
3737
// 获取所有 MCP 服务器列表
@@ -52,6 +52,7 @@ export interface MCPTool {
5252
export enum MCP_SERVER_TYPE {
5353
STDIO = 'stdio',
5454
SSE = 'sse',
55+
BUILTIN = 'builtin',
5556
}
5657

5758
export interface CodeBlockData {

packages/ai-native/src/node/mcp-server.sse.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
// have to import with extension since the exports map is ./* -> ./dist/cjs/*
22
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
33
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
4+
import { EventSource } from 'eventsource';
45

56
import { ILogger } from '@opensumi/ide-core-common';
67

78
import pkg from '../../package.json';
89
import { IMCPServer } from '../common/mcp-server-manager';
910

11+
global.EventSource = EventSource as any;
1012
export class SSEMCPServer implements IMCPServer {
1113
private name: string;
1214
public serverHost: string;
@@ -32,7 +34,9 @@ export class SSEMCPServer implements IMCPServer {
3234
}
3335
this.logger?.log(`Starting server "${this.name}" with serverHost: ${this.serverHost}`);
3436

35-
const transport = new SSEClientTransport(new URL(this.serverHost));
37+
const transport = new SSEClientTransport(new URL(this.serverHost), {
38+
eventSourceInit: {},
39+
});
3640
transport.onerror = (error) => {
3741
this.logger?.error('Transport Error:', error);
3842
};
@@ -74,7 +78,9 @@ export class SSEMCPServer implements IMCPServer {
7478
}
7579

7680
async getTools() {
77-
return await this.client.listTools();
81+
const tools = await this.client.listTools();
82+
this.logger?.log(`Got tools from MCP server "${this.name}" with serverHost "${this.serverHost}":`, tools);
83+
return tools;
7884
}
7985

8086
update(serverHost: string): void {

packages/ai-native/src/node/mcp/sumi-mcp-server.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,15 @@ import { RPCService } from '@opensumi/ide-connection';
1010
import { ILogger } from '@opensumi/ide-core-common';
1111
import { INodeLogger } from '@opensumi/ide-core-node';
1212

13+
import pkg from '../../../package.json';
1314
import { BUILTIN_MCP_SERVER_NAME, ISumiMCPServerBackend } from '../../common';
1415
import { IMCPServer, MCPServerDescription } from '../../common/mcp-server-manager';
1516
import { IToolInvocationRegistryManager, ToolInvocationRegistryManager } from '../../common/tool-invocation-registry';
1617
import { IMCPServerProxyService, MCPTool, MCP_SERVER_TYPE } from '../../common/types';
1718
import { MCPServerManagerImpl } from '../mcp-server-manager-impl';
1819
import { SSEMCPServer } from '../mcp-server.sse';
1920
import { StdioMCPServer } from '../mcp-server.stdio';
21+
2022
// 每个 BrowserTab 都对应了一个 SumiMCPServerBackend 实例
2123
// SumiMCPServerBackend 需要做的事情:
2224
// 维护 Browser 端工具的注册和调用
@@ -49,12 +51,12 @@ export class SumiMCPServerBackend extends RPCService<IMCPServerProxyService> imp
4951
this.mcpServerManager.setClientId(clientId);
5052
}
5153

52-
async getMCPTools() {
54+
async $getMCPTools() {
5355
if (!this.client) {
5456
throw new Error('SUMI MCP RPC Client not initialized');
5557
}
56-
// 获取 MCP 工具
57-
const tools = await this.client.$getMCPTools();
58+
// 获取 MCP 工具,这里的 client 为 MCPServerProxyService
59+
const tools = await this.client.$getBuiltinMCPTools();
5860
return tools;
5961
}
6062

@@ -98,7 +100,7 @@ export class SumiMCPServerBackend extends RPCService<IMCPServerProxyService> imp
98100
this.server = new Server(
99101
{
100102
name: 'sumi-ide-mcp-server',
101-
version: '0.2.0',
103+
version: pkg.version,
102104
},
103105
{
104106
capabilities: {
@@ -108,8 +110,8 @@ export class SumiMCPServerBackend extends RPCService<IMCPServerProxyService> imp
108110
);
109111

110112
// 设置工具列表请求处理器
111-
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
112-
const tools = await this.getMCPTools();
113+
this.server.setRequestHandler(ListToolsRequestSchema, async (event) => {
114+
const tools = await this.$getMCPTools();
113115
return { tools };
114116
});
115117

@@ -138,6 +140,7 @@ export class SumiMCPServerBackend extends RPCService<IMCPServerProxyService> imp
138140
if (server.isStarted()) {
139141
// 只获取正在运行的 MCP Server 的工具列表
140142
const toolsResponse = await server.getTools();
143+
this.logger.log(`Server ${serverName} tools:`, toolsResponse.tools);
141144
toolNames = toolsResponse.tools.map((tool) => tool.name);
142145
}
143146

@@ -146,7 +149,7 @@ export class SumiMCPServerBackend extends RPCService<IMCPServerProxyService> imp
146149
return {
147150
name: server.getServerName(),
148151
isStarted: server.isStarted(),
149-
type: 'builtin rpc',
152+
type: MCP_SERVER_TYPE.BUILTIN,
150153
tools: toolNames,
151154
};
152155
}
@@ -166,6 +169,7 @@ export class SumiMCPServerBackend extends RPCService<IMCPServerProxyService> imp
166169
isStarted: server.isStarted(),
167170
type: MCP_SERVER_TYPE.SSE,
168171
serverHost: server.serverHost,
172+
tools: toolNames,
169173
};
170174
}
171175

@@ -245,7 +249,7 @@ export class BuiltinMCPServer implements IMCPServer {
245249
if (!this.started) {
246250
throw new Error('MCP Server not started');
247251
}
248-
const tools = await this.sumiMCPServer.getMCPTools();
252+
const tools = await this.sumiMCPServer.$getMCPTools();
249253
return { tools } as any;
250254
}
251255

packages/i18n/src/common/en-US.lang.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1603,10 +1603,13 @@ export const localizationBundle = {
16031603
'ai.native.mcp.buttonCancel': 'Cancel',
16041604
'ai.native.mcp.addMCPServer.title': 'Add MCP Server',
16051605
'ai.native.mcp.editMCPServer.title': 'Edit MCP Server',
1606-
'ai.native.mcp.serverHost.placeHolder': 'Enter Server Host',
1607-
'ai.native.mcp.command.placeHolder': 'Enter Command',
1608-
'ai.native.mcp.args.placeHolder': 'Enter Arguments',
1606+
'ai.native.mcp.serverHost.placeHolder': 'URL to SSE endpoint',
1607+
'ai.native.mcp.command.placeHolder': 'Command to execute',
1608+
'ai.native.mcp.args.placeHolder': 'Arguments',
16091609
'ai.native.mcp.env.placeHolder': 'KEY=value (one per line)',
1610-
'ai.native.mcp.name.placeHolder': 'Enter Server Name',
1610+
'ai.native.mcp.name.placeHolder': 'Server Name',
1611+
'ai.native.mcp.type.builtin': 'Built-in',
1612+
'ai.native.mcp.type.sse': 'SSE',
1613+
'ai.native.mcp.type.stdio': 'Command',
16111614
},
16121615
};

packages/i18n/src/common/zh-CN.lang.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1366,10 +1366,13 @@ export const localizationBundle = {
13661366
'ai.native.mcp.buttonCancel': '取消',
13671367
'ai.native.mcp.addMCPServer.title': '添加 MCP 服务器',
13681368
'ai.native.mcp.editMCPServer.title': '编辑 MCP 服务器',
1369-
'ai.native.mcp.serverHost.placeHolder': '输入服务器主机',
1369+
'ai.native.mcp.serverHost.placeHolder': '输入 SSE URL',
13701370
'ai.native.mcp.command.placeHolder': '输入命令',
1371-
'ai.native.mcp.args.placeHolder': '输入参数',
1371+
'ai.native.mcp.args.placeHolder': '运行参数',
13721372
'ai.native.mcp.env.placeHolder': 'KEY=value(每行一个)',
1373-
'ai.native.mcp.name.placeHolder': '输入服务器名称',
1373+
'ai.native.mcp.name.placeHolder': '服务器名称',
1374+
'ai.native.mcp.type.builtin': '内置',
1375+
'ai.native.mcp.type.sse': 'SSE',
1376+
'ai.native.mcp.type.stdio': '命令',
13741377
},
13751378
};

0 commit comments

Comments
 (0)