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,467 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MUSUBI MCP Tool Registry
|
|
3
|
+
*
|
|
4
|
+
* Manages MCP tools from discovered servers and provides
|
|
5
|
+
* automatic registration with the Agent Loop and Skill System.
|
|
6
|
+
*
|
|
7
|
+
* @module integrations/mcp/mcp-tool-registry
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
'use strict';
|
|
11
|
+
|
|
12
|
+
const EventEmitter = require('events');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {Object} MCPToolDefinition
|
|
16
|
+
* @property {string} name - Tool name
|
|
17
|
+
* @property {string} description - Tool description
|
|
18
|
+
* @property {Object} inputSchema - JSON Schema for input
|
|
19
|
+
* @property {string} serverName - Source MCP server
|
|
20
|
+
* @property {Object} [annotations] - Tool annotations
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @typedef {Object} ToolInvocationResult
|
|
25
|
+
* @property {boolean} success - Whether invocation succeeded
|
|
26
|
+
* @property {*} result - Tool result
|
|
27
|
+
* @property {Error} [error] - Error if failed
|
|
28
|
+
* @property {number} duration - Execution time in ms
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* MCP Tool Registry for managing tools from MCP servers
|
|
33
|
+
*/
|
|
34
|
+
class MCPToolRegistry extends EventEmitter {
|
|
35
|
+
/**
|
|
36
|
+
* @param {Object} options
|
|
37
|
+
* @param {Object} [options.connector] - MCP connector instance
|
|
38
|
+
* @param {Object} [options.skillRegistry] - MUSUBI skill registry
|
|
39
|
+
* @param {boolean} [options.autoRegister=true] - Auto-register with skills
|
|
40
|
+
*/
|
|
41
|
+
constructor(options = {}) {
|
|
42
|
+
super();
|
|
43
|
+
|
|
44
|
+
this.connector = options.connector || null;
|
|
45
|
+
this.skillRegistry = options.skillRegistry || null;
|
|
46
|
+
this.autoRegister = options.autoRegister ?? true;
|
|
47
|
+
|
|
48
|
+
/** @type {Map<string, MCPToolDefinition>} */
|
|
49
|
+
this.tools = new Map();
|
|
50
|
+
|
|
51
|
+
/** @type {Map<string, string[]>} */
|
|
52
|
+
this.serverTools = new Map();
|
|
53
|
+
|
|
54
|
+
/** @type {Map<string, number>} */
|
|
55
|
+
this.invocationCounts = new Map();
|
|
56
|
+
|
|
57
|
+
/** @type {Map<string, number[]>} */
|
|
58
|
+
this.latencyHistory = new Map();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Set the MCP connector
|
|
63
|
+
* @param {Object} connector
|
|
64
|
+
*/
|
|
65
|
+
setConnector(connector) {
|
|
66
|
+
this.connector = connector;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Set the skill registry for auto-registration
|
|
71
|
+
* @param {Object} skillRegistry
|
|
72
|
+
*/
|
|
73
|
+
setSkillRegistry(skillRegistry) {
|
|
74
|
+
this.skillRegistry = skillRegistry;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Discover and register tools from an MCP server
|
|
79
|
+
* @param {string} serverName - Server name
|
|
80
|
+
* @param {Object} serverConfig - Server configuration
|
|
81
|
+
* @returns {Promise<MCPToolDefinition[]>}
|
|
82
|
+
*/
|
|
83
|
+
async discoverTools(serverName, serverConfig) {
|
|
84
|
+
if (!this.connector) {
|
|
85
|
+
throw new Error('MCP connector not set');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
// Connect to server
|
|
90
|
+
await this.connector.connect(serverName, serverConfig);
|
|
91
|
+
|
|
92
|
+
// List tools
|
|
93
|
+
const toolsResponse = await this.connector.listTools(serverName);
|
|
94
|
+
const tools = toolsResponse.tools || [];
|
|
95
|
+
|
|
96
|
+
const registered = [];
|
|
97
|
+
|
|
98
|
+
for (const tool of tools) {
|
|
99
|
+
const toolDef = {
|
|
100
|
+
name: `${serverName}/${tool.name}`,
|
|
101
|
+
originalName: tool.name,
|
|
102
|
+
description: tool.description || '',
|
|
103
|
+
inputSchema: tool.inputSchema || { type: 'object', properties: {} },
|
|
104
|
+
serverName,
|
|
105
|
+
annotations: tool.annotations || {}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
this.registerTool(toolDef);
|
|
109
|
+
registered.push(toolDef);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Track tools per server
|
|
113
|
+
this.serverTools.set(serverName, registered.map(t => t.name));
|
|
114
|
+
|
|
115
|
+
this.emit('tools:discovered', {
|
|
116
|
+
serverName,
|
|
117
|
+
count: registered.length,
|
|
118
|
+
tools: registered.map(t => t.name)
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
return registered;
|
|
122
|
+
|
|
123
|
+
} catch (error) {
|
|
124
|
+
this.emit('tools:discovery:error', { serverName, error });
|
|
125
|
+
throw error;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Register a tool
|
|
131
|
+
* @param {MCPToolDefinition} toolDef
|
|
132
|
+
*/
|
|
133
|
+
registerTool(toolDef) {
|
|
134
|
+
this.tools.set(toolDef.name, toolDef);
|
|
135
|
+
this.invocationCounts.set(toolDef.name, 0);
|
|
136
|
+
this.latencyHistory.set(toolDef.name, []);
|
|
137
|
+
|
|
138
|
+
// Auto-register with skill registry
|
|
139
|
+
if (this.autoRegister && this.skillRegistry) {
|
|
140
|
+
this.registerAsSkill(toolDef);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
this.emit('tool:registered', { name: toolDef.name });
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Register tool as a MUSUBI skill
|
|
148
|
+
* @param {MCPToolDefinition} toolDef
|
|
149
|
+
*/
|
|
150
|
+
registerAsSkill(toolDef) {
|
|
151
|
+
if (!this.skillRegistry) return;
|
|
152
|
+
|
|
153
|
+
const skill = {
|
|
154
|
+
id: `mcp-${toolDef.name.replace('/', '-')}`,
|
|
155
|
+
name: toolDef.name,
|
|
156
|
+
category: 'mcp-tool',
|
|
157
|
+
description: toolDef.description,
|
|
158
|
+
metadata: {
|
|
159
|
+
serverName: toolDef.serverName,
|
|
160
|
+
originalName: toolDef.originalName,
|
|
161
|
+
inputSchema: toolDef.inputSchema,
|
|
162
|
+
source: 'mcp'
|
|
163
|
+
},
|
|
164
|
+
handler: async (input) => {
|
|
165
|
+
return this.invokeTool(toolDef.name, input);
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
this.skillRegistry.registerSkill(skill);
|
|
171
|
+
this.emit('skill:registered', { skillId: skill.id, toolName: toolDef.name });
|
|
172
|
+
} catch (error) {
|
|
173
|
+
this.emit('skill:registration:error', { toolName: toolDef.name, error });
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Invoke a tool
|
|
179
|
+
* @param {string} toolName - Full tool name (serverName/toolName)
|
|
180
|
+
* @param {Object} input - Tool input
|
|
181
|
+
* @returns {Promise<ToolInvocationResult>}
|
|
182
|
+
*/
|
|
183
|
+
async invokeTool(toolName, input) {
|
|
184
|
+
const tool = this.tools.get(toolName);
|
|
185
|
+
if (!tool) {
|
|
186
|
+
throw new Error(`Tool not found: ${toolName}`);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (!this.connector) {
|
|
190
|
+
throw new Error('MCP connector not set');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const startTime = Date.now();
|
|
194
|
+
|
|
195
|
+
try {
|
|
196
|
+
// Validate input
|
|
197
|
+
const validation = this.validateInput(tool, input);
|
|
198
|
+
if (!validation.valid) {
|
|
199
|
+
throw new Error(`Invalid input: ${validation.errors.join(', ')}`);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Call tool via connector
|
|
203
|
+
const result = await this.connector.callTool(
|
|
204
|
+
tool.serverName,
|
|
205
|
+
tool.originalName,
|
|
206
|
+
input
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
const duration = Date.now() - startTime;
|
|
210
|
+
this.recordInvocation(toolName, duration, true);
|
|
211
|
+
|
|
212
|
+
this.emit('tool:invoked', {
|
|
213
|
+
name: toolName,
|
|
214
|
+
duration,
|
|
215
|
+
success: true
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
return {
|
|
219
|
+
success: true,
|
|
220
|
+
result: result.content || result,
|
|
221
|
+
duration
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
} catch (error) {
|
|
225
|
+
const duration = Date.now() - startTime;
|
|
226
|
+
this.recordInvocation(toolName, duration, false);
|
|
227
|
+
|
|
228
|
+
this.emit('tool:error', {
|
|
229
|
+
name: toolName,
|
|
230
|
+
error: error.message,
|
|
231
|
+
duration
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
return {
|
|
235
|
+
success: false,
|
|
236
|
+
error,
|
|
237
|
+
duration
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Validate tool input against schema
|
|
244
|
+
* @param {MCPToolDefinition} tool
|
|
245
|
+
* @param {Object} input
|
|
246
|
+
* @returns {Object}
|
|
247
|
+
*/
|
|
248
|
+
validateInput(tool, input) {
|
|
249
|
+
const errors = [];
|
|
250
|
+
const schema = tool.inputSchema;
|
|
251
|
+
|
|
252
|
+
if (!schema || !schema.properties) {
|
|
253
|
+
return { valid: true, errors: [] };
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Check required fields
|
|
257
|
+
if (schema.required) {
|
|
258
|
+
for (const field of schema.required) {
|
|
259
|
+
if (!(field in input)) {
|
|
260
|
+
errors.push(`Missing required field: ${field}`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Type checking
|
|
266
|
+
for (const [key, prop] of Object.entries(schema.properties)) {
|
|
267
|
+
if (key in input) {
|
|
268
|
+
const value = input[key];
|
|
269
|
+
const expectedType = prop.type;
|
|
270
|
+
|
|
271
|
+
if (expectedType && !this.checkType(value, expectedType)) {
|
|
272
|
+
errors.push(`Invalid type for ${key}: expected ${expectedType}`);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return {
|
|
278
|
+
valid: errors.length === 0,
|
|
279
|
+
errors
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Check if value matches expected type
|
|
285
|
+
* @param {*} value
|
|
286
|
+
* @param {string} expectedType
|
|
287
|
+
* @returns {boolean}
|
|
288
|
+
*/
|
|
289
|
+
checkType(value, expectedType) {
|
|
290
|
+
switch (expectedType) {
|
|
291
|
+
case 'string': return typeof value === 'string';
|
|
292
|
+
case 'number': return typeof value === 'number';
|
|
293
|
+
case 'integer': return Number.isInteger(value);
|
|
294
|
+
case 'boolean': return typeof value === 'boolean';
|
|
295
|
+
case 'array': return Array.isArray(value);
|
|
296
|
+
case 'object': return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
297
|
+
case 'null': return value === null;
|
|
298
|
+
default: return true;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Record invocation metrics
|
|
304
|
+
* @param {string} toolName
|
|
305
|
+
* @param {number} duration
|
|
306
|
+
* @param {boolean} success
|
|
307
|
+
*/
|
|
308
|
+
recordInvocation(toolName, duration, success) {
|
|
309
|
+
const count = this.invocationCounts.get(toolName) || 0;
|
|
310
|
+
this.invocationCounts.set(toolName, count + 1);
|
|
311
|
+
|
|
312
|
+
const history = this.latencyHistory.get(toolName) || [];
|
|
313
|
+
history.push(duration);
|
|
314
|
+
|
|
315
|
+
// Keep last 100 latencies
|
|
316
|
+
if (history.length > 100) {
|
|
317
|
+
history.shift();
|
|
318
|
+
}
|
|
319
|
+
this.latencyHistory.set(toolName, history);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Get tool by name
|
|
324
|
+
* @param {string} name
|
|
325
|
+
* @returns {MCPToolDefinition|undefined}
|
|
326
|
+
*/
|
|
327
|
+
getTool(name) {
|
|
328
|
+
return this.tools.get(name);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Get all tools
|
|
333
|
+
* @returns {MCPToolDefinition[]}
|
|
334
|
+
*/
|
|
335
|
+
getAllTools() {
|
|
336
|
+
return Array.from(this.tools.values());
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Get tools by server
|
|
341
|
+
* @param {string} serverName
|
|
342
|
+
* @returns {MCPToolDefinition[]}
|
|
343
|
+
*/
|
|
344
|
+
getToolsByServer(serverName) {
|
|
345
|
+
const toolNames = this.serverTools.get(serverName) || [];
|
|
346
|
+
return toolNames.map(name => this.tools.get(name)).filter(Boolean);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Get tools in OpenAI format
|
|
351
|
+
* @returns {Object[]}
|
|
352
|
+
*/
|
|
353
|
+
getOpenAITools() {
|
|
354
|
+
return this.getAllTools().map(tool => ({
|
|
355
|
+
type: 'function',
|
|
356
|
+
function: {
|
|
357
|
+
name: tool.name.replace('/', '_'),
|
|
358
|
+
description: tool.description,
|
|
359
|
+
parameters: tool.inputSchema
|
|
360
|
+
}
|
|
361
|
+
}));
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Get tools in Anthropic format
|
|
366
|
+
* @returns {Object[]}
|
|
367
|
+
*/
|
|
368
|
+
getAnthropicTools() {
|
|
369
|
+
return this.getAllTools().map(tool => ({
|
|
370
|
+
name: tool.name.replace('/', '_'),
|
|
371
|
+
description: tool.description,
|
|
372
|
+
input_schema: tool.inputSchema
|
|
373
|
+
}));
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Get tool statistics
|
|
378
|
+
* @param {string} toolName
|
|
379
|
+
* @returns {Object}
|
|
380
|
+
*/
|
|
381
|
+
getToolStats(toolName) {
|
|
382
|
+
const invocations = this.invocationCounts.get(toolName) || 0;
|
|
383
|
+
const latencies = this.latencyHistory.get(toolName) || [];
|
|
384
|
+
|
|
385
|
+
let avgLatency = 0;
|
|
386
|
+
let minLatency = 0;
|
|
387
|
+
let maxLatency = 0;
|
|
388
|
+
|
|
389
|
+
if (latencies.length > 0) {
|
|
390
|
+
avgLatency = latencies.reduce((a, b) => a + b, 0) / latencies.length;
|
|
391
|
+
minLatency = Math.min(...latencies);
|
|
392
|
+
maxLatency = Math.max(...latencies);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
return {
|
|
396
|
+
invocations,
|
|
397
|
+
avgLatency: Math.round(avgLatency),
|
|
398
|
+
minLatency,
|
|
399
|
+
maxLatency,
|
|
400
|
+
recentLatencies: latencies.slice(-10)
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Get registry statistics
|
|
406
|
+
* @returns {Object}
|
|
407
|
+
*/
|
|
408
|
+
getStats() {
|
|
409
|
+
const tools = this.getAllTools();
|
|
410
|
+
let totalInvocations = 0;
|
|
411
|
+
|
|
412
|
+
for (const count of this.invocationCounts.values()) {
|
|
413
|
+
totalInvocations += count;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
return {
|
|
417
|
+
totalTools: tools.length,
|
|
418
|
+
servers: this.serverTools.size,
|
|
419
|
+
totalInvocations,
|
|
420
|
+
toolsByServer: Object.fromEntries(
|
|
421
|
+
Array.from(this.serverTools.entries()).map(([server, tools]) => [server, tools.length])
|
|
422
|
+
)
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Unregister tools from a server
|
|
428
|
+
* @param {string} serverName
|
|
429
|
+
*/
|
|
430
|
+
unregisterServer(serverName) {
|
|
431
|
+
const toolNames = this.serverTools.get(serverName) || [];
|
|
432
|
+
|
|
433
|
+
for (const name of toolNames) {
|
|
434
|
+
this.tools.delete(name);
|
|
435
|
+
this.invocationCounts.delete(name);
|
|
436
|
+
this.latencyHistory.delete(name);
|
|
437
|
+
|
|
438
|
+
// Unregister from skill registry
|
|
439
|
+
if (this.skillRegistry) {
|
|
440
|
+
const skillId = `mcp-${name.replace('/', '-')}`;
|
|
441
|
+
try {
|
|
442
|
+
this.skillRegistry.unregisterSkill(skillId);
|
|
443
|
+
} catch (e) {
|
|
444
|
+
// Ignore if not found
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
this.serverTools.delete(serverName);
|
|
450
|
+
this.emit('server:unregistered', { serverName, toolCount: toolNames.length });
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Clear all tools
|
|
455
|
+
*/
|
|
456
|
+
clear() {
|
|
457
|
+
this.tools.clear();
|
|
458
|
+
this.serverTools.clear();
|
|
459
|
+
this.invocationCounts.clear();
|
|
460
|
+
this.latencyHistory.clear();
|
|
461
|
+
this.emit('registry:cleared');
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
module.exports = {
|
|
466
|
+
MCPToolRegistry
|
|
467
|
+
};
|