Agent Integration
runspec turns any runnable into something an agent can understand and safely use — without any extra code beyond defining your interface in TOML.
There are two ways agents discover runspec runnables:
- Locally via
runspec serve— MCP stdio server exposing every installed runspec-aware runnable as a tool. - Remotely via
runspec jump— SSH+MCP into a configured jump host and run a tool there. See Jump Hosts.
The problem runspec solves
Agents need to know three things about every tool they might call:
- What arguments does it take? Types, required vs optional, valid values.
- What does it do? Enough context to decide whether to call it.
- Can it run this automatically? Or does it need to stop and ask a human first?
Without a structured interface, agents are guessing. With runspec, the answers are machine-readable, always accurate, and derived from the same source the CLI uses — so they can never drift.
Emitting schemas
runspec local --format mcp converts your installed runnables into JSON
Schema tool definitions ready for any agent framework.
runspec local --format mcp
{
"tools": [
{
"name": "greet",
"description": "Greet someone from the command line",
"x-autonomy": "autonomous",
"x-output": "text",
"inputSchema": {
"type": "object",
"properties": {
"name": { "type": "string" },
"loud": { "type": "boolean", "default": false },
"times": { "type": "integer", "default": 1 }
},
"required": ["name"]
}
}
]
}
The agent gets the full interface: argument names, types, defaults, which args are required, autonomy level, and output type — all from the one place you already maintain.
Format options
runspec local --format mcp # MCP (the standard schema format)
runspec local --format openai # OpenAI tool calling
runspec local --format anthropic # Anthropic tool use
runspec local --format mcp --runnable deploy # one runnable only
Autonomy control
The x-autonomy field in every emitted schema declares how an agent runtime
should gate invocation. This is a contract for agent invocation, not a
directive for human users — a human typing the command has already chosen
the action.
| Level | What it means for an agent |
|---|---|
autonomous |
Run freely — no confirmation needed |
confirm |
Stop and confirm with the user before running |
supervised |
Run, but hold the output for human review before acting on it |
manual |
Do not call this tool — hand off to a human entirely |
This is set in your spec:
[deploy]
description = "Deploy to production"
autonomy = "manual"
autonomy-reason = "Irreversible — requires human sign-off"
[compress]
description = "Compress output files"
autonomy = "autonomous"
And surfaces in the emitted schema:
{
"name": "deploy",
"description": "Deploy to production",
"x-autonomy": "manual",
"x-autonomy-reason": "Irreversible — requires human sign-off",
"x-output": "text",
"inputSchema": { ... }
}
A conforming MCP host reads x-autonomy and gates accordingly: blocking
manual tools, prompting before confirm tools, and running autonomous
tools freely.
Per-argument autonomy
Autonomy can also be declared on individual arguments. The most restrictive
level wins — so a confirm-level runnable with a manual-level arg becomes
effectively manual when that arg is used:
[pipeline.args]
input = {type = "path"}
api-key = {type = "str", env = "PIPELINE_API_KEY", autonomy = "manual"}
The runspec library calculates the effective autonomy for you and exposes it on the parsed result:
args = runspec.parse()
print(args.runspec_autonomy) # "manual" if api-key was provided
const args = parse();
console.log(args.__runspec_autonomy__); // "manual" if api-key was provided
Tool-side enforcement
Because host gating isn't universal, runnables that perform destructive actions should also enforce autonomy themselves at runtime. The recommended pattern for a destructive flag:
if args.delete:
if args.runspec_agent and args.runspec_autonomy != "autonomous":
raise SystemExit(
"✗ --delete requires autonomy='autonomous' for agent invocation"
)
# ... proceed
if (args.delete && args.__runspec_agent__ && args.__runspec_autonomy__ !== 'autonomous') {
console.error("✗ --delete requires autonomy='autonomous' for agent invocation");
process.exit(1);
}
This refuses agent invocation unless the spec explicitly permits unattended execution. Human invocation is unaffected.
Discovery
runspec local finds every runspec-aware runnable installed in the
environment and reports them — with no per-tool configuration.
runspec local
Found 3 installed runnable(s):
/home/user/project/mypkg/runspec.toml
deploy Deploy the application [confirm]
process Process input files [confirm]
validate Validate input data [autonomous]
Run 'runspec local --format mcp' to emit MCP tool schemas.
With --format mcp, this becomes a complete tool list ready to hand to an
MCP server or agent framework — no per-tool setup, no skills.md to
maintain.
Runnables must be installed (pip install -e . for Python; npm install
for Node) to appear.
Environment variables for agents
The env field lets sensitive arguments be set via environment variables
rather than passed directly on the command line. Combined with
autonomy = "manual", this keeps secrets out of agent reach entirely:
[deploy.args]
server = {type = "str", env = "DEPLOY_SERVER"}
api-key = {type = "str", env = "DEPLOY_API_KEY", autonomy = "manual"}
region = {type = "str", env = "AWS_REGION", default = "us-east-1"}
The operator sets the environment variables. The agent calls the runnable with zero args. The runnable gets the values it needs. The agent never sees or touches the secrets.
This pattern works in any environment where you can set variables before the agent runs — CI/CD pipelines, container orchestration, Ansible, Docker Compose, system services.
Live MCP server
runspec serve starts an MCP stdio server that exposes every runnable in
your environment as a callable tool. This is the recommended way to connect
any MCP-compatible agent — Claude Desktop, Cursor, or your own agent loop —
to your runnables.
runspec serve
The server reads your runspec config, advertises each runnable as an MCP tool, and runs the corresponding script when the agent calls it. No separate MCP server to write or maintain.
Connecting Claude Desktop
Add an entry to claude_desktop_config.json (typically at
~/Library/Application Support/Claude/claude_desktop_config.json on macOS
or %APPDATA%\Claude\claude_desktop_config.json on Windows):
{
"mcpServers": {
"analytics-pipeline": {
"command": "/home/user/envs/analytics-pipeline/bin/runspec",
"args": ["serve"],
"cwd": "/home/user/projects/analytics"
}
}
}
The key ("analytics-pipeline") is the display name shown in Claude
Desktop. cwd is the directory serve searches for your runspec config —
set it to your project root.
Each virtual environment is its own MCP server. For multiple projects, add one entry per environment — each exposes only its own runnables.
What the agent sees
The agent receives tool definitions with full argument schemas, descriptions, and autonomy levels — everything from your TOML. Calling a tool runs the script and returns its stdout. On non-zero exit, the tool returns an error with the exit code, stdout, and stderr intact.
Agent-aware output
Scripts called via serve receive RUNSPEC_AGENT=1 in their environment.
Read it through the parsed args to switch between human and machine output:
args = runspec.parse()
if args.runspec_agent:
print(json.dumps({"status": "ok", "deployed_to": str(args.env)}))
else:
print(f"✓ Deployed to {args.env}")
const args = parse();
if (args.__runspec_agent__) {
console.log(JSON.stringify({ status: 'ok', deployed_to: args.env }));
} else {
console.log(`✓ Deployed to ${args.env}`);
}
When the runnable's output field is set to "json", set it in your spec
and agents will get x-output: "json" in the schema — they know to parse
the response rather than display it as text.
Logging in agent mode
When [config.logging] is configured, the routing is the same as CLI
mode — there's no separate agent code path. runspec serve captures the
subprocess's stdout as the MCP tool response, so every logger.info(...)
line reaches the calling agent automatically.
- stdout → MCP response. INFO and below land here (plain message,
reads like
print()). The agent sees them as the tool's reply. - stderr → forwarded to the agent's logs. WARNING and above with a
LEVEL:prefix. Stays at WARNING regardless of--debug. - File logging always on.
{package_dir}/logs/{runnable}.logis the audit trail — INFO by default, DEBUG with--debug. --debugis the runtime knob. An agent can pass--debug(or setRUNSPEC_DEBUG=1) on a one-off invocation to flip DEBUG on across stdout and the file. There is nolevelknob — silencing INFO would blank out the MCP response.
You write the same logger.info(...) calls; runspec routes them
appropriately. See Logging for the full picture, including
the sensitive-data redaction filter.
Remote execution
For tools that need to run on a different machine, configure
[config.jump-hosts] and use runspec jump. The agent talks to a local
runspec jump process, which SSHes to the remote and speaks MCP with
runspec serve over there.
runspec jump --list-jump-hosts # list configured hosts
runspec jump prod-app # list tools on prod-app
runspec jump prod-app deploy -- --env production # run a tool on prod-app
The full model — SSH argv construction, hosts filtering, run_as /
become_method / become_flags privilege escalation, the trust model — is
on the dedicated Jump Hosts page.
Replaces the old runspec-registry
Earlier versions of runspec shipped an HTTP registry service for tool
discovery. It was removed in 0.7.0 in favour of [config.jump-hosts] +
SSH + MCP. The runspec-registry PyPI package is archived.
Practical patterns
Validate before exposing
Run runspec local in CI before deploying. It reports missing descriptions,
undeclared autonomy levels, required args without descriptions, and other
agent-facing gaps. Exits with code 1 on errors:
# .github/workflows/ci.yml
- name: Validate runspec
run: runspec local
Emit schemas for inspection
runspec local --format mcp # exactly what the agent will see
runspec local --format openai # for an OpenAI-shaped framework
Check before running (from an agent loop)
import json, subprocess
schemas = json.loads(subprocess.check_output(["runspec", "local", "--format", "mcp"]))
for tool in schemas["tools"]:
autonomy = tool.get("x-autonomy", "confirm")
if autonomy == "manual":
print(f" {tool['name']}: skip — requires human operator")
elif autonomy == "confirm":
print(f" {tool['name']}: ask user first")
else:
print(f" {tool['name']}: ok to run")
What agents see
A well-specified runnable gives an agent everything it needs without any extra documentation:
| Spec field | Agent use |
|---|---|
description |
Decides whether this is the right tool |
args[].description |
Knows what value to pass |
args[].type |
Passes the right type |
args[].options |
Picks from valid choices |
args[].required |
Knows what it must provide |
args[].default |
Knows what happens if it doesn't provide |
args[].env |
Knows there's an env var fallback |
x-autonomy |
Decides whether to run or ask first |
x-autonomy-reason |
Explains to the user why confirmation is needed |
x-output |
Knows whether to display output as text or parse it as JSON |
The more of these you fill in, the more reliably an agent can use your runnable without human help.
Tip
Run runspec local to see exactly which fields are missing. The output
maps directly to gaps in what agents can infer.