· via dev.to (home feed)
mcp-safe-actions adds code-level human approval gates to destructive MCP tools
A TypeScript library called mcp-safe-actions splits destructive MCP tools into preview and execute steps, requiring human approval via elicitation or a one-time token fallback.

Prompts are requests, not controls
A dev.to post by Saurabh Pathak makes a simple argument: telling an agent in a system prompt to ask before deleting things is a request, not an enforcement mechanism. A confused or prompt-injected model can still invoke a destructive tool with bad arguments. The fix belongs in server code, the post argues, and to make that practical, Pathak released mcp-safe-actions, an open-source TypeScript library that wraps dangerous MCP tools so they stay inert until a human approves.
MCP, the Model Context Protocol, is the mechanism through which AI agents call external tools. As agents gain real write access to filesystems, databases and mail, the failure mode shifts from wrong answers to wrong actions.
Preview, approve, execute
The library's core idea is to split a destructive tool into two halves. A preview function describes exactly what will happen — say, which file will be permanently deleted and how large it is — and side-effects nothing. An execute function only runs once approval has been granted. Wrapped tools are also registered with the destructiveHint flag set, so capable clients can present them accordingly.
Two approval paths
Client support is the wrinkle. MCP has a native mechanism, elicitation, that lets a server ask the human a question through the client's interface, but not every client implements it. The library handles both cases.
When the client supports elicitation, the approval request is routed to the client UI. The model never participates in that step, so it cannot approve its own action; a decline, cancellation, timeout or crash all leave nothing executed.
When the client does not support elicitation, the server returns the plan along with a single-use token. The assistant has to show the plan to the user and call the tool again with the token. Tokens expire and are valid only for the same tool invoked with the same arguments. A mode setting — auto by default, with elicit and token as explicit options — controls which behaviour applies.
Tested the way safety code should be
According to the post, the project ships 64 tests: unit tests, end-to-end runs against a real MCP client and server held in memory, property tests over 300 randomly generated argument objects, and stdio tests that spawn the example server as a child process. Coverage is reported at 100 percent of the source directory, and a mutation script intentionally introduces 20 defects — among them a reusable token, approval being skipped on error, and secrets leaking into the log — failing the build if the suite misses any of them. All 20 were caught, and the process exposed one weak test that was subsequently fixed and documented.
Stated limits
The post is candid about what the library does not guarantee. Token mode slows a model down rather than proving a person was in the loop, since nothing technically stops a model from calling twice without asking; anything that genuinely requires a human gate should use elicit mode. Previews must be truthful, and execute should re-verify state that changed after the preview was generated. The library gates only the tools it wraps — it is not a sandbox — so least-privilege credentials and input validation remain necessary. Elicitation behaviour also varies by client and version.
New in version 1.2.0
Two additions arrived in version 1.2.0, now on npm. An autoApprove option accepts a narrow, user-written function that can exempt low-risk calls from prompting — but only a strict boolean true bypasses the human; thrown errors or truthy values like 'yes' still prompt, and the option is ignored entirely in elicit mode.
There is also a channel mode for headless servers: with a webhookApprover URL, plans are sent to a service you operate, and approval requires an HTTP 2xx response carrying an approved: true field, with errors and timeouts treated as rejection. A plain Slack incoming webhook cannot return an answer, so an intermediary service is needed.
A Python port targeting FastMCP servers is listed as the next step on the roadmap.
Why it matters
Most agent safety guidance stops at prompting conventions, which bind nothing. This project treats approval as an access-control problem solved in code, and its preview/execute split with a token fallback makes the guarantee portable across clients regardless of elicitation support. The honest threat model — distinguishing a true human gate from a speed bump — is itself a useful template for anyone shipping agents that can delete, send or drop things unattended.
- #model-context-protocol
- #ai-agents
- #typescript
- #human-in-the-loop
- #open-source