musubi-sdd 3.10.0 → 5.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/README.md +24 -19
- package/package.json +1 -1
- package/src/agents/agent-loop.js +532 -0
- package/src/agents/agentic/code-generator.js +767 -0
- package/src/agents/agentic/code-reviewer.js +698 -0
- package/src/agents/agentic/index.js +43 -0
- package/src/agents/function-tool.js +432 -0
- package/src/agents/index.js +45 -0
- package/src/agents/schema-generator.js +514 -0
- package/src/analyzers/ast-extractor.js +870 -0
- package/src/analyzers/context-optimizer.js +681 -0
- package/src/analyzers/repository-map.js +692 -0
- package/src/integrations/index.js +7 -1
- package/src/integrations/mcp/index.js +175 -0
- package/src/integrations/mcp/mcp-context-provider.js +472 -0
- package/src/integrations/mcp/mcp-discovery.js +436 -0
- package/src/integrations/mcp/mcp-tool-registry.js +467 -0
- package/src/integrations/mcp-connector.js +818 -0
- package/src/integrations/tool-discovery.js +589 -0
- package/src/managers/index.js +7 -0
- package/src/managers/skill-tools.js +565 -0
- package/src/monitoring/cost-tracker.js +7 -0
- package/src/monitoring/incident-manager.js +10 -0
- package/src/monitoring/observability.js +10 -0
- package/src/monitoring/quality-dashboard.js +491 -0
- package/src/monitoring/release-manager.js +10 -0
- package/src/orchestration/agent-skill-binding.js +655 -0
- package/src/orchestration/error-handler.js +827 -0
- package/src/orchestration/index.js +235 -1
- package/src/orchestration/mcp-tool-adapters.js +896 -0
- package/src/orchestration/reasoning/index.js +58 -0
- package/src/orchestration/reasoning/planning-engine.js +831 -0
- package/src/orchestration/reasoning/reasoning-engine.js +710 -0
- package/src/orchestration/reasoning/self-correction.js +751 -0
- package/src/orchestration/skill-executor.js +665 -0
- package/src/orchestration/skill-registry.js +650 -0
- package/src/orchestration/workflow-examples.js +1072 -0
- package/src/orchestration/workflow-executor.js +779 -0
- package/src/phase4-integration.js +248 -0
- package/src/phase5-integration.js +402 -0
- package/src/steering/steering-auto-update.js +572 -0
- package/src/steering/steering-validator.js +547 -0
- package/src/templates/template-constraints.js +646 -0
- package/src/validators/advanced-validation.js +580 -0
|
@@ -0,0 +1,818 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview MCP (Model Context Protocol) Connector
|
|
3
|
+
* @description Base MCP client integration for tool ecosystem connectivity
|
|
4
|
+
* @version 3.11.0
|
|
5
|
+
*
|
|
6
|
+
* Supports:
|
|
7
|
+
* - Standard MCP server connections (stdio, SSE, HTTP)
|
|
8
|
+
* - Tool discovery and invocation
|
|
9
|
+
* - Resource management
|
|
10
|
+
* - Prompt handling
|
|
11
|
+
* - Connection pooling and retry logic
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
'use strict';
|
|
15
|
+
|
|
16
|
+
const { EventEmitter } = require('events');
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* MCP Connection States
|
|
20
|
+
*/
|
|
21
|
+
const ConnectionState = {
|
|
22
|
+
DISCONNECTED: 'disconnected',
|
|
23
|
+
CONNECTING: 'connecting',
|
|
24
|
+
CONNECTED: 'connected',
|
|
25
|
+
RECONNECTING: 'reconnecting',
|
|
26
|
+
ERROR: 'error'
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* MCP Transport Types
|
|
31
|
+
*/
|
|
32
|
+
const TransportType = {
|
|
33
|
+
STDIO: 'stdio',
|
|
34
|
+
SSE: 'sse',
|
|
35
|
+
HTTP: 'http',
|
|
36
|
+
WEBSOCKET: 'websocket'
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Default MCP Connector Configuration
|
|
41
|
+
*/
|
|
42
|
+
const DEFAULT_CONFIG = {
|
|
43
|
+
timeout: 30000,
|
|
44
|
+
retryAttempts: 3,
|
|
45
|
+
retryDelay: 1000,
|
|
46
|
+
keepAlive: true,
|
|
47
|
+
keepAliveInterval: 30000,
|
|
48
|
+
maxConcurrentRequests: 10,
|
|
49
|
+
enableLogging: false
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* MCP Tool Definition
|
|
54
|
+
*/
|
|
55
|
+
class MCPTool {
|
|
56
|
+
constructor(definition) {
|
|
57
|
+
this.name = definition.name;
|
|
58
|
+
this.description = definition.description || '';
|
|
59
|
+
this.inputSchema = definition.inputSchema || {};
|
|
60
|
+
this.annotations = definition.annotations || {};
|
|
61
|
+
this.server = definition.server || null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Validate input against schema
|
|
66
|
+
* @param {Object} input - Input to validate
|
|
67
|
+
* @returns {Object} Validation result
|
|
68
|
+
*/
|
|
69
|
+
validateInput(input) {
|
|
70
|
+
const errors = [];
|
|
71
|
+
const schema = this.inputSchema;
|
|
72
|
+
|
|
73
|
+
if (schema.required) {
|
|
74
|
+
for (const field of schema.required) {
|
|
75
|
+
if (!(field in input)) {
|
|
76
|
+
errors.push(`Missing required field: ${field}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (schema.properties) {
|
|
82
|
+
for (const [key, prop] of Object.entries(schema.properties)) {
|
|
83
|
+
if (key in input) {
|
|
84
|
+
const value = input[key];
|
|
85
|
+
if (prop.type && typeof value !== prop.type) {
|
|
86
|
+
// Allow number/integer mismatch
|
|
87
|
+
if (!(prop.type === 'integer' && typeof value === 'number')) {
|
|
88
|
+
errors.push(`Invalid type for ${key}: expected ${prop.type}, got ${typeof value}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
valid: errors.length === 0,
|
|
97
|
+
errors
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
toJSON() {
|
|
102
|
+
return {
|
|
103
|
+
name: this.name,
|
|
104
|
+
description: this.description,
|
|
105
|
+
inputSchema: this.inputSchema,
|
|
106
|
+
annotations: this.annotations
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* MCP Resource Definition
|
|
113
|
+
*/
|
|
114
|
+
class MCPResource {
|
|
115
|
+
constructor(definition) {
|
|
116
|
+
this.uri = definition.uri;
|
|
117
|
+
this.name = definition.name;
|
|
118
|
+
this.description = definition.description || '';
|
|
119
|
+
this.mimeType = definition.mimeType || 'text/plain';
|
|
120
|
+
this.annotations = definition.annotations || {};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
toJSON() {
|
|
124
|
+
return {
|
|
125
|
+
uri: this.uri,
|
|
126
|
+
name: this.name,
|
|
127
|
+
description: this.description,
|
|
128
|
+
mimeType: this.mimeType,
|
|
129
|
+
annotations: this.annotations
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* MCP Prompt Definition
|
|
136
|
+
*/
|
|
137
|
+
class MCPPrompt {
|
|
138
|
+
constructor(definition) {
|
|
139
|
+
this.name = definition.name;
|
|
140
|
+
this.description = definition.description || '';
|
|
141
|
+
this.arguments = definition.arguments || [];
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
toJSON() {
|
|
145
|
+
return {
|
|
146
|
+
name: this.name,
|
|
147
|
+
description: this.description,
|
|
148
|
+
arguments: this.arguments
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* MCP Server Connection
|
|
155
|
+
*/
|
|
156
|
+
class MCPServerConnection extends EventEmitter {
|
|
157
|
+
constructor(serverConfig, options = {}) {
|
|
158
|
+
super();
|
|
159
|
+
this.config = serverConfig;
|
|
160
|
+
this.options = { ...DEFAULT_CONFIG, ...options };
|
|
161
|
+
this.state = ConnectionState.DISCONNECTED;
|
|
162
|
+
this.transport = null;
|
|
163
|
+
this.tools = new Map();
|
|
164
|
+
this.resources = new Map();
|
|
165
|
+
this.prompts = new Map();
|
|
166
|
+
this.requestId = 0;
|
|
167
|
+
this.pendingRequests = new Map();
|
|
168
|
+
this.serverInfo = null;
|
|
169
|
+
this.capabilities = {};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Get server name
|
|
174
|
+
*/
|
|
175
|
+
get name() {
|
|
176
|
+
return this.config.name || 'unnamed-server';
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Get transport type
|
|
181
|
+
*/
|
|
182
|
+
get transportType() {
|
|
183
|
+
return this.config.transport || TransportType.STDIO;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Connect to MCP server
|
|
188
|
+
* @returns {Promise<void>}
|
|
189
|
+
*/
|
|
190
|
+
async connect() {
|
|
191
|
+
if (this.state === ConnectionState.CONNECTED) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
this.state = ConnectionState.CONNECTING;
|
|
196
|
+
this.emit('connecting');
|
|
197
|
+
|
|
198
|
+
try {
|
|
199
|
+
await this._initializeTransport();
|
|
200
|
+
await this._initialize();
|
|
201
|
+
await this._discoverCapabilities();
|
|
202
|
+
|
|
203
|
+
this.state = ConnectionState.CONNECTED;
|
|
204
|
+
this.emit('connected', this.serverInfo);
|
|
205
|
+
|
|
206
|
+
if (this.options.keepAlive) {
|
|
207
|
+
this._startKeepAlive();
|
|
208
|
+
}
|
|
209
|
+
} catch (error) {
|
|
210
|
+
this.state = ConnectionState.ERROR;
|
|
211
|
+
this.emit('error', error);
|
|
212
|
+
throw error;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Disconnect from MCP server
|
|
218
|
+
* @returns {Promise<void>}
|
|
219
|
+
*/
|
|
220
|
+
async disconnect() {
|
|
221
|
+
if (this.state === ConnectionState.DISCONNECTED) {
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
this._stopKeepAlive();
|
|
226
|
+
|
|
227
|
+
try {
|
|
228
|
+
await this._closeTransport();
|
|
229
|
+
} finally {
|
|
230
|
+
this.state = ConnectionState.DISCONNECTED;
|
|
231
|
+
this.tools.clear();
|
|
232
|
+
this.resources.clear();
|
|
233
|
+
this.prompts.clear();
|
|
234
|
+
this.emit('disconnected');
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Initialize transport layer
|
|
240
|
+
* @private
|
|
241
|
+
*/
|
|
242
|
+
async _initializeTransport() {
|
|
243
|
+
// Transport initialization based on type
|
|
244
|
+
// In production, this would spawn processes or establish connections
|
|
245
|
+
this.transport = {
|
|
246
|
+
type: this.transportType,
|
|
247
|
+
connected: true,
|
|
248
|
+
send: async (message) => {
|
|
249
|
+
if (this.options.enableLogging) {
|
|
250
|
+
console.log(`[MCP ${this.name}] Sending:`, JSON.stringify(message));
|
|
251
|
+
}
|
|
252
|
+
return this._handleMessage(message);
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Close transport layer
|
|
259
|
+
* @private
|
|
260
|
+
*/
|
|
261
|
+
async _closeTransport() {
|
|
262
|
+
if (this.transport) {
|
|
263
|
+
this.transport.connected = false;
|
|
264
|
+
this.transport = null;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Initialize MCP session
|
|
270
|
+
* @private
|
|
271
|
+
*/
|
|
272
|
+
async _initialize() {
|
|
273
|
+
const response = await this._sendRequest('initialize', {
|
|
274
|
+
protocolVersion: '2024-11-05',
|
|
275
|
+
capabilities: {
|
|
276
|
+
roots: { listChanged: true },
|
|
277
|
+
sampling: {}
|
|
278
|
+
},
|
|
279
|
+
clientInfo: {
|
|
280
|
+
name: 'musubi-sdd',
|
|
281
|
+
version: '3.11.0'
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
this.serverInfo = response.serverInfo || {};
|
|
286
|
+
this.capabilities = response.capabilities || {};
|
|
287
|
+
|
|
288
|
+
// Send initialized notification
|
|
289
|
+
await this._sendNotification('notifications/initialized', {});
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Discover server capabilities (tools, resources, prompts)
|
|
294
|
+
* @private
|
|
295
|
+
*/
|
|
296
|
+
async _discoverCapabilities() {
|
|
297
|
+
// Discover tools
|
|
298
|
+
if (this.capabilities.tools) {
|
|
299
|
+
const toolsResponse = await this._sendRequest('tools/list', {});
|
|
300
|
+
for (const tool of toolsResponse.tools || []) {
|
|
301
|
+
this.tools.set(tool.name, new MCPTool({ ...tool, server: this.name }));
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Discover resources
|
|
306
|
+
if (this.capabilities.resources) {
|
|
307
|
+
const resourcesResponse = await this._sendRequest('resources/list', {});
|
|
308
|
+
for (const resource of resourcesResponse.resources || []) {
|
|
309
|
+
this.resources.set(resource.uri, new MCPResource(resource));
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Discover prompts
|
|
314
|
+
if (this.capabilities.prompts) {
|
|
315
|
+
const promptsResponse = await this._sendRequest('prompts/list', {});
|
|
316
|
+
for (const prompt of promptsResponse.prompts || []) {
|
|
317
|
+
this.prompts.set(prompt.name, new MCPPrompt(prompt));
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Send JSON-RPC request
|
|
324
|
+
* @private
|
|
325
|
+
*/
|
|
326
|
+
async _sendRequest(method, params) {
|
|
327
|
+
const id = ++this.requestId;
|
|
328
|
+
const message = {
|
|
329
|
+
jsonrpc: '2.0',
|
|
330
|
+
id,
|
|
331
|
+
method,
|
|
332
|
+
params
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
return new Promise((resolve, reject) => {
|
|
336
|
+
const timeout = setTimeout(() => {
|
|
337
|
+
this.pendingRequests.delete(id);
|
|
338
|
+
reject(new Error(`Request timeout: ${method}`));
|
|
339
|
+
}, this.options.timeout);
|
|
340
|
+
|
|
341
|
+
this.pendingRequests.set(id, { resolve, reject, timeout });
|
|
342
|
+
|
|
343
|
+
if (this.transport) {
|
|
344
|
+
this.transport.send(message).then(response => {
|
|
345
|
+
clearTimeout(timeout);
|
|
346
|
+
this.pendingRequests.delete(id);
|
|
347
|
+
if (response.error) {
|
|
348
|
+
reject(new Error(response.error.message || 'Unknown error'));
|
|
349
|
+
} else {
|
|
350
|
+
resolve(response.result || {});
|
|
351
|
+
}
|
|
352
|
+
}).catch(error => {
|
|
353
|
+
clearTimeout(timeout);
|
|
354
|
+
this.pendingRequests.delete(id);
|
|
355
|
+
reject(error);
|
|
356
|
+
});
|
|
357
|
+
} else {
|
|
358
|
+
clearTimeout(timeout);
|
|
359
|
+
this.pendingRequests.delete(id);
|
|
360
|
+
reject(new Error('Transport not connected'));
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Send JSON-RPC notification
|
|
367
|
+
* @private
|
|
368
|
+
*/
|
|
369
|
+
async _sendNotification(method, params) {
|
|
370
|
+
const message = {
|
|
371
|
+
jsonrpc: '2.0',
|
|
372
|
+
method,
|
|
373
|
+
params
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
if (this.transport) {
|
|
377
|
+
await this.transport.send(message);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Handle incoming message (mock for testing)
|
|
383
|
+
* @private
|
|
384
|
+
*/
|
|
385
|
+
async _handleMessage(message) {
|
|
386
|
+
// Mock responses for testing
|
|
387
|
+
const method = message.method;
|
|
388
|
+
|
|
389
|
+
if (method === 'initialize') {
|
|
390
|
+
return {
|
|
391
|
+
result: {
|
|
392
|
+
protocolVersion: '2024-11-05',
|
|
393
|
+
capabilities: {
|
|
394
|
+
tools: { listChanged: true },
|
|
395
|
+
resources: { subscribe: true, listChanged: true },
|
|
396
|
+
prompts: { listChanged: true }
|
|
397
|
+
},
|
|
398
|
+
serverInfo: {
|
|
399
|
+
name: this.config.name || 'mock-server',
|
|
400
|
+
version: '1.0.0'
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
if (method === 'tools/list') {
|
|
407
|
+
return {
|
|
408
|
+
result: {
|
|
409
|
+
tools: this.config.mockTools || []
|
|
410
|
+
}
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
if (method === 'resources/list') {
|
|
415
|
+
return {
|
|
416
|
+
result: {
|
|
417
|
+
resources: this.config.mockResources || []
|
|
418
|
+
}
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if (method === 'prompts/list') {
|
|
423
|
+
return {
|
|
424
|
+
result: {
|
|
425
|
+
prompts: this.config.mockPrompts || []
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
if (method === 'tools/call') {
|
|
431
|
+
return {
|
|
432
|
+
result: {
|
|
433
|
+
content: [
|
|
434
|
+
{
|
|
435
|
+
type: 'text',
|
|
436
|
+
text: `Tool ${message.params.name} executed successfully`
|
|
437
|
+
}
|
|
438
|
+
]
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
if (method === 'resources/read') {
|
|
444
|
+
return {
|
|
445
|
+
result: {
|
|
446
|
+
contents: [
|
|
447
|
+
{
|
|
448
|
+
uri: message.params.uri,
|
|
449
|
+
mimeType: 'text/plain',
|
|
450
|
+
text: `Content of ${message.params.uri}`
|
|
451
|
+
}
|
|
452
|
+
]
|
|
453
|
+
}
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
if (method === 'prompts/get') {
|
|
458
|
+
return {
|
|
459
|
+
result: {
|
|
460
|
+
messages: [
|
|
461
|
+
{
|
|
462
|
+
role: 'user',
|
|
463
|
+
content: {
|
|
464
|
+
type: 'text',
|
|
465
|
+
text: `Prompt: ${message.params.name}`
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
]
|
|
469
|
+
}
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Notifications don't require response
|
|
474
|
+
if (method.startsWith('notifications/')) {
|
|
475
|
+
return { result: {} };
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
return { result: {} };
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Start keep-alive timer
|
|
483
|
+
* @private
|
|
484
|
+
*/
|
|
485
|
+
_startKeepAlive() {
|
|
486
|
+
this._keepAliveTimer = setInterval(async () => {
|
|
487
|
+
try {
|
|
488
|
+
await this._sendRequest('ping', {});
|
|
489
|
+
} catch (error) {
|
|
490
|
+
this.emit('keepAliveError', error);
|
|
491
|
+
}
|
|
492
|
+
}, this.options.keepAliveInterval);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Stop keep-alive timer
|
|
497
|
+
* @private
|
|
498
|
+
*/
|
|
499
|
+
_stopKeepAlive() {
|
|
500
|
+
if (this._keepAliveTimer) {
|
|
501
|
+
clearInterval(this._keepAliveTimer);
|
|
502
|
+
this._keepAliveTimer = null;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* Call a tool
|
|
508
|
+
* @param {string} toolName - Tool name
|
|
509
|
+
* @param {Object} args - Tool arguments
|
|
510
|
+
* @returns {Promise<Object>} Tool result
|
|
511
|
+
*/
|
|
512
|
+
async callTool(toolName, args = {}) {
|
|
513
|
+
const tool = this.tools.get(toolName);
|
|
514
|
+
if (!tool) {
|
|
515
|
+
throw new Error(`Tool not found: ${toolName}`);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
const validation = tool.validateInput(args);
|
|
519
|
+
if (!validation.valid) {
|
|
520
|
+
throw new Error(`Invalid tool input: ${validation.errors.join(', ')}`);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
const response = await this._sendRequest('tools/call', {
|
|
524
|
+
name: toolName,
|
|
525
|
+
arguments: args
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
return response;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* Read a resource
|
|
533
|
+
* @param {string} uri - Resource URI
|
|
534
|
+
* @returns {Promise<Object>} Resource content
|
|
535
|
+
*/
|
|
536
|
+
async readResource(uri) {
|
|
537
|
+
const response = await this._sendRequest('resources/read', { uri });
|
|
538
|
+
return response;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* Get a prompt
|
|
543
|
+
* @param {string} promptName - Prompt name
|
|
544
|
+
* @param {Object} args - Prompt arguments
|
|
545
|
+
* @returns {Promise<Object>} Prompt messages
|
|
546
|
+
*/
|
|
547
|
+
async getPrompt(promptName, args = {}) {
|
|
548
|
+
const response = await this._sendRequest('prompts/get', {
|
|
549
|
+
name: promptName,
|
|
550
|
+
arguments: args
|
|
551
|
+
});
|
|
552
|
+
return response;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* Get connection status
|
|
557
|
+
* @returns {Object} Status info
|
|
558
|
+
*/
|
|
559
|
+
getStatus() {
|
|
560
|
+
return {
|
|
561
|
+
name: this.name,
|
|
562
|
+
state: this.state,
|
|
563
|
+
transportType: this.transportType,
|
|
564
|
+
toolCount: this.tools.size,
|
|
565
|
+
resourceCount: this.resources.size,
|
|
566
|
+
promptCount: this.prompts.size,
|
|
567
|
+
serverInfo: this.serverInfo,
|
|
568
|
+
capabilities: this.capabilities
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* MCP Connector - Main class for managing multiple MCP server connections
|
|
575
|
+
*/
|
|
576
|
+
class MCPConnector extends EventEmitter {
|
|
577
|
+
constructor(options = {}) {
|
|
578
|
+
super();
|
|
579
|
+
this.options = { ...DEFAULT_CONFIG, ...options };
|
|
580
|
+
this.servers = new Map();
|
|
581
|
+
this.toolIndex = new Map(); // tool name -> server name mapping
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* Add an MCP server configuration
|
|
586
|
+
* @param {string} name - Server name
|
|
587
|
+
* @param {Object} config - Server configuration
|
|
588
|
+
* @returns {MCPServerConnection} Server connection
|
|
589
|
+
*/
|
|
590
|
+
addServer(name, config) {
|
|
591
|
+
if (this.servers.has(name)) {
|
|
592
|
+
throw new Error(`Server already exists: ${name}`);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
const serverConfig = { name, ...config };
|
|
596
|
+
const connection = new MCPServerConnection(serverConfig, this.options);
|
|
597
|
+
|
|
598
|
+
// Forward events
|
|
599
|
+
connection.on('connected', (info) => this.emit('serverConnected', name, info));
|
|
600
|
+
connection.on('disconnected', () => this.emit('serverDisconnected', name));
|
|
601
|
+
connection.on('error', (error) => this.emit('serverError', name, error));
|
|
602
|
+
|
|
603
|
+
this.servers.set(name, connection);
|
|
604
|
+
return connection;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/**
|
|
608
|
+
* Remove an MCP server
|
|
609
|
+
* @param {string} name - Server name
|
|
610
|
+
* @returns {Promise<void>}
|
|
611
|
+
*/
|
|
612
|
+
async removeServer(name) {
|
|
613
|
+
const connection = this.servers.get(name);
|
|
614
|
+
if (connection) {
|
|
615
|
+
await connection.disconnect();
|
|
616
|
+
this.servers.delete(name);
|
|
617
|
+
this._rebuildToolIndex();
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
/**
|
|
622
|
+
* Connect to a specific server
|
|
623
|
+
* @param {string} name - Server name
|
|
624
|
+
* @returns {Promise<void>}
|
|
625
|
+
*/
|
|
626
|
+
async connectServer(name) {
|
|
627
|
+
const connection = this.servers.get(name);
|
|
628
|
+
if (!connection) {
|
|
629
|
+
throw new Error(`Server not found: ${name}`);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
await connection.connect();
|
|
633
|
+
this._rebuildToolIndex();
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
/**
|
|
637
|
+
* Connect to all servers
|
|
638
|
+
* @returns {Promise<Object>} Connection results
|
|
639
|
+
*/
|
|
640
|
+
async connectAll() {
|
|
641
|
+
const results = {
|
|
642
|
+
success: [],
|
|
643
|
+
failed: []
|
|
644
|
+
};
|
|
645
|
+
|
|
646
|
+
for (const [name, connection] of this.servers) {
|
|
647
|
+
try {
|
|
648
|
+
await connection.connect();
|
|
649
|
+
results.success.push(name);
|
|
650
|
+
} catch (error) {
|
|
651
|
+
results.failed.push({ name, error: error.message });
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
this._rebuildToolIndex();
|
|
656
|
+
return results;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
* Disconnect from all servers
|
|
661
|
+
* @returns {Promise<void>}
|
|
662
|
+
*/
|
|
663
|
+
async disconnectAll() {
|
|
664
|
+
for (const connection of this.servers.values()) {
|
|
665
|
+
await connection.disconnect();
|
|
666
|
+
}
|
|
667
|
+
this.toolIndex.clear();
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/**
|
|
671
|
+
* Rebuild tool index across all servers
|
|
672
|
+
* @private
|
|
673
|
+
*/
|
|
674
|
+
_rebuildToolIndex() {
|
|
675
|
+
this.toolIndex.clear();
|
|
676
|
+
for (const [serverName, connection] of this.servers) {
|
|
677
|
+
if (connection.state === ConnectionState.CONNECTED) {
|
|
678
|
+
for (const toolName of connection.tools.keys()) {
|
|
679
|
+
this.toolIndex.set(toolName, serverName);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
/**
|
|
686
|
+
* Get all available tools across all servers
|
|
687
|
+
* @returns {Array<MCPTool>} All tools
|
|
688
|
+
*/
|
|
689
|
+
getAllTools() {
|
|
690
|
+
const tools = [];
|
|
691
|
+
for (const connection of this.servers.values()) {
|
|
692
|
+
if (connection.state === ConnectionState.CONNECTED) {
|
|
693
|
+
tools.push(...connection.tools.values());
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
return tools;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
/**
|
|
700
|
+
* Get all available resources across all servers
|
|
701
|
+
* @returns {Array<MCPResource>} All resources
|
|
702
|
+
*/
|
|
703
|
+
getAllResources() {
|
|
704
|
+
const resources = [];
|
|
705
|
+
for (const connection of this.servers.values()) {
|
|
706
|
+
if (connection.state === ConnectionState.CONNECTED) {
|
|
707
|
+
resources.push(...connection.resources.values());
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
return resources;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
/**
|
|
714
|
+
* Get all available prompts across all servers
|
|
715
|
+
* @returns {Array<MCPPrompt>} All prompts
|
|
716
|
+
*/
|
|
717
|
+
getAllPrompts() {
|
|
718
|
+
const prompts = [];
|
|
719
|
+
for (const connection of this.servers.values()) {
|
|
720
|
+
if (connection.state === ConnectionState.CONNECTED) {
|
|
721
|
+
prompts.push(...connection.prompts.values());
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
return prompts;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
/**
|
|
728
|
+
* Find tool by name
|
|
729
|
+
* @param {string} toolName - Tool name
|
|
730
|
+
* @returns {Object|null} Tool info with server
|
|
731
|
+
*/
|
|
732
|
+
findTool(toolName) {
|
|
733
|
+
const serverName = this.toolIndex.get(toolName);
|
|
734
|
+
if (!serverName) return null;
|
|
735
|
+
|
|
736
|
+
const connection = this.servers.get(serverName);
|
|
737
|
+
const tool = connection?.tools.get(toolName);
|
|
738
|
+
|
|
739
|
+
return tool ? { tool, server: serverName } : null;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
/**
|
|
743
|
+
* Call a tool by name (auto-routes to correct server)
|
|
744
|
+
* @param {string} toolName - Tool name
|
|
745
|
+
* @param {Object} args - Tool arguments
|
|
746
|
+
* @returns {Promise<Object>} Tool result
|
|
747
|
+
*/
|
|
748
|
+
async callTool(toolName, args = {}) {
|
|
749
|
+
const serverName = this.toolIndex.get(toolName);
|
|
750
|
+
if (!serverName) {
|
|
751
|
+
throw new Error(`Tool not found: ${toolName}`);
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
const connection = this.servers.get(serverName);
|
|
755
|
+
return connection.callTool(toolName, args);
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
/**
|
|
759
|
+
* Get connection status for all servers
|
|
760
|
+
* @returns {Object} Status info
|
|
761
|
+
*/
|
|
762
|
+
getStatus() {
|
|
763
|
+
const servers = {};
|
|
764
|
+
for (const [name, connection] of this.servers) {
|
|
765
|
+
servers[name] = connection.getStatus();
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
return {
|
|
769
|
+
serverCount: this.servers.size,
|
|
770
|
+
connectedCount: Array.from(this.servers.values())
|
|
771
|
+
.filter(c => c.state === ConnectionState.CONNECTED).length,
|
|
772
|
+
totalTools: this.toolIndex.size,
|
|
773
|
+
servers
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
/**
|
|
778
|
+
* Load servers from configuration object
|
|
779
|
+
* @param {Object} config - Configuration with servers array
|
|
780
|
+
* @returns {MCPConnector} This connector
|
|
781
|
+
*/
|
|
782
|
+
loadConfig(config) {
|
|
783
|
+
const servers = config.mcpServers || config.servers || {};
|
|
784
|
+
|
|
785
|
+
for (const [name, serverConfig] of Object.entries(servers)) {
|
|
786
|
+
this.addServer(name, serverConfig);
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
return this;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
/**
|
|
793
|
+
* Export current configuration
|
|
794
|
+
* @returns {Object} Configuration object
|
|
795
|
+
*/
|
|
796
|
+
exportConfig() {
|
|
797
|
+
const servers = {};
|
|
798
|
+
for (const [name, connection] of this.servers) {
|
|
799
|
+
servers[name] = {
|
|
800
|
+
transport: connection.transportType,
|
|
801
|
+
...connection.config
|
|
802
|
+
};
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
return { mcpServers: servers };
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
module.exports = {
|
|
810
|
+
MCPConnector,
|
|
811
|
+
MCPServerConnection,
|
|
812
|
+
MCPTool,
|
|
813
|
+
MCPResource,
|
|
814
|
+
MCPPrompt,
|
|
815
|
+
ConnectionState,
|
|
816
|
+
TransportType,
|
|
817
|
+
DEFAULT_CONFIG
|
|
818
|
+
};
|