@opensumi/ide-ai-native 3.8.1-next-1740556231.0 → 3.8.1-next-1740625266.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/ai-core.contextkeys.d.ts +1 -1
- package/lib/browser/ai-core.contextkeys.d.ts.map +1 -1
- package/lib/browser/ai-core.contextkeys.js +1 -1
- package/lib/browser/ai-core.contextkeys.js.map +1 -1
- package/lib/browser/ai-core.contribution.d.ts.map +1 -1
- package/lib/browser/ai-core.contribution.js +10 -3
- package/lib/browser/ai-core.contribution.js.map +1 -1
- package/lib/browser/chat/chat.view.d.ts.map +1 -1
- package/lib/browser/chat/chat.view.js +30 -1
- package/lib/browser/chat/chat.view.js.map +1 -1
- package/lib/browser/contrib/inline-completions/inline-completions.controller.js +1 -1
- package/lib/browser/contrib/inline-completions/inline-completions.controller.js.map +1 -1
- package/lib/browser/contrib/intelligent-completions/index.d.ts +2 -1
- package/lib/browser/contrib/intelligent-completions/index.d.ts.map +1 -1
- package/lib/browser/contrib/intelligent-completions/index.js +4 -1
- package/lib/browser/contrib/intelligent-completions/index.js.map +1 -1
- package/lib/browser/contrib/intelligent-completions/intelligent-completions.contribution.js +2 -2
- package/lib/browser/contrib/intelligent-completions/intelligent-completions.contribution.js.map +1 -1
- package/lib/browser/contrib/intelligent-completions/intelligent-completions.controller.d.ts.map +1 -1
- package/lib/browser/contrib/intelligent-completions/intelligent-completions.controller.js +5 -4
- package/lib/browser/contrib/intelligent-completions/intelligent-completions.controller.js.map +1 -1
- package/lib/browser/contrib/intelligent-completions/view/code-edits-previewer.d.ts.map +1 -1
- package/lib/browser/contrib/intelligent-completions/view/code-edits-previewer.js +4 -2
- package/lib/browser/contrib/intelligent-completions/view/code-edits-previewer.js.map +1 -1
- package/lib/browser/contrib/intelligent-completions/view/default.d.ts.map +1 -1
- package/lib/browser/contrib/intelligent-completions/view/default.js +17 -11
- package/lib/browser/contrib/intelligent-completions/view/default.js.map +1 -1
- package/lib/browser/index.d.ts.map +1 -1
- package/lib/browser/index.js +4 -0
- package/lib/browser/index.js.map +1 -1
- package/lib/browser/mcp/config/components/mcp-config.module.less +178 -0
- package/lib/browser/mcp/config/components/mcp-config.view.d.ts +3 -0
- package/lib/browser/mcp/config/components/mcp-config.view.d.ts.map +1 -0
- package/lib/browser/mcp/config/components/mcp-config.view.js +150 -0
- package/lib/browser/mcp/config/components/mcp-config.view.js.map +1 -0
- package/lib/browser/mcp/config/components/mcp-server-form.d.ts +16 -0
- package/lib/browser/mcp/config/components/mcp-server-form.d.ts.map +1 -0
- package/lib/browser/mcp/config/components/mcp-server-form.js +84 -0
- package/lib/browser/mcp/config/components/mcp-server-form.js.map +1 -0
- package/lib/browser/mcp/config/components/mcp-server-form.module.less +78 -0
- package/lib/browser/mcp/config/mcp-config.commands.d.ts +10 -0
- package/lib/browser/mcp/config/mcp-config.commands.d.ts.map +1 -0
- package/lib/browser/mcp/config/mcp-config.commands.js +35 -0
- package/lib/browser/mcp/config/mcp-config.commands.js.map +1 -0
- package/lib/browser/mcp/config/mcp-config.contribution.d.ts +16 -0
- package/lib/browser/mcp/config/mcp-config.contribution.d.ts.map +1 -0
- package/lib/browser/mcp/config/mcp-config.contribution.js +62 -0
- package/lib/browser/mcp/config/mcp-config.contribution.js.map +1 -0
- package/lib/browser/mcp/mcp-server-proxy.service.d.ts +6 -0
- package/lib/browser/mcp/mcp-server-proxy.service.d.ts.map +1 -1
- package/lib/browser/mcp/mcp-server-proxy.service.js +10 -1
- package/lib/browser/mcp/mcp-server-proxy.service.js.map +1 -1
- package/lib/browser/mcp/mcp-server.feature.registry.d.ts.map +1 -1
- package/lib/browser/mcp/mcp-server.feature.registry.js +3 -2
- package/lib/browser/mcp/mcp-server.feature.registry.js.map +1 -1
- package/lib/browser/preferences/schema.d.ts.map +1 -1
- package/lib/browser/preferences/schema.js +16 -0
- package/lib/browser/preferences/schema.js.map +1 -1
- package/lib/common/index.d.ts +8 -1
- package/lib/common/index.d.ts.map +1 -1
- package/lib/common/index.js +3 -1
- package/lib/common/index.js.map +1 -1
- package/lib/common/mcp-server-manager.d.ts +17 -1
- package/lib/common/mcp-server-manager.d.ts.map +1 -1
- package/lib/common/mcp-server-manager.js.map +1 -1
- package/lib/common/tool-invocation-registry.d.ts +2 -2
- package/lib/common/tool-invocation-registry.d.ts.map +1 -1
- package/lib/common/tool-invocation-registry.js +1 -1
- package/lib/common/tool-invocation-registry.js.map +1 -1
- package/lib/common/types.d.ts +6 -0
- package/lib/common/types.d.ts.map +1 -1
- package/lib/common/utils.d.ts.map +1 -1
- package/lib/common/utils.js +2 -1
- package/lib/common/utils.js.map +1 -1
- package/lib/node/base-language-model.d.ts.map +1 -1
- package/lib/node/base-language-model.js +5 -4
- package/lib/node/base-language-model.js.map +1 -1
- package/lib/node/mcp/sumi-mcp-server.d.ts +17 -3
- package/lib/node/mcp/sumi-mcp-server.d.ts.map +1 -1
- package/lib/node/mcp/sumi-mcp-server.js +59 -6
- package/lib/node/mcp/sumi-mcp-server.js.map +1 -1
- package/lib/node/mcp-server-manager-impl.d.ts +4 -3
- package/lib/node/mcp-server-manager-impl.d.ts.map +1 -1
- package/lib/node/mcp-server-manager-impl.js +26 -6
- package/lib/node/mcp-server-manager-impl.js.map +1 -1
- package/lib/node/mcp-server.d.ts +5 -16
- package/lib/node/mcp-server.d.ts.map +1 -1
- package/lib/node/mcp-server.js +12 -6
- package/lib/node/mcp-server.js.map +1 -1
- package/lib/node/openai/openai-language-model.d.ts +4 -3
- package/lib/node/openai/openai-language-model.d.ts.map +1 -1
- package/lib/node/openai/openai-language-model.js +3 -2
- package/lib/node/openai/openai-language-model.js.map +1 -1
- package/package.json +27 -27
- package/src/browser/ai-core.contextkeys.ts +3 -3
- package/src/browser/ai-core.contribution.ts +13 -4
- package/src/browser/chat/chat.view.tsx +47 -0
- package/src/browser/contrib/inline-completions/inline-completions.controller.ts +1 -1
- package/src/browser/contrib/intelligent-completions/index.ts +5 -1
- package/src/browser/contrib/intelligent-completions/intelligent-completions.contribution.ts +3 -3
- package/src/browser/contrib/intelligent-completions/intelligent-completions.controller.ts +6 -5
- package/src/browser/contrib/intelligent-completions/view/code-edits-previewer.ts +4 -2
- package/src/browser/contrib/intelligent-completions/view/default.ts +27 -19
- package/src/browser/index.ts +4 -0
- package/src/browser/mcp/config/components/mcp-config.module.less +178 -0
- package/src/browser/mcp/config/components/mcp-config.view.tsx +215 -0
- package/src/browser/mcp/config/components/mcp-server-form.module.less +78 -0
- package/src/browser/mcp/config/components/mcp-server-form.tsx +144 -0
- package/src/browser/mcp/config/mcp-config.commands.ts +29 -0
- package/src/browser/mcp/config/mcp-config.contribution.ts +65 -0
- package/src/browser/mcp/mcp-server-proxy.service.ts +14 -2
- package/src/browser/mcp/mcp-server.feature.registry.ts +3 -2
- package/src/browser/preferences/schema.ts +16 -0
- package/src/common/index.ts +7 -1
- package/src/common/mcp-server-manager.ts +17 -1
- package/src/common/tool-invocation-registry.ts +2 -2
- package/src/common/types.ts +6 -0
- package/src/common/utils.ts +3 -1
- package/src/node/base-language-model.ts +7 -4
- package/src/node/mcp/sumi-mcp-server.ts +67 -9
- package/src/node/mcp-server-manager-impl.ts +30 -9
- package/src/node/mcp-server.ts +11 -14
- package/src/node/openai/openai-language-model.ts +7 -4
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import { AINativeSettingSectionsId, ILogger, useInjectable } from '@opensumi/ide-core-browser';
|
|
4
|
+
import { PreferenceService } from '@opensumi/ide-core-browser/lib/preferences';
|
|
5
|
+
|
|
6
|
+
import { BUILTIN_MCP_SERVER_NAME } from '../../../../common';
|
|
7
|
+
import { MCPServerDescription } from '../../../../common/mcp-server-manager';
|
|
8
|
+
import { MCPServerProxyService } from '../../mcp-server-proxy.service';
|
|
9
|
+
|
|
10
|
+
import styles from './mcp-config.module.less';
|
|
11
|
+
import { MCPServerForm, MCPServerFormData } from './mcp-server-form';
|
|
12
|
+
|
|
13
|
+
interface MCPServer {
|
|
14
|
+
name: string;
|
|
15
|
+
isStarted: boolean;
|
|
16
|
+
tools?: string[];
|
|
17
|
+
command?: string;
|
|
18
|
+
type?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const MCPConfigView: React.FC = () => {
|
|
22
|
+
const mcpServerProxyService = useInjectable<MCPServerProxyService>(MCPServerProxyService);
|
|
23
|
+
const preferenceService = useInjectable<PreferenceService>(PreferenceService);
|
|
24
|
+
const logger = useInjectable<ILogger>(ILogger);
|
|
25
|
+
const [servers, setServers] = React.useState<MCPServer[]>([]);
|
|
26
|
+
const [formVisible, setFormVisible] = React.useState(false);
|
|
27
|
+
const [editingServer, setEditingServer] = React.useState<MCPServerFormData | undefined>();
|
|
28
|
+
|
|
29
|
+
const loadServers = React.useCallback(async () => {
|
|
30
|
+
const allServers = await mcpServerProxyService.$getServers();
|
|
31
|
+
setServers(allServers);
|
|
32
|
+
}, [mcpServerProxyService]);
|
|
33
|
+
|
|
34
|
+
React.useEffect(() => {
|
|
35
|
+
loadServers();
|
|
36
|
+
const disposer = mcpServerProxyService.onChangeMCPServers(() => {
|
|
37
|
+
loadServers();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
return () => {
|
|
41
|
+
disposer.dispose();
|
|
42
|
+
};
|
|
43
|
+
}, [mcpServerProxyService, loadServers]);
|
|
44
|
+
|
|
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
|
+
}
|
|
52
|
+
|
|
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
|
+
];
|
|
71
|
+
} else {
|
|
72
|
+
// 如果已经存在配置,更新 enabled 状态
|
|
73
|
+
updatedServers = servers.map((server) => {
|
|
74
|
+
if (server.name === BUILTIN_MCP_SERVER_NAME) {
|
|
75
|
+
return { ...server, enabled: start };
|
|
76
|
+
}
|
|
77
|
+
return server;
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
} else {
|
|
81
|
+
// 处理其他外部服务器
|
|
82
|
+
updatedServers = servers.map((server) => {
|
|
83
|
+
if (server.name === serverName) {
|
|
84
|
+
return { ...server, enabled: start };
|
|
85
|
+
}
|
|
86
|
+
return server;
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
await preferenceService.set(AINativeSettingSectionsId.MCPServers, updatedServers);
|
|
91
|
+
await loadServers();
|
|
92
|
+
} catch (error) {
|
|
93
|
+
logger.error(`Failed to ${start ? 'start' : 'stop'} server ${serverName}:`, error);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const handleAddServer = () => {
|
|
98
|
+
setEditingServer(undefined);
|
|
99
|
+
setFormVisible(true);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const handleEditServer = (server: MCPServer) => {
|
|
103
|
+
const servers = preferenceService.get<MCPServerFormData[]>(AINativeSettingSectionsId.MCPServers, []);
|
|
104
|
+
const serverConfig = servers.find((s) => s.name === server.name);
|
|
105
|
+
|
|
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);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
await preferenceService.set(AINativeSettingSectionsId.MCPServers, servers);
|
|
130
|
+
setFormVisible(false);
|
|
131
|
+
await loadServers();
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<div className={styles.container}>
|
|
136
|
+
<div className={styles.header}>
|
|
137
|
+
<div>
|
|
138
|
+
<h2 className={styles.title}>MCP Servers</h2>
|
|
139
|
+
<p className={styles.description}>Manage your MCP server connections.</p>
|
|
140
|
+
</div>
|
|
141
|
+
<button className={styles.addButton} onClick={handleAddServer}>
|
|
142
|
+
+ Add new MCP server
|
|
143
|
+
</button>
|
|
144
|
+
</div>
|
|
145
|
+
<div className={styles.serversList}>
|
|
146
|
+
{servers.map((server) => (
|
|
147
|
+
<div key={server.name} className={styles.serverItem}>
|
|
148
|
+
<div className={styles.serverHeader}>
|
|
149
|
+
<div className={styles.serverTitleRow}>
|
|
150
|
+
<h3 className={styles.serverName}>{server.name}</h3>
|
|
151
|
+
</div>
|
|
152
|
+
<div className={styles.serverActions}>
|
|
153
|
+
<button className={styles.iconButton} title='Edit' onClick={() => handleEditServer(server)}>
|
|
154
|
+
<i className='codicon codicon-edit' />
|
|
155
|
+
</button>
|
|
156
|
+
<button
|
|
157
|
+
className={styles.iconButton}
|
|
158
|
+
title={server.isStarted ? 'Stop' : 'Start'}
|
|
159
|
+
onClick={() => handleServerControl(server.name, !server.isStarted)}
|
|
160
|
+
>
|
|
161
|
+
<i className={`codicon ${server.isStarted ? 'codicon-debug-stop' : 'codicon-debug-start'}`} />
|
|
162
|
+
</button>
|
|
163
|
+
<button className={styles.iconButton} title='Delete' onClick={() => handleDeleteServer(server.name)}>
|
|
164
|
+
<i className='codicon codicon-trash' />
|
|
165
|
+
</button>
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
168
|
+
<div className={styles.serverDetail}>
|
|
169
|
+
<div className={styles.detailRow}>
|
|
170
|
+
<span className={styles.detailLabel}>Status:</span>
|
|
171
|
+
<span className={`${styles.serverStatus} ${server.isStarted ? styles.running : styles.stopped}`}>
|
|
172
|
+
{server.isStarted ? 'Running' : 'Stopped'}
|
|
173
|
+
</span>
|
|
174
|
+
</div>
|
|
175
|
+
{server.type && (
|
|
176
|
+
<div className={styles.detailRow}>
|
|
177
|
+
<span className={styles.detailLabel}>Type:</span>
|
|
178
|
+
<span className={styles.serverType}>{server.type}</span>
|
|
179
|
+
</div>
|
|
180
|
+
)}
|
|
181
|
+
</div>
|
|
182
|
+
{server.tools && server.tools.length > 0 && (
|
|
183
|
+
<div className={styles.serverDetail}>
|
|
184
|
+
<div className={styles.detailRow}>
|
|
185
|
+
<span className={styles.detailLabel}>Tools:</span>
|
|
186
|
+
<span className={styles.detailContent}>
|
|
187
|
+
{server.tools.map((tool, index) => (
|
|
188
|
+
<span key={index} className={styles.toolTag}>
|
|
189
|
+
{tool}
|
|
190
|
+
</span>
|
|
191
|
+
))}
|
|
192
|
+
</span>
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
195
|
+
)}
|
|
196
|
+
{server.command && (
|
|
197
|
+
<div className={styles.serverDetail}>
|
|
198
|
+
<div className={styles.detailRow}>
|
|
199
|
+
<span className={styles.detailLabel}>Command:</span>
|
|
200
|
+
<span className={styles.detailContent}>{server.command}</span>
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
203
|
+
)}
|
|
204
|
+
</div>
|
|
205
|
+
))}
|
|
206
|
+
</div>
|
|
207
|
+
<MCPServerForm
|
|
208
|
+
visible={formVisible}
|
|
209
|
+
initialData={editingServer}
|
|
210
|
+
onSave={handleSaveServer}
|
|
211
|
+
onCancel={() => setFormVisible(false)}
|
|
212
|
+
/>
|
|
213
|
+
</div>
|
|
214
|
+
);
|
|
215
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
.form {
|
|
2
|
+
padding: 16px;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.formItem {
|
|
6
|
+
margin-bottom: 16px;
|
|
7
|
+
|
|
8
|
+
label {
|
|
9
|
+
display: block;
|
|
10
|
+
margin-bottom: 8px;
|
|
11
|
+
color: var(--foreground);
|
|
12
|
+
font-size: 13px;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
input,
|
|
16
|
+
textarea {
|
|
17
|
+
width: 100%;
|
|
18
|
+
padding: 8px;
|
|
19
|
+
border: 1px solid var(--input-border);
|
|
20
|
+
border-radius: 4px;
|
|
21
|
+
background-color: var(--input-background);
|
|
22
|
+
color: var(--input-foreground);
|
|
23
|
+
font-size: 13px;
|
|
24
|
+
font-family: var(--monaco-monospace-font);
|
|
25
|
+
|
|
26
|
+
&:focus {
|
|
27
|
+
border-color: var(--focusBorder);
|
|
28
|
+
outline: none;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
&::placeholder {
|
|
32
|
+
color: var(--descriptionForeground);
|
|
33
|
+
opacity: 0.6;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
textarea {
|
|
38
|
+
resize: vertical;
|
|
39
|
+
min-height: 60px;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.formActions {
|
|
44
|
+
display: flex;
|
|
45
|
+
justify-content: flex-end;
|
|
46
|
+
gap: 8px;
|
|
47
|
+
margin-top: 24px;
|
|
48
|
+
padding-top: 16px;
|
|
49
|
+
border-top: 1px solid var(--border-color);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.cancelButton {
|
|
53
|
+
padding: 6px 12px;
|
|
54
|
+
border: 1px solid var(--button-border);
|
|
55
|
+
border-radius: 4px;
|
|
56
|
+
background-color: var(--button-secondary-background);
|
|
57
|
+
color: var(--button-secondary-foreground);
|
|
58
|
+
cursor: pointer;
|
|
59
|
+
font-size: 13px;
|
|
60
|
+
|
|
61
|
+
&:hover {
|
|
62
|
+
background-color: var(--button-secondary-hover-background);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.submitButton {
|
|
67
|
+
padding: 6px 12px;
|
|
68
|
+
border: none;
|
|
69
|
+
border-radius: 4px;
|
|
70
|
+
background-color: var(--button-primary-background);
|
|
71
|
+
color: var(--button-primary-foreground);
|
|
72
|
+
cursor: pointer;
|
|
73
|
+
font-size: 13px;
|
|
74
|
+
|
|
75
|
+
&:hover {
|
|
76
|
+
background-color: var(--button-primary-hover-background);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import { Button } from '@opensumi/ide-components/lib/button';
|
|
4
|
+
import { Modal } from '@opensumi/ide-components/lib/modal';
|
|
5
|
+
|
|
6
|
+
import styles from './mcp-server-form.module.less';
|
|
7
|
+
|
|
8
|
+
export interface MCPServerFormData {
|
|
9
|
+
name: string;
|
|
10
|
+
command: string;
|
|
11
|
+
args: string[];
|
|
12
|
+
env?: Record<string, string>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface Props {
|
|
16
|
+
visible: boolean;
|
|
17
|
+
initialData?: MCPServerFormData;
|
|
18
|
+
onSave: (data: MCPServerFormData) => void;
|
|
19
|
+
onCancel: () => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const MCPServerForm: React.FC<Props> = ({ visible, initialData, onSave, onCancel }) => {
|
|
23
|
+
const [formData, setFormData] = React.useState<MCPServerFormData>(() => ({
|
|
24
|
+
name: '',
|
|
25
|
+
command: '',
|
|
26
|
+
args: [],
|
|
27
|
+
env: {},
|
|
28
|
+
type: 'stdio', // TODO: 支持 SSE
|
|
29
|
+
...initialData,
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
const [argsText, setArgsText] = React.useState(() => initialData?.args?.join(' ') || '');
|
|
33
|
+
const [envText, setEnvText] = React.useState(() => {
|
|
34
|
+
if (!initialData?.env) {
|
|
35
|
+
return '';
|
|
36
|
+
}
|
|
37
|
+
return Object.entries(initialData.env)
|
|
38
|
+
.map(([key, value]) => `${key}=${value}`)
|
|
39
|
+
.join('\n');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Update form data when initialData changes
|
|
43
|
+
React.useEffect(() => {
|
|
44
|
+
setFormData({
|
|
45
|
+
name: '',
|
|
46
|
+
command: '',
|
|
47
|
+
args: [],
|
|
48
|
+
env: {},
|
|
49
|
+
...initialData,
|
|
50
|
+
});
|
|
51
|
+
setArgsText(initialData?.args?.join(' ') || '');
|
|
52
|
+
setEnvText(
|
|
53
|
+
initialData?.env
|
|
54
|
+
? Object.entries(initialData.env)
|
|
55
|
+
.map(([key, value]) => `${key}=${value}`)
|
|
56
|
+
.join('\n')
|
|
57
|
+
: '',
|
|
58
|
+
);
|
|
59
|
+
}, [initialData]);
|
|
60
|
+
|
|
61
|
+
const handleSubmit = (e: React.FormEvent) => {
|
|
62
|
+
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({
|
|
76
|
+
...formData,
|
|
77
|
+
args,
|
|
78
|
+
env,
|
|
79
|
+
});
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<Modal
|
|
84
|
+
title={initialData ? 'Edit MCP Server' : 'Add New MCP Server'}
|
|
85
|
+
visible={visible}
|
|
86
|
+
onCancel={onCancel}
|
|
87
|
+
centered
|
|
88
|
+
width={600}
|
|
89
|
+
footer={null}
|
|
90
|
+
style={{
|
|
91
|
+
background: 'var(--editor-background)',
|
|
92
|
+
}}
|
|
93
|
+
>
|
|
94
|
+
<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
|
+
/>
|
|
132
|
+
</div>
|
|
133
|
+
<div className={styles.formActions}>
|
|
134
|
+
<Button onClick={onCancel} type='secondary'>
|
|
135
|
+
Cancel
|
|
136
|
+
</Button>
|
|
137
|
+
<Button onClick={handleSubmit} type='primary'>
|
|
138
|
+
Save
|
|
139
|
+
</Button>
|
|
140
|
+
</div>
|
|
141
|
+
</form>
|
|
142
|
+
</Modal>
|
|
143
|
+
);
|
|
144
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Autowired } from '@opensumi/di';
|
|
2
|
+
import { CommandContribution, CommandRegistry, URI } from '@opensumi/ide-core-browser';
|
|
3
|
+
import { Domain } from '@opensumi/ide-core-common';
|
|
4
|
+
import { WorkbenchEditorService } from '@opensumi/ide-editor';
|
|
5
|
+
|
|
6
|
+
import { MCP_CONFIG_COMPONENTS_SCHEME_ID } from './mcp-config.contribution';
|
|
7
|
+
|
|
8
|
+
export const OPEN_MCP_CONFIG_COMMAND = {
|
|
9
|
+
id: 'opensumi-mcp.openConfig',
|
|
10
|
+
label: 'Open MCP Configuration',
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
@Domain(CommandContribution)
|
|
14
|
+
export class MCPConfigCommandContribution implements CommandContribution {
|
|
15
|
+
@Autowired(WorkbenchEditorService)
|
|
16
|
+
private readonly editorService: WorkbenchEditorService;
|
|
17
|
+
|
|
18
|
+
registerCommands(registry: CommandRegistry) {
|
|
19
|
+
registry.registerCommand(OPEN_MCP_CONFIG_COMMAND, {
|
|
20
|
+
execute: () => {
|
|
21
|
+
const uri = new URI().withScheme(MCP_CONFIG_COMPONENTS_SCHEME_ID);
|
|
22
|
+
this.editorService.open(uri, {
|
|
23
|
+
preview: false,
|
|
24
|
+
focus: true,
|
|
25
|
+
});
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Autowired } from '@opensumi/di';
|
|
2
|
+
import { LabelService } from '@opensumi/ide-core-browser/lib/services';
|
|
3
|
+
import { Domain, Schemes, URI } from '@opensumi/ide-core-common';
|
|
4
|
+
import {
|
|
5
|
+
BrowserEditorContribution,
|
|
6
|
+
EditorComponentRegistry,
|
|
7
|
+
EditorComponentRenderMode,
|
|
8
|
+
IResource,
|
|
9
|
+
ResourceService,
|
|
10
|
+
} from '@opensumi/ide-editor/lib/browser/types';
|
|
11
|
+
import { IconService } from '@opensumi/ide-theme/lib/browser';
|
|
12
|
+
import { IWorkspaceService } from '@opensumi/ide-workspace/lib/common';
|
|
13
|
+
|
|
14
|
+
import { MCPConfigView } from './components/mcp-config.view';
|
|
15
|
+
|
|
16
|
+
const COMPONENTS_ID = 'opensumi-mcp-config-viewer';
|
|
17
|
+
export const MCP_CONFIG_COMPONENTS_SCHEME_ID = 'mcp-config';
|
|
18
|
+
|
|
19
|
+
export type IMCPConfigResource = IResource<{ configType: string }>;
|
|
20
|
+
|
|
21
|
+
@Domain(BrowserEditorContribution)
|
|
22
|
+
export class MCPConfigContribution implements BrowserEditorContribution {
|
|
23
|
+
@Autowired(IWorkspaceService)
|
|
24
|
+
protected readonly workspaceService: IWorkspaceService;
|
|
25
|
+
|
|
26
|
+
@Autowired(IconService)
|
|
27
|
+
protected readonly iconService: IconService;
|
|
28
|
+
|
|
29
|
+
@Autowired()
|
|
30
|
+
labelService: LabelService;
|
|
31
|
+
|
|
32
|
+
registerEditorComponent(registry: EditorComponentRegistry) {
|
|
33
|
+
registry.registerEditorComponent({
|
|
34
|
+
uid: COMPONENTS_ID,
|
|
35
|
+
scheme: MCP_CONFIG_COMPONENTS_SCHEME_ID,
|
|
36
|
+
component: MCPConfigView,
|
|
37
|
+
renderMode: EditorComponentRenderMode.ONE_PER_WORKBENCH,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
registry.registerEditorComponentResolver(MCP_CONFIG_COMPONENTS_SCHEME_ID, (resource, results) => {
|
|
41
|
+
results.push({
|
|
42
|
+
type: 'component',
|
|
43
|
+
componentId: COMPONENTS_ID,
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
registerResource(service: ResourceService) {
|
|
49
|
+
service.registerResourceProvider({
|
|
50
|
+
scheme: MCP_CONFIG_COMPONENTS_SCHEME_ID,
|
|
51
|
+
provideResource: async (uri: URI): Promise<IMCPConfigResource> => {
|
|
52
|
+
const { configType } = uri.getParsedQuery();
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
uri,
|
|
56
|
+
name: 'MCP Configuration',
|
|
57
|
+
icon: 'settings',
|
|
58
|
+
metadata: {
|
|
59
|
+
configType,
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -4,7 +4,7 @@ import { Autowired, Injectable } from '@opensumi/di';
|
|
|
4
4
|
import { ILogger } from '@opensumi/ide-core-browser';
|
|
5
5
|
import { Emitter, Event } from '@opensumi/ide-core-common';
|
|
6
6
|
|
|
7
|
-
import { ISumiMCPServerBackend, SumiMCPServerProxyServicePath } from '../../common';
|
|
7
|
+
import { BUILTIN_MCP_SERVER_NAME, ISumiMCPServerBackend, SumiMCPServerProxyServicePath } from '../../common';
|
|
8
8
|
import { IMCPServerProxyService } from '../../common/types';
|
|
9
9
|
import { IMCPServerRegistry, TokenMCPServerRegistry } from '../types';
|
|
10
10
|
|
|
@@ -35,7 +35,7 @@ export class MCPServerProxyService implements IMCPServerProxyService {
|
|
|
35
35
|
name: tool.name,
|
|
36
36
|
description: tool.description,
|
|
37
37
|
inputSchema: zodToJsonSchema(tool.inputSchema),
|
|
38
|
-
providerName:
|
|
38
|
+
providerName: BUILTIN_MCP_SERVER_NAME,
|
|
39
39
|
}),
|
|
40
40
|
);
|
|
41
41
|
|
|
@@ -52,4 +52,16 @@ export class MCPServerProxyService implements IMCPServerProxyService {
|
|
|
52
52
|
async getAllMCPTools() {
|
|
53
53
|
return this.sumiMCPServerProxyService.getAllMCPTools();
|
|
54
54
|
}
|
|
55
|
+
|
|
56
|
+
async $getServers() {
|
|
57
|
+
return this.sumiMCPServerProxyService.getServers();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async $startServer(serverName: string) {
|
|
61
|
+
await this.sumiMCPServerProxyService.startServer(serverName);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async $stopServer(serverName: string) {
|
|
65
|
+
await this.sumiMCPServerProxyService.stopServer(serverName);
|
|
66
|
+
}
|
|
55
67
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { Autowired, Injectable } from '@opensumi/di';
|
|
3
3
|
import { ILogger } from '@opensumi/ide-core-common';
|
|
4
4
|
|
|
5
|
+
import { BUILTIN_MCP_SERVER_NAME } from '../../common';
|
|
5
6
|
import { getToolName } from '../../common/utils';
|
|
6
7
|
import { IMCPServerRegistry, IMCPServerToolComponentProps, MCPLogger, MCPToolDefinition } from '../types';
|
|
7
8
|
|
|
@@ -25,7 +26,7 @@ export class MCPServerRegistry implements IMCPServerRegistry {
|
|
|
25
26
|
return new LoggerAdapter(this.baseLogger);
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
getMCPTool(name: string, serverName =
|
|
29
|
+
getMCPTool(name: string, serverName = BUILTIN_MCP_SERVER_NAME): MCPToolDefinition | undefined {
|
|
29
30
|
return this.tools.find((tool) => getToolName(tool.name, serverName) === name);
|
|
30
31
|
}
|
|
31
32
|
|
|
@@ -36,7 +37,7 @@ export class MCPServerRegistry implements IMCPServerRegistry {
|
|
|
36
37
|
registerToolComponent(
|
|
37
38
|
name: string,
|
|
38
39
|
component: React.FC<IMCPServerToolComponentProps>,
|
|
39
|
-
serverName =
|
|
40
|
+
serverName = BUILTIN_MCP_SERVER_NAME,
|
|
40
41
|
): void {
|
|
41
42
|
this.toolComponents[getToolName(name, serverName)] = component;
|
|
42
43
|
}
|
|
@@ -108,11 +108,27 @@ export const aiNativePreferenceSchema: PreferenceSchema = {
|
|
|
108
108
|
type: 'string',
|
|
109
109
|
description: localize('preference.ai.native.mcp.servers.command.description'),
|
|
110
110
|
},
|
|
111
|
+
type: {
|
|
112
|
+
type: 'string',
|
|
113
|
+
enum: ['stdio', 'sse'],
|
|
114
|
+
enumDescriptions: [
|
|
115
|
+
localize('preference.ai.native.mcp.servers.type.stdio'),
|
|
116
|
+
localize('preference.ai.native.mcp.servers.type.sse'),
|
|
117
|
+
],
|
|
118
|
+
description: localize('preference.ai.native.mcp.servers.type.description'),
|
|
119
|
+
default: 'stdio',
|
|
120
|
+
},
|
|
121
|
+
enabled: {
|
|
122
|
+
type: 'boolean',
|
|
123
|
+
description: localize('preference.ai.native.mcp.servers.enabled.description'),
|
|
124
|
+
default: true,
|
|
125
|
+
},
|
|
111
126
|
args: {
|
|
112
127
|
type: 'array',
|
|
113
128
|
items: {
|
|
114
129
|
type: 'string',
|
|
115
130
|
},
|
|
131
|
+
default: [],
|
|
116
132
|
description: localize('preference.ai.native.mcp.servers.args.description'),
|
|
117
133
|
},
|
|
118
134
|
env: {
|
package/src/common/index.ts
CHANGED
|
@@ -31,6 +31,9 @@ export const AI_CHAT_CONTAINER_ID = 'AI-Chat-Container';
|
|
|
31
31
|
export const AI_CHAT_LOGO_AVATAR_ID = 'AI-Chat-Logo-Avatar';
|
|
32
32
|
export const AI_MENU_BAR_DEBUG_TOOLBAR = 'AI_MENU_BAR_DEBUG_TOOLBAR';
|
|
33
33
|
|
|
34
|
+
// 内置 MCP 服务器名称
|
|
35
|
+
export const BUILTIN_MCP_SERVER_NAME = 'sumi-builtin';
|
|
36
|
+
|
|
34
37
|
/**
|
|
35
38
|
* @deprecated Use {@link DESIGN_MENUBAR_CONTAINER_VIEW_ID} instead
|
|
36
39
|
*/
|
|
@@ -123,9 +126,12 @@ export const ChatProxyServiceToken = Symbol('ChatProxyServiceToken');
|
|
|
123
126
|
export const TokenMCPServerProxyService = Symbol('TokenMCPServerProxyService');
|
|
124
127
|
|
|
125
128
|
export interface ISumiMCPServerBackend {
|
|
126
|
-
initBuiltinMCPServer(): void;
|
|
129
|
+
initBuiltinMCPServer(enabled: boolean): void;
|
|
127
130
|
initExternalMCPServers(servers: MCPServerDescription[]): void;
|
|
128
131
|
getAllMCPTools(): Promise<MCPTool[]>;
|
|
132
|
+
getServers(): Promise<Array<{ name: string; isStarted: boolean }>>;
|
|
133
|
+
startServer(serverName: string): Promise<void>;
|
|
134
|
+
stopServer(serverName: string): Promise<void>;
|
|
129
135
|
}
|
|
130
136
|
|
|
131
137
|
export const SumiMCPServerProxyServicePath = 'SumiMCPServerProxyServicePath';
|
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
2
2
|
|
|
3
|
+
export interface IMCPServer {
|
|
4
|
+
isStarted(): boolean;
|
|
5
|
+
start(): Promise<void>;
|
|
6
|
+
getServerName(): string;
|
|
7
|
+
callTool(toolName: string, toolCallId: string, arg_string: string): ReturnType<Client['callTool']>;
|
|
8
|
+
getTools(): ReturnType<Client['listTools']>;
|
|
9
|
+
update(command: string, args?: string[], env?: { [key: string]: string }): void;
|
|
10
|
+
stop(): void;
|
|
11
|
+
}
|
|
12
|
+
|
|
3
13
|
export interface MCPServerManager {
|
|
4
14
|
callTool(
|
|
5
15
|
serverName: string,
|
|
@@ -11,7 +21,7 @@ export interface MCPServerManager {
|
|
|
11
21
|
addOrUpdateServer(description: MCPServerDescription): void;
|
|
12
22
|
// invoke in node.js only
|
|
13
23
|
addOrUpdateServerDirectly(server: any): void;
|
|
14
|
-
initBuiltinServer(builtinMCPServer: any): void;
|
|
24
|
+
initBuiltinServer(builtinMCPServer: any, enabled: boolean): void;
|
|
15
25
|
getTools(serverName: string): ReturnType<Client['listTools']>;
|
|
16
26
|
getServerNames(): Promise<string[]>;
|
|
17
27
|
startServer(serverName: string): Promise<void>;
|
|
@@ -19,6 +29,7 @@ export interface MCPServerManager {
|
|
|
19
29
|
getStartedServers(): Promise<string[]>;
|
|
20
30
|
registerTools(serverName: string): Promise<void>;
|
|
21
31
|
addExternalMCPServers(servers: MCPServerDescription[]): void;
|
|
32
|
+
getServers(): Map<string, IMCPServer>;
|
|
22
33
|
}
|
|
23
34
|
|
|
24
35
|
export type MCPTool = Awaited<ReturnType<MCPServerManager['getTools']>>['tools'][number];
|
|
@@ -45,6 +56,11 @@ export interface MCPServerDescription {
|
|
|
45
56
|
* Optional environment variables to set when starting the server.
|
|
46
57
|
*/
|
|
47
58
|
env?: { [key: string]: string };
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Whether to enable the MCP server.
|
|
62
|
+
*/
|
|
63
|
+
enabled?: boolean;
|
|
48
64
|
}
|
|
49
65
|
|
|
50
66
|
export const MCPServerManager = Symbol('MCPServerManager');
|