jiva-core 0.1.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/.env.example +18 -0
- package/.fluen/cache/state.json +7 -0
- package/README.md +350 -0
- package/actions/action_registry.py +75 -0
- package/actions/python_coder.py +470 -0
- package/api/main.py +269 -0
- package/dist/core/agent.d.ts +69 -0
- package/dist/core/agent.d.ts.map +1 -0
- package/dist/core/agent.js +214 -0
- package/dist/core/agent.js.map +1 -0
- package/dist/core/config.d.ts +222 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +138 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/workspace.d.ts +53 -0
- package/dist/core/workspace.d.ts.map +1 -0
- package/dist/core/workspace.js +164 -0
- package/dist/core/workspace.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/interfaces/cli/index.d.ts +6 -0
- package/dist/interfaces/cli/index.d.ts.map +1 -0
- package/dist/interfaces/cli/index.js +257 -0
- package/dist/interfaces/cli/index.js.map +1 -0
- package/dist/interfaces/cli/repl.d.ts +9 -0
- package/dist/interfaces/cli/repl.d.ts.map +1 -0
- package/dist/interfaces/cli/repl.js +139 -0
- package/dist/interfaces/cli/repl.js.map +1 -0
- package/dist/interfaces/cli/setup-wizard.d.ts +9 -0
- package/dist/interfaces/cli/setup-wizard.d.ts.map +1 -0
- package/dist/interfaces/cli/setup-wizard.js +321 -0
- package/dist/interfaces/cli/setup-wizard.js.map +1 -0
- package/dist/mcp/client.d.ts +58 -0
- package/dist/mcp/client.d.ts.map +1 -0
- package/dist/mcp/client.js +178 -0
- package/dist/mcp/client.js.map +1 -0
- package/dist/mcp/server-manager.d.ts +58 -0
- package/dist/mcp/server-manager.d.ts.map +1 -0
- package/dist/mcp/server-manager.js +135 -0
- package/dist/mcp/server-manager.js.map +1 -0
- package/dist/models/base.d.ts +57 -0
- package/dist/models/base.d.ts.map +1 -0
- package/dist/models/base.js +5 -0
- package/dist/models/base.js.map +1 -0
- package/dist/models/harmony.d.ts +78 -0
- package/dist/models/harmony.d.ts.map +1 -0
- package/dist/models/harmony.js +226 -0
- package/dist/models/harmony.js.map +1 -0
- package/dist/models/krutrim.d.ts +30 -0
- package/dist/models/krutrim.d.ts.map +1 -0
- package/dist/models/krutrim.js +185 -0
- package/dist/models/krutrim.js.map +1 -0
- package/dist/models/orchestrator.d.ts +49 -0
- package/dist/models/orchestrator.d.ts.map +1 -0
- package/dist/models/orchestrator.js +140 -0
- package/dist/models/orchestrator.js.map +1 -0
- package/dist/utils/errors.d.ts +23 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +45 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/logger.d.ts +24 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +74 -0
- package/dist/utils/logger.js.map +1 -0
- package/docs/BUILD.md +317 -0
- package/docs/DEV_WORKFLOW.md +197 -0
- package/docs/FILESYSTEM_ACCESS.md +244 -0
- package/docs/IMPLEMENTATION_SUMMARY.md +459 -0
- package/docs/QUICKSTART.md +162 -0
- package/docs/TROUBLESHOOTING.md +393 -0
- package/examples/code-review-directive.md +26 -0
- package/examples/data-analysis-directive.md +26 -0
- package/examples/programmatic-usage.ts +120 -0
- package/jiva-directive.md +24 -0
- package/package.json +46 -0
- package/setup.sh +65 -0
- package/src/core/agent.ts +275 -0
- package/src/core/config.ts +177 -0
- package/src/core/workspace.ts +205 -0
- package/src/index.ts +21 -0
- package/src/interfaces/cli/index.ts +290 -0
- package/src/interfaces/cli/repl.ts +182 -0
- package/src/interfaces/cli/setup-wizard.ts +355 -0
- package/src/mcp/client.ts +231 -0
- package/src/mcp/server-manager.ts +168 -0
- package/src/models/base.ts +63 -0
- package/src/models/harmony.ts +301 -0
- package/src/models/krutrim.ts +236 -0
- package/src/models/orchestrator.ts +180 -0
- package/src/utils/errors.ts +41 -0
- package/src/utils/logger.ts +87 -0
- package/tsconfig.json +22 -0
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Setup Wizard for first-time configuration
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import inquirer from 'inquirer';
|
|
6
|
+
import { configManager } from '../../core/config.js';
|
|
7
|
+
import { logger } from '../../utils/logger.js';
|
|
8
|
+
import chalk from 'chalk';
|
|
9
|
+
|
|
10
|
+
export async function runSetupWizard(): Promise<void> {
|
|
11
|
+
console.log(chalk.bold.cyan('\n🤖 Welcome to Jiva Setup Wizard\n'));
|
|
12
|
+
console.log('This wizard will help you configure Jiva for the first time.\n');
|
|
13
|
+
|
|
14
|
+
// Reasoning Model Configuration
|
|
15
|
+
console.log(chalk.bold('Reasoning Model Configuration (gpt-oss-120b)'));
|
|
16
|
+
console.log(chalk.gray('This model will be used for reasoning and tool calling.\n'));
|
|
17
|
+
|
|
18
|
+
const reasoningAnswers = await inquirer.prompt([
|
|
19
|
+
{
|
|
20
|
+
type: 'input',
|
|
21
|
+
name: 'endpoint',
|
|
22
|
+
message: 'API Endpoint URL:',
|
|
23
|
+
default: 'https://cloud.olakrutrim.com/v1/chat/completions',
|
|
24
|
+
validate: (input: string) => {
|
|
25
|
+
try {
|
|
26
|
+
new URL(input);
|
|
27
|
+
return true;
|
|
28
|
+
} catch {
|
|
29
|
+
return 'Please enter a valid URL';
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
type: 'password',
|
|
35
|
+
name: 'apiKey',
|
|
36
|
+
message: 'API Key:',
|
|
37
|
+
validate: (input: string) => input.length > 0 || 'API key is required',
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
type: 'input',
|
|
41
|
+
name: 'model',
|
|
42
|
+
message: 'Model name:',
|
|
43
|
+
default: 'gpt-oss-120b',
|
|
44
|
+
},
|
|
45
|
+
]);
|
|
46
|
+
|
|
47
|
+
configManager.setReasoningModel({
|
|
48
|
+
name: 'reasoning',
|
|
49
|
+
endpoint: reasoningAnswers.endpoint,
|
|
50
|
+
apiKey: reasoningAnswers.apiKey,
|
|
51
|
+
type: 'reasoning',
|
|
52
|
+
defaultModel: reasoningAnswers.model,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
logger.success('Reasoning model configured');
|
|
56
|
+
|
|
57
|
+
// Multimodal Model Configuration (Optional)
|
|
58
|
+
console.log(chalk.bold('\nMultimodal Model Configuration (Optional)'));
|
|
59
|
+
console.log(chalk.gray('This model will be used for understanding images.\n'));
|
|
60
|
+
|
|
61
|
+
const { configureMultimodal } = await inquirer.prompt([
|
|
62
|
+
{
|
|
63
|
+
type: 'confirm',
|
|
64
|
+
name: 'configureMultimodal',
|
|
65
|
+
message: 'Would you like to configure a multimodal model?',
|
|
66
|
+
default: true,
|
|
67
|
+
},
|
|
68
|
+
]);
|
|
69
|
+
|
|
70
|
+
if (configureMultimodal) {
|
|
71
|
+
const multimodalAnswers = await inquirer.prompt([
|
|
72
|
+
{
|
|
73
|
+
type: 'input',
|
|
74
|
+
name: 'endpoint',
|
|
75
|
+
message: 'Multimodal API Endpoint URL:',
|
|
76
|
+
default: 'https://cloud.olakrutrim.com/v1/chat/completions',
|
|
77
|
+
validate: (input: string) => {
|
|
78
|
+
try {
|
|
79
|
+
new URL(input);
|
|
80
|
+
return true;
|
|
81
|
+
} catch {
|
|
82
|
+
return 'Please enter a valid URL';
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
type: 'password',
|
|
88
|
+
name: 'apiKey',
|
|
89
|
+
message: 'Multimodal API Key:',
|
|
90
|
+
default: reasoningAnswers.apiKey,
|
|
91
|
+
validate: (input: string) => input.length > 0 || 'API key is required',
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
type: 'input',
|
|
95
|
+
name: 'model',
|
|
96
|
+
message: 'Multimodal model name:',
|
|
97
|
+
default: 'Llama-4-Maverick-17B-128E-Instruct',
|
|
98
|
+
},
|
|
99
|
+
]);
|
|
100
|
+
|
|
101
|
+
configManager.setMultimodalModel({
|
|
102
|
+
name: 'multimodal',
|
|
103
|
+
endpoint: multimodalAnswers.endpoint,
|
|
104
|
+
apiKey: multimodalAnswers.apiKey,
|
|
105
|
+
type: 'multimodal',
|
|
106
|
+
defaultModel: multimodalAnswers.model,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
logger.success('Multimodal model configured');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// MCP Servers Configuration
|
|
113
|
+
console.log(chalk.bold('\nMCP Servers Configuration'));
|
|
114
|
+
console.log(chalk.gray('Setting up default MCP servers (filesystem, commands)...\n'));
|
|
115
|
+
|
|
116
|
+
configManager.initializeDefaultServers();
|
|
117
|
+
logger.success('Default MCP servers configured');
|
|
118
|
+
|
|
119
|
+
// Debug Mode
|
|
120
|
+
const { enableDebug } = await inquirer.prompt([
|
|
121
|
+
{
|
|
122
|
+
type: 'confirm',
|
|
123
|
+
name: 'enableDebug',
|
|
124
|
+
message: 'Enable debug mode?',
|
|
125
|
+
default: false,
|
|
126
|
+
},
|
|
127
|
+
]);
|
|
128
|
+
|
|
129
|
+
configManager.setDebug(enableDebug);
|
|
130
|
+
|
|
131
|
+
console.log(chalk.bold.green('\n✓ Setup complete!\n'));
|
|
132
|
+
console.log(`Configuration saved to: ${chalk.cyan(configManager.getConfigPath())}`);
|
|
133
|
+
console.log('\nYou can now run:', chalk.cyan('jiva'));
|
|
134
|
+
console.log('');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Update existing configuration interactively
|
|
139
|
+
*/
|
|
140
|
+
export async function updateConfiguration(): Promise<void> {
|
|
141
|
+
console.log(chalk.bold.cyan('\n🔧 Update Jiva Configuration\n'));
|
|
142
|
+
|
|
143
|
+
const { choice } = await inquirer.prompt([
|
|
144
|
+
{
|
|
145
|
+
type: 'list',
|
|
146
|
+
name: 'choice',
|
|
147
|
+
message: 'What would you like to update?',
|
|
148
|
+
choices: [
|
|
149
|
+
{ name: 'Reasoning Model', value: 'reasoning' },
|
|
150
|
+
{ name: 'Multimodal Model', value: 'multimodal' },
|
|
151
|
+
{ name: 'MCP Servers', value: 'mcp' },
|
|
152
|
+
{ name: 'Debug Mode', value: 'debug' },
|
|
153
|
+
{ name: 'View Configuration', value: 'view' },
|
|
154
|
+
{ name: 'Reset All', value: 'reset' },
|
|
155
|
+
{ name: 'Cancel', value: 'cancel' },
|
|
156
|
+
],
|
|
157
|
+
},
|
|
158
|
+
]);
|
|
159
|
+
|
|
160
|
+
switch (choice) {
|
|
161
|
+
case 'reasoning':
|
|
162
|
+
await updateReasoningModel();
|
|
163
|
+
break;
|
|
164
|
+
case 'multimodal':
|
|
165
|
+
await updateMultimodalModel();
|
|
166
|
+
break;
|
|
167
|
+
case 'mcp':
|
|
168
|
+
await manageMCPServers();
|
|
169
|
+
break;
|
|
170
|
+
case 'debug':
|
|
171
|
+
await toggleDebugMode();
|
|
172
|
+
break;
|
|
173
|
+
case 'view':
|
|
174
|
+
viewConfiguration();
|
|
175
|
+
break;
|
|
176
|
+
case 'reset':
|
|
177
|
+
await resetConfiguration();
|
|
178
|
+
break;
|
|
179
|
+
case 'cancel':
|
|
180
|
+
console.log('Cancelled');
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async function updateReasoningModel() {
|
|
186
|
+
const current = configManager.getReasoningModel();
|
|
187
|
+
|
|
188
|
+
const answers = await inquirer.prompt([
|
|
189
|
+
{
|
|
190
|
+
type: 'input',
|
|
191
|
+
name: 'endpoint',
|
|
192
|
+
message: 'API Endpoint URL:',
|
|
193
|
+
default: current?.endpoint,
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
type: 'password',
|
|
197
|
+
name: 'apiKey',
|
|
198
|
+
message: 'API Key:',
|
|
199
|
+
default: current?.apiKey,
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
type: 'input',
|
|
203
|
+
name: 'model',
|
|
204
|
+
message: 'Model name:',
|
|
205
|
+
default: current?.defaultModel,
|
|
206
|
+
},
|
|
207
|
+
]);
|
|
208
|
+
|
|
209
|
+
configManager.setReasoningModel({
|
|
210
|
+
name: 'reasoning',
|
|
211
|
+
endpoint: answers.endpoint,
|
|
212
|
+
apiKey: answers.apiKey,
|
|
213
|
+
type: 'reasoning',
|
|
214
|
+
defaultModel: answers.model,
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
logger.success('Reasoning model updated');
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
async function updateMultimodalModel() {
|
|
221
|
+
const current = configManager.getMultimodalModel();
|
|
222
|
+
|
|
223
|
+
const answers = await inquirer.prompt([
|
|
224
|
+
{
|
|
225
|
+
type: 'input',
|
|
226
|
+
name: 'endpoint',
|
|
227
|
+
message: 'Multimodal API Endpoint URL:',
|
|
228
|
+
default: current?.endpoint || 'https://cloud.olakrutrim.com/v1/chat/completions',
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
type: 'password',
|
|
232
|
+
name: 'apiKey',
|
|
233
|
+
message: 'Multimodal API Key:',
|
|
234
|
+
default: current?.apiKey,
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
type: 'input',
|
|
238
|
+
name: 'model',
|
|
239
|
+
message: 'Multimodal model name:',
|
|
240
|
+
default: current?.defaultModel || 'Llama-4-Maverick-17B-128E-Instruct',
|
|
241
|
+
},
|
|
242
|
+
]);
|
|
243
|
+
|
|
244
|
+
configManager.setMultimodalModel({
|
|
245
|
+
name: 'multimodal',
|
|
246
|
+
endpoint: answers.endpoint,
|
|
247
|
+
apiKey: answers.apiKey,
|
|
248
|
+
type: 'multimodal',
|
|
249
|
+
defaultModel: answers.model,
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
logger.success('Multimodal model updated');
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
async function manageMCPServers() {
|
|
256
|
+
const servers = configManager.getMCPServers();
|
|
257
|
+
const serverNames = Object.keys(servers);
|
|
258
|
+
|
|
259
|
+
const { action } = await inquirer.prompt([
|
|
260
|
+
{
|
|
261
|
+
type: 'list',
|
|
262
|
+
name: 'action',
|
|
263
|
+
message: 'MCP Server Action:',
|
|
264
|
+
choices: [
|
|
265
|
+
{ name: 'List Servers', value: 'list' },
|
|
266
|
+
{ name: 'Add Server', value: 'add' },
|
|
267
|
+
{ name: 'Remove Server', value: 'remove' },
|
|
268
|
+
{ name: 'Back', value: 'back' },
|
|
269
|
+
],
|
|
270
|
+
},
|
|
271
|
+
]);
|
|
272
|
+
|
|
273
|
+
if (action === 'list') {
|
|
274
|
+
console.log('\nConfigured MCP Servers:');
|
|
275
|
+
Object.entries(servers).forEach(([name, config]) => {
|
|
276
|
+
console.log(` ${config.enabled ? '✓' : '✗'} ${name}: ${config.command} ${config.args?.join(' ') || ''}`);
|
|
277
|
+
});
|
|
278
|
+
} else if (action === 'add') {
|
|
279
|
+
const answers = await inquirer.prompt([
|
|
280
|
+
{
|
|
281
|
+
type: 'input',
|
|
282
|
+
name: 'name',
|
|
283
|
+
message: 'Server name:',
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
type: 'input',
|
|
287
|
+
name: 'command',
|
|
288
|
+
message: 'Command:',
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
type: 'input',
|
|
292
|
+
name: 'args',
|
|
293
|
+
message: 'Arguments (space-separated):',
|
|
294
|
+
},
|
|
295
|
+
]);
|
|
296
|
+
|
|
297
|
+
configManager.addMCPServer(answers.name, {
|
|
298
|
+
command: answers.command,
|
|
299
|
+
args: answers.args ? answers.args.split(' ') : [],
|
|
300
|
+
enabled: true,
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
logger.success(`MCP server '${answers.name}' added`);
|
|
304
|
+
} else if (action === 'remove' && serverNames.length > 0) {
|
|
305
|
+
const { serverName } = await inquirer.prompt([
|
|
306
|
+
{
|
|
307
|
+
type: 'list',
|
|
308
|
+
name: 'serverName',
|
|
309
|
+
message: 'Select server to remove:',
|
|
310
|
+
choices: serverNames,
|
|
311
|
+
},
|
|
312
|
+
]);
|
|
313
|
+
|
|
314
|
+
configManager.removeMCPServer(serverName);
|
|
315
|
+
logger.success(`MCP server '${serverName}' removed`);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
async function toggleDebugMode() {
|
|
320
|
+
const current = configManager.isDebug();
|
|
321
|
+
|
|
322
|
+
const { enabled } = await inquirer.prompt([
|
|
323
|
+
{
|
|
324
|
+
type: 'confirm',
|
|
325
|
+
name: 'enabled',
|
|
326
|
+
message: 'Enable debug mode?',
|
|
327
|
+
default: current,
|
|
328
|
+
},
|
|
329
|
+
]);
|
|
330
|
+
|
|
331
|
+
configManager.setDebug(enabled);
|
|
332
|
+
logger.success(`Debug mode ${enabled ? 'enabled' : 'disabled'}`);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function viewConfiguration() {
|
|
336
|
+
const config = configManager.getConfig();
|
|
337
|
+
console.log('\nCurrent Configuration:');
|
|
338
|
+
console.log(JSON.stringify(config, null, 2));
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
async function resetConfiguration() {
|
|
342
|
+
const { confirm } = await inquirer.prompt([
|
|
343
|
+
{
|
|
344
|
+
type: 'confirm',
|
|
345
|
+
name: 'confirm',
|
|
346
|
+
message: chalk.red('Are you sure you want to reset all configuration?'),
|
|
347
|
+
default: false,
|
|
348
|
+
},
|
|
349
|
+
]);
|
|
350
|
+
|
|
351
|
+
if (confirm) {
|
|
352
|
+
configManager.reset();
|
|
353
|
+
logger.success('Configuration reset');
|
|
354
|
+
}
|
|
355
|
+
}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Client Implementation
|
|
3
|
+
*
|
|
4
|
+
* Manages connections to MCP servers and provides tool discovery/execution.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
8
|
+
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
9
|
+
import {
|
|
10
|
+
CallToolResultSchema,
|
|
11
|
+
ListToolsResultSchema,
|
|
12
|
+
} from '@modelcontextprotocol/sdk/types.js';
|
|
13
|
+
import { MCPError } from '../utils/errors.js';
|
|
14
|
+
import { logger } from '../utils/logger.js';
|
|
15
|
+
import { Tool } from '../models/base.js';
|
|
16
|
+
|
|
17
|
+
export interface MCPServerConnection {
|
|
18
|
+
name: string;
|
|
19
|
+
client: Client;
|
|
20
|
+
transport: StdioClientTransport;
|
|
21
|
+
tools: Tool[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export class MCPClient {
|
|
25
|
+
private connections: Map<string, MCPServerConnection> = new Map();
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Connect to an MCP server
|
|
29
|
+
*/
|
|
30
|
+
async connect(
|
|
31
|
+
name: string,
|
|
32
|
+
command: string,
|
|
33
|
+
args: string[] = [],
|
|
34
|
+
env?: Record<string, string>
|
|
35
|
+
): Promise<void> {
|
|
36
|
+
try {
|
|
37
|
+
logger.info(`Connecting to MCP server: ${name}`);
|
|
38
|
+
logger.debug(`Command: ${command}`);
|
|
39
|
+
logger.debug(`Args: ${JSON.stringify(args)}`);
|
|
40
|
+
logger.debug(`Args length: ${args.length}, values: ${args.map((a, i) => `[${i}]="${a}"`).join(', ')}`);
|
|
41
|
+
|
|
42
|
+
const transport = new StdioClientTransport({
|
|
43
|
+
command,
|
|
44
|
+
args,
|
|
45
|
+
env: env || {},
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const client = new Client(
|
|
49
|
+
{
|
|
50
|
+
name: 'jiva-agent',
|
|
51
|
+
version: '0.1.0',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
capabilities: {},
|
|
55
|
+
}
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
await client.connect(transport);
|
|
59
|
+
|
|
60
|
+
// List available tools
|
|
61
|
+
const toolsResult = await client.listTools();
|
|
62
|
+
const tools = this.convertMCPTools(toolsResult.tools || []);
|
|
63
|
+
|
|
64
|
+
this.connections.set(name, {
|
|
65
|
+
name,
|
|
66
|
+
client,
|
|
67
|
+
transport,
|
|
68
|
+
tools,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
logger.success(`Connected to MCP server: ${name} (${tools.length} tools available)`);
|
|
72
|
+
} catch (error) {
|
|
73
|
+
throw new MCPError(
|
|
74
|
+
`Failed to connect to MCP server '${name}': ${error instanceof Error ? error.message : String(error)}`,
|
|
75
|
+
name
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Disconnect from an MCP server
|
|
82
|
+
*/
|
|
83
|
+
async disconnect(name: string): Promise<void> {
|
|
84
|
+
const connection = this.connections.get(name);
|
|
85
|
+
if (!connection) {
|
|
86
|
+
logger.warn(`MCP server '${name}' not connected`);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
await connection.client.close();
|
|
92
|
+
this.connections.delete(name);
|
|
93
|
+
logger.info(`Disconnected from MCP server: ${name}`);
|
|
94
|
+
} catch (error) {
|
|
95
|
+
logger.error(`Error disconnecting from MCP server '${name}'`, error);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Disconnect from all MCP servers
|
|
101
|
+
*/
|
|
102
|
+
async disconnectAll(): Promise<void> {
|
|
103
|
+
const names = Array.from(this.connections.keys());
|
|
104
|
+
await Promise.all(names.map(name => this.disconnect(name)));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Get all available tools from all connected servers
|
|
109
|
+
*/
|
|
110
|
+
getAllTools(): Tool[] {
|
|
111
|
+
const allTools: Tool[] = [];
|
|
112
|
+
|
|
113
|
+
for (const connection of this.connections.values()) {
|
|
114
|
+
// Prefix tool names with server name to avoid conflicts
|
|
115
|
+
const prefixedTools = connection.tools.map(tool => ({
|
|
116
|
+
...tool,
|
|
117
|
+
name: `${connection.name}__${tool.name}`,
|
|
118
|
+
description: `[${connection.name}] ${tool.description}`,
|
|
119
|
+
}));
|
|
120
|
+
allTools.push(...prefixedTools);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return allTools;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Get tools from a specific server
|
|
128
|
+
*/
|
|
129
|
+
getServerTools(serverName: string): Tool[] {
|
|
130
|
+
const connection = this.connections.get(serverName);
|
|
131
|
+
if (!connection) {
|
|
132
|
+
return [];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return connection.tools.map(tool => ({
|
|
136
|
+
...tool,
|
|
137
|
+
name: `${serverName}__${tool.name}`,
|
|
138
|
+
description: `[${serverName}] ${tool.description}`,
|
|
139
|
+
}));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Execute a tool call
|
|
144
|
+
*/
|
|
145
|
+
async executeTool(toolName: string, args: Record<string, any>): Promise<any> {
|
|
146
|
+
// Parse server name and actual tool name
|
|
147
|
+
const [serverName, ...toolParts] = toolName.split('__');
|
|
148
|
+
const actualToolName = toolParts.join('__');
|
|
149
|
+
|
|
150
|
+
const connection = this.connections.get(serverName);
|
|
151
|
+
if (!connection) {
|
|
152
|
+
throw new MCPError(`MCP server '${serverName}' not connected`, serverName);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
logger.debug(`Executing tool: ${toolName}`, args);
|
|
157
|
+
|
|
158
|
+
const result = await connection.client.callTool({
|
|
159
|
+
name: actualToolName,
|
|
160
|
+
arguments: args,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
logger.debug(`Tool result from ${toolName}:`, result);
|
|
164
|
+
|
|
165
|
+
// Extract content from MCP response
|
|
166
|
+
if (result.content && Array.isArray(result.content)) {
|
|
167
|
+
const textContent = result.content
|
|
168
|
+
.filter((c: any) => c.type === 'text')
|
|
169
|
+
.map((c: any) => c.text)
|
|
170
|
+
.join('\n');
|
|
171
|
+
return textContent || result;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return result;
|
|
175
|
+
} catch (error) {
|
|
176
|
+
throw new MCPError(
|
|
177
|
+
`Failed to execute tool '${toolName}': ${error instanceof Error ? error.message : String(error)}`,
|
|
178
|
+
serverName
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Convert MCP tool definitions to our internal Tool format
|
|
185
|
+
*/
|
|
186
|
+
private convertMCPTools(mcpTools: any[]): Tool[] {
|
|
187
|
+
return mcpTools.map(tool => ({
|
|
188
|
+
name: tool.name,
|
|
189
|
+
description: tool.description || tool.name,
|
|
190
|
+
parameters: tool.inputSchema || {
|
|
191
|
+
type: 'object',
|
|
192
|
+
properties: {},
|
|
193
|
+
},
|
|
194
|
+
}));
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Check if a server is connected
|
|
199
|
+
*/
|
|
200
|
+
isConnected(serverName: string): boolean {
|
|
201
|
+
return this.connections.has(serverName);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Get list of connected server names
|
|
206
|
+
*/
|
|
207
|
+
getConnectedServers(): string[] {
|
|
208
|
+
return Array.from(this.connections.keys());
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Refresh tools from a specific server
|
|
213
|
+
*/
|
|
214
|
+
async refreshTools(serverName: string): Promise<void> {
|
|
215
|
+
const connection = this.connections.get(serverName);
|
|
216
|
+
if (!connection) {
|
|
217
|
+
throw new MCPError(`MCP server '${serverName}' not connected`, serverName);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
try {
|
|
221
|
+
const toolsResult = await connection.client.listTools();
|
|
222
|
+
connection.tools = this.convertMCPTools(toolsResult.tools || []);
|
|
223
|
+
logger.info(`Refreshed tools for MCP server: ${serverName} (${connection.tools.length} tools)`);
|
|
224
|
+
} catch (error) {
|
|
225
|
+
throw new MCPError(
|
|
226
|
+
`Failed to refresh tools for '${serverName}': ${error instanceof Error ? error.message : String(error)}`,
|
|
227
|
+
serverName
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|