YAML Tool Format¶
Tools are defined in YAML files. Each file declares a list of tools, optionally scoped to one or more groups.
Be explicit
Fill out as much detail in the tool and parameter descriptions as all info is sent to the LLM for context on how to use it.
Basic structure¶
groups: [mygroup] # optional — defaults to [public]
tools:
- fn: mymodule:my_fn # python callable (fn: or exec: required, not both)
name: my-tool # optional for fn, required for exec
description: "..." # optional for fn, recommended for exec
example: "..." # optional — shown to the LLM as a usage example
params: # optional for fn (introspected), required for exec
- name: arg
type: str
required: true
Each tool uses either fn to call a Python callable or exec to run a shell command — see Python Tools and Exec Tools respectively.
groups¶
Controls which users can access the tools in this file. Can be set at the file level and overridden per tool:
groups: [engineering] # all tools in this file default to [engineering]
tools:
- fn: mymodule:tool_a # inherits [engineering]
- fn: mymodule:tool_b
groups: [admin] # overrides to [admin] for this tool only
Omit groups entirely to default to [public] (accessible to all users).
name, description, and example¶
For fn tools name and description are optional — MCC introspects them from the callable. For exec tools, name is required and description should be set manually since there's no callable to inspect.
example is optional for all tool types. Use it to give the LLM a concrete usage example shown directly in the tool signature.
Description and example directly affect LLM behavior
The tool signature is the only information the LLM has about what a tool does and when to use it. A missing or vague description causes the LLM to misuse the tool or skip it entirely. A good description explains the tool's purpose and when to reach for it; a good example shows a realistic invocation so the LLM can pattern-match against it.
tools:
- fn: mymodule:send_email # name → "send_email", description → __doc__
- name: compress # exec tools must declare name explicitly
exec: "gzip {{ file | quote }}"
description: Compress a file with gzip
example: "compress file=/var/log/app.log"
params¶
Params define what the tool accepts. For fn tools they're introspected automatically. For exec tools they must be declared explicitly.
params:
- name: message
type: str
required: true
description: The message to send
example: "Hello, world!"
- name: retries
type: int
default: 3
description: Number of retry attempts
See Parameters for types, defaults, and overrides.
Tool key¶
Each tool gets a key derived from its groups and name. public and admin sort to the front:
| groups | name | key |
|---|---|---|
[public] |
greet |
public.greet |
[admin] |
shell |
admin.shell |
[admin, ops] |
deploy |
admin.ops.deploy |
Use the key with execute().
Loading multiple files¶
Register YAML files or directories in settings.local.yaml:
tools:
- mytools.yaml
- path/to/more_tools.yaml
- path/to/tools_dir # loads all *.yaml files in this directory
Runtime options¶
The following fields apply to both fn and exec tools. They control the subprocess environment and execution constraints. Tool-specific options (python: for fn, stdin: for exec) are covered in their respective pages.
Quick reference¶
| Field | Type | Default | Description |
|---|---|---|---|
env |
dict |
{} |
Explicit environment variables |
env_file |
str |
none | Path to a .env file to source |
env_passthrough |
bool |
false |
Inherit the parent process environment |
transform |
str or list[str] |
none | Shell pipeline to filter output before returning to the LLM |
cwd |
str |
inherit | Working directory for the subprocess |
cache_ttl |
int |
none | Cache responses for N seconds; omit to disable caching |
limits |
dict |
none | Unix resource limits (memory, CPU, etc.) and wall-clock timeout |
env, env_file, and env_passthrough¶
Control what environment variables the subprocess receives. See Environment Variables for the full reference — including env: key/value pairs, env_file: dotenv files, combining them, and the env_passthrough flag that controls whether the subprocess inherits the parent environment.
transform¶
Pipe tool output through a shell command before it's returned to the LLM. Use this to strip noise from large HTML, XML, or JSON responses so less content lands in the LLM's context window.
tools:
# single command
- name: fetch-article
curl: "https://example.com/{{ slug }}"
transform: "pup 'article p text{}'"
# multi-step pipeline (list is joined with |)
- name: search
curl: "https://api.example.com/search?q={{ query }}"
transform:
- "jq -r '.hits[].title'"
- "head -c 4000"
The transform value is Jinja-templated with the same kwargs as the main command:
tools:
- name: extract
curl: "https://example.com/{{ path }}"
transform: "jq -r '.{{ field }}'"
params:
- name: path
type: str
required: true
- name: field
type: str
default: data
Works with both exec/curl tools and fn tools. If the tool call fails, the transform is skipped and the error is returned unchanged. The transform shares the tool's limits.timeout budget.
cwd¶
Set the working directory for the subprocess. Defaults to the MCC server's working directory.
tools:
- name: build
exec: make all
cwd: /srv/myproject
- fn: mypackage.jobs:run
cwd: /data/workspace
Useful when a tool reads relative paths or relies on a specific project root.
cache_ttl¶
Cache tool responses for N seconds. When set, identical calls (same tool key and parameters) return the cached result without re-executing the tool. Useful for slow or rate-limited tools like OSINT lookups.
Omit cache_ttl entirely to disable caching for a tool (the default).
tools:
- fn: osint.crtsh:search
cache_ttl: 300 # cache certificate transparency results for 5 minutes
- fn: osint.abuseipdb:check_ip
cache_ttl: 60 # IP reputation data — shorter TTL, changes more often
The cache backend is configured in settings.yaml under cache.backend (defaults to in-memory; set to a Redis URI for shared caching across instances).
limits¶
Cap CPU time, memory, file sizes, open file descriptors, and wall-clock timeout. See Resource Limits for a full reference.
tools:
- name: sandbox
exec: python {{ script | quote }}
limits:
timeout: 30 # kill after 30 wall-clock seconds
mem_mb: 256 # max virtual memory
cpu_sec: 10 # max CPU time in seconds
fsize_mb: 50 # max file write size
nofile: 64 # max open file descriptors
params:
- name: script
type: str
required: true
timeout is the only limit that applies on all platforms; the others are Unix only.