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,896 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Tool Adapters - Bidirectional MCP-Skill Integration
|
|
3
|
+
* Sprint 3.3.5: MCP Tool Ecosystem
|
|
4
|
+
*
|
|
5
|
+
* Features:
|
|
6
|
+
* - MCP tool to MUSUBI skill conversion
|
|
7
|
+
* - MUSUBI skill to MCP tool export
|
|
8
|
+
* - Schema translation (JSON Schema ↔ MCP)
|
|
9
|
+
* - Capability mapping
|
|
10
|
+
* - Transport abstraction (stdio, sse, http)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const EventEmitter = require('events');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* MCP Transport types
|
|
17
|
+
*/
|
|
18
|
+
const MCPTransport = {
|
|
19
|
+
STDIO: 'stdio',
|
|
20
|
+
SSE: 'sse',
|
|
21
|
+
HTTP: 'http',
|
|
22
|
+
WEBSOCKET: 'websocket'
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Adapter direction
|
|
27
|
+
*/
|
|
28
|
+
const AdapterDirection = {
|
|
29
|
+
MCP_TO_SKILL: 'mcp-to-skill',
|
|
30
|
+
SKILL_TO_MCP: 'skill-to-mcp'
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* MCP Tool Definition (incoming format)
|
|
35
|
+
*/
|
|
36
|
+
class MCPToolDefinition {
|
|
37
|
+
constructor(options = {}) {
|
|
38
|
+
this.name = options.name || '';
|
|
39
|
+
this.description = options.description || '';
|
|
40
|
+
this.inputSchema = options.inputSchema || { type: 'object', properties: {} };
|
|
41
|
+
this.annotations = options.annotations || {};
|
|
42
|
+
this.server = options.server || null;
|
|
43
|
+
this.transport = options.transport || MCPTransport.STDIO;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
validate() {
|
|
47
|
+
const errors = [];
|
|
48
|
+
|
|
49
|
+
if (!this.name) {
|
|
50
|
+
errors.push('Tool name is required');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!this.inputSchema || this.inputSchema.type !== 'object') {
|
|
54
|
+
errors.push('Input schema must be an object type');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
valid: errors.length === 0,
|
|
59
|
+
errors
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Schema translator between MCP and MUSUBI formats
|
|
66
|
+
*/
|
|
67
|
+
class SchemaTranslator {
|
|
68
|
+
/**
|
|
69
|
+
* Convert MCP input schema to MUSUBI skill schema
|
|
70
|
+
*/
|
|
71
|
+
static mcpToMusubi(mcpSchema) {
|
|
72
|
+
if (!mcpSchema) {
|
|
73
|
+
return {
|
|
74
|
+
type: 'object',
|
|
75
|
+
properties: {},
|
|
76
|
+
required: []
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const musubiSchema = {
|
|
81
|
+
type: mcpSchema.type || 'object',
|
|
82
|
+
properties: {},
|
|
83
|
+
required: mcpSchema.required || []
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
if (mcpSchema.properties) {
|
|
87
|
+
for (const [key, prop] of Object.entries(mcpSchema.properties)) {
|
|
88
|
+
musubiSchema.properties[key] = this._translateProperty(prop);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Add MUSUBI-specific metadata
|
|
93
|
+
musubiSchema.$schema = 'https://musubi.dev/schemas/skill-input/v1';
|
|
94
|
+
|
|
95
|
+
return musubiSchema;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Convert MUSUBI skill schema to MCP format
|
|
100
|
+
*/
|
|
101
|
+
static musubiToMcp(musubiSchema) {
|
|
102
|
+
if (!musubiSchema) {
|
|
103
|
+
return {
|
|
104
|
+
type: 'object',
|
|
105
|
+
properties: {}
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const mcpSchema = {
|
|
110
|
+
type: musubiSchema.type || 'object',
|
|
111
|
+
properties: {}
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
if (musubiSchema.required && musubiSchema.required.length > 0) {
|
|
115
|
+
mcpSchema.required = musubiSchema.required;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (musubiSchema.properties) {
|
|
119
|
+
for (const [key, prop] of Object.entries(musubiSchema.properties)) {
|
|
120
|
+
mcpSchema.properties[key] = this._translatePropertyToMcp(prop);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return mcpSchema;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Translate individual property from MCP to MUSUBI
|
|
129
|
+
*/
|
|
130
|
+
static _translateProperty(prop) {
|
|
131
|
+
const result = {
|
|
132
|
+
type: prop.type || 'string'
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
if (prop.description) {
|
|
136
|
+
result.description = prop.description;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (prop.default !== undefined) {
|
|
140
|
+
result.default = prop.default;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (prop.enum) {
|
|
144
|
+
result.enum = prop.enum;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Handle nested objects
|
|
148
|
+
if (prop.type === 'object' && prop.properties) {
|
|
149
|
+
result.properties = {};
|
|
150
|
+
for (const [key, nestedProp] of Object.entries(prop.properties)) {
|
|
151
|
+
result.properties[key] = this._translateProperty(nestedProp);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Handle arrays
|
|
156
|
+
if (prop.type === 'array' && prop.items) {
|
|
157
|
+
result.items = this._translateProperty(prop.items);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Handle anyOf/oneOf
|
|
161
|
+
if (prop.anyOf) {
|
|
162
|
+
result.anyOf = prop.anyOf.map(p => this._translateProperty(p));
|
|
163
|
+
}
|
|
164
|
+
if (prop.oneOf) {
|
|
165
|
+
result.oneOf = prop.oneOf.map(p => this._translateProperty(p));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return result;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Translate individual property from MUSUBI to MCP
|
|
173
|
+
*/
|
|
174
|
+
static _translatePropertyToMcp(prop) {
|
|
175
|
+
const result = {
|
|
176
|
+
type: prop.type || 'string'
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
if (prop.description) {
|
|
180
|
+
result.description = prop.description;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (prop.default !== undefined) {
|
|
184
|
+
result.default = prop.default;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (prop.enum) {
|
|
188
|
+
result.enum = prop.enum;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Handle nested objects
|
|
192
|
+
if (prop.type === 'object' && prop.properties) {
|
|
193
|
+
result.properties = {};
|
|
194
|
+
for (const [key, nestedProp] of Object.entries(prop.properties)) {
|
|
195
|
+
result.properties[key] = this._translatePropertyToMcp(nestedProp);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Handle arrays
|
|
200
|
+
if (prop.type === 'array' && prop.items) {
|
|
201
|
+
result.items = this._translatePropertyToMcp(prop.items);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return result;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* MCP to MUSUBI Skill Adapter
|
|
210
|
+
* Wraps MCP tools as MUSUBI skills
|
|
211
|
+
*/
|
|
212
|
+
class MCPToSkillAdapter extends EventEmitter {
|
|
213
|
+
constructor(options = {}) {
|
|
214
|
+
super();
|
|
215
|
+
this.registry = options.registry || null;
|
|
216
|
+
this.adapters = new Map();
|
|
217
|
+
this.connections = new Map();
|
|
218
|
+
this.defaultTimeout = options.timeout || 30000;
|
|
219
|
+
this.retryConfig = options.retry || {
|
|
220
|
+
maxRetries: 3,
|
|
221
|
+
baseDelay: 1000,
|
|
222
|
+
maxDelay: 10000
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Register an MCP server and import its tools as skills
|
|
228
|
+
*/
|
|
229
|
+
async registerServer(serverConfig) {
|
|
230
|
+
const { id, transport, endpoint, tools } = serverConfig;
|
|
231
|
+
|
|
232
|
+
if (this.connections.has(id)) {
|
|
233
|
+
throw new Error(`Server ${id} is already registered`);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Store connection config
|
|
237
|
+
this.connections.set(id, {
|
|
238
|
+
id,
|
|
239
|
+
transport,
|
|
240
|
+
endpoint,
|
|
241
|
+
status: 'connected',
|
|
242
|
+
registeredAt: new Date()
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
// Import tools as skills
|
|
246
|
+
const importedSkills = [];
|
|
247
|
+
for (const tool of tools || []) {
|
|
248
|
+
try {
|
|
249
|
+
const skill = await this.importTool(id, tool);
|
|
250
|
+
importedSkills.push(skill);
|
|
251
|
+
} catch (error) {
|
|
252
|
+
this.emit('import-error', { serverId: id, tool, error });
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
this.emit('server-registered', {
|
|
257
|
+
serverId: id,
|
|
258
|
+
skillCount: importedSkills.length
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
return importedSkills;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Import a single MCP tool as a MUSUBI skill
|
|
266
|
+
*/
|
|
267
|
+
async importTool(serverId, mcpTool) {
|
|
268
|
+
const toolDef = new MCPToolDefinition(mcpTool);
|
|
269
|
+
const validation = toolDef.validate();
|
|
270
|
+
|
|
271
|
+
if (!validation.valid) {
|
|
272
|
+
throw new Error(`Invalid MCP tool: ${validation.errors.join(', ')}`);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Generate skill ID from server and tool name
|
|
276
|
+
const skillId = `mcp.${serverId}.${mcpTool.name}`;
|
|
277
|
+
|
|
278
|
+
// Create skill definition
|
|
279
|
+
const skillDefinition = {
|
|
280
|
+
id: skillId,
|
|
281
|
+
name: mcpTool.name,
|
|
282
|
+
description: mcpTool.description || `MCP tool: ${mcpTool.name}`,
|
|
283
|
+
category: 'mcp-imported',
|
|
284
|
+
version: '1.0.0',
|
|
285
|
+
inputSchema: SchemaTranslator.mcpToMusubi(mcpTool.inputSchema),
|
|
286
|
+
outputSchema: {
|
|
287
|
+
type: 'object',
|
|
288
|
+
properties: {
|
|
289
|
+
content: { type: 'array' },
|
|
290
|
+
isError: { type: 'boolean' }
|
|
291
|
+
}
|
|
292
|
+
},
|
|
293
|
+
tags: ['mcp', `mcp-server:${serverId}`],
|
|
294
|
+
metadata: {
|
|
295
|
+
source: 'mcp',
|
|
296
|
+
serverId,
|
|
297
|
+
originalName: mcpTool.name,
|
|
298
|
+
transport: toolDef.transport,
|
|
299
|
+
annotations: mcpTool.annotations || {}
|
|
300
|
+
},
|
|
301
|
+
// Execution handler
|
|
302
|
+
handler: async (input) => {
|
|
303
|
+
return this.executeMCPTool(serverId, mcpTool.name, input);
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
// Store adapter mapping
|
|
308
|
+
this.adapters.set(skillId, {
|
|
309
|
+
skillId,
|
|
310
|
+
serverId,
|
|
311
|
+
toolName: mcpTool.name,
|
|
312
|
+
definition: toolDef
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
// Register with skill registry if available
|
|
316
|
+
if (this.registry) {
|
|
317
|
+
this.registry.register(skillDefinition);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
this.emit('tool-imported', { skillId, serverId, toolName: mcpTool.name });
|
|
321
|
+
|
|
322
|
+
return skillDefinition;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Execute an MCP tool via its server
|
|
327
|
+
*/
|
|
328
|
+
async executeMCPTool(serverId, toolName, input) {
|
|
329
|
+
const connection = this.connections.get(serverId);
|
|
330
|
+
|
|
331
|
+
if (!connection) {
|
|
332
|
+
throw new Error(`Server ${serverId} not found`);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (connection.status !== 'connected') {
|
|
336
|
+
throw new Error(`Server ${serverId} is not connected (status: ${connection.status})`);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const startTime = Date.now();
|
|
340
|
+
|
|
341
|
+
try {
|
|
342
|
+
// Create MCP tool call request
|
|
343
|
+
const request = {
|
|
344
|
+
jsonrpc: '2.0',
|
|
345
|
+
method: 'tools/call',
|
|
346
|
+
params: {
|
|
347
|
+
name: toolName,
|
|
348
|
+
arguments: input
|
|
349
|
+
},
|
|
350
|
+
id: this._generateRequestId()
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
// Execute based on transport
|
|
354
|
+
const response = await this._sendRequest(connection, request);
|
|
355
|
+
|
|
356
|
+
const duration = Date.now() - startTime;
|
|
357
|
+
this.emit('tool-executed', {
|
|
358
|
+
serverId,
|
|
359
|
+
toolName,
|
|
360
|
+
duration,
|
|
361
|
+
success: !response.isError
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
return response;
|
|
365
|
+
|
|
366
|
+
} catch (error) {
|
|
367
|
+
this.emit('tool-error', { serverId, toolName, error });
|
|
368
|
+
throw error;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Send request to MCP server (transport abstraction)
|
|
374
|
+
*/
|
|
375
|
+
async _sendRequest(connection, request) {
|
|
376
|
+
// Note: This is a simplified implementation
|
|
377
|
+
// Real implementation would handle actual transport protocols
|
|
378
|
+
|
|
379
|
+
switch (connection.transport) {
|
|
380
|
+
case MCPTransport.STDIO:
|
|
381
|
+
return this._sendStdioRequest(connection, request);
|
|
382
|
+
|
|
383
|
+
case MCPTransport.HTTP:
|
|
384
|
+
return this._sendHttpRequest(connection, request);
|
|
385
|
+
|
|
386
|
+
case MCPTransport.SSE:
|
|
387
|
+
return this._sendSseRequest(connection, request);
|
|
388
|
+
|
|
389
|
+
case MCPTransport.WEBSOCKET:
|
|
390
|
+
return this._sendWebSocketRequest(connection, request);
|
|
391
|
+
|
|
392
|
+
default:
|
|
393
|
+
throw new Error(`Unsupported transport: ${connection.transport}`);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Stdio transport (placeholder)
|
|
399
|
+
*/
|
|
400
|
+
async _sendStdioRequest(connection, request) {
|
|
401
|
+
// Placeholder for stdio transport implementation
|
|
402
|
+
// In real implementation, this would:
|
|
403
|
+
// 1. Spawn child process
|
|
404
|
+
// 2. Send JSON-RPC request via stdin
|
|
405
|
+
// 3. Read response from stdout
|
|
406
|
+
return {
|
|
407
|
+
content: [{ type: 'text', text: 'Stdio transport result' }],
|
|
408
|
+
isError: false
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* HTTP transport (placeholder)
|
|
414
|
+
*/
|
|
415
|
+
async _sendHttpRequest(connection, request) {
|
|
416
|
+
// Placeholder for HTTP transport implementation
|
|
417
|
+
return {
|
|
418
|
+
content: [{ type: 'text', text: 'HTTP transport result' }],
|
|
419
|
+
isError: false
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* SSE transport (placeholder)
|
|
425
|
+
*/
|
|
426
|
+
async _sendSseRequest(connection, request) {
|
|
427
|
+
// Placeholder for SSE transport implementation
|
|
428
|
+
return {
|
|
429
|
+
content: [{ type: 'text', text: 'SSE transport result' }],
|
|
430
|
+
isError: false
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* WebSocket transport (placeholder)
|
|
436
|
+
*/
|
|
437
|
+
async _sendWebSocketRequest(connection, request) {
|
|
438
|
+
// Placeholder for WebSocket transport implementation
|
|
439
|
+
return {
|
|
440
|
+
content: [{ type: 'text', text: 'WebSocket transport result' }],
|
|
441
|
+
isError: false
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Generate unique request ID
|
|
447
|
+
*/
|
|
448
|
+
_generateRequestId() {
|
|
449
|
+
return `mcp-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Disconnect from an MCP server
|
|
454
|
+
*/
|
|
455
|
+
async disconnectServer(serverId) {
|
|
456
|
+
const connection = this.connections.get(serverId);
|
|
457
|
+
|
|
458
|
+
if (!connection) {
|
|
459
|
+
return false;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// Remove all skills from this server
|
|
463
|
+
for (const [skillId, adapter] of this.adapters) {
|
|
464
|
+
if (adapter.serverId === serverId) {
|
|
465
|
+
if (this.registry) {
|
|
466
|
+
this.registry.unregister(skillId);
|
|
467
|
+
}
|
|
468
|
+
this.adapters.delete(skillId);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
this.connections.delete(serverId);
|
|
473
|
+
this.emit('server-disconnected', { serverId });
|
|
474
|
+
|
|
475
|
+
return true;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Get all imported skills from a server
|
|
480
|
+
*/
|
|
481
|
+
getServerSkills(serverId) {
|
|
482
|
+
const skills = [];
|
|
483
|
+
|
|
484
|
+
for (const [skillId, adapter] of this.adapters) {
|
|
485
|
+
if (adapter.serverId === serverId) {
|
|
486
|
+
skills.push(adapter);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
return skills;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Get connection status
|
|
495
|
+
*/
|
|
496
|
+
getConnectionStatus() {
|
|
497
|
+
const status = {};
|
|
498
|
+
|
|
499
|
+
for (const [id, conn] of this.connections) {
|
|
500
|
+
status[id] = {
|
|
501
|
+
status: conn.status,
|
|
502
|
+
transport: conn.transport,
|
|
503
|
+
skillCount: this.getServerSkills(id).length,
|
|
504
|
+
registeredAt: conn.registeredAt
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
return status;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* MUSUBI Skill to MCP Tool Adapter
|
|
514
|
+
* Exports MUSUBI skills as MCP tools
|
|
515
|
+
*/
|
|
516
|
+
class SkillToMCPAdapter extends EventEmitter {
|
|
517
|
+
constructor(options = {}) {
|
|
518
|
+
super();
|
|
519
|
+
this.registry = options.registry || null;
|
|
520
|
+
this.executor = options.executor || null;
|
|
521
|
+
this.exportedTools = new Map();
|
|
522
|
+
this.serverInfo = {
|
|
523
|
+
name: options.serverName || 'musubi-mcp-server',
|
|
524
|
+
version: options.version || '1.0.0',
|
|
525
|
+
protocolVersion: '2024-11-05'
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* Export a MUSUBI skill as MCP tool
|
|
531
|
+
*/
|
|
532
|
+
exportSkill(skillId) {
|
|
533
|
+
if (!this.registry) {
|
|
534
|
+
throw new Error('Skill registry is required');
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
const skill = this.registry.getSkill(skillId);
|
|
538
|
+
if (!skill) {
|
|
539
|
+
throw new Error(`Skill ${skillId} not found`);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
const mcpTool = {
|
|
543
|
+
name: this._sanitizeToolName(skillId),
|
|
544
|
+
description: skill.description || '',
|
|
545
|
+
inputSchema: SchemaTranslator.musubiToMcp(skill.inputSchema),
|
|
546
|
+
annotations: {
|
|
547
|
+
musubiSkillId: skillId,
|
|
548
|
+
category: skill.category,
|
|
549
|
+
version: skill.version,
|
|
550
|
+
tags: skill.tags
|
|
551
|
+
}
|
|
552
|
+
};
|
|
553
|
+
|
|
554
|
+
this.exportedTools.set(skillId, mcpTool);
|
|
555
|
+
this.emit('skill-exported', { skillId, toolName: mcpTool.name });
|
|
556
|
+
|
|
557
|
+
return mcpTool;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* Export multiple skills
|
|
562
|
+
*/
|
|
563
|
+
exportSkills(skillIds) {
|
|
564
|
+
const tools = [];
|
|
565
|
+
|
|
566
|
+
for (const skillId of skillIds) {
|
|
567
|
+
try {
|
|
568
|
+
tools.push(this.exportSkill(skillId));
|
|
569
|
+
} catch (error) {
|
|
570
|
+
this.emit('export-error', { skillId, error });
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
return tools;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* Export all skills from a category
|
|
579
|
+
*/
|
|
580
|
+
exportCategory(category) {
|
|
581
|
+
if (!this.registry) {
|
|
582
|
+
throw new Error('Skill registry is required');
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
const skills = this.registry.findByCategory(category);
|
|
586
|
+
return this.exportSkills(skills.map(s => s.id));
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/**
|
|
590
|
+
* Handle MCP tools/list request
|
|
591
|
+
*/
|
|
592
|
+
handleListTools() {
|
|
593
|
+
const tools = [];
|
|
594
|
+
|
|
595
|
+
for (const [skillId, tool] of this.exportedTools) {
|
|
596
|
+
tools.push(tool);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
return { tools };
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* Handle MCP tools/call request
|
|
604
|
+
*/
|
|
605
|
+
async handleCallTool(params) {
|
|
606
|
+
const { name, arguments: args } = params;
|
|
607
|
+
|
|
608
|
+
// Find skill by tool name
|
|
609
|
+
let skillId = null;
|
|
610
|
+
for (const [id, tool] of this.exportedTools) {
|
|
611
|
+
if (tool.name === name) {
|
|
612
|
+
skillId = id;
|
|
613
|
+
break;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
if (!skillId) {
|
|
618
|
+
return {
|
|
619
|
+
content: [{ type: 'text', text: `Tool not found: ${name}` }],
|
|
620
|
+
isError: true
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
try {
|
|
625
|
+
// Execute via skill executor if available
|
|
626
|
+
if (this.executor) {
|
|
627
|
+
const result = await this.executor.execute(skillId, args);
|
|
628
|
+
return this._formatMCPResult(result);
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
// Direct execution via registry
|
|
632
|
+
const skill = this.registry.getSkill(skillId);
|
|
633
|
+
if (skill.handler) {
|
|
634
|
+
const result = await skill.handler(args);
|
|
635
|
+
return this._formatMCPResult(result);
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
return {
|
|
639
|
+
content: [{ type: 'text', text: 'No handler available for skill' }],
|
|
640
|
+
isError: true
|
|
641
|
+
};
|
|
642
|
+
|
|
643
|
+
} catch (error) {
|
|
644
|
+
this.emit('call-error', { toolName: name, skillId, error });
|
|
645
|
+
return {
|
|
646
|
+
content: [{ type: 'text', text: error.message }],
|
|
647
|
+
isError: true
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* Format skill result as MCP response
|
|
654
|
+
*/
|
|
655
|
+
_formatMCPResult(result) {
|
|
656
|
+
if (result === null || result === undefined) {
|
|
657
|
+
return {
|
|
658
|
+
content: [{ type: 'text', text: 'null' }],
|
|
659
|
+
isError: false
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
if (typeof result === 'string') {
|
|
664
|
+
return {
|
|
665
|
+
content: [{ type: 'text', text: result }],
|
|
666
|
+
isError: false
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
if (typeof result === 'object') {
|
|
671
|
+
// Check if already in MCP format
|
|
672
|
+
if (result.content && Array.isArray(result.content)) {
|
|
673
|
+
return result;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
// Convert to JSON text
|
|
677
|
+
return {
|
|
678
|
+
content: [{
|
|
679
|
+
type: 'text',
|
|
680
|
+
text: JSON.stringify(result, null, 2)
|
|
681
|
+
}],
|
|
682
|
+
isError: false
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
return {
|
|
687
|
+
content: [{ type: 'text', text: String(result) }],
|
|
688
|
+
isError: false
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
/**
|
|
693
|
+
* Sanitize skill ID to valid MCP tool name
|
|
694
|
+
*/
|
|
695
|
+
_sanitizeToolName(skillId) {
|
|
696
|
+
// MCP tool names should be alphanumeric with underscores
|
|
697
|
+
return skillId.replace(/[^a-zA-Z0-9_]/g, '_');
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
/**
|
|
701
|
+
* Get MCP server manifest
|
|
702
|
+
*/
|
|
703
|
+
getServerManifest() {
|
|
704
|
+
return {
|
|
705
|
+
...this.serverInfo,
|
|
706
|
+
capabilities: {
|
|
707
|
+
tools: { listChanged: true }
|
|
708
|
+
}
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
/**
|
|
713
|
+
* Create MCP protocol handler
|
|
714
|
+
*/
|
|
715
|
+
createProtocolHandler() {
|
|
716
|
+
return async (request) => {
|
|
717
|
+
const { method, params, id } = request;
|
|
718
|
+
|
|
719
|
+
switch (method) {
|
|
720
|
+
case 'initialize':
|
|
721
|
+
return {
|
|
722
|
+
jsonrpc: '2.0',
|
|
723
|
+
id,
|
|
724
|
+
result: this.getServerManifest()
|
|
725
|
+
};
|
|
726
|
+
|
|
727
|
+
case 'tools/list':
|
|
728
|
+
return {
|
|
729
|
+
jsonrpc: '2.0',
|
|
730
|
+
id,
|
|
731
|
+
result: this.handleListTools()
|
|
732
|
+
};
|
|
733
|
+
|
|
734
|
+
case 'tools/call':
|
|
735
|
+
const result = await this.handleCallTool(params);
|
|
736
|
+
return {
|
|
737
|
+
jsonrpc: '2.0',
|
|
738
|
+
id,
|
|
739
|
+
result
|
|
740
|
+
};
|
|
741
|
+
|
|
742
|
+
default:
|
|
743
|
+
return {
|
|
744
|
+
jsonrpc: '2.0',
|
|
745
|
+
id,
|
|
746
|
+
error: {
|
|
747
|
+
code: -32601,
|
|
748
|
+
message: `Method not found: ${method}`
|
|
749
|
+
}
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
/**
|
|
757
|
+
* Unified MCP Adapter Manager
|
|
758
|
+
* Manages both import and export adapters
|
|
759
|
+
*/
|
|
760
|
+
class MCPAdapterManager extends EventEmitter {
|
|
761
|
+
constructor(options = {}) {
|
|
762
|
+
super();
|
|
763
|
+
this.registry = options.registry || null;
|
|
764
|
+
this.executor = options.executor || null;
|
|
765
|
+
|
|
766
|
+
this.importAdapter = new MCPToSkillAdapter({
|
|
767
|
+
registry: this.registry,
|
|
768
|
+
timeout: options.timeout,
|
|
769
|
+
retry: options.retry
|
|
770
|
+
});
|
|
771
|
+
|
|
772
|
+
this.exportAdapter = new SkillToMCPAdapter({
|
|
773
|
+
registry: this.registry,
|
|
774
|
+
executor: this.executor,
|
|
775
|
+
serverName: options.serverName,
|
|
776
|
+
version: options.version
|
|
777
|
+
});
|
|
778
|
+
|
|
779
|
+
// Forward events
|
|
780
|
+
this._setupEventForwarding();
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
/**
|
|
784
|
+
* Setup event forwarding from child adapters
|
|
785
|
+
*/
|
|
786
|
+
_setupEventForwarding() {
|
|
787
|
+
const importEvents = [
|
|
788
|
+
'server-registered',
|
|
789
|
+
'server-disconnected',
|
|
790
|
+
'tool-imported',
|
|
791
|
+
'tool-executed',
|
|
792
|
+
'tool-error',
|
|
793
|
+
'import-error'
|
|
794
|
+
];
|
|
795
|
+
|
|
796
|
+
const exportEvents = [
|
|
797
|
+
'skill-exported',
|
|
798
|
+
'export-error',
|
|
799
|
+
'call-error'
|
|
800
|
+
];
|
|
801
|
+
|
|
802
|
+
for (const event of importEvents) {
|
|
803
|
+
this.importAdapter.on(event, (data) => {
|
|
804
|
+
this.emit(`import:${event}`, data);
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
for (const event of exportEvents) {
|
|
809
|
+
this.exportAdapter.on(event, (data) => {
|
|
810
|
+
this.emit(`export:${event}`, data);
|
|
811
|
+
});
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
/**
|
|
816
|
+
* Register MCP server and import tools
|
|
817
|
+
*/
|
|
818
|
+
async registerMCPServer(serverConfig) {
|
|
819
|
+
return this.importAdapter.registerServer(serverConfig);
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
/**
|
|
823
|
+
* Disconnect from MCP server
|
|
824
|
+
*/
|
|
825
|
+
async disconnectMCPServer(serverId) {
|
|
826
|
+
return this.importAdapter.disconnectServer(serverId);
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
/**
|
|
830
|
+
* Execute imported MCP tool
|
|
831
|
+
*/
|
|
832
|
+
async executeMCPTool(serverId, toolName, input) {
|
|
833
|
+
return this.importAdapter.executeMCPTool(serverId, toolName, input);
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
/**
|
|
837
|
+
* Export skill as MCP tool
|
|
838
|
+
*/
|
|
839
|
+
exportSkill(skillId) {
|
|
840
|
+
return this.exportAdapter.exportSkill(skillId);
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
/**
|
|
844
|
+
* Export multiple skills
|
|
845
|
+
*/
|
|
846
|
+
exportSkills(skillIds) {
|
|
847
|
+
return this.exportAdapter.exportSkills(skillIds);
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
/**
|
|
851
|
+
* Get MCP protocol handler for exported skills
|
|
852
|
+
*/
|
|
853
|
+
getMCPHandler() {
|
|
854
|
+
return this.exportAdapter.createProtocolHandler();
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
/**
|
|
858
|
+
* Get comprehensive status
|
|
859
|
+
*/
|
|
860
|
+
getStatus() {
|
|
861
|
+
return {
|
|
862
|
+
import: {
|
|
863
|
+
connections: this.importAdapter.getConnectionStatus(),
|
|
864
|
+
adapterCount: this.importAdapter.adapters.size
|
|
865
|
+
},
|
|
866
|
+
export: {
|
|
867
|
+
toolCount: this.exportAdapter.exportedTools.size,
|
|
868
|
+
serverInfo: this.exportAdapter.serverInfo
|
|
869
|
+
}
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
/**
|
|
874
|
+
* Get imported skill count
|
|
875
|
+
*/
|
|
876
|
+
getImportedSkillCount() {
|
|
877
|
+
return this.importAdapter.adapters.size;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
/**
|
|
881
|
+
* Get exported tool count
|
|
882
|
+
*/
|
|
883
|
+
getExportedToolCount() {
|
|
884
|
+
return this.exportAdapter.exportedTools.size;
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
module.exports = {
|
|
889
|
+
MCPTransport,
|
|
890
|
+
AdapterDirection,
|
|
891
|
+
MCPToolDefinition,
|
|
892
|
+
SchemaTranslator,
|
|
893
|
+
MCPToSkillAdapter,
|
|
894
|
+
SkillToMCPAdapter,
|
|
895
|
+
MCPAdapterManager
|
|
896
|
+
};
|