Configuration reference

Top-level shape

Warning

Configuration uses top-level mcp and admin sections. The former server section and its nested web section are not supported: move MCP settings from server to mcp, and move Web UI settings from server.web to admin.

Previous key Replacement
server.transport mcp.transport
server.listen mcp.listen
server.auth_token_file mcp.auth_token_file
server.request_log mcp.request_log
server.allowed_cwd_roots mcp.allowed_cwd_roots
server.web.listen admin.listen
server.web.auth_token_file admin.auth_token_file
mcp:          # how the MCP transport is exposed
admin:        # operator-only request review UI
tools:        # one entry per wrapped CLI tool
mcp_servers:  # local stdio MCP servers, filtered by tool name

mcp_servers declares local stdio MCP servers that wraptool spawns and filters. Only explicitly allowed upstream tool names are exposed; deny wins. Argument schemas and values pass through without CLI-style constraints. Capability requests and the config editors do not manage these rules. See MCP integration.

NoteFirst run scaffolds this file

If no config exists at the resolved path, wraptool serve and wraptool up write a commented starter there (mode 0600) and log the location, rather than erroring. They never overwrite an existing file. The starter enables one privileged tool — git pull — so the server comes up and demonstrates the point of wraptool: it wraps commands that need host credentials the AI never sees. Read-only local git (status, log, diff) is intentionally not included — it needs no credentials and the assistant can run it itself. Edit the file to expose the tools you want; it hot-reloads on save.

mcp

Field Type Default Description
transport sse | unix sse MCP transport
listen string host:port (sse) or socket path (unix)
auth_token_file string Bearer token file for sse/unix
log_level string info parsed and defaulted; currently does not change logging
request_log string XDG data dir path to capability-request JSONL
allowed_cwd_roots []string when non-empty, constrains a client-supplied cwd to these roots (symlinks resolved); a cwd outside them is refused. Empty = no containment

The sse/unix TCP listener serves both MCP HTTP transports on the one port — legacy SSE at /sse and Streamable HTTP at /mcp — behind the same auth_token_file Bearer check. Clients hit whichever their generated config targets.

admin

Starts an operator-facing Web UI for reviewing capability requests inside the serve process, on its own listener — independent of the MCP transport, so it works regardless of transport. Leave listen empty to disable.

Field Type Default Description
listen string host:port for the UI; bind 127.0.0.1 — it is not AI-facing
auth_token_file string required token, distinct from mcp.auth_token_file. Browsers authenticate via ?token= or a login form ⇒ an HttpOnly session cookie; scripts send it as a Bearer header
mcp:
  transport: sse
  listen: 127.0.0.1:8717
  auth_token_file: ~/.config/wraptool/mcp-token
admin:
  listen: 127.0.0.1:8718
  auth_token_file: ~/.config/wraptool/admin-token

Approving with apply to config runs the same config.ApplyRequest merge as requests approve --apply; the running server hot-reloads via the config file-watch.

tools.<name>

Each tool is keyed by the user-visible name (which becomes the MCP tool prefix: gitgit_pull, git_push, …).

tools:
  gcloud:
    binary: /usr/bin/gcloud         # absolute path, validated at startup
    working_dir: /var/lib/wraptool  # optional cwd for child processes
    timeout: 30s                    # per-call timeout
    env:                            # inline env vars (override env_files)
      CLOUDSDK_CORE_PROJECT: my-project
    env_files:                      # KEY=VALUE files, merged left-to-right
      - /etc/wraptool/gcloud.env
    introspection:
      cache_ttl: 24h
      overrides_file: ~/.config/wraptool/overrides/gcloud.yaml
    allow:
      - subcommand: [compute, instances, list]
        flags: ["--format", "--filter"]
        deny_flags: ["--quiet"]
        flag_constraints:
          "--filter": { pattern: "^[a-zA-Z0-9_ =.\"'-]+$" }
        arg_constraints:
          positional_max: 2
    deny:
      - subcommand: [auth]

Allow rules

Field Type Description
subcommand []string Token list; matched exactly ([remote, set-url] does not match [remote])
flags []string Whitelist of permitted flags
deny_flags []string Flags blocked even though in flags
flag_constraints map[flag]Constraint Value-level rules
arg_constraints.positional_max int Max positional args

Help flags are always permitted. At config load wraptool seeds -h and --help into every allow rule’s flags (config.SeedHelpFlags), so they are callable and introspectable on any approved subcommand without listing them. The MCP schema renders them as boolean params. To revoke help on a rule, list the flag under its deny_flags — deny wins.

Deny rules

Evaluated before allow rules. Take absolute precedence.

deny:
  - subcommand: [exec]
  - subcommand: [auth]

Flag constraints

Field Type Description
required bool Flag must be provided
type string integer, boolean, or string
min, max int Range for integer values
pattern string Regex the value must match
deny bool Flag is always denied

Boolean (valueless) flags. A flag’s value-taking-ness is decided by, in order: an explicit type constraint, then --help introspection, then a name heuristic. The heuristic treats --no-*/--enable-*/--disable-* prefixes and the well-known switches --yes/-y, --force, --dry-run, --quiet, --verbose as boolean, so they render as a bare --flag (not --flag true) without needing a flag_constraints entry. Short -f is deliberately excluded (it’s a filename flag as often as a force switch). If a tool uses one of these names with a value, introspection detects it (or set type: string).

Evaluation order

flowchart LR
  start([Tool call]) --> tool{Tool<br/>configured?}
  tool  -- yes --> deny{Deny rule<br/>matches?}
  deny  -- no  --> allow{Allow rule<br/>matches?}
  allow -- yes --> flags{Flags +<br/>constraints<br/>pass?}
  flags -- yes --> pargs{Positional args<br/>within limit?}
  pargs -- yes --> exec([Execute])

  tool  -- no  --> refuse([Refuse])
  deny  -- yes --> refuse
  allow -- no  --> refuse
  flags -- no  --> refuse
  pargs -- no  --> refuse

  classDef ok fill:#efe,stroke:#2a7
  classDef no fill:#fee,stroke:#E60028
  class exec ok
  class refuse no

Validating a config

Check a config before the server loads it:

wraptool config validate                 # default / $WRAPTOOL_CONFIG path
wraptool config validate ./config.yaml   # an explicit file

It exits non-zero with a message on the first problem, and touches nothing — no server, no spawned tools. Validation, server startup, and hot reload all strictly decode the YAML: a misspelled key such as flag_constraint (instead of flag_constraints) is reported rather than silently dropped. It then applies defaults and runs the same semantic checks serve does at startup.

Handy before an edit that the server would otherwise hot-reload: a config that fails validation is rejected on reload and the previous policy stays in effect, so validating first tells you whether a change will actually take.

CLI introspection

wraptool parses --help output at startup to enrich MCP tool schemas with real flag descriptions and types. Supported styles:

  • Git/Cobra (git commit -h, kubectl get -h)
  • GNU (curl --help, wget --help)
  • Fallback regex extraction

Results cache to ~/.cache/wraptool/introspection/. Override misparses with a YAML file referenced from the tool’s introspection block:

# ~/.config/wraptool/overrides/git.yaml
subcommands:
  commit:
    description: "Record changes to the repository"
    flags:
      "--message":
        short: "-m"
        type: string
        description: "Commit message"

Environment isolation

Each child process gets a minimal base environment:

  • PATH — the tool binary’s own directory prepended to the server’s inherited PATH (falling back to /usr/bin:/bin only when that is unset), so a tool finds both co-located helpers (e.g. kubectlgke-gcloud-auth-plugin) and genuine runtime deps in a separate prefix (e.g. gitssh on a store-based distro like Guix)
  • HOME — copied from the server environment; override it in tool env when user-global configuration or credentials must not be visible
  • LANG=C.UTF-8
  • Variables from env_files (merged in order)
  • Variables from inline env (override env_files)

No other parent variable is inherited. This minimizes ambient environment exposure, but inheriting HOME means it is not by itself credential isolation for host-side child processes.

mcp_servers.<name>

Field Type Default Description
command string required Child command; resolved by exec, not required to be absolute
args []string Child arguments
working_dir string Parsed, but currently not applied to the child process
env, env_files map, list Child environment additions
timeout duration 30s Per-tool-call timeout
init_timeout duration 10s Initialization budget
name_prefix string map key Prefix for exposed tools
allow, deny list of {tool: name} Exact upstream names; default-deny and deny-first

Platform paths

Purpose Linux macOS
Config ~/.config/wraptool/ ~/Library/Application Support/wraptool/
Data ~/.local/share/wraptool/ ~/Library/Application Support/wraptool/
Cache ~/.cache/wraptool/ ~/Library/Caches/wraptool/

Override with WRAPTOOL_CONFIG or --config.

Back to top