This guide covers development setup, commands, and testing for the MCP Workflow Server.
- Node.js 18+
- npm or yarn
- Git (for worktree setup)
# Clone the repository
git clone https://github.com/m2ux/workflow-server.git
cd workflow-server
# Install dependencies
npm install
# Set up workflow data (worktree for orphan branch)
git worktree add ./workflows workflows# Install dependencies
npm install
# Type check
npm run typecheck
# Build for production
npm run build
# Run in development mode (with hot reload via tsx)
npm run devworkflow-server/
├── src/
│ ├── index.ts # Entry point: config → server → stdio transport
│ ├── server.ts # MCP server creation and tool/resource registration
│ ├── config.ts # ServerConfig: workflowDir, schemasDir, schemaPreamble, traceStore, minCheckpointResponseSeconds
│ ├── errors.ts # Custom error classes (WorkflowNotFoundError, etc.)
│ ├── result.ts # Result<T, E> monad for typed error handling
│ ├── logging.ts # Structured JSON logging + audit event wrapper (withAuditLog)
│ ├── trace.ts # TraceStore, TraceEvent, trace token encode/decode
│ ├── schema/ # Zod runtime schemas for validation
│ │ ├── workflow.schema.ts
│ │ ├── activity.schema.ts
│ │ ├── technique.schema.ts
│ │ ├── condition.schema.ts
│ │ ├── state.schema.ts
│ │ ├── resource.schema.ts
│ │ └── common.ts
│ ├── types/ # Re-export layer (types + schemas)
│ ├── loaders/ # File loaders (filesystem → validated objects)
│ │ ├── workflow-loader.ts
│ │ ├── activity-loader.ts
│ │ ├── technique-loader.ts # Includes resolveTechniques (resolves techniques via :: paths)
│ │ ├── markdown-technique-loader.ts # Parses markdown technique files into Technique objects
│ │ ├── resource-loader.ts
│ │ ├── core-ops.ts # CORE_ORCHESTRATOR_TECHNIQUES / CORE_WORKER_TECHNIQUES (core technique refs bundled into get_workflow / get_activity)
│ │ ├── schema-loader.ts
│ │ ├── filename-utils.ts
│ │ └── index.ts # Barrel exports
│ ├── tools/ # MCP tool implementations
│ │ ├── workflow-tools.ts # discover, list_workflows, get_workflow, next_activity, get_activity, yield_checkpoint, resume_checkpoint, present_checkpoint, respond_checkpoint, get_trace, health_check, get_workflow_status
│ │ ├── resource-tools.ts # start_session, dispatch_child, get_technique, get_resource
│ │ └── index.ts # Tool registration entry point
│ ├── resources/ # MCP resource registration
│ │ └── schema-resources.ts # workflow-server://schemas
│ └── utils/ # Utility functions
│ ├── yaml.ts # YAML format parser wrapper
│ ├── session.ts # Session token create/decode/advance (HMAC-SHA256)
│ ├── validation.ts # Transition, manifest, and activity validation
│ ├── crypto.ts # AES-256-GCM encryption, HMAC signing
│ └── index.ts # Barrel exports
├── schemas/ # JSON Schema files for IDE tooling
│ ├── workflow.schema.json
│ ├── activity.schema.json
│ ├── technique.schema.json
│ ├── condition.schema.json
│ └── state.schema.json
├── scripts/ # Build scripts
│ ├── generate-schemas.ts
│ └── validate-workflow-yaml.ts
├── tests/ # Test suites
├── workflows/ # Worktree (workflows branch)
│ ├── meta/ # Bootstrap workflow
│ │ ├── workflow.yaml
│ │ ├── activities/
│ │ └── techniques/
│ └── {workflow-id}/ # Each workflow folder
│ ├── workflow.yaml
│ ├── activities/
│ ├── resources/
│ └── techniques/
└── docs/ # Documentation
| Variable | Default | Description |
|---|---|---|
WORKFLOW_DIR |
./workflows |
Path to workflow directories |
SERVER_NAME |
workflow-server |
Server name in health check |
SERVER_VERSION |
1.0.0 |
Server version in health check |
# Run all tests (watch mode)
npm test
# Run tests once (no watch)
npm test -- --run
# Run specific test file
npm test -- --run tests/mcp-server.test.ts
# Run with coverage
npm test -- --run --coverage| Test Suite | Coverage |
|---|---|
workflow-loader.test.ts |
Workflow loading, transitions, validation |
schema-validation.test.ts |
All Zod schemas |
schema-loader.test.ts |
JSON Schema loading and serving |
mcp-server.test.ts |
All MCP tools, trace lifecycle, activity manifest, technique bundles |
activity-loader.test.ts |
Activity loading and dynamic index |
technique-loader.test.ts |
Technique loading, dynamic index, technique resolution |
session.test.ts |
Token create/decode/advance, sid, aid, parent context |
trace.test.ts |
TraceStore, trace token encode/decode |
validation.test.ts |
Transition, manifest, condition validation |
dispatch.test.ts |
Workflow dispatch, status, parent-child trace correlation |
Run npm test -- --run for the live count and pass/fail summary.
- Framework: Vitest
- MCP Testing: Uses
InMemoryTransportfor integration tests - Schema Validation: Tests all Zod schemas with valid/invalid inputs
Several layered checks validate workflow data:
# Structural / schema validation of YAML files (whole-directory sweep)
npx tsx scripts/validate-workflow-yaml.ts <workflow-path>
# Every step.technique reference resolves through the loader
npx tsx scripts/check-all-refs.ts
# Binding fidelity: every step.technique.inputs key is a declared input, and every
# interpolation/condition read resolves to a producer (declared id, $-local,
# workflow.yaml variable, or set-target). Fails only on NEW drift beyond
# scripts/binding-fidelity-baseline.json (re-snapshot intentional changes with
# --update-baseline).
npm run check:binding
# Normative technique template: metadata.version-only frontmatter, no H1 title,
# the canonical H2 set in order (Capability, Inputs, Outputs, Protocol, Rules —
# Outputs before Protocol), snake_case entry ids (camelCase tool-parameter
# mirrors allowed), kebab-case rule names, snake_case {$name} sigils. Hard-zero.
npm run check:technique-template
# Shared fragments (#166 B10): every rules { ref } and checkpoint ref resolves,
# every declared fragment is referenced somewhere, no inline copy of a fragment,
# and no identical rule/checkpoint body authored inline at multiple sites.
# Near-duplicates of a fragment are reported as warnings. Hard-zero.
npm run check:fragments
# Audience convention: every output declared audience: agent that also carries an
# #### artifact filename must name a JSON artifact (an agent-audience artifact is
# serialized as JSON on disk). Fails only on NEW violations beyond
# scripts/audience-baseline.json (re-snapshot intentional changes with
# --update-baseline).
npm run check:audienceThe binding-fidelity, technique-template, fragments, and audience guards also run as Vitest tests (tests/binding-fidelity.test.ts, tests/technique-template.test.ts, tests/fragments-guard.test.ts, tests/audience-guard.test.ts), so npm test fails on new binding drift, a template deviation, fragment drift, or a non-JSON agent-audience artifact.
| Branch | Content | Purpose |
|---|---|---|
main |
TypeScript server code | Implementation |
workflows |
YAML workflows + resources | Data (orphan branch) |
The workflows branch is an orphan branch with separate history. Access it via worktree:
# Add worktree (one-time setup)
git worktree add ./workflows workflows
# Update workflow data
cd workflows
git pull origin workflows
# Commit workflow changes
cd workflows
git add -A
git commit -m "feat: update workflow"
git push origin workflows- Create a new directory in
workflows/{workflow-id}/ - Create
workflow.yamlworkflow definition in that directory - Validate with:
npx tsx scripts/validate-workflow-yaml.ts <path>, then runnpx tsx scripts/check-all-refs.tsandnpm run check:bindingto confirm references resolve and no binding drift - Commit to the
workflowsbranch
Resources are stored in a resources/ subdirectory within each workflow as slug-named markdown files:
- Create
{slug}.mdinworkflows/{workflow-id}/resources/ - The filename slug is the resource id (the frontmatter
name:should match) - Resources are auto-discovered - no manifest update needed
- Access via:
get_resourcewith the resource slug (referenced from a technique); cross-workflow refs use{workflow}/{slug} - Commit to the
workflowsbranch
Note: For backwards compatibility, the loader also checks the guides/ folder if resources/ doesn't exist.
Techniques are markdown files. They can be universal (apply to all workflows, stored in meta) or workflow-specific. A technique lives at techniques/{slug}.md. A technique can contain nested techniques in its folder; a nested technique is itself a technique, addressed by appending its slug to the parent's path.
A technique file has:
- YAML frontmatter carrying
metadata.version. ## Capability— what the technique does.## Inputs/## Outputs(optional) — each### entrymay carry####sub-section components, plus the reserved#### artifact(output persistence filename) and#### default(input default).## Protocol— ordered blocks### N. Titlewith step bullets, or a flat list. Failure handling is written inline in the protocol step that gives rise to it.## Rules— constraints the technique enforces.
Universal techniques are stored in the meta workflow's techniques/ subdirectory:
- Create
techniques/{slug}.mdunderworkflows/meta/ - Access via:
get_technique(workflow- or activity-level first declared technique, optionally a step's technique viastep_id) - Commit to the
workflowsbranch
Workflow-specific techniques are stored in each workflow's techniques/ subdirectory:
- Create
techniques/{slug}.mdinworkflows/{workflow-id}/ - Techniques are auto-discovered - no manifest update needed
- Access via:
get_technique { session_index, step_id: "{step-id}" }(when referenced by a step) - Commit to the
workflowsbranch
Techniques are addressed by ::-delimited paths: [workflow::]technique[::nested…]. A same-workflow reference omits the workflow prefix. A {workflow}/{technique} slash form is normalized to the :: form.
When loading a technique, the workflow is determined from the session's session.json (resolved via session_index):
- First checks the session's workflow (current-workflow-first)
- Falls back to the
metalayer (universal)