@ebowwa/workflows 0.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 +257 -0
- package/dist/index.d.ts +47 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4641 -0
- package/dist/mcp/index.d.ts +57 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +17545 -0
- package/dist/runner.d.ts +62 -0
- package/dist/runner.d.ts.map +1 -0
- package/dist/sandbox.d.ts +67 -0
- package/dist/sandbox.d.ts.map +1 -0
- package/dist/scheduler.d.ts +93 -0
- package/dist/scheduler.d.ts.map +1 -0
- package/dist/types.d.ts +823 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +62 -0
- package/src/index.ts +72 -0
- package/src/mcp/index.ts +572 -0
- package/src/runner.ts +308 -0
- package/src/sandbox.ts +305 -0
- package/src/scheduler.ts +287 -0
- package/src/types.ts +218 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ebowwa/workflows",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"import": "./dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"./mcp": {
|
|
13
|
+
"types": "./dist/mcp/index.d.ts",
|
|
14
|
+
"import": "./dist/mcp/index.js"
|
|
15
|
+
},
|
|
16
|
+
"./types": {
|
|
17
|
+
"types": "./dist/types.d.ts",
|
|
18
|
+
"import": "./dist/types.js"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"src",
|
|
24
|
+
"README.md"
|
|
25
|
+
],
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "rm -rf dist && bun build ./src/index.ts ./src/mcp/index.ts --outdir ./dist --target bun --format esm && tsc --emitDeclarationOnly --declaration",
|
|
28
|
+
"dev": "bun run --watch src/index.ts",
|
|
29
|
+
"mcp": "bun run src/mcp/index.ts",
|
|
30
|
+
"server": "bun run server/index.ts",
|
|
31
|
+
"gui": "cd gui && bunx vite --port 3000",
|
|
32
|
+
"gui:build": "cd gui && bunx vite build",
|
|
33
|
+
"test": "bun test",
|
|
34
|
+
"typecheck": "tsc --noEmit"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@ebowwa/cron-notifier": "^0.7.0",
|
|
38
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
39
|
+
"react": "^18.2.0",
|
|
40
|
+
"react-dom": "^18.2.0",
|
|
41
|
+
"xterm": "^5.3.0",
|
|
42
|
+
"xterm-addon-fit": "^0.8.0",
|
|
43
|
+
"zod": "^3.24.0"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/bun": "latest",
|
|
47
|
+
"@types/react": "^18.2.0",
|
|
48
|
+
"@types/react-dom": "^18.2.0",
|
|
49
|
+
"@vitejs/plugin-react": "^4.2.0",
|
|
50
|
+
"typescript": "^5.0.0",
|
|
51
|
+
"vite": "^5.0.0"
|
|
52
|
+
},
|
|
53
|
+
"ownership": {
|
|
54
|
+
"domain": "automation",
|
|
55
|
+
"responsibilities": [
|
|
56
|
+
"ci-cd",
|
|
57
|
+
"workflows",
|
|
58
|
+
"job-scheduling",
|
|
59
|
+
"process-isolation"
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @ebowwa/workflows - CI/CD Workflow System
|
|
3
|
+
*
|
|
4
|
+
* A GitHub Actions-like workflow system with process-level isolation,
|
|
5
|
+
* job scheduling, and real-time event streaming.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { createRunner, WorkflowSchema } from '@ebowwa/workflows';
|
|
10
|
+
*
|
|
11
|
+
* const workflow = WorkflowSchema.parse({
|
|
12
|
+
* id: 'build-test',
|
|
13
|
+
* name: 'Build and Test',
|
|
14
|
+
* triggers: [{ type: 'manual' }],
|
|
15
|
+
* jobs: {
|
|
16
|
+
* build: {
|
|
17
|
+
* name: 'Build',
|
|
18
|
+
* runsOn: 'bun',
|
|
19
|
+
* steps: [
|
|
20
|
+
* { name: 'Install', run: 'bun install' },
|
|
21
|
+
* { name: 'Build', run: 'bun run build' },
|
|
22
|
+
* ],
|
|
23
|
+
* },
|
|
24
|
+
* test: {
|
|
25
|
+
* name: 'Test',
|
|
26
|
+
* runsOn: 'bun',
|
|
27
|
+
* needs: ['build'],
|
|
28
|
+
* steps: [
|
|
29
|
+
* { name: 'Test', run: 'bun test' },
|
|
30
|
+
* ],
|
|
31
|
+
* },
|
|
32
|
+
* },
|
|
33
|
+
* });
|
|
34
|
+
*
|
|
35
|
+
* const runner = createRunner();
|
|
36
|
+
* runner.on('event', (event) => console.log(event));
|
|
37
|
+
* runner.on('log', (log) => process.stdout.write(log.content));
|
|
38
|
+
*
|
|
39
|
+
* const run = await runner.runWorkflow(workflow);
|
|
40
|
+
* console.log(`Workflow ${run.status}`);
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
// Core types
|
|
45
|
+
export * from './types.js';
|
|
46
|
+
|
|
47
|
+
// Sandbox (process isolation)
|
|
48
|
+
export {
|
|
49
|
+
ProcessSandbox,
|
|
50
|
+
createSandbox,
|
|
51
|
+
evaluateCondition,
|
|
52
|
+
type SandboxOptions,
|
|
53
|
+
type ActiveProcess,
|
|
54
|
+
} from './sandbox.js';
|
|
55
|
+
|
|
56
|
+
// Scheduler (job dependencies)
|
|
57
|
+
export {
|
|
58
|
+
JobScheduler,
|
|
59
|
+
buildDependencyGraph,
|
|
60
|
+
createExecutionPlan,
|
|
61
|
+
getReadyJobs,
|
|
62
|
+
type JobNode,
|
|
63
|
+
type ExecutionPlan,
|
|
64
|
+
} from './scheduler.js';
|
|
65
|
+
|
|
66
|
+
// Runner (execution engine)
|
|
67
|
+
export {
|
|
68
|
+
WorkflowRunner,
|
|
69
|
+
createRunner,
|
|
70
|
+
type RunnerOptions,
|
|
71
|
+
type RunOptions,
|
|
72
|
+
} from './runner.js';
|
package/src/mcp/index.ts
ADDED
|
@@ -0,0 +1,572 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* MCP Server for Workflow Control
|
|
4
|
+
*
|
|
5
|
+
* Provides MCP tools for:
|
|
6
|
+
* - Listing and managing workflows
|
|
7
|
+
* - Triggering workflow runs
|
|
8
|
+
* - Checking run status
|
|
9
|
+
* - Canceling runs
|
|
10
|
+
* - Streaming logs
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
14
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
15
|
+
import {
|
|
16
|
+
CallToolRequestSchema,
|
|
17
|
+
ListToolsRequestSchema,
|
|
18
|
+
type Tool,
|
|
19
|
+
} from '@modelcontextprotocol/sdk/types.js';
|
|
20
|
+
import { z } from 'zod';
|
|
21
|
+
import { randomUUID } from 'node:crypto';
|
|
22
|
+
import {
|
|
23
|
+
WorkflowRunner,
|
|
24
|
+
createRunner,
|
|
25
|
+
WorkflowSchema,
|
|
26
|
+
type Workflow,
|
|
27
|
+
type WorkflowRun,
|
|
28
|
+
type Trigger,
|
|
29
|
+
type LogEntry,
|
|
30
|
+
} from '../index.js';
|
|
31
|
+
|
|
32
|
+
// ============================================================================
|
|
33
|
+
// Tool Definitions
|
|
34
|
+
// ============================================================================
|
|
35
|
+
|
|
36
|
+
const WORKFLOW_LIST_TOOL: Tool = {
|
|
37
|
+
name: 'workflow_list',
|
|
38
|
+
description: 'List all registered workflows',
|
|
39
|
+
inputSchema: {
|
|
40
|
+
type: 'object',
|
|
41
|
+
properties: {},
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const WORKFLOW_CREATE_TOOL: Tool = {
|
|
46
|
+
name: 'workflow_create',
|
|
47
|
+
description: 'Create a new workflow from a definition',
|
|
48
|
+
inputSchema: {
|
|
49
|
+
type: 'object',
|
|
50
|
+
properties: {
|
|
51
|
+
workflow: {
|
|
52
|
+
type: 'object',
|
|
53
|
+
description: 'Workflow definition (Workflow schema)',
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
required: ['workflow'],
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const WORKFLOW_GET_TOOL: Tool = {
|
|
61
|
+
name: 'workflow_get',
|
|
62
|
+
description: 'Get details of a specific workflow',
|
|
63
|
+
inputSchema: {
|
|
64
|
+
type: 'object',
|
|
65
|
+
properties: {
|
|
66
|
+
workflowId: {
|
|
67
|
+
type: 'string',
|
|
68
|
+
description: 'Workflow ID to retrieve',
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
required: ['workflowId'],
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const WORKFLOW_DELETE_TOOL: Tool = {
|
|
76
|
+
name: 'workflow_delete',
|
|
77
|
+
description: 'Delete a workflow',
|
|
78
|
+
inputSchema: {
|
|
79
|
+
type: 'object',
|
|
80
|
+
properties: {
|
|
81
|
+
workflowId: {
|
|
82
|
+
type: 'string',
|
|
83
|
+
description: 'Workflow ID to delete',
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
required: ['workflowId'],
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const WORKFLOW_RUN_TOOL: Tool = {
|
|
91
|
+
name: 'workflow_run',
|
|
92
|
+
description: 'Trigger a workflow run',
|
|
93
|
+
inputSchema: {
|
|
94
|
+
type: 'object',
|
|
95
|
+
properties: {
|
|
96
|
+
workflowId: {
|
|
97
|
+
type: 'string',
|
|
98
|
+
description: 'Workflow ID to run',
|
|
99
|
+
},
|
|
100
|
+
trigger: {
|
|
101
|
+
type: 'object',
|
|
102
|
+
description: 'Optional trigger configuration',
|
|
103
|
+
},
|
|
104
|
+
env: {
|
|
105
|
+
type: 'object',
|
|
106
|
+
description: 'Optional environment variables for this run',
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
required: ['workflowId'],
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const RUN_LIST_TOOL: Tool = {
|
|
114
|
+
name: 'run_list',
|
|
115
|
+
description: 'List all workflow runs',
|
|
116
|
+
inputSchema: {
|
|
117
|
+
type: 'object',
|
|
118
|
+
properties: {
|
|
119
|
+
workflowId: {
|
|
120
|
+
type: 'string',
|
|
121
|
+
description: 'Filter by workflow ID',
|
|
122
|
+
},
|
|
123
|
+
status: {
|
|
124
|
+
type: 'string',
|
|
125
|
+
enum: ['pending', 'running', 'success', 'failed', 'cancelled'],
|
|
126
|
+
description: 'Filter by status',
|
|
127
|
+
},
|
|
128
|
+
limit: {
|
|
129
|
+
type: 'number',
|
|
130
|
+
description: 'Maximum number of runs to return',
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const RUN_GET_TOOL: Tool = {
|
|
137
|
+
name: 'run_get',
|
|
138
|
+
description: 'Get details of a specific run',
|
|
139
|
+
inputSchema: {
|
|
140
|
+
type: 'object',
|
|
141
|
+
properties: {
|
|
142
|
+
runId: {
|
|
143
|
+
type: 'string',
|
|
144
|
+
description: 'Run ID to retrieve',
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
required: ['runId'],
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const RUN_CANCEL_TOOL: Tool = {
|
|
152
|
+
name: 'run_cancel',
|
|
153
|
+
description: 'Cancel a running workflow',
|
|
154
|
+
inputSchema: {
|
|
155
|
+
type: 'object',
|
|
156
|
+
properties: {
|
|
157
|
+
runId: {
|
|
158
|
+
type: 'string',
|
|
159
|
+
description: 'Run ID to cancel',
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
required: ['runId'],
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const RUN_LOGS_TOOL: Tool = {
|
|
167
|
+
name: 'run_logs',
|
|
168
|
+
description: 'Get logs for a workflow run',
|
|
169
|
+
inputSchema: {
|
|
170
|
+
type: 'object',
|
|
171
|
+
properties: {
|
|
172
|
+
runId: {
|
|
173
|
+
type: 'string',
|
|
174
|
+
description: 'Run ID to get logs for',
|
|
175
|
+
},
|
|
176
|
+
jobId: {
|
|
177
|
+
type: 'string',
|
|
178
|
+
description: 'Filter by job ID',
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
required: ['runId'],
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
// ============================================================================
|
|
186
|
+
// MCP Server Implementation
|
|
187
|
+
// ============================================================================
|
|
188
|
+
|
|
189
|
+
export interface WorkflowMCPOptions {
|
|
190
|
+
/** Default workspace directory */
|
|
191
|
+
workspace?: string;
|
|
192
|
+
/** Default environment variables */
|
|
193
|
+
env?: Record<string, string>;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export class WorkflowMCPServer {
|
|
197
|
+
private server: Server;
|
|
198
|
+
private runner: WorkflowRunner;
|
|
199
|
+
private workflows = new Map<string, Workflow>();
|
|
200
|
+
private runs = new Map<string, WorkflowRun>();
|
|
201
|
+
private logs = new Map<string, LogEntry[]>();
|
|
202
|
+
|
|
203
|
+
constructor(options: WorkflowMCPOptions = {}) {
|
|
204
|
+
this.runner = createRunner({
|
|
205
|
+
workspace: options.workspace,
|
|
206
|
+
env: options.env,
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// Collect logs for each run
|
|
210
|
+
this.runner.on('log', (entry: LogEntry) => {
|
|
211
|
+
const runLogs = this.logs.get(entry.stepId) ?? [];
|
|
212
|
+
runLogs.push(entry);
|
|
213
|
+
this.logs.set(entry.stepId, runLogs);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
this.server = new Server(
|
|
217
|
+
{
|
|
218
|
+
name: '@ebowwa/workflows-mcp',
|
|
219
|
+
version: '0.1.0',
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
capabilities: {
|
|
223
|
+
tools: {},
|
|
224
|
+
},
|
|
225
|
+
}
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
this.setupHandlers();
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
private setupHandlers(): void {
|
|
232
|
+
// List tools
|
|
233
|
+
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
234
|
+
return {
|
|
235
|
+
tools: [
|
|
236
|
+
WORKFLOW_LIST_TOOL,
|
|
237
|
+
WORKFLOW_CREATE_TOOL,
|
|
238
|
+
WORKFLOW_GET_TOOL,
|
|
239
|
+
WORKFLOW_DELETE_TOOL,
|
|
240
|
+
WORKFLOW_RUN_TOOL,
|
|
241
|
+
RUN_LIST_TOOL,
|
|
242
|
+
RUN_GET_TOOL,
|
|
243
|
+
RUN_CANCEL_TOOL,
|
|
244
|
+
RUN_LOGS_TOOL,
|
|
245
|
+
],
|
|
246
|
+
};
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// Handle tool calls
|
|
250
|
+
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
251
|
+
const { name, arguments: args } = request.params;
|
|
252
|
+
|
|
253
|
+
try {
|
|
254
|
+
switch (name) {
|
|
255
|
+
case 'workflow_list':
|
|
256
|
+
return await this.handleWorkflowList();
|
|
257
|
+
|
|
258
|
+
case 'workflow_create':
|
|
259
|
+
return await this.handleWorkflowCreate(args as { workflow: unknown });
|
|
260
|
+
|
|
261
|
+
case 'workflow_get':
|
|
262
|
+
return await this.handleWorkflowGet(args as { workflowId: string });
|
|
263
|
+
|
|
264
|
+
case 'workflow_delete':
|
|
265
|
+
return await this.handleWorkflowDelete(args as { workflowId: string });
|
|
266
|
+
|
|
267
|
+
case 'workflow_run':
|
|
268
|
+
return await this.handleWorkflowRun(args as {
|
|
269
|
+
workflowId: string;
|
|
270
|
+
trigger?: Trigger;
|
|
271
|
+
env?: Record<string, string>;
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
case 'run_list':
|
|
275
|
+
return await this.handleRunList(args as {
|
|
276
|
+
workflowId?: string;
|
|
277
|
+
status?: string;
|
|
278
|
+
limit?: number;
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
case 'run_get':
|
|
282
|
+
return await this.handleRunGet(args as { runId: string });
|
|
283
|
+
|
|
284
|
+
case 'run_cancel':
|
|
285
|
+
return await this.handleRunCancel(args as { runId: string });
|
|
286
|
+
|
|
287
|
+
case 'run_logs':
|
|
288
|
+
return await this.handleRunLogs(args as { runId: string; jobId?: string });
|
|
289
|
+
|
|
290
|
+
default:
|
|
291
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
292
|
+
}
|
|
293
|
+
} catch (error) {
|
|
294
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
295
|
+
return {
|
|
296
|
+
content: [{ type: 'text', text: `Error: ${message}` }],
|
|
297
|
+
isError: true,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Workflow handlers
|
|
304
|
+
private async handleWorkflowList() {
|
|
305
|
+
const workflows = Array.from(this.workflows.values()).map(w => ({
|
|
306
|
+
id: w.id,
|
|
307
|
+
name: w.name,
|
|
308
|
+
triggers: w.triggers,
|
|
309
|
+
jobCount: Object.keys(w.jobs).length,
|
|
310
|
+
}));
|
|
311
|
+
|
|
312
|
+
return {
|
|
313
|
+
content: [
|
|
314
|
+
{
|
|
315
|
+
type: 'text',
|
|
316
|
+
text: JSON.stringify({ workflows, total: workflows.length }, null, 2),
|
|
317
|
+
},
|
|
318
|
+
],
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
private async handleWorkflowCreate(args: { workflow: unknown }) {
|
|
323
|
+
const workflow = WorkflowSchema.parse(args.workflow);
|
|
324
|
+
|
|
325
|
+
if (this.workflows.has(workflow.id)) {
|
|
326
|
+
throw new Error(`Workflow already exists: ${workflow.id}`);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
this.workflows.set(workflow.id, workflow);
|
|
330
|
+
|
|
331
|
+
return {
|
|
332
|
+
content: [
|
|
333
|
+
{
|
|
334
|
+
type: 'text',
|
|
335
|
+
text: JSON.stringify({
|
|
336
|
+
success: true,
|
|
337
|
+
workflowId: workflow.id,
|
|
338
|
+
message: `Workflow '${workflow.name}' created`,
|
|
339
|
+
}, null, 2),
|
|
340
|
+
},
|
|
341
|
+
],
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
private async handleWorkflowGet(args: { workflowId: string }) {
|
|
346
|
+
const workflow = this.workflows.get(args.workflowId);
|
|
347
|
+
|
|
348
|
+
if (!workflow) {
|
|
349
|
+
throw new Error(`Workflow not found: ${args.workflowId}`);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return {
|
|
353
|
+
content: [
|
|
354
|
+
{
|
|
355
|
+
type: 'text',
|
|
356
|
+
text: JSON.stringify(workflow, null, 2),
|
|
357
|
+
},
|
|
358
|
+
],
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
private async handleWorkflowDelete(args: { workflowId: string }) {
|
|
363
|
+
const deleted = this.workflows.delete(args.workflowId);
|
|
364
|
+
|
|
365
|
+
return {
|
|
366
|
+
content: [
|
|
367
|
+
{
|
|
368
|
+
type: 'text',
|
|
369
|
+
text: JSON.stringify({
|
|
370
|
+
success: deleted,
|
|
371
|
+
message: deleted
|
|
372
|
+
? `Workflow ${args.workflowId} deleted`
|
|
373
|
+
: `Workflow ${args.workflowId} not found`,
|
|
374
|
+
}, null, 2),
|
|
375
|
+
},
|
|
376
|
+
],
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
private async handleWorkflowRun(args: {
|
|
381
|
+
workflowId: string;
|
|
382
|
+
trigger?: Trigger;
|
|
383
|
+
env?: Record<string, string>;
|
|
384
|
+
}) {
|
|
385
|
+
const workflow = this.workflows.get(args.workflowId);
|
|
386
|
+
|
|
387
|
+
if (!workflow) {
|
|
388
|
+
throw new Error(`Workflow not found: ${args.workflowId}`);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Run workflow asynchronously
|
|
392
|
+
const runPromise = this.runner.runWorkflow(workflow, {
|
|
393
|
+
trigger: args.trigger,
|
|
394
|
+
env: args.env,
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
// Store run when complete
|
|
398
|
+
runPromise.then(run => {
|
|
399
|
+
this.runs.set(run.id, run);
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
// Return immediately with run ID
|
|
403
|
+
const runId = randomUUID(); // This is a placeholder - actual ID generated by runner
|
|
404
|
+
|
|
405
|
+
return {
|
|
406
|
+
content: [
|
|
407
|
+
{
|
|
408
|
+
type: 'text',
|
|
409
|
+
text: JSON.stringify({
|
|
410
|
+
success: true,
|
|
411
|
+
runId,
|
|
412
|
+
workflowId: args.workflowId,
|
|
413
|
+
message: `Workflow '${workflow.name}' triggered`,
|
|
414
|
+
}, null, 2),
|
|
415
|
+
},
|
|
416
|
+
],
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Run handlers
|
|
421
|
+
private async handleRunList(args: {
|
|
422
|
+
workflowId?: string;
|
|
423
|
+
status?: string;
|
|
424
|
+
limit?: number;
|
|
425
|
+
}) {
|
|
426
|
+
let runs = Array.from(this.runs.values());
|
|
427
|
+
|
|
428
|
+
if (args.workflowId) {
|
|
429
|
+
runs = runs.filter(r => r.workflowId === args.workflowId);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if (args.status) {
|
|
433
|
+
runs = runs.filter(r => r.status === args.status);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
if (args.limit) {
|
|
437
|
+
runs = runs.slice(0, args.limit);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const summary = runs.map(r => ({
|
|
441
|
+
id: r.id,
|
|
442
|
+
workflowId: r.workflowId,
|
|
443
|
+
status: r.status,
|
|
444
|
+
startedAt: r.startedAt,
|
|
445
|
+
finishedAt: r.finishedAt,
|
|
446
|
+
}));
|
|
447
|
+
|
|
448
|
+
return {
|
|
449
|
+
content: [
|
|
450
|
+
{
|
|
451
|
+
type: 'text',
|
|
452
|
+
text: JSON.stringify({ runs: summary, total: summary.length }, null, 2),
|
|
453
|
+
},
|
|
454
|
+
],
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
private async handleRunGet(args: { runId: string }) {
|
|
459
|
+
const run = this.runs.get(args.runId);
|
|
460
|
+
|
|
461
|
+
if (!run) {
|
|
462
|
+
throw new Error(`Run not found: ${args.runId}`);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
return {
|
|
466
|
+
content: [
|
|
467
|
+
{
|
|
468
|
+
type: 'text',
|
|
469
|
+
text: JSON.stringify(run, null, 2),
|
|
470
|
+
},
|
|
471
|
+
],
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
private async handleRunCancel(args: { runId: string }) {
|
|
476
|
+
const cancelled = this.runner.cancelRun(args.runId);
|
|
477
|
+
|
|
478
|
+
return {
|
|
479
|
+
content: [
|
|
480
|
+
{
|
|
481
|
+
type: 'text',
|
|
482
|
+
text: JSON.stringify({
|
|
483
|
+
success: cancelled,
|
|
484
|
+
message: cancelled
|
|
485
|
+
? `Run ${args.runId} cancelled`
|
|
486
|
+
: `Run ${args.runId} not found or already complete`,
|
|
487
|
+
}, null, 2),
|
|
488
|
+
},
|
|
489
|
+
],
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
private async handleRunLogs(args: { runId: string; jobId?: string }) {
|
|
494
|
+
const run = this.runs.get(args.runId);
|
|
495
|
+
|
|
496
|
+
if (!run) {
|
|
497
|
+
throw new Error(`Run not found: ${args.runId}`);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// Get logs for all jobs or specific job
|
|
501
|
+
let logs: LogEntry[] = [];
|
|
502
|
+
|
|
503
|
+
if (args.jobId) {
|
|
504
|
+
const jobRun = run.jobs[args.jobId];
|
|
505
|
+
if (jobRun?.logs) {
|
|
506
|
+
logs = jobRun.logs;
|
|
507
|
+
}
|
|
508
|
+
} else {
|
|
509
|
+
// Collect all logs
|
|
510
|
+
for (const jobRun of Object.values(run.jobs)) {
|
|
511
|
+
if (jobRun.logs) {
|
|
512
|
+
logs.push(...jobRun.logs);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// Sort by timestamp
|
|
518
|
+
logs.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
|
|
519
|
+
|
|
520
|
+
return {
|
|
521
|
+
content: [
|
|
522
|
+
{
|
|
523
|
+
type: 'text',
|
|
524
|
+
text: JSON.stringify({ runId: args.runId, logs }, null, 2),
|
|
525
|
+
},
|
|
526
|
+
],
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* Start the MCP server
|
|
532
|
+
*/
|
|
533
|
+
async start(): Promise<void> {
|
|
534
|
+
const transport = new StdioServerTransport();
|
|
535
|
+
await this.server.connect(transport);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* Get the underlying runner for direct access
|
|
540
|
+
*/
|
|
541
|
+
getRunner(): WorkflowRunner {
|
|
542
|
+
return this.runner;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* Add a workflow programmatically
|
|
547
|
+
*/
|
|
548
|
+
addWorkflow(workflow: Workflow): void {
|
|
549
|
+
this.workflows.set(workflow.id, workflow);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* Get a workflow by ID
|
|
554
|
+
*/
|
|
555
|
+
getWorkflow(id: string): Workflow | undefined {
|
|
556
|
+
return this.workflows.get(id);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* Create and start an MCP server
|
|
562
|
+
*/
|
|
563
|
+
export async function createMCPServer(options?: WorkflowMCPOptions): Promise<WorkflowMCPServer> {
|
|
564
|
+
const server = new WorkflowMCPServer(options);
|
|
565
|
+
await server.start();
|
|
566
|
+
return server;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// Auto-start if run directly
|
|
570
|
+
if (import.meta.main) {
|
|
571
|
+
createMCPServer().catch(console.error);
|
|
572
|
+
}
|