/**
* Orchestrator Tool (M6)
*
* Demonstrates Multi-Dimensional Orchestration by spawning child tools
* and composing them into workflows.
*
* Capabilities:
* - workflow: Execute multi-step workflow (create → validate → read)
* - pipeline: Execute custom pipeline of tool actions
* - aggregate: Run multiple actions in parallel and aggregate results
*/
import { BaseTool, type ToolContext, type ToolResult, type ToolIdentity, type ToolClass, type ActionSchema } from '../core/tool-interface.js';
class OrchestratorTool extends BaseTool {
constructor() {
super({
name: 'orchestrator-tool',
version: '1.0.0',
capabilities: ['workflow', 'pipeline', 'aggregate'],
});
}
async execute(action: string, context: ToolContext): Promise<ToolResult> {
// M6: Verify orchestration context is available
if (!context.conversationManager) {
return {
success: false,
error: 'M6 orchestration not available (conversationManager missing from context)',
};
}
if (!context.toolRegistry) {
return {
success: false,
error: 'M6 orchestration not available (toolRegistry missing from context)',
};
}
switch (action) {
case 'workflow':
return this.executeWorkflow(context);
case 'pipeline':
return this.executePipeline(context);
case 'aggregate':
return this.executeAggregate(context);
default:
return {
success: false,
error: `Unknown action: ${action}. Available: workflow, pipeline, aggregate`,
};
}
}
/**
* Execute multi-step workflow: create → validate → read
*/
private async executeWorkflow(context: ToolContext): Promise<ToolResult> {
const results: any[] = [];
console.error('[OrchestratorTool] Starting workflow...');
// Step 1: Create resource with data-tool
console.error('[OrchestratorTool] Step 1: Creating resource with data-tool');
const createResult = await context.conversationManager.negotiate(
context.conversationId,
'create-resource',
{ name: 'workflow-data', data: { step: 1, workflow: true } },
undefined, // no explicit tool
{
// M6: Pass orchestration context (children are at depth+1)
parentTool: context.toolName,
depth: (context.depth || 0) + 1,
callChain: context.callChain,
}
);
results.push({ step: 'create', result: createResult });
if (!createResult.success) {
return {
success: false,
error: `Workflow failed at step 1 (create): ${createResult.error}`,
output: { completedSteps: results },
};
}
// Step 2: Validate resource with admin-tool
console.error('[OrchestratorTool] Step 2: Validating resource with admin-tool');
const validateResult = await context.conversationManager.negotiate(
context.conversationId,
'validate-resource',
{ name: 'workflow-data' },
undefined,
{
parentTool: context.toolName,
depth: (context.depth || 0) + 1,
callChain: context.callChain,
}
);
results.push({ step: 'validate', result: validateResult });
if (!validateResult.success) {
return {
success: false,
error: `Workflow failed at step 2 (validate): ${validateResult.error}`,
output: { completedSteps: results },
};
}
// Step 3: Read final state with data-tool
console.error('[OrchestratorTool] Step 3: Reading resource with data-tool');
const readResult = await context.conversationManager.negotiate(
context.conversationId,
'read-resource',
{ name: 'workflow-data' },
undefined,
{
parentTool: context.toolName,
depth: (context.depth || 0) + 1,
callChain: context.callChain,
}
);
results.push({ step: 'read', result: readResult });
if (!readResult.success) {
return {
success: false,
error: `Workflow failed at step 3 (read): ${readResult.error}`,
output: { completedSteps: results },
};
}
console.error('[OrchestratorTool] Workflow completed successfully');
return {
success: true,
output: {
workflow: 'create-validate-read',
steps: results.length,
results: results,
message: 'Workflow completed successfully ✅ [v1.1.0-refactored]',
finalData: readResult.output,
},
};
}
/**
* Normalize action format from various MCP client serialization formats
*
* Handles three formats:
* - Format 1: {tool, action, params} or {tool, action, name, data, ...}
* - Format 2: {action: "tool/action", args}
* - Format 3: {action, args}
*/
private normalizeActionFormat(actionDef: any): { action: string; args: any } {
const RESERVED_KEYS = ['tool', 'action', 'params', 'args'];
if (actionDef.tool && actionDef.action) {
// Format 1: {tool, action, params} OR {tool, action, name, data, ...}
const action = actionDef.action;
let args: any;
if (actionDef.params || actionDef.args) {
// Nested params/args provided
args = actionDef.params || actionDef.args || {};
} else {
// Extract all non-reserved keys as args (top-level parameters)
args = {};
for (const [key, value] of Object.entries(actionDef)) {
if (!RESERVED_KEYS.includes(key)) {
args[key] = value;
}
}
}
return { action, args };
}
if (actionDef.action && actionDef.action.includes('/')) {
// Format 2: {action: "data-tool/create-resource", args: {...}}
const parts = actionDef.action.split('/');
return {
action: parts.length > 1 ? parts[1] : actionDef.action,
args: actionDef.args || {},
};
}
// Format 3: {action: "create-resource", args: {...}} - already correct
return {
action: actionDef.action,
args: actionDef.args || {},
};
}
/**
* Execute custom pipeline of tool actions
*/
private async executePipeline(context: ToolContext): Promise<ToolResult> {
let steps = (context.args as any)?.steps;
// Handle JSON string from Claude Desktop (serializes arrays as strings)
if (typeof steps === 'string') {
try {
steps = JSON.parse(steps);
} catch (e) {
return {
success: false,
error: 'Pipeline "steps" must be a valid JSON array',
};
}
}
if (!steps || !Array.isArray(steps)) {
return {
success: false,
error: 'Pipeline requires "steps" array with {action, args} objects',
};
}
const results: any[] = [];
console.error(`[OrchestratorTool] Starting pipeline with ${steps.length} steps...`);
for (let i = 0; i < steps.length; i++) {
const step = steps[i];
// Normalize step format using shared helper
const { action, args } = this.normalizeActionFormat(step);
console.error(`[OrchestratorTool] Pipeline step ${i + 1}: ${action}`);
const result = await context.conversationManager.negotiate(
context.conversationId,
action,
args,
undefined,
{
parentTool: context.toolName,
depth: context.depth,
callChain: context.callChain,
}
);
results.push({ step: i + 1, action: action, result });
if (!result.success) {
const completedActions = results
.slice(0, -1)
.map((r) => ` ✓ Step ${r.step}: ${r.action}`)
.join('\n');
const statusMsg =
completedActions
? `\n\nCompleted before failure:\n${completedActions}`
: '\n\nFailed on first step.';
return {
success: false,
error: `Pipeline failed at step ${i + 1} (${action}): ${result.error}${statusMsg}`,
output: { completedSteps: results },
};
}
}
console.error('[OrchestratorTool] Pipeline completed successfully');
return {
success: true,
output: {
pipeline: 'custom',
steps: results.length,
results: results,
message: 'Pipeline completed successfully ✅ [v1.1.0-refactored]',
},
};
}
/**
* Run multiple actions in parallel and aggregate results
*/
private async executeAggregate(context: ToolContext): Promise<ToolResult> {
let actions = (context.args as any)?.actions;
// Handle JSON string from Claude Desktop (serializes arrays as strings)
if (typeof actions === 'string') {
try {
actions = JSON.parse(actions);
} catch (e) {
return {
success: false,
error: 'Aggregate "actions" must be a valid JSON array',
};
}
}
if (!actions || !Array.isArray(actions)) {
return {
success: false,
error: 'Aggregate requires "actions" array with {action, args} objects',
};
}
console.error(`[OrchestratorTool] Starting aggregate with ${actions.length} parallel actions...`);
// Run all actions in parallel
const promises = actions.map((actionDef: any, index: number) => {
// Normalize action format using shared helper
const { action, args } = this.normalizeActionFormat(actionDef);
console.error(`[OrchestratorTool] Aggregate action ${index + 1}: ${action}`);
return context.conversationManager.negotiate(
context.conversationId,
action,
args,
undefined,
{
parentTool: context.toolName,
depth: context.depth,
callChain: context.callChain,
}
);
});
const results = await Promise.all(promises);
// Check for failures
const failures = results.filter((r) => !r.success);
if (failures.length > 0) {
const failureDetails = results
.map((r, i) => ({ action: actions[i].action, success: r.success, error: r.error }))
.filter((r) => !r.success)
.map((r) => ` - ${r.action}: ${r.error}`)
.join('\n');
return {
success: false,
error: `Aggregate completed with ${failures.length}/${results.length} failures:\n${failureDetails}\n\nSuccesses: ${results.length - failures.length}/${results.length}`,
output: {
total: results.length,
successes: results.length - failures.length,
failures: failures.length,
results: results.map((r, i) => ({ action: actions[i].action, result: r })),
},
};
}
console.error('[OrchestratorTool] Aggregate completed successfully');
return {
success: true,
output: {
aggregate: 'parallel',
total: results.length,
results: results.map((r, i) => ({ action: actions[i].action, result: r })),
message: 'Aggregate completed successfully ✅ [v1.1.0-refactored]',
},
};
}
}
const OrchestratorToolClass: ToolClass = Object.assign(OrchestratorTool, {
identity: {
name: 'orchestrator-tool',
version: '1.0.0',
capabilities: ['workflow', 'pipeline', 'aggregate'],
} as ToolIdentity,
actionSchemas: {
workflow: {
params: [],
required: [],
description: 'Execute multi-step workflow (create → validate → read)',
},
pipeline: {
params: ['steps'],
required: ['steps'],
description: 'Execute custom pipeline of tool actions',
},
aggregate: {
params: ['actions'],
required: ['actions'],
description: 'Run multiple actions in parallel and aggregate results',
},
} as Record<string, ActionSchema>,
});
export default OrchestratorToolClass;