@opensumi/ide-ai-native 3.8.1-next-1740452912.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.
- package/lib/browser/ai-core.contribution.d.ts.map +1 -1
- package/lib/browser/ai-core.contribution.js +10 -3
- package/lib/browser/ai-core.contribution.js.map +1 -1
- package/lib/browser/chat/chat-manager.service.d.ts +5 -0
- package/lib/browser/chat/chat-manager.service.d.ts.map +1 -1
- package/lib/browser/chat/chat-manager.service.js +18 -1
- package/lib/browser/chat/chat-manager.service.js.map +1 -1
- package/lib/browser/chat/chat-model.d.ts +2 -0
- package/lib/browser/chat/chat-model.d.ts.map +1 -1
- package/lib/browser/chat/chat-model.js +8 -2
- package/lib/browser/chat/chat-model.js.map +1 -1
- package/lib/browser/chat/chat.view.d.ts.map +1 -1
- package/lib/browser/chat/chat.view.js +31 -8
- package/lib/browser/chat/chat.view.js.map +1 -1
- package/lib/browser/components/ChatContext/index.js +2 -2
- package/lib/browser/components/ChatContext/index.js.map +1 -1
- package/lib/browser/context/llm-context.service.d.ts +16 -5
- package/lib/browser/context/llm-context.service.d.ts.map +1 -1
- package/lib/browser/context/llm-context.service.js +78 -47
- package/lib/browser/context/llm-context.service.js.map +1 -1
- package/lib/browser/index.d.ts.map +1 -1
- package/lib/browser/index.js +4 -0
- package/lib/browser/index.js.map +1 -1
- package/lib/browser/mcp/config/components/mcp-config.module.less +178 -0
- package/lib/browser/mcp/config/components/mcp-config.view.d.ts +3 -0
- package/lib/browser/mcp/config/components/mcp-config.view.d.ts.map +1 -0
- package/lib/browser/mcp/config/components/mcp-config.view.js +150 -0
- package/lib/browser/mcp/config/components/mcp-config.view.js.map +1 -0
- package/lib/browser/mcp/config/components/mcp-server-form.d.ts +16 -0
- package/lib/browser/mcp/config/components/mcp-server-form.d.ts.map +1 -0
- package/lib/browser/mcp/config/components/mcp-server-form.js +84 -0
- package/lib/browser/mcp/config/components/mcp-server-form.js.map +1 -0
- package/lib/browser/mcp/config/components/mcp-server-form.module.less +78 -0
- package/lib/browser/mcp/config/mcp-config.commands.d.ts +10 -0
- package/lib/browser/mcp/config/mcp-config.commands.d.ts.map +1 -0
- package/lib/browser/mcp/config/mcp-config.commands.js +35 -0
- package/lib/browser/mcp/config/mcp-config.commands.js.map +1 -0
- package/lib/browser/mcp/config/mcp-config.contribution.d.ts +16 -0
- package/lib/browser/mcp/config/mcp-config.contribution.d.ts.map +1 -0
- package/lib/browser/mcp/config/mcp-config.contribution.js +62 -0
- package/lib/browser/mcp/config/mcp-config.contribution.js.map +1 -0
- package/lib/browser/mcp/mcp-server-proxy.service.d.ts +6 -0
- package/lib/browser/mcp/mcp-server-proxy.service.d.ts.map +1 -1
- package/lib/browser/mcp/mcp-server-proxy.service.js +10 -1
- package/lib/browser/mcp/mcp-server-proxy.service.js.map +1 -1
- package/lib/browser/mcp/mcp-server.feature.registry.d.ts.map +1 -1
- package/lib/browser/mcp/mcp-server.feature.registry.js +3 -2
- package/lib/browser/mcp/mcp-server.feature.registry.js.map +1 -1
- package/lib/browser/mcp/tools/handlers/RunCommand.d.ts.map +1 -1
- package/lib/browser/mcp/tools/handlers/RunCommand.js +2 -0
- package/lib/browser/mcp/tools/handlers/RunCommand.js.map +1 -1
- package/lib/browser/preferences/schema.d.ts.map +1 -1
- package/lib/browser/preferences/schema.js +16 -0
- package/lib/browser/preferences/schema.js.map +1 -1
- package/lib/common/index.d.ts +8 -1
- package/lib/common/index.d.ts.map +1 -1
- package/lib/common/index.js +3 -1
- package/lib/common/index.js.map +1 -1
- package/lib/common/llm-context.d.ts +12 -9
- package/lib/common/llm-context.d.ts.map +1 -1
- package/lib/common/llm-context.js.map +1 -1
- package/lib/common/mcp-server-manager.d.ts +17 -1
- package/lib/common/mcp-server-manager.d.ts.map +1 -1
- package/lib/common/mcp-server-manager.js.map +1 -1
- package/lib/common/prompts/context-prompt-provider.d.ts +2 -3
- package/lib/common/prompts/context-prompt-provider.d.ts.map +1 -1
- package/lib/common/prompts/context-prompt-provider.js +21 -22
- package/lib/common/prompts/context-prompt-provider.js.map +1 -1
- package/lib/common/tool-invocation-registry.d.ts +2 -2
- package/lib/common/tool-invocation-registry.d.ts.map +1 -1
- package/lib/common/tool-invocation-registry.js +1 -1
- package/lib/common/tool-invocation-registry.js.map +1 -1
- package/lib/common/types.d.ts +6 -0
- package/lib/common/types.d.ts.map +1 -1
- package/lib/common/utils.d.ts.map +1 -1
- package/lib/common/utils.js +2 -1
- package/lib/common/utils.js.map +1 -1
- package/lib/node/mcp/sumi-mcp-server.d.ts +17 -3
- package/lib/node/mcp/sumi-mcp-server.d.ts.map +1 -1
- package/lib/node/mcp/sumi-mcp-server.js +59 -6
- package/lib/node/mcp/sumi-mcp-server.js.map +1 -1
- package/lib/node/mcp-server-manager-impl.d.ts +4 -3
- package/lib/node/mcp-server-manager-impl.d.ts.map +1 -1
- package/lib/node/mcp-server-manager-impl.js +26 -6
- package/lib/node/mcp-server-manager-impl.js.map +1 -1
- package/lib/node/mcp-server.d.ts +5 -16
- package/lib/node/mcp-server.d.ts.map +1 -1
- package/lib/node/mcp-server.js +12 -6
- package/lib/node/mcp-server.js.map +1 -1
- package/lib/node/openai/openai-language-model.d.ts +4 -3
- package/lib/node/openai/openai-language-model.d.ts.map +1 -1
- package/lib/node/openai/openai-language-model.js +3 -2
- package/lib/node/openai/openai-language-model.js.map +1 -1
- package/package.json +27 -27
- package/src/browser/ai-core.contribution.ts +13 -4
- package/src/browser/chat/chat-manager.service.ts +17 -1
- package/src/browser/chat/chat-model.ts +18 -3
- package/src/browser/chat/chat.view.tsx +48 -8
- package/src/browser/components/ChatContext/index.tsx +2 -2
- package/src/browser/context/llm-context.service.ts +90 -54
- package/src/browser/index.ts +4 -0
- package/src/browser/mcp/config/components/mcp-config.module.less +178 -0
- package/src/browser/mcp/config/components/mcp-config.view.tsx +215 -0
- package/src/browser/mcp/config/components/mcp-server-form.module.less +78 -0
- package/src/browser/mcp/config/components/mcp-server-form.tsx +144 -0
- package/src/browser/mcp/config/mcp-config.commands.ts +29 -0
- package/src/browser/mcp/config/mcp-config.contribution.ts +65 -0
- package/src/browser/mcp/mcp-server-proxy.service.ts +14 -2
- package/src/browser/mcp/mcp-server.feature.registry.ts +3 -2
- package/src/browser/mcp/tools/handlers/RunCommand.ts +2 -0
- package/src/browser/preferences/schema.ts +16 -0
- package/src/common/index.ts +7 -1
- package/src/common/llm-context.ts +10 -4
- package/src/common/mcp-server-manager.ts +17 -1
- package/src/common/prompts/context-prompt-provider.ts +26 -28
- package/src/common/tool-invocation-registry.ts +2 -2
- package/src/common/types.ts +6 -0
- package/src/common/utils.ts +3 -1
- package/src/node/mcp/sumi-mcp-server.ts +67 -9
- package/src/node/mcp-server-manager-impl.ts +30 -9
- package/src/node/mcp-server.ts +11 -14
- package/src/node/openai/openai-language-model.ts +7 -4
|
@@ -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
|
|
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
|
|
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
|
-
|
|
35
|
-
|
|
43
|
+
list.push(file);
|
|
44
|
+
if (list.length > maxLimit) {
|
|
45
|
+
list.shift();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
36
48
|
|
|
37
|
-
addFileToContext(uri: URI, selection?: [number, number], isManual =
|
|
38
|
-
|
|
49
|
+
addFileToContext(uri: URI, selection?: [number, number], isManual = false): void {
|
|
50
|
+
if (!uri) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
39
53
|
|
|
40
|
-
|
|
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 (
|
|
43
|
-
this.
|
|
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.
|
|
51
|
-
this.
|
|
71
|
+
this.attachedFiles.length = 0;
|
|
72
|
+
this.notifyContextChange();
|
|
52
73
|
}
|
|
53
74
|
|
|
54
75
|
private getAllContextFiles() {
|
|
55
|
-
return
|
|
76
|
+
return {
|
|
77
|
+
viewed: this.recentlyViewFiles,
|
|
78
|
+
attached: this.attachedFiles,
|
|
79
|
+
};
|
|
56
80
|
}
|
|
57
81
|
|
|
58
|
-
removeFileFromContext(uri: URI): void {
|
|
59
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
}
|
package/src/browser/index.ts
CHANGED
|
@@ -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
|
+
}
|