@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
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { CommandResult } from './commands.js';
|
|
2
|
+
export interface MCPMessage {
|
|
3
|
+
id: string;
|
|
4
|
+
role: 'user' | 'assistant';
|
|
5
|
+
content: string;
|
|
6
|
+
timestamp: Date;
|
|
7
|
+
}
|
|
8
|
+
export interface MCPToolCall {
|
|
9
|
+
id: string;
|
|
10
|
+
role: 'tool';
|
|
11
|
+
tool_name: string;
|
|
12
|
+
tool_input: Record<string, any>;
|
|
13
|
+
tool_output: Record<string, any>;
|
|
14
|
+
}
|
|
15
|
+
export declare class MCPService {
|
|
16
|
+
private agent;
|
|
17
|
+
private isInitialized;
|
|
18
|
+
private commandHandler;
|
|
19
|
+
private client;
|
|
20
|
+
initialize(config?: any): Promise<void>;
|
|
21
|
+
private initializeAgent;
|
|
22
|
+
private reinitializeWithNewServers;
|
|
23
|
+
sendMessage(message: string, isApiKeyInput?: boolean, pendingProvider?: string, pendingModel?: string, isServerConfigInput?: boolean, serverConfigStep?: string, serverConfig?: any): Promise<{
|
|
24
|
+
response: string;
|
|
25
|
+
toolCalls: MCPToolCall[];
|
|
26
|
+
isCommand?: boolean;
|
|
27
|
+
commandResult?: CommandResult;
|
|
28
|
+
}>;
|
|
29
|
+
private getExampleModel;
|
|
30
|
+
streamMessage(message: string): AsyncGenerator<{
|
|
31
|
+
content?: string;
|
|
32
|
+
toolCall?: MCPToolCall;
|
|
33
|
+
done: boolean;
|
|
34
|
+
}>;
|
|
35
|
+
isReady(): boolean;
|
|
36
|
+
getCurrentModel(): string;
|
|
37
|
+
getConfiguredServers(): string[];
|
|
38
|
+
getConnectedServers(): string[];
|
|
39
|
+
getAvailableTools(): Promise<{
|
|
40
|
+
tools: any[];
|
|
41
|
+
error?: string;
|
|
42
|
+
}>;
|
|
43
|
+
}
|
|
44
|
+
export declare const mcpService: MCPService;
|
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
import { MCPAgent, MCPClient } from 'mcp-use';
|
|
2
|
+
import { config } from 'dotenv';
|
|
3
|
+
import { CommandHandler } from './commands.js';
|
|
4
|
+
// Load environment variables
|
|
5
|
+
config();
|
|
6
|
+
export class MCPService {
|
|
7
|
+
constructor() {
|
|
8
|
+
Object.defineProperty(this, "agent", {
|
|
9
|
+
enumerable: true,
|
|
10
|
+
configurable: true,
|
|
11
|
+
writable: true,
|
|
12
|
+
value: null
|
|
13
|
+
});
|
|
14
|
+
Object.defineProperty(this, "isInitialized", {
|
|
15
|
+
enumerable: true,
|
|
16
|
+
configurable: true,
|
|
17
|
+
writable: true,
|
|
18
|
+
value: false
|
|
19
|
+
});
|
|
20
|
+
Object.defineProperty(this, "commandHandler", {
|
|
21
|
+
enumerable: true,
|
|
22
|
+
configurable: true,
|
|
23
|
+
writable: true,
|
|
24
|
+
value: new CommandHandler()
|
|
25
|
+
});
|
|
26
|
+
Object.defineProperty(this, "client", {
|
|
27
|
+
enumerable: true,
|
|
28
|
+
configurable: true,
|
|
29
|
+
writable: true,
|
|
30
|
+
value: null
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
async initialize(config) {
|
|
34
|
+
if (this.isInitialized)
|
|
35
|
+
return;
|
|
36
|
+
try {
|
|
37
|
+
// Load servers from persistent storage and session
|
|
38
|
+
const storedConfig = this.commandHandler.getCurrentStoredConfig();
|
|
39
|
+
const storedServers = storedConfig.mcpServers || {};
|
|
40
|
+
const sessionServers = this.commandHandler.getSessionServers();
|
|
41
|
+
// Default filesystem server
|
|
42
|
+
const defaultFilesystemServer = {
|
|
43
|
+
"command": "npx",
|
|
44
|
+
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"],
|
|
45
|
+
"env": {}
|
|
46
|
+
};
|
|
47
|
+
// Merge stored servers, session servers with default (session servers override persistent ones)
|
|
48
|
+
const servers = {
|
|
49
|
+
"filesystem": defaultFilesystemServer,
|
|
50
|
+
...storedServers,
|
|
51
|
+
...sessionServers
|
|
52
|
+
};
|
|
53
|
+
const defaultConfig = {
|
|
54
|
+
"servers": servers
|
|
55
|
+
};
|
|
56
|
+
const mcpConfig = config || defaultConfig;
|
|
57
|
+
// Initialize MCP client with logging
|
|
58
|
+
console.log('Initializing MCP client with config:', JSON.stringify(mcpConfig, null, 2));
|
|
59
|
+
this.client = MCPClient.fromDict(mcpConfig);
|
|
60
|
+
console.log('MCP client created successfully');
|
|
61
|
+
// Only initialize agent if we have a configured LLM
|
|
62
|
+
if (this.commandHandler.isAnyProviderAvailable() && this.commandHandler.getCurrentConfig()) {
|
|
63
|
+
await this.initializeAgent();
|
|
64
|
+
}
|
|
65
|
+
this.isInitialized = true;
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
console.error('Failed to initialize MCP service:', error);
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async initializeAgent() {
|
|
73
|
+
if (!this.client) {
|
|
74
|
+
throw new Error('MCP client not initialized');
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
// Create LLM using command handler
|
|
78
|
+
console.log('Creating LLM for agent...');
|
|
79
|
+
const llm = this.commandHandler.createLLM();
|
|
80
|
+
console.log('LLM created successfully');
|
|
81
|
+
// Create agent
|
|
82
|
+
console.log('Creating MCPAgent...');
|
|
83
|
+
this.agent = new MCPAgent({
|
|
84
|
+
llm,
|
|
85
|
+
client: this.client,
|
|
86
|
+
maxSteps: 20,
|
|
87
|
+
});
|
|
88
|
+
console.log('MCPAgent created successfully');
|
|
89
|
+
// Initialize the agent
|
|
90
|
+
console.log('Initializing MCP agent with servers...');
|
|
91
|
+
await this.agent.initialize();
|
|
92
|
+
console.log('MCP agent initialized successfully');
|
|
93
|
+
// Wait a bit for servers to start up
|
|
94
|
+
console.log('Waiting for servers to start...');
|
|
95
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
96
|
+
// Log available tools for debugging
|
|
97
|
+
try {
|
|
98
|
+
console.log('Checking for available tools...');
|
|
99
|
+
const toolsResult = await this.getAvailableTools();
|
|
100
|
+
if (toolsResult.tools.length > 0) {
|
|
101
|
+
console.log('✅ Available MCP tools:', toolsResult.tools.map((t) => t.name || 'unnamed').join(', '));
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
console.log('❌ No MCP tools found:', toolsResult.error || 'Unknown reason');
|
|
105
|
+
// Try to get more info about the client state
|
|
106
|
+
console.log('Client details:', this.client);
|
|
107
|
+
const clientAny = this.client;
|
|
108
|
+
if (clientAny.servers) {
|
|
109
|
+
console.log('Client servers:', Object.keys(clientAny.servers));
|
|
110
|
+
for (const [name, server] of Object.entries(clientAny.servers)) {
|
|
111
|
+
console.log(`Server ${name}:`, server);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
catch (e) {
|
|
117
|
+
console.log('❌ Error checking MCP tools:', e);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
console.error('❌ Failed to initialize agent:', error);
|
|
122
|
+
console.error('Error details:', error);
|
|
123
|
+
// Try to get more specific error info
|
|
124
|
+
if (error instanceof Error) {
|
|
125
|
+
console.error('Error stack:', error.stack);
|
|
126
|
+
}
|
|
127
|
+
throw error;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
async reinitializeWithNewServers() {
|
|
131
|
+
try {
|
|
132
|
+
console.log('🔄 Reinitializing agent with new servers...');
|
|
133
|
+
// Get updated server configuration
|
|
134
|
+
const storedConfig = this.commandHandler.getCurrentStoredConfig();
|
|
135
|
+
const storedServers = storedConfig.mcpServers || {};
|
|
136
|
+
const sessionServers = this.commandHandler.getSessionServers();
|
|
137
|
+
console.log('Stored servers:', Object.keys(storedServers));
|
|
138
|
+
console.log('Session servers:', Object.keys(sessionServers));
|
|
139
|
+
// Default filesystem server
|
|
140
|
+
const defaultFilesystemServer = {
|
|
141
|
+
"command": "npx",
|
|
142
|
+
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"],
|
|
143
|
+
"env": {}
|
|
144
|
+
};
|
|
145
|
+
// Merge stored servers, session servers, with default (session servers override persistent ones)
|
|
146
|
+
const servers = {
|
|
147
|
+
"filesystem": defaultFilesystemServer,
|
|
148
|
+
...storedServers,
|
|
149
|
+
...sessionServers
|
|
150
|
+
};
|
|
151
|
+
console.log('Final server configuration:', Object.keys(servers));
|
|
152
|
+
const newConfig = {
|
|
153
|
+
"servers": servers
|
|
154
|
+
};
|
|
155
|
+
// Reinitialize MCP client with new configuration
|
|
156
|
+
console.log('Reinitializing MCP client with new config:', JSON.stringify(newConfig, null, 2));
|
|
157
|
+
this.client = MCPClient.fromDict(newConfig);
|
|
158
|
+
console.log('MCP client reinitialized successfully');
|
|
159
|
+
// If we have an agent configured, reinitialize it with the new client
|
|
160
|
+
if (this.commandHandler.isAnyProviderAvailable() && this.commandHandler.getCurrentConfig()) {
|
|
161
|
+
console.log('Reinitializing agent...');
|
|
162
|
+
await this.initializeAgent();
|
|
163
|
+
console.log('✅ Agent reinitialized successfully');
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
console.log('⚠️ No LLM configured, skipping agent initialization');
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
console.error('❌ Failed to reinitialize with new servers:', error);
|
|
171
|
+
if (error instanceof Error) {
|
|
172
|
+
console.error('Error stack:', error.stack);
|
|
173
|
+
}
|
|
174
|
+
// Don't throw here, just log - the old agent should still work
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
async sendMessage(message, isApiKeyInput, pendingProvider, pendingModel, isServerConfigInput, serverConfigStep, serverConfig) {
|
|
178
|
+
// Handle server configuration input
|
|
179
|
+
if (isServerConfigInput && serverConfigStep) {
|
|
180
|
+
const commandResult = this.commandHandler.handleServerConfigInput(message.trim(), serverConfigStep, serverConfig);
|
|
181
|
+
// If servers were added successfully, reinitialize the agent
|
|
182
|
+
if (commandResult.data?.serversAdded || commandResult.data?.serverAdded) {
|
|
183
|
+
await this.reinitializeWithNewServers();
|
|
184
|
+
}
|
|
185
|
+
return {
|
|
186
|
+
response: commandResult.message,
|
|
187
|
+
toolCalls: [],
|
|
188
|
+
isCommand: true,
|
|
189
|
+
commandResult
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
// Handle API key input
|
|
193
|
+
if (isApiKeyInput && pendingProvider && pendingModel) {
|
|
194
|
+
const commandResult = this.commandHandler.handleApiKeyInput(message.trim(), pendingProvider, pendingModel);
|
|
195
|
+
// If successful, reinitialize the agent
|
|
196
|
+
if (commandResult.data?.llmConfig) {
|
|
197
|
+
await this.initializeAgent();
|
|
198
|
+
}
|
|
199
|
+
return {
|
|
200
|
+
response: commandResult.message,
|
|
201
|
+
toolCalls: [],
|
|
202
|
+
isCommand: true,
|
|
203
|
+
commandResult
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
// Check if it's a slash command
|
|
207
|
+
if (this.commandHandler.isCommand(message)) {
|
|
208
|
+
try {
|
|
209
|
+
const commandResult = await this.commandHandler.handleCommand(message);
|
|
210
|
+
// Handle special commands that need MCP service interaction
|
|
211
|
+
if (commandResult.data?.checkTools) {
|
|
212
|
+
const toolsResult = await this.getAvailableTools();
|
|
213
|
+
let toolsMessage = '🔧 Available MCP Tools:\n\n';
|
|
214
|
+
if (toolsResult.error) {
|
|
215
|
+
toolsMessage += `❌ Error: ${toolsResult.error}\n\n`;
|
|
216
|
+
toolsMessage += '💡 This might indicate:\n';
|
|
217
|
+
toolsMessage += '• MCP servers failed to start\n';
|
|
218
|
+
toolsMessage += '• Agent fell back to default LLM tools\n';
|
|
219
|
+
toolsMessage += '• Connection issues with configured servers\n\n';
|
|
220
|
+
toolsMessage += 'Check console logs for more details.';
|
|
221
|
+
}
|
|
222
|
+
else if (toolsResult.tools.length === 0) {
|
|
223
|
+
toolsMessage += '❌ No MCP tools found\n\n';
|
|
224
|
+
toolsMessage += '💡 This suggests:\n';
|
|
225
|
+
toolsMessage += '• MCP servers failed to start or connect\n';
|
|
226
|
+
toolsMessage += '• Agent fell back to default LangChain tools\n';
|
|
227
|
+
toolsMessage += '• Server packages may not be installed\n\n';
|
|
228
|
+
toolsMessage += '🔍 Debug steps:\n';
|
|
229
|
+
toolsMessage += '1. Check console logs for errors\n';
|
|
230
|
+
toolsMessage += '2. Test server manually: /test-server <name>\n';
|
|
231
|
+
toolsMessage += '3. Ask agent "Which tools do you have?" to see fallback tools\n\n';
|
|
232
|
+
toolsMessage += '⚠️ If you see Wolfram/Wikipedia tools, MCP integration failed completely.';
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
toolsMessage += `✅ Found ${toolsResult.tools.length} MCP tools:\n\n`;
|
|
236
|
+
toolsResult.tools.forEach((tool, index) => {
|
|
237
|
+
toolsMessage += `${index + 1}. **${tool.name || 'Unknown'}**`;
|
|
238
|
+
if (tool.description) {
|
|
239
|
+
toolsMessage += `: ${tool.description}`;
|
|
240
|
+
}
|
|
241
|
+
toolsMessage += '\n';
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
return {
|
|
245
|
+
response: toolsMessage,
|
|
246
|
+
toolCalls: [],
|
|
247
|
+
isCommand: true,
|
|
248
|
+
commandResult: { type: 'info', message: toolsMessage }
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
// If the command changed the LLM config, reinitialize the agent
|
|
252
|
+
if (commandResult.data?.llmConfig) {
|
|
253
|
+
await this.initializeAgent();
|
|
254
|
+
}
|
|
255
|
+
// If servers were added, connected, or disconnected, reinitialize the agent
|
|
256
|
+
if (commandResult.data?.serversAdded || commandResult.data?.serverAdded ||
|
|
257
|
+
commandResult.data?.serverConnected || commandResult.data?.serverDisconnected) {
|
|
258
|
+
await this.reinitializeWithNewServers();
|
|
259
|
+
}
|
|
260
|
+
return {
|
|
261
|
+
response: commandResult.message,
|
|
262
|
+
toolCalls: [],
|
|
263
|
+
isCommand: true,
|
|
264
|
+
commandResult
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
catch (error) {
|
|
268
|
+
return {
|
|
269
|
+
response: `Command error: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
270
|
+
toolCalls: [],
|
|
271
|
+
isCommand: true,
|
|
272
|
+
commandResult: { type: 'error', message: 'Command failed' }
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
if (!this.agent) {
|
|
277
|
+
const availableProviders = this.commandHandler.getAvailableProviders();
|
|
278
|
+
if (availableProviders.length === 0) {
|
|
279
|
+
return {
|
|
280
|
+
response: `🤖 Choose a model to get started!\n\nTry one of these popular options:\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 help you set up the API key when needed.\nUse /models to see all available models.`,
|
|
281
|
+
toolCalls: [],
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
const firstProvider = availableProviders[0];
|
|
286
|
+
const exampleModel = firstProvider ? this.getExampleModel(firstProvider) : 'model-name';
|
|
287
|
+
return {
|
|
288
|
+
response: `🔧 No model selected.\n\nAvailable providers: ${availableProviders.join(', ')}\n\nUse /model <provider> <model> to get started.\n\nExample: /model ${firstProvider} ${exampleModel}`,
|
|
289
|
+
toolCalls: [],
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
try {
|
|
294
|
+
const result = await this.agent.run(message);
|
|
295
|
+
// Parse the result to extract tool calls
|
|
296
|
+
// Note: This is a simplified example - you may need to adjust based on actual mcp-use response format
|
|
297
|
+
const toolCalls = [];
|
|
298
|
+
// Extract tool calls from the result if available
|
|
299
|
+
// This would depend on how mcp-use exposes tool execution details
|
|
300
|
+
return {
|
|
301
|
+
response: result || 'No response received',
|
|
302
|
+
toolCalls,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
catch (error) {
|
|
306
|
+
console.error('Error sending message to MCP agent:', error);
|
|
307
|
+
throw error;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
getExampleModel(provider) {
|
|
311
|
+
const examples = {
|
|
312
|
+
openai: 'gpt-4o-mini',
|
|
313
|
+
anthropic: 'claude-3-5-sonnet-20241022',
|
|
314
|
+
google: 'gemini-1.5-pro',
|
|
315
|
+
mistral: 'mistral-large-latest'
|
|
316
|
+
};
|
|
317
|
+
return examples[provider] || 'model-name';
|
|
318
|
+
}
|
|
319
|
+
async *streamMessage(message) {
|
|
320
|
+
if (!this.agent) {
|
|
321
|
+
throw new Error('MCP service not initialized');
|
|
322
|
+
}
|
|
323
|
+
try {
|
|
324
|
+
// MCPAgent doesn't support streaming in the current version
|
|
325
|
+
// Fallback to non-streaming
|
|
326
|
+
const result = await this.sendMessage(message);
|
|
327
|
+
yield { content: result.response, done: false };
|
|
328
|
+
for (const toolCall of result.toolCalls) {
|
|
329
|
+
yield { toolCall, done: false };
|
|
330
|
+
}
|
|
331
|
+
yield { done: true };
|
|
332
|
+
}
|
|
333
|
+
catch (error) {
|
|
334
|
+
console.error('Error streaming message:', error);
|
|
335
|
+
yield { content: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`, done: true };
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
isReady() {
|
|
339
|
+
return this.isInitialized && this.agent !== null;
|
|
340
|
+
}
|
|
341
|
+
getCurrentModel() {
|
|
342
|
+
const config = this.commandHandler.getCurrentConfig();
|
|
343
|
+
if (!config) {
|
|
344
|
+
const availableProviders = this.commandHandler.getAvailableProviders();
|
|
345
|
+
if (availableProviders.length === 0) {
|
|
346
|
+
return 'No API keys configured';
|
|
347
|
+
}
|
|
348
|
+
return 'No model selected';
|
|
349
|
+
}
|
|
350
|
+
return `${config.provider}/${config.model}`;
|
|
351
|
+
}
|
|
352
|
+
getConfiguredServers() {
|
|
353
|
+
const storedConfig = this.commandHandler.getCurrentStoredConfig();
|
|
354
|
+
const storedServers = storedConfig.mcpServers || {};
|
|
355
|
+
const sessionServers = this.commandHandler.getSessionServers();
|
|
356
|
+
// Always include filesystem as it's the default
|
|
357
|
+
const servers = ['filesystem'];
|
|
358
|
+
// Add persistent servers (avoid duplicates)
|
|
359
|
+
const persistentCustomServers = Object.keys(storedServers).filter(name => name !== 'filesystem');
|
|
360
|
+
servers.push(...persistentCustomServers);
|
|
361
|
+
// Add session servers (avoid duplicates)
|
|
362
|
+
const sessionCustomServers = Object.keys(sessionServers).filter(name => name !== 'filesystem' && !persistentCustomServers.includes(name));
|
|
363
|
+
servers.push(...sessionCustomServers);
|
|
364
|
+
return servers;
|
|
365
|
+
}
|
|
366
|
+
getConnectedServers() {
|
|
367
|
+
const sessionServers = this.commandHandler.getSessionServers();
|
|
368
|
+
// Always include filesystem as it's built-in and always connected
|
|
369
|
+
const servers = ['filesystem'];
|
|
370
|
+
// Add only connected servers (those in sessionServers)
|
|
371
|
+
const connectedCustomServers = Object.keys(sessionServers);
|
|
372
|
+
servers.push(...connectedCustomServers);
|
|
373
|
+
return servers;
|
|
374
|
+
}
|
|
375
|
+
async getAvailableTools() {
|
|
376
|
+
if (!this.agent) {
|
|
377
|
+
return { tools: [], error: 'No agent initialized' };
|
|
378
|
+
}
|
|
379
|
+
try {
|
|
380
|
+
// Try to access the MCP client directly to get available tools
|
|
381
|
+
if (this.client) {
|
|
382
|
+
// Try to call listTools on the client
|
|
383
|
+
const clientAny = this.client;
|
|
384
|
+
if (typeof clientAny.listTools === 'function') {
|
|
385
|
+
const tools = await clientAny.listTools();
|
|
386
|
+
return { tools: tools || [] };
|
|
387
|
+
}
|
|
388
|
+
// Try to access servers and their tools
|
|
389
|
+
if (clientAny.servers) {
|
|
390
|
+
const allTools = [];
|
|
391
|
+
for (const [serverName, server] of Object.entries(clientAny.servers)) {
|
|
392
|
+
const serverAny = server;
|
|
393
|
+
if (serverAny.listTools && typeof serverAny.listTools === 'function') {
|
|
394
|
+
try {
|
|
395
|
+
const serverTools = await serverAny.listTools();
|
|
396
|
+
if (Array.isArray(serverTools)) {
|
|
397
|
+
allTools.push(...serverTools.map(tool => ({ ...tool, server: serverName })));
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
catch (e) {
|
|
401
|
+
console.log(`Failed to get tools from server ${serverName}:`, e);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
if (allTools.length > 0) {
|
|
406
|
+
return { tools: allTools };
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
// Try the agent directly (with type assertions to avoid TS errors)
|
|
411
|
+
const agentAny = this.agent;
|
|
412
|
+
if (typeof agentAny.listTools === 'function') {
|
|
413
|
+
const tools = await agentAny.listTools();
|
|
414
|
+
return { tools: tools || [] };
|
|
415
|
+
}
|
|
416
|
+
else if (typeof agentAny.getTools === 'function') {
|
|
417
|
+
const tools = await agentAny.getTools();
|
|
418
|
+
return { tools: tools || [] };
|
|
419
|
+
}
|
|
420
|
+
else if (agentAny.tools) {
|
|
421
|
+
return { tools: agentAny.tools || [] };
|
|
422
|
+
}
|
|
423
|
+
else {
|
|
424
|
+
return { tools: [], error: 'Agent and client do not expose tool listing functionality' };
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
catch (error) {
|
|
428
|
+
return {
|
|
429
|
+
tools: [],
|
|
430
|
+
error: `Failed to get tools: ${error instanceof Error ? error.message : 'Unknown error'}`
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
// Export a singleton instance
|
|
436
|
+
export const mcpService = new MCPService();
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface StoredConfig {
|
|
2
|
+
apiKeys: Record<string, string>;
|
|
3
|
+
lastModel?: {
|
|
4
|
+
provider: string;
|
|
5
|
+
model: string;
|
|
6
|
+
temperature?: number;
|
|
7
|
+
maxTokens?: number;
|
|
8
|
+
};
|
|
9
|
+
mcpServers?: Record<string, {
|
|
10
|
+
command: string;
|
|
11
|
+
args?: string[];
|
|
12
|
+
env?: Record<string, string>;
|
|
13
|
+
}>;
|
|
14
|
+
}
|
|
15
|
+
export declare class SecureStorage {
|
|
16
|
+
private static getKey;
|
|
17
|
+
private static encrypt;
|
|
18
|
+
private static decrypt;
|
|
19
|
+
static ensureConfigDir(): void;
|
|
20
|
+
static loadConfig(): StoredConfig;
|
|
21
|
+
static saveConfig(config: StoredConfig): void;
|
|
22
|
+
static clearConfig(): void;
|
|
23
|
+
static getConfigPath(): string;
|
|
24
|
+
}
|
package/dist/storage.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { scryptSync, randomBytes, createCipheriv, createDecipheriv } from 'node:crypto';
|
|
2
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
const CONFIG_DIR = join(homedir(), '.mcp-use-cli');
|
|
6
|
+
const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
|
|
7
|
+
const SALT = 'mcp-use-cli-salt'; // In production, this should be more secure
|
|
8
|
+
export class SecureStorage {
|
|
9
|
+
static getKey() {
|
|
10
|
+
return scryptSync('mcp-use-cli-encryption-key', SALT, 32);
|
|
11
|
+
}
|
|
12
|
+
static encrypt(text) {
|
|
13
|
+
try {
|
|
14
|
+
const iv = randomBytes(16);
|
|
15
|
+
const key = this.getKey();
|
|
16
|
+
const cipher = createCipheriv('aes-256-cbc', key, iv);
|
|
17
|
+
let encrypted = cipher.update(text, 'utf8', 'hex');
|
|
18
|
+
encrypted += cipher.final('hex');
|
|
19
|
+
return iv.toString('hex') + ':' + encrypted;
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
console.error('Encryption error:', error);
|
|
23
|
+
return text; // Fallback to plaintext if encryption fails
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
static decrypt(encryptedText) {
|
|
27
|
+
try {
|
|
28
|
+
const parts = encryptedText.split(':');
|
|
29
|
+
if (parts.length !== 2 || !parts[0] || !parts[1]) {
|
|
30
|
+
// Old format or plaintext, return as-is
|
|
31
|
+
return encryptedText;
|
|
32
|
+
}
|
|
33
|
+
const iv = Buffer.from(parts[0], 'hex');
|
|
34
|
+
const encryptedData = parts[1];
|
|
35
|
+
const key = this.getKey();
|
|
36
|
+
const decipher = createDecipheriv('aes-256-cbc', key, iv);
|
|
37
|
+
let decrypted = decipher.update(encryptedData, 'hex', 'utf8');
|
|
38
|
+
decrypted += decipher.final('utf8');
|
|
39
|
+
return decrypted;
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
console.error('Decryption error:', error);
|
|
43
|
+
return encryptedText; // Fallback to return as-is if decryption fails
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
static ensureConfigDir() {
|
|
47
|
+
if (!existsSync(CONFIG_DIR)) {
|
|
48
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
static loadConfig() {
|
|
52
|
+
this.ensureConfigDir();
|
|
53
|
+
if (!existsSync(CONFIG_FILE)) {
|
|
54
|
+
return { apiKeys: {} };
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
const configData = readFileSync(CONFIG_FILE, 'utf8');
|
|
58
|
+
const parsed = JSON.parse(configData);
|
|
59
|
+
// Decrypt API keys
|
|
60
|
+
const decryptedApiKeys = {};
|
|
61
|
+
for (const [key, encryptedValue] of Object.entries(parsed.apiKeys || {})) {
|
|
62
|
+
if (typeof encryptedValue === 'string') {
|
|
63
|
+
decryptedApiKeys[key] = this.decrypt(encryptedValue);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
...parsed,
|
|
68
|
+
apiKeys: decryptedApiKeys
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
console.error('Error loading config:', error);
|
|
73
|
+
return { apiKeys: {} };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
static saveConfig(config) {
|
|
77
|
+
this.ensureConfigDir();
|
|
78
|
+
try {
|
|
79
|
+
// Encrypt API keys before saving
|
|
80
|
+
const encryptedApiKeys = {};
|
|
81
|
+
for (const [key, value] of Object.entries(config.apiKeys)) {
|
|
82
|
+
encryptedApiKeys[key] = this.encrypt(value);
|
|
83
|
+
}
|
|
84
|
+
const configToSave = {
|
|
85
|
+
...config,
|
|
86
|
+
apiKeys: encryptedApiKeys
|
|
87
|
+
};
|
|
88
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(configToSave, null, 2), 'utf8');
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
console.error('Error saving config:', error);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
static clearConfig() {
|
|
95
|
+
this.ensureConfigDir();
|
|
96
|
+
try {
|
|
97
|
+
if (existsSync(CONFIG_FILE)) {
|
|
98
|
+
writeFileSync(CONFIG_FILE, JSON.stringify({ apiKeys: {} }, null, 2), 'utf8');
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
console.error('Error clearing config:', error);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
static getConfigPath() {
|
|
106
|
+
return CONFIG_FILE;
|
|
107
|
+
}
|
|
108
|
+
}
|