@mcp-use/cli 1.0.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/dist/app.d.ts +6 -0
- package/dist/app.js +343 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +37 -0
- package/dist/commands.d.ts +51 -0
- package/dist/commands.js +1024 -0
- package/dist/mcp-service.d.ts +44 -0
- package/dist/mcp-service.js +436 -0
- package/dist/storage.d.ts +24 -0
- package/dist/storage.js +108 -0
- package/package.json +82 -0
- package/readme.md +154 -0
package/dist/commands.js
ADDED
|
@@ -0,0 +1,1024 @@
|
|
|
1
|
+
import { ChatOpenAI } from '@langchain/openai';
|
|
2
|
+
import { ChatAnthropic } from '@langchain/anthropic';
|
|
3
|
+
import { ChatGoogleGenerativeAI } from '@langchain/google-genai';
|
|
4
|
+
import { ChatMistralAI } from '@langchain/mistralai';
|
|
5
|
+
import { SecureStorage } from './storage.js';
|
|
6
|
+
export class CommandHandler {
|
|
7
|
+
constructor() {
|
|
8
|
+
Object.defineProperty(this, "currentLLMConfig", {
|
|
9
|
+
enumerable: true,
|
|
10
|
+
configurable: true,
|
|
11
|
+
writable: true,
|
|
12
|
+
value: null
|
|
13
|
+
});
|
|
14
|
+
Object.defineProperty(this, "sessionApiKeys", {
|
|
15
|
+
enumerable: true,
|
|
16
|
+
configurable: true,
|
|
17
|
+
writable: true,
|
|
18
|
+
value: {}
|
|
19
|
+
});
|
|
20
|
+
Object.defineProperty(this, "persistentConfig", {
|
|
21
|
+
enumerable: true,
|
|
22
|
+
configurable: true,
|
|
23
|
+
writable: true,
|
|
24
|
+
value: void 0
|
|
25
|
+
});
|
|
26
|
+
Object.defineProperty(this, "sessionServers", {
|
|
27
|
+
enumerable: true,
|
|
28
|
+
configurable: true,
|
|
29
|
+
writable: true,
|
|
30
|
+
value: {}
|
|
31
|
+
});
|
|
32
|
+
Object.defineProperty(this, "availableModels", {
|
|
33
|
+
enumerable: true,
|
|
34
|
+
configurable: true,
|
|
35
|
+
writable: true,
|
|
36
|
+
value: {
|
|
37
|
+
openai: [
|
|
38
|
+
'gpt-4o',
|
|
39
|
+
'gpt-4o-mini',
|
|
40
|
+
'gpt-4-turbo',
|
|
41
|
+
'gpt-4',
|
|
42
|
+
'gpt-3.5-turbo'
|
|
43
|
+
],
|
|
44
|
+
anthropic: [
|
|
45
|
+
'claude-3-5-sonnet-20241022',
|
|
46
|
+
'claude-3-5-haiku-20241022',
|
|
47
|
+
'claude-3-opus-20240229',
|
|
48
|
+
'claude-3-sonnet-20240229',
|
|
49
|
+
'claude-3-haiku-20240307'
|
|
50
|
+
],
|
|
51
|
+
google: [
|
|
52
|
+
'gemini-1.5-pro',
|
|
53
|
+
'gemini-1.5-flash',
|
|
54
|
+
'gemini-pro'
|
|
55
|
+
],
|
|
56
|
+
mistral: [
|
|
57
|
+
'mistral-large-latest',
|
|
58
|
+
'mistral-medium-latest',
|
|
59
|
+
'mistral-small-latest'
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
// Load persistent config
|
|
64
|
+
this.persistentConfig = SecureStorage.loadConfig();
|
|
65
|
+
// Auto-detect available provider and set default
|
|
66
|
+
this.initializeDefaultProvider();
|
|
67
|
+
}
|
|
68
|
+
initializeDefaultProvider() {
|
|
69
|
+
// First, try to load the last used model from persistent config
|
|
70
|
+
if (this.persistentConfig.lastModel) {
|
|
71
|
+
const lastModel = this.persistentConfig.lastModel;
|
|
72
|
+
const envVar = {
|
|
73
|
+
openai: 'OPENAI_API_KEY',
|
|
74
|
+
anthropic: 'ANTHROPIC_API_KEY',
|
|
75
|
+
google: 'GOOGLE_API_KEY',
|
|
76
|
+
mistral: 'MISTRAL_API_KEY'
|
|
77
|
+
}[lastModel.provider];
|
|
78
|
+
// Check if we still have the API key for the last used model
|
|
79
|
+
if (envVar && this.getApiKey(envVar)) {
|
|
80
|
+
this.currentLLMConfig = {
|
|
81
|
+
provider: lastModel.provider,
|
|
82
|
+
model: lastModel.model,
|
|
83
|
+
temperature: lastModel.temperature || 0.7,
|
|
84
|
+
maxTokens: lastModel.maxTokens
|
|
85
|
+
};
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const providers = [
|
|
90
|
+
{ name: 'openai', envVar: 'OPENAI_API_KEY', defaultModel: 'gpt-4o-mini' },
|
|
91
|
+
{ name: 'anthropic', envVar: 'ANTHROPIC_API_KEY', defaultModel: 'claude-3-5-sonnet-20241022' },
|
|
92
|
+
{ name: 'google', envVar: 'GOOGLE_API_KEY', defaultModel: 'gemini-1.5-pro' },
|
|
93
|
+
{ name: 'mistral', envVar: 'MISTRAL_API_KEY', defaultModel: 'mistral-large-latest' }
|
|
94
|
+
];
|
|
95
|
+
// Find first available provider
|
|
96
|
+
for (const provider of providers) {
|
|
97
|
+
if (this.getApiKey(provider.envVar)) {
|
|
98
|
+
this.currentLLMConfig = {
|
|
99
|
+
provider: provider.name,
|
|
100
|
+
model: provider.defaultModel,
|
|
101
|
+
temperature: 0.7
|
|
102
|
+
};
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// No provider found - leave null, will show setup message
|
|
107
|
+
this.currentLLMConfig = null;
|
|
108
|
+
}
|
|
109
|
+
getAvailableProviders() {
|
|
110
|
+
const providers = [];
|
|
111
|
+
if (this.getApiKey('OPENAI_API_KEY'))
|
|
112
|
+
providers.push('openai');
|
|
113
|
+
if (this.getApiKey('ANTHROPIC_API_KEY'))
|
|
114
|
+
providers.push('anthropic');
|
|
115
|
+
if (this.getApiKey('GOOGLE_API_KEY'))
|
|
116
|
+
providers.push('google');
|
|
117
|
+
if (this.getApiKey('MISTRAL_API_KEY'))
|
|
118
|
+
providers.push('mistral');
|
|
119
|
+
return providers;
|
|
120
|
+
}
|
|
121
|
+
getApiKey(envVar) {
|
|
122
|
+
// Check session keys first, then persistent storage, then environment variables
|
|
123
|
+
return this.sessionApiKeys[envVar] || this.persistentConfig.apiKeys[envVar] || process.env[envVar];
|
|
124
|
+
}
|
|
125
|
+
isAnyProviderAvailable() {
|
|
126
|
+
return this.getAvailableProviders().length > 0;
|
|
127
|
+
}
|
|
128
|
+
async handleCommand(input) {
|
|
129
|
+
const parts = input.trim().split(/\s+/);
|
|
130
|
+
const command = parts[0];
|
|
131
|
+
const args = parts.slice(1);
|
|
132
|
+
switch (command) {
|
|
133
|
+
case '/help':
|
|
134
|
+
return this.handleHelp();
|
|
135
|
+
case '/model':
|
|
136
|
+
return this.handleModel(args);
|
|
137
|
+
case '/models':
|
|
138
|
+
return this.handleListModels(args);
|
|
139
|
+
case '/status':
|
|
140
|
+
return this.handleStatus();
|
|
141
|
+
case '/config':
|
|
142
|
+
return this.handleConfig(args);
|
|
143
|
+
case '/setkey':
|
|
144
|
+
return this.handleSetKey(args);
|
|
145
|
+
case '/clearkeys':
|
|
146
|
+
return this.handleClearKeys();
|
|
147
|
+
case '/server':
|
|
148
|
+
return this.handleServer(args);
|
|
149
|
+
case '/servers':
|
|
150
|
+
return this.handleListServers();
|
|
151
|
+
case '/tools':
|
|
152
|
+
return this.handleListTools();
|
|
153
|
+
case '/test-server':
|
|
154
|
+
return this.handleTestServer(args);
|
|
155
|
+
default:
|
|
156
|
+
return {
|
|
157
|
+
type: 'error',
|
|
158
|
+
message: `Unknown command: ${command}. Type /help for available commands.`
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
handleHelp() {
|
|
163
|
+
const helpText = `
|
|
164
|
+
Available slash commands:
|
|
165
|
+
|
|
166
|
+
š¤ Get Started:
|
|
167
|
+
/model <provider> <model> - Choose your LLM (CLI handles API key setup)
|
|
168
|
+
/models [provider] - List available models for a provider
|
|
169
|
+
|
|
170
|
+
š MCP Servers:
|
|
171
|
+
/server add - Configure a new server (auto-connects)
|
|
172
|
+
/server connect <name> - Connect to a configured server by name
|
|
173
|
+
/server disconnect <name> - Disconnect from a connected server
|
|
174
|
+
/servers - List servers and their connection status
|
|
175
|
+
/tools - Show available tools from connected servers
|
|
176
|
+
/test-server <name> - Test if a server package can be started
|
|
177
|
+
/status - Show current configuration
|
|
178
|
+
|
|
179
|
+
š API Keys (automatic):
|
|
180
|
+
/setkey <provider> <key> - Set API key manually (stored securely)
|
|
181
|
+
/clearkeys - Clear all stored API keys
|
|
182
|
+
|
|
183
|
+
āļø Configuration:
|
|
184
|
+
/config temp <value> - Set temperature (0.0-2.0)
|
|
185
|
+
/config tokens <value> - Set max tokens
|
|
186
|
+
/help - Show this help
|
|
187
|
+
|
|
188
|
+
š Quick Start Examples:
|
|
189
|
+
/model openai gpt-4o-mini
|
|
190
|
+
/server add # Interactive server setup
|
|
191
|
+
/servers
|
|
192
|
+
/config temp 0.5
|
|
193
|
+
`.trim();
|
|
194
|
+
return {
|
|
195
|
+
type: 'info',
|
|
196
|
+
message: helpText
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
handleModel(args) {
|
|
200
|
+
if (args.length < 2) {
|
|
201
|
+
const availableProviders = this.getAvailableProviders();
|
|
202
|
+
if (availableProviders.length === 0) {
|
|
203
|
+
return {
|
|
204
|
+
type: 'info',
|
|
205
|
+
message: 'Usage: /model <provider> <model>\n\nPopular models to try:\n⢠/model openai gpt-4o-mini\n⢠/model anthropic claude-3-5-sonnet-20241022\n⢠/model google gemini-1.5-pro\n\nThe CLI will prompt for your API key when needed.\nUse /models to see all available options.'
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
return {
|
|
209
|
+
type: 'error',
|
|
210
|
+
message: `Usage: /model <provider> <model>\nExample: /model openai gpt-4o\n\nAvailable providers: ${availableProviders.join(', ')}`
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
const provider = args[0];
|
|
214
|
+
const model = args[1];
|
|
215
|
+
if (!provider || !this.availableModels[provider]) {
|
|
216
|
+
return {
|
|
217
|
+
type: 'error',
|
|
218
|
+
message: `Unknown provider: ${provider}\nAvailable providers: ${Object.keys(this.availableModels).join(', ')}`
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
// Check if the provider's API key is available
|
|
222
|
+
const availableProviders = this.getAvailableProviders();
|
|
223
|
+
if (!availableProviders.includes(provider)) {
|
|
224
|
+
const envVarMap = {
|
|
225
|
+
openai: 'OPENAI_API_KEY',
|
|
226
|
+
anthropic: 'ANTHROPIC_API_KEY',
|
|
227
|
+
google: 'GOOGLE_API_KEY',
|
|
228
|
+
mistral: 'MISTRAL_API_KEY'
|
|
229
|
+
};
|
|
230
|
+
// Prompt for API key instead of showing error
|
|
231
|
+
return {
|
|
232
|
+
type: 'prompt_key',
|
|
233
|
+
message: `Please enter your ${provider.toUpperCase()} API key:`,
|
|
234
|
+
data: {
|
|
235
|
+
provider,
|
|
236
|
+
model,
|
|
237
|
+
envVar: envVarMap[provider]
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
if (!model || !this.availableModels[provider].includes(model)) {
|
|
242
|
+
return {
|
|
243
|
+
type: 'error',
|
|
244
|
+
message: `Model ${model} not available for ${provider}\nUse /models ${provider} to see available models`
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
this.currentLLMConfig = {
|
|
248
|
+
provider,
|
|
249
|
+
model,
|
|
250
|
+
temperature: this.currentLLMConfig?.temperature || 0.7,
|
|
251
|
+
maxTokens: this.currentLLMConfig?.maxTokens
|
|
252
|
+
};
|
|
253
|
+
// Save the current model to persistent storage
|
|
254
|
+
this.persistentConfig.lastModel = this.currentLLMConfig;
|
|
255
|
+
SecureStorage.saveConfig(this.persistentConfig);
|
|
256
|
+
return {
|
|
257
|
+
type: 'success',
|
|
258
|
+
message: `ā
Switched to ${provider} ${model}`,
|
|
259
|
+
data: { llmConfig: this.currentLLMConfig }
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
handleListModels(args) {
|
|
263
|
+
if (args.length === 0) {
|
|
264
|
+
let modelList = 'š Available models by provider:\n\n';
|
|
265
|
+
Object.entries(this.availableModels).forEach(([provider, models]) => {
|
|
266
|
+
modelList += `šø ${provider}:\n`;
|
|
267
|
+
models.forEach(model => {
|
|
268
|
+
const current = provider === this.currentLLMConfig?.provider && model === this.currentLLMConfig?.model ? ' ā current' : '';
|
|
269
|
+
modelList += ` ⢠${model}${current}\n`;
|
|
270
|
+
});
|
|
271
|
+
modelList += '\n';
|
|
272
|
+
});
|
|
273
|
+
return {
|
|
274
|
+
type: 'info',
|
|
275
|
+
message: modelList.trim()
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
const provider = args[0];
|
|
279
|
+
if (!this.availableModels[provider]) {
|
|
280
|
+
return {
|
|
281
|
+
type: 'error',
|
|
282
|
+
message: `Unknown provider: ${provider}\nAvailable providers: ${Object.keys(this.availableModels).join(', ')}`
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
let modelList = `š Available ${provider} models:\n\n`;
|
|
286
|
+
this.availableModels[provider].forEach(model => {
|
|
287
|
+
const current = provider === this.currentLLMConfig?.provider && model === this.currentLLMConfig?.model ? ' ā current' : '';
|
|
288
|
+
modelList += `⢠${model}${current}\n`;
|
|
289
|
+
});
|
|
290
|
+
return {
|
|
291
|
+
type: 'info',
|
|
292
|
+
message: modelList.trim()
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
handleStatus() {
|
|
296
|
+
const availableProviders = this.getAvailableProviders();
|
|
297
|
+
let statusText = 'š¤ Current Configuration:\n\n';
|
|
298
|
+
// API Keys status
|
|
299
|
+
statusText += 'š API Keys:\n';
|
|
300
|
+
const allProviders = ['openai', 'anthropic', 'google', 'mistral'];
|
|
301
|
+
allProviders.forEach(provider => {
|
|
302
|
+
const envVarMap = {
|
|
303
|
+
openai: 'OPENAI_API_KEY',
|
|
304
|
+
anthropic: 'ANTHROPIC_API_KEY',
|
|
305
|
+
google: 'GOOGLE_API_KEY',
|
|
306
|
+
mistral: 'MISTRAL_API_KEY'
|
|
307
|
+
};
|
|
308
|
+
const envVar = envVarMap[provider];
|
|
309
|
+
const hasEnvKey = !!process.env[envVar];
|
|
310
|
+
const hasSessionKey = !!this.sessionApiKeys[envVar];
|
|
311
|
+
const hasPersistentKey = !!this.persistentConfig.apiKeys[envVar];
|
|
312
|
+
if (hasSessionKey) {
|
|
313
|
+
const key = this.sessionApiKeys[envVar];
|
|
314
|
+
if (key) {
|
|
315
|
+
const maskedKey = this.maskApiKey(key);
|
|
316
|
+
statusText += ` ⢠${provider}: ${maskedKey} (session)\n`;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
else if (hasPersistentKey) {
|
|
320
|
+
const key = this.persistentConfig.apiKeys[envVar];
|
|
321
|
+
if (key) {
|
|
322
|
+
const maskedKey = this.maskApiKey(key);
|
|
323
|
+
statusText += ` ⢠${provider}: ${maskedKey} (stored)\n`;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
else if (hasEnvKey) {
|
|
327
|
+
const key = process.env[envVar];
|
|
328
|
+
if (key) {
|
|
329
|
+
const maskedKey = this.maskApiKey(key);
|
|
330
|
+
statusText += ` ⢠${provider}: ${maskedKey} (env)\n`;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
else {
|
|
334
|
+
statusText += ` ⢠${provider}: ā not set\n`;
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
statusText += '\n';
|
|
338
|
+
// Current model
|
|
339
|
+
if (!this.currentLLMConfig) {
|
|
340
|
+
if (availableProviders.length === 0) {
|
|
341
|
+
statusText += 'ā ļø No model selected\n';
|
|
342
|
+
statusText += '\nChoose a model to get started:\n';
|
|
343
|
+
statusText += '⢠/model openai gpt-4o-mini\n';
|
|
344
|
+
statusText += '⢠/model anthropic claude-3-5-sonnet-20241022\n';
|
|
345
|
+
statusText += '⢠/model google gemini-1.5-pro\n';
|
|
346
|
+
statusText += '\nThe CLI will help you set up API keys when needed.';
|
|
347
|
+
}
|
|
348
|
+
else {
|
|
349
|
+
statusText += 'ā ļø No model selected\n';
|
|
350
|
+
statusText += `\nAvailable providers: ${availableProviders.join(', ')}\n`;
|
|
351
|
+
statusText += 'Use /model <provider> <model> to get started';
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
else {
|
|
355
|
+
const config = this.currentLLMConfig;
|
|
356
|
+
statusText += `šÆ Active Model:\n`;
|
|
357
|
+
statusText += ` Provider: ${config.provider}\n`;
|
|
358
|
+
statusText += ` Model: ${config.model}\n`;
|
|
359
|
+
statusText += ` Temperature: ${config.temperature || 0.7}\n`;
|
|
360
|
+
statusText += ` Max Tokens: ${config.maxTokens || 'default'}\n`;
|
|
361
|
+
statusText += '\nUse /model to switch models or /config to adjust settings';
|
|
362
|
+
}
|
|
363
|
+
return {
|
|
364
|
+
type: 'info',
|
|
365
|
+
message: statusText
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
handleConfig(args) {
|
|
369
|
+
if (args.length < 2) {
|
|
370
|
+
return {
|
|
371
|
+
type: 'error',
|
|
372
|
+
message: 'Usage: /config <setting> <value>\nAvailable settings: temp, tokens'
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
const setting = args[0];
|
|
376
|
+
const value = args[1];
|
|
377
|
+
if (!value) {
|
|
378
|
+
return {
|
|
379
|
+
type: 'error',
|
|
380
|
+
message: 'Value is required'
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
if (!this.currentLLMConfig) {
|
|
384
|
+
return {
|
|
385
|
+
type: 'error',
|
|
386
|
+
message: 'No model configured. Use /model to select a provider and model first.'
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
switch (setting) {
|
|
390
|
+
case 'temp':
|
|
391
|
+
case 'temperature':
|
|
392
|
+
const temp = parseFloat(value);
|
|
393
|
+
if (isNaN(temp) || temp < 0 || temp > 2) {
|
|
394
|
+
return {
|
|
395
|
+
type: 'error',
|
|
396
|
+
message: 'Temperature must be a number between 0.0 and 2.0'
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
this.currentLLMConfig.temperature = temp;
|
|
400
|
+
return {
|
|
401
|
+
type: 'success',
|
|
402
|
+
message: `ā
Temperature set to ${temp}`,
|
|
403
|
+
data: { llmConfig: this.currentLLMConfig }
|
|
404
|
+
};
|
|
405
|
+
case 'tokens':
|
|
406
|
+
case 'max-tokens':
|
|
407
|
+
const tokens = parseInt(value);
|
|
408
|
+
if (isNaN(tokens) || tokens < 1) {
|
|
409
|
+
return {
|
|
410
|
+
type: 'error',
|
|
411
|
+
message: 'Max tokens must be a positive integer'
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
this.currentLLMConfig.maxTokens = tokens;
|
|
415
|
+
return {
|
|
416
|
+
type: 'success',
|
|
417
|
+
message: `ā
Max tokens set to ${tokens}`,
|
|
418
|
+
data: { llmConfig: this.currentLLMConfig }
|
|
419
|
+
};
|
|
420
|
+
default:
|
|
421
|
+
return {
|
|
422
|
+
type: 'error',
|
|
423
|
+
message: `Unknown setting: ${setting}\nAvailable settings: temp, tokens`
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
handleSetKey(args) {
|
|
428
|
+
if (args.length < 2) {
|
|
429
|
+
return {
|
|
430
|
+
type: 'error',
|
|
431
|
+
message: 'Usage: /setkey <provider> <api_key>\n\nSupported providers: openai, anthropic, google, mistral\n\nExample:\n/setkey openai sk-1234567890abcdef...'
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
const provider = args[0]?.toLowerCase();
|
|
435
|
+
const apiKey = args[1];
|
|
436
|
+
if (!provider || !apiKey) {
|
|
437
|
+
return {
|
|
438
|
+
type: 'error',
|
|
439
|
+
message: 'Both provider and API key are required'
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
// Validate provider
|
|
443
|
+
const validProviders = ['openai', 'anthropic', 'google', 'mistral'];
|
|
444
|
+
if (!validProviders.includes(provider)) {
|
|
445
|
+
return {
|
|
446
|
+
type: 'error',
|
|
447
|
+
message: `Invalid provider: ${provider}\nSupported providers: ${validProviders.join(', ')}`
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
// Basic API key validation
|
|
451
|
+
const validationResult = this.validateApiKey(provider, apiKey);
|
|
452
|
+
if (!validationResult.valid) {
|
|
453
|
+
return {
|
|
454
|
+
type: 'error',
|
|
455
|
+
message: validationResult.message
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
// Map provider to environment variable name
|
|
459
|
+
const envVarMap = {
|
|
460
|
+
openai: 'OPENAI_API_KEY',
|
|
461
|
+
anthropic: 'ANTHROPIC_API_KEY',
|
|
462
|
+
google: 'GOOGLE_API_KEY',
|
|
463
|
+
mistral: 'MISTRAL_API_KEY'
|
|
464
|
+
};
|
|
465
|
+
const envVar = envVarMap[provider];
|
|
466
|
+
// Store the API key in persistent storage
|
|
467
|
+
this.persistentConfig.apiKeys[envVar] = apiKey;
|
|
468
|
+
SecureStorage.saveConfig(this.persistentConfig);
|
|
469
|
+
// Check if we can auto-select this provider
|
|
470
|
+
const shouldAutoSelect = !this.currentLLMConfig;
|
|
471
|
+
if (shouldAutoSelect) {
|
|
472
|
+
const defaultModels = {
|
|
473
|
+
openai: 'gpt-4o-mini',
|
|
474
|
+
anthropic: 'claude-3-5-sonnet-20241022',
|
|
475
|
+
google: 'gemini-1.5-pro',
|
|
476
|
+
mistral: 'mistral-large-latest'
|
|
477
|
+
};
|
|
478
|
+
this.currentLLMConfig = {
|
|
479
|
+
provider: provider,
|
|
480
|
+
model: defaultModels[provider],
|
|
481
|
+
temperature: 0.7
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
const maskedKey = this.maskApiKey(apiKey);
|
|
485
|
+
let message = `ā
${provider} API key set (${maskedKey})`;
|
|
486
|
+
if (shouldAutoSelect) {
|
|
487
|
+
message += `\nš¤ Auto-selected ${this.currentLLMConfig.provider}/${this.currentLLMConfig.model}`;
|
|
488
|
+
}
|
|
489
|
+
return {
|
|
490
|
+
type: 'success',
|
|
491
|
+
message,
|
|
492
|
+
data: shouldAutoSelect ? { llmConfig: this.currentLLMConfig } : undefined
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
validateApiKey(provider, apiKey) {
|
|
496
|
+
if (!apiKey || apiKey.trim().length === 0) {
|
|
497
|
+
return { valid: false, message: 'API key cannot be empty' };
|
|
498
|
+
}
|
|
499
|
+
// Basic format validation for each provider
|
|
500
|
+
switch (provider) {
|
|
501
|
+
case 'openai':
|
|
502
|
+
if (!apiKey.startsWith('sk-') || apiKey.length < 20) {
|
|
503
|
+
return { valid: false, message: 'OpenAI API keys should start with "sk-" and be at least 20 characters long' };
|
|
504
|
+
}
|
|
505
|
+
break;
|
|
506
|
+
case 'anthropic':
|
|
507
|
+
if (!apiKey.startsWith('ant_') && !apiKey.startsWith('sk-ant-') || apiKey.length < 20) {
|
|
508
|
+
return { valid: false, message: 'Anthropic API keys should start with "ant_" or "sk-ant-" and be at least 20 characters long' };
|
|
509
|
+
}
|
|
510
|
+
break;
|
|
511
|
+
case 'google':
|
|
512
|
+
if (apiKey.length < 20) {
|
|
513
|
+
return { valid: false, message: 'Google API keys should be at least 20 characters long' };
|
|
514
|
+
}
|
|
515
|
+
break;
|
|
516
|
+
case 'mistral':
|
|
517
|
+
if (apiKey.length < 20) {
|
|
518
|
+
return { valid: false, message: 'Mistral API keys should be at least 20 characters long' };
|
|
519
|
+
}
|
|
520
|
+
break;
|
|
521
|
+
}
|
|
522
|
+
return { valid: true, message: '' };
|
|
523
|
+
}
|
|
524
|
+
maskApiKey(apiKey) {
|
|
525
|
+
if (apiKey.length <= 8) {
|
|
526
|
+
return '*'.repeat(apiKey.length);
|
|
527
|
+
}
|
|
528
|
+
const start = apiKey.substring(0, 4);
|
|
529
|
+
const end = apiKey.substring(apiKey.length - 4);
|
|
530
|
+
const middle = '*'.repeat(Math.min(12, apiKey.length - 8));
|
|
531
|
+
return `${start}${middle}${end}`;
|
|
532
|
+
}
|
|
533
|
+
createLLM() {
|
|
534
|
+
if (!this.currentLLMConfig) {
|
|
535
|
+
throw new Error('No LLM configured. Use /model command to select a provider and model.');
|
|
536
|
+
}
|
|
537
|
+
const config = this.currentLLMConfig;
|
|
538
|
+
const baseConfig = {
|
|
539
|
+
temperature: config.temperature || 0.7,
|
|
540
|
+
maxTokens: config.maxTokens
|
|
541
|
+
};
|
|
542
|
+
switch (config.provider) {
|
|
543
|
+
case 'openai':
|
|
544
|
+
const openaiKey = this.getApiKey('OPENAI_API_KEY');
|
|
545
|
+
if (!openaiKey) {
|
|
546
|
+
throw new Error('OPENAI_API_KEY is required for OpenAI models. Use /setkey openai <your-key>');
|
|
547
|
+
}
|
|
548
|
+
return new ChatOpenAI({
|
|
549
|
+
modelName: config.model,
|
|
550
|
+
openAIApiKey: openaiKey,
|
|
551
|
+
...baseConfig
|
|
552
|
+
});
|
|
553
|
+
case 'anthropic':
|
|
554
|
+
const anthropicKey = this.getApiKey('ANTHROPIC_API_KEY');
|
|
555
|
+
if (!anthropicKey) {
|
|
556
|
+
throw new Error('ANTHROPIC_API_KEY is required for Anthropic models. Use /setkey anthropic <your-key>');
|
|
557
|
+
}
|
|
558
|
+
return new ChatAnthropic({
|
|
559
|
+
modelName: config.model,
|
|
560
|
+
anthropicApiKey: anthropicKey,
|
|
561
|
+
...baseConfig
|
|
562
|
+
});
|
|
563
|
+
case 'google':
|
|
564
|
+
const googleKey = this.getApiKey('GOOGLE_API_KEY');
|
|
565
|
+
if (!googleKey) {
|
|
566
|
+
throw new Error('GOOGLE_API_KEY is required for Google models. Use /setkey google <your-key>');
|
|
567
|
+
}
|
|
568
|
+
return new ChatGoogleGenerativeAI({
|
|
569
|
+
model: config.model,
|
|
570
|
+
apiKey: googleKey,
|
|
571
|
+
...baseConfig
|
|
572
|
+
});
|
|
573
|
+
case 'mistral':
|
|
574
|
+
const mistralKey = this.getApiKey('MISTRAL_API_KEY');
|
|
575
|
+
if (!mistralKey) {
|
|
576
|
+
throw new Error('MISTRAL_API_KEY is required for Mistral models. Use /setkey mistral <your-key>');
|
|
577
|
+
}
|
|
578
|
+
return new ChatMistralAI({
|
|
579
|
+
modelName: config.model,
|
|
580
|
+
apiKey: mistralKey,
|
|
581
|
+
...baseConfig
|
|
582
|
+
});
|
|
583
|
+
default:
|
|
584
|
+
throw new Error(`Unsupported provider: ${config.provider}`);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
getCurrentConfig() {
|
|
588
|
+
return this.currentLLMConfig ? { ...this.currentLLMConfig } : null;
|
|
589
|
+
}
|
|
590
|
+
getCurrentStoredConfig() {
|
|
591
|
+
return this.persistentConfig;
|
|
592
|
+
}
|
|
593
|
+
getSessionServers() {
|
|
594
|
+
return this.sessionServers;
|
|
595
|
+
}
|
|
596
|
+
handleListTools() {
|
|
597
|
+
return {
|
|
598
|
+
type: 'info',
|
|
599
|
+
message: 'š§ Checking available MCP tools...\n\nThis command will show tools available from connected MCP servers.\nNote: This requires the MCP service to provide tool listing functionality.',
|
|
600
|
+
data: { checkTools: true }
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
handleTestServer(args) {
|
|
604
|
+
if (args.length === 0) {
|
|
605
|
+
const configuredServers = Object.keys(this.persistentConfig.mcpServers || {});
|
|
606
|
+
if (configuredServers.length === 0) {
|
|
607
|
+
return {
|
|
608
|
+
type: 'error',
|
|
609
|
+
message: 'No servers configured to test.\n\nUsage: /test-server <server_name>\n\nUse /server add to configure servers first.'
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
return {
|
|
613
|
+
type: 'info',
|
|
614
|
+
message: `Usage: /test-server <server_name>\n\nConfigured servers: ${configuredServers.join(', ')}\n\nThis command will test if the server package can be started manually.`
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
const serverName = args[0];
|
|
618
|
+
if (!serverName) {
|
|
619
|
+
return {
|
|
620
|
+
type: 'error',
|
|
621
|
+
message: 'Server name is required.\n\nUsage: /test-server <server_name>'
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
const serverConfig = this.persistentConfig.mcpServers?.[serverName];
|
|
625
|
+
if (!serverConfig) {
|
|
626
|
+
const configuredServers = Object.keys(this.persistentConfig.mcpServers || {});
|
|
627
|
+
return {
|
|
628
|
+
type: 'error',
|
|
629
|
+
message: `Server "${serverName}" is not configured.\n\nConfigured servers: ${configuredServers.length > 0 ? configuredServers.join(', ') : 'none'}`
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
const command = serverConfig.command;
|
|
633
|
+
const args_str = serverConfig.args ? serverConfig.args.join(' ') : '';
|
|
634
|
+
const full_command = `${command} ${args_str}`.trim();
|
|
635
|
+
return {
|
|
636
|
+
type: 'info',
|
|
637
|
+
message: `š§Ŗ Testing server "${serverName}"...\n\nCommand: ${full_command}\n\nā ļø Note: This will attempt to run the server command manually.\nCheck the console for output and errors.\n\nš” Try running this command manually in your terminal:\n${full_command}`,
|
|
638
|
+
data: { testServer: true, serverName, command: full_command }
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
isCommand(input) {
|
|
642
|
+
return input.trim().startsWith('/');
|
|
643
|
+
}
|
|
644
|
+
handleClearKeys() {
|
|
645
|
+
// Clear both session and persistent keys
|
|
646
|
+
this.sessionApiKeys = {};
|
|
647
|
+
this.persistentConfig.apiKeys = {};
|
|
648
|
+
this.persistentConfig.lastModel = undefined;
|
|
649
|
+
// Clear the current LLM config since we have no keys
|
|
650
|
+
this.currentLLMConfig = null;
|
|
651
|
+
// Save the cleared config
|
|
652
|
+
SecureStorage.saveConfig(this.persistentConfig);
|
|
653
|
+
return {
|
|
654
|
+
type: 'success',
|
|
655
|
+
message: 'ā
All API keys cleared from storage.\n\nUse /setkey or /model to set up a new provider.',
|
|
656
|
+
data: { llmConfig: null }
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
// Method to handle server configuration input
|
|
660
|
+
handleServerConfigInput(input, step, serverConfig) {
|
|
661
|
+
const config = serverConfig || {};
|
|
662
|
+
switch (step) {
|
|
663
|
+
case 'name_or_json':
|
|
664
|
+
// Check if input looks like JSON
|
|
665
|
+
const trimmedInput = input.trim();
|
|
666
|
+
if (trimmedInput.startsWith('{') && trimmedInput.includes('mcpServers')) {
|
|
667
|
+
try {
|
|
668
|
+
const parsedConfig = JSON.parse(trimmedInput);
|
|
669
|
+
// Validate JSON structure
|
|
670
|
+
if (!parsedConfig.mcpServers || typeof parsedConfig.mcpServers !== 'object') {
|
|
671
|
+
return {
|
|
672
|
+
type: 'error',
|
|
673
|
+
message: 'Invalid JSON format. Expected format:\n{\n "mcpServers": {\n "servername": {\n "command": "...",\n "args": [...]\n }\n }\n}'
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
const servers = parsedConfig.mcpServers;
|
|
677
|
+
const serverNames = Object.keys(servers);
|
|
678
|
+
if (serverNames.length === 0) {
|
|
679
|
+
return {
|
|
680
|
+
type: 'error',
|
|
681
|
+
message: 'No servers found in JSON configuration.'
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
// Check for conflicts with existing servers
|
|
685
|
+
const existingServers = this.persistentConfig.mcpServers || {};
|
|
686
|
+
const conflicts = serverNames.filter(name => existingServers[name]);
|
|
687
|
+
if (conflicts.length > 0) {
|
|
688
|
+
return {
|
|
689
|
+
type: 'error',
|
|
690
|
+
message: `Server(s) already exist: ${conflicts.join(', ')}. Please use different names.`
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
// Validate each server config
|
|
694
|
+
for (const [name, serverConfig] of Object.entries(servers)) {
|
|
695
|
+
const server = serverConfig;
|
|
696
|
+
if (!server.command || typeof server.command !== 'string') {
|
|
697
|
+
return {
|
|
698
|
+
type: 'error',
|
|
699
|
+
message: `Server "${name}" missing required "command" field.`
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
// All validation passed, save the servers
|
|
704
|
+
if (!this.persistentConfig.mcpServers) {
|
|
705
|
+
this.persistentConfig.mcpServers = {};
|
|
706
|
+
}
|
|
707
|
+
// Add all servers from JSON
|
|
708
|
+
Object.assign(this.persistentConfig.mcpServers, servers);
|
|
709
|
+
SecureStorage.saveConfig(this.persistentConfig);
|
|
710
|
+
// Auto-connect all newly configured servers
|
|
711
|
+
Object.assign(this.sessionServers, servers);
|
|
712
|
+
const addedCount = serverNames.length;
|
|
713
|
+
const serverList = serverNames.map(name => `⢠${name}`).join('\n');
|
|
714
|
+
return {
|
|
715
|
+
type: 'success',
|
|
716
|
+
message: `ā
Configured and connected ${addedCount} server(s)!\n\n${serverList}\n\nš Agent will be reinitialized with these servers - attempting to establish connections...\nUse /tools to verify the server tools are available.`,
|
|
717
|
+
data: { serversAdded: true, serverConnected: true, serverNames }
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
catch (error) {
|
|
721
|
+
return {
|
|
722
|
+
type: 'error',
|
|
723
|
+
message: `Invalid JSON format: ${error instanceof Error ? error.message : 'Parse error'}\n\nPlease check your JSON syntax and try again.`
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
// Not JSON, treat as server name for interactive setup
|
|
728
|
+
if (!trimmedInput) {
|
|
729
|
+
return {
|
|
730
|
+
type: 'error',
|
|
731
|
+
message: 'Server name cannot be empty.'
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
// Check if server name already exists
|
|
735
|
+
if (this.persistentConfig.mcpServers?.[trimmedInput]) {
|
|
736
|
+
return {
|
|
737
|
+
type: 'error',
|
|
738
|
+
message: `Server "${trimmedInput}" already exists. Use a different name.`
|
|
739
|
+
};
|
|
740
|
+
}
|
|
741
|
+
config.name = trimmedInput;
|
|
742
|
+
return {
|
|
743
|
+
type: 'prompt_server_config',
|
|
744
|
+
message: `Server name: ${config.name}\n\nEnter the command to run this server (e.g., "npx", "node", "python"):`,
|
|
745
|
+
data: { step: 'command', config }
|
|
746
|
+
};
|
|
747
|
+
case 'name':
|
|
748
|
+
if (!input.trim()) {
|
|
749
|
+
return {
|
|
750
|
+
type: 'error',
|
|
751
|
+
message: 'Server name cannot be empty.'
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
// Check if server name already exists
|
|
755
|
+
if (this.persistentConfig.mcpServers?.[input.trim()]) {
|
|
756
|
+
return {
|
|
757
|
+
type: 'error',
|
|
758
|
+
message: `Server "${input.trim()}" already exists. Use a different name.`
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
config.name = input.trim();
|
|
762
|
+
return {
|
|
763
|
+
type: 'prompt_server_config',
|
|
764
|
+
message: `Server name: ${config.name}\n\nEnter the command to run this server (e.g., "npx", "node", "python"):`,
|
|
765
|
+
data: { step: 'command', config }
|
|
766
|
+
};
|
|
767
|
+
case 'command':
|
|
768
|
+
if (!input.trim()) {
|
|
769
|
+
return {
|
|
770
|
+
type: 'error',
|
|
771
|
+
message: 'Command cannot be empty.'
|
|
772
|
+
};
|
|
773
|
+
}
|
|
774
|
+
config.command = input.trim();
|
|
775
|
+
return {
|
|
776
|
+
type: 'prompt_server_config',
|
|
777
|
+
message: `Server name: ${config.name}\nCommand: ${config.command}\n\nEnter arguments (space-separated, or press Enter for none):\nExample: "-y @modelcontextprotocol/server-filesystem /tmp"`,
|
|
778
|
+
data: { step: 'args', config }
|
|
779
|
+
};
|
|
780
|
+
case 'args':
|
|
781
|
+
config.args = input.trim() ? input.trim().split(/\s+/) : [];
|
|
782
|
+
return {
|
|
783
|
+
type: 'prompt_server_config',
|
|
784
|
+
message: `Server name: ${config.name}\nCommand: ${config.command}\nArgs: ${config.args.length > 0 ? config.args.join(' ') : 'none'}\n\nEnter environment variables (KEY=VALUE format, one per line, or press Enter for none):\nExample: "DEBUG=1" or press Enter to skip:`,
|
|
785
|
+
data: { step: 'env', config }
|
|
786
|
+
};
|
|
787
|
+
case 'env':
|
|
788
|
+
config.env = {};
|
|
789
|
+
if (input.trim()) {
|
|
790
|
+
const envLines = input.trim().split('\n');
|
|
791
|
+
for (const line of envLines) {
|
|
792
|
+
const [key, ...valueParts] = line.split('=');
|
|
793
|
+
if (key && valueParts.length > 0) {
|
|
794
|
+
config.env[key.trim()] = valueParts.join('=').trim();
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
return {
|
|
799
|
+
type: 'prompt_server_config',
|
|
800
|
+
message: `Server Configuration Summary:\n\nName: ${config.name}\nCommand: ${config.command}\nArgs: ${config.args.length > 0 ? config.args.join(' ') : 'none'}\nEnv: ${Object.keys(config.env).length > 0 ? Object.entries(config.env).map(([k, v]) => `${k}=${v}`).join(', ') : 'none'}\n\nConfirm to add this server? (y/n):`,
|
|
801
|
+
data: { step: 'confirm', config }
|
|
802
|
+
};
|
|
803
|
+
case 'confirm':
|
|
804
|
+
if (input.trim().toLowerCase() === 'y' || input.trim().toLowerCase() === 'yes') {
|
|
805
|
+
const serverConfig = {
|
|
806
|
+
command: config.command,
|
|
807
|
+
args: config.args,
|
|
808
|
+
env: config.env
|
|
809
|
+
};
|
|
810
|
+
// Add server to persistent configuration (servers are configured but not automatically connected)
|
|
811
|
+
if (!this.persistentConfig.mcpServers) {
|
|
812
|
+
this.persistentConfig.mcpServers = {};
|
|
813
|
+
}
|
|
814
|
+
this.persistentConfig.mcpServers[config.name] = serverConfig;
|
|
815
|
+
// Save configuration
|
|
816
|
+
SecureStorage.saveConfig(this.persistentConfig);
|
|
817
|
+
// Auto-connect the newly configured server
|
|
818
|
+
this.sessionServers[config.name] = serverConfig;
|
|
819
|
+
return {
|
|
820
|
+
type: 'success',
|
|
821
|
+
message: `ā
Server "${config.name}" configured and connected!\n\nš Agent will be reinitialized with this server - attempting to establish connection...\nUse /tools to verify the server tools are available.`,
|
|
822
|
+
data: { serverAdded: true, serverConnected: true, serverName: config.name }
|
|
823
|
+
};
|
|
824
|
+
}
|
|
825
|
+
else if (input.trim().toLowerCase() === 'n' || input.trim().toLowerCase() === 'no') {
|
|
826
|
+
return {
|
|
827
|
+
type: 'info',
|
|
828
|
+
message: 'Server configuration cancelled.'
|
|
829
|
+
};
|
|
830
|
+
}
|
|
831
|
+
else {
|
|
832
|
+
return {
|
|
833
|
+
type: 'error',
|
|
834
|
+
message: 'Please enter "y" for yes or "n" for no.'
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
default:
|
|
838
|
+
return {
|
|
839
|
+
type: 'error',
|
|
840
|
+
message: 'Invalid server configuration step.'
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
handleServer(args) {
|
|
845
|
+
if (args.length === 0) {
|
|
846
|
+
return {
|
|
847
|
+
type: 'info',
|
|
848
|
+
message: 'Server management commands:\n\n/server add - Configure a new server (stored but not connected)\n/server connect <name> - Connect to a configured server by name\n/server disconnect <name> - Disconnect from a connected server\n/servers - List configured servers and connection status\n\nUse /server <command> for specific help.'
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
if (args[0] === 'add') {
|
|
852
|
+
return {
|
|
853
|
+
type: 'prompt_server_config',
|
|
854
|
+
message: 'Let\'s configure a new MCP server!\n\nYou can either:\n1. Enter a server name for interactive setup\n2. Paste a complete JSON configuration\n\nExample JSON:\n{\n "mcpServers": {\n "myserver": {\n "command": "npx",\n "args": ["-y", "@example/server"]\n }\n }\n}\n\nEnter server name or paste JSON:',
|
|
855
|
+
data: { step: 'name_or_json' }
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
if (args[0] === 'connect') {
|
|
859
|
+
if (args.length < 2) {
|
|
860
|
+
const configuredServers = Object.keys({ ...this.persistentConfig.mcpServers });
|
|
861
|
+
if (configuredServers.length === 0) {
|
|
862
|
+
return {
|
|
863
|
+
type: 'error',
|
|
864
|
+
message: 'No servers configured. Use /server add to configure servers first.\n\nUsage: /server connect <server_name>'
|
|
865
|
+
};
|
|
866
|
+
}
|
|
867
|
+
return {
|
|
868
|
+
type: 'error',
|
|
869
|
+
message: `Usage: /server connect <server_name>\n\nConfigured servers: ${configuredServers.join(', ')}`
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
const serverName = args[1];
|
|
873
|
+
if (!serverName) {
|
|
874
|
+
return {
|
|
875
|
+
type: 'error',
|
|
876
|
+
message: 'Server name is required.\n\nUsage: /server connect <server_name>'
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
return this.handleConnectServer(serverName);
|
|
880
|
+
}
|
|
881
|
+
if (args[0] === 'disconnect') {
|
|
882
|
+
if (args.length < 2) {
|
|
883
|
+
const connectedServers = Object.keys(this.sessionServers);
|
|
884
|
+
if (connectedServers.length === 0) {
|
|
885
|
+
return {
|
|
886
|
+
type: 'info',
|
|
887
|
+
message: 'No servers currently connected.\n\nUsage: /server disconnect <server_name>'
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
return {
|
|
891
|
+
type: 'error',
|
|
892
|
+
message: `Usage: /server disconnect <server_name>\n\nConnected servers: ${connectedServers.join(', ')}`
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
const serverName = args[1];
|
|
896
|
+
if (!serverName) {
|
|
897
|
+
return {
|
|
898
|
+
type: 'error',
|
|
899
|
+
message: 'Server name is required.\n\nUsage: /server disconnect <server_name>'
|
|
900
|
+
};
|
|
901
|
+
}
|
|
902
|
+
return this.handleDisconnectServer(serverName);
|
|
903
|
+
}
|
|
904
|
+
return {
|
|
905
|
+
type: 'error',
|
|
906
|
+
message: 'Usage: /server <command>\n\nCommands:\n add - Configure server\n connect <name> - Connect to server\n disconnect <name> - Disconnect server\n\nExample: /server connect airbnb'
|
|
907
|
+
};
|
|
908
|
+
}
|
|
909
|
+
handleListServers() {
|
|
910
|
+
const persistentServers = this.persistentConfig.mcpServers || {};
|
|
911
|
+
const connectedServers = this.sessionServers || {};
|
|
912
|
+
let serverList = 'š MCP Server Status:\n\n';
|
|
913
|
+
// Built-in filesystem server (always connected)
|
|
914
|
+
serverList += 'šø filesystem:\n';
|
|
915
|
+
serverList += ' Status: š¢ Connected (built-in)\n';
|
|
916
|
+
serverList += ' Command: npx\n';
|
|
917
|
+
serverList += ' Args: -y @modelcontextprotocol/server-filesystem /tmp\n\n';
|
|
918
|
+
// Custom configured servers
|
|
919
|
+
const configuredServerNames = Object.keys(persistentServers);
|
|
920
|
+
if (configuredServerNames.length === 0) {
|
|
921
|
+
serverList += 'No custom servers configured.\n\nUse /server add to configure servers, then /server connect <name> to connect.';
|
|
922
|
+
}
|
|
923
|
+
else {
|
|
924
|
+
for (const [name, config] of Object.entries(persistentServers)) {
|
|
925
|
+
const isConnected = connectedServers[name] !== undefined;
|
|
926
|
+
const status = isConnected ? 'š¢ Connected' : 'š“ Disconnected';
|
|
927
|
+
const action = isConnected ? '/server disconnect' : '/server connect';
|
|
928
|
+
serverList += `šø ${name}:\n`;
|
|
929
|
+
serverList += ` Status: ${status}\n`;
|
|
930
|
+
serverList += ` Command: ${config.command}\n`;
|
|
931
|
+
if (config.args && config.args.length > 0) {
|
|
932
|
+
serverList += ` Args: ${config.args.join(' ')}\n`;
|
|
933
|
+
}
|
|
934
|
+
if (config.env && Object.keys(config.env).length > 0) {
|
|
935
|
+
serverList += ` Env: ${Object.entries(config.env).map(([k, v]) => `${k}=${v}`).join(', ')}\n`;
|
|
936
|
+
}
|
|
937
|
+
serverList += ` Action: ${action} ${name}\n\n`;
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
return {
|
|
941
|
+
type: 'info',
|
|
942
|
+
message: serverList.trim()
|
|
943
|
+
};
|
|
944
|
+
}
|
|
945
|
+
handleConnectServer(serverName) {
|
|
946
|
+
// Check if server is configured
|
|
947
|
+
const configuredServer = this.persistentConfig.mcpServers?.[serverName];
|
|
948
|
+
if (!configuredServer) {
|
|
949
|
+
const availableServers = Object.keys(this.persistentConfig.mcpServers || {});
|
|
950
|
+
return {
|
|
951
|
+
type: 'error',
|
|
952
|
+
message: `Server "${serverName}" is not configured.\n\nConfigured servers: ${availableServers.length > 0 ? availableServers.join(', ') : 'none'}\n\nUse /server add to configure new servers.`
|
|
953
|
+
};
|
|
954
|
+
}
|
|
955
|
+
// Check if already connected (in session servers)
|
|
956
|
+
if (this.sessionServers[serverName]) {
|
|
957
|
+
return {
|
|
958
|
+
type: 'info',
|
|
959
|
+
message: `ā
Server "${serverName}" is already connected.`
|
|
960
|
+
};
|
|
961
|
+
}
|
|
962
|
+
// Connect the server (add to session servers)
|
|
963
|
+
this.sessionServers[serverName] = configuredServer;
|
|
964
|
+
return {
|
|
965
|
+
type: 'success',
|
|
966
|
+
message: `ā
Connected to server "${serverName}"!\n\nš Agent will be reinitialized with this server - attempting to establish connection...\nUse /tools to verify the server tools are available.`,
|
|
967
|
+
data: { serverConnected: true, serverName }
|
|
968
|
+
};
|
|
969
|
+
}
|
|
970
|
+
handleDisconnectServer(serverName) {
|
|
971
|
+
// Check if server is connected
|
|
972
|
+
if (!this.sessionServers[serverName]) {
|
|
973
|
+
const connectedServers = Object.keys(this.sessionServers);
|
|
974
|
+
return {
|
|
975
|
+
type: 'error',
|
|
976
|
+
message: `Server "${serverName}" is not connected.\n\nConnected servers: ${connectedServers.length > 0 ? connectedServers.join(', ') : 'none'}`
|
|
977
|
+
};
|
|
978
|
+
}
|
|
979
|
+
// Disconnect the server (remove from session servers)
|
|
980
|
+
delete this.sessionServers[serverName];
|
|
981
|
+
return {
|
|
982
|
+
type: 'success',
|
|
983
|
+
message: `ā
Disconnected from server "${serverName}".\n\nš Agent will be reinitialized without this server.`,
|
|
984
|
+
data: { serverDisconnected: true, serverName }
|
|
985
|
+
};
|
|
986
|
+
}
|
|
987
|
+
// Method to handle API key input and complete model selection
|
|
988
|
+
handleApiKeyInput(apiKey, provider, model) {
|
|
989
|
+
// Validate the API key
|
|
990
|
+
const validationResult = this.validateApiKey(provider, apiKey);
|
|
991
|
+
if (!validationResult.valid) {
|
|
992
|
+
return {
|
|
993
|
+
type: 'error',
|
|
994
|
+
message: validationResult.message
|
|
995
|
+
};
|
|
996
|
+
}
|
|
997
|
+
// Map provider to environment variable name
|
|
998
|
+
const envVarMap = {
|
|
999
|
+
openai: 'OPENAI_API_KEY',
|
|
1000
|
+
anthropic: 'ANTHROPIC_API_KEY',
|
|
1001
|
+
google: 'GOOGLE_API_KEY',
|
|
1002
|
+
mistral: 'MISTRAL_API_KEY'
|
|
1003
|
+
};
|
|
1004
|
+
const envVar = envVarMap[provider];
|
|
1005
|
+
// Store the API key in persistent storage
|
|
1006
|
+
this.persistentConfig.apiKeys[envVar] = apiKey;
|
|
1007
|
+
// Set the model configuration
|
|
1008
|
+
this.currentLLMConfig = {
|
|
1009
|
+
provider: provider,
|
|
1010
|
+
model,
|
|
1011
|
+
temperature: this.currentLLMConfig?.temperature || 0.7,
|
|
1012
|
+
maxTokens: this.currentLLMConfig?.maxTokens
|
|
1013
|
+
};
|
|
1014
|
+
// Save both the API key and the current model to persistent storage
|
|
1015
|
+
this.persistentConfig.lastModel = this.currentLLMConfig;
|
|
1016
|
+
SecureStorage.saveConfig(this.persistentConfig);
|
|
1017
|
+
const maskedKey = this.maskApiKey(apiKey);
|
|
1018
|
+
return {
|
|
1019
|
+
type: 'success',
|
|
1020
|
+
message: `ā
${provider} API key set (${maskedKey})\nš¤ Switched to ${provider}/${model}`,
|
|
1021
|
+
data: { llmConfig: this.currentLLMConfig }
|
|
1022
|
+
};
|
|
1023
|
+
}
|
|
1024
|
+
}
|