@opensumi/ide-ai-native 3.8.1-next-1740452912.0 → 3.8.1-next-1740465035.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 (126) 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.module.less +1 -2
  13. package/lib/browser/chat/chat.view.d.ts.map +1 -1
  14. package/lib/browser/chat/chat.view.js +14 -9
  15. package/lib/browser/chat/chat.view.js.map +1 -1
  16. package/lib/browser/components/ChatContext/index.js +2 -2
  17. package/lib/browser/components/ChatContext/index.js.map +1 -1
  18. package/lib/browser/components/chat-history.css +1 -1
  19. package/lib/browser/context/llm-context.service.d.ts +16 -5
  20. package/lib/browser/context/llm-context.service.d.ts.map +1 -1
  21. package/lib/browser/context/llm-context.service.js +78 -47
  22. package/lib/browser/context/llm-context.service.js.map +1 -1
  23. package/lib/browser/index.d.ts.map +1 -1
  24. package/lib/browser/index.js +4 -0
  25. package/lib/browser/index.js.map +1 -1
  26. package/lib/browser/mcp/config/components/mcp-config.module.less +178 -0
  27. package/lib/browser/mcp/config/components/mcp-config.view.d.ts +3 -0
  28. package/lib/browser/mcp/config/components/mcp-config.view.d.ts.map +1 -0
  29. package/lib/browser/mcp/config/components/mcp-config.view.js +150 -0
  30. package/lib/browser/mcp/config/components/mcp-config.view.js.map +1 -0
  31. package/lib/browser/mcp/config/components/mcp-server-form.d.ts +16 -0
  32. package/lib/browser/mcp/config/components/mcp-server-form.d.ts.map +1 -0
  33. package/lib/browser/mcp/config/components/mcp-server-form.js +84 -0
  34. package/lib/browser/mcp/config/components/mcp-server-form.js.map +1 -0
  35. package/lib/browser/mcp/config/components/mcp-server-form.module.less +78 -0
  36. package/lib/browser/mcp/config/mcp-config.commands.d.ts +10 -0
  37. package/lib/browser/mcp/config/mcp-config.commands.d.ts.map +1 -0
  38. package/lib/browser/mcp/config/mcp-config.commands.js +35 -0
  39. package/lib/browser/mcp/config/mcp-config.commands.js.map +1 -0
  40. package/lib/browser/mcp/config/mcp-config.contribution.d.ts +16 -0
  41. package/lib/browser/mcp/config/mcp-config.contribution.d.ts.map +1 -0
  42. package/lib/browser/mcp/config/mcp-config.contribution.js +62 -0
  43. package/lib/browser/mcp/config/mcp-config.contribution.js.map +1 -0
  44. package/lib/browser/mcp/mcp-server-proxy.service.d.ts +6 -0
  45. package/lib/browser/mcp/mcp-server-proxy.service.d.ts.map +1 -1
  46. package/lib/browser/mcp/mcp-server-proxy.service.js +10 -1
  47. package/lib/browser/mcp/mcp-server-proxy.service.js.map +1 -1
  48. package/lib/browser/mcp/mcp-server.feature.registry.d.ts.map +1 -1
  49. package/lib/browser/mcp/mcp-server.feature.registry.js +3 -2
  50. package/lib/browser/mcp/mcp-server.feature.registry.js.map +1 -1
  51. package/lib/browser/mcp/tools/handlers/RunCommand.d.ts.map +1 -1
  52. package/lib/browser/mcp/tools/handlers/RunCommand.js +2 -0
  53. package/lib/browser/mcp/tools/handlers/RunCommand.js.map +1 -1
  54. package/lib/browser/preferences/schema.d.ts.map +1 -1
  55. package/lib/browser/preferences/schema.js +16 -0
  56. package/lib/browser/preferences/schema.js.map +1 -1
  57. package/lib/common/index.d.ts +8 -1
  58. package/lib/common/index.d.ts.map +1 -1
  59. package/lib/common/index.js +3 -1
  60. package/lib/common/index.js.map +1 -1
  61. package/lib/common/llm-context.d.ts +12 -9
  62. package/lib/common/llm-context.d.ts.map +1 -1
  63. package/lib/common/llm-context.js.map +1 -1
  64. package/lib/common/mcp-server-manager.d.ts +17 -1
  65. package/lib/common/mcp-server-manager.d.ts.map +1 -1
  66. package/lib/common/mcp-server-manager.js.map +1 -1
  67. package/lib/common/prompts/context-prompt-provider.d.ts +2 -3
  68. package/lib/common/prompts/context-prompt-provider.d.ts.map +1 -1
  69. package/lib/common/prompts/context-prompt-provider.js +21 -22
  70. package/lib/common/prompts/context-prompt-provider.js.map +1 -1
  71. package/lib/common/tool-invocation-registry.d.ts +2 -2
  72. package/lib/common/tool-invocation-registry.d.ts.map +1 -1
  73. package/lib/common/tool-invocation-registry.js +1 -1
  74. package/lib/common/tool-invocation-registry.js.map +1 -1
  75. package/lib/common/types.d.ts +6 -0
  76. package/lib/common/types.d.ts.map +1 -1
  77. package/lib/common/utils.d.ts.map +1 -1
  78. package/lib/common/utils.js +2 -1
  79. package/lib/common/utils.js.map +1 -1
  80. package/lib/node/mcp/sumi-mcp-server.d.ts +17 -3
  81. package/lib/node/mcp/sumi-mcp-server.d.ts.map +1 -1
  82. package/lib/node/mcp/sumi-mcp-server.js +59 -6
  83. package/lib/node/mcp/sumi-mcp-server.js.map +1 -1
  84. package/lib/node/mcp-server-manager-impl.d.ts +4 -3
  85. package/lib/node/mcp-server-manager-impl.d.ts.map +1 -1
  86. package/lib/node/mcp-server-manager-impl.js +26 -6
  87. package/lib/node/mcp-server-manager-impl.js.map +1 -1
  88. package/lib/node/mcp-server.d.ts +5 -16
  89. package/lib/node/mcp-server.d.ts.map +1 -1
  90. package/lib/node/mcp-server.js +12 -6
  91. package/lib/node/mcp-server.js.map +1 -1
  92. package/lib/node/openai/openai-language-model.d.ts +4 -3
  93. package/lib/node/openai/openai-language-model.d.ts.map +1 -1
  94. package/lib/node/openai/openai-language-model.js +3 -2
  95. package/lib/node/openai/openai-language-model.js.map +1 -1
  96. package/package.json +27 -27
  97. package/src/browser/ai-core.contribution.ts +13 -4
  98. package/src/browser/chat/chat-manager.service.ts +17 -1
  99. package/src/browser/chat/chat-model.ts +18 -3
  100. package/src/browser/chat/chat.module.less +1 -2
  101. package/src/browser/chat/chat.view.tsx +45 -23
  102. package/src/browser/components/ChatContext/index.tsx +2 -2
  103. package/src/browser/components/chat-history.css +1 -1
  104. package/src/browser/context/llm-context.service.ts +90 -54
  105. package/src/browser/index.ts +4 -0
  106. package/src/browser/mcp/config/components/mcp-config.module.less +178 -0
  107. package/src/browser/mcp/config/components/mcp-config.view.tsx +215 -0
  108. package/src/browser/mcp/config/components/mcp-server-form.module.less +78 -0
  109. package/src/browser/mcp/config/components/mcp-server-form.tsx +144 -0
  110. package/src/browser/mcp/config/mcp-config.commands.ts +29 -0
  111. package/src/browser/mcp/config/mcp-config.contribution.ts +65 -0
  112. package/src/browser/mcp/mcp-server-proxy.service.ts +14 -2
  113. package/src/browser/mcp/mcp-server.feature.registry.ts +3 -2
  114. package/src/browser/mcp/tools/handlers/RunCommand.ts +2 -0
  115. package/src/browser/preferences/schema.ts +16 -0
  116. package/src/common/index.ts +7 -1
  117. package/src/common/llm-context.ts +10 -4
  118. package/src/common/mcp-server-manager.ts +17 -1
  119. package/src/common/prompts/context-prompt-provider.ts +26 -28
  120. package/src/common/tool-invocation-registry.ts +2 -2
  121. package/src/common/types.ts +6 -0
  122. package/src/common/utils.ts +3 -1
  123. package/src/node/mcp/sumi-mcp-server.ts +67 -9
  124. package/src/node/mcp-server-manager-impl.ts +30 -9
  125. package/src/node/mcp-server.ts +11 -14
  126. package/src/node/openai/openai-language-model.ts +7 -4
@@ -3,7 +3,7 @@
3
3
  align-items: center;
4
4
  justify-content: space-between;
5
5
  font-size: 13px;
6
- padding: 0 4px 0 12px;
6
+ padding: 0 0 0 12px;
7
7
  color: var(--editor-foreground);
8
8
  text-overflow: ellipsis;
9
9
  white-space: nowrap;
@@ -12,7 +12,7 @@ import {
12
12
  import { EditorSelectionChangeEvent } from '@opensumi/ide-editor/lib/browser/types';
13
13
  import { IMarkerService } from '@opensumi/ide-markers/lib/common/types';
14
14
 
15
- import { FileContext, LLMContextService, SerializedContext } from '../../common/llm-context';
15
+ import { AttachFileContext, FileContext, LLMContextService, SerializedContext } from '../../common/llm-context';
16
16
 
17
17
  @Injectable()
18
18
  export class LLMContextServiceImpl extends WithEventBus implements LLMContextService {
@@ -27,40 +27,65 @@ export class LLMContextServiceImpl extends WithEventBus implements LLMContextSer
27
27
 
28
28
  private isAutoCollecting = false;
29
29
 
30
- private contextFiles: FileContext[] = [];
30
+ private readonly maxAttachFilesLimit = 10;
31
+ private readonly maxViewFilesLimit = 20;
32
+ private readonly attachedFiles: FileContext[] = [];
33
+ private readonly recentlyViewFiles: FileContext[] = [];
34
+ private readonly onDidContextFilesChangeEmitter = new Emitter<{ viewed: FileContext[]; attached: FileContext[] }>();
35
+ onDidContextFilesChangeEvent = this.onDidContextFilesChangeEmitter.event;
31
36
 
32
- private maxFiles: number = 10; // 上下文的最大长度限制
37
+ private addFileToList(file: FileContext, list: FileContext[], maxLimit: number) {
38
+ const existingIndex = list.findIndex((f) => f.uri.toString() === file.uri.toString());
39
+ if (existingIndex > -1) {
40
+ list.splice(existingIndex, 1);
41
+ }
33
42
 
34
- private onDidContextFilesChangeEmitter = new Emitter<FileContext[]>();
35
- onDidContextFilesChangeEvent = this.onDidContextFilesChangeEmitter.event;
43
+ list.push(file);
44
+ if (list.length > maxLimit) {
45
+ list.shift();
46
+ }
47
+ }
36
48
 
37
- addFileToContext(uri: URI, selection?: [number, number], isManual = true): void {
38
- this.removeFileFromContext(uri);
49
+ addFileToContext(uri: URI, selection?: [number, number], isManual = false): void {
50
+ if (!uri) {
51
+ return;
52
+ }
39
53
 
40
- this.contextFiles.push({ uri, selection, isManual });
54
+ const file = { uri, selection };
55
+ const targetList = isManual ? this.attachedFiles : this.recentlyViewFiles;
56
+ const maxLimit = isManual ? this.maxAttachFilesLimit : this.maxViewFilesLimit;
41
57
 
42
- if (this.contextFiles.length > this.maxFiles) {
43
- this.contextFiles.shift();
58
+ if (isManual) {
59
+ this.docModelManager.createModelReference(uri);
44
60
  }
45
61
 
62
+ this.addFileToList(file, targetList, maxLimit);
63
+ this.notifyContextChange();
64
+ }
65
+
66
+ private notifyContextChange(): void {
46
67
  this.onDidContextFilesChangeEmitter.fire(this.getAllContextFiles());
47
68
  }
48
69
 
49
70
  cleanFileContext() {
50
- this.contextFiles = [];
51
- this.onDidContextFilesChangeEmitter.fire(this.getAllContextFiles());
71
+ this.attachedFiles.length = 0;
72
+ this.notifyContextChange();
52
73
  }
53
74
 
54
75
  private getAllContextFiles() {
55
- return [...this.contextFiles];
76
+ return {
77
+ viewed: this.recentlyViewFiles,
78
+ attached: this.attachedFiles,
79
+ };
56
80
  }
57
81
 
58
- removeFileFromContext(uri: URI): void {
59
- const index = this.contextFiles.findIndex((file) => file.uri.toString() === uri.toString());
82
+ removeFileFromContext(uri: URI, isManual = false): void {
83
+ const targetList = isManual ? this.attachedFiles : this.recentlyViewFiles;
84
+ const index = targetList.findIndex((file) => file.uri.toString() === uri.toString());
60
85
  if (index > -1) {
61
- this.contextFiles.splice(index, 1);
62
- this.onDidContextFilesChangeEmitter.fire(this.getAllContextFiles());
86
+ targetList.splice(index, 1);
63
87
  }
88
+ this.notifyContextChange();
64
89
  }
65
90
 
66
91
  startAutoCollection(): void {
@@ -78,8 +103,7 @@ export class LLMContextServiceImpl extends WithEventBus implements LLMContextSer
78
103
  if (event.payload.uri.scheme !== 'file') {
79
104
  return;
80
105
  }
81
- // FIXME: 暂时不自动添加
82
- // this.addFileToContext(event.payload.uri);
106
+ this.addFileToContext(event.payload.uri, undefined, false);
83
107
  }),
84
108
  );
85
109
 
@@ -88,6 +112,8 @@ export class LLMContextServiceImpl extends WithEventBus implements LLMContextSer
88
112
  if (event.payload.scheme !== 'file') {
89
113
  return;
90
114
  }
115
+
116
+ this.removeFileFromContext(event.payload, false);
91
117
  }),
92
118
  );
93
119
 
@@ -109,11 +135,12 @@ export class LLMContextServiceImpl extends WithEventBus implements LLMContextSer
109
135
  ].sort() as [number, number];
110
136
 
111
137
  if (selection[0] === selection[1]) {
112
- this.addFileToContext(event.payload.editorUri, undefined);
138
+ this.addFileToContext(event.payload.editorUri, undefined, false);
113
139
  } else {
114
140
  this.addFileToContext(
115
141
  event.payload.editorUri,
116
142
  selection.sort((a, b) => a - b),
143
+ false,
117
144
  );
118
145
  }
119
146
  }
@@ -127,42 +154,51 @@ export class LLMContextServiceImpl extends WithEventBus implements LLMContextSer
127
154
 
128
155
  serialize(): SerializedContext {
129
156
  const files = this.getAllContextFiles();
130
- const recentlyViewFiles = files
131
- .filter((v) => !v.selection)
132
- .map((file) => {
133
- const relativePath = URI.file(this.appConfig.workspaceDir).relative(file.uri);
134
- if (relativePath) {
135
- return relativePath.toString();
136
- }
137
- return file.uri.parent.toString();
138
- })
139
- .filter(Boolean);
140
-
141
- const attachedFiles = files
142
- .filter((v) => v.selection)
143
- .map((file) => {
144
- const ref = this.docModelManager.getModelReference(file.uri);
145
- const content = ref!.instance.getText();
146
- const lineErrors = this.markerService
147
- .getManager()
148
- .getMarkers({
149
- resource: file.uri.toString(),
150
- severities: MarkerSeverity.Error,
151
- })
152
- .map((marker) => marker.message);
153
-
154
- return {
155
- content,
156
- lineErrors,
157
- path: URI.file(this.appConfig.workspaceDir).relative(file.uri)!.toString(),
158
- language: ref?.instance.languageId!,
159
- };
160
- })
161
- .filter(Boolean);
157
+ const workspaceRoot = URI.file(this.appConfig.workspaceDir);
162
158
 
163
159
  return {
164
- recentlyViewFiles,
165
- attachedFiles,
160
+ recentlyViewFiles: this.serializeRecentlyViewFiles(files.viewed, workspaceRoot),
161
+ attachedFiles: this.serializeAttachedFiles(files.attached, workspaceRoot),
166
162
  };
167
163
  }
164
+
165
+ private serializeRecentlyViewFiles(files: FileContext[], workspaceRoot: URI): string[] {
166
+ return files
167
+ .map((file) => workspaceRoot.relative(file.uri)?.toString() || file.uri.parent.toString())
168
+ .filter(Boolean);
169
+ }
170
+
171
+ private serializeAttachedFiles(files: FileContext[], workspaceRoot: URI): AttachFileContext[] {
172
+ return files
173
+ .map((file) => this.serializeAttachedFile(file, workspaceRoot))
174
+ .filter(Boolean) as unknown as AttachFileContext[];
175
+ }
176
+
177
+ private serializeAttachedFile(file: FileContext, workspaceRoot: URI) {
178
+ try {
179
+ const ref = this.docModelManager.getModelReference(file.uri);
180
+ if (!ref) {
181
+ return null;
182
+ }
183
+
184
+ return {
185
+ content: ref.instance.getText(),
186
+ lineErrors: this.getFileErrors(file.uri),
187
+ path: workspaceRoot.relative(file.uri)!.toString(),
188
+ language: ref.instance.languageId!,
189
+ };
190
+ } catch (e) {
191
+ return null;
192
+ }
193
+ }
194
+
195
+ private getFileErrors(uri: URI): string[] {
196
+ return this.markerService
197
+ .getManager()
198
+ .getMarkers({
199
+ resource: uri.toString(),
200
+ severities: MarkerSeverity.Error,
201
+ })
202
+ .map((marker) => marker.message);
203
+ }
168
204
  }
@@ -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
+ };
@@ -0,0 +1,78 @@
1
+ .form {
2
+ padding: 16px;
3
+ }
4
+
5
+ .formItem {
6
+ margin-bottom: 16px;
7
+
8
+ label {
9
+ display: block;
10
+ margin-bottom: 8px;
11
+ color: var(--foreground);
12
+ font-size: 13px;
13
+ }
14
+
15
+ input,
16
+ textarea {
17
+ width: 100%;
18
+ padding: 8px;
19
+ border: 1px solid var(--input-border);
20
+ border-radius: 4px;
21
+ background-color: var(--input-background);
22
+ color: var(--input-foreground);
23
+ font-size: 13px;
24
+ font-family: var(--monaco-monospace-font);
25
+
26
+ &:focus {
27
+ border-color: var(--focusBorder);
28
+ outline: none;
29
+ }
30
+
31
+ &::placeholder {
32
+ color: var(--descriptionForeground);
33
+ opacity: 0.6;
34
+ }
35
+ }
36
+
37
+ textarea {
38
+ resize: vertical;
39
+ min-height: 60px;
40
+ }
41
+ }
42
+
43
+ .formActions {
44
+ display: flex;
45
+ justify-content: flex-end;
46
+ gap: 8px;
47
+ margin-top: 24px;
48
+ padding-top: 16px;
49
+ border-top: 1px solid var(--border-color);
50
+ }
51
+
52
+ .cancelButton {
53
+ padding: 6px 12px;
54
+ border: 1px solid var(--button-border);
55
+ border-radius: 4px;
56
+ background-color: var(--button-secondary-background);
57
+ color: var(--button-secondary-foreground);
58
+ cursor: pointer;
59
+ font-size: 13px;
60
+
61
+ &:hover {
62
+ background-color: var(--button-secondary-hover-background);
63
+ }
64
+ }
65
+
66
+ .submitButton {
67
+ padding: 6px 12px;
68
+ border: none;
69
+ border-radius: 4px;
70
+ background-color: var(--button-primary-background);
71
+ color: var(--button-primary-foreground);
72
+ cursor: pointer;
73
+ font-size: 13px;
74
+
75
+ &:hover {
76
+ background-color: var(--button-primary-hover-background);
77
+ }
78
+ }