@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.
- package/lib/browser/components/ChatInput.js +1 -1
- package/lib/browser/components/ChatInput.js.map +1 -1
- package/lib/browser/mcp/config/components/mcp-config.module.less +13 -0
- package/lib/browser/mcp/config/components/mcp-config.view.d.ts.map +1 -1
- package/lib/browser/mcp/config/components/mcp-config.view.js +46 -21
- package/lib/browser/mcp/config/components/mcp-config.view.js.map +1 -1
- package/lib/browser/mcp/config/components/mcp-server-form.d.ts +8 -4
- package/lib/browser/mcp/config/components/mcp-server-form.d.ts.map +1 -1
- package/lib/browser/mcp/config/components/mcp-server-form.js +113 -38
- package/lib/browser/mcp/config/components/mcp-server-form.js.map +1 -1
- package/lib/browser/mcp/config/components/mcp-server-form.module.less +24 -1
- package/lib/browser/mcp/mcp-server-proxy.service.d.ts +1 -1
- package/lib/browser/mcp/mcp-server-proxy.service.d.ts.map +1 -1
- package/lib/browser/mcp/mcp-server-proxy.service.js +1 -1
- package/lib/browser/mcp/mcp-server-proxy.service.js.map +1 -1
- package/lib/browser/types.d.ts.map +1 -1
- package/lib/browser/types.js.map +1 -1
- package/lib/common/index.d.ts +2 -0
- package/lib/common/index.d.ts.map +1 -1
- package/lib/common/index.js.map +1 -1
- package/lib/common/mcp-server-manager.d.ts +21 -3
- package/lib/common/mcp-server-manager.d.ts.map +1 -1
- package/lib/common/mcp-server-manager.js.map +1 -1
- package/lib/common/types.d.ts +14 -1
- package/lib/common/types.d.ts.map +1 -1
- package/lib/common/types.js +7 -1
- package/lib/common/types.js.map +1 -1
- package/lib/node/mcp/sumi-mcp-server.d.ts +14 -3
- package/lib/node/mcp/sumi-mcp-server.d.ts.map +1 -1
- package/lib/node/mcp/sumi-mcp-server.js +30 -12
- package/lib/node/mcp/sumi-mcp-server.js.map +1 -1
- package/lib/node/mcp-server-manager-impl.d.ts.map +1 -1
- package/lib/node/mcp-server-manager-impl.js +22 -8
- package/lib/node/mcp-server-manager-impl.js.map +1 -1
- package/lib/node/mcp-server.sse.d.ts +192 -0
- package/lib/node/mcp-server.sse.d.ts.map +1 -0
- package/lib/node/mcp-server.sse.js +93 -0
- package/lib/node/mcp-server.sse.js.map +1 -0
- package/lib/node/{mcp-server.d.ts → mcp-server.stdio.d.ts} +2 -2
- package/lib/node/mcp-server.stdio.d.ts.map +1 -0
- package/lib/node/{mcp-server.js → mcp-server.stdio.js} +8 -6
- package/lib/node/mcp-server.stdio.js.map +1 -0
- package/package.json +24 -23
- package/src/browser/components/ChatInput.tsx +1 -1
- package/src/browser/mcp/config/components/mcp-config.module.less +13 -0
- package/src/browser/mcp/config/components/mcp-config.view.tsx +128 -91
- package/src/browser/mcp/config/components/mcp-server-form.module.less +24 -1
- package/src/browser/mcp/config/components/mcp-server-form.tsx +188 -70
- package/src/browser/mcp/mcp-server-proxy.service.ts +1 -1
- package/src/browser/types.ts +0 -1
- package/src/common/index.ts +2 -0
- package/src/common/mcp-server-manager.ts +22 -3
- package/src/common/types.ts +16 -1
- package/src/node/mcp/sumi-mcp-server.ts +31 -14
- package/src/node/mcp-server-manager-impl.ts +20 -9
- package/src/node/mcp-server.sse.ts +103 -0
- package/src/node/{mcp-server.ts → mcp-server.stdio.ts} +4 -3
- package/lib/node/mcp-server.d.ts.map +0 -1
- package/lib/node/mcp-server.js.map +0 -1
|
@@ -1,32 +1,29 @@
|
|
|
1
|
-
import
|
|
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 =
|
|
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
|
-
}, [
|
|
40
|
+
}, []);
|
|
44
41
|
|
|
45
|
-
const handleServerControl =
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
//
|
|
79
|
+
// 处理其他外部服务器
|
|
73
80
|
updatedServers = servers.map((server) => {
|
|
74
|
-
if (server.name ===
|
|
81
|
+
if (server.name === serverName) {
|
|
75
82
|
return { ...server, enabled: start };
|
|
76
83
|
}
|
|
77
84
|
return server;
|
|
78
85
|
});
|
|
79
86
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
};
|
|
122
|
+
},
|
|
123
|
+
[editingServer, formVisible],
|
|
124
|
+
);
|
|
96
125
|
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
|
|
154
|
-
<
|
|
155
|
-
|
|
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
|
-
|
|
164
|
-
<
|
|
165
|
-
|
|
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
|
-
<
|
|
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
|
|
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
|
|
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
|
|
11
|
-
args
|
|
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:
|
|
23
|
-
const [formData, setFormData] =
|
|
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:
|
|
37
|
+
type: MCP_SERVER_TYPE.STDIO,
|
|
29
38
|
...initialData,
|
|
30
39
|
}));
|
|
31
|
-
|
|
32
|
-
const [argsText, setArgsText] =
|
|
33
|
-
const [envText, setEnvText] =
|
|
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
|
-
|
|
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:
|
|
70
|
+
const handleSubmit = (e: FormEvent) => {
|
|
62
71
|
e.preventDefault();
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
78
|
-
|
|
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 ? '
|
|
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.
|
|
96
|
-
<
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
<
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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='
|
|
135
|
-
|
|
252
|
+
<Button onClick={onCancel} type='ghost'>
|
|
253
|
+
{localize('ai.native.mcp.buttonCancel')}
|
|
136
254
|
</Button>
|
|
137
255
|
<Button onClick={handleSubmit} type='primary'>
|
|
138
|
-
|
|
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 $
|
|
31
|
+
async $getBuiltinMCPTools() {
|
|
32
32
|
const tools = await this.mcpServerRegistry.getMCPTools().map((tool) =>
|
|
33
33
|
// 不要传递 handler
|
|
34
34
|
({
|
package/src/browser/types.ts
CHANGED
|
@@ -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,
|