Recipes API
Recipes system for dcc-mcp-core skills (issues #428, #616). Formalizes the metadata.dcc-mcp.recipes sibling-file key per the #356 sibling-file pattern and supports both legacy Markdown anchors and structured YAML recipe packs.
Exported symbols: RecipeDefinition, get_recipe_content, get_recipes_path, get_recipes_paths, parse_recipe_anchors, load_recipe_pack, list_recipe_entries, find_recipe_entry, validate_recipe_inputs, register_recipes_tools
get_recipes_path
get_recipes_path(metadata: Any) -> str | NoneExtract the recipes file path from a SkillMetadata object. Supports both flat ("dcc-mcp.recipes") and nested ("dcc-mcp": {"recipes": ...}) forms. Returns absolute path resolved relative to skill's skill_path.
get_recipes_paths
get_recipes_paths(metadata: Any) -> list[str]Extract all recipe sibling file paths. The metadata value may be a filename, glob string, or list of filenames/globs.
parse_recipe_anchors
parse_recipe_anchors(recipes_path: str) -> list[str]Return the list of anchor names from a RECIPES.md file. Anchors are ## headings.
get_recipe_content
get_recipe_content(recipes_path: str, anchor: str) -> str | NoneReturn the Markdown content of a specific anchor section (from ## <anchor> up to the next ## heading).
Structured Recipe Packs
YAML recipe packs use a recipes: list:
recipes:
- name: build_pbr_material
dcc: maya
description: Build a PBR material network.
inputs_schema:
type: object
required: [material_name]
properties:
material_name:
type: string
steps:
- tool: maya_materials__create
arguments:
name: ${material_name}
output_contract:
type: object
required: [material_name, changed]
properties:
material_name:
type: string
changed:
type: boolean
const: trueoutput_contract is a Draft 2020-12 schema, not a label: after the tool runs, validate the observed result against it. Contracts that cannot fail are useless, so require the fields that prove the operation happened — for example a changed: {const: true} receipt flag, post-operation element counts, and for UV work a uv_count: {minimum: 1} bound so a mesh left without UVs fails the contract. Legacy packs may still carry a plain string label, which callers treat as documentation only.
load_recipe_pack(path) -> list[RecipeDefinition]
list_recipe_entries(skill_metadata) -> list[dict]
validate_recipe_inputs(recipe, inputs) -> list[str]Step materialization
recipes__apply validates inputs against inputs_schema and returns the steps verbatim: Core does not substitute ${param} placeholders and does not apply schema defaults. The caller materializes each step argument:
step.arguments[k] == "${name}" -> inputs[name] if present
else inputs_schema.properties[name].defaultPack authors must therefore keep every placeholder resolvable: an argument named by a placeholder is either listed in required or declares a default.
validate_recipe_inputs evaluates the published inputs_schema as a Draft 2020-12 contract (including same-resource nested/root JSON-Pointer $ref, bounds, enums/constants, additionalProperties, unevaluatedProperties/ unevaluatedItems, and composition such as oneOf). Applicator annotations are merged before unevaluated assertions are applied. It is implemented in Core without a runtime jsonschema dependency. Malformed or resource-heavy schemas (over 128 schema nesting levels, 10,000 schema nodes, or 1 MiB of JSON) fail closed; instance validation has the same bounded-depth/node behavior. Resource identifiers ($id) and dynamic references are not admitted until resource-relative scope is implemented; schemas using them fail closed. Returned errors are path-oriented without echoing input values.
register_recipes_tools
register_recipes_tools(server, *, skills: list, dcc_name="dcc") -> NoneRegister recipes__list, recipes__search, recipes__get, recipes__validate, and recipes__apply MCP tools on server. Call before server.start().
from dcc_mcp_core import create_skill_server, McpHttpConfig, scan_and_load
from dcc_mcp_core.recipes import register_recipes_tools
loaded, _ = scan_and_load(dcc_name="maya")
server = create_skill_server("maya", McpHttpConfig(port=8765))
register_recipes_tools(server, skills=loaded, dcc_name="maya")
handle = server.start()