/**
* Scheduler Internal MCP
*
* Provides tools for scheduling MCP tool executions with cron/launchd/Task Scheduler.
*/
import { InternalMCP, InternalTool, InternalToolResult } from './types.js';
import { Scheduler } from '../services/scheduler/scheduler.js';
import { logger } from '../utils/logger.js';
export class SchedulerMCP implements InternalMCP {
name = 'schedule';
description = 'Schedule MCP tool executions with cron (built-in, Unix/Linux/macOS only)';
/**
* Announce validation capability following MCP protocol
* This makes the scheduler a reference implementation for capability-based validation
*/
capabilities = {
experimental: {
toolValidation: {
supported: true,
method: 'validate'
}
}
};
private scheduler: Scheduler;
private orchestrator?: any; // NCPOrchestrator
constructor() {
this.scheduler = new Scheduler(); // No orchestrator yet - will be injected later
}
/**
* Set the orchestrator instance (called by MCP server after initialization)
*/
setOrchestrator(orchestrator: any): void {
logger.info('[SchedulerMCP] Orchestrator injected - re-creating scheduler with orchestrator');
this.orchestrator = orchestrator;
this.scheduler = new Scheduler(orchestrator);
}
tools: InternalTool[] = [
{
name: 'validate',
description: 'Validate tool parameters before scheduling (dry-run). REFERENCE IMPLEMENTATION for MCP validation protocol.',
inputSchema: {
type: 'object',
properties: {
tool: {
type: 'string',
description: 'Tool to validate in format "mcp:tool" (e.g., "filesystem:read_file")'
},
parameters: {
type: 'object',
description: 'Tool parameters to validate'
},
schedule: {
type: 'string',
description: 'Schedule to validate (optional). Supports: "in 5 minutes", "in 2 hours", "every day at 2pm", cron expressions, or RFC 3339 datetimes'
}
},
required: ['tool', 'parameters']
}
},
{
name: 'create',
description: 'Create scheduled job with cron/natural language timing.',
inputSchema: {
type: 'object',
properties: {
// SAME AS RUN
tool: {
type: 'string',
description: 'MCP tool (format: mcp:tool)'
},
parameters: {
type: 'object',
description: 'Tool parameters'
},
// SCHEDULER-SPECIFIC
name: {
type: 'string',
description: 'Job name'
},
schedule: {
type: 'string',
description: 'Schedule: "in 5min", "every day at 9am", cron (0 9 * * *), or RFC 3339'
},
timezone: {
type: 'string',
description: `IANA timezone (default: ${Intl.DateTimeFormat().resolvedOptions().timeZone}). Ignored for RFC 3339.`
},
active: {
type: 'boolean',
description: 'Start active (default: true) or paused',
default: true
},
// OPTIONAL
description: {
type: 'string',
description: 'Job description'
},
fireOnce: {
type: 'boolean',
description: 'Execute once then stop (default: false)',
default: false
},
maxExecutions: {
type: 'number',
description: 'Max executions before stopping'
},
endDate: {
type: 'string',
description: 'Stop after this date (ISO 8601)'
},
testRun: {
type: 'boolean',
description: 'Test execute before scheduling',
default: false
},
skipValidation: {
type: 'boolean',
description: 'Skip validation (not recommended)',
default: false
}
},
required: ['tool', 'parameters', 'name', 'schedule']
}
},
{
name: 'retrieve',
description: 'Get jobs and/or executions with search and filtering.',
inputSchema: {
type: 'object',
properties: {
include: {
type: 'string',
enum: ['jobs', 'executions', 'both'],
description: 'What to return: jobs, executions, or both',
default: 'jobs'
},
query: {
type: 'string',
description: 'Search term (omit for all)'
},
job_id: {
type: 'string',
description: 'Filter by job ID or name'
},
execution_id: {
type: 'string',
description: 'Get specific execution'
},
status: {
type: 'string',
enum: ['active', 'paused', 'completed', 'error', 'all', 'success', 'failure', 'timeout'],
description: 'Filter by status',
default: 'all'
},
page: {
type: 'number',
description: 'Page number',
default: 1
},
limit: {
type: 'number',
description: 'Max results per page',
default: 50
}
}
}
},
{
name: 'update',
description: 'Update job timing, parameters, or active state.',
inputSchema: {
type: 'object',
properties: {
job_id: {
type: 'string',
description: 'Job ID or name'
},
// Can update any of these
name: {
type: 'string',
description: 'New name'
},
schedule: {
type: 'string',
description: 'New schedule'
},
tool: {
type: 'string',
description: 'New tool'
},
parameters: {
type: 'object',
description: 'New parameters'
},
active: {
type: 'boolean',
description: 'Activate or pause'
},
description: {
type: 'string',
description: 'New description'
},
fireOnce: {
type: 'boolean',
description: 'New fireOnce'
},
maxExecutions: {
type: 'number',
description: 'New max executions'
},
endDate: {
type: 'string',
description: 'New end date'
}
},
required: ['job_id']
}
},
{
name: 'delete',
description: 'Delete scheduled job.',
inputSchema: {
type: 'object',
properties: {
job_id: {
type: 'string',
description: 'Job ID or name'
}
},
required: ['job_id']
}
},
{
name: 'list',
description: 'List all scheduled jobs.',
inputSchema: {
type: 'object',
properties: {
status: {
type: 'string',
enum: ['active', 'paused', 'completed', 'error', 'all'],
description: 'Filter by status',
default: 'all'
}
}
}
},
{
name: 'get',
description: 'Get job details.',
inputSchema: {
type: 'object',
properties: {
job_id: {
type: 'string',
description: 'Job ID or name'
}
},
required: ['job_id']
}
},
{
name: 'pause',
description: 'Pause job (stops future executions).',
inputSchema: {
type: 'object',
properties: {
job_id: {
type: 'string',
description: 'Job ID or name'
}
},
required: ['job_id']
}
},
{
name: 'resume',
description: 'Resume paused job.',
inputSchema: {
type: 'object',
properties: {
job_id: {
type: 'string',
description: 'Job ID or name'
}
},
required: ['job_id']
}
},
{
name: 'executions',
description: 'View execution history.',
inputSchema: {
type: 'object',
properties: {
job_id: {
type: 'string',
description: 'Filter by job ID or name (omit for all)'
},
status: {
type: 'string',
enum: ['success', 'failure', 'timeout', 'all'],
description: 'Filter by status',
default: 'all'
},
limit: {
type: 'number',
description: 'Max results',
default: 50
}
}
}
},
{
name: 'trigger',
description: 'Trigger an immediate execution of a scheduled job (manual run).',
inputSchema: {
type: 'object',
properties: {
job_id: {
type: 'string',
description: 'Job ID or name'
}
},
required: ['job_id']
}
}
];
async executeTool(toolName: string, args: any): Promise<InternalToolResult> {
try {
// Check if scheduler is available on this platform
if (!this.scheduler.isAvailable()) {
return {
success: false,
error: 'Schedule MCP not available on this platform',
content: [{
type: 'text',
text: 'β Schedule MCP not available on this platform (Windows not supported). Scheduling requires Unix/Linux/macOS with cron.'
}]
};
}
switch (toolName) {
case 'validate':
return await this.handleValidate(args);
case 'create':
return await this.handleCreate(args);
case 'retrieve':
return await this.handleRetrieve(args);
case 'update':
return await this.handleUpdate(args);
case 'delete':
return await this.handleDelete(args);
// CLI-style action tools
case 'list':
return await this.handleList(args);
case 'get':
return await this.handleGet(args);
case 'pause':
return await this.handlePause(args);
case 'resume':
return await this.handleResume(args);
case 'executions':
return await this.handleExecutions(args);
case 'trigger':
return await this.handleTrigger(args);
default:
return {
success: false,
error: `Unknown schedule tool: ${toolName}. Available: validate, create, list, get, retrieve, pause, resume, cancel, executions, trigger`,
content: [{
type: 'text',
text: `β Unknown schedule tool: ${toolName}\n\nπ Available tools: validate, create, list, get, retrieve, pause, resume, cancel, executions, trigger`
}]
};
}
} catch (error) {
logger.error(`[SchedulerMCP] Tool execution error: ${error instanceof Error ? error.message : String(error)}`);
const errorMessage = error instanceof Error ? error.message : String(error);
return {
success: false,
error: errorMessage,
content: [{
type: 'text',
text: `β Error: ${errorMessage}`
}]
};
}
}
/**
* REFERENCE IMPLEMENTATION: tools/validate
*
* This demonstrates capability-based validation for the MCP protocol.
* Validates the TOOL being scheduled (e.g., filesystem:read_file), not the schedule itself.
*/
private async handleValidate(args: any): Promise<InternalToolResult> {
const { tool, parameters, schedule } = args;
if (!tool || !parameters) {
return {
success: true,
content: JSON.stringify({
valid: false,
errors: ['Missing required parameters: tool and parameters'],
warnings: []
})
};
}
try {
const errors: string[] = [];
const warnings: string[] = [];
// Validate schedule if provided
if (schedule) {
const { NaturalLanguageParser } = await import('../services/scheduler/natural-language-parser.js');
const scheduleResult = NaturalLanguageParser.parseSchedule(schedule);
if (!scheduleResult.success) {
errors.push(`Invalid schedule: ${scheduleResult.error}`);
}
}
// Use ToolValidator to validate the tool being scheduled
const { ToolValidator } = await import('../services/scheduler/tool-validator.js');
const validator = new ToolValidator(this.orchestrator);
const result = await validator.validateTool(tool, parameters);
return {
success: true,
content: JSON.stringify({
valid: result.valid && errors.length === 0,
errors: [...errors, ...result.errors],
warnings: [...warnings, ...result.warnings],
validationMethod: result.validationMethod,
schema: result.schema
})
};
} catch (error) {
return {
success: true,
content: JSON.stringify({
valid: false,
errors: [`Validation error: ${error instanceof Error ? error.message : String(error)}`],
warnings: []
})
};
}
}
/**
* Create a new scheduled job
* Uses same parameters as run + schedule + active flag
*/
private async handleCreate(args: any): Promise<InternalToolResult> {
try {
// Default active to true if not provided
const active = args.active !== false;
const job = await this.scheduler.createJob({
name: args.name,
schedule: args.schedule,
timezone: args.timezone, // IANA timezone or defaults to system
tool: args.tool,
parameters: args.parameters,
description: args.description,
fireOnce: args.fireOnce,
maxExecutions: args.maxExecutions,
endDate: args.endDate,
testRun: args.testRun,
skipValidation: args.skipValidation
});
// If created as paused, pause it immediately
if (!active) {
this.scheduler.pauseJob(job.id);
}
let successMessage = `β
Scheduled job created successfully!\n\n` +
`π Job Details:\n` +
` β’ Name: ${job.name}\n` +
` β’ ID: ${job.id}\n` +
` β’ Tool: ${job.tool}\n` +
` β’ Schedule: ${job.cronExpression}\n` +
`${job.timezone ? ` β’ Timezone: ${job.timezone}\n` : ''}` +
` β’ Status: ${active ? 'active' : 'paused'}\n` +
` β’ Type: ${job.fireOnce ? 'One-time' : 'Recurring'}\n` +
`${job.description ? ` β’ Description: ${job.description}\n` : ''}` +
`${job.maxExecutions ? ` β’ Max Executions: ${job.maxExecutions}\n` : ''}` +
`${job.endDate ? ` β’ End Date: ${job.endDate}\n` : ''}`;
// Add validation info
if (args.testRun) {
successMessage += `\nβ
Test execution completed successfully - parameters validated\n`;
} else if (!args.skipValidation) {
successMessage += `\nβ
Parameters validated against tool schema\n`;
}
if (active) {
successMessage += `\nπ‘ The job will execute automatically according to its schedule.\n`;
} else {
successMessage += `\nβΈοΈ Job created in paused state. Use update with active:true to start execution.\n`;
}
successMessage += `π Use retrieve with job_id="${job.id}" to monitor execution results.`;
return {
success: true,
content: [{
type: 'text',
text: successMessage
}]
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
success: false,
error: errorMessage,
content: [{
type: 'text',
text: `β Failed to create scheduled job\n\n` +
`Error: ${errorMessage}\n\n` +
`π‘ Tips:\n` +
` β’ Verify the tool exists: use find to search\n` +
` β’ Check parameter types match the tool's schema\n` +
` β’ Use testRun: true to verify parameters before scheduling\n` +
` β’ Use validate tool to dry-run test parameters`
}]
};
}
}
/**
* Retrieve jobs and/or executions with search and filtering
* Unified retrieval for all scheduler data
*/
private async handleRetrieve(args: any): Promise<InternalToolResult> {
const include = args.include || 'jobs';
const query = args.query;
const jobId = args.job_id;
const executionId = args.execution_id;
const status = args.status || 'all';
const limit = args.limit || 50;
let result = '';
try {
// Handle specific execution lookup
if (executionId) {
return this.getExecutionDetails(executionId);
}
// Handle specific job lookup
if (jobId && include === 'jobs') {
return this.getJobDetails(jobId);
}
// Handle jobs
if (include === 'jobs' || include === 'both') {
const jobsResult = await this.retrieveJobs({ query, jobId, status, limit });
result += jobsResult;
}
// Handle executions
if (include === 'executions' || include === 'both') {
if (result) result += '\n\n';
const execResult = await this.retrieveExecutions({ query, jobId, status, limit });
result += execResult;
}
return {
success: true,
content: [{
type: 'text',
text: result || 'No results found matching the criteria.'
}]
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
success: false,
error: errorMessage,
content: [{
type: 'text',
text: `β Retrieve failed: ${errorMessage}`
}]
};
}
}
/**
* Update existing job (including active state for pause/resume)
*/
private async handleUpdate(args: any): Promise<InternalToolResult> {
let jobId = args.job_id;
// Try to find by name if not found by ID
let job = this.scheduler.getJob(jobId);
if (!job) {
job = this.scheduler.getJobByName(jobId);
if (job) {
jobId = job.id;
}
}
if (!job) {
return {
success: false,
error: `Job not found: ${args.job_id}`,
content: [{
type: 'text',
text: `β Job not found: ${args.job_id}`
}]
};
}
try {
// Handle active state change (pause/resume)
if (args.active !== undefined) {
if (args.active) {
this.scheduler.resumeJob(jobId);
} else {
this.scheduler.pauseJob(jobId);
}
}
// Update other fields if provided
if (args.name || args.schedule || args.tool || args.parameters ||
args.description !== undefined || args.fireOnce !== undefined ||
args.maxExecutions !== undefined || args.endDate !== undefined) {
const updatedJob = await this.scheduler.updateJob(jobId, {
name: args.name,
schedule: args.schedule,
tool: args.tool,
parameters: args.parameters,
description: args.description,
fireOnce: args.fireOnce,
maxExecutions: args.maxExecutions,
endDate: args.endDate
});
return {
success: true,
content: [{
type: 'text',
text: `β
Job updated successfully!\n\n` +
`π ${updatedJob.name}\n` +
` β’ Schedule: ${updatedJob.cronExpression}\n` +
` β’ Tool: ${updatedJob.tool}\n` +
` β’ Status: ${updatedJob.status}`
}]
};
} else {
// Only status change
const updatedJob = this.scheduler.getJob(jobId)!;
return {
success: true,
content: [{
type: 'text',
text: `β
Job ${args.active ? 'resumed' : 'paused'}: ${updatedJob.name}\n\n` +
`Status: ${updatedJob.status}${args.active ? ' - will execute according to schedule' : ' - execution stopped'}`
}]
};
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
success: false,
error: errorMessage,
content: [{
type: 'text',
text: `β Update failed: ${errorMessage}`
}]
};
}
}
/**
* Delete a scheduled job permanently
*/
private async handleDelete(args: any): Promise<InternalToolResult> {
let jobId = args.job_id;
let job = this.scheduler.getJob(jobId);
if (!job) {
job = this.scheduler.getJobByName(jobId);
if (job) jobId = job.id;
}
if (!job) {
return {
success: false,
error: `Job not found: ${args.job_id}`,
content: [{
type: 'text',
text: `β Job not found: ${args.job_id}`
}]
};
}
try {
this.scheduler.deleteJob(jobId);
return {
success: true,
content: [{
type: 'text',
text: `β
Job deleted: ${job.name}\n\nThe job has been permanently removed from the schedule.`
}]
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
success: false,
error: errorMessage,
content: [{
type: 'text',
text: `β Delete failed: ${errorMessage}`
}]
};
}
}
// Helper: Retrieve jobs with filtering
private async retrieveJobs(filters: { query?: string; jobId?: string; status?: string; limit?: number }): Promise<string> {
const statusFilter = filters.status === 'all' ? undefined : filters.status as 'active' | 'paused' | 'completed' | 'error' | undefined;
let jobs = this.scheduler.listJobs(statusFilter);
// Filter by query
if (filters.query) {
const q = filters.query.toLowerCase();
jobs = jobs.filter(job =>
job.name.toLowerCase().includes(q) ||
job.tool.toLowerCase().includes(q) ||
job.description?.toLowerCase().includes(q)
);
}
// Filter by job ID
if (filters.jobId) {
const job = this.scheduler.getJob(filters.jobId) || this.scheduler.getJobByName(filters.jobId);
jobs = job ? [job] : [];
}
// Apply limit
const limit = filters.limit || 50;
const limited = jobs.slice(0, limit);
if (limited.length === 0) {
return 'π No jobs found matching the criteria.';
}
const jobsList = limited.map(job => {
const execInfo = job.executionCount > 0
? `Executed ${job.executionCount} time${job.executionCount === 1 ? '' : 's'}`
: 'Not yet executed';
return `β’ ${job.name} (${job.status})\n` +
` ID: ${job.id}\n` +
` Tool: ${job.tool}\n` +
` Schedule: ${job.cronExpression}\n` +
` ${execInfo}\n` +
`${job.lastExecutionAt ? ` Last: ${job.lastExecutionAt}\n` : ''}`;
}).join('\n');
const stats = this.scheduler.getJobStatistics();
return `π Scheduled Jobs (${limited.length} of ${jobs.length})\n\n` +
jobsList +
`\nπ Statistics:\n` +
` β’ Active: ${stats.active}\n` +
` β’ Paused: ${stats.paused}\n` +
` β’ Completed: ${stats.completed}\n` +
` β’ Error: ${stats.error}`;
}
// Helper: Retrieve executions with filtering
private async retrieveExecutions(filters: { query?: string; jobId?: string; status?: string; limit?: number }): Promise<string> {
let jobId = filters.jobId;
if (jobId) {
let job = this.scheduler.getJob(jobId);
if (!job) {
job = this.scheduler.getJobByName(jobId);
if (job) jobId = job.id;
}
}
const executions = this.scheduler.queryExecutions({
jobId,
status: filters.status === 'all' ? undefined : filters.status
});
// Filter by query
let filtered = executions;
if (filters.query) {
const q = filters.query.toLowerCase();
filtered = executions.filter(exec =>
(exec.jobName || exec.taskName || '').toLowerCase().includes(q) ||
exec.tool.toLowerCase().includes(q)
);
}
const limit = filters.limit || 50;
const limited = filtered.slice(0, limit);
if (limited.length === 0) {
return 'π No executions found matching the criteria.';
}
const executionsList = limited.map(exec => {
const status = exec.status === 'success' ? 'β
' : exec.status === 'failure' ? 'β' : 'β±οΈ';
const duration = exec.duration ? `${exec.duration}ms` : 'N/A';
return `${status} ${exec.jobName}\n` +
` ID: ${exec.executionId}\n` +
` Time: ${exec.startedAt}\n` +
` Duration: ${duration}\n` +
`${exec.errorMessage ? ` Error: ${exec.errorMessage}\n` : ''}`;
}).join('\n');
return `π Executions (${limited.length} of ${filtered.length})\n\n${executionsList}`;
}
// Helper: Get detailed job info
private getJobDetails(jobId: string): InternalToolResult {
let job = this.scheduler.getJob(jobId);
if (!job) {
job = this.scheduler.getJobByName(jobId);
}
if (!job) {
return {
success: false,
error: `Job not found: ${jobId}`,
content: [{
type: 'text',
text: `β Job not found: ${jobId}`
}]
};
}
const execStats = this.scheduler.getExecutionStatistics(job.id);
return {
success: true,
content: [{
type: 'text',
text: `π Job: ${job.name}\n\n` +
`π ID: ${job.id}\n` +
`π§ Tool: ${job.tool}\n` +
`β° Schedule: ${job.cronExpression}\n` +
`π Status: ${job.status}\n` +
`π Type: ${job.fireOnce ? 'One-time' : 'Recurring'}\n` +
`${job.description ? `π Description: ${job.description}\n` : ''}` +
`${job.maxExecutions ? `π’ Max Executions: ${job.maxExecutions}\n` : ''}` +
`${job.endDate ? `π
End Date: ${job.endDate}\n` : ''}` +
`\nπ Execution Statistics:\n` +
` β’ Total: ${execStats.total}\n` +
` β’ Success: ${execStats.success}\n` +
` β’ Failure: ${execStats.failure}\n` +
` β’ Timeout: ${execStats.timeout}\n` +
`${execStats.avgDuration ? ` β’ Avg Duration: ${Math.round(execStats.avgDuration)}ms\n` : ''}` +
`\nβοΈ Parameters:\n${JSON.stringify(job.parameters, null, 2)}`
}]
};
}
// Helper: Get execution details
private getExecutionDetails(executionId: string): InternalToolResult {
const execution = this.scheduler.executionRecorder.getExecution(executionId);
if (!execution) {
return {
success: false,
error: `Execution not found: ${executionId}`,
content: [{
type: 'text',
text: `β Execution not found: ${executionId}`
}]
};
}
const status = execution.status === 'success' ? 'β
Success' :
execution.status === 'failure' ? 'β Failure' :
execution.status === 'timeout' ? 'β±οΈ Timeout' : 'π Running';
return {
success: true,
content: [{
type: 'text',
text: `π Execution: ${execution.executionId}\n\n` +
`π Job: ${execution.jobName} (${execution.jobId})\n` +
`π§ Tool: ${execution.tool}\n` +
`π Status: ${status}\n` +
`β° Started: ${execution.startedAt}\n` +
`${execution.completedAt ? `β
Completed: ${execution.completedAt}\n` : ''}` +
`${execution.duration ? `β±οΈ Duration: ${execution.duration}ms\n` : ''}` +
`\nβοΈ Parameters:\n${JSON.stringify(execution.parameters, null, 2)}\n` +
`${execution.result ? `\nπ€ Result:\n${JSON.stringify(execution.result, null, 2)}` : ''}` +
`${execution.error ? `\nβ Error: ${execution.error.message}` : ''}`
}]
};
}
/**
* List all jobs (simpler alternative to retrieve)
* Delegates to handleRetrieve with include='jobs'
*/
private async handleList(args: any): Promise<InternalToolResult> {
return await this.handleRetrieve({
include: 'jobs',
status: args?.status || 'all'
});
}
/**
* Get specific job details
* Delegates to handleRetrieve with job_id filter
*/
private async handleGet(args: any): Promise<InternalToolResult> {
if (!args?.job_id) {
return {
success: false,
error: 'job_id parameter is required',
content: [{
type: 'text',
text: 'β job_id parameter is required'
}]
};
}
return await this.handleRetrieve({
include: 'jobs',
job_id: args.job_id
});
}
/**
* Pause a job (set active=false)
* Delegates to handleUpdate with active=false
*/
private async handlePause(args: any): Promise<InternalToolResult> {
if (!args?.job_id) {
return {
success: false,
error: 'job_id parameter is required',
content: [{
type: 'text',
text: 'β job_id parameter is required'
}]
};
}
return await this.handleUpdate({
job_id: args.job_id,
active: false
});
}
/**
* Resume a job (set active=true)
* Delegates to handleUpdate with active=true
*/
private async handleResume(args: any): Promise<InternalToolResult> {
if (!args?.job_id) {
return {
success: false,
error: 'job_id parameter is required',
content: [{
type: 'text',
text: 'β job_id parameter is required'
}]
};
}
return await this.handleUpdate({
job_id: args.job_id,
active: true
});
}
/**
* View execution history
* Delegates to handleRetrieve with include='executions'
*/
private async handleExecutions(args: any): Promise<InternalToolResult> {
return await this.handleRetrieve({
include: 'executions',
job_id: args?.job_id,
status: args?.status || 'all',
limit: args?.limit || 50
});
}
/**
* Trigger immediate execution of a job
*/
private async handleTrigger(args: any): Promise<InternalToolResult> {
if (!args?.job_id) {
return {
success: false,
error: 'job_id parameter is required',
content: [{
type: 'text',
text: 'β job_id parameter is required'
}]
};
}
let jobId = args.job_id;
let job = this.scheduler.getJob(jobId);
if (!job) {
job = this.scheduler.getJobByName(jobId);
if (job) jobId = job.id;
}
if (!job) {
return {
success: false,
error: `Job not found: ${args.job_id}`,
content: [{
type: 'text',
text: `β Job not found: ${args.job_id}`
}]
};
}
try {
const result = await this.scheduler.runTaskNow(jobId);
const statusIcon = result.status === 'success' ? 'β
' : 'β';
const duration = result.duration ? `${result.duration}ms` : 'N/A';
let message = `${statusIcon} Manual execution completed: ${job.name}\n\n` +
` ID: ${result.executionId}\n` +
` Status: ${result.status}\n` +
` Duration: ${duration}\n`;
if (result.result) {
message += `\nπ€ Result:\n${result.result}`;
}
if (result.error) {
message += `\nβ Error:\n${result.error}`;
}
return {
success: result.status === 'success',
content: [{
type: 'text',
text: message
}]
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
success: false,
error: errorMessage,
content: [{
type: 'text',
text: `β Trigger failed: ${errorMessage}`
}]
};
}
}
}