@frontmcp/skills 0.0.1

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.
Files changed (65) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +135 -0
  3. package/catalog/TEMPLATE.md +49 -0
  4. package/catalog/adapters/create-adapter/SKILL.md +127 -0
  5. package/catalog/adapters/official-adapters/SKILL.md +136 -0
  6. package/catalog/auth/configure-auth/SKILL.md +250 -0
  7. package/catalog/auth/configure-auth/references/auth-modes.md +77 -0
  8. package/catalog/auth/configure-session/SKILL.md +201 -0
  9. package/catalog/config/configure-elicitation/SKILL.md +136 -0
  10. package/catalog/config/configure-http/SKILL.md +167 -0
  11. package/catalog/config/configure-throttle/SKILL.md +189 -0
  12. package/catalog/config/configure-throttle/references/guard-config.md +68 -0
  13. package/catalog/config/configure-transport/SKILL.md +151 -0
  14. package/catalog/config/configure-transport/references/protocol-presets.md +57 -0
  15. package/catalog/deployment/build-for-browser/SKILL.md +95 -0
  16. package/catalog/deployment/build-for-cli/SKILL.md +100 -0
  17. package/catalog/deployment/build-for-sdk/SKILL.md +218 -0
  18. package/catalog/deployment/deploy-to-cloudflare/SKILL.md +192 -0
  19. package/catalog/deployment/deploy-to-lambda/SKILL.md +304 -0
  20. package/catalog/deployment/deploy-to-node/SKILL.md +229 -0
  21. package/catalog/deployment/deploy-to-node/references/Dockerfile.example +45 -0
  22. package/catalog/deployment/deploy-to-vercel/SKILL.md +196 -0
  23. package/catalog/deployment/deploy-to-vercel/references/vercel.json.example +60 -0
  24. package/catalog/development/create-agent/SKILL.md +563 -0
  25. package/catalog/development/create-agent/references/llm-config.md +46 -0
  26. package/catalog/development/create-job/SKILL.md +566 -0
  27. package/catalog/development/create-prompt/SKILL.md +400 -0
  28. package/catalog/development/create-provider/SKILL.md +233 -0
  29. package/catalog/development/create-resource/SKILL.md +437 -0
  30. package/catalog/development/create-skill/SKILL.md +526 -0
  31. package/catalog/development/create-skill-with-tools/SKILL.md +579 -0
  32. package/catalog/development/create-tool/SKILL.md +418 -0
  33. package/catalog/development/create-tool/references/output-schema-types.md +56 -0
  34. package/catalog/development/create-tool/references/tool-annotations.md +34 -0
  35. package/catalog/development/create-workflow/SKILL.md +709 -0
  36. package/catalog/development/decorators-guide/SKILL.md +598 -0
  37. package/catalog/plugins/create-plugin/SKILL.md +336 -0
  38. package/catalog/plugins/create-plugin-hooks/SKILL.md +282 -0
  39. package/catalog/plugins/official-plugins/SKILL.md +667 -0
  40. package/catalog/setup/frontmcp-skills-usage/SKILL.md +200 -0
  41. package/catalog/setup/multi-app-composition/SKILL.md +358 -0
  42. package/catalog/setup/nx-workflow/SKILL.md +357 -0
  43. package/catalog/setup/project-structure-nx/SKILL.md +186 -0
  44. package/catalog/setup/project-structure-standalone/SKILL.md +153 -0
  45. package/catalog/setup/setup-project/SKILL.md +493 -0
  46. package/catalog/setup/setup-redis/SKILL.md +385 -0
  47. package/catalog/setup/setup-sqlite/SKILL.md +359 -0
  48. package/catalog/skills-manifest.json +414 -0
  49. package/catalog/testing/setup-testing/SKILL.md +539 -0
  50. package/catalog/testing/setup-testing/references/test-auth.md +88 -0
  51. package/catalog/testing/setup-testing/references/test-browser-build.md +57 -0
  52. package/catalog/testing/setup-testing/references/test-cli-binary.md +48 -0
  53. package/catalog/testing/setup-testing/references/test-direct-client.md +62 -0
  54. package/catalog/testing/setup-testing/references/test-e2e-handler.md +51 -0
  55. package/catalog/testing/setup-testing/references/test-tool-unit.md +41 -0
  56. package/package.json +34 -0
  57. package/src/index.d.ts +3 -0
  58. package/src/index.js +16 -0
  59. package/src/index.js.map +1 -0
  60. package/src/loader.d.ts +46 -0
  61. package/src/loader.js +75 -0
  62. package/src/loader.js.map +1 -0
  63. package/src/manifest.d.ts +81 -0
  64. package/src/manifest.js +26 -0
  65. package/src/manifest.js.map +1 -0
@@ -0,0 +1,709 @@
1
+ ---
2
+ name: create-workflow
3
+ description: Create multi-step workflows that connect jobs into managed execution pipelines with dependencies and conditions. Use when orchestrating sequential or parallel job execution.
4
+ tags: [workflow, pipeline, orchestration, steps, jobs]
5
+ priority: 6
6
+ visibility: both
7
+ license: Apache-2.0
8
+ metadata:
9
+ docs: https://docs.agentfront.dev/frontmcp/servers/workflows
10
+ ---
11
+
12
+ # Creating Workflows
13
+
14
+ Workflows connect multiple jobs into managed execution pipelines with step dependencies, conditions, and triggers. A workflow defines a directed acyclic graph (DAG) of steps where each step runs a named job, and the framework handles ordering, parallelism, error propagation, and trigger management.
15
+
16
+ ## When to Use @Workflow
17
+
18
+ Use `@Workflow` when you need to orchestrate multiple jobs in a defined order with dependencies between them. Examples include:
19
+
20
+ - CI/CD pipelines (build, test, deploy)
21
+ - Data processing pipelines (extract, transform, load, verify)
22
+ - Approval workflows (submit, review, approve, execute)
23
+ - Multi-stage provisioning (create resources, configure, validate, notify)
24
+
25
+ If you only need a single background task, use a `@Job` instead. If you need real-time sequential tool calls guided by an AI, use a `@Skill`.
26
+
27
+ ## Class-Based Pattern
28
+
29
+ Create a class decorated with `@Workflow`. The decorator requires `name` and `steps` (at least one step).
30
+
31
+ ### WorkflowMetadata Fields
32
+
33
+ | Field | Type | Required | Default | Description |
34
+ | ---------------- | ---------------------------------- | ----------- | ----------------- | ----------------------------------------------------- |
35
+ | `name` | `string` | Yes | -- | Unique workflow name |
36
+ | `steps` | `WorkflowStep[]` | Yes (min 1) | -- | Array of step definitions |
37
+ | `description` | `string` | No | -- | Human-readable description |
38
+ | `trigger` | `'manual' \| 'webhook' \| 'event'` | No | `'manual'` | How the workflow is initiated |
39
+ | `webhook` | `WebhookConfig` | No | -- | Webhook configuration (when trigger is `'webhook'`) |
40
+ | `timeout` | `number` | No | `600000` (10 min) | Maximum total workflow execution time in milliseconds |
41
+ | `maxConcurrency` | `number` | No | `5` | Maximum number of steps running in parallel |
42
+ | `permissions` | `WorkflowPermissions` | No | -- | Access control configuration |
43
+
44
+ ### WorkflowStep Fields
45
+
46
+ | Field | Type | Required | Description |
47
+ | ----------------- | ------------------------------------------ | -------- | --------------------------------------------------------------------------- |
48
+ | `id` | `string` | Yes | Unique step identifier within the workflow |
49
+ | `jobName` | `string` | Yes | Name of the registered job to run |
50
+ | `input` | `object \| (steps: StepResults) => object` | No | Static input object or function that receives previous step results |
51
+ | `dependsOn` | `string[]` | No | Array of step IDs that must complete before this step runs |
52
+ | `condition` | `(steps: StepResults) => boolean` | No | Predicate that determines if the step should run |
53
+ | `continueOnError` | `boolean` | No | If `true`, workflow continues even if this step fails |
54
+ | `timeout` | `number` | No | Per-step timeout in milliseconds (overrides workflow timeout for this step) |
55
+ | `retry` | `RetryPolicy` | No | Per-step retry policy (overrides the job's retry policy for this step) |
56
+
57
+ ### Basic Example
58
+
59
+ ```typescript
60
+ import { Workflow } from '@frontmcp/sdk';
61
+
62
+ @Workflow({
63
+ name: 'deploy-pipeline',
64
+ description: 'Build, test, and deploy a service',
65
+ steps: [
66
+ {
67
+ id: 'build',
68
+ jobName: 'build-project',
69
+ input: { target: 'production', optimize: true },
70
+ },
71
+ {
72
+ id: 'test',
73
+ jobName: 'run-tests',
74
+ input: { suite: 'all', coverage: true },
75
+ dependsOn: ['build'],
76
+ },
77
+ {
78
+ id: 'deploy',
79
+ jobName: 'deploy-to-env',
80
+ input: (steps) => ({
81
+ artifact: steps.get('build').outputs.artifactUrl,
82
+ environment: 'production',
83
+ }),
84
+ dependsOn: ['test'],
85
+ },
86
+ ],
87
+ })
88
+ class DeployPipeline {}
89
+ ```
90
+
91
+ ## Step Dependencies and DAG Execution
92
+
93
+ Steps form a directed acyclic graph (DAG) based on their `dependsOn` declarations. The framework:
94
+
95
+ 1. Identifies steps with no dependencies and runs them in parallel (up to `maxConcurrency`)
96
+ 2. As each step completes, checks which dependent steps have all their dependencies satisfied
97
+ 3. Runs newly unblocked steps in parallel
98
+ 4. Continues until all steps complete or a step fails (unless `continueOnError` is set)
99
+
100
+ ### Parallel Steps
101
+
102
+ Steps without mutual dependencies run concurrently:
103
+
104
+ ```typescript
105
+ @Workflow({
106
+ name: 'data-validation-pipeline',
107
+ description: 'Validate data from multiple sources in parallel, then merge',
108
+ maxConcurrency: 3,
109
+ steps: [
110
+ // These three steps have no dependencies -- they run in parallel
111
+ {
112
+ id: 'validate-users',
113
+ jobName: 'validate-dataset',
114
+ input: { dataset: 'users', rules: ['no-nulls', 'email-format'] },
115
+ },
116
+ {
117
+ id: 'validate-orders',
118
+ jobName: 'validate-dataset',
119
+ input: { dataset: 'orders', rules: ['no-nulls', 'positive-amounts'] },
120
+ },
121
+ {
122
+ id: 'validate-products',
123
+ jobName: 'validate-dataset',
124
+ input: { dataset: 'products', rules: ['no-nulls', 'unique-sku'] },
125
+ },
126
+ // This step depends on all three -- runs after all complete
127
+ {
128
+ id: 'merge-results',
129
+ jobName: 'merge-validations',
130
+ dependsOn: ['validate-users', 'validate-orders', 'validate-products'],
131
+ input: (steps) => ({
132
+ userReport: steps.get('validate-users').outputs,
133
+ orderReport: steps.get('validate-orders').outputs,
134
+ productReport: steps.get('validate-products').outputs,
135
+ }),
136
+ },
137
+ ],
138
+ })
139
+ class DataValidationPipeline {}
140
+ ```
141
+
142
+ ### Diamond Dependencies
143
+
144
+ Steps can share dependencies, forming diamond patterns:
145
+
146
+ ```typescript
147
+ @Workflow({
148
+ name: 'build-and-publish',
149
+ description: 'Build artifacts and publish to multiple registries',
150
+ steps: [
151
+ { id: 'compile', jobName: 'compile-source', input: { target: 'es2022' } },
152
+ {
153
+ id: 'publish-npm',
154
+ jobName: 'publish-to-registry',
155
+ dependsOn: ['compile'],
156
+ input: (steps) => ({ artifact: steps.get('compile').outputs.bundlePath, registry: 'npm' }),
157
+ },
158
+ {
159
+ id: 'publish-docker',
160
+ jobName: 'publish-to-registry',
161
+ dependsOn: ['compile'],
162
+ input: (steps) => ({ artifact: steps.get('compile').outputs.bundlePath, registry: 'docker' }),
163
+ },
164
+ {
165
+ id: 'notify',
166
+ jobName: 'send-notification',
167
+ dependsOn: ['publish-npm', 'publish-docker'],
168
+ input: (steps) => ({
169
+ message: `Published to npm (${steps.get('publish-npm').outputs.version}) and Docker (${steps.get('publish-docker').outputs.tag})`,
170
+ }),
171
+ },
172
+ ],
173
+ })
174
+ class BuildAndPublish {}
175
+ ```
176
+
177
+ ## Dynamic Input from Previous Steps
178
+
179
+ Use a function for `input` to pass data from completed steps. The function receives a `StepResults` map where each entry contains the step's state and outputs.
180
+
181
+ ```typescript
182
+ {
183
+ id: 'transform',
184
+ jobName: 'transform-data',
185
+ dependsOn: ['extract'],
186
+ input: (steps) => ({
187
+ data: steps.get('extract').outputs.records,
188
+ schema: steps.get('extract').outputs.schema,
189
+ rowCount: steps.get('extract').outputs.count,
190
+ }),
191
+ }
192
+ ```
193
+
194
+ The `steps.get(stepId)` method returns a step result object:
195
+
196
+ ```typescript
197
+ interface StepResult {
198
+ state: 'pending' | 'running' | 'completed' | 'failed' | 'skipped';
199
+ outputs: Record<string, unknown>; // job output from the completed step
200
+ error?: string; // error message if the step failed
201
+ startedAt?: string;
202
+ completedAt?: string;
203
+ }
204
+ ```
205
+
206
+ ## Conditional Steps
207
+
208
+ Use `condition` to conditionally run a step based on the results of previous steps. The condition receives the same `StepResults` map.
209
+
210
+ ```typescript
211
+ @Workflow({
212
+ name: 'conditional-deploy',
213
+ description: 'Deploy only if tests pass and coverage meets threshold',
214
+ steps: [
215
+ {
216
+ id: 'test',
217
+ jobName: 'run-tests',
218
+ input: { suite: 'all', coverage: true },
219
+ },
220
+ {
221
+ id: 'deploy',
222
+ jobName: 'deploy-to-env',
223
+ dependsOn: ['test'],
224
+ condition: (steps) => {
225
+ const testResult = steps.get('test');
226
+ return testResult.state === 'completed' && testResult.outputs.coverage >= 95;
227
+ },
228
+ input: (steps) => ({
229
+ artifact: steps.get('test').outputs.buildPath,
230
+ environment: 'staging',
231
+ }),
232
+ },
233
+ {
234
+ id: 'notify-failure',
235
+ jobName: 'send-notification',
236
+ dependsOn: ['test'],
237
+ condition: (steps) => steps.get('test').state === 'failed',
238
+ input: { channel: '#alerts', message: 'Test suite failed -- deployment blocked' },
239
+ },
240
+ ],
241
+ })
242
+ class ConditionalDeploy {}
243
+ ```
244
+
245
+ When a `condition` returns `false`, the step is marked as `skipped`. Downstream steps that depend on a skipped step check their own conditions with the skipped step's state.
246
+
247
+ ## Error Handling with continueOnError
248
+
249
+ By default, a failed step stops the entire workflow. Set `continueOnError: true` on a step to allow the workflow to proceed even if that step fails.
250
+
251
+ ```typescript
252
+ @Workflow({
253
+ name: 'resilient-pipeline',
254
+ description: 'Pipeline that continues past non-critical failures',
255
+ steps: [
256
+ {
257
+ id: 'extract',
258
+ jobName: 'extract-data',
259
+ input: { source: 'primary-db' },
260
+ },
261
+ {
262
+ id: 'enrich',
263
+ jobName: 'enrich-data',
264
+ dependsOn: ['extract'],
265
+ continueOnError: true, // enrichment is optional
266
+ input: (steps) => ({ data: steps.get('extract').outputs.records }),
267
+ },
268
+ {
269
+ id: 'load',
270
+ jobName: 'load-data',
271
+ dependsOn: ['extract', 'enrich'],
272
+ input: (steps) => {
273
+ const enrichResult = steps.get('enrich');
274
+ // Use enriched data if available, fall back to raw
275
+ const data =
276
+ enrichResult.state === 'completed'
277
+ ? enrichResult.outputs.enrichedRecords
278
+ : steps.get('extract').outputs.records;
279
+ return { data, destination: 'warehouse' };
280
+ },
281
+ },
282
+ ],
283
+ })
284
+ class ResilientPipeline {}
285
+ ```
286
+
287
+ ## Workflow Triggers
288
+
289
+ ### Manual (Default)
290
+
291
+ The workflow is started by an explicit API call or MCP request:
292
+
293
+ ```typescript
294
+ @Workflow({
295
+ name: 'manual-deploy',
296
+ description: 'Manually triggered deployment',
297
+ trigger: 'manual',
298
+ steps: [
299
+ /* ... */
300
+ ],
301
+ })
302
+ class ManualDeploy {}
303
+ ```
304
+
305
+ ### Webhook
306
+
307
+ The workflow is triggered by an incoming HTTP request. Configure the webhook path, secret, and allowed HTTP methods.
308
+
309
+ ```typescript
310
+ @Workflow({
311
+ name: 'github-deploy',
312
+ description: 'Deploy on GitHub push events',
313
+ trigger: 'webhook',
314
+ webhook: {
315
+ path: '/webhooks/github-deploy',
316
+ secret: process.env.WEBHOOK_SECRET,
317
+ methods: ['POST'],
318
+ },
319
+ steps: [
320
+ {
321
+ id: 'build',
322
+ jobName: 'build-project',
323
+ input: { branch: 'main' },
324
+ },
325
+ {
326
+ id: 'deploy',
327
+ jobName: 'deploy-to-env',
328
+ dependsOn: ['build'],
329
+ input: (steps) => ({
330
+ artifact: steps.get('build').outputs.artifactUrl,
331
+ environment: 'production',
332
+ }),
333
+ },
334
+ ],
335
+ })
336
+ class GithubDeploy {}
337
+ ```
338
+
339
+ #### WebhookConfig Fields
340
+
341
+ | Field | Type | Default | Description |
342
+ | --------- | ---------- | --------------------------------- | ---------------------------------------------- |
343
+ | `path` | `string` | Auto-generated from workflow name | HTTP path for the webhook endpoint |
344
+ | `secret` | `string` | -- | Shared secret for webhook signature validation |
345
+ | `methods` | `string[]` | `['POST']` | Allowed HTTP methods |
346
+
347
+ ### Event
348
+
349
+ The workflow is triggered by an internal event emitted by the application:
350
+
351
+ ```typescript
352
+ @Workflow({
353
+ name: 'on-user-signup',
354
+ description: 'Workflow triggered when a new user signs up',
355
+ trigger: 'event',
356
+ steps: [
357
+ {
358
+ id: 'create-profile',
359
+ jobName: 'create-user-profile',
360
+ input: { template: 'default' },
361
+ },
362
+ {
363
+ id: 'send-welcome',
364
+ jobName: 'send-email',
365
+ dependsOn: ['create-profile'],
366
+ input: (steps) => ({
367
+ to: steps.get('create-profile').outputs.email,
368
+ template: 'welcome',
369
+ }),
370
+ },
371
+ {
372
+ id: 'setup-defaults',
373
+ jobName: 'setup-user-defaults',
374
+ dependsOn: ['create-profile'],
375
+ input: (steps) => ({
376
+ userId: steps.get('create-profile').outputs.userId,
377
+ }),
378
+ },
379
+ ],
380
+ })
381
+ class OnUserSignup {}
382
+ ```
383
+
384
+ ## Function Builder
385
+
386
+ For workflows that do not need a class, use the `workflow()` function builder:
387
+
388
+ ```typescript
389
+ import { workflow } from '@frontmcp/sdk';
390
+
391
+ const QuickDeploy = workflow({
392
+ name: 'quick-deploy',
393
+ description: 'Simplified deployment workflow',
394
+ steps: [
395
+ {
396
+ id: 'build',
397
+ jobName: 'build-project',
398
+ input: { target: 'production' },
399
+ },
400
+ {
401
+ id: 'deploy',
402
+ jobName: 'deploy-to-env',
403
+ dependsOn: ['build'],
404
+ input: (steps) => ({
405
+ artifact: steps.get('build').outputs.artifactUrl,
406
+ environment: 'staging',
407
+ }),
408
+ },
409
+ ],
410
+ });
411
+ ```
412
+
413
+ Register it the same way as a class workflow: `workflows: [QuickDeploy]`.
414
+
415
+ ## Registration
416
+
417
+ Add workflow classes (or function-style workflows) to the `workflows` array in `@App`. Workflows require jobs to be enabled since each step runs a named job.
418
+
419
+ ```typescript
420
+ import { FrontMcp, App } from '@frontmcp/sdk';
421
+
422
+ @App({
423
+ name: 'pipeline-app',
424
+ jobs: [BuildProjectJob, RunTestsJob, DeployToEnvJob, SendNotificationJob],
425
+ workflows: [DeployPipeline, DataValidationPipeline, QuickDeploy],
426
+ })
427
+ class PipelineApp {}
428
+
429
+ @FrontMcp({
430
+ info: { name: 'pipeline-server', version: '1.0.0' },
431
+ apps: [PipelineApp],
432
+ jobs: {
433
+ enabled: true,
434
+ store: {
435
+ redis: {
436
+ provider: 'redis',
437
+ host: 'localhost',
438
+ port: 6379,
439
+ keyPrefix: 'mcp:jobs:',
440
+ },
441
+ },
442
+ },
443
+ })
444
+ class PipelineServer {}
445
+ ```
446
+
447
+ ## Nx Generator
448
+
449
+ Scaffold a new workflow using the Nx generator:
450
+
451
+ ```bash
452
+ nx generate @frontmcp/nx:workflow
453
+ ```
454
+
455
+ This creates the workflow file, spec file, and updates barrel exports.
456
+
457
+ ## Complete Example: CI/CD Pipeline
458
+
459
+ ```typescript
460
+ import { Workflow, Job, JobContext, FrontMcp, App, workflow } from '@frontmcp/sdk';
461
+ import { z } from 'zod';
462
+
463
+ // --- Jobs ---
464
+
465
+ @Job({
466
+ name: 'checkout-code',
467
+ description: 'Checkout code from repository',
468
+ inputSchema: {
469
+ repo: z.string().describe('Repository URL'),
470
+ branch: z.string().default('main'),
471
+ },
472
+ outputSchema: {
473
+ workDir: z.string(),
474
+ commitSha: z.string(),
475
+ },
476
+ })
477
+ class CheckoutCodeJob extends JobContext {
478
+ async execute(input: { repo: string; branch: string }) {
479
+ this.log(`Checking out ${input.repo}@${input.branch}`);
480
+ return { workDir: '/tmp/build/workspace', commitSha: 'abc123' };
481
+ }
482
+ }
483
+
484
+ @Job({
485
+ name: 'run-linter',
486
+ description: 'Run linter on codebase',
487
+ inputSchema: {
488
+ workDir: z.string(),
489
+ },
490
+ outputSchema: {
491
+ passed: z.boolean(),
492
+ issues: z.number().int(),
493
+ },
494
+ })
495
+ class RunLinterJob extends JobContext {
496
+ async execute(input: { workDir: string }) {
497
+ this.log(`Linting ${input.workDir}`);
498
+ return { passed: true, issues: 0 };
499
+ }
500
+ }
501
+
502
+ @Job({
503
+ name: 'run-unit-tests',
504
+ description: 'Run unit test suite',
505
+ inputSchema: {
506
+ workDir: z.string(),
507
+ coverage: z.boolean().default(true),
508
+ },
509
+ outputSchema: {
510
+ passed: z.boolean(),
511
+ testCount: z.number().int(),
512
+ coverage: z.number(),
513
+ },
514
+ retry: { maxAttempts: 2, backoffMs: 3000, backoffMultiplier: 1, maxBackoffMs: 3000 },
515
+ })
516
+ class RunUnitTestsJob extends JobContext {
517
+ async execute(input: { workDir: string; coverage: boolean }) {
518
+ this.log(`Running unit tests in ${input.workDir}`);
519
+ this.progress(50, 100, 'Tests running');
520
+ return { passed: true, testCount: 342, coverage: 96.4 };
521
+ }
522
+ }
523
+
524
+ @Job({
525
+ name: 'build-artifact',
526
+ description: 'Build production artifact',
527
+ inputSchema: {
528
+ workDir: z.string(),
529
+ commitSha: z.string(),
530
+ },
531
+ outputSchema: {
532
+ artifactUrl: z.string().url(),
533
+ size: z.number().int(),
534
+ },
535
+ timeout: 180000,
536
+ })
537
+ class BuildArtifactJob extends JobContext {
538
+ async execute(input: { workDir: string; commitSha: string }) {
539
+ this.log(`Building artifact from ${input.commitSha}`);
540
+ this.progress(0, 100, 'Compiling');
541
+ this.progress(100, 100, 'Build complete');
542
+ return {
543
+ artifactUrl: `https://artifacts.example.com/builds/${input.commitSha}.tar.gz`,
544
+ size: 52428800,
545
+ };
546
+ }
547
+ }
548
+
549
+ @Job({
550
+ name: 'deploy-artifact',
551
+ description: 'Deploy artifact to target environment',
552
+ inputSchema: {
553
+ artifactUrl: z.string().url(),
554
+ environment: z.string(),
555
+ },
556
+ outputSchema: {
557
+ deploymentId: z.string(),
558
+ url: z.string().url(),
559
+ },
560
+ retry: { maxAttempts: 3, backoffMs: 5000, backoffMultiplier: 2, maxBackoffMs: 30000 },
561
+ permissions: {
562
+ actions: ['execute'],
563
+ roles: ['admin', 'deployer'],
564
+ scopes: ['deploy:write'],
565
+ },
566
+ })
567
+ class DeployArtifactJob extends JobContext {
568
+ async execute(input: { artifactUrl: string; environment: string }) {
569
+ this.log(`Deploying ${input.artifactUrl} to ${input.environment}`);
570
+ return {
571
+ deploymentId: 'deploy-001',
572
+ url: `https://${input.environment}.example.com`,
573
+ };
574
+ }
575
+ }
576
+
577
+ @Job({
578
+ name: 'notify-team',
579
+ description: 'Send notification to the team',
580
+ inputSchema: {
581
+ channel: z.string(),
582
+ message: z.string(),
583
+ },
584
+ outputSchema: {
585
+ sent: z.boolean(),
586
+ },
587
+ })
588
+ class NotifyTeamJob extends JobContext {
589
+ async execute(input: { channel: string; message: string }) {
590
+ this.log(`Notifying ${input.channel}: ${input.message}`);
591
+ return { sent: true };
592
+ }
593
+ }
594
+
595
+ // --- Workflow ---
596
+
597
+ @Workflow({
598
+ name: 'ci-cd-pipeline',
599
+ description: 'Full CI/CD pipeline: checkout, lint, test, build, deploy, notify',
600
+ trigger: 'webhook',
601
+ webhook: {
602
+ path: '/webhooks/ci-cd',
603
+ secret: process.env.CI_WEBHOOK_SECRET,
604
+ methods: ['POST'],
605
+ },
606
+ timeout: 900000, // 15 minutes
607
+ maxConcurrency: 3,
608
+ permissions: {
609
+ actions: ['create', 'read', 'execute', 'list'],
610
+ roles: ['admin', 'ci-bot'],
611
+ },
612
+ steps: [
613
+ {
614
+ id: 'checkout',
615
+ jobName: 'checkout-code',
616
+ input: { repo: 'https://github.com/org/repo.git', branch: 'main' },
617
+ },
618
+ {
619
+ id: 'lint',
620
+ jobName: 'run-linter',
621
+ dependsOn: ['checkout'],
622
+ input: (steps) => ({
623
+ workDir: steps.get('checkout').outputs.workDir,
624
+ }),
625
+ },
626
+ {
627
+ id: 'test',
628
+ jobName: 'run-unit-tests',
629
+ dependsOn: ['checkout'],
630
+ input: (steps) => ({
631
+ workDir: steps.get('checkout').outputs.workDir,
632
+ coverage: true,
633
+ }),
634
+ },
635
+ {
636
+ id: 'build',
637
+ jobName: 'build-artifact',
638
+ dependsOn: ['lint', 'test'],
639
+ condition: (steps) =>
640
+ steps.get('lint').state === 'completed' &&
641
+ steps.get('lint').outputs.passed === true &&
642
+ steps.get('test').state === 'completed' &&
643
+ steps.get('test').outputs.passed === true &&
644
+ steps.get('test').outputs.coverage >= 95,
645
+ input: (steps) => ({
646
+ workDir: steps.get('checkout').outputs.workDir,
647
+ commitSha: steps.get('checkout').outputs.commitSha,
648
+ }),
649
+ },
650
+ {
651
+ id: 'deploy',
652
+ jobName: 'deploy-artifact',
653
+ dependsOn: ['build'],
654
+ condition: (steps) => steps.get('build').state === 'completed',
655
+ input: (steps) => ({
656
+ artifactUrl: steps.get('build').outputs.artifactUrl,
657
+ environment: 'staging',
658
+ }),
659
+ },
660
+ {
661
+ id: 'notify-success',
662
+ jobName: 'notify-team',
663
+ dependsOn: ['deploy'],
664
+ condition: (steps) => steps.get('deploy').state === 'completed',
665
+ input: (steps) => ({
666
+ channel: '#deployments',
667
+ message: `Deployed ${steps.get('deploy').outputs.deploymentId} to ${steps.get('deploy').outputs.url}`,
668
+ }),
669
+ },
670
+ {
671
+ id: 'notify-failure',
672
+ jobName: 'notify-team',
673
+ dependsOn: ['lint', 'test'],
674
+ condition: (steps) => steps.get('lint').state === 'failed' || steps.get('test').state === 'failed',
675
+ input: {
676
+ channel: '#alerts',
677
+ message: 'CI pipeline failed -- check lint and test results',
678
+ },
679
+ },
680
+ ],
681
+ })
682
+ class CiCdPipeline {}
683
+
684
+ // --- Registration ---
685
+
686
+ @App({
687
+ name: 'ci-app',
688
+ jobs: [CheckoutCodeJob, RunLinterJob, RunUnitTestsJob, BuildArtifactJob, DeployArtifactJob, NotifyTeamJob],
689
+ workflows: [CiCdPipeline],
690
+ })
691
+ class CiApp {}
692
+
693
+ @FrontMcp({
694
+ info: { name: 'ci-server', version: '1.0.0' },
695
+ apps: [CiApp],
696
+ jobs: {
697
+ enabled: true,
698
+ store: {
699
+ redis: {
700
+ provider: 'redis',
701
+ host: 'localhost',
702
+ port: 6379,
703
+ keyPrefix: 'mcp:ci:',
704
+ },
705
+ },
706
+ },
707
+ })
708
+ class CiServer {}
709
+ ```