@opensumi/ide-ai-native 3.8.1-next-1740452092.0 → 3.8.1-next-1740463566.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 (114) hide show
  1. package/lib/browser/ai-core.contribution.d.ts.map +1 -1
  2. package/lib/browser/ai-core.contribution.js +10 -3
  3. package/lib/browser/ai-core.contribution.js.map +1 -1
  4. package/lib/browser/chat/chat-manager.service.d.ts +5 -0
  5. package/lib/browser/chat/chat-manager.service.d.ts.map +1 -1
  6. package/lib/browser/chat/chat-manager.service.js +18 -1
  7. package/lib/browser/chat/chat-manager.service.js.map +1 -1
  8. package/lib/browser/chat/chat-model.d.ts +2 -0
  9. package/lib/browser/chat/chat-model.d.ts.map +1 -1
  10. package/lib/browser/chat/chat-model.js +8 -2
  11. package/lib/browser/chat/chat-model.js.map +1 -1
  12. package/lib/browser/chat/chat.view.d.ts.map +1 -1
  13. package/lib/browser/chat/chat.view.js +31 -8
  14. package/lib/browser/chat/chat.view.js.map +1 -1
  15. package/lib/browser/context/llm-context.service.d.ts.map +1 -1
  16. package/lib/browser/context/llm-context.service.js +3 -0
  17. package/lib/browser/context/llm-context.service.js.map +1 -1
  18. package/lib/browser/index.d.ts.map +1 -1
  19. package/lib/browser/index.js +4 -0
  20. package/lib/browser/index.js.map +1 -1
  21. package/lib/browser/mcp/config/components/mcp-config.module.less +178 -0
  22. package/lib/browser/mcp/config/components/mcp-config.view.d.ts +3 -0
  23. package/lib/browser/mcp/config/components/mcp-config.view.d.ts.map +1 -0
  24. package/lib/browser/mcp/config/components/mcp-config.view.js +150 -0
  25. package/lib/browser/mcp/config/components/mcp-config.view.js.map +1 -0
  26. package/lib/browser/mcp/config/components/mcp-server-form.d.ts +16 -0
  27. package/lib/browser/mcp/config/components/mcp-server-form.d.ts.map +1 -0
  28. package/lib/browser/mcp/config/components/mcp-server-form.js +84 -0
  29. package/lib/browser/mcp/config/components/mcp-server-form.js.map +1 -0
  30. package/lib/browser/mcp/config/components/mcp-server-form.module.less +78 -0
  31. package/lib/browser/mcp/config/mcp-config.commands.d.ts +10 -0
  32. package/lib/browser/mcp/config/mcp-config.commands.d.ts.map +1 -0
  33. package/lib/browser/mcp/config/mcp-config.commands.js +35 -0
  34. package/lib/browser/mcp/config/mcp-config.commands.js.map +1 -0
  35. package/lib/browser/mcp/config/mcp-config.contribution.d.ts +16 -0
  36. package/lib/browser/mcp/config/mcp-config.contribution.d.ts.map +1 -0
  37. package/lib/browser/mcp/config/mcp-config.contribution.js +62 -0
  38. package/lib/browser/mcp/config/mcp-config.contribution.js.map +1 -0
  39. package/lib/browser/mcp/mcp-server-proxy.service.d.ts +6 -0
  40. package/lib/browser/mcp/mcp-server-proxy.service.d.ts.map +1 -1
  41. package/lib/browser/mcp/mcp-server-proxy.service.js +10 -1
  42. package/lib/browser/mcp/mcp-server-proxy.service.js.map +1 -1
  43. package/lib/browser/mcp/mcp-server.feature.registry.d.ts.map +1 -1
  44. package/lib/browser/mcp/mcp-server.feature.registry.js +3 -2
  45. package/lib/browser/mcp/mcp-server.feature.registry.js.map +1 -1
  46. package/lib/browser/mcp/tools/handlers/RunCommand.d.ts.map +1 -1
  47. package/lib/browser/mcp/tools/handlers/RunCommand.js +2 -0
  48. package/lib/browser/mcp/tools/handlers/RunCommand.js.map +1 -1
  49. package/lib/browser/preferences/schema.d.ts.map +1 -1
  50. package/lib/browser/preferences/schema.js +16 -0
  51. package/lib/browser/preferences/schema.js.map +1 -1
  52. package/lib/common/index.d.ts +8 -1
  53. package/lib/common/index.d.ts.map +1 -1
  54. package/lib/common/index.js +3 -1
  55. package/lib/common/index.js.map +1 -1
  56. package/lib/common/mcp-server-manager.d.ts +17 -1
  57. package/lib/common/mcp-server-manager.d.ts.map +1 -1
  58. package/lib/common/mcp-server-manager.js.map +1 -1
  59. package/lib/common/prompts/context-prompt-provider.d.ts +2 -3
  60. package/lib/common/prompts/context-prompt-provider.d.ts.map +1 -1
  61. package/lib/common/prompts/context-prompt-provider.js +21 -22
  62. package/lib/common/prompts/context-prompt-provider.js.map +1 -1
  63. package/lib/common/tool-invocation-registry.d.ts +2 -2
  64. package/lib/common/tool-invocation-registry.d.ts.map +1 -1
  65. package/lib/common/tool-invocation-registry.js +1 -1
  66. package/lib/common/tool-invocation-registry.js.map +1 -1
  67. package/lib/common/types.d.ts +6 -0
  68. package/lib/common/types.d.ts.map +1 -1
  69. package/lib/common/utils.d.ts.map +1 -1
  70. package/lib/common/utils.js +2 -1
  71. package/lib/common/utils.js.map +1 -1
  72. package/lib/node/mcp/sumi-mcp-server.d.ts +17 -3
  73. package/lib/node/mcp/sumi-mcp-server.d.ts.map +1 -1
  74. package/lib/node/mcp/sumi-mcp-server.js +59 -6
  75. package/lib/node/mcp/sumi-mcp-server.js.map +1 -1
  76. package/lib/node/mcp-server-manager-impl.d.ts +4 -3
  77. package/lib/node/mcp-server-manager-impl.d.ts.map +1 -1
  78. package/lib/node/mcp-server-manager-impl.js +26 -6
  79. package/lib/node/mcp-server-manager-impl.js.map +1 -1
  80. package/lib/node/mcp-server.d.ts +5 -16
  81. package/lib/node/mcp-server.d.ts.map +1 -1
  82. package/lib/node/mcp-server.js +12 -6
  83. package/lib/node/mcp-server.js.map +1 -1
  84. package/lib/node/openai/openai-language-model.d.ts +4 -3
  85. package/lib/node/openai/openai-language-model.d.ts.map +1 -1
  86. package/lib/node/openai/openai-language-model.js +3 -2
  87. package/lib/node/openai/openai-language-model.js.map +1 -1
  88. package/package.json +27 -27
  89. package/src/browser/ai-core.contribution.ts +13 -4
  90. package/src/browser/chat/chat-manager.service.ts +17 -1
  91. package/src/browser/chat/chat-model.ts +18 -3
  92. package/src/browser/chat/chat.view.tsx +48 -8
  93. package/src/browser/context/llm-context.service.ts +4 -0
  94. package/src/browser/index.ts +4 -0
  95. package/src/browser/mcp/config/components/mcp-config.module.less +178 -0
  96. package/src/browser/mcp/config/components/mcp-config.view.tsx +215 -0
  97. package/src/browser/mcp/config/components/mcp-server-form.module.less +78 -0
  98. package/src/browser/mcp/config/components/mcp-server-form.tsx +144 -0
  99. package/src/browser/mcp/config/mcp-config.commands.ts +29 -0
  100. package/src/browser/mcp/config/mcp-config.contribution.ts +65 -0
  101. package/src/browser/mcp/mcp-server-proxy.service.ts +14 -2
  102. package/src/browser/mcp/mcp-server.feature.registry.ts +3 -2
  103. package/src/browser/mcp/tools/handlers/RunCommand.ts +2 -0
  104. package/src/browser/preferences/schema.ts +16 -0
  105. package/src/common/index.ts +7 -1
  106. package/src/common/mcp-server-manager.ts +17 -1
  107. package/src/common/prompts/context-prompt-provider.ts +26 -28
  108. package/src/common/tool-invocation-registry.ts +2 -2
  109. package/src/common/types.ts +6 -0
  110. package/src/common/utils.ts +3 -1
  111. package/src/node/mcp/sumi-mcp-server.ts +67 -9
  112. package/src/node/mcp-server-manager-impl.ts +30 -9
  113. package/src/node/mcp-server.ts +11 -14
  114. package/src/node/openai/openai-language-model.ts +7 -4
@@ -72,6 +72,7 @@ import {
72
72
  AI_CHAT_LOGO_AVATAR_ID,
73
73
  AI_CHAT_VIEW_ID,
74
74
  AI_MENU_BAR_DEBUG_TOOLBAR,
75
+ BUILTIN_MCP_SERVER_NAME,
75
76
  ChatProxyServiceToken,
76
77
  IChatInternalService,
77
78
  IChatManagerService,
@@ -327,15 +328,23 @@ export class AINativeBrowserContribution
327
328
  }
328
329
 
329
330
  if (supportsMCP) {
330
- // 初始化内置 MCP Server
331
- this.sumiMCPServerBackendProxy.initBuiltinMCPServer();
332
-
333
331
  // 从 preferences 获取并初始化外部 MCP Servers
334
332
  const mcpServers = this.preferenceService.getValid<MCPServerDescription[]>(
335
333
  AINativeSettingSectionsId.MCPServers,
336
334
  );
335
+
336
+ // 查找内置 MCP Server 的配置
337
+ const builtinServer = mcpServers?.find((server) => server.name === BUILTIN_MCP_SERVER_NAME);
338
+
339
+ // 总是初始化内置服务器,根据配置决定是否启用
340
+ this.sumiMCPServerBackendProxy.initBuiltinMCPServer(builtinServer?.enabled ?? true);
341
+
342
+ // 初始化其他外部 MCP Servers
337
343
  if (mcpServers && mcpServers.length > 0) {
338
- this.sumiMCPServerBackendProxy.initExternalMCPServers(mcpServers);
344
+ const externalServers = mcpServers.filter((server) => server.name !== BUILTIN_MCP_SERVER_NAME);
345
+ if (externalServers.length > 0) {
346
+ this.sumiMCPServerBackendProxy.initExternalMCPServers(externalServers);
347
+ }
339
348
  }
340
349
  }
341
350
  });
@@ -13,6 +13,8 @@ import {
13
13
  import { ChatMessageRole, IChatMessage, IHistoryChatMessage } from '@opensumi/ide-core-common/lib/types/ai-native';
14
14
 
15
15
  import { IChatAgentService, IChatFollowup, IChatRequestMessage, IChatResponseErrorDetails } from '../../common';
16
+ import { LLMContextService, LLMContextServiceToken } from '../../common/llm-context';
17
+ import { ChatAgentPromptProvider } from '../../common/prompts/context-prompt-provider';
16
18
  import { MsgHistoryManager } from '../model/msg-history-manager';
17
19
 
18
20
  import { ChatModel, ChatRequestModel, ChatResponseModel, IChatProgressResponseContent } from './chat-model';
@@ -49,6 +51,12 @@ export class ChatManagerService extends Disposable {
49
51
  @Autowired(StorageProvider)
50
52
  private storageProvider: StorageProvider;
51
53
 
54
+ @Autowired(ChatAgentPromptProvider)
55
+ protected readonly promptProvider: ChatAgentPromptProvider;
56
+
57
+ @Autowired(LLMContextServiceToken)
58
+ protected readonly contextService: LLMContextService;
59
+
52
60
  private _chatStorage: IStorage;
53
61
 
54
62
  protected fromJSON(data: ISessionModel[]) {
@@ -98,11 +106,19 @@ export class ChatManagerService extends Disposable {
98
106
  }
99
107
 
100
108
  startSession() {
101
- const model = new ChatModel();
109
+ const model = new ChatModel({
110
+ provideContext: this.provideContextPrompt.bind(this),
111
+ });
102
112
  this.#sessionModels.set(model.sessionId, model);
103
113
  return model;
104
114
  }
105
115
 
116
+ private provideContextPrompt(message: string) {
117
+ const context = this.contextService.serialize();
118
+ const fullMessage = this.promptProvider.provideContextPrompt(context, message);
119
+ return fullMessage;
120
+ }
121
+
106
122
  getSession(sessionId: string): ChatModel | undefined {
107
123
  return this.#sessionModels.get(sessionId);
108
124
  }
@@ -274,13 +274,22 @@ export class ChatRequestModel implements IChatRequestModel {
274
274
  export class ChatModel extends Disposable implements IChatModel {
275
275
  private static requestIdPool = 0;
276
276
 
277
- constructor(initParams?: { sessionId?: string; history?: MsgHistoryManager; requests?: ChatRequestModel[] }) {
277
+ private provideContextPrompt?: (string) => string;
278
+
279
+ constructor(initParams?: {
280
+ sessionId?: string;
281
+ history?: MsgHistoryManager;
282
+ requests?: ChatRequestModel[];
283
+ provideContext?: (msg: string) => string;
284
+ }) {
278
285
  super();
279
286
  this.#sessionId = initParams?.sessionId ?? uuid();
280
287
  this.history = initParams?.history ?? new MsgHistoryManager();
281
288
  if (initParams?.requests) {
282
289
  this.#requests = new Map(initParams.requests.map((r) => [r.requestId, r]));
283
290
  }
291
+
292
+ this.provideContextPrompt = initParams?.provideContext;
284
293
  }
285
294
 
286
295
  #sessionId: string;
@@ -300,9 +309,15 @@ export class ChatModel extends Disposable implements IChatModel {
300
309
  readonly history: MsgHistoryManager;
301
310
 
302
311
  addRequest(message: IChatRequestMessage): ChatRequestModel {
312
+ const msg = message;
313
+ // first msg
314
+ if (ChatModel.requestIdPool === 0 && this.provideContextPrompt) {
315
+ msg.prompt = this.provideContextPrompt(msg.prompt);
316
+ }
317
+
303
318
  const requestId = `${this.sessionId}_request_${ChatModel.requestIdPool++}`;
304
- const response = new ChatResponseModel(requestId, this, message.agentId);
305
- const request = new ChatRequestModel(requestId, this, message, response);
319
+ const response = new ChatResponseModel(requestId, this, msg.agentId);
320
+ const request = new ChatRequestModel(requestId, this, msg, response);
306
321
 
307
322
  this.#requests.set(requestId, request);
308
323
  return request;
@@ -3,6 +3,7 @@ import { MessageList } from 'react-chat-elements';
3
3
 
4
4
  import {
5
5
  AINativeConfigService,
6
+ CommandService,
6
7
  getIcon,
7
8
  useEventEffect,
8
9
  useInjectable,
@@ -41,8 +42,6 @@ import {
41
42
  IChatMessageStructure,
42
43
  TokenMCPServerProxyService,
43
44
  } from '../../common';
44
- import { LLMContextService, LLMContextServiceToken } from '../../common/llm-context';
45
- import { ChatAgentPromptProvider } from '../../common/prompts/context-prompt-provider';
46
45
  import { ChatContext } from '../components/ChatContext';
47
46
  import { CodeBlockWrapperInput } from '../components/ChatEditor';
48
47
  import ChatHistory, { IChatHistoryItem } from '../components/ChatHistory';
@@ -52,6 +51,7 @@ import { ChatNotify, ChatReply } from '../components/ChatReply';
52
51
  import { SlashCustomRender } from '../components/SlashCustomRender';
53
52
  import { MessageData, createMessageByAI, createMessageByUser } from '../components/utils';
54
53
  import { WelcomeMessage } from '../components/WelcomeMsg';
54
+ import { OPEN_MCP_CONFIG_COMMAND } from '../mcp/config/mcp-config.commands';
55
55
  import { MCPServerProxyService } from '../mcp/mcp-server-proxy.service';
56
56
  import { MCPToolsDialog } from '../mcp/mcp-tools-dialog.view';
57
57
  import { ChatViewHeaderRender, TSlashCommandCustomRender } from '../types';
@@ -63,6 +63,7 @@ import { ChatFeatureRegistry } from './chat.feature.registry';
63
63
  import { ChatInternalService } from './chat.internal.service';
64
64
  import styles from './chat.module.less';
65
65
  import { ChatRenderRegistry } from './chat.render.registry';
66
+
66
67
  const SCROLL_CLASSNAME = 'chat_scroll';
67
68
 
68
69
  interface TDispatchAction {
@@ -79,13 +80,15 @@ export const AIChatView = () => {
79
80
  const chatAgentService = useInjectable<IChatAgentService>(IChatAgentService);
80
81
  const chatFeatureRegistry = useInjectable<ChatFeatureRegistry>(ChatFeatureRegistryToken);
81
82
  const chatRenderRegistry = useInjectable<ChatRenderRegistry>(ChatRenderRegistryToken);
82
- const contextService = useInjectable<LLMContextService>(LLMContextServiceToken);
83
- const promptProvider = useInjectable<ChatAgentPromptProvider>(ChatAgentPromptProvider);
83
+ const mcpServerProxyService = useInjectable<MCPServerProxyService>(TokenMCPServerProxyService);
84
84
 
85
85
  const layoutService = useInjectable<IMainLayoutService>(IMainLayoutService);
86
86
  const msgHistoryManager = aiChatService.sessionModel.history;
87
87
  const containerRef = React.useRef<HTMLDivElement>(null);
88
88
  const chatInputRef = React.useRef<{ setInputValue: (v: string) => void } | null>(null);
89
+ const dialogService = useInjectable<IDialogService>(IDialogService);
90
+ const aiNativeConfigService = useInjectable<AINativeConfigService>(AINativeConfigService);
91
+ const commandService = useInjectable<CommandService>(CommandService);
89
92
 
90
93
  const [shortcutCommands, setShortcutCommands] = React.useState<ChatSlashCommandItemModel[]>([]);
91
94
 
@@ -107,6 +110,8 @@ export const AIChatView = () => {
107
110
  const [defaultAgentId, setDefaultAgentId] = React.useState<string>('');
108
111
  const [command, setCommand] = React.useState('');
109
112
  const [theme, setTheme] = React.useState<string | null>(null);
113
+ const [mcpToolsCount, setMcpToolsCount] = React.useState<number>(0);
114
+ const [mcpServersCount, setMcpServersCount] = React.useState<number>(0);
110
115
 
111
116
  React.useEffect(() => {
112
117
  const featureSlashCommands = chatFeatureRegistry.getAllShortcutSlashCommand();
@@ -506,10 +511,7 @@ export const AIChatView = () => {
506
511
  const { message, agentId, command, reportExtra } = value;
507
512
  const { actionType, actionSource } = reportExtra || {};
508
513
 
509
- const context = contextService.serialize();
510
- const fullMessage = await promptProvider.provideContextPrompt(context, message);
511
-
512
- const request = aiChatService.createRequest(fullMessage, agentId!, command);
514
+ const request = aiChatService.createRequest(message, agentId!, command);
513
515
  if (!request) {
514
516
  return;
515
517
  }
@@ -658,6 +660,32 @@ export const AIChatView = () => {
658
660
  };
659
661
  }, [aiChatService.sessionModel]);
660
662
 
663
+ useEventEffect(
664
+ mcpServerProxyService.onChangeMCPServers,
665
+ () => {
666
+ mcpServerProxyService.getAllMCPTools().then((tools) => {
667
+ setMcpToolsCount(tools.length);
668
+ });
669
+ mcpServerProxyService.$getServers().then((servers) => {
670
+ setMcpServersCount(servers.length);
671
+ });
672
+ },
673
+ [mcpServerProxyService],
674
+ );
675
+
676
+ const handleShowMCPTools = React.useCallback(async () => {
677
+ const tools = await mcpServerProxyService.getAllMCPTools();
678
+ dialogService.open({
679
+ message: <MCPToolsDialog tools={tools} />,
680
+ type: MessageType.Empty,
681
+ buttons: ['关闭'],
682
+ });
683
+ }, [mcpServerProxyService, dialogService]);
684
+
685
+ const handleShowMCPConfig = React.useCallback(() => {
686
+ commandService.executeCommand(OPEN_MCP_CONFIG_COMMAND.id);
687
+ }, [commandService]);
688
+
661
689
  return (
662
690
  <div id={styles.ai_chat_view}>
663
691
  <div className={styles.header_container}>
@@ -697,6 +725,18 @@ export const AIChatView = () => {
697
725
  </Popover>
698
726
  ))}
699
727
  </div>
728
+ <div className={styles.header_operate_right}>
729
+ {aiNativeConfigService.capabilities.supportsMCP && (
730
+ <>
731
+ <div className={styles.tag} onClick={handleShowMCPConfig}>
732
+ {`MCP Servers: ${mcpServersCount}`}
733
+ </div>
734
+ <div className={styles.tag} onClick={handleShowMCPTools}>
735
+ {`MCP Tools: ${mcpToolsCount}`}
736
+ </div>
737
+ </>
738
+ )}
739
+ </div>
700
740
  </div>
701
741
  <ChatInputWrapperRender
702
742
  onSend={(value, agentId, command) =>
@@ -55,6 +55,10 @@ export class LLMContextServiceImpl extends WithEventBus implements LLMContextSer
55
55
  const targetList = isManual ? this.attachedFiles : this.recentlyViewFiles;
56
56
  const maxLimit = isManual ? this.maxAttachFilesLimit : this.maxViewFilesLimit;
57
57
 
58
+ if (isManual) {
59
+ this.docModelManager.createModelReference(uri);
60
+ }
61
+
58
62
  this.addFileToList(file, targetList, maxLimit);
59
63
  this.notifyContextChange();
60
64
  }
@@ -55,6 +55,8 @@ import { RenameCandidatesProviderRegistry } from './contrib/rename/rename.featur
55
55
  import { TerminalAIContribution } from './contrib/terminal/terminal-ai.contributon';
56
56
  import { TerminalFeatureRegistry } from './contrib/terminal/terminal.feature.registry';
57
57
  import { LanguageParserService } from './languages/service';
58
+ import { MCPConfigCommandContribution } from './mcp/config/mcp-config.commands';
59
+ import { MCPConfigContribution } from './mcp/config/mcp-config.contribution';
58
60
  import { MCPServerProxyService } from './mcp/mcp-server-proxy.service';
59
61
  import { MCPServerRegistry } from './mcp/mcp-server.feature.registry';
60
62
  import { CreateNewFileWithTextTool } from './mcp/tools/createNewFileWithText';
@@ -91,6 +93,8 @@ export class AINativeModule extends BrowserModule {
91
93
  AICodeActionContribution,
92
94
  AINativePreferencesContribution,
93
95
  IntelligentCompletionsContribution,
96
+ MCPConfigContribution,
97
+ MCPConfigCommandContribution,
94
98
 
95
99
  // MCP Server Contributions START
96
100
  ListDirTool,
@@ -0,0 +1,178 @@
1
+ .container {
2
+ padding: 20px;
3
+ color: var(--foreground);
4
+ }
5
+
6
+ .header {
7
+ display: flex;
8
+ justify-content: space-between;
9
+ align-items: flex-start;
10
+ margin-bottom: 24px;
11
+ }
12
+
13
+ .title {
14
+ margin: 0 0 8px 0;
15
+ font-size: 24px;
16
+ font-weight: 600;
17
+ }
18
+
19
+ .description {
20
+ margin: 0;
21
+ color: var(--descriptionForeground);
22
+ font-size: 14px;
23
+ }
24
+
25
+ .addButton {
26
+ padding: 8px 16px;
27
+ border-radius: 4px;
28
+ background-color: var(--button-primary-background);
29
+ color: var(--button-primary-foreground);
30
+ border: none;
31
+ cursor: pointer;
32
+ font-size: 13px;
33
+
34
+ &:hover {
35
+ background-color: var(--button-primary-hover-background);
36
+ }
37
+ }
38
+
39
+ .serversList {
40
+ display: flex;
41
+ flex-direction: column;
42
+ gap: 12px;
43
+ }
44
+
45
+ .serverItem {
46
+ padding: 16px;
47
+ border-radius: 8px;
48
+ background-color: var(--editorWidget-background);
49
+ border: 1px solid var(--border-color);
50
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
51
+ transition: all 0.2s ease;
52
+
53
+ &:hover {
54
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
55
+ border-color: var(--focusBorder);
56
+ }
57
+ }
58
+
59
+ .serverHeader {
60
+ display: flex;
61
+ justify-content: space-between;
62
+ align-items: center;
63
+ margin-bottom: 12px;
64
+ }
65
+
66
+ .serverTitleRow {
67
+ display: flex;
68
+ align-items: center;
69
+ gap: 8px;
70
+ }
71
+
72
+ .serverName {
73
+ margin: 0;
74
+ font-size: 14px;
75
+ font-weight: 500;
76
+ }
77
+
78
+ .serverStatus,
79
+ .serverType {
80
+ font-size: 12px;
81
+ padding: 2px 8px;
82
+ border-radius: 12px;
83
+ font-weight: 500;
84
+ display: inline-flex;
85
+ align-items: center;
86
+ }
87
+
88
+ .serverType {
89
+ background-color: var(--editor-background);
90
+ color: var(--descriptionForeground);
91
+ }
92
+
93
+ .running {
94
+ background-color: var(--notification-info-background);
95
+ color: var(--notification-info-foreground);
96
+ }
97
+
98
+ .stopped {
99
+ background-color: var(--notification-error-background);
100
+ color: var(--notification-error-foreground);
101
+ }
102
+
103
+ .serverActions {
104
+ display: flex;
105
+ gap: 4px;
106
+ }
107
+
108
+ .iconButton {
109
+ padding: 4px;
110
+ border: none;
111
+ background: none;
112
+ color: var(--icon-foreground);
113
+ cursor: pointer;
114
+ border-radius: 4px;
115
+
116
+ &:hover {
117
+ background-color: var(--list-hover-background);
118
+ }
119
+ }
120
+
121
+ .serverDetail {
122
+ margin-top: 12px;
123
+ padding: 12px;
124
+ border-radius: 6px;
125
+ background-color: var(--editor-background);
126
+ font-size: 13px;
127
+
128
+ &:not(:last-child) {
129
+ margin-bottom: 8px;
130
+ }
131
+ }
132
+
133
+ .detailRow {
134
+ display: flex;
135
+ gap: 8px;
136
+ margin-bottom: 4px;
137
+ align-items: center;
138
+
139
+ &:last-child {
140
+ margin-bottom: 0;
141
+ }
142
+ }
143
+
144
+ .detailLabel {
145
+ color: var(--descriptionForeground);
146
+ min-width: 70px;
147
+ height: 24px;
148
+ display: flex;
149
+ align-items: center;
150
+ }
151
+
152
+ .detailContent {
153
+ color: var(--foreground);
154
+ word-break: break-all;
155
+ flex: 1;
156
+ background-color: var(--editor-background);
157
+ padding: 4px 8px;
158
+ border-radius: 4px;
159
+ font-family: var(--monaco-monospace-font);
160
+ min-height: 24px;
161
+ display: flex;
162
+ align-items: center;
163
+ gap: 8px;
164
+ flex-wrap: wrap;
165
+ }
166
+
167
+ .toolTag {
168
+ display: inline-flex;
169
+ align-items: center;
170
+ padding: 2px 8px;
171
+ background-color: var(--editorWidget-background);
172
+ color: var(--button-secondary-foreground);
173
+ border-radius: 12px;
174
+ font-size: 12px;
175
+ font-weight: 500;
176
+ transition: all 0.2s ease;
177
+ border: 1px solid var(--border-color);
178
+ }
@@ -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
+ };