Home / AI & GenAI

AI & GenAIHow-toAdvanced

Switching off one agent tool without stopping the agent: tool-level kill switches and MCP servers

Often you want the agent to keep answering questions but stop it from using one risky tool. Build a tool registry with per-tool enable flags and a read-only mode, enforce it in both the agent and the MCP server, and give the model a clear message when a tool is off.

Deepak Bhardwaj4 min read

Applies toLLM agents with function/tool calling, Model Context Protocol (MCP) servers, any language

Why per tool

An agent that can search the knowledge base, read tickets, update tickets and issue refunds has very different risks per tool. If refunds go wrong, switching off the whole agent also stops the harmless 90% of its work. A per-tool switch keeps the agent useful while you investigate.

The tool registry

Keep tool state in a config store the agent reads often (feature flags, a table, a key-value store):

{
  "mode": "normal",
  "tools": {
    "search_kb":       { "enabled": true,  "risk": "read" },
    "get_ticket":      { "enabled": true,  "risk": "read" },
    "update_ticket":   { "enabled": true,  "risk": "write" },
    "issue_refund":    { "enabled": false, "risk": "irreversible", "reason": "INC-4411 under review" }
  }
}

mode can be normal, read_only (all write/irreversible tools off) or off.

Enforce in the agent: two places

1. Don't offer disabled tools to the model

def tools_for_request(registry: dict, all_tools: dict) -> list:
    mode = registry["mode"]
    allowed = []
    for name, meta in registry["tools"].items():
        if not meta["enabled"]:
            continue
        if mode == "read_only" and meta["risk"] != "read":
            continue
        if mode == "off":
            continue
        allowed.append(all_tools[name])
    return allowed

The model can't call a tool it wasn't given. It will answer without it or say it can't do that.

2. Check again at execution time

The registry may change between the model's decision and execution, and a model can still produce a call for a tool name from earlier context. Check before running:

def execute_tool(name: str, args: dict, registry: dict):
    meta = registry["tools"].get(name)
    if not meta or not meta["enabled"] or registry["mode"] in ("off",) or \
       (registry["mode"] == "read_only" and meta["risk"] != "read"):
        log_blocked_call(name, args)
        return {"error": "tool_disabled",
                "message": f"The tool '{name}' is currently disabled by operators. "
                           "Tell the user this action can't be done right now and offer a handoff to a person."}
    return TOOLS[name](**args)

Returning a structured message (not raising an exception) lets the model explain the situation to the user cleanly.

Enforce in the MCP server too

If tools are exposed through an MCP server, the server is the right enforcement point because every client goes through it:

  • Don't list disabled tools in the server's tools list.
  • Reject calls to disabled tools with an error result, even if a client calls them by name.
  • Read the registry on each call (or cache for a few seconds).

This protects you from clients you don't control (other teams' agents, desktop assistants) that connect to the same server.

Credentials follow the switch

A disabled tool should also lose its access where possible: for irreversible tools, use a separate credential (e.g. a refund API key) that can be revoked on its own. Then a bug in the registry check can't still issue refunds.

Flip it

# Example with Azure App Configuration key-value
az appconfig kv set --name appcs-agents --key "agent:helpdesk:tools:issue_refund:enabled" --value false --yes
az appconfig kv set --name appcs-agents --key "agent:helpdesk:mode" --value read_only --yes

Monitor

  • Count of tool_disabled blocks per tool, so you know users are hitting the switch.
  • Alert if a disabled tool's backend still receives calls. That means a bypass.
Signal Meaning
Blocks rising after a flip Expected; users asking for that action
Backend calls continue after flip Bypass: cached registry, other client or direct API use
Model keeps retrying the tool Message to the model not clear enough. Improve it

Tip

Give irreversible tools a "requires approval" state as well as on/off. Often the right response to an incident is "approval required", not "off".

Checklist

  • Registry with enable flag and risk level per tool
  • Disabled tools removed from the tool list AND blocked at execution
  • MCP server enforces the same registry
  • Separate, revocable credentials for irreversible tools
  • Blocks and bypasses monitored