Skip to content

Handle MCP-CLI calls through bash

The pi coding agent can use mcp-cli to interact with MCP servers. Since mcp-cli runs through the bash tool, the path CEL variable is "" by default — the actual path is buried inside JSON in the command string.

Problem

An mcp-cli invocation looks like this:

mcp-cli call filesystem read_file '{"path": "./README.md"}'

pi.hitl sees: - tool = "bash" - command = "mcp-cli call filesystem read_file '{\\"path\\": \"./README.md\"}'"

path.startsWith(cwd) does not work because path is "" for bash commands.

Solution

Use command.contains() or command.matches() (regex) to match mcp-cli patterns in the bash command string.

Examples

Allow safe discovery operations

YAML escaping note: The double backslash \\ in \\s is required because the condition string is inside a YAML double-quoted string. YAML interprets \\ as a single \, which is what the CEL regex engine receives.

rules:
  - name: "Allow mcp-cli discovery"
    condition: 'tool == "bash" && command.matches("mcp-cli\\s+(info|grep|list)")'
    action: allow

Allow filesystem reads

rules:
  - name: "Allow mcp-cli filesystem reads"
    condition: 'tool == "bash" && command.contains("mcp-cli call filesystem read_file")'
    action: allow

Confirm filesystem writes

rules:
  - name: "Confirm mcp-cli filesystem writes"
    condition: 'tool == "bash" && command.contains("mcp-cli call filesystem") && (command.contains("write_file") || command.contains("edit_file"))'
    action: confirm
    message: "MCP filesystem write requires approval"

Block dangerous GitHub operations

rules:
  - name: "Block dangerous github operations"
    condition: 'tool == "bash" && command.contains("mcp-cli call github") && command.matches("(delete_file|create_issue|create_pull_request)")'
    action: block
    message: "This GitHub MCP operation is blocked by policy"

Confirm all remaining mcp-cli calls

rules:
  - name: "Confirm any other mcp-cli call"
    condition: 'tool == "bash" && command.contains("mcp-cli call")'
    action: confirm

Caveats

  • Arguments piped from stdin (cat args.json | mcp-cli call ...) can't be inspected — the rule matches on the visible command string only.
  • Only coarse server/tool-level rules are possible. You cannot do path-based sandboxing (e.g. "only allow reads within cwd") without parsing the JSON arguments.
  • If you need path-based rules for MCP filesystem operations, the mcp-cli arguments must be inline in the command. Piped JSON is invisible to CEL.

See also