Workflows as services
Expose registered workflows as stable REST and MCP endpoints using flux service — create, inspect, and manage named service definitions without restarting anything.
A Flux service is a named set of selectors that maps registered workflows to stable HTTP endpoints. Once created, a service requires no restarts when workflows are added or updated; selectors resolve dynamically against the workflow catalog at request time.
This page covers creating and managing services, calling them over HTTP, running standalone service processes, and enabling the optional MCP interface.
Prerequisites
A Flux server and at least one worker must be running. The service layer sits in front of the server and adds routing, not execution.
flux start server
# In a separate terminal:
export FLUX_WORKERS__BOOTSTRAP_TOKEN=$(flux server bootstrap-token)
flux start worker
Create a service
The simplest form binds a service name to a namespace. Every workflow in that namespace becomes an endpoint:
flux service create billing --namespace billing
To include specific workflows rather than an entire namespace:
flux service create reporting \
--workflow billing/monthly_report \
--workflow analytics/dashboard
To combine both — all workflows from one namespace plus specific extras, minus anything sensitive:
flux service create billing-api \
--namespace billing \
--workflow payments/process \
--exclude billing/internal_cleanup
The --exclude flag takes a qualified reference (namespace/workflow_name). Exclusions apply after all includes, so a named workflow is suppressed even when an earlier include rule would have matched it.
Enable MCP at creation
Pass --mcp to expose the service’s workflows as Model Context Protocol tools in addition to REST endpoints:
flux service create billing --namespace billing --mcp
Inspect services
List all registered services:
flux service list
Show a specific service — selectors and currently resolved endpoints:
flux service show billing
Service: billing
MCP: disabled
Namespaces:
- billing
Endpoints:
- billing/invoice
- billing/refund
- billing/receipt
Both commands accept --format json for machine-readable output and --server-url to target a remote Flux server.
Modify selectors
Add and remove selectors on a live service without deleting and re-creating it.
# Add a namespace
flux service add billing --namespace payments
# Add a specific workflow
flux service add billing --workflow analytics/report
# Remove a namespace
flux service remove billing --namespace payments
# Exclude a workflow by qualified reference
flux service exclude billing billing/debug_tool
# Lift a previous exclusion
flux service include billing billing/debug_tool
add and remove modify the namespace and workflow selector lists. exclude and include modify the exclusions list. All four commands route through the same PUT /services/{name} API with different payload fields.
Toggle MCP
# Enable MCP on an existing service
flux service update billing --mcp
# Disable
flux service update billing --no-mcp
Delete a service
flux service delete billing --yes
Without --yes, the CLI prompts for confirmation.
Call a service endpoint
Any registered workflow the service selects is reachable at:
POST /services/{service}/{workflow}
The request body is the workflow’s JSON input; the response body is the raw output:
curl -X POST http://localhost:8000/services/billing/invoice \
-H "Content-Type: application/json" \
-d '{"customer_id": "C-100", "amount": 99.50}'
# {"invoice_id": "INV-123", "total": 99.50}
Execution modes
Append a mode segment to the URL to control how the server runs the workflow:
# Sync (default) — blocks until the workflow completes
POST /services/billing/invoice
# Async — returns immediately with an execution ID
POST /services/billing/invoice/async
# Check status of an async execution
GET /services/billing/invoice/status/{execution_id}
# Resume a workflow that called pause()
POST /services/billing/invoice/resume/{execution_id}
Add ?detailed=true to any sync or status request to get the full execution envelope (state, namespace, workflow name) instead of raw output:
curl -X POST "http://localhost:8000/services/billing/invoice?detailed=true" \
-H "Content-Type: application/json" \
-d '{"customer_id": "C-100"}'
{
"execution_id": "abc123def",
"state": "COMPLETED",
"output": {"invoice_id": "INV-123", "total": 99.50},
"namespace": "billing",
"workflow": "invoice"
}
Dynamic endpoint resolution
Selectors are evaluated at request time. Register a new workflow in the billing namespace and it is immediately available through any service that selects billing, with no update to the service definition and no restart. Delete a workflow and its endpoint returns 404.
If two selected workflows from different namespaces share a name, the service returns 409 Conflict. Resolve the collision by excluding one side with flux service exclude.
Run a standalone service process
A standalone process serves one service at root-level URLs, independent of the main Flux server. Use it for dedicated deployments where you want clean URLs and independent scaling:
flux service start billing --port 9000 --server-url http://flux-internal:8000
With the service name removed from the path, the same invoice endpoint becomes:
curl -X POST http://localhost:9000/invoice \
-H "Content-Type: application/json" \
-d '{"customer_id": "C-100"}'
The process forwards every request to the Flux server and caches the endpoint list with a configurable TTL (default: 60 seconds):
flux service start billing \
--port 9000 \
--host 0.0.0.0 \
--server-url http://flux:8000 \
--cache-ttl 30
A health endpoint is available automatically:
curl http://localhost:9000/health
# {"status": "healthy", "service": "billing", "endpoints": 3, "cache_age_seconds": 12.4}
MCP endpoint
When MCP is enabled, each workflow in the service gets five MCP tools:
| Tool | What it does |
|---|---|
{name} | Run the workflow synchronously |
{name}_async | Run asynchronously — returns an execution ID |
resume_{name} | Resume a paused execution synchronously |
resume_{name}_async | Resume asynchronously |
status_{name} | Check execution status |
If a workflow declares a Pydantic model as its input type, the generated tool exposes individual parameters matching the model’s fields, so an AI agent can call it without constructing raw JSON.
from pydantic import BaseModel
import flux
class InvoiceInput(BaseModel):
customer_id: str
amount: float
@flux.workflow.with_options(namespace="billing")
async def invoice(ctx: flux.ExecutionContext[InvoiceInput]):
...
With that definition, the invoice MCP tool exposes customer_id: str and amount: float as discrete parameters.
MCP in the standalone process
Start the standalone process with MCP enabled:
flux service start billing --port 9000 --mcp --server-url http://flux:8000
The MCP server is available at http://localhost:9000/mcp. The --mcp and --no-mcp flags on service start override the stored setting; without an explicit flag, the value stored at service creation or last update is used.
MCP authentication
The standalone MCP endpoint can validate bearer tokens from any OAuth 2.0 / OIDC identity provider and advertise the IdP for discovery (RFC 9728):
flux service start billing --port 9000 --mcp \
--mcp-issuer https://idp.example.com/realms/my-realm \
--mcp-audience billing-api \
--mcp-jwks-uri https://idp.example.com/realms/my-realm/protocol/openid-connect/certs
If --mcp-issuer is omitted and your flux.toml has OIDC enabled ([flux.security.auth.oidc]), the same issuer and audience are used automatically.
What’s next
- Reference: flux service — complete flag reference for all
flux servicesub-commands. - Running workflows from MCP — the system-level
flux start mcpserver that exposes the full Flux API as MCP tools (as opposed to per-service MCP endpoints). - Running workflows from the REST API — call the Flux HTTP API directly without a service layer.
- Workflow control → Scheduling — automate executions on a cron or interval schedule.