@opensumi/ide-ai-native 3.8.1-next-1741262660.0 → 3.8.1-next-1741331921.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/lib/browser/components/ChatInput.js +1 -1
  2. package/lib/browser/components/ChatInput.js.map +1 -1
  3. package/lib/browser/mcp/config/components/mcp-config.module.less +13 -0
  4. package/lib/browser/mcp/config/components/mcp-config.view.d.ts.map +1 -1
  5. package/lib/browser/mcp/config/components/mcp-config.view.js +46 -21
  6. package/lib/browser/mcp/config/components/mcp-config.view.js.map +1 -1
  7. package/lib/browser/mcp/config/components/mcp-server-form.d.ts +8 -4
  8. package/lib/browser/mcp/config/components/mcp-server-form.d.ts.map +1 -1
  9. package/lib/browser/mcp/config/components/mcp-server-form.js +113 -38
  10. package/lib/browser/mcp/config/components/mcp-server-form.js.map +1 -1
  11. package/lib/browser/mcp/config/components/mcp-server-form.module.less +24 -1
  12. package/lib/browser/mcp/mcp-server-proxy.service.d.ts +1 -1
  13. package/lib/browser/mcp/mcp-server-proxy.service.d.ts.map +1 -1
  14. package/lib/browser/mcp/mcp-server-proxy.service.js +1 -1
  15. package/lib/browser/mcp/mcp-server-proxy.service.js.map +1 -1
  16. package/lib/browser/types.d.ts.map +1 -1
  17. package/lib/browser/types.js.map +1 -1
  18. package/lib/common/index.d.ts +2 -0
  19. package/lib/common/index.d.ts.map +1 -1
  20. package/lib/common/index.js.map +1 -1
  21. package/lib/common/mcp-server-manager.d.ts +21 -3
  22. package/lib/common/mcp-server-manager.d.ts.map +1 -1
  23. package/lib/common/mcp-server-manager.js.map +1 -1
  24. package/lib/common/types.d.ts +14 -1
  25. package/lib/common/types.d.ts.map +1 -1
  26. package/lib/common/types.js +7 -1
  27. package/lib/common/types.js.map +1 -1
  28. package/lib/node/mcp/sumi-mcp-server.d.ts +14 -3
  29. package/lib/node/mcp/sumi-mcp-server.d.ts.map +1 -1
  30. package/lib/node/mcp/sumi-mcp-server.js +30 -12
  31. package/lib/node/mcp/sumi-mcp-server.js.map +1 -1
  32. package/lib/node/mcp-server-manager-impl.d.ts.map +1 -1
  33. package/lib/node/mcp-server-manager-impl.js +22 -8
  34. package/lib/node/mcp-server-manager-impl.js.map +1 -1
  35. package/lib/node/mcp-server.sse.d.ts +192 -0
  36. package/lib/node/mcp-server.sse.d.ts.map +1 -0
  37. package/lib/node/mcp-server.sse.js +93 -0
  38. package/lib/node/mcp-server.sse.js.map +1 -0
  39. package/lib/node/{mcp-server.d.ts → mcp-server.stdio.d.ts} +2 -2
  40. package/lib/node/mcp-server.stdio.d.ts.map +1 -0
  41. package/lib/node/{mcp-server.js → mcp-server.stdio.js} +8 -6
  42. package/lib/node/mcp-server.stdio.js.map +1 -0
  43. package/package.json +24 -23
  44. package/src/browser/components/ChatInput.tsx +1 -1
  45. package/src/browser/mcp/config/components/mcp-config.module.less +13 -0
  46. package/src/browser/mcp/config/components/mcp-config.view.tsx +128 -91
  47. package/src/browser/mcp/config/components/mcp-server-form.module.less +24 -1
  48. package/src/browser/mcp/config/components/mcp-server-form.tsx +188 -70
  49. package/src/browser/mcp/mcp-server-proxy.service.ts +1 -1
  50. package/src/browser/types.ts +0 -1
  51. package/src/common/index.ts +2 -0
  52. package/src/common/mcp-server-manager.ts +22 -3
  53. package/src/common/types.ts +16 -1
  54. package/src/node/mcp/sumi-mcp-server.ts +31 -14
  55. package/src/node/mcp-server-manager-impl.ts +20 -9
  56. package/src/node/mcp-server.sse.ts +103 -0
  57. package/src/node/{mcp-server.ts → mcp-server.stdio.ts} +4 -3
  58. package/lib/node/mcp-server.d.ts.map +0 -1
  59. package/lib/node/mcp-server.js.map +0 -1
@@ -1,32 +1,29 @@
1
- import React from 'react';
1
+ import cls from 'classnames';
2
+ import React, { useCallback } from 'react';
2
3
 
4
+ import { Badge } from '@opensumi/ide-components';
3
5
  import { AINativeSettingSectionsId, ILogger, useInjectable } from '@opensumi/ide-core-browser';
4
6
  import { PreferenceService } from '@opensumi/ide-core-browser/lib/preferences';
7
+ import { localize } from '@opensumi/ide-core-common';
5
8
 
6
- import { BUILTIN_MCP_SERVER_NAME } from '../../../../common';
9
+ import { BUILTIN_MCP_SERVER_NAME, ISumiMCPServerBackend, SumiMCPServerProxyServicePath } from '../../../../common';
7
10
  import { MCPServerDescription } from '../../../../common/mcp-server-manager';
11
+ import { MCPServer, MCP_SERVER_TYPE } from '../../../../common/types';
8
12
  import { MCPServerProxyService } from '../../mcp-server-proxy.service';
9
13
 
10
14
  import styles from './mcp-config.module.less';
11
15
  import { MCPServerForm, MCPServerFormData } from './mcp-server-form';
12
16
 
13
- interface MCPServer {
14
- name: string;
15
- isStarted: boolean;
16
- tools?: string[];
17
- command?: string;
18
- type?: string;
19
- }
20
-
21
17
  export const MCPConfigView: React.FC = () => {
22
18
  const mcpServerProxyService = useInjectable<MCPServerProxyService>(MCPServerProxyService);
23
19
  const preferenceService = useInjectable<PreferenceService>(PreferenceService);
20
+ const sumiMCPServerBackendProxy = useInjectable<ISumiMCPServerBackend>(SumiMCPServerProxyServicePath);
24
21
  const logger = useInjectable<ILogger>(ILogger);
25
22
  const [servers, setServers] = React.useState<MCPServer[]>([]);
26
23
  const [formVisible, setFormVisible] = React.useState(false);
27
24
  const [editingServer, setEditingServer] = React.useState<MCPServerFormData | undefined>();
28
25
 
29
- const loadServers = React.useCallback(async () => {
26
+ const loadServers = useCallback(async () => {
30
27
  const allServers = await mcpServerProxyService.$getServers();
31
28
  setServers(allServers);
32
29
  }, [mcpServerProxyService]);
@@ -40,96 +37,123 @@ export const MCPConfigView: React.FC = () => {
40
37
  return () => {
41
38
  disposer.dispose();
42
39
  };
43
- }, [mcpServerProxyService, loadServers]);
40
+ }, []);
44
41
 
45
- const handleServerControl = async (serverName: string, start: boolean) => {
46
- try {
47
- if (start) {
48
- await mcpServerProxyService.$startServer(serverName);
49
- } else {
50
- await mcpServerProxyService.$stopServer(serverName);
51
- }
42
+ const handleServerControl = useCallback(
43
+ async (serverName: string, start: boolean) => {
44
+ try {
45
+ if (start) {
46
+ await mcpServerProxyService.$startServer(serverName);
47
+ } else {
48
+ await mcpServerProxyService.$stopServer(serverName);
49
+ }
52
50
 
53
- // Update enabled state in preferences
54
- const servers = preferenceService.get<MCPServerDescription[]>(AINativeSettingSectionsId.MCPServers, []);
55
- let updatedServers = servers;
56
-
57
- // 处理内置服务器的特殊情况
58
- if (serverName === BUILTIN_MCP_SERVER_NAME) {
59
- const builtinServerExists = servers.some((server) => server.name === BUILTIN_MCP_SERVER_NAME);
60
- if (!builtinServerExists && !start) {
61
- // 如果是停止内置服务器且之前没有配置,添加一个新的配置项
62
- // 内置服务器不需要 command,因为它是直接集成在 IDE 中的
63
- updatedServers = [
64
- ...servers,
65
- {
66
- name: BUILTIN_MCP_SERVER_NAME,
67
- enabled: false,
68
- command: '', // 内置服务器的 command 为空字符串
69
- },
70
- ];
51
+ // Update enabled state in preferences
52
+ const servers = preferenceService.get<MCPServerDescription[]>(AINativeSettingSectionsId.MCPServers, []);
53
+ let updatedServers = servers;
54
+ // 处理内置服务器的特殊情况
55
+ if (serverName === BUILTIN_MCP_SERVER_NAME) {
56
+ const builtinServerExists = servers.some((server) => server.name === BUILTIN_MCP_SERVER_NAME);
57
+ if (!builtinServerExists && !start) {
58
+ // 如果是停止内置服务器且之前没有配置,添加一个新的配置项
59
+ // 内置服务器不需要 command,因为它是直接集成在 IDE 中的
60
+ updatedServers = [
61
+ ...servers,
62
+ {
63
+ name: BUILTIN_MCP_SERVER_NAME,
64
+ enabled: false,
65
+ command: '', // 内置服务器的 command 为空字符串
66
+ type: MCP_SERVER_TYPE.STDIO,
67
+ },
68
+ ];
69
+ } else {
70
+ // 如果已经存在配置,更新 enabled 状态
71
+ updatedServers = servers.map((server) => {
72
+ if (server.name === BUILTIN_MCP_SERVER_NAME) {
73
+ return { ...server, enabled: start };
74
+ }
75
+ return server;
76
+ });
77
+ }
71
78
  } else {
72
- // 如果已经存在配置,更新 enabled 状态
79
+ // 处理其他外部服务器
73
80
  updatedServers = servers.map((server) => {
74
- if (server.name === BUILTIN_MCP_SERVER_NAME) {
81
+ if (server.name === serverName) {
75
82
  return { ...server, enabled: start };
76
83
  }
77
84
  return server;
78
85
  });
79
86
  }
80
- } else {
81
- // 处理其他外部服务器
82
- updatedServers = servers.map((server) => {
83
- if (server.name === serverName) {
84
- return { ...server, enabled: start };
85
- }
86
- return server;
87
- });
87
+
88
+ await preferenceService.set(AINativeSettingSectionsId.MCPServers, updatedServers);
89
+ await loadServers();
90
+ } catch (error) {
91
+ logger.error(`Failed to ${start ? 'start' : 'stop'} server ${serverName}:`, error);
88
92
  }
93
+ },
94
+ [mcpServerProxyService, preferenceService, sumiMCPServerBackendProxy, loadServers],
95
+ );
96
+
97
+ const handleAddServer = useCallback(() => {
98
+ setEditingServer(undefined);
99
+ setFormVisible(true);
100
+ }, [editingServer, formVisible]);
101
+
102
+ const handleEditServer = useCallback(
103
+ (server: MCPServer) => {
104
+ const servers = preferenceService.get<MCPServerFormData[]>(AINativeSettingSectionsId.MCPServers, []);
105
+ const serverConfig = servers.find((s) => s.name === server.name);
106
+
107
+ if (serverConfig) {
108
+ setEditingServer(serverConfig);
109
+ setFormVisible(true);
110
+ }
111
+ },
112
+ [editingServer, formVisible],
113
+ );
89
114
 
115
+ const handleDeleteServer = useCallback(
116
+ async (serverName: string) => {
117
+ const servers = preferenceService.get<MCPServerFormData[]>(AINativeSettingSectionsId.MCPServers, []);
118
+ const updatedServers = servers.filter((s) => s.name !== serverName);
119
+ sumiMCPServerBackendProxy.removeServer(serverName);
90
120
  await preferenceService.set(AINativeSettingSectionsId.MCPServers, updatedServers);
91
121
  await loadServers();
92
- } catch (error) {
93
- logger.error(`Failed to ${start ? 'start' : 'stop'} server ${serverName}:`, error);
94
- }
95
- };
122
+ },
123
+ [editingServer, formVisible],
124
+ );
96
125
 
97
- const handleAddServer = () => {
98
- setEditingServer(undefined);
99
- setFormVisible(true);
100
- };
126
+ const handleSaveServer = useCallback(
127
+ async (data: MCPServerFormData) => {
128
+ const servers = preferenceService.get<MCPServerFormData[]>(AINativeSettingSectionsId.MCPServers, []);
129
+ const existingIndex = servers.findIndex((s) => s.name === data.name);
101
130
 
102
- const handleEditServer = (server: MCPServer) => {
103
- const servers = preferenceService.get<MCPServerFormData[]>(AINativeSettingSectionsId.MCPServers, []);
104
- const serverConfig = servers.find((s) => s.name === server.name);
131
+ if (existingIndex >= 0) {
132
+ servers[existingIndex] = data;
133
+ } else {
134
+ servers.push(data);
135
+ }
136
+ setServers(servers as MCPServer[]);
137
+ setFormVisible(false);
138
+ await sumiMCPServerBackendProxy.addOrUpdateServer(data as MCPServerDescription);
139
+ await preferenceService.set(AINativeSettingSectionsId.MCPServers, servers);
140
+ await loadServers();
141
+ },
142
+ [servers, formVisible, loadServers],
143
+ );
105
144
 
106
- if (serverConfig) {
107
- setEditingServer(serverConfig);
108
- setFormVisible(true);
109
- }
110
- };
111
-
112
- const handleDeleteServer = async (serverName: string) => {
113
- const servers = preferenceService.get<MCPServerFormData[]>(AINativeSettingSectionsId.MCPServers, []);
114
- const updatedServers = servers.filter((s) => s.name !== serverName);
115
- await preferenceService.set(AINativeSettingSectionsId.MCPServers, updatedServers);
116
- await loadServers();
117
- };
118
-
119
- const handleSaveServer = async (data: MCPServerFormData) => {
120
- const servers = preferenceService.get<MCPServerFormData[]>(AINativeSettingSectionsId.MCPServers, []);
121
- const existingIndex = servers.findIndex((s) => s.name === data.name);
122
-
123
- if (existingIndex >= 0) {
124
- servers[existingIndex] = data;
125
- } else {
126
- servers.push(data);
145
+ const getReadableServerType = useCallback((type: string) => {
146
+ switch (type) {
147
+ case MCP_SERVER_TYPE.STDIO:
148
+ return localize('ai.native.mcp.type.stdio');
149
+ case MCP_SERVER_TYPE.SSE:
150
+ return localize('ai.native.mcp.type.sse');
151
+ case MCP_SERVER_TYPE.BUILTIN:
152
+ return localize('ai.native.mcp.type.builtin');
153
+ default:
154
+ return type;
127
155
  }
128
-
129
- await preferenceService.set(AINativeSettingSectionsId.MCPServers, servers);
130
- setFormVisible(false);
131
- await loadServers();
132
- };
156
+ }, []);
133
157
 
134
158
  return (
135
159
  <div className={styles.container}>
@@ -150,9 +174,11 @@ export const MCPConfigView: React.FC = () => {
150
174
  <h3 className={styles.serverName}>{server.name}</h3>
151
175
  </div>
152
176
  <div className={styles.serverActions}>
153
- <button className={styles.iconButton} title='Edit' onClick={() => handleEditServer(server)}>
154
- <i className='codicon codicon-edit' />
155
- </button>
177
+ {server.name !== BUILTIN_MCP_SERVER_NAME && (
178
+ <button className={styles.iconButton} title='Edit' onClick={() => handleEditServer(server)}>
179
+ <i className='codicon codicon-edit' />
180
+ </button>
181
+ )}
156
182
  <button
157
183
  className={styles.iconButton}
158
184
  title={server.isStarted ? 'Stop' : 'Start'}
@@ -160,9 +186,11 @@ export const MCPConfigView: React.FC = () => {
160
186
  >
161
187
  <i className={`codicon ${server.isStarted ? 'codicon-debug-stop' : 'codicon-debug-start'}`} />
162
188
  </button>
163
- <button className={styles.iconButton} title='Delete' onClick={() => handleDeleteServer(server.name)}>
164
- <i className='codicon codicon-trash' />
165
- </button>
189
+ {server.name !== BUILTIN_MCP_SERVER_NAME && (
190
+ <button className={styles.iconButton} title='Delete' onClick={() => handleDeleteServer(server.name)}>
191
+ <i className='codicon codicon-trash' />
192
+ </button>
193
+ )}
166
194
  </div>
167
195
  </div>
168
196
  <div className={styles.serverDetail}>
@@ -175,7 +203,7 @@ export const MCPConfigView: React.FC = () => {
175
203
  {server.type && (
176
204
  <div className={styles.detailRow}>
177
205
  <span className={styles.detailLabel}>Type:</span>
178
- <span className={styles.serverType}>{server.type}</span>
206
+ <Badge className={cls(styles.serverType, styles.typeTag)}>{getReadableServerType(server.type)}</Badge>
179
207
  </div>
180
208
  )}
181
209
  </div>
@@ -201,12 +229,21 @@ export const MCPConfigView: React.FC = () => {
201
229
  </div>
202
230
  </div>
203
231
  )}
232
+ {server.serverHost && (
233
+ <div className={styles.serverDetail}>
234
+ <div className={styles.detailRow}>
235
+ <span className={styles.detailLabel}>Server Link:</span>
236
+ <span className={cls(styles.detailContent, styles.link)}>{server.serverHost}</span>
237
+ </div>
238
+ </div>
239
+ )}
204
240
  </div>
205
241
  ))}
206
242
  </div>
207
243
  <MCPServerForm
208
244
  visible={formVisible}
209
245
  initialData={editingServer}
246
+ servers={servers}
210
247
  onSave={handleSaveServer}
211
248
  onCancel={() => setFormVisible(false)}
212
249
  />
@@ -44,11 +44,34 @@
44
44
  display: flex;
45
45
  justify-content: flex-end;
46
46
  gap: 8px;
47
- margin-top: 24px;
48
47
  padding-top: 16px;
49
48
  border-top: 1px solid var(--border-color);
50
49
  }
51
50
 
51
+ .formRow {
52
+ display: flex;
53
+ gap: 16px;
54
+ flex-direction: row;
55
+ align-items: center;
56
+ justify-content: flex-start;
57
+ }
58
+
59
+ .formItemName {
60
+ flex: 1;
61
+ }
62
+
63
+ .formItemType {
64
+ flex-grow: 0;
65
+ min-width: 120px;
66
+ }
67
+
68
+ .formItemSelect {
69
+ height: 37.5px;
70
+ :global(.kt-select-value-large) {
71
+ height: 100% !important;
72
+ }
73
+ }
74
+
52
75
  .cancelButton {
53
76
  padding: 6px 12px;
54
77
  border: 1px solid var(--button-border);
@@ -1,36 +1,45 @@
1
- import React from 'react';
1
+ import cls from 'classnames';
2
+ import React, { ChangeEvent, FC, FormEvent, useCallback, useEffect, useState } from 'react';
2
3
 
4
+ import { Select } from '@opensumi/ide-components';
3
5
  import { Button } from '@opensumi/ide-components/lib/button';
4
6
  import { Modal } from '@opensumi/ide-components/lib/modal';
7
+ import { useInjectable } from '@opensumi/ide-core-browser';
8
+ import { formatLocalize, localize } from '@opensumi/ide-core-common';
9
+ import { IMessageService } from '@opensumi/ide-overlay';
5
10
 
6
- import styles from './mcp-server-form.module.less';
11
+ import { MCPServer, MCP_SERVER_TYPE } from '../../../../common/types';
7
12
 
13
+ import styles from './mcp-server-form.module.less';
8
14
  export interface MCPServerFormData {
9
15
  name: string;
10
- command: string;
11
- args: string[];
16
+ command?: string;
17
+ args?: string[];
12
18
  env?: Record<string, string>;
19
+ type: MCP_SERVER_TYPE;
20
+ serverHost?: string;
13
21
  }
14
22
 
15
23
  interface Props {
16
24
  visible: boolean;
17
25
  initialData?: MCPServerFormData;
26
+ servers: MCPServer[];
18
27
  onSave: (data: MCPServerFormData) => void;
19
28
  onCancel: () => void;
20
29
  }
21
30
 
22
- export const MCPServerForm: React.FC<Props> = ({ visible, initialData, onSave, onCancel }) => {
23
- const [formData, setFormData] = React.useState<MCPServerFormData>(() => ({
31
+ export const MCPServerForm: FC<Props> = ({ visible, initialData, onSave, onCancel, servers: existingServers }) => {
32
+ const [formData, setFormData] = useState<MCPServerFormData>(() => ({
24
33
  name: '',
25
34
  command: '',
26
35
  args: [],
27
36
  env: {},
28
- type: 'stdio', // TODO: 支持 SSE
37
+ type: MCP_SERVER_TYPE.STDIO,
29
38
  ...initialData,
30
39
  }));
31
-
32
- const [argsText, setArgsText] = React.useState(() => initialData?.args?.join(' ') || '');
33
- const [envText, setEnvText] = React.useState(() => {
40
+ const messageService = useInjectable<IMessageService>(IMessageService);
41
+ const [argsText, setArgsText] = useState(() => initialData?.args?.join(' ') || '');
42
+ const [envText, setEnvText] = useState(() => {
34
43
  if (!initialData?.env) {
35
44
  return '';
36
45
  }
@@ -39,13 +48,13 @@ export const MCPServerForm: React.FC<Props> = ({ visible, initialData, onSave, o
39
48
  .join('\n');
40
49
  });
41
50
 
42
- // Update form data when initialData changes
43
- React.useEffect(() => {
51
+ useEffect(() => {
44
52
  setFormData({
45
53
  name: '',
46
54
  command: '',
47
55
  args: [],
48
56
  env: {},
57
+ type: MCP_SERVER_TYPE.STDIO,
49
58
  ...initialData,
50
59
  });
51
60
  setArgsText(initialData?.args?.join(' ') || '');
@@ -58,30 +67,151 @@ export const MCPServerForm: React.FC<Props> = ({ visible, initialData, onSave, o
58
67
  );
59
68
  }, [initialData]);
60
69
 
61
- const handleSubmit = (e: React.FormEvent) => {
70
+ const handleSubmit = (e: FormEvent) => {
62
71
  e.preventDefault();
63
- const args = argsText.split(' ').filter(Boolean);
64
- const env = envText
65
- .split('\n')
66
- .filter(Boolean)
67
- .reduce((acc, line) => {
68
- const [key, value] = line.split('=');
69
- if (key && value) {
70
- acc[key.trim()] = value.trim();
71
- }
72
- return acc;
73
- }, {} as Record<string, string>);
74
-
75
- onSave({
72
+ const isValid = validateForm(formData);
73
+ if (!isValid) {
74
+ return;
75
+ }
76
+ const form = {
76
77
  ...formData,
77
- args,
78
- env,
79
- });
78
+ };
79
+ if (formData.type === MCP_SERVER_TYPE.SSE) {
80
+ form.serverHost = form.serverHost?.trim();
81
+ } else {
82
+ const args = argsText.split(' ').filter(Boolean);
83
+ const env = envText
84
+ .split('\n')
85
+ .filter(Boolean)
86
+ .reduce((acc, line) => {
87
+ const [key, value] = line.split('=');
88
+ if (key && value) {
89
+ acc[key.trim()] = value.trim();
90
+ }
91
+ return acc;
92
+ }, {} as Record<string, string>);
93
+ form.args = args;
94
+ form.env = env;
95
+ }
96
+
97
+ onSave(form);
80
98
  };
81
99
 
100
+ const handleCommandChange = useCallback(
101
+ (e: ChangeEvent<HTMLInputElement>) => {
102
+ setFormData({ ...formData, command: e.target.value });
103
+ },
104
+ [formData, argsText],
105
+ );
106
+
107
+ const handleArgsChange = useCallback(
108
+ (e: ChangeEvent<HTMLTextAreaElement>) => {
109
+ setArgsText(e.target.value);
110
+ },
111
+ [argsText],
112
+ );
113
+
114
+ const handleEnvChange = useCallback(
115
+ (e: ChangeEvent<HTMLTextAreaElement>) => {
116
+ setEnvText(e.target.value);
117
+ },
118
+ [envText],
119
+ );
120
+
121
+ const handleServerHostChange = useCallback(
122
+ (e: ChangeEvent<HTMLTextAreaElement>) => {
123
+ setFormData({ ...formData, serverHost: e.target.value });
124
+ },
125
+ [formData],
126
+ );
127
+
128
+ const handleTypeChange = useCallback(
129
+ (value: MCP_SERVER_TYPE) => {
130
+ setFormData({ ...formData, type: value });
131
+ },
132
+ [formData],
133
+ );
134
+
135
+ const renderFormItems = useCallback(() => {
136
+ if (formData?.type === MCP_SERVER_TYPE.STDIO) {
137
+ return (
138
+ <>
139
+ <div className={styles.formItem}>
140
+ <label>{localize('ai.native.mcp.command')}</label>
141
+ <input
142
+ type='text'
143
+ value={formData.command}
144
+ onChange={handleCommandChange}
145
+ placeholder={localize('ai.native.mcp.command.placeHolder')}
146
+ required
147
+ />
148
+ </div>
149
+ <div className={styles.formItem}>
150
+ <label>{localize('ai.native.mcp.args')}</label>
151
+ <textarea
152
+ value={argsText}
153
+ onChange={handleArgsChange}
154
+ placeholder={localize('ai.native.mcp.args.placeHolder')}
155
+ rows={3}
156
+ />
157
+ </div>
158
+ <div className={styles.formItem}>
159
+ <label>{localize('ai.native.mcp.env')}</label>
160
+ <textarea
161
+ value={envText}
162
+ onChange={handleEnvChange}
163
+ placeholder={localize('ai.native.mcp.env.placeHolder')}
164
+ rows={3}
165
+ />
166
+ </div>
167
+ </>
168
+ );
169
+ } else {
170
+ return (
171
+ <>
172
+ <div className={styles.formItem}>
173
+ <label>{localize('ai.native.mcp.serverHost')}</label>
174
+ <textarea
175
+ value={formData.serverHost}
176
+ onChange={handleServerHostChange}
177
+ placeholder={localize('ai.native.mcp.serverHost.placeHolder')}
178
+ rows={3}
179
+ />
180
+ </div>
181
+ </>
182
+ );
183
+ }
184
+ }, [formData, argsText, envText]);
185
+
186
+ const validateForm = useCallback(
187
+ (formData: MCPServerFormData) => {
188
+ if (formData.name.trim() === '') {
189
+ messageService.error(localize('ai.native.mcp.name.isRequired'));
190
+ return false;
191
+ }
192
+ if (existingServers.some((server) => server.name.toLocaleLowerCase() === formData.name.toLocaleLowerCase())) {
193
+ messageService.error(formatLocalize('ai.native.mcp.serverNameExists', formData.name));
194
+ return false;
195
+ }
196
+ if (formData.type === MCP_SERVER_TYPE.SSE) {
197
+ const isServerHostValid = formData.serverHost?.trim() !== '';
198
+ if (!isServerHostValid) {
199
+ messageService.error(localize('ai.native.mcp.serverHost.isRequired'));
200
+ }
201
+ return isServerHostValid;
202
+ }
203
+ const isCommandValid = formData.command?.trim() !== '';
204
+ if (!isCommandValid) {
205
+ messageService.error(localize('ai.native.mcp.command.isRequired'));
206
+ }
207
+ return isCommandValid;
208
+ },
209
+ [existingServers],
210
+ );
211
+
82
212
  return (
83
213
  <Modal
84
- title={initialData ? 'Edit MCP Server' : 'Add New MCP Server'}
214
+ title={initialData ? localize('ai.native.mcp.editMCPServer.title') : localize('ai.native.mcp.addMCPServer.title')}
85
215
  visible={visible}
86
216
  onCancel={onCancel}
87
217
  centered
@@ -92,50 +222,38 @@ export const MCPServerForm: React.FC<Props> = ({ visible, initialData, onSave, o
92
222
  }}
93
223
  >
94
224
  <form className={styles.form} onSubmit={(e) => e.preventDefault()}>
95
- <div className={styles.formItem}>
96
- <label>Name:</label>
97
- <input
98
- type='text'
99
- value={formData.name}
100
- onChange={(e) => setFormData({ ...formData, name: e.target.value })}
101
- placeholder='Enter server name'
102
- required
103
- />
104
- </div>
105
- <div className={styles.formItem}>
106
- <label>Command:</label>
107
- <input
108
- type='text'
109
- value={formData.command}
110
- onChange={(e) => setFormData({ ...formData, command: e.target.value })}
111
- placeholder='Enter command (e.g., npx)'
112
- required
113
- />
114
- </div>
115
- <div className={styles.formItem}>
116
- <label>Arguments:</label>
117
- <textarea
118
- value={argsText}
119
- onChange={(e) => setArgsText(e.target.value)}
120
- placeholder='Enter arguments separated by space'
121
- rows={3}
122
- />
123
- </div>
124
- <div className={styles.formItem}>
125
- <label>Environment Variables:</label>
126
- <textarea
127
- value={envText}
128
- onChange={(e) => setEnvText(e.target.value)}
129
- placeholder='KEY=value (one per line)'
130
- rows={3}
131
- />
225
+ <div className={styles.formRow}>
226
+ <div className={cls(styles.formItem, styles.formItemName)}>
227
+ <label>{localize('ai.native.mcp.name')}</label>
228
+ <input
229
+ type='text'
230
+ value={formData.name}
231
+ onChange={(e) => setFormData({ ...formData, name: e.target.value })}
232
+ placeholder={localize('ai.native.mcp.name.placeHolder')}
233
+ required
234
+ />
235
+ </div>
236
+ <div className={cls(styles.formItem, styles.formItemType)}>
237
+ <label>{localize('ai.native.mcp.type')}</label>
238
+ <Select
239
+ size='large'
240
+ value={formData.type}
241
+ options={[
242
+ { label: localize('ai.native.mcp.stdio'), value: MCP_SERVER_TYPE.STDIO },
243
+ { label: localize('ai.native.mcp.sse'), value: MCP_SERVER_TYPE.SSE },
244
+ ]}
245
+ className={styles.formItemSelect}
246
+ onChange={handleTypeChange}
247
+ />
248
+ </div>
132
249
  </div>
250
+ {renderFormItems()}
133
251
  <div className={styles.formActions}>
134
- <Button onClick={onCancel} type='secondary'>
135
- Cancel
252
+ <Button onClick={onCancel} type='ghost'>
253
+ {localize('ai.native.mcp.buttonCancel')}
136
254
  </Button>
137
255
  <Button onClick={handleSubmit} type='primary'>
138
- Save
256
+ {initialData ? localize('ai.native.mcp.buttonUpdate') : localize('ai.native.mcp.buttonSave')}
139
257
  </Button>
140
258
  </div>
141
259
  </form>
@@ -28,7 +28,7 @@ export class MCPServerProxyService implements IMCPServerProxyService {
28
28
  }
29
29
 
30
30
  // 获取 OpenSumi 内部注册的 MCP tools
31
- async $getMCPTools() {
31
+ async $getBuiltinMCPTools() {
32
32
  const tools = await this.mcpServerRegistry.getMCPTools().map((tool) =>
33
33
  // 不要传递 handler
34
34
  ({
@@ -27,7 +27,6 @@ import { SumiReadableStream } from '@opensumi/ide-utils/lib/stream';
27
27
  import { IMarker } from '@opensumi/monaco-editor-core/esm/vs/platform/markers/common/markers';
28
28
 
29
29
  import { IChatWelcomeMessageContent, ISampleQuestions, ITerminalCommandSuggestionDesc } from '../common';
30
- import { SerializedContext } from '../common/llm-context';
31
30
 
32
31
  import {
33
32
  ICodeEditsContextBean,