@opensumi/ide-ai-native 3.8.3-next-1745805174.0 → 3.8.3-next-1745835516.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.contribution.d.ts +2 -0
- package/lib/browser/ai-core.contribution.d.ts.map +1 -1
- package/lib/browser/ai-core.contribution.js +58 -12
- package/lib/browser/ai-core.contribution.js.map +1 -1
- package/lib/browser/components/ChatInput.js +1 -1
- package/lib/browser/components/ChatInput.js.map +1 -1
- package/lib/browser/components/ChatMentionInput.js +1 -1
- package/lib/browser/components/ChatMentionInput.js.map +1 -1
- package/lib/browser/components/ChatToolResult.module.less +0 -2
- package/lib/browser/components/mention-input/mention-input.d.ts.map +1 -1
- package/lib/browser/components/mention-input/mention-input.js +9 -7
- package/lib/browser/components/mention-input/mention-input.js.map +1 -1
- package/lib/browser/index.d.ts.map +1 -1
- package/lib/browser/index.js +15 -0
- package/lib/browser/index.js.map +1 -1
- package/lib/browser/mcp/config/components/mcp-config.module.less +4 -1
- package/lib/browser/mcp/config/components/mcp-config.view.d.ts.map +1 -1
- package/lib/browser/mcp/config/components/mcp-config.view.js +27 -119
- package/lib/browser/mcp/config/components/mcp-config.view.js.map +1 -1
- package/lib/browser/mcp/config/components/mcp-server-form.d.ts +1 -1
- package/lib/browser/mcp/config/components/mcp-server-form.d.ts.map +1 -1
- package/lib/browser/mcp/config/components/mcp-server-form.js +8 -8
- package/lib/browser/mcp/config/components/mcp-server-form.js.map +1 -1
- package/lib/browser/mcp/config/mcp-config.commands.d.ts +11 -4
- package/lib/browser/mcp/config/mcp-config.commands.d.ts.map +1 -1
- package/lib/browser/mcp/config/mcp-config.commands.js +23 -6
- package/lib/browser/mcp/config/mcp-config.commands.js.map +1 -1
- package/lib/browser/mcp/config/mcp-config.contribution.d.ts +4 -1
- package/lib/browser/mcp/config/mcp-config.contribution.d.ts.map +1 -1
- package/lib/browser/mcp/config/mcp-config.contribution.js +20 -1
- package/lib/browser/mcp/config/mcp-config.contribution.js.map +1 -1
- package/lib/browser/mcp/config/mcp-config.service.d.ts +30 -0
- package/lib/browser/mcp/config/mcp-config.service.d.ts.map +1 -0
- package/lib/browser/mcp/config/mcp-config.service.js +236 -0
- package/lib/browser/mcp/config/mcp-config.service.js.map +1 -0
- package/lib/browser/mcp/mcp-folder-preference-provider.d.ts +6 -0
- package/lib/browser/mcp/mcp-folder-preference-provider.d.ts.map +1 -0
- package/lib/browser/mcp/mcp-folder-preference-provider.js +29 -0
- package/lib/browser/mcp/mcp-folder-preference-provider.js.map +1 -0
- package/lib/browser/mcp/mcp-preferences-contribution.d.ts +15 -0
- package/lib/browser/mcp/mcp-preferences-contribution.d.ts.map +1 -0
- package/lib/browser/mcp/mcp-preferences-contribution.js +49 -0
- package/lib/browser/mcp/mcp-preferences-contribution.js.map +1 -0
- package/lib/browser/mcp/mcp-preferences.d.ts +6 -0
- package/lib/browser/mcp/mcp-preferences.d.ts.map +1 -0
- package/lib/browser/mcp/mcp-preferences.js +47 -0
- package/lib/browser/mcp/mcp-preferences.js.map +1 -0
- package/lib/browser/preferences/schema.d.ts.map +1 -1
- package/lib/browser/preferences/schema.js +3 -0
- package/lib/browser/preferences/schema.js.map +1 -1
- package/lib/common/mcp-server-manager.d.ts +2 -1
- package/lib/common/mcp-server-manager.d.ts.map +1 -1
- package/lib/common/mcp-server-manager.js +2 -1
- package/lib/common/mcp-server-manager.js.map +1 -1
- package/lib/common/types.d.ts +1 -1
- package/lib/common/types.d.ts.map +1 -1
- package/lib/node/mcp/sumi-mcp-server.d.ts +3 -3
- package/lib/node/mcp/sumi-mcp-server.js +1 -1
- 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 +9 -4
- package/lib/node/mcp-server-manager-impl.js.map +1 -1
- package/lib/node/mcp-server.sse.d.ts +10 -37
- package/lib/node/mcp-server.sse.d.ts.map +1 -1
- package/lib/node/mcp-server.sse.js +43 -15
- package/lib/node/mcp-server.sse.js.map +1 -1
- package/lib/node/mcp-server.stdio.d.ts +7 -34
- package/lib/node/mcp-server.stdio.d.ts.map +1 -1
- package/lib/node/mcp-server.stdio.js +27 -2
- package/lib/node/mcp-server.stdio.js.map +1 -1
- package/package.json +24 -24
- package/src/browser/ai-core.contribution.ts +71 -16
- package/src/browser/components/ChatInput.tsx +2 -2
- package/src/browser/components/ChatMentionInput.tsx +2 -2
- package/src/browser/components/ChatToolResult.module.less +0 -2
- package/src/browser/components/mention-input/mention-input.tsx +27 -23
- package/src/browser/index.ts +16 -0
- package/src/browser/mcp/config/components/mcp-config.module.less +4 -1
- package/src/browser/mcp/config/components/mcp-config.view.tsx +37 -125
- package/src/browser/mcp/config/components/mcp-server-form.tsx +10 -10
- package/src/browser/mcp/config/mcp-config.commands.ts +22 -6
- package/src/browser/mcp/config/mcp-config.contribution.ts +24 -2
- package/src/browser/mcp/config/mcp-config.service.ts +285 -0
- package/src/browser/mcp/mcp-folder-preference-provider.ts +23 -0
- package/src/browser/mcp/mcp-preferences-contribution.ts +62 -0
- package/src/browser/mcp/mcp-preferences.ts +48 -0
- package/src/browser/preferences/schema.ts +3 -0
- package/src/common/mcp-server-manager.ts +3 -1
- package/src/common/types.ts +1 -1
- package/src/node/mcp/sumi-mcp-server.ts +1 -1
- package/src/node/mcp-server-manager-impl.ts +8 -4
- package/src/node/mcp-server.sse.ts +41 -14
- package/src/node/mcp-server.stdio.ts +26 -2
|
@@ -52,9 +52,13 @@ import {
|
|
|
52
52
|
CommandService,
|
|
53
53
|
InlineChatFeatureRegistryToken,
|
|
54
54
|
IntelligentCompletionsRegistryToken,
|
|
55
|
+
MCPConfigServiceToken,
|
|
56
|
+
PreferenceScope,
|
|
55
57
|
ProblemFixRegistryToken,
|
|
56
58
|
RenameCandidatesProviderRegistryToken,
|
|
57
59
|
ResolveConflictRegistryToken,
|
|
60
|
+
STORAGE_NAMESPACE,
|
|
61
|
+
StorageProvider,
|
|
58
62
|
TerminalRegistryToken,
|
|
59
63
|
isUndefined,
|
|
60
64
|
runWhenIdle,
|
|
@@ -88,7 +92,7 @@ import {
|
|
|
88
92
|
deepSeekModels,
|
|
89
93
|
openAiNativeModels,
|
|
90
94
|
} from '../common';
|
|
91
|
-
import { MCPServerDescription } from '../common/mcp-server-manager';
|
|
95
|
+
import { MCPServerDescription, MCPServersEnabledKey } from '../common/mcp-server-manager';
|
|
92
96
|
import { MCP_SERVER_TYPE } from '../common/types';
|
|
93
97
|
|
|
94
98
|
import { ChatManagerService } from './chat/chat-manager.service';
|
|
@@ -111,6 +115,7 @@ import {
|
|
|
111
115
|
} from './layout/tabbar.view';
|
|
112
116
|
import { AIChatLogoAvatar } from './layout/view/avatar/avatar.view';
|
|
113
117
|
import { BaseApplyService } from './mcp/base-apply.service';
|
|
118
|
+
import { MCPConfigService } from './mcp/config/mcp-config.service';
|
|
114
119
|
import {
|
|
115
120
|
AINativeCoreContribution,
|
|
116
121
|
IChatFeatureRegistry,
|
|
@@ -248,6 +253,9 @@ export class AINativeBrowserContribution
|
|
|
248
253
|
@Autowired(SumiMCPServerProxyServicePath)
|
|
249
254
|
private readonly sumiMCPServerBackendProxy: ISumiMCPServerBackend;
|
|
250
255
|
|
|
256
|
+
@Autowired(MCPConfigServiceToken)
|
|
257
|
+
private readonly mcpConfigService: MCPConfigService;
|
|
258
|
+
|
|
251
259
|
@Autowired(WorkbenchEditorService)
|
|
252
260
|
private readonly workbenchEditorService: WorkbenchEditorServiceImpl;
|
|
253
261
|
|
|
@@ -260,6 +268,9 @@ export class AINativeBrowserContribution
|
|
|
260
268
|
@Autowired(BaseApplyService)
|
|
261
269
|
private readonly applyService: BaseApplyService;
|
|
262
270
|
|
|
271
|
+
@Autowired(StorageProvider)
|
|
272
|
+
private readonly storageProvider: StorageProvider;
|
|
273
|
+
|
|
263
274
|
constructor() {
|
|
264
275
|
this.registerFeature();
|
|
265
276
|
}
|
|
@@ -411,24 +422,68 @@ export class AINativeBrowserContribution
|
|
|
411
422
|
});
|
|
412
423
|
}
|
|
413
424
|
|
|
414
|
-
private initMCPServers() {
|
|
415
|
-
|
|
416
|
-
|
|
425
|
+
private async initMCPServers() {
|
|
426
|
+
const storage = await this.storageProvider(STORAGE_NAMESPACE.CHAT);
|
|
427
|
+
let enabledMCPServers = storage.get<string[]>(MCPServersEnabledKey, [BUILTIN_MCP_SERVER_NAME]);
|
|
417
428
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
429
|
+
const oldMCPServers = this.preferenceService.get<MCPServerDescription[]>(AINativeSettingSectionsId.MCPServers, []);
|
|
430
|
+
let mcpServerFromWorkspace = this.preferenceService.resolve<{ mcpServers: Record<string, any>[] }>(
|
|
431
|
+
'mcp',
|
|
432
|
+
{
|
|
433
|
+
mcpServers: [] as any,
|
|
434
|
+
},
|
|
435
|
+
undefined,
|
|
421
436
|
);
|
|
422
|
-
|
|
437
|
+
if (mcpServerFromWorkspace.scope === PreferenceScope.Default && oldMCPServers.length > 0) {
|
|
438
|
+
// 如果用户没有配置,也没有存储,则从旧配置迁移
|
|
439
|
+
const newMCPServers = {
|
|
440
|
+
mcpServers: [] as any,
|
|
441
|
+
};
|
|
442
|
+
const mcpServersEnabled = new Set<string>([BUILTIN_MCP_SERVER_NAME]);
|
|
443
|
+
oldMCPServers.forEach((server) => {
|
|
444
|
+
if (server.type === MCP_SERVER_TYPE.SSE) {
|
|
445
|
+
newMCPServers.mcpServers.push({
|
|
446
|
+
[server.name]: {
|
|
447
|
+
url: (server as any).serverHost,
|
|
448
|
+
},
|
|
449
|
+
});
|
|
450
|
+
} else if (server.type === MCP_SERVER_TYPE.STDIO) {
|
|
451
|
+
newMCPServers.mcpServers.push({
|
|
452
|
+
[server.name]: {
|
|
453
|
+
command: server.command,
|
|
454
|
+
args: server.args,
|
|
455
|
+
env: server.env,
|
|
456
|
+
},
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
if (server.enabled) {
|
|
460
|
+
mcpServersEnabled.add(server.name);
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
await this.preferenceService.set('mcp', newMCPServers, PreferenceScope.Workspace);
|
|
464
|
+
mcpServerFromWorkspace = this.preferenceService.resolve<{ mcpServers: Record<string, any>[] }>(
|
|
465
|
+
'mcp',
|
|
466
|
+
undefined,
|
|
467
|
+
undefined,
|
|
468
|
+
);
|
|
469
|
+
enabledMCPServers = Array.from(mcpServersEnabled);
|
|
470
|
+
storage.set(MCPServersEnabledKey, enabledMCPServers);
|
|
471
|
+
}
|
|
472
|
+
const userServers = mcpServerFromWorkspace.value?.mcpServers;
|
|
423
473
|
// 总是初始化内置服务器,根据配置决定是否启用
|
|
424
|
-
this.sumiMCPServerBackendProxy.$initBuiltinMCPServer(
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
474
|
+
this.sumiMCPServerBackendProxy.$initBuiltinMCPServer(enabledMCPServers.includes(BUILTIN_MCP_SERVER_NAME));
|
|
475
|
+
|
|
476
|
+
if (userServers && userServers.length > 0) {
|
|
477
|
+
const mcpServers = (
|
|
478
|
+
await Promise.all(
|
|
479
|
+
userServers.map(async (server) => {
|
|
480
|
+
const name = Object.keys(server)[0];
|
|
481
|
+
return await this.mcpConfigService.getServerConfigByName(name);
|
|
482
|
+
}),
|
|
483
|
+
)
|
|
484
|
+
).filter((server) => server !== undefined) as MCPServerDescription[];
|
|
485
|
+
await this.sumiMCPServerBackendProxy.$initExternalMCPServers(mcpServers);
|
|
486
|
+
this.mcpConfigService.fireMCPServersChange(true);
|
|
432
487
|
}
|
|
433
488
|
}
|
|
434
489
|
|
|
@@ -28,7 +28,7 @@ import { ChatSlashCommandItemModel } from '../chat/chat-model';
|
|
|
28
28
|
import { ChatProxyService } from '../chat/chat-proxy.service';
|
|
29
29
|
import { ChatFeatureRegistry } from '../chat/chat.feature.registry';
|
|
30
30
|
import { ChatInternalService } from '../chat/chat.internal.service';
|
|
31
|
-
import {
|
|
31
|
+
import { MCPConfigCommands } from '../mcp/config/mcp-config.commands';
|
|
32
32
|
import { MCPServerProxyService } from '../mcp/mcp-server-proxy.service';
|
|
33
33
|
import { MCPToolsDialog } from '../mcp/mcp-tools-dialog.view';
|
|
34
34
|
import { IChatSlashCommandItem } from '../types';
|
|
@@ -223,7 +223,7 @@ export const ChatInput = React.forwardRef((props: IChatInputProps, ref) => {
|
|
|
223
223
|
const currentAgentIdRef = useLatest(agentId);
|
|
224
224
|
|
|
225
225
|
const handleShowMCPConfig = React.useCallback(() => {
|
|
226
|
-
commandService.executeCommand(
|
|
226
|
+
commandService.executeCommand(MCPConfigCommands.OPEN_MCP_CONFIG.id);
|
|
227
227
|
}, [commandService]);
|
|
228
228
|
|
|
229
229
|
const handleShowMCPTools = React.useCallback(async () => {
|
|
@@ -18,7 +18,7 @@ import { IChatInternalService } from '../../common';
|
|
|
18
18
|
import { LLMContextService } from '../../common/llm-context';
|
|
19
19
|
import { ChatFeatureRegistry } from '../chat/chat.feature.registry';
|
|
20
20
|
import { ChatInternalService } from '../chat/chat.internal.service';
|
|
21
|
-
import {
|
|
21
|
+
import { MCPConfigCommands } from '../mcp/config/mcp-config.commands';
|
|
22
22
|
|
|
23
23
|
import styles from './components.module.less';
|
|
24
24
|
import { MentionInput } from './mention-input/mention-input';
|
|
@@ -71,7 +71,7 @@ export const ChatMentionInput = (props: IChatMentionInputProps) => {
|
|
|
71
71
|
const outlineTreeService = useInjectable<OutlineTreeService>(OutlineTreeService);
|
|
72
72
|
const prevOutlineItems = useRef<MentionItem[]>([]);
|
|
73
73
|
const handleShowMCPConfig = React.useCallback(() => {
|
|
74
|
-
commandService.executeCommand(
|
|
74
|
+
commandService.executeCommand(MCPConfigCommands.OPEN_MCP_CONFIG.id);
|
|
75
75
|
}, [commandService]);
|
|
76
76
|
|
|
77
77
|
useEffect(() => {
|
|
@@ -483,7 +483,6 @@ export const MentionInput: React.FC<MentionInputProps> = ({
|
|
|
483
483
|
// 这里可以添加额外的逻辑,如果需要的话
|
|
484
484
|
};
|
|
485
485
|
|
|
486
|
-
// 添加粘贴事件处理
|
|
487
486
|
const handlePaste = async (e: React.ClipboardEvent<HTMLDivElement>) => {
|
|
488
487
|
const items = e.clipboardData.items;
|
|
489
488
|
|
|
@@ -551,13 +550,16 @@ export const MentionInput: React.FC<MentionInputProps> = ({
|
|
|
551
550
|
});
|
|
552
551
|
|
|
553
552
|
// 插入处理后的内容
|
|
553
|
+
const lastNode = fragment.lastChild;
|
|
554
554
|
range.insertNode(fragment);
|
|
555
555
|
|
|
556
556
|
// 将光标移动到插入内容的末尾
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
557
|
+
if (lastNode && lastNode.parentNode) {
|
|
558
|
+
const newRange = document.createRange();
|
|
559
|
+
newRange.setStartAfter(lastNode);
|
|
560
|
+
selection.removeAllRanges();
|
|
561
|
+
selection.addRange(newRange);
|
|
562
|
+
}
|
|
561
563
|
|
|
562
564
|
// 触发 input 事件以更新状态
|
|
563
565
|
handleInput();
|
|
@@ -1060,25 +1062,27 @@ export const MentionInput: React.FC<MentionInputProps> = ({
|
|
|
1060
1062
|
</div>
|
|
1061
1063
|
<div className={styles.right_control}>
|
|
1062
1064
|
{renderButtons(FooterButtonPosition.RIGHT)}
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
<div className={styles.
|
|
1071
|
-
<
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
{
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1065
|
+
{hasContext && (
|
|
1066
|
+
<Popover
|
|
1067
|
+
overlayClassName={styles.popover_icon}
|
|
1068
|
+
id={'ai-chat-clear-context'}
|
|
1069
|
+
position={PopoverPosition.top}
|
|
1070
|
+
content={localize('aiNative.chat.context.clear')}
|
|
1071
|
+
>
|
|
1072
|
+
<div className={styles.context_container} onClick={handleClearContext}>
|
|
1073
|
+
<div className={styles.context_icon}>
|
|
1074
|
+
<Icon icon='out-link' />
|
|
1075
|
+
<Icon icon='close' />
|
|
1076
|
+
</div>
|
|
1077
|
+
<div className={styles.context_description}>
|
|
1078
|
+
{formatLocalize(
|
|
1079
|
+
'aiNative.chat.context.description',
|
|
1080
|
+
attachedFiles.files.length + attachedFiles.folders.length,
|
|
1081
|
+
)}
|
|
1082
|
+
</div>
|
|
1079
1083
|
</div>
|
|
1080
|
-
</
|
|
1081
|
-
|
|
1084
|
+
</Popover>
|
|
1085
|
+
)}
|
|
1082
1086
|
<Popover
|
|
1083
1087
|
overlayClassName={styles.popover_icon}
|
|
1084
1088
|
id={'ai-chat-send'}
|
package/src/browser/index.ts
CHANGED
|
@@ -15,9 +15,11 @@ import {
|
|
|
15
15
|
} from '@opensumi/ide-core-browser';
|
|
16
16
|
import {
|
|
17
17
|
IntelligentCompletionsRegistryToken,
|
|
18
|
+
MCPConfigServiceToken,
|
|
18
19
|
ProblemFixRegistryToken,
|
|
19
20
|
TerminalRegistryToken,
|
|
20
21
|
} from '@opensumi/ide-core-common';
|
|
22
|
+
import { FolderFilePreferenceProvider } from '@opensumi/ide-preferences/lib/browser/folder-file-preference-provider';
|
|
21
23
|
|
|
22
24
|
import {
|
|
23
25
|
ChatProxyServiceToken,
|
|
@@ -60,6 +62,9 @@ import { LanguageParserService } from './languages/service';
|
|
|
60
62
|
import { BaseApplyService } from './mcp/base-apply.service';
|
|
61
63
|
import { MCPConfigCommandContribution } from './mcp/config/mcp-config.commands';
|
|
62
64
|
import { MCPConfigContribution } from './mcp/config/mcp-config.contribution';
|
|
65
|
+
import { MCPConfigService } from './mcp/config/mcp-config.service';
|
|
66
|
+
import { MCPFolderPreferenceProvider } from './mcp/mcp-folder-preference-provider';
|
|
67
|
+
import { MCPPreferencesContribution } from './mcp/mcp-preferences-contribution';
|
|
63
68
|
import { MCPServerProxyService } from './mcp/mcp-server-proxy.service';
|
|
64
69
|
import { MCPServerRegistry } from './mcp/mcp-server.feature.registry';
|
|
65
70
|
import { CreateNewFileWithTextTool } from './mcp/tools/createNewFileWithText';
|
|
@@ -98,6 +103,7 @@ export class AINativeModule extends BrowserModule {
|
|
|
98
103
|
IntelligentCompletionsContribution,
|
|
99
104
|
MCPConfigContribution,
|
|
100
105
|
MCPConfigCommandContribution,
|
|
106
|
+
MCPPreferencesContribution,
|
|
101
107
|
|
|
102
108
|
// MCP Server Contributions START
|
|
103
109
|
ListDirTool,
|
|
@@ -206,6 +212,16 @@ export class AINativeModule extends BrowserModule {
|
|
|
206
212
|
token: BaseApplyService,
|
|
207
213
|
useClass: ApplyService,
|
|
208
214
|
},
|
|
215
|
+
{
|
|
216
|
+
token: MCPConfigServiceToken,
|
|
217
|
+
useClass: MCPConfigService,
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
token: FolderFilePreferenceProvider,
|
|
221
|
+
useClass: MCPFolderPreferenceProvider,
|
|
222
|
+
dropdownForTag: true,
|
|
223
|
+
tag: 'mcp',
|
|
224
|
+
},
|
|
209
225
|
];
|
|
210
226
|
|
|
211
227
|
backServices = [
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
font-size: 14px;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
.
|
|
25
|
+
.actionButton {
|
|
26
26
|
padding: 8px 16px;
|
|
27
27
|
border-radius: 4px;
|
|
28
28
|
background-color: var(--button-primary-background);
|
|
@@ -30,6 +30,9 @@
|
|
|
30
30
|
border: none;
|
|
31
31
|
cursor: pointer;
|
|
32
32
|
font-size: 13px;
|
|
33
|
+
.actionButtonIcon {
|
|
34
|
+
margin-right: 5px;
|
|
35
|
+
}
|
|
33
36
|
|
|
34
37
|
&:hover {
|
|
35
38
|
background-color: var(--button-primary-hover-background);
|
|
@@ -1,190 +1,99 @@
|
|
|
1
1
|
import cls from 'classnames';
|
|
2
2
|
import React, { useCallback } from 'react';
|
|
3
3
|
|
|
4
|
-
import { Badge, Button, Popover, PopoverTriggerType } from '@opensumi/ide-components';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { PreferenceScope, localize } from '@opensumi/ide-core-common';
|
|
8
|
-
import { IMessageService } from '@opensumi/ide-overlay';
|
|
4
|
+
import { Badge, Button, Icon, Popover, PopoverTriggerType } from '@opensumi/ide-components';
|
|
5
|
+
import { useInjectable } from '@opensumi/ide-core-browser';
|
|
6
|
+
import { MCPConfigServiceToken, localize } from '@opensumi/ide-core-common';
|
|
9
7
|
|
|
10
|
-
import { BUILTIN_MCP_SERVER_NAME
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import { MCPServerProxyService } from '../../mcp-server-proxy.service';
|
|
8
|
+
import { BUILTIN_MCP_SERVER_NAME } from '../../../../common';
|
|
9
|
+
import { MCPServer } from '../../../../common/types';
|
|
10
|
+
import { MCPConfigService } from '../mcp-config.service';
|
|
14
11
|
|
|
15
12
|
import styles from './mcp-config.module.less';
|
|
16
13
|
import { MCPServerForm, MCPServerFormData } from './mcp-server-form';
|
|
17
14
|
|
|
18
15
|
export const MCPConfigView: React.FC = () => {
|
|
19
|
-
const
|
|
20
|
-
const preferenceService = useInjectable<PreferenceService>(PreferenceService);
|
|
21
|
-
const messageService = useInjectable<IMessageService>(IMessageService);
|
|
22
|
-
const sumiMCPServerBackendProxy = useInjectable<ISumiMCPServerBackend>(SumiMCPServerProxyServicePath);
|
|
23
|
-
const logger = useInjectable<ILogger>(ILogger);
|
|
16
|
+
const mcpConfigService = useInjectable<MCPConfigService>(MCPConfigServiceToken);
|
|
24
17
|
const [servers, setServers] = React.useState<MCPServer[]>([]);
|
|
25
18
|
const [formVisible, setFormVisible] = React.useState(false);
|
|
26
19
|
const [editingServer, setEditingServer] = React.useState<MCPServerFormData | undefined>();
|
|
27
20
|
const [loadingServer, setLoadingServer] = React.useState<string | undefined>();
|
|
21
|
+
const [isReady, setIsReady] = React.useState(false);
|
|
22
|
+
|
|
28
23
|
const loadServers = useCallback(async () => {
|
|
29
|
-
const
|
|
30
|
-
const runningServers = await mcpServerProxyService.$getServers();
|
|
31
|
-
const builtinServer = runningServers.find((server) => server.name === BUILTIN_MCP_SERVER_NAME);
|
|
32
|
-
const allServers = userServers
|
|
33
|
-
.filter((server) => server.type === MCP_SERVER_TYPE.STDIO || server.type === MCP_SERVER_TYPE.SSE)
|
|
34
|
-
.map((server) => {
|
|
35
|
-
const runningServer = runningServers.find((s) => s.name === server.name);
|
|
36
|
-
return {
|
|
37
|
-
...server,
|
|
38
|
-
name: server.name,
|
|
39
|
-
isStarted: runningServer?.isStarted,
|
|
40
|
-
tools: runningServer?.tools,
|
|
41
|
-
};
|
|
42
|
-
}) as MCPServer[];
|
|
43
|
-
if (builtinServer) {
|
|
44
|
-
allServers.unshift(builtinServer);
|
|
45
|
-
}
|
|
24
|
+
const allServers = await mcpConfigService.getServers();
|
|
46
25
|
setServers(allServers);
|
|
47
|
-
}, [
|
|
26
|
+
}, [mcpConfigService]);
|
|
48
27
|
|
|
49
28
|
React.useEffect(() => {
|
|
50
29
|
loadServers();
|
|
51
|
-
const disposer =
|
|
30
|
+
const disposer = mcpConfigService.onMCPServersChange((isReady) => {
|
|
31
|
+
if (isReady) {
|
|
32
|
+
setIsReady(true);
|
|
33
|
+
}
|
|
52
34
|
loadServers();
|
|
53
35
|
});
|
|
54
36
|
|
|
55
37
|
return () => {
|
|
56
38
|
disposer.dispose();
|
|
57
39
|
};
|
|
58
|
-
}, []);
|
|
40
|
+
}, [loadServers]);
|
|
59
41
|
|
|
60
42
|
const handleServerControl = useCallback(
|
|
61
43
|
async (serverName: string, start: boolean) => {
|
|
62
44
|
try {
|
|
63
45
|
setLoadingServer(serverName);
|
|
64
|
-
|
|
65
|
-
await mcpServerProxyService.$startServer(serverName);
|
|
66
|
-
} else {
|
|
67
|
-
await mcpServerProxyService.$stopServer(serverName);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Update enabled state in preferences
|
|
71
|
-
const servers = preferenceService.get<MCPServerDescription[]>(AINativeSettingSectionsId.MCPServers, []);
|
|
72
|
-
let updatedServers = servers;
|
|
73
|
-
// 处理内置服务器的特殊情况
|
|
74
|
-
if (serverName === BUILTIN_MCP_SERVER_NAME) {
|
|
75
|
-
const builtinServerExists = servers.some((server) => server.name === BUILTIN_MCP_SERVER_NAME);
|
|
76
|
-
if (!builtinServerExists && !start) {
|
|
77
|
-
// 如果是停止内置服务器且之前没有配置,添加一个新的配置项
|
|
78
|
-
// 内置服务器不需要 command,因为它是直接集成在 IDE 中的
|
|
79
|
-
updatedServers = [
|
|
80
|
-
...servers,
|
|
81
|
-
{
|
|
82
|
-
name: BUILTIN_MCP_SERVER_NAME,
|
|
83
|
-
enabled: false,
|
|
84
|
-
type: MCP_SERVER_TYPE.BUILTIN,
|
|
85
|
-
},
|
|
86
|
-
];
|
|
87
|
-
} else {
|
|
88
|
-
// 如果已经存在配置,更新 enabled 状态
|
|
89
|
-
updatedServers = servers.map((server) => {
|
|
90
|
-
if (server.name === BUILTIN_MCP_SERVER_NAME) {
|
|
91
|
-
return { ...server, enabled: start };
|
|
92
|
-
}
|
|
93
|
-
return server;
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
} else {
|
|
97
|
-
// 处理其他外部服务器
|
|
98
|
-
updatedServers = servers.map((server) => {
|
|
99
|
-
if (server.name === serverName) {
|
|
100
|
-
return { ...server, enabled: start };
|
|
101
|
-
}
|
|
102
|
-
return server;
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
await preferenceService.set(AINativeSettingSectionsId.MCPServers, updatedServers, PreferenceScope.User);
|
|
46
|
+
await mcpConfigService.controlServer(serverName, start);
|
|
107
47
|
await loadServers();
|
|
108
48
|
setLoadingServer(undefined);
|
|
109
49
|
} catch (error) {
|
|
110
|
-
const msg = error.message || error;
|
|
111
|
-
logger.error(`Failed to ${start ? 'start' : 'stop'} server ${serverName}:`, msg);
|
|
112
|
-
messageService.error(msg);
|
|
113
50
|
setLoadingServer(undefined);
|
|
114
51
|
}
|
|
115
52
|
},
|
|
116
|
-
[
|
|
53
|
+
[mcpConfigService, loadServers],
|
|
117
54
|
);
|
|
118
55
|
|
|
119
56
|
const handleAddServer = useCallback(() => {
|
|
120
57
|
setEditingServer(undefined);
|
|
121
58
|
setFormVisible(true);
|
|
122
|
-
}, [
|
|
59
|
+
}, []);
|
|
123
60
|
|
|
124
61
|
const handleEditServer = useCallback(
|
|
125
|
-
(server: MCPServer) => {
|
|
126
|
-
const
|
|
127
|
-
const serverConfig = servers.find((s) => s.name === server.name);
|
|
128
|
-
|
|
62
|
+
async (server: MCPServer) => {
|
|
63
|
+
const serverConfig = await mcpConfigService.getServerConfigByName(server.name);
|
|
129
64
|
if (serverConfig) {
|
|
130
65
|
setEditingServer(serverConfig);
|
|
131
66
|
setFormVisible(true);
|
|
132
67
|
}
|
|
133
68
|
},
|
|
134
|
-
[
|
|
69
|
+
[mcpConfigService],
|
|
135
70
|
);
|
|
136
71
|
|
|
137
72
|
const handleDeleteServer = useCallback(
|
|
138
73
|
async (serverName: string) => {
|
|
139
|
-
|
|
140
|
-
const updatedServers = servers.filter((s) => s.name !== serverName);
|
|
141
|
-
sumiMCPServerBackendProxy.$removeServer(serverName);
|
|
142
|
-
await preferenceService.set(AINativeSettingSectionsId.MCPServers, updatedServers, PreferenceScope.User);
|
|
74
|
+
await mcpConfigService.deleteServer(serverName);
|
|
143
75
|
await loadServers();
|
|
144
76
|
},
|
|
145
|
-
[
|
|
77
|
+
[mcpConfigService, loadServers],
|
|
146
78
|
);
|
|
147
79
|
|
|
148
80
|
const handleSaveServer = useCallback(
|
|
149
81
|
async (data: MCPServerFormData) => {
|
|
150
|
-
|
|
151
|
-
const existingIndex = servers.findIndex((s) => s.name === data.name);
|
|
152
|
-
|
|
153
|
-
if (existingIndex >= 0) {
|
|
154
|
-
servers[existingIndex] = data;
|
|
155
|
-
} else {
|
|
156
|
-
servers.push(data);
|
|
157
|
-
}
|
|
158
|
-
setServers(servers as MCPServer[]);
|
|
82
|
+
await mcpConfigService.saveServer(data);
|
|
159
83
|
setFormVisible(false);
|
|
160
|
-
await sumiMCPServerBackendProxy.$addOrUpdateServer(data as MCPServerDescription);
|
|
161
|
-
await preferenceService.set(AINativeSettingSectionsId.MCPServers, servers, PreferenceScope.User);
|
|
162
84
|
await loadServers();
|
|
163
85
|
},
|
|
164
|
-
[
|
|
86
|
+
[mcpConfigService, loadServers],
|
|
165
87
|
);
|
|
166
88
|
|
|
167
|
-
const getReadableServerType = useCallback((type: string) => {
|
|
168
|
-
switch (type) {
|
|
169
|
-
case MCP_SERVER_TYPE.STDIO:
|
|
170
|
-
return localize('ai.native.mcp.type.stdio');
|
|
171
|
-
case MCP_SERVER_TYPE.SSE:
|
|
172
|
-
return localize('ai.native.mcp.type.sse');
|
|
173
|
-
case MCP_SERVER_TYPE.BUILTIN:
|
|
174
|
-
return localize('ai.native.mcp.type.builtin');
|
|
175
|
-
default:
|
|
176
|
-
return type;
|
|
177
|
-
}
|
|
178
|
-
}, []);
|
|
179
|
-
|
|
180
89
|
const handleSyncServer = useCallback(
|
|
181
90
|
async (server: MCPServer) => {
|
|
182
91
|
setLoadingServer(server.name);
|
|
183
|
-
await
|
|
92
|
+
await mcpConfigService.syncServer(server.name);
|
|
184
93
|
await loadServers();
|
|
185
94
|
setLoadingServer(undefined);
|
|
186
95
|
},
|
|
187
|
-
[
|
|
96
|
+
[mcpConfigService, loadServers],
|
|
188
97
|
);
|
|
189
98
|
|
|
190
99
|
return (
|
|
@@ -194,8 +103,9 @@ export const MCPConfigView: React.FC = () => {
|
|
|
194
103
|
<h2 className={styles.title}>MCP Servers</h2>
|
|
195
104
|
<p className={styles.description}>{localize('ai.native.mcp.manage.connections')}</p>
|
|
196
105
|
</div>
|
|
197
|
-
<button className={styles.
|
|
198
|
-
|
|
106
|
+
<button className={styles.actionButton} onClick={handleAddServer}>
|
|
107
|
+
<Icon icon='plus' className={styles.actionButtonIcon} />
|
|
108
|
+
{localize('ai.native.mcp.addMCPServer.title')}
|
|
199
109
|
</button>
|
|
200
110
|
</div>
|
|
201
111
|
<div className={styles.serversList}>
|
|
@@ -225,7 +135,7 @@ export const MCPConfigView: React.FC = () => {
|
|
|
225
135
|
>
|
|
226
136
|
<i
|
|
227
137
|
className={`codicon ${
|
|
228
|
-
loadingServer === server.name
|
|
138
|
+
loadingServer === server.name || (!isReady && server.name !== BUILTIN_MCP_SERVER_NAME)
|
|
229
139
|
? 'codicon-loading kt-icon-loading'
|
|
230
140
|
: server.isStarted
|
|
231
141
|
? 'codicon-check'
|
|
@@ -268,7 +178,9 @@ export const MCPConfigView: React.FC = () => {
|
|
|
268
178
|
{server.type && (
|
|
269
179
|
<div className={styles.detailRow}>
|
|
270
180
|
<span className={styles.detailLabel}>Type:</span>
|
|
271
|
-
<Badge className={cls(styles.serverType, styles.typeTag)}>
|
|
181
|
+
<Badge className={cls(styles.serverType, styles.typeTag)}>
|
|
182
|
+
{mcpConfigService.getReadableServerType(server.type)}
|
|
183
|
+
</Badge>
|
|
272
184
|
</div>
|
|
273
185
|
)}
|
|
274
186
|
</div>
|
|
@@ -294,11 +206,11 @@ export const MCPConfigView: React.FC = () => {
|
|
|
294
206
|
</div>
|
|
295
207
|
</div>
|
|
296
208
|
)}
|
|
297
|
-
{server.
|
|
209
|
+
{server.url && (
|
|
298
210
|
<div className={styles.serverDetail}>
|
|
299
211
|
<div className={styles.detailRow}>
|
|
300
212
|
<span className={styles.detailLabel}>Server Link:</span>
|
|
301
|
-
<span className={cls(styles.detailContent, styles.link)}>{server.
|
|
213
|
+
<span className={cls(styles.detailContent, styles.link)}>{server.url}</span>
|
|
302
214
|
</div>
|
|
303
215
|
</div>
|
|
304
216
|
)}
|
|
@@ -17,7 +17,7 @@ export interface MCPServerFormData {
|
|
|
17
17
|
args?: string[];
|
|
18
18
|
env?: Record<string, string>;
|
|
19
19
|
type: MCP_SERVER_TYPE;
|
|
20
|
-
|
|
20
|
+
url?: string;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
interface Props {
|
|
@@ -81,9 +81,9 @@ export const MCPServerForm: FC<Props> = ({ visible, initialData, onSave, onCance
|
|
|
81
81
|
return false;
|
|
82
82
|
}
|
|
83
83
|
if (formData.type === MCP_SERVER_TYPE.SSE) {
|
|
84
|
-
const isServerHostValid = formData.
|
|
84
|
+
const isServerHostValid = formData.url?.trim() !== '';
|
|
85
85
|
if (!isServerHostValid) {
|
|
86
|
-
messageService.error(localize('ai.native.mcp.
|
|
86
|
+
messageService.error(localize('ai.native.mcp.url.isRequired'));
|
|
87
87
|
}
|
|
88
88
|
return isServerHostValid;
|
|
89
89
|
}
|
|
@@ -107,7 +107,7 @@ export const MCPServerForm: FC<Props> = ({ visible, initialData, onSave, onCance
|
|
|
107
107
|
...formData,
|
|
108
108
|
};
|
|
109
109
|
if (formData.type === MCP_SERVER_TYPE.SSE) {
|
|
110
|
-
form.
|
|
110
|
+
form.url = form.url?.trim();
|
|
111
111
|
} else {
|
|
112
112
|
const args = argsText.split(' ').filter(Boolean);
|
|
113
113
|
const env = envText
|
|
@@ -127,7 +127,7 @@ export const MCPServerForm: FC<Props> = ({ visible, initialData, onSave, onCance
|
|
|
127
127
|
setFormData({
|
|
128
128
|
...formData,
|
|
129
129
|
command: '',
|
|
130
|
-
|
|
130
|
+
url: '',
|
|
131
131
|
args: [],
|
|
132
132
|
env: {},
|
|
133
133
|
});
|
|
@@ -160,7 +160,7 @@ export const MCPServerForm: FC<Props> = ({ visible, initialData, onSave, onCance
|
|
|
160
160
|
|
|
161
161
|
const handleServerHostChange = useCallback(
|
|
162
162
|
(e: ChangeEvent<HTMLTextAreaElement>) => {
|
|
163
|
-
setFormData({ ...formData,
|
|
163
|
+
setFormData({ ...formData, url: e.target.value });
|
|
164
164
|
},
|
|
165
165
|
[formData],
|
|
166
166
|
);
|
|
@@ -173,7 +173,7 @@ export const MCPServerForm: FC<Props> = ({ visible, initialData, onSave, onCance
|
|
|
173
173
|
command: '',
|
|
174
174
|
args: [],
|
|
175
175
|
env: {},
|
|
176
|
-
|
|
176
|
+
url: '',
|
|
177
177
|
});
|
|
178
178
|
},
|
|
179
179
|
[formData],
|
|
@@ -217,11 +217,11 @@ export const MCPServerForm: FC<Props> = ({ visible, initialData, onSave, onCance
|
|
|
217
217
|
return (
|
|
218
218
|
<>
|
|
219
219
|
<div className={styles.formItem}>
|
|
220
|
-
<label>{localize('ai.native.mcp.
|
|
220
|
+
<label>{localize('ai.native.mcp.url')}</label>
|
|
221
221
|
<textarea
|
|
222
|
-
value={formData.
|
|
222
|
+
value={formData.url}
|
|
223
223
|
onChange={handleServerHostChange}
|
|
224
|
-
placeholder={localize('ai.native.mcp.
|
|
224
|
+
placeholder={localize('ai.native.mcp.url.placeHolder')}
|
|
225
225
|
rows={3}
|
|
226
226
|
/>
|
|
227
227
|
</div>
|