onetool Configuration¶
Complete reference for onetool.yaml configuration.
TL;DR¶
- Use
onetool init -c ~/.onetoolfor first-time setup. - Core config lives in
onetool.yaml; secrets live insecrets.yaml. - Relative include paths resolve from the directory containing
onetool.yaml. - Start with
version,include,tools_dir,servers,tools,security. - Validate with
onetool init validate -c ~/.onetool/onetool.yaml.
CLI Flags¶
Configuration is specified via CLI flags — there is no automatic global or project config discovery.
| Flag | Short | Required | Description |
|---|---|---|---|
--config PATH |
-c |
Yes (server) | Path to onetool.yaml config file or directory |
--secrets PATH |
-s |
No | Path to secrets.yaml. If omitted, no secrets are loaded |
Config path resolution: If PATH ends in .yaml/.yml it is used as the config file directly; otherwise onetool.yaml is appended to the directory path.
Include path resolution: All relative paths in include:, tools_dir:, etc. resolve from the parent directory of the config file (i.e., the directory containing onetool.yaml).
Windows paths: Use backslashes or forward slashes, e.g., C:\Users\name\.onetool\onetool.yaml.
First Run Setup¶
Run onetool init to create a config interactively:
onetool init # Create onetool.yaml in current directory
onetool init -c .onetool # Create .onetool/onetool.yaml
onetool init -c .onetool/onetool.yaml # Explicit file path
onetool init validate -c .onetool/onetool.yaml # Validate + show status
onetool init opens a TUI (interactive checkbox) to select which extensions to materialise. Existing files are backed up to .bak automatically. Running non-interactively (e.g., in CI) writes a minimal onetool.yaml with version: 2.
YAML Schema¶
version: 2 # Config schema version (required)
include: # External config files to merge
- prompts.yaml
- snippets.yaml
- servers.yaml
tools_dir: # Tool discovery patterns
- src/ottools/*.py
env: # Default subprocess environment variables
LANG: en_US.UTF-8
log_level: INFO # DEBUG, INFO, WARNING, ERROR
security: # Code validation settings
validate_code: true
enabled: true
servers: {} # External MCP servers
tools: {} # Pack-specific configuration
alias: {} # Function aliases
snippets: {} # Reusable code templates
prompts: {} # Inline prompts (overrides included)
Config Includes¶
Compose configuration from multiple files:
version: 2
include:
- config/prompts.yaml # Relative to config parent dir
- config/snippets.yaml
- local-snippets.yaml # Project-only additions
servers:
local_dev:
type: stdio
command: python
Include resolution:
- Absolute paths: used as-is
- Tilde paths (
~/...): expanded to home directory - Relative paths: checked in config parent dir first, then falls back to package-bundled defaults
Merge behaviour:
- Files are merged left-to-right (later files override earlier)
- Inline content in the main file overrides everything
Bundled defaults are not auto-loaded
snippets.yaml, diagram.yaml, security.yaml, and the other bundled config files are
not loaded automatically — they only activate when listed under include:.
onetool init writes an onetool.yaml that already includes all of them, so this only
matters if you write your own config from scratch.
Skills (.onetool/skills/) are the exception: they are auto-discovered at runtime without
any include: entry.
Pack Configuration¶
Pack settings go under tools.<pack_name> in onetool.yaml.
aws¶
| Field | Type | Default | Range | Description |
|---|---|---|---|---|
profile |
string | null | null |
- | Active AWS profile name |
region |
string | null | null |
- | Active AWS region |
timeout |
int | 30 |
>=1 |
Boto3 API call timeout seconds |
roles |
object |
{} |
- | User-defined role name -> server short names |
servers |
object |
{} |
- | Additional/override AWS MCP servers |
brave¶
| Field | Type | Default | Range | Description |
|---|---|---|---|---|
timeout |
float | 60.0 |
1.0-300.0 |
Request timeout in seconds |
chrome_util¶
No pack-specific tools.chrome_util settings.
context7¶
| Field | Type | Default | Range | Description |
|---|---|---|---|---|
timeout |
float | 30.0 |
1.0-120.0 |
Request timeout in seconds |
convert¶
No pack-specific tools.convert settings.
db¶
| Field | Type | Default | Range | Description |
|---|---|---|---|---|
max_chars |
int | 4000 |
100-100000 |
Maximum characters in query result output |
diagram¶
| Field | Type | Default | Range | Description |
|---|---|---|---|---|
backend.type |
"kroki" |
"kroki" |
fixed | Backend type |
backend.remote_url |
string | https://kroki.io |
- | Remote Kroki service URL |
backend.self_hosted_url |
string | http://localhost:8000 |
- | Self-hosted Kroki URL |
backend.prefer |
enum | remote |
remote \| self_hosted \| auto |
Backend preference |
backend.timeout |
float | 30.0 |
1.0-120.0 |
Backend request timeout |
policy.rules |
string | built-in policy text | - | Diagram generation policy text |
policy.preferred_format |
enum | svg |
svg \| png \| pdf |
Preferred output format |
policy.preferred_providers |
string[] | ["mermaid","d2","plantuml"] |
- | Preferred providers in order |
output.dir |
string | diagrams |
- | Output directory |
output.naming |
string | {provider}_{name}_{timestamp} |
- | Filename template |
output.default_format |
enum | svg |
svg \| png \| pdf |
Default output format |
output.save_source |
bool | true |
- | Save source next to rendered output |
instructions |
object |
{} |
- | Provider guidance overrides |
templates |
object |
{} |
- | Named template references |
excel¶
No pack-specific tools.excel settings.
file¶
| Field | Type | Default | Range | Description |
|---|---|---|---|---|
allowed_dirs |
string[] | [] |
- | Allowed dirs (empty = cwd only) |
exclude_patterns |
string[] | [".git","node_modules","__pycache__",".venv","venv"] |
- | Excluded path patterns |
max_file_size |
int | 10000000 |
1000-100000000 |
Max file size in bytes |
max_list_entries |
int | 1000 |
10-10000 |
Max entries for list/tree |
backup_on_write |
bool | true |
- | Create .bak before overwrite |
use_trash |
bool | true |
- | Move deleted files to trash |
relative_paths |
bool | true |
- | Return relative paths in output |
ground¶
| Field | Type | Default | Range | Description |
|---|---|---|---|---|
model |
string | gemini-2.5-flash |
- | Gemini model for grounded search |
mem¶
| Field | Type | Default | Range | Description |
|---|---|---|---|---|
db_path |
string | mem.db |
- | SQLite path for memory store |
model |
string | text-embedding-3-small |
- | Embedding model |
base_url |
string | https://openrouter.ai/api/v1 |
- | OpenAI-compatible embedding API base |
dimensions |
int | 1536 |
- | Embedding dimensions |
search_limit |
int | 10 |
1-100 |
Default max search results |
search_extract |
int | 200 |
>=0 |
Extract length per result (0 = full) |
redaction_enabled |
bool | true |
- | Enable redaction on write |
redaction_patterns |
string[] | [] |
- | Extra regex redaction patterns |
tags_whitelist |
string[] | [] |
- | Allowed tag prefixes |
decay_half_life_days |
int | 30 |
>=1 |
Importance decay half-life |
allowed_file_dirs |
string[] | [] |
- | Allowed dirs for mem file I/O |
exclude_file_patterns |
string[] | built-in defaults | - | Excluded paths for mem file I/O |
max_embedding_tokens |
int | 8191 |
>=1 |
Max tokens per embedding input |
read_cache_max_size |
int | 128 |
>=0 |
Read cache size (0 = off) |
read_cache_ttl_seconds |
int | 300 |
>=0 |
Read cache TTL (0 = no expiry) |
embeddings_enabled |
bool | false |
- | Enable semantic embeddings |
embeddings_async |
bool | true |
- | Generate embeddings async |
ot¶
No pack-specific tools.ot settings.
ot_forge¶
No pack-specific tools.ot_forge settings.
ot_llm¶
| Field | Type | Default | Range | Description |
|---|---|---|---|---|
base_url |
string | "" |
- | OpenAI-compatible API base URL |
model |
string | "" |
- | Default model for transforms |
timeout |
int | 30 |
- | API timeout in seconds |
max_tokens |
int | null | null |
- | Max response tokens (null = no limit) |
ot_secrets¶
No pack-specific tools.ot_secrets settings.
ot_timer¶
No pack-specific tools.ot_timer settings.
package¶
| Field | Type | Default | Range | Description |
|---|---|---|---|---|
timeout |
float | 30.0 |
1.0-120.0 |
Request timeout in seconds |
play_util¶
No pack-specific tools.play_util settings.
ripgrep¶
| Field | Type | Default | Range | Description |
|---|---|---|---|---|
timeout |
float | 60.0 |
1.0-300.0 |
Command timeout in seconds |
relative_paths |
bool | true |
- | Return relative paths in output |
whiteboard¶
No pack-specific tools.whiteboard settings.
web¶
| Field | Type | Default | Range | Description |
|---|---|---|---|---|
timeout |
float | 30.0 |
1.0-120.0 |
Request timeout in seconds |
max_length |
int | 50000 |
1000-500000 |
Maximum content length in chars |
worktree¶
| Field | Type | Default | Range | Description |
|---|---|---|---|---|
workspace_dir |
string | ../{repo}-work/{task_id} |
- | Worktree dir template |
branch_name |
string | {task_id} |
- | Branch name template |
launch_cmd |
string | cd {workspace_dir} && claude |
- | Launch command template |
ot_cmd |
string | worktree.info() |
- | First tool call instruction |
prepare |
string[] | [] |
- | Post-create shell commands |
commit.types |
string[] | ["feat","fix","refactor","perf","docs","test","build","ci","chore","style","revert"] |
- | Allowed conventional commit types |
commit.scopes |
string[] | [] |
- | Project commit scopes |
Example:
tools:
aws:
profile: dev
timeout: 45
diagram:
backend:
prefer: auto
output:
dir: docs/diagrams
ripgrep:
timeout: 120
relative_paths: true
ot_llm:
model: openai/gpt-4o-mini
Secrets Configuration¶
API keys stored separately in secrets.yaml (gitignored). Pass the path via --secrets:
# secrets.yaml — values are literal, no ${VAR} expansion
BRAVE_API_KEY: "your-brave-api-key"
OPENAI_API_KEY: "sk-..."
CONTEXT7_API_KEY: "your-context7-key"
GEMINI_API_KEY: "your-gemini-key"
DATABASE_URL: "postgresql://user:pass@localhost/db"
If --secrets is omitted, no secrets file is loaded. Tools that require API keys will report a configuration error when called.
Accessing Secrets in Tools¶
from ot.config.secrets import get_secret
api_key = get_secret("BRAVE_API_KEY")
if not api_key:
return "Error: BRAVE_API_KEY not configured in secrets.yaml"
Encrypting Secrets at Rest¶
Values in secrets.yaml can be encrypted using age encryption. This is opt-in — plain files continue to work without any changes. Once set up, decryption is transparent: OneTool decrypts values in memory when secrets are loaded, and your tools see the plaintext as normal.
Setup (once per machine):
# 1. Generate an age X25519 identity and store it in the OS keychain
>>> ot_secrets.init(label="my-machine")
# 2. Encrypt all plain values in your secrets file
>>> ot_secrets.encrypt(file="~/.onetool/secrets.yaml")
After encryption, secrets.yaml looks like:
# Encrypt values with: >>> ot_secrets.encrypt(file=<this file>)
BRAVE_API_KEY: "age1enc:YWdlLWVuY3J5cHRpb24ub3JnL3Yx..."
OPENAI_API_KEY: "age1enc:YWdlLWVuY3J5cHRpb24ub3JnL3Yy..."
The file is safe to inspect — values cannot be recovered without the private key in your OS keychain. Encrypted values are safe to commit to version control if needed.
How it works:
- The private key is stored in the OS keychain (macOS Keychain, Windows Credential Locker, GNOME libsecret) — never on disk
age1enc:values are decrypted in memory automatically whensecrets.yamlis loaded- Plain and encrypted values can coexist in the same file
- Keychain access is lazy: if no
age1enc:values are present, the keychain is never touched
Managing encrypted secrets:
# Check identity status and count encrypted/plain values
>>> ot_secrets.status(file="~/.onetool/secrets.yaml")
# Scan for any unencrypted values (safe to run before committing)
>>> ot_secrets.audit(file="~/.onetool/secrets.yaml")
# Rotate to a new key (re-encrypts all values)
>>> ot_secrets.rotate(file="~/.onetool/secrets.yaml")
Headless / CI environments: This is a local-dev security feature. CI/CD should continue using environment variables (existing behavior). Plain secrets.yaml files are completely unaffected — encryption only triggers when age1enc: values are present.
External MCP Servers¶
Proxy external MCP servers through OneTool. Supports both stdio (local process) and HTTP (remote server) transports.
Stdio Servers¶
Local MCP servers running as subprocesses:
servers:
github:
type: stdio
command: npx
args: ["-y", "@anthropic-ai/github-mcp-server@latest"]
timeout: 30
chrome-devtools:
type: stdio
command: npx
args: ["-y", "@anthropic-ai/chrome-devtools-mcp@latest"]
aws:
type: stdio
command: uvx
args: ["awslabs.core-mcp-server@latest"]
tool_prefix: "aws_" # Strip this prefix so aws_knowledge.search() → knowledge.search()
inherit_env: true
tool_prefix: When set, callers may omit the prefix. For example, with tool_prefix: "aws_" you can call knowledge.search_documentation() instead of aws_knowledge.search_documentation(). Prefix stripping is resolved automatically at call time.
HTTP Servers¶
Remote MCP servers accessed via HTTP/HTTPS:
servers:
# HTTP server without authentication
local_dev:
type: http
url: https://localhost:3000/mcp
timeout: 30
# HTTP server with Bearer token authentication
github:
type: http
url: https://api.githubcopilot.com/mcp/
auth:
type: bearer
token: ${GITHUB_TOKEN} # Expands from secrets.yaml
headers:
Accept: "application/json, text/event-stream"
timeout: 120
# HTTP server with OAuth 2.1 + PKCE
context7:
type: http
url: https://mcp.context7.com/mcp
auth:
type: oauth
scopes: [tools:read, tools:write]
timeout: 60
Authentication Types:
- None (default): No authentication required
- bearer: Static token authentication (use
${VAR}for secrets) - oauth: OAuth 2.1 with PKCE flow (browser-based authorization)
Aliases¶
Short names for common tool functions:
Snippets¶
Reusable code templates with Jinja2 substitution:
snippets:
multi_search:
description: Search multiple queries
params:
queries: { required: true, description: "List of queries" }
body: |
results = []
for q in {{ queries }}:
results.append(brave.search(query=q))
"\n---\n".join(results)
External snippet files:
include:
- config/snippets.yaml # Falls back to package default
- local-snippets.yaml # Project-specific additions
snippets:
custom:
body: "brave.search(query='test')" # Inline overrides included
Statistics Configuration¶
Track runtime statistics:
stats:
enabled: true
persist_dir: stats
persist_path: stats.jsonl
flush_interval_seconds: 30
context_per_call: 30000
time_overhead_per_call_ms: 4000
model: anthropic/claude-opus-4.5
cost_per_million_input_tokens: 15.0
cost_per_million_output_tokens: 75.0
chars_per_token: 4.0
View with ot.stats():
ot.stats() # All-time
ot.stats(period="day") # Last 24 hours
ot.stats(period="week", tool="brave.search")
ot.stats(output="stats_report.html") # HTML report
Transform Configuration¶
Configure the ot_llm.transform() tool for LLM-powered text transformations. Add under tools::
tools:
ot_llm:
model: "openai/gpt-5-mini" # Model for transformations
base_url: "https://openrouter.ai/api/v1" # OpenAI-compatible API endpoint
max_tokens: 4096 # Max output tokens (optional)
timeout: 30 # API timeout in seconds
Requires OPENAI_API_KEY in secrets.yaml (or compatible provider key).
Message Configuration¶
Configure ot.notify() topic-to-file routing:
tools:
msg:
topics:
- pattern: "status:*" # Glob-style topic pattern
file: "~/.onetool/status.log" # Output file (supports ~ and ${VAR})
- pattern: "doc:*"
file: "./docs/notes.md"
Messages are appended to matching files. First pattern match wins.
Output Configuration¶
Control large output handling:
output:
max_inline_size: 3000 # Threshold in bytes (0 to disable)
result_store_dir: tmp # Directory for stored results
result_ttl: 3600 # Time-to-live in seconds
preview_lines: 10 # Lines in summary preview
When output exceeds max_inline_size, OneTool stores the result and returns a handle. Query with ot.result(handle="...").
Security Configuration¶
OneTool uses an allowlist-based security model: everything is blocked by default, you explicitly allow what's safe. Tool namespaces (ot.*, brave.*, etc.) are auto-allowed.
Security rules are defined in security.yaml (included by default):
security:
validate_code: true # Enable AST validation
enabled: true # Enable security checks
builtins:
allow:
- [str, int, float, list, dict, set, tuple] # Types
- [len, range, enumerate, zip, sorted] # Iteration
- [print, repr, format] # Output
imports:
allow: [json, re, math, datetime, collections, itertools]
warn: [yaml] # Allowed but logs warning
calls:
block: [pickle.*, yaml.load] # Blocked qualified calls
warn: [random.seed] # Warned qualified calls
dunders:
allow: [__format__, __sanitize__] # Allowed magic variables
sanitize:
enabled: true # Output sanitization (prompt injection protection)
Compact array format: Group related items for readability:
Security Introspection¶
Check what's allowed at runtime:
ot.security() # Summary of all rules
ot.security(check="json") # Check specific pattern
ot.security(check="pickle.load") # Check qualified call
Output Sanitization¶
The security.sanitize subsection protects against indirect prompt injection by sanitizing tool outputs:
- Trigger sanitization: Replace
__ot,__run,mcp__onetoolpatterns - Tag sanitization: Remove
<external-content-*>patterns - GUID-tagged boundaries: Wrap content in unpredictable tags
Disable per-call with __sanitize__ = False prefix.
See Security Model for full documentation.
Environment Variables¶
| Variable | Description |
|---|---|
OT_LOG_LEVEL |
Log level (DEBUG/INFO/WARNING/ERROR) |
OT_LOG_VERBOSE |
Enable verbose logging (true/false) |
OT_LOG_DIR |
Log directory path |
OT_COMPACT_MAX_LENGTH |
Max value length in compact output |
Environment Variable Expansion¶
Config values support ${VAR} and ${VAR:-default} syntax. Variables are expanded at runtime from secrets.yaml:
servers:
myserver:
type: http
url: ${MY_SERVER_URL:-https://localhost:3000/mcp}
auth:
type: bearer
token: ${MY_API_KEY}
Validation¶
Invalid values are rejected at load time:
ValueError: Invalid configuration in config/onetool.yaml:
tools.brave.timeout: Input should be greater than or equal to 1