natureco-cli 2.9.2 โ 2.10.1
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/package.json +1 -1
- package/src/commands/dashboard.js +2 -2
- package/src/commands/gateway-server.js +1 -1
- package/src/utils/api.js +171 -11
- package/src/utils/mcp-client.js +311 -0
- package/src/utils/mcp.js +1 -0
package/package.json
CHANGED
|
@@ -211,7 +211,7 @@ body::before{
|
|
|
211
211
|
<div class="header-bot-name" id="header-bot-name">Nature Bot</div>
|
|
212
212
|
<div class="header-bot-model" id="header-bot-model">NatureCo</div>
|
|
213
213
|
</div>
|
|
214
|
-
<div class="version-badge" id="version-badge">v2.
|
|
214
|
+
<div class="version-badge" id="version-badge">v2.10.1</div>
|
|
215
215
|
</div>
|
|
216
216
|
<div class="messages" id="messages"></div>
|
|
217
217
|
<div class="input-area">
|
|
@@ -341,7 +341,7 @@ function dashboard(action) {
|
|
|
341
341
|
apiKey: cfg.apiKey,
|
|
342
342
|
defaultBot: cfg.defaultBot,
|
|
343
343
|
defaultBotId: cfg.defaultBotId,
|
|
344
|
-
version: 'v2.
|
|
344
|
+
version: 'v2.10.1',
|
|
345
345
|
bots: cfg.bots || [],
|
|
346
346
|
telegramToken: cfg.telegramToken || null,
|
|
347
347
|
whatsappConnected: cfg.whatsappConnected || false,
|
|
@@ -138,7 +138,7 @@ async function startGateway() {
|
|
|
138
138
|
|
|
139
139
|
async function runGatewayWorker() {
|
|
140
140
|
// This runs in the background
|
|
141
|
-
log('gateway', 'Starting NatureCo Gateway v2.
|
|
141
|
+
log('gateway', 'Starting NatureCo Gateway v2.10.1...', 'green');
|
|
142
142
|
|
|
143
143
|
// Load config
|
|
144
144
|
const { getConfig } = require('../utils/config');
|
package/src/utils/api.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// NatureCo CLI v2.
|
|
1
|
+
// NatureCo CLI v2.10.1 - Universal LLM Provider Support + MCP Integration
|
|
2
2
|
// Supports: OpenAI, Groq, Together, Fireworks, Perplexity, Mistral, DeepSeek, OpenRouter, Ollama, LM Studio, Anthropic
|
|
3
3
|
|
|
4
4
|
const fs = require('fs');
|
|
@@ -6,6 +6,7 @@ const os = require('os');
|
|
|
6
6
|
const path = require('path');
|
|
7
7
|
const { getConfig } = require('./config');
|
|
8
8
|
const { getToolDefinitions, executeToolCalls } = require('./tool-runner');
|
|
9
|
+
const { MCPClient } = require('./mcp-client');
|
|
9
10
|
|
|
10
11
|
// Persistent conversation directory
|
|
11
12
|
const CONV_DIR = path.join(os.homedir(), '.natureco', 'conversations');
|
|
@@ -13,6 +14,9 @@ const CONV_DIR = path.join(os.homedir(), '.natureco', 'conversations');
|
|
|
13
14
|
// Conversation history for multi-turn chat (deprecated - now using disk storage)
|
|
14
15
|
const conversationHistory = new Map();
|
|
15
16
|
|
|
17
|
+
// MCP clients (server name -> { client, tools })
|
|
18
|
+
const mcpClients = {};
|
|
19
|
+
|
|
16
20
|
/**
|
|
17
21
|
* Generate default conversation ID based on provider config
|
|
18
22
|
*/
|
|
@@ -60,6 +64,124 @@ function saveConversation(convId, messages) {
|
|
|
60
64
|
}
|
|
61
65
|
}
|
|
62
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Start MCP servers from config
|
|
69
|
+
*/
|
|
70
|
+
async function startMcpServers() {
|
|
71
|
+
const config = getConfig();
|
|
72
|
+
const servers = config.mcpServers || {};
|
|
73
|
+
|
|
74
|
+
for (const [name, server] of Object.entries(servers)) {
|
|
75
|
+
// Skip disabled servers
|
|
76
|
+
if (server.disabled) {
|
|
77
|
+
debugLog(`[MCP] Skipping disabled server: ${name}`);
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Skip if already started
|
|
82
|
+
if (mcpClients[name]) {
|
|
83
|
+
debugLog(`[MCP] Server already running: ${name}`);
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
debugLog(`[MCP] Starting server: ${name}`);
|
|
89
|
+
|
|
90
|
+
const client = new MCPClient(server.command, server.args, server.env || {});
|
|
91
|
+
await client.start();
|
|
92
|
+
|
|
93
|
+
const tools = await client.listTools();
|
|
94
|
+
debugLog(`[MCP] Server ${name} loaded ${tools.length} tools`);
|
|
95
|
+
|
|
96
|
+
mcpClients[name] = { client, tools };
|
|
97
|
+
|
|
98
|
+
} catch (err) {
|
|
99
|
+
debugLog(`[MCP] Failed to start server ${name}: ${err.message}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Stop all MCP servers
|
|
106
|
+
*/
|
|
107
|
+
function stopMcpServers() {
|
|
108
|
+
for (const [name, { client }] of Object.entries(mcpClients)) {
|
|
109
|
+
try {
|
|
110
|
+
debugLog(`[MCP] Stopping server: ${name}`);
|
|
111
|
+
client.stop();
|
|
112
|
+
} catch (err) {
|
|
113
|
+
debugLog(`[MCP] Failed to stop server ${name}: ${err.message}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Clear clients
|
|
118
|
+
Object.keys(mcpClients).forEach(key => delete mcpClients[key]);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Get all MCP tools (combined from all servers)
|
|
123
|
+
*/
|
|
124
|
+
function getMcpTools() {
|
|
125
|
+
const allTools = [];
|
|
126
|
+
|
|
127
|
+
for (const [serverName, { tools }] of Object.entries(mcpClients)) {
|
|
128
|
+
for (const tool of tools) {
|
|
129
|
+
allTools.push({
|
|
130
|
+
...tool,
|
|
131
|
+
_mcpServer: serverName, // Track which server this tool belongs to
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return allTools;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Execute MCP tool call
|
|
141
|
+
*/
|
|
142
|
+
async function executeMcpTool(toolName, toolArgs) {
|
|
143
|
+
// Find which server has this tool
|
|
144
|
+
for (const [serverName, { client, tools }] of Object.entries(mcpClients)) {
|
|
145
|
+
const tool = tools.find(t => t.name === toolName);
|
|
146
|
+
|
|
147
|
+
if (tool) {
|
|
148
|
+
debugLog(`[MCP] Calling tool ${toolName} on server ${serverName}`);
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
const result = await client.callTool(toolName, toolArgs);
|
|
152
|
+
|
|
153
|
+
// MCP returns { content: [{ type: 'text', text: '...' }] }
|
|
154
|
+
if (result.content && result.content.length > 0) {
|
|
155
|
+
const textContent = result.content.find(c => c.type === 'text');
|
|
156
|
+
if (textContent) {
|
|
157
|
+
return {
|
|
158
|
+
success: true,
|
|
159
|
+
output: textContent.text
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Fallback: return entire result
|
|
165
|
+
return {
|
|
166
|
+
success: true,
|
|
167
|
+
output: JSON.stringify(result, null, 2)
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
} catch (err) {
|
|
171
|
+
return {
|
|
172
|
+
success: false,
|
|
173
|
+
error: `MCP tool error: ${err.message}`
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
success: false,
|
|
181
|
+
error: `MCP tool not found: ${toolName}`
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
63
185
|
/**
|
|
64
186
|
* Check if debug mode is enabled
|
|
65
187
|
*/
|
|
@@ -110,14 +232,18 @@ function getProviderConfig() {
|
|
|
110
232
|
* Format tool definitions for OpenAI-compatible APIs
|
|
111
233
|
*/
|
|
112
234
|
function formatToolsForOpenAI() {
|
|
113
|
-
const
|
|
235
|
+
const localTools = getToolDefinitions();
|
|
236
|
+
const mcpTools = getMcpTools();
|
|
237
|
+
|
|
238
|
+
// Combine local and MCP tools
|
|
239
|
+
const allTools = [...localTools, ...mcpTools];
|
|
114
240
|
|
|
115
|
-
return
|
|
241
|
+
return allTools.map(tool => ({
|
|
116
242
|
type: 'function',
|
|
117
243
|
function: {
|
|
118
244
|
name: tool.name,
|
|
119
245
|
description: tool.description,
|
|
120
|
-
parameters: tool.inputSchema
|
|
246
|
+
parameters: tool.inputSchema || tool.input_schema || { type: 'object', properties: {} }
|
|
121
247
|
}
|
|
122
248
|
}));
|
|
123
249
|
}
|
|
@@ -126,12 +252,16 @@ function formatToolsForOpenAI() {
|
|
|
126
252
|
* Format tool definitions for Anthropic API
|
|
127
253
|
*/
|
|
128
254
|
function formatToolsForAnthropic() {
|
|
129
|
-
const
|
|
255
|
+
const localTools = getToolDefinitions();
|
|
256
|
+
const mcpTools = getMcpTools();
|
|
130
257
|
|
|
131
|
-
|
|
258
|
+
// Combine local and MCP tools
|
|
259
|
+
const allTools = [...localTools, ...mcpTools];
|
|
260
|
+
|
|
261
|
+
return allTools.map(tool => ({
|
|
132
262
|
name: tool.name,
|
|
133
263
|
description: tool.description,
|
|
134
|
-
input_schema: tool.inputSchema
|
|
264
|
+
input_schema: tool.inputSchema || tool.input_schema || { type: 'object', properties: {} }
|
|
135
265
|
}));
|
|
136
266
|
}
|
|
137
267
|
|
|
@@ -234,6 +364,11 @@ async function sendMessageToProvider(apiKey, message, conversationId = null, sys
|
|
|
234
364
|
);
|
|
235
365
|
}
|
|
236
366
|
|
|
367
|
+
// Start MCP servers if not already started
|
|
368
|
+
if (Object.keys(mcpClients).length === 0) {
|
|
369
|
+
await startMcpServers();
|
|
370
|
+
}
|
|
371
|
+
|
|
237
372
|
// Get or create conversation history (load from disk)
|
|
238
373
|
// Use consistent ID based on provider config instead of timestamp
|
|
239
374
|
const convId = conversationId || generateDefaultConvId();
|
|
@@ -247,7 +382,7 @@ async function sendMessageToProvider(apiKey, message, conversationId = null, sys
|
|
|
247
382
|
messages.push(...history);
|
|
248
383
|
messages.push({ role: 'user', content: message });
|
|
249
384
|
|
|
250
|
-
// Get tool definitions
|
|
385
|
+
// Get tool definitions (local + MCP)
|
|
251
386
|
const tools = providerConfig.isAnthropic
|
|
252
387
|
? formatToolsForAnthropic()
|
|
253
388
|
: formatToolsForOpenAI();
|
|
@@ -257,7 +392,7 @@ async function sendMessageToProvider(apiKey, message, conversationId = null, sys
|
|
|
257
392
|
debugLog('[Provider] Model:', providerConfig.model);
|
|
258
393
|
debugLog('[Provider] Type:', providerConfig.isAnthropic ? 'Anthropic' : 'OpenAI-compatible');
|
|
259
394
|
debugLog('[Provider] Messages:', messages.length);
|
|
260
|
-
debugLog('[Provider] Tools:', tools.length);
|
|
395
|
+
debugLog('[Provider] Tools:', tools.length, `(${Object.keys(mcpClients).length} MCP servers)`);
|
|
261
396
|
|
|
262
397
|
// Tool execution loop (max 10 iterations)
|
|
263
398
|
let iteration = 0;
|
|
@@ -282,14 +417,36 @@ async function sendMessageToProvider(apiKey, message, conversationId = null, sys
|
|
|
282
417
|
if (assistantMessage.tool_calls && assistantMessage.tool_calls.length > 0) {
|
|
283
418
|
debugLog(`[Provider] Tool calls: ${assistantMessage.tool_calls.length}`);
|
|
284
419
|
|
|
285
|
-
//
|
|
420
|
+
// Separate local and MCP tool calls
|
|
286
421
|
const toolCalls = assistantMessage.tool_calls.map(tc => ({
|
|
287
422
|
id: tc.id,
|
|
288
423
|
name: tc.function.name,
|
|
289
424
|
input: JSON.parse(tc.function.arguments)
|
|
290
425
|
}));
|
|
291
426
|
|
|
292
|
-
const toolResults =
|
|
427
|
+
const toolResults = [];
|
|
428
|
+
|
|
429
|
+
for (const toolCall of toolCalls) {
|
|
430
|
+
// Check if this is an MCP tool
|
|
431
|
+
const mcpTools = getMcpTools();
|
|
432
|
+
const isMcpTool = mcpTools.find(t => t.name === toolCall.name);
|
|
433
|
+
|
|
434
|
+
if (isMcpTool) {
|
|
435
|
+
// Execute MCP tool
|
|
436
|
+
debugLog(`[MCP] Executing tool: ${toolCall.name}`);
|
|
437
|
+
const result = await executeMcpTool(toolCall.name, toolCall.input);
|
|
438
|
+
toolResults.push({
|
|
439
|
+
id: toolCall.id,
|
|
440
|
+
name: toolCall.name,
|
|
441
|
+
result: result
|
|
442
|
+
});
|
|
443
|
+
} else {
|
|
444
|
+
// Execute local tool
|
|
445
|
+
debugLog(`[Local] Executing tool: ${toolCall.name}`);
|
|
446
|
+
const localResults = await executeToolCalls([toolCall]);
|
|
447
|
+
toolResults.push(...localResults);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
293
450
|
|
|
294
451
|
// Add tool results to messages (base64 encoded for safety)
|
|
295
452
|
for (const result of toolResults) {
|
|
@@ -452,5 +609,8 @@ module.exports = {
|
|
|
452
609
|
getBots,
|
|
453
610
|
clearConversation,
|
|
454
611
|
getProviderConfig,
|
|
612
|
+
startMcpServers,
|
|
613
|
+
stopMcpServers,
|
|
614
|
+
getMcpTools,
|
|
455
615
|
_sendMessage: sendMessage, // Alias for compatibility
|
|
456
616
|
};
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
const { spawn } = require('child_process');
|
|
2
|
+
const { EventEmitter } = require('events');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* MCP Client - JSON-RPC over stdio
|
|
6
|
+
* Manages communication with MCP servers via stdin/stdout
|
|
7
|
+
*/
|
|
8
|
+
class MCPClient extends EventEmitter {
|
|
9
|
+
constructor(command, args = [], env = {}) {
|
|
10
|
+
super();
|
|
11
|
+
this.command = command;
|
|
12
|
+
this.args = args;
|
|
13
|
+
this.env = { ...process.env, ...env };
|
|
14
|
+
this.process = null;
|
|
15
|
+
this.messageId = 0;
|
|
16
|
+
this.pendingRequests = new Map();
|
|
17
|
+
this.buffer = '';
|
|
18
|
+
this.isInitialized = false;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Start MCP server process
|
|
23
|
+
*/
|
|
24
|
+
async start() {
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
try {
|
|
27
|
+
this.process = spawn(this.command, this.args, {
|
|
28
|
+
env: this.env,
|
|
29
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Handle stdout (JSON-RPC responses)
|
|
33
|
+
this.process.stdout.on('data', (data) => {
|
|
34
|
+
this.handleData(data);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Handle stderr (logs)
|
|
38
|
+
this.process.stderr.on('data', (data) => {
|
|
39
|
+
const message = data.toString().trim();
|
|
40
|
+
if (message) {
|
|
41
|
+
this.emit('log', message);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Handle process exit
|
|
46
|
+
this.process.on('exit', (code) => {
|
|
47
|
+
this.emit('exit', code);
|
|
48
|
+
this.cleanup();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Handle process error
|
|
52
|
+
this.process.on('error', (err) => {
|
|
53
|
+
reject(err);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Initialize connection
|
|
57
|
+
this.initialize()
|
|
58
|
+
.then(() => {
|
|
59
|
+
this.isInitialized = true;
|
|
60
|
+
resolve();
|
|
61
|
+
})
|
|
62
|
+
.catch(reject);
|
|
63
|
+
|
|
64
|
+
} catch (err) {
|
|
65
|
+
reject(err);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Handle incoming data from stdout
|
|
72
|
+
*/
|
|
73
|
+
handleData(data) {
|
|
74
|
+
this.buffer += data.toString();
|
|
75
|
+
|
|
76
|
+
// Process complete JSON-RPC messages (newline-delimited)
|
|
77
|
+
const lines = this.buffer.split('\n');
|
|
78
|
+
this.buffer = lines.pop() || ''; // Keep incomplete line in buffer
|
|
79
|
+
|
|
80
|
+
for (const line of lines) {
|
|
81
|
+
if (!line.trim()) continue;
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
const message = JSON.parse(line);
|
|
85
|
+
this.handleMessage(message);
|
|
86
|
+
} catch (err) {
|
|
87
|
+
this.emit('error', new Error(`Failed to parse JSON-RPC message: ${err.message}`));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Handle parsed JSON-RPC message
|
|
94
|
+
*/
|
|
95
|
+
handleMessage(message) {
|
|
96
|
+
// Response to our request
|
|
97
|
+
if (message.id !== undefined && this.pendingRequests.has(message.id)) {
|
|
98
|
+
const { resolve, reject } = this.pendingRequests.get(message.id);
|
|
99
|
+
this.pendingRequests.delete(message.id);
|
|
100
|
+
|
|
101
|
+
if (message.error) {
|
|
102
|
+
reject(new Error(message.error.message || 'MCP request failed'));
|
|
103
|
+
} else {
|
|
104
|
+
resolve(message.result);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// Notification from server
|
|
108
|
+
else if (message.method) {
|
|
109
|
+
this.emit('notification', message);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Send JSON-RPC request
|
|
115
|
+
*/
|
|
116
|
+
async sendRequest(method, params = {}) {
|
|
117
|
+
if (!this.process || this.process.killed) {
|
|
118
|
+
throw new Error('MCP server not running');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const id = ++this.messageId;
|
|
122
|
+
const request = {
|
|
123
|
+
jsonrpc: '2.0',
|
|
124
|
+
id,
|
|
125
|
+
method,
|
|
126
|
+
params,
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
return new Promise((resolve, reject) => {
|
|
130
|
+
this.pendingRequests.set(id, { resolve, reject });
|
|
131
|
+
|
|
132
|
+
// Timeout after 30 seconds
|
|
133
|
+
const timeout = setTimeout(() => {
|
|
134
|
+
if (this.pendingRequests.has(id)) {
|
|
135
|
+
this.pendingRequests.delete(id);
|
|
136
|
+
reject(new Error(`Request timeout: ${method}`));
|
|
137
|
+
}
|
|
138
|
+
}, 30000);
|
|
139
|
+
|
|
140
|
+
// Clear timeout on response
|
|
141
|
+
const originalResolve = resolve;
|
|
142
|
+
const originalReject = reject;
|
|
143
|
+
|
|
144
|
+
this.pendingRequests.set(id, {
|
|
145
|
+
resolve: (result) => {
|
|
146
|
+
clearTimeout(timeout);
|
|
147
|
+
originalResolve(result);
|
|
148
|
+
},
|
|
149
|
+
reject: (err) => {
|
|
150
|
+
clearTimeout(timeout);
|
|
151
|
+
originalReject(err);
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// Send request
|
|
156
|
+
const message = JSON.stringify(request) + '\n';
|
|
157
|
+
this.process.stdin.write(message);
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Initialize MCP connection
|
|
163
|
+
*/
|
|
164
|
+
async initialize() {
|
|
165
|
+
const result = await this.sendRequest('initialize', {
|
|
166
|
+
protocolVersion: '2024-11-05',
|
|
167
|
+
capabilities: {
|
|
168
|
+
roots: {
|
|
169
|
+
listChanged: false
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
clientInfo: {
|
|
173
|
+
name: 'natureco-cli',
|
|
174
|
+
version: '2.10.0',
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// Send initialized notification
|
|
179
|
+
const notification = {
|
|
180
|
+
jsonrpc: '2.0',
|
|
181
|
+
method: 'notifications/initialized',
|
|
182
|
+
};
|
|
183
|
+
this.process.stdin.write(JSON.stringify(notification) + '\n');
|
|
184
|
+
|
|
185
|
+
return result;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* List available tools
|
|
190
|
+
*/
|
|
191
|
+
async listTools() {
|
|
192
|
+
if (!this.isInitialized) {
|
|
193
|
+
throw new Error('MCP client not initialized');
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const result = await this.sendRequest('tools/list', {});
|
|
197
|
+
return result.tools || [];
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Call a tool
|
|
202
|
+
*/
|
|
203
|
+
async callTool(toolName, args = {}) {
|
|
204
|
+
if (!this.isInitialized) {
|
|
205
|
+
throw new Error('MCP client not initialized');
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const result = await this.sendRequest('tools/call', {
|
|
209
|
+
name: toolName,
|
|
210
|
+
arguments: args,
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
return result;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Stop MCP server
|
|
218
|
+
*/
|
|
219
|
+
stop() {
|
|
220
|
+
if (this.process && !this.process.killed) {
|
|
221
|
+
this.process.kill();
|
|
222
|
+
}
|
|
223
|
+
this.cleanup();
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Cleanup resources
|
|
228
|
+
*/
|
|
229
|
+
cleanup() {
|
|
230
|
+
this.pendingRequests.clear();
|
|
231
|
+
this.buffer = '';
|
|
232
|
+
this.isInitialized = false;
|
|
233
|
+
this.process = null;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Create and start MCP client
|
|
239
|
+
*/
|
|
240
|
+
async function createMCPClient(command, args = [], env = {}) {
|
|
241
|
+
const client = new MCPClient(command, args, env);
|
|
242
|
+
await client.start();
|
|
243
|
+
return client;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Test function - Start filesystem MCP server and list tools
|
|
248
|
+
*/
|
|
249
|
+
async function test() {
|
|
250
|
+
console.log('๐งช Testing MCP Client...\n');
|
|
251
|
+
|
|
252
|
+
try {
|
|
253
|
+
// Start filesystem MCP server
|
|
254
|
+
console.log('Starting filesystem MCP server...');
|
|
255
|
+
const client = await createMCPClient('npx', [
|
|
256
|
+
'-y',
|
|
257
|
+
'@modelcontextprotocol/server-filesystem',
|
|
258
|
+
process.cwd(),
|
|
259
|
+
]);
|
|
260
|
+
|
|
261
|
+
console.log('โ
MCP server started\n');
|
|
262
|
+
|
|
263
|
+
// Listen to logs
|
|
264
|
+
client.on('log', (message) => {
|
|
265
|
+
console.log('[MCP Log]', message);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
// List tools
|
|
269
|
+
console.log('Listing tools...');
|
|
270
|
+
const tools = await client.listTools();
|
|
271
|
+
console.log(`โ
Found ${tools.length} tools:\n`);
|
|
272
|
+
|
|
273
|
+
tools.forEach((tool) => {
|
|
274
|
+
console.log(` - ${tool.name}`);
|
|
275
|
+
console.log(` ${tool.description}`);
|
|
276
|
+
console.log('');
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
// Test tool call - read directory
|
|
280
|
+
if (tools.find(t => t.name === 'read_file')) {
|
|
281
|
+
console.log('Testing read_file tool...');
|
|
282
|
+
try {
|
|
283
|
+
const result = await client.callTool('read_file', {
|
|
284
|
+
path: 'package.json',
|
|
285
|
+
});
|
|
286
|
+
console.log('โ
Tool call successful');
|
|
287
|
+
console.log('Result:', JSON.stringify(result, null, 2).substring(0, 200) + '...\n');
|
|
288
|
+
} catch (err) {
|
|
289
|
+
console.log('โ Tool call failed:', err.message, '\n');
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Stop server
|
|
294
|
+
console.log('Stopping MCP server...');
|
|
295
|
+
client.stop();
|
|
296
|
+
console.log('โ
Test completed\n');
|
|
297
|
+
|
|
298
|
+
process.exit(0);
|
|
299
|
+
|
|
300
|
+
} catch (err) {
|
|
301
|
+
console.error('โ Test failed:', err.message);
|
|
302
|
+
console.error(err.stack);
|
|
303
|
+
process.exit(1);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
module.exports = {
|
|
308
|
+
MCPClient,
|
|
309
|
+
createMCPClient,
|
|
310
|
+
test,
|
|
311
|
+
};
|