Turn any command-line tool into an AI tool with simple YAML.
Enact lets AI models safely discover and execute command-line tools. Instead of writing complex integrations, you define tools with simple YAML:
name: hello-world
description: "Greets the world"
command: "echo 'Hello, ${name}!'"That's it. This tool can now be:
- 🔍 Discovered by AI models searching for "greeting"
- 🚀 Executed safely without local installation
- 🔐 Verified with cryptographic signatures
- 📌 Versioned with semantic versioning
Install:
npm install -g @enactprotocol/cliCreate your first tool:
enact init my-tool
enact publish tool.yamlNow any AI using MCP can discover and use your tool!
enact: "1.0.0"
name: enact/text/analyzer
description: "Analyzes text statistics"
command: "npx text-stats@1.0.0 '${text}'"enact: "1.0.0"
name: enact/markdown/converter
description: "Converts markdown to HTML"
from: "node:18-alpine"
command: "npx markdown-it@14.0.0 '${input}'"
timeout: "30s"
license: "MIT"
inputSchema:
type: object
properties:
input:
type: string
description: "Markdown content"
required: ["input"]Tools use filepath-style names for organization:
enact/text/analyzer- Official text toolsacme-corp/internal/processor- Company toolsusername/personal/utility- Personal tools
Any shell command works:
# NPX with versions (recommended)
command: "npx prettier@3.3.3 --write '${file}'"
# Python tools with UVX
command: "uvx black@24.4.2 '${file}'"
# Docker containers
command: "docker run pandoc/core:3.1.11 -f markdown -t html"
# API calls
command: "curl -s 'https://api.example.com/v1/process' -d '${json}'"
# With container image specification
from: "node:18-alpine"
command: "npx prettier@3.3.3 --write '${file}'"
# Python environment
from: "python:3.11-slim"
command: "python -m pip install requests && python -c 'import requests; print(requests.get(\"${url}\").text)'"Tools can be signed by multiple parties:
signatures:
- signer: "71e02e2c-148c-4534-9900-bd9646e99333"
algorithm: "sha256"
type: "ecdsa-p256"
value: "drwaN6pjbV24JeGOPmQhe8mgQTD1f9LZ4qRsKAV5/8jODTzTbcyToQ36lt9uv06S0Y60IdchR/40WLtAUc3Bdg=="
created: "2025-07-08T18:42:15.501+00:00"
role: "author"
- signer: "security-team"
algorithm: "sha256"
type: "ecdsa-p256"
value: "MEUCIDxNLAzYZQAul2/uhPkdjxNrNwkFWy2qYOGV5pWIpdabAiEB..."
created: "2025-07-08T20:30:00.000+00:00"
role: "reviewer"Tools in the same package share API keys and secrets:
name: "acme-corp/discord/bot-maker"
env:
DISCORD_API_KEY:
description: "Discord bot API key"
source: "https://discord.com/developers → Bot → Token"
required: trueAll Discord tools (discord/webhook, discord/bot-manager) share the same credentials stored in ~/.enact/env/acme-corp/discord/.env.
Specify the container image for command execution:
name: "acme-corp/python/data-processor"
from: "python:3.11-slim"
command: "python -m pip install pandas numpy && python process.py '${data}'"
# Or use custom images
from: "ghcr.io/company/custom-env:v2.1.0"
command: "analyze-data '${input}'"
# Default behavior (no from field)
command: "echo 'Hello World'" # Runs on system shellThe from field provides:
- Reproducible environments - Same runtime across different systems
- Dependency isolation - Tools don't interfere with each other
- Version control - Pin exact image versions for consistency
- Security - Run tools in isolated containers
Help AI models understand tool safety:
enact: "1.0.0"
annotations:
readOnlyHint: true # Safe, no system changes
destructiveHint: false # Won't break anything
openWorldHint: true # Connects to internet
idempotentHint: true # Multiple calls = same result| Feature | MCP | Enact |
|---|---|---|
| Tool Communication | ✅ | ✅ Uses MCP |
| Tool Execution | ❌ | ✅ Command-based |
| Tool Discovery | ❌ | ✅ Semantic search |
| Versioning | ❌ | ✅ Semantic versions |
| Security | ❌ | ✅ Crypto signatures |
# Tool lifecycle
enact init my-tool # Create new tool
enact validate tool.yaml # Validate definition
enact test tool.yaml # Test locally
enact sign tool.yaml # Add signature
enact publish tool.yaml # Publish to registry
# Discovery
enact search "text analysis" # Find tools
enact verify tool.yaml # Check signaturesWhile the Enact protocol is implementation-agnostic, the reference CLI uses Dagger for secure containerized execution:
# When you specify a container image
from: "python:3.11-slim"
command: "uvx black@24.4.2 '${file}'"Behind the scenes:
- Dagger pipeline creates an isolated container from the specified image
- Code execution happens entirely within the container boundary
- File system isolation prevents tools from accessing host system
- Network policies can be applied per-tool for additional security
- Resource limits are enforced at the container level
Security benefits:
- ✅ Zero host access - Tools can't modify your system
- ✅ Dependency isolation - No version conflicts between tools
- ✅ Reproducible environments - Same runtime across all machines
- ✅ Resource containment - Memory/CPU limits prevent resource exhaustion
- ✅ Audit trail - All execution happens in logged, traceable containers
Why Dagger specifically:
- Programmable - Define execution pipelines in code
- Portable - Works across different container runtimes
- Cacheable - Efficient layer caching for fast execution
- Composable - Easy to chain multiple tools together
This architecture ensures that even untrusted tools can be executed safely, making Enact suitable for enterprise environments where security is paramount.
- Use exact versions:
npx prettier@3.3.3notnpx prettier - Hierarchical names:
company/category/tool-name - Include license: Use SPDX identifiers like
"MIT" - Add input schemas: Help AI models use tools correctly
- Set timeouts: Match expected execution time
- Tag appropriately:
["text", "analysis", "nlp"] - Pin container images: Use specific tags like
python:3.11-slimnotpython:latest - Use minimal images: Prefer
alpineorslimvariants for faster startup
enact: "1.0.0"
name: enact/text/word-count
description: "Counts words in text"
command: "echo '${text}' | wc -w"
inputSchema:
type: object
properties:
text: {type: string}
required: ["text"]enact: "1.0.0"
name: enact/code/prettier
description: "Formats JavaScript/TypeScript code"
from: "node:18-alpine"
command: "npx prettier@3.3.3 --write '${file}'"
inputSchema:
type: object
properties:
file: {type: string, description: "File to format"}
required: ["file"]
annotations:
destructiveHint: true # Modifies files in placeenact: "1.0.0"
name: enact/web/markdown-crawler
description: "Extracts content as markdown"
from: "python:3.11-slim"
command: "uvx markdown-crawler@2.1.0 '${url}'"
inputSchema:
type: object
properties:
url: {type: string, format: uri}
required: ["url"]
annotations:
openWorldHint: true # Connects to internet
readOnlyHint: true # Safe, no system changesFor Developers:
- Turn any CLI tool into an AI tool instantly
- No complex integrations or API servers
- Version and secure your tools
- Test locally before publishing
For AI Applications:
- Discover tools semantically (
search "image resize") - Execute safely in Dagger-powered containers
- Trust verified tools with cryptographic signatures
- Scale without managing infrastructure or security concerns
For Enterprises:
- Control tool approval with multi-party signatures
- Audit all tool usage and versions
- Ensure reproducible, containerized environments
- Manage security policies with Dagger-based isolation
- Install:
npm install -g @enactprotocol/cli - Create:
enact init my-first-tool - Publish:
enact publish tool.yaml - Use: AI models can now discover and execute your tool!
# REQUIRED FIELDS
name: string # Tool identifier with hierarchical path (required)
description: string # Human-readable description (required)
command: string # Shell command to execute with versions (required)
# RECOMMENDED FIELDS
from: string # Container image to run the command on (optional, defaults to system shell)
timeout: string # Go duration format: "30s", "5m", "1h" (default: "30s")
tags: [string] # Tags for search and categorization
license: string # SPDX License identifier (e.g., "MIT", "Apache-2.0", "GPL-3.0")
outputSchema: object # Output structure as JSON Schema (strongly recommended)
# OPTIONAL FIELDS
version: string # Tool definition version for tracking changes
enact: string # Version of enact being used
resources: # Resource requirements
memory: string # System memory needed (e.g., "16Gi", "32Gi")
gpu: string # GPU memory needed (e.g., "24Gi", "48Gi")
disk: string # Disk space needed (e.g., "100Gi", "500Gi")env:
SOME_VARIABLE_NAME:
description: string # What this variable is for (required)
source: string # Where to get this value (required)
required: boolean # Whether this is required (required)
default: string # Default value if not set (optional)inputSchema: object # Input parameters as JSON Schema (recommended)
outputSchema: object # Output structure as JSON Schema (strongly recommended)doc: string # Markdown documentation (optional)
authors: # Tool creators (optional)
- name: string # Author name (required)
email: string # Author email (optional)
url: string # Author website (optional)
examples: # Test cases and expected outputs (optional)
- input: object # Input parameters (optional, omit for tools with no inputs)
output: any # Expected output (optional)
description: string # Test description (optional)annotations: # MCP-aligned behavior hints (all default to false)
title: string # Human-readable display name (optional)
readOnlyHint: boolean # No environment modifications
destructiveHint: boolean # May make irreversible changes
idempotentHint: boolean # Multiple calls = single call
openWorldHint: boolean # Interacts with external systemssignatures: # Cryptographic signatures (optional, supports multiple signers)
- signer: string # Signer identifier (UUID or human-readable name) (required)
algorithm: string # Hash algorithm: "sha256" (required)
type: string # Signature type: "ecdsa-p256" (required)
value: string # Base64 encoded signature (required)
created: string # ISO timestamp (required)
role: string # Signer role: "author", "reviewer", "approver", etc. (optional)
- signer: string # Additional signers
algorithm: string
type: string
value: string
created: string
role: stringUse the x- prefix for custom fields:
name: "company/tool/example"
description: "Example tool"
command: "echo 'Hello ${name}'"
# Custom extensions
x-internal-id: "tool-12345"
x-team-owner: "platform-team"
x-cost-center: "engineering"Enact cryptographically signs only a subset of critical security fields to prevent tampering and ensure deterministic, reproducible signatures. These fields are now:
- Listed alphabetically for deterministic ordering
- Empty values are excluded (null, empty string, empty object/array)
Critical fields included in the signature:
annotations— Security behavior hintscommand— The actual execution payloaddescription— What the tool claims to doenact— Protocol version securityenv— Environment variablesfrom— Container image (critical for security)inputSchema— Defines the attack surfacename— Tool identity (prevents impersonation)timeout— Prevents DoS attacksversion— Tool version for compatibility
Note: Only non-empty values are included in the canonical JSON for signing. This ensures signatures are consistent and not affected by empty or missing fields.
Example (deterministic, critical-fields-only, sorted JSON):
{
"annotations": { ... },
"command": "npx prettier@3.3.3 --write '${file}'",
"description": "Formats JavaScript/TypeScript code",
"enact": "1.0.0",
"env": { ... },
"from": "node:18-alpine",
"inputSchema": { ... },
"name": "enact/code/prettier",
"timeout": "30s",
"version": "1.2.3"
}- The signature is computed over this canonical JSON, with keys sorted alphabetically.
- Any field that is empty or missing is omitted from the signed data.
- 💬 Discord - Chat with developers
- 🐛 GitHub - Report issues
- 📖 Documentation - Full specification
- 🌟 Registry - Browse tools (coming soon)
MIT License - see LICENSE for details.
© 2025 Enact Protocol Contributors
"Perfection is achieved not when there is nothing more to add, but when there is nothing left to take away." — Antoine de Saint-Exupéry