Inputs and Interfaces
The accepts: attribute defines a schema for validating and defaulting operation inputs. This creates a clear contract for what data your operation expects.
Interface Definition
Define an interface using the accepts: key in your operation:
dyngle:
operations:
greet-user:
accepts:
name: { type: string }
age: { type: integer }
steps:
- echo "Hello {{name}}, age {{age}}"
When inputs are provided (via stdin, send:, or MCP JSON), Dyngle validates them against this schema before executing the operation.
Basic Syntax
The interface syntax is loosely based on JSON Schema but with adaptations tailored for Dyngle's use cases. While it shares some conventions (like type names), it has its own behavior for required fields, defaults, and type inference.
Root Object
The interface always defines properties of a root object:
accepts:
field1: { type: string }
field2: { type: integer }
This expects input like:
field1: some text
field2: 42
Type System
Supported types:
- string - Text values
- integer - Whole numbers (no decimals)
- number - Any numeric value (integers and floats)
- boolean - True or false values
- array - Lists of items
- object - Nested structures with properties
Type Examples
dyngle:
operations:
demo:
accepts:
text: { type: string }
count: { type: integer }
price: { type: number }
enabled: { type: boolean }
tags:
type: array
items: { type: string }
config:
type: object
properties:
host: { type: string }
port: { type: integer }
Type Inference
Types can be inferred automatically based on other attributes:
accepts:
name: {} # Defaults to string
message: # Also defaults to string (YAML null value)
user:
properties:
email: { type: string } # Inferred as object from properties
tags:
items: { type: string } # Inferred as array from items
Simplified syntax: You can omit the field value entirely (YAML null) or use an empty dict {} for string fields with blank defaults. These are equivalent:
accepts:
name: # YAML null - string with blank default
title: {} # Empty dict - string with blank default
email: { type: string } # Explicit string type
All three patterns above create optional string fields with blank string defaults.
Precedence for type inference:
- Explicit
type:if declared objectifproperties:is presentarrayifitems:is present- Otherwise
string
Required Fields and Defaults
Field requirements vary by type:
- String fields without explicit
requiredordefaultget a blank string""as the default (making them optional) - Other types are required by default unless marked
required: falseor given adefault
Examples
accepts:
name: {} # String type, gets blank default "" if omitted
nickname: { type: string, required: false } # Also optional, no default
age: { type: integer } # Required (non-string type)
email: { type: string, required: true } # Explicitly required
Default Values
Provide explicit default values for optional fields:
accepts:
name: { type: string } # Gets blank string if omitted
country:
type: string
default: "US" # Gets "US" if omitted
port:
type: integer
default: 8080 # Gets 8080 if omitted
Having an explicit default makes the field optional automatically.
Nested Objects
Define nested structures with properties:. The object type is automatically inferred from the presence of properties:, so you don't need to declare it explicitly:
dyngle:
operations:
process-order:
accepts:
customer:
properties: # object type inferred
name: # string type inferred (YAML null)
email:
shipping:
properties: # object type inferred
address:
city:
zip:
steps:
- echo "Processing order for {{customer.name}}"
- echo "Shipping to {{shipping.city}}"
You can nest objects arbitrarily deep:
accepts:
order:
properties:
shipping:
properties:
address:
properties: # deeply nested object
street:
city:
state:
Arrays
Define array types with items:
accepts:
tags:
type: array
items: { type: string }
scores:
items: { type: integer } # type: array inferred from items
Arrays of Objects
Combine arrays with nested objects. The array type is inferred from items:, and the object type for each item is inferred from properties::
accepts:
items:
items: # array type inferred
properties: # object type inferred for each item
product: # string type inferred
quantity: { type: integer }
price: { type: number }
Validation Process
When an operation with accepts: is invoked:
- Input is received (from stdin,
send:, or MCP JSON) - Schema validation - Input structure is checked against the interface
- Type validation - Values are checked for correct types
- Defaults applied - Missing optional fields get their defaults
- Required fields checked - Missing required fields cause an error
- Execution proceeds - Validated input becomes available in the operation context
Validation Success
echo "name: Alice\nage: 30" | dyngle run greet-user
# Output: Hello Alice, age 30
Validation Failure
echo "age: 30" | dyngle run greet-user
# Error: Input validation failed for operation 'greet-user':
# Field 'name' is required at root
Extra Fields
Extra fields are allowed by default:
echo "name: Bob\nage: 25\ncity: Seattle" | dyngle run greet-user
# Output: Hello Bob, age 25 (city is ignored)
Complete Example
This example demonstrates all the key features including type inference, nested objects, arrays, defaults, and required fields:
dyngle:
operations:
create-user:
description: Create a new user account
accepts:
username: # string with blank default (optional)
email: { type: string, required: true } # explicitly required
age: { type: integer } # required (non-string type)
role: { default: "user" } # string with custom default
preferences:
properties: # object type inferred
theme: { default: "light" }
notifications: { type: boolean, default: true }
tags:
items: # array type inferred
type: string
default: []
returns: result
steps:
- echo "Creating user {{username}} ({{email}})"
- echo "Role: {{role}}, Age: {{age}}"
- echo "Theme: {{preferences.theme}}"
- echo "User created successfully" => result
Using Interfaces with Sub-operations
When using send: to pass data to sub-operations, the data is validated against the child operation's accepts: schema. This example shows nested objects being passed via send::
dyngle:
operations:
process-user:
accepts:
user:
properties: # object type inferred
name: # string type inferred
email:
age: { type: integer }
steps:
- echo "Processing {{user.name}}, age {{user.age}}"
main:
constants:
user-data:
user:
name: Alice
email: [email protected]
age: 30
steps:
- sub: process-user
send: user-data # Nested structure validated against accepts schema
If validation fails, the parent operation stops with an error.
Using Interfaces with MCP
When operations are exposed via the MCP server, the accepts: schema determines the tool's input parameters:
With accepts:
dyngle:
operations:
get-weather:
description: Get current weather for a city
accepts:
city: { type: string }
units:
type: string
default: "metric"
returns: weather-info
steps:
- curl -s "https://api.example.com/weather?city={{city}}&units={{units}}" => weather-info
The MCP tool will have city and units as input parameters, with validation and defaults applied automatically.
Without accepts:
dyngle:
operations:
run-backup:
description: Run the nightly backup process
returns: result
steps:
- /usr/local/bin/backup.sh => result
The MCP tool will have no input parameters.
See MCP Server for more details.
Best Practices
Use accepts for Public Operations
Operations exposed via MCP or called as sub-operations should define their interfaces. Use the simplified syntax for cleaner definitions:
dyngle:
operations:
deploy-service:
description: Deploy a service to an environment
accepts:
service-name: # string type inferred
environment:
version:
steps:
- echo "Deploying {{service-name}} v{{version}} to {{environment}}"
Provide Sensible Defaults
Use defaults for optional parameters to make operations easier to use:
accepts:
environment:
type: string
default: "development"
verbose:
type: boolean
default: false
Document with Descriptions
While Dyngle's interface syntax doesn't currently support field descriptions, use the operation's description: attribute to document expected inputs:
dyngle:
operations:
process-data:
description: "Process data file. Expects: filename (string), format (json|csv), validate (boolean)"
accepts:
filename: { type: string }
format: { type: string, default: "json" }
validate: { type: boolean, default: true }