MCP Workflow Orchestration Server
Cho phép các tác nhân AI khám phá, tạo và thực thi các quy trình làm việc phức tạp, nhiều bước được định nghĩa trong các tệp YAML đơn giản.
Tài liệu
@cyanheads/workflows-mcp-server
Store, query, and create YAML workflow playbooks for LLM agents via MCP. STDIO or Streamable HTTP.
Overview
A declarative workflow library for LLM agents, backed by local YAML files. Store, list, and retrieve named, versioned multi-step playbooks — each a sequence of MCP server/tool calls — for permanent reuse or one-shot temporary runs. Runs as a stdio process or a local Streamable HTTP server; an in-memory index rebuilds automatically as files change.
Tools
| Tool | Description |
|---|---|
workflow_list | List all permanent workflows in the index, with optional keyword, category, and tag filters. |
workflow_get | Retrieve a complete workflow definition by name, with global instructions prepended. |
workflow_create | Write a new permanent workflow YAML to the library. |
workflow_create_temp | Write a temporary workflow draft, indexed but excluded from list results, kept until deleted. |
workflow_delete | Remove a permanent workflow or a temporary draft by name and optional version, after the user confirms the resolved target. |
Capability reference
workflow_list tool
- Optional keyword
queryfilter (case-insensitive substring across workflow name and description) - Optional category filter (case-insensitive substring match)
- Optional tag filter (case-insensitive AND match — all listed tags must be present)
- Filter values are trimmed of leading and trailing whitespace before matching; a blank
queryorcategoryapplies no filter - Set
includeTools: trueto surface the uniqueserver/toolpairs used across each workflow's steps - Temporary workflows are excluded; results sorted by name, then by semver precedence descending (a release before its prereleases)
- Empty results echo the applied filters with a hint to broaden
workflow_get tool
- Semver-aware: omit
versionto get the highest available match; specify a version for an exact lookup - A
versionthat is not valid semver is rejected as invalid arguments before any lookup; a tolerated spelling (v1.0.0, surrounding whitespace, build metadata) resolves to its canonical form (1.0.0) nameis trimmed before lookup, matching how the create tools store it; a blanknameis rejected as invalid arguments- Returns the full workflow YAML structure with all steps and metadata
- Injects the
global_instructions.mdcontent asglobalInstructions— apply these when executing the workflow;nullwhen the file is absent - Temporary workflows are accessible here even though excluded from
workflow_list - Template placeholders (
{{input.foo}},{{steps.X.output.Y}}) are returned verbatim — the server never interpolates them
workflow_create tool
- Workflow stored at
categories/<slugified-category>/<slugified-name>-<slugified-version>-<hash>-workflow.yaml, where<hash>is the first 8 hex characters of SHA-256 overname@version— one file pername@version, so multiple versions coexist and keys whose slugs coincide (Deploy/deploy,Café Plan/Caf Plan) never share a file - Any name with visible content is accepted; one with no ASCII letters or digits (e.g.
Рабочий процесс) usesworkflowas the name part of its filename - Rejects if
name@versionalready exists, as a permanent workflow or a temporary draft — bump the version to create a new revision, or delete the draft withworkflow_deleteto store it permanently - Concurrent creates of one
name@versionproduce one workflow and onealready_exists, even across categories versionis stored in canonical semver form: a leadingv, surrounding whitespace, and build metadata are dropped, sov1.0.0+build.5is stored, keyed, and retrieved as1.0.0- Rejects a whitespace-only
name,description,author,category, or stepserver/toolwithinvalid_input - Rejects a
namelonger than 200 characters or acategorylonger than 255 characters after slugification withinvalid_input, so every file and directory name fits the 255-byte limit - Server stamps
created_dateandlast_updated_dateautomatically - Index and snapshot rebuilt after write; filesystem watcher also fires (idempotent, debounced)
- A filesystem failure is reported as
write_failedwith the error code and description only, never the absolute path
workflow_create_temp tool
- Writing a
name@versionthat already has a draft overwrites that draft in place:statusis"created"for a new draft and"overwritten"for a replaced one, and an overwrite keeps the draft's originalcreated_date - Rejects a
name@versionheld by a permanent workflow withalready_exists - Stored under
temp/with the same filename scheme, canonicalversionstorage, and whitespace-only field rejection asworkflow_create - Indexed and accessible via
workflow_getbut excluded fromworkflow_listresults; anoticefield saying so rides along in bothstructuredContentand the text output - Drafts persist: a draft stays under
temp/across restarts untilworkflow_deleteremoves it — nothing expires drafts or cleans them up - Useful for one-shot plans, scaffolding, or drafts not yet ready for the permanent library
workflow_delete tool
- Deletes permanent workflows and temporary drafts alike; the output's
source("permanent"or"temp") says which was removed - Semver-aware: omit
versionto delete the highest available match across permanent workflows and drafts; specify a version to target one exactly - Same
versionandnamerules asworkflow_get: non-semver input is rejected before anything is deleted, a tolerated spelling targets its canonical form, and a paddednameis trimmed - Asks the user first: the call returns a confirmation prompt naming the resolved
name@version, its source, and its file path relative toWORKFLOWS_DIR, and deletes only when the user answersconfirm: true. Answeringfalse, declining, or cancelling fails withcancelledand deletes nothing - The server keeps each prompt's record and hands the client only a random id for it. An answer must come back within 10 minutes and works once; an answer to a prompt the server never issued, already answered, or issued too long ago fails with
confirmation_invalidand deletes nothing - The file is deleted only if it is still the one the user saw: if the name resolves to a different workflow or file, or the file's content changed, by the time the answer arrives, the call fails with
target_changedand deletes nothing - Needs a client that can show the prompt (elicitation); a client without it cannot delete, and there is no way around the prompt
- Irreversible: the file is removed and the workflow no longer appears in
workflow_listorworkflow_get— unless another hand-authored file declares the samename@version. That copy then takes its place, and the result carries anoticenaming its path relative toWORKFLOWS_DIR - Deleting a draft frees its
name@version, soworkflow_createcan then store it permanently - Deleting the last workflow in a
categories/<slug>/directory removes that emptied directory; a directory still holding any file stays
Features
Built on @cyanheads/mcp-ts-core: stdio and Streamable HTTP transports, pluggable auth (none / jwt / oauth), swappable storage (in-memory, filesystem, Supabase, Cloudflare KV/R2/D1), structured logging with optional OpenTelemetry tracing.
Workflow library:
- YAML workflow files validated against a schema at index time; invalid files (including a whitespace-only
name,description,author,category, or stepserver/tool) are skipped and logged, never crash the server - Versions indexed in canonical semver form — a file authored as
version: v1.0.0indexes asname@1.0.0 - One index entry per
name@version: a permanent workflow outranks a temporary draft with the same key (the draft is skipped with a warning naming both files), and two files of one kind that share a key log a duplicate warning, with the last one read winning - In-memory index keyed by
name@version, built at startup fromworkflows-yaml/categories/andworkflows-yaml/temp/recursively, kept fresh by a debounced recursive filesystem watcher on any add/change/remove - The index reads each workflow's identity from its file content, never its filename, so files named under any scheme — including the earlier
<name>-<version>-workflow.yaml— are listed, retrieved, and deleted like any other - Creates and deletes run one at a time within the server, so each one's existence check holds until its write lands
- Semver-aware lookup — latest version returned when
versionis omitted _index.jsonsnapshot written on every rebuild for external tooling and debugging- Configurable
WORKFLOWS_DIR,GLOBAL_INSTRUCTIONS_PATH, and debounce interval
Agent-friendly output:
- Discriminated output —
source: "permanent" | "temp"on everyworkflow_getresponse and typedreasoncodes (not_found,version_not_found,already_exists,cancelled,confirmation_invalid,target_changed,index_unavailable, …) on failures, so callers branch on data instead of parsing error strings - No extra round trip —
workflow_getalways returnsglobalInstructionsalongside the workflow definition in the same response - Response shaping —
workflow_list's optionalincludeToolsflag pre-derives the uniqueserver/toolpairs used by a workflow, and an empty result echoes the applied filters with a broadening hint instead of returning nothing
Getting started
No API keys required. The server reads from a local workflows-yaml/ directory by default.
Add the following to your MCP client configuration file:
{
"mcpServers": {
"workflows-mcp-server": {
"type": "stdio",
"command": "bunx",
"args": ["@cyanheads/workflows-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"WORKFLOWS_DIR": "/absolute/path/to/your/workflows-yaml"
}
}
}
}
Or with npx (no Bun required):
{
"mcpServers": {
"workflows-mcp-server": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@cyanheads/workflows-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"WORKFLOWS_DIR": "/absolute/path/to/your/workflows-yaml"
}
}
}
}
Or with Docker:
{
"mcpServers": {
"workflows-mcp-server": {
"type": "stdio",
"command": "docker",
"args": [
"run", "-i", "--rm",
"-e", "MCP_TRANSPORT_TYPE=stdio",
"-v", "/absolute/path/to/your/workflows-yaml:/workflows-yaml",
"-e", "WORKFLOWS_DIR=/workflows-yaml",
"ghcr.io/cyanheads/workflows-mcp-server:latest"
]
}
}
}
For Streamable HTTP, set the transport and start the server:
MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 bun run start:http
# Server listens at http://localhost:3010/mcp
Seed workflows
The repository ships a workflows-yaml/ directory with example workflows organized under categories/. These are ready to use as a starting point. The workflows-yaml/global_instructions.md file contains instructions the server prepends to every workflow_get response — edit it to set global guidance for your agent.
Prerequisites
- Bun v1.4.0 or higher (or Node.js v24+).
- A local directory containing YAML workflow files (or use the bundled
workflows-yaml/seed).
Installation
- Clone the repository:
git clone https://github.com/cyanheads/workflows-mcp-server.git
- Navigate into the directory:
cd workflows-mcp-server
- Install dependencies:
bun install
- Configure environment:
cp .env.example .env
# edit .env if needed — most settings have defaults
Configuration
| Variable | Description | Default |
|---|---|---|
WORKFLOWS_DIR | Absolute or relative path to the workflows root directory. | ./workflows-yaml |
GLOBAL_INSTRUCTIONS_PATH | Path to the global instructions markdown file. Derives from WORKFLOWS_DIR when not set. | <WORKFLOWS_DIR>/global_instructions.md |
WATCHER_DEBOUNCE_MS | Milliseconds to debounce filesystem change events before rebuilding the index. | 500 |
MCP_TRANSPORT_TYPE | Transport: stdio or http. | stdio |
MCP_HTTP_PORT | Port for HTTP server. | 3010 |
MCP_SESSION_MODE | HTTP sessions: auto or stateful. workflow_delete's confirmation prompt needs a live session, so HTTP startup with stateless fails with a configuration error. Ignored over stdio. | stateful, declared in src/index.ts |
MCP_AUTH_MODE | Auth mode: none, jwt, or oauth. | none |
MCP_LOG_LEVEL | Log level (RFC 5424). | info |
OTEL_ENABLED | Enable OpenTelemetry instrumentation (spans, metrics, completion logs). | false |
See .env.example for the full list of optional overrides.
Running the server
Local development
-
Build and run:
# One-time build bun run rebuild # Run the built server bun run start:stdio # or bun run start:http -
Run checks and tests:
bun run devcheck # Lint, format, typecheck, security bun run test # Vitest test suite bun run lint:mcp # Validate MCP definitions against spec
Docker
docker build -t workflows-mcp-server .
docker run --rm \
-v /path/to/workflows-yaml:/workflows-yaml \
-e WORKFLOWS_DIR=/workflows-yaml \
-p 3010:3010 \
workflows-mcp-server
The Dockerfile defaults to HTTP transport, stateful session mode (required — see MCP_SESSION_MODE above), and logs to /var/log/workflows-mcp-server. OpenTelemetry peer dependencies are installed by default — build with --build-arg OTEL_ENABLED=false to omit them.
Project structure
| Directory | Purpose |
|---|---|
src/index.ts | createApp() entry point — registers tools and inits the workflow index service. |
src/config/ | Server-specific environment variable parsing and validation with Zod. |
src/mcp-server/tools/ | Tool definitions (*.tool.ts). |
src/services/workflow-index/ | WorkflowIndexService — YAML parsing, index build, watcher, semver lookup, write helpers. |
tests/ | Unit and integration tests mirroring src/. |
workflows-yaml/ | Seed workflow library — categories/ for permanent workflows, temp/ for temporary drafts, global_instructions.md for agent-global guidance. |
Development guide
See CLAUDE.md for development guidelines and architectural rules. The short version:
- Handlers throw, framework catches — no
try/catchin tool logic - Use
ctx.logfor request-scoped logging - Register new tools via the barrel in
src/mcp-server/tools/definitions/index.ts - Filesystem operations go through
WorkflowIndexService, not directly in tool handlers
Contributing
Issues are welcome. Run checks and tests before submitting:
bun run devcheck
bun run test
License
Apache-2.0 — see LICENSE for details.