Shotoku
← Blog

How to require human approval before an MCP tool call

· By the Shotoku team

Short answer: put a proxy MCP server between the agent and your real servers, so the proxy is the only server the agent can reach. It checks each tool call against a policy, lets safe calls through and holds risky ones until a person approves them outside the agent.

The MCP specificationrecommends this kind of control: “a human in the loop with the ability to deny tool invocations.” It leaves the how to each application.

What are the ways to add approval?

ApproachCan the agent skip it?Trade-off
Host confirmation promptsDepends on the host and its settingsSimple, but differs per host and keeps no shared record
An approval tool the agent should callYesThe model decides whether to ask, so it isn't a real gate
A proxy in front of your serversNot through MCPTakes some setup, and works across MCP hosts

Asking for approval on everything has its own cost: people stop reading and click yes. Let reads run freely and save the prompts for calls that change something.

How do you set it up?

Install the CLI and give Shotoku a fixed home:

bash
npm install -g shotoku-cli
mkdir -p ~/shotoku-home
cd ~/shotoku-home
shotoku init

Move your servers into an upstreams file. It uses the same format as your host config:

~/shotoku-home/upstreams.json
{
  "mcpServers": {
    "postgres": {
      "command": "/path/to/your-postgres-mcp-server"
    }
  }
}

Make Shotoku the only server in your host config, for example in Claude Desktop:

claude_desktop_config.json
{
  "mcpServers": {
    "shotoku": {
      "command": "npx",
      "args": ["-y", "shotoku-mcp"],
      "env": {
        "SHOTOKU_HOME": "/absolute/path/to/shotoku-home",
        "SHOTOKU_MCP_UPSTREAMS": "/absolute/path/to/shotoku-home/upstreams.json"
      }
    }
  }
}
Remove the old entries. If a server stays in your host config, the agent still has a direct route to it and the gate does nothing.

Wrapped tools are named <server>__<tool>, and that name is the resource in your policy. Your tool names will differ from these:

~/shotoku-home/policy.yaml
rules:
  - resource: postgres__query
    verdict: approved
  - resource: postgres__list_*
    verdict: approved
  - resource: postgres__drop_*
    verdict: denied

defaultVerdict: pending_approval

defaultVerdict covers everything else, so a tool added to the server later is held instead of quietly allowed. When a call is held, approve it from ~/shotoku-home in your terminal:

bash
shotoku status
shotoku approve dec_abc123

What does the agent see?

We connected an MCP client to the current npm release of shotoku-mcp, with a stand-in server behind it that reports when a tool really runs:

CallResultReached the server?
postgres__queryapprovedYes
postgres__drop_tabledeniedNo
postgres__delete_rows on userspending_approvalNo
Same call, after a human approved itapprovedYes
Same call a third timepending_approvalNo
postgres__delete_rows on orderspending_approvalNo
approve_decision, called by the agentrefusedNo

An approval works once, for the exact call you approved. Approving a delete on users gave the agent no way to delete orders. For standing permission, write a rule in the policy.

What doesn’t this cover?

Common questions

Does this work with Cursor and other hosts?

Shotoku is a standard MCP server, so it fits any host that launches MCP servers. We ran this test with our own MCP client, so check your host’s config format.

Can the agent approve its own request?

No. The approval tools are withheld by default and refused if called. Approval happens in a place the agent can’t reach through MCP.

Is every decision recorded?

Yes, in a local, tamper-evident log, including denied and held calls.

Full details are in the MCP guide.

Related