@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.
Files changed (93) hide show
  1. package/lib/browser/ai-core.contribution.d.ts +2 -0
  2. package/lib/browser/ai-core.contribution.d.ts.map +1 -1
  3. package/lib/browser/ai-core.contribution.js +58 -12
  4. package/lib/browser/ai-core.contribution.js.map +1 -1
  5. package/lib/browser/components/ChatInput.js +1 -1
  6. package/lib/browser/components/ChatInput.js.map +1 -1
  7. package/lib/browser/components/ChatMentionInput.js +1 -1
  8. package/lib/browser/components/ChatMentionInput.js.map +1 -1
  9. package/lib/browser/components/ChatToolResult.module.less +0 -2
  10. package/lib/browser/components/mention-input/mention-input.d.ts.map +1 -1
  11. package/lib/browser/components/mention-input/mention-input.js +9 -7
  12. package/lib/browser/components/mention-input/mention-input.js.map +1 -1
  13. package/lib/browser/index.d.ts.map +1 -1
  14. package/lib/browser/index.js +15 -0
  15. package/lib/browser/index.js.map +1 -1
  16. package/lib/browser/mcp/config/components/mcp-config.module.less +4 -1
  17. package/lib/browser/mcp/config/components/mcp-config.view.d.ts.map +1 -1
  18. package/lib/browser/mcp/config/components/mcp-config.view.js +27 -119
  19. package/lib/browser/mcp/config/components/mcp-config.view.js.map +1 -1
  20. package/lib/browser/mcp/config/components/mcp-server-form.d.ts +1 -1
  21. package/lib/browser/mcp/config/components/mcp-server-form.d.ts.map +1 -1
  22. package/lib/browser/mcp/config/components/mcp-server-form.js +8 -8
  23. package/lib/browser/mcp/config/components/mcp-server-form.js.map +1 -1
  24. package/lib/browser/mcp/config/mcp-config.commands.d.ts +11 -4
  25. package/lib/browser/mcp/config/mcp-config.commands.d.ts.map +1 -1
  26. package/lib/browser/mcp/config/mcp-config.commands.js +23 -6
  27. package/lib/browser/mcp/config/mcp-config.commands.js.map +1 -1
  28. package/lib/browser/mcp/config/mcp-config.contribution.d.ts +4 -1
  29. package/lib/browser/mcp/config/mcp-config.contribution.d.ts.map +1 -1
  30. package/lib/browser/mcp/config/mcp-config.contribution.js +20 -1
  31. package/lib/browser/mcp/config/mcp-config.contribution.js.map +1 -1
  32. package/lib/browser/mcp/config/mcp-config.service.d.ts +30 -0
  33. package/lib/browser/mcp/config/mcp-config.service.d.ts.map +1 -0
  34. package/lib/browser/mcp/config/mcp-config.service.js +236 -0
  35. package/lib/browser/mcp/config/mcp-config.service.js.map +1 -0
  36. package/lib/browser/mcp/mcp-folder-preference-provider.d.ts +6 -0
  37. package/lib/browser/mcp/mcp-folder-preference-provider.d.ts.map +1 -0
  38. package/lib/browser/mcp/mcp-folder-preference-provider.js +29 -0
  39. package/lib/browser/mcp/mcp-folder-preference-provider.js.map +1 -0
  40. package/lib/browser/mcp/mcp-preferences-contribution.d.ts +15 -0
  41. package/lib/browser/mcp/mcp-preferences-contribution.d.ts.map +1 -0
  42. package/lib/browser/mcp/mcp-preferences-contribution.js +49 -0
  43. package/lib/browser/mcp/mcp-preferences-contribution.js.map +1 -0
  44. package/lib/browser/mcp/mcp-preferences.d.ts +6 -0
  45. package/lib/browser/mcp/mcp-preferences.d.ts.map +1 -0
  46. package/lib/browser/mcp/mcp-preferences.js +47 -0
  47. package/lib/browser/mcp/mcp-preferences.js.map +1 -0
  48. package/lib/browser/preferences/schema.d.ts.map +1 -1
  49. package/lib/browser/preferences/schema.js +3 -0
  50. package/lib/browser/preferences/schema.js.map +1 -1
  51. package/lib/common/mcp-server-manager.d.ts +2 -1
  52. package/lib/common/mcp-server-manager.d.ts.map +1 -1
  53. package/lib/common/mcp-server-manager.js +2 -1
  54. package/lib/common/mcp-server-manager.js.map +1 -1
  55. package/lib/common/types.d.ts +1 -1
  56. package/lib/common/types.d.ts.map +1 -1
  57. package/lib/node/mcp/sumi-mcp-server.d.ts +3 -3
  58. package/lib/node/mcp/sumi-mcp-server.js +1 -1
  59. package/lib/node/mcp/sumi-mcp-server.js.map +1 -1
  60. package/lib/node/mcp-server-manager-impl.d.ts.map +1 -1
  61. package/lib/node/mcp-server-manager-impl.js +9 -4
  62. package/lib/node/mcp-server-manager-impl.js.map +1 -1
  63. package/lib/node/mcp-server.sse.d.ts +10 -37
  64. package/lib/node/mcp-server.sse.d.ts.map +1 -1
  65. package/lib/node/mcp-server.sse.js +43 -15
  66. package/lib/node/mcp-server.sse.js.map +1 -1
  67. package/lib/node/mcp-server.stdio.d.ts +7 -34
  68. package/lib/node/mcp-server.stdio.d.ts.map +1 -1
  69. package/lib/node/mcp-server.stdio.js +27 -2
  70. package/lib/node/mcp-server.stdio.js.map +1 -1
  71. package/package.json +24 -24
  72. package/src/browser/ai-core.contribution.ts +71 -16
  73. package/src/browser/components/ChatInput.tsx +2 -2
  74. package/src/browser/components/ChatMentionInput.tsx +2 -2
  75. package/src/browser/components/ChatToolResult.module.less +0 -2
  76. package/src/browser/components/mention-input/mention-input.tsx +27 -23
  77. package/src/browser/index.ts +16 -0
  78. package/src/browser/mcp/config/components/mcp-config.module.less +4 -1
  79. package/src/browser/mcp/config/components/mcp-config.view.tsx +37 -125
  80. package/src/browser/mcp/config/components/mcp-server-form.tsx +10 -10
  81. package/src/browser/mcp/config/mcp-config.commands.ts +22 -6
  82. package/src/browser/mcp/config/mcp-config.contribution.ts +24 -2
  83. package/src/browser/mcp/config/mcp-config.service.ts +285 -0
  84. package/src/browser/mcp/mcp-folder-preference-provider.ts +23 -0
  85. package/src/browser/mcp/mcp-preferences-contribution.ts +62 -0
  86. package/src/browser/mcp/mcp-preferences.ts +48 -0
  87. package/src/browser/preferences/schema.ts +3 -0
  88. package/src/common/mcp-server-manager.ts +3 -1
  89. package/src/common/types.ts +1 -1
  90. package/src/node/mcp/sumi-mcp-server.ts +1 -1
  91. package/src/node/mcp-server-manager-impl.ts +8 -4
  92. package/src/node/mcp-server.sse.ts +41 -14
  93. 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
- // preferences 获取并初始化外部 MCP Servers
416
- const mcpServers = this.preferenceService.getValid<MCPServerDescription[]>(AINativeSettingSectionsId.MCPServers);
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
- // 查找内置 MCP Server 的配置
419
- const builtinServer = mcpServers?.find(
420
- (server) => server.name === BUILTIN_MCP_SERVER_NAME && server.type === MCP_SERVER_TYPE.BUILTIN,
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(builtinServer?.enabled ?? true);
425
-
426
- // 初始化其他外部 MCP Servers
427
- if (mcpServers && mcpServers.length > 0) {
428
- const externalServers = mcpServers.filter((server) => server.name !== BUILTIN_MCP_SERVER_NAME);
429
- if (externalServers.length > 0) {
430
- this.sumiMCPServerBackendProxy.$initExternalMCPServers(externalServers);
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 { OPEN_MCP_CONFIG_COMMAND } from '../mcp/config/mcp-config.commands';
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(OPEN_MCP_CONFIG_COMMAND.id);
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 { OPEN_MCP_CONFIG_COMMAND } from '../mcp/config/mcp-config.commands';
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(OPEN_MCP_CONFIG_COMMAND.id);
74
+ commandService.executeCommand(MCPConfigCommands.OPEN_MCP_CONFIG.id);
75
75
  }, [commandService]);
76
76
 
77
77
  useEffect(() => {
@@ -7,8 +7,6 @@
7
7
  margin: 8px 0;
8
8
  width: 100%;
9
9
  padding: 5px;
10
- background-color: var(--editor-background);
11
- color: var(--editor-foreground);
12
10
  }
13
11
 
14
12
  .image_content {
@@ -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
- range.setStartAfter(fragment);
558
- range.setEndAfter(fragment);
559
- selection.removeAllRanges();
560
- selection.addRange(range);
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
- <Popover
1064
- overlayClassName={styles.popover_icon}
1065
- id={'ai-chat-clear-context'}
1066
- position={PopoverPosition.top}
1067
- content={localize('aiNative.chat.context.clear')}
1068
- >
1069
- <div className={styles.context_container} onClick={handleClearContext}>
1070
- <div className={styles.context_icon}>
1071
- <Icon icon='out-link' />
1072
- <Icon icon='close' />
1073
- </div>
1074
- <div className={styles.context_description}>
1075
- {formatLocalize(
1076
- 'aiNative.chat.context.description',
1077
- attachedFiles.files.length + attachedFiles.folders.length,
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
- </div>
1081
- </Popover>
1084
+ </Popover>
1085
+ )}
1082
1086
  <Popover
1083
1087
  overlayClassName={styles.popover_icon}
1084
1088
  id={'ai-chat-send'}
@@ -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
- .addButton {
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 { AINativeSettingSectionsId, ILogger, useInjectable } from '@opensumi/ide-core-browser';
6
- import { PreferenceService } from '@opensumi/ide-core-browser/lib/preferences';
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, ISumiMCPServerBackend, SumiMCPServerProxyServicePath } from '../../../../common';
11
- import { MCPServerDescription } from '../../../../common/mcp-server-manager';
12
- import { MCPServer, MCP_SERVER_TYPE } from '../../../../common/types';
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 mcpServerProxyService = useInjectable<MCPServerProxyService>(MCPServerProxyService);
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 userServers = preferenceService.get<MCPServerDescription[]>(AINativeSettingSectionsId.MCPServers, []);
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
- }, [mcpServerProxyService]);
26
+ }, [mcpConfigService]);
48
27
 
49
28
  React.useEffect(() => {
50
29
  loadServers();
51
- const disposer = mcpServerProxyService.onChangeMCPServers(() => {
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
- if (start) {
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
- [mcpServerProxyService, preferenceService, sumiMCPServerBackendProxy, loadServers],
53
+ [mcpConfigService, loadServers],
117
54
  );
118
55
 
119
56
  const handleAddServer = useCallback(() => {
120
57
  setEditingServer(undefined);
121
58
  setFormVisible(true);
122
- }, [editingServer, formVisible]);
59
+ }, []);
123
60
 
124
61
  const handleEditServer = useCallback(
125
- (server: MCPServer) => {
126
- const servers = preferenceService.get<MCPServerFormData[]>(AINativeSettingSectionsId.MCPServers, []);
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
- [editingServer, formVisible],
69
+ [mcpConfigService],
135
70
  );
136
71
 
137
72
  const handleDeleteServer = useCallback(
138
73
  async (serverName: string) => {
139
- const servers = preferenceService.get<MCPServerFormData[]>(AINativeSettingSectionsId.MCPServers, []);
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
- [editingServer, formVisible],
77
+ [mcpConfigService, loadServers],
146
78
  );
147
79
 
148
80
  const handleSaveServer = useCallback(
149
81
  async (data: MCPServerFormData) => {
150
- const servers = preferenceService.get<MCPServerFormData[]>(AINativeSettingSectionsId.MCPServers, []);
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
- [servers, formVisible, loadServers],
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 sumiMCPServerBackendProxy.$syncServer(server.name);
92
+ await mcpConfigService.syncServer(server.name);
184
93
  await loadServers();
185
94
  setLoadingServer(undefined);
186
95
  },
187
- [loadServers, loadingServer],
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.addButton} onClick={handleAddServer}>
198
- + {localize('ai.native.mcp.addMCPServer.title')}
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)}>{getReadableServerType(server.type)}</Badge>
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.serverHost && (
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.serverHost}</span>
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
- serverHost?: string;
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.serverHost?.trim() !== '';
84
+ const isServerHostValid = formData.url?.trim() !== '';
85
85
  if (!isServerHostValid) {
86
- messageService.error(localize('ai.native.mcp.serverHost.isRequired'));
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.serverHost = form.serverHost?.trim();
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
- serverHost: '',
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, serverHost: e.target.value });
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
- serverHost: '',
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.serverHost')}</label>
220
+ <label>{localize('ai.native.mcp.url')}</label>
221
221
  <textarea
222
- value={formData.serverHost}
222
+ value={formData.url}
223
223
  onChange={handleServerHostChange}
224
- placeholder={localize('ai.native.mcp.serverHost.placeHolder')}
224
+ placeholder={localize('ai.native.mcp.url.placeHolder')}
225
225
  rows={3}
226
226
  />
227
227
  </div>