@boundless-oss/atlas
Version:
Atlas - MCP Server for comprehensive startup project management
820 lines • 31.9 kB
JavaScript
import { randomUUID } from 'crypto';
import { createTool, createSuccessResult, createErrorResult } from '../../core/tool-framework.js';
/**
* Create a new automation process
*/
const createProcessTool = createTool({
name: 'create_process',
description: 'Create a new automation process with triggers and activities',
category: 'process-automation',
inputSchema: {
type: 'object',
properties: {
name: {
type: 'string',
description: 'Process name',
minLength: 1,
maxLength: 200
},
description: {
type: 'string',
description: 'Process description',
maxLength: 1000
},
version: {
type: 'string',
description: 'Process version',
default: '1.0.0',
pattern: '^\\d+\\.\\d+\\.\\d+$'
},
persona: {
type: 'string',
description: 'Persona type for the process'
},
category: {
type: 'string',
description: 'Process category'
},
tags: {
type: 'array',
items: { type: 'string' },
description: 'Process tags for organization',
maxItems: 20
},
triggers: {
type: 'array',
items: {
type: 'object',
properties: {
type: {
type: 'string',
enum: ['manual', 'schedule', 'event', 'webhook', 'condition', 'chain']
},
name: { type: 'string' },
enabled: { type: 'boolean', default: true },
config: { type: 'object' }
},
required: ['type', 'name', 'config']
},
description: 'Process triggers',
minItems: 1
},
activities: {
type: 'array',
items: {
type: 'object',
properties: {
id: { type: 'string' },
type: {
type: 'string',
enum: ['tool', 'human', 'agent', 'conditional', 'loop', 'parallel', 'external']
},
name: { type: 'string' },
description: { type: 'string' },
config: { type: 'object' },
retryPolicy: {
type: 'object',
properties: {
maxRetries: { type: 'integer', minimum: 0 },
backoffMultiplier: { type: 'number' },
initialDelay: { type: 'integer' }
}
},
timeout: { type: 'integer', minimum: 1000 },
required: { type: 'boolean', default: true }
},
required: ['id', 'type', 'name', 'config']
},
description: 'Process activities to execute',
minItems: 1
},
variables: {
type: 'object',
description: 'Process variables'
},
onSuccess: {
type: 'array',
description: 'Activities to run on success'
},
onFailure: {
type: 'array',
description: 'Activities to run on failure'
}
},
required: ['name', 'activities'],
additionalProperties: false
},
async execute(input, context) {
try {
const processId = randomUUID();
const now = Date.now();
// Check for duplicate process names
const existingProcess = await context.db.get('SELECT id FROM process_definitions WHERE name = ? AND project_id = ? AND is_active = TRUE', [input.name, context.projectId || 'default']);
if (existingProcess.success && existingProcess.data) {
return createErrorResult({
code: 'DUPLICATE_RESOURCE',
message: 'An active process with this name already exists',
category: 'validation'
});
}
// Start transaction for process creation
await context.db.transaction(async (tx) => {
// Create process definition
await tx.run(`INSERT INTO process_definitions
(id, project_id, name, description, version, persona, category, tags,
variables, on_success, on_failure, is_active, created_by, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
processId,
context.projectId || 'default',
input.name,
input.description || '',
input.version || '1.0.0',
input.persona || null,
input.category || null,
JSON.stringify(input.tags || []),
JSON.stringify(input.variables || {}),
input.onSuccess ? JSON.stringify(input.onSuccess) : null,
input.onFailure ? JSON.stringify(input.onFailure) : null,
true,
context.userId || 'system',
now,
now
]);
// Create activities
for (let i = 0; i < input.activities.length; i++) {
const activity = input.activities[i];
await tx.run(`INSERT INTO process_activities
(id, process_id, activity_order, activity_id, type, name, description,
config, retry_policy, timeout, required, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
randomUUID(),
processId,
i,
activity.id,
activity.type,
activity.name,
activity.description || '',
JSON.stringify(activity.config),
activity.retryPolicy ? JSON.stringify(activity.retryPolicy) : null,
activity.timeout || null,
activity.required !== false,
now
]);
}
// Create triggers if provided
if (input.triggers) {
for (const trigger of input.triggers) {
await tx.run(`INSERT INTO process_triggers
(id, process_id, trigger_id, type, name, enabled, config, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
randomUUID(),
processId,
randomUUID(),
trigger.type,
trigger.name,
trigger.enabled !== false,
JSON.stringify(trigger.config),
now,
now
]);
}
}
else {
// Create default manual trigger
await tx.run(`INSERT INTO process_triggers
(id, process_id, trigger_id, type, name, enabled, config, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
randomUUID(),
processId,
randomUUID(),
'manual',
'Manual Trigger',
true,
JSON.stringify({}),
now,
now
]);
}
});
return createSuccessResult({
process: {
id: processId,
name: input.name,
description: input.description || '',
version: input.version || '1.0.0',
persona: input.persona || null,
category: input.category || null,
tags: input.tags || [],
activityCount: input.activities.length,
triggerCount: input.triggers?.length || 1,
createdAt: new Date(now).toISOString()
},
message: `Process "${input.name}" created successfully`,
nextSteps: [
'Test the process with manual execution',
'Configure additional triggers if needed',
'Monitor execution results'
]
});
}
catch (error) {
return createErrorResult({
code: 'EXECUTION_ERROR',
message: `Failed to create process: ${error instanceof Error ? error.message : 'Unknown error'}`,
category: 'execution'
});
}
}
});
/**
* Execute a process
*/
const executeProcessTool = createTool({
name: 'execute_process',
description: 'Execute an automation process manually',
category: 'process-automation',
inputSchema: {
type: 'object',
properties: {
processId: {
type: 'string',
description: 'Process ID to execute',
pattern: '^[a-zA-Z0-9-]+$'
},
variables: {
type: 'object',
description: 'Variables to pass to the process'
},
triggeredBy: {
type: 'string',
description: 'Who/what triggered the execution'
},
triggerType: {
type: 'string',
description: 'Type of trigger'
}
},
required: ['processId'],
additionalProperties: false
},
async execute(input, context) {
try {
// Get process definition
const processResult = await context.db.get('SELECT * FROM process_definitions WHERE id = ? AND project_id = ? AND is_active = TRUE', [input.processId, context.projectId || 'default']);
if (!processResult.success || !processResult.data) {
return createErrorResult({
code: 'RESOURCE_NOT_FOUND',
message: 'Process not found or inactive',
category: 'validation'
});
}
const process = processResult.data;
const executionId = randomUUID();
const now = Date.now();
// Merge process variables with input variables
const processVariables = JSON.parse(process.variables || '{}');
const executionVariables = { ...processVariables, ...input.variables };
// Create execution record
const execResult = await context.db.run(`INSERT INTO process_executions
(id, process_id, project_id, process_version, status, triggered_by,
trigger_type, variables, started_at, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
executionId,
input.processId,
context.projectId || 'default',
process.version,
'pending',
input.triggeredBy || context.userId || 'manual',
input.triggerType || 'manual',
JSON.stringify(executionVariables),
now,
now
]);
if (!execResult.success) {
return createErrorResult({
code: 'DATABASE_ERROR',
message: 'Failed to create execution record',
category: 'system'
});
}
// Log execution start
await context.db.run(`INSERT INTO process_execution_logs
(id, execution_id, timestamp, level, message)
VALUES (?, ?, ?, ?, ?)`, [
randomUUID(),
executionId,
now,
'info',
`Process execution started for "${process.name}"`
]);
// Note: Actual execution would be handled by a background worker
// This is just creating the execution record
return createSuccessResult({
execution: {
id: executionId,
processId: input.processId,
processName: process.name,
status: 'pending',
triggeredBy: input.triggeredBy || context.userId || 'manual',
startedAt: new Date(now).toISOString()
},
message: `Process execution started successfully`,
note: 'Execution will be processed by the automation engine'
});
}
catch (error) {
return createErrorResult({
code: 'EXECUTION_ERROR',
message: `Failed to execute process: ${error instanceof Error ? error.message : 'Unknown error'}`,
category: 'execution'
});
}
}
});
/**
* List processes
*/
const listProcessesTool = createTool({
name: 'list_processes',
description: 'List all automation processes',
category: 'process-automation',
readOnly: true,
inputSchema: {
type: 'object',
properties: {
category: {
type: 'string',
description: 'Filter by category'
},
persona: {
type: 'string',
description: 'Filter by persona'
},
tags: {
type: 'array',
items: { type: 'string' },
description: 'Filter by tags'
},
isActive: {
type: 'boolean',
description: 'Filter by active status',
default: true
}
},
required: [],
additionalProperties: false
},
async execute(input, context) {
try {
let query = 'SELECT * FROM process_definitions WHERE project_id = ?';
const params = [context.projectId || 'default'];
if (input.isActive !== undefined) {
query += ' AND is_active = ?';
params.push(input.isActive);
}
if (input.category) {
query += ' AND category = ?';
params.push(input.category);
}
if (input.persona) {
query += ' AND persona = ?';
params.push(input.persona);
}
query += ' ORDER BY created_at DESC';
const result = await context.db.query(query, params);
if (!result.success) {
return createErrorResult({
code: 'DATABASE_ERROR',
message: 'Failed to list processes',
category: 'system'
});
}
const processes = [];
for (const proc of result.data || []) {
// Filter by tags if specified
if (input.tags && input.tags.length > 0) {
const processTags = JSON.parse(proc.tags || '[]');
if (!input.tags.some(tag => processTags.includes(tag))) {
continue;
}
}
// Get trigger count
const triggerResult = await context.db.get('SELECT COUNT(*) as count FROM process_triggers WHERE process_id = ?', [proc.id]);
// Get activity count
const activityResult = await context.db.get('SELECT COUNT(*) as count FROM process_activities WHERE process_id = ?', [proc.id]);
processes.push({
id: proc.id,
name: proc.name,
description: proc.description,
version: proc.version,
category: proc.category,
persona: proc.persona,
tags: JSON.parse(proc.tags || '[]'),
isActive: proc.is_active,
triggerCount: triggerResult.data?.count || 0,
activityCount: activityResult.data?.count || 0,
createdAt: new Date(proc.created_at).toISOString(),
updatedAt: new Date(proc.updated_at).toISOString()
});
}
return createSuccessResult({
processes,
count: processes.length,
filters: {
category: input.category,
persona: input.persona,
tags: input.tags,
isActive: input.isActive
}
});
}
catch (error) {
return createErrorResult({
code: 'EXECUTION_ERROR',
message: `Failed to list processes: ${error instanceof Error ? error.message : 'Unknown error'}`,
category: 'execution'
});
}
}
});
/**
* List executions
*/
const listExecutionsTool = createTool({
name: 'list_executions',
description: 'List process executions',
category: 'process-automation',
readOnly: true,
inputSchema: {
type: 'object',
properties: {
processId: {
type: 'string',
description: 'Filter by process ID',
pattern: '^[a-zA-Z0-9-]+$'
},
status: {
type: 'string',
enum: ['pending', 'running', 'completed', 'failed', 'cancelled'],
description: 'Filter by status'
},
limit: {
type: 'integer',
description: 'Maximum number of results',
minimum: 1,
maximum: 100,
default: 20
},
offset: {
type: 'integer',
description: 'Offset for pagination',
minimum: 0,
default: 0
}
},
required: [],
additionalProperties: false
},
async execute(input, context) {
try {
let query = `
SELECT e.*, p.name as process_name
FROM process_executions e
JOIN process_definitions p ON e.process_id = p.id
WHERE e.project_id = ?
`;
const params = [context.projectId || 'default'];
if (input.processId) {
query += ' AND e.process_id = ?';
params.push(input.processId);
}
if (input.status) {
query += ' AND e.status = ?';
params.push(input.status);
}
query += ' ORDER BY e.started_at DESC LIMIT ? OFFSET ?';
params.push(input.limit || 20, input.offset || 0);
const result = await context.db.query(query, params);
if (!result.success) {
return createErrorResult({
code: 'DATABASE_ERROR',
message: 'Failed to list executions',
category: 'system'
});
}
const executions = (result.data || []).map((exec) => ({
id: exec.id,
processId: exec.process_id,
processName: exec.process_name,
processVersion: exec.process_version,
status: exec.status,
triggeredBy: exec.triggered_by,
triggerType: exec.trigger_type,
variables: JSON.parse(exec.variables || '{}'),
errorMessage: exec.error_message,
errorCode: exec.error_code,
startedAt: new Date(exec.started_at).toISOString(),
completedAt: exec.completed_at ? new Date(exec.completed_at).toISOString() : null,
duration: exec.duration
}));
return createSuccessResult({
executions,
count: executions.length,
limit: input.limit || 20,
offset: input.offset || 0
});
}
catch (error) {
return createErrorResult({
code: 'EXECUTION_ERROR',
message: `Failed to list executions: ${error instanceof Error ? error.message : 'Unknown error'}`,
category: 'execution'
});
}
}
});
/**
* Suggest triggers for a process
*/
const suggestTriggersTool = createTool({
name: 'suggest_triggers',
description: 'Get AI-powered trigger suggestions for a process',
category: 'process-automation',
readOnly: true,
inputSchema: {
type: 'object',
properties: {
processName: {
type: 'string',
description: 'Process name'
},
activities: {
type: 'array',
items: {
type: 'object',
properties: {
type: { type: 'string' },
name: { type: 'string' },
toolName: { type: 'string' }
},
required: ['type', 'name']
},
description: 'Process activities'
},
persona: {
type: 'string',
description: 'Persona type'
}
},
required: ['processName', 'activities'],
additionalProperties: false
},
async execute(input, context) {
try {
// This is a simplified version - in production, this would use AI
const suggestions = [];
// Analyze activities to suggest appropriate triggers
const hasDevActivities = input.activities.some(a => a.toolName?.includes('test') || a.toolName?.includes('build') || a.toolName?.includes('deploy'));
const hasDataActivities = input.activities.some(a => a.toolName?.includes('backup') || a.toolName?.includes('sync') || a.toolName?.includes('report'));
const hasReviewActivities = input.activities.some(a => a.type === 'human' || a.toolName?.includes('review') || a.toolName?.includes('approve'));
// Development-related triggers
if (hasDevActivities) {
suggestions.push({
type: 'event',
name: 'On Code Push',
config: {
eventType: 'git.push',
branch: 'main'
},
reasoning: 'Automatically run tests and builds when code is pushed',
confidence: 0.9
});
suggestions.push({
type: 'schedule',
name: 'Nightly Build',
config: {
cronExpression: '0 2 * * *',
timezone: 'UTC'
},
reasoning: 'Run comprehensive tests during off-hours',
confidence: 0.8
});
}
// Data-related triggers
if (hasDataActivities) {
suggestions.push({
type: 'schedule',
name: 'Daily Backup',
config: {
cronExpression: '0 3 * * *',
timezone: 'UTC'
},
reasoning: 'Regular backups ensure data safety',
confidence: 0.95
});
suggestions.push({
type: 'condition',
name: 'On Storage Threshold',
config: {
condition: 'storage.usage > 80',
checkInterval: 3600
},
reasoning: 'Trigger cleanup or alerts when storage is high',
confidence: 0.85
});
}
// Review-related triggers
if (hasReviewActivities) {
suggestions.push({
type: 'webhook',
name: 'External Request',
config: {
path: `/webhooks/process/${input.processName.toLowerCase().replace(/\s+/g, '-')}`,
auth: 'bearer'
},
reasoning: 'Allow external systems to trigger reviews',
confidence: 0.7
});
}
// Always suggest manual trigger
suggestions.push({
type: 'manual',
name: 'Manual Execution',
config: {},
reasoning: 'Always useful for testing and ad-hoc runs',
confidence: 1.0
});
return createSuccessResult({
suggestions: suggestions.sort((a, b) => b.confidence - a.confidence),
processContext: {
name: input.processName,
activityCount: input.activities.length,
persona: input.persona
}
});
}
catch (error) {
return createErrorResult({
code: 'EXECUTION_ERROR',
message: `Failed to suggest triggers: ${error instanceof Error ? error.message : 'Unknown error'}`,
category: 'execution'
});
}
}
});
/**
* List process templates
*/
const listTemplatesTool = createTool({
name: 'process_list_templates',
description: 'List available process templates',
category: 'process-automation',
readOnly: true,
inputSchema: {
type: 'object',
properties: {},
required: [],
additionalProperties: false
},
async execute(input, context) {
try {
const result = await context.db.query('SELECT * FROM process_templates WHERE project_id = ? OR is_system = TRUE ORDER BY category, name', [context.projectId || 'default']);
if (!result.success) {
return createErrorResult({
code: 'DATABASE_ERROR',
message: 'Failed to list templates',
category: 'system'
});
}
const templates = (result.data || []).map((template) => ({
id: template.id,
name: template.name,
description: template.description,
category: template.category,
persona: template.persona,
isSystem: template.is_system,
variables: JSON.parse(template.variables || '{}'),
createdAt: new Date(template.created_at).toISOString()
}));
// Group by category
const templatesByCategory = templates.reduce((acc, template) => {
if (!acc[template.category]) {
acc[template.category] = [];
}
acc[template.category].push(template);
return acc;
}, {});
return createSuccessResult({
templates,
templatesByCategory,
count: templates.length
});
}
catch (error) {
return createErrorResult({
code: 'EXECUTION_ERROR',
message: `Failed to list templates: ${error instanceof Error ? error.message : 'Unknown error'}`,
category: 'execution'
});
}
}
});
/**
* Create process from template
*/
const createFromTemplateTool = createTool({
name: 'process_create_from_template',
description: 'Create a process from a template',
category: 'process-automation',
inputSchema: {
type: 'object',
properties: {
templateId: {
type: 'string',
description: 'Template ID',
pattern: '^[a-zA-Z0-9-]+$'
},
name: {
type: 'string',
description: 'Process name',
minLength: 1,
maxLength: 200
},
description: {
type: 'string',
description: 'Process description',
maxLength: 1000
},
variables: {
type: 'object',
description: 'Override template variables'
}
},
required: ['templateId', 'name'],
additionalProperties: false
},
async execute(input, context) {
try {
// Get template
const templateResult = await context.db.get('SELECT * FROM process_templates WHERE id = ? AND (project_id = ? OR is_system = TRUE)', [input.templateId, context.projectId || 'default']);
if (!templateResult.success || !templateResult.data) {
return createErrorResult({
code: 'RESOURCE_NOT_FOUND',
message: 'Template not found',
category: 'validation'
});
}
const template = templateResult.data;
const processDefinition = JSON.parse(template.process_definition);
const templateVariables = JSON.parse(template.variables || '{}');
// Merge variables
const finalVariables = { ...templateVariables, ...input.variables };
// Create process from template
const createInput = {
name: input.name,
description: input.description || template.description,
version: '1.0.0',
persona: template.persona,
category: template.category,
tags: processDefinition.tags || [],
triggers: processDefinition.triggers || [{
type: 'manual',
name: 'Manual Trigger',
enabled: true,
config: {}
}],
activities: processDefinition.activities,
variables: finalVariables,
onSuccess: processDefinition.onSuccess,
onFailure: processDefinition.onFailure
};
// Use the create process tool
return await createProcessTool.execute(createInput, context);
}
catch (error) {
return createErrorResult({
code: 'EXECUTION_ERROR',
message: `Failed to create from template: ${error instanceof Error ? error.message : 'Unknown error'}`,
category: 'execution'
});
}
}
});
/**
* Setup process automation tools
*/
export async function setupProcessAutomationTools() {
return {
module: 'process-automation',
tools: [
createProcessTool,
executeProcessTool,
listProcessesTool,
listExecutionsTool,
suggestTriggersTool,
listTemplatesTool,
createFromTemplateTool
]
};
}
//# sourceMappingURL=tools.js.map