MCP Per-Tool Kill Switches: Disable Individual Tools Without Server Downtime
Running 91 GitHub MCP tools can burn 46,000 tokens before your LLM writes a line. Here's how to disable individual MCP tools at runtime — no server restart required.
Mohammed Kafeel
Machine Learning Researcher
On this page
- What Is an MCP Per-Tool Kill Switch?
- Why You Need Per-Tool Kill Switches
- Method 1 - Client-Side Config Kill Switches (No Code Required)
- Method 2 - Server-Side Dynamic Tool Registration
- Method 3 - Environment Variable Kill Switches
- Security: Kill Switches as Defense Against Tool Poisoning
- Real-World Use Cases
- Choosing the Right Method
- Key Takeaways
- FAQ
- Useful Sources
Here's a scenario you've probably hit: you spin up the GitHub MCP server, and suddenly your LLM is staring down 91 tools worth ~46,000 tokens of context overhead - that's roughly 25% of Claude Sonnet's entire context window, gone before a single line of code is written.
Or worse: your AI agent just tried to run a destructive Terraform command you forgot was still exposed. No warning. No guard. Just an agent doing exactly what you accidentally allowed.
MCP per-tool kill switches fix both problems. They let you disable - or whitelist - specific tools on a running MCP server without touching the server process itself. No restarts. No downtime. Just surgical control over what your LLM can actually reach.
This guide covers every method: client-side config, server-side dynamic registration, and environment variable toggles. We'll also get into why this matters for security, compliance, and plain old performance.
What Is an MCP Per-Tool Kill Switch?
An MCP per-tool kill switch is a configuration or runtime mechanism that disables specific tools on an MCP server - without stopping or restarting the server process.
Think of it as a circuit breaker for individual capabilities. Your server keeps running; the LLM just can't see or call the tools you've switched off.
There are two main flavors:
- Client-side config - you declare
disabledToolsorenabledToolsin your MCP client's config file (Cursor, VS Code, Claude Desktop). Zero server code changes required. - Server-side dynamic registration - the server itself conditionally registers tools based on user role, environment, or session context, then notifies clients via the
notifications/tools/list_changedJSON-RPC message.
Why it matters
- Context window bloat - every tool description eats tokens, even when the tool is never called
- Security - fewer exposed tools means a smaller attack surface for prompt injection
- Performance - model accuracy measurably degrades as context grows ("context rot")
- Compliance - some tools may violate your data handling policies in certain environments
Why You Need Per-Tool Kill Switches
The real cost of running every tool all the time is higher than most teams realize.
The context window problem
Token overhead from popular MCP servers adds up fast:
| MCP Server | Tools | Approx. Token Cost |
|---|---|---|
| GitHub MCP | 91 tools | ~46,000 tokens |
| Playwright MCP | 21 tools | ~9,700 tokens |
| AWS Cost Explorer | ~15 tools | ~9,100 tokens |
The GitHub MCP server alone consumes ~25% of Claude Sonnet's context window just by existing in the tool list. That's context you're not spending on your actual task.
Context rot is real
Research on long-context LLM performance consistently shows that model accuracy drops as input length grows. When your LLM is wading through 90 tool descriptions to find the one it needs, it's not operating at peak accuracy. Irrelevant tools create noise that degrades reasoning quality - especially for multi-step agentic tasks.
Security: tool poisoning attacks
In April 2025, Invariant Labs disclosed a critical vulnerability class they named Tool Poisoning Attacks (TPAs) - a form of indirect prompt injection where malicious instructions are embedded inside tool descriptions, invisible to users but fully visible to the LLM. (We break the full attack chain down in how attackers hijack agent behavior with tool poisoning.)
A poisoned add tool, for example, can instruct the model to silently read ~/.cursor/mcp.json and ~/.ssh/id_rsa, then exfiltrate both via a hidden parameter. The user sees a math operation. The model executes a data breach.
CVE-2025-54136 (MCPoison) and CVE-2025-54135 (CurXecute) both exploited this vector in 2025, affecting Cursor, Claude Desktop, and GitHub Copilot. Fewer active tools = smaller attack surface.
Compliance
Some tools - data export utilities, admin endpoints, migration runners - may be perfectly fine in development but violate your data handling policies in production. Kill switches let you enforce that boundary without maintaining separate server builds.
All Tools On vs. Per-Tool Kill Switches
| Factor | All Tools On | Per-Tool Kill Switches |
|---|---|---|
| Context usage | Maximum (all tool schemas loaded) | Minimal (only active tools) |
| Attack surface | Every tool is a potential vector | Only enabled tools exposed |
| Flexibility | None - restart required to change | Runtime toggle, no restart |
| Compliance | Hard to enforce per-environment | Env-var or config-driven |
| LLM accuracy | Degrades with tool count | Stays high with focused tool set |
Method 1 - Client-Side Config Kill Switches (No Code Required)
This is the easiest approach, and it requires zero server-side changes. Cursor, VS Code, and Claude Desktop all support disabledTools and enabledTools directly in their MCP config files.
disabledTools (blacklist)
List the tools you want to turn off. Everything else stays on.
{
"mcpServers": {
"my-server": {
"command": "my-mcp-server",
"disabledTools": ["ExecuteTerraformCommand", "RunCheckovScan"]
}
}
}
Good for: blocking a handful of dangerous tools while keeping the rest accessible.
enabledTools (whitelist)
List only the tools you want on. Everything else is off.
{
"mcpServers": {
"github": {
"command": "npx -y @modelcontextprotocol/server-github",
"enabledTools": ["search_code", "search_repositories", "get_file_contents"]
}
}
}
Good for: context optimization and read-only modes. This is the safer default for production.
Precedence rule
If you specify both enabledTools and disabledTools in the same server config, enabledTools wins. The whitelist always takes precedence over the blacklist.
Config file locations
| Client | Config file path |
|---|---|
| Cursor | ~/.cursor/mcp.json |
| VS Code | .vscode/mcp.json or ~/.vscode/mcp.json |
| Claude Desktop | claude_desktop_config.json |
Cursor UI Toggle (v0.50+)
If you're on Cursor v0.50 or later, you don't even need to edit a config file. Navigate to Settings → Tools & MCP, expand your server, and you'll see a checkbox next to each individual tool. Toggle it off. Done.
This is the fastest way to do MCP runtime tool toggling during development - no JSON editing, no server restart.
Method 2 - Server-Side Dynamic Tool Registration
Client-side config is great for personal setups, but it doesn't scale. If you're building a multi-tenant SaaS, need role-based access control, or want to toggle tools based on deployment environment, you need server-side dynamic registration. (For the full access-control picture, see per-tool RBAC for AI agents.)
How it works
The server conditionally registers tools based on user role, environment variable, or session context. When the tool list changes, it fires a notifications/tools/list_changed JSON-RPC notification, which tells the client to re-fetch the tool list - no reconnection required.
The server must declare this capability upfront:
{
"capabilities": {
"tools": {
"listChanged": true
}
}
}
When the list changes, the server sends:
{
"jsonrpc": "2.0",
"method": "notifications/tools/list_changed"
}
The client then re-issues tools/list to get the updated set. No restart. No dropped session.
Python example (FastMCP)
import os
from typing import Set
def get_disabled_tools() -> Set[str]:
disabled_str = os.environ.get('DISABLED_TOOLS', '')
return set(t.strip() for t in disabled_str.split(',') if t.strip())
def setup_tools(session):
user = session.user if session else None
disabled = get_disabled_tools()
for tool in ALL_TOOLS:
if tool["name"] not in disabled and has_access(tool["name"], user):
mcp.add_tool(
name=tool["name"],
description=tool["description"],
func=tool["func"]
)
def has_access(tool_name: str, user) -> bool:
if tool_name == "read_data": return True # everyone
if tool_name == "delete_record": return user and user.role == "admin"
return False
TypeScript example (FastMCP)
const hasAccess = (toolName: string, user: any): boolean => {
if (toolName === "read_data") return true;
if (toolName === "delete_record") return user?.role === "admin";
return false;
};
export function registerTools(mcpServer: FastMCP) {
const user = mcpServer.context?.session?.user;
availableTools.forEach(tool => {
if (hasAccess(tool.name, user)) {
mcpServer.addTool({
name: tool.name,
description: tool.description,
execute: tool.handler,
});
}
});
}
This pattern gives you role-based MCP tool filtering - admins see delete_record, regular users don't. The LLM never even knows the tool exists for non-admin sessions.
Client support caveat
⚠️ Not all clients handle mid-session notifications/tools/list_changed yet. As of mid-2026:
- Claude Desktop has been reported to ignore the notification mid-session
- Cursor CLI similarly may not re-fetch automatically
- Workaround: pre-register all tools at startup and use input schema annotations to mark tools as unavailable, rather than relying on dynamic deregistration
Check your specific client's changelog before building a production flow that depends on mid-session tool list updates.
Method 3 - Environment Variable Kill Switches
This is the DevOps-friendly approach. No config file editing, no code changes per environment - just set an env var and deploy.
# Disable specific tools in production
DISABLED_TOOLS="ExecuteTerraformCommand,DeleteDatabase,RunMigration" node server.js
Or via a .env file:
# .env.production
DISABLED_TOOLS=ExecuteTerraformCommand,RunCheckovScan,DropTable
Your server reads DISABLED_TOOLS at startup and filters accordingly. Combine this with Docker or Kubernetes environment variable injection for per-environment tool sets:
# kubernetes deployment
env:
- name: DISABLED_TOOLS
valueFrom:
configMapKeyRef:
name: mcp-config
key: disabled_tools_production
This approach pairs naturally with CI/CD pipelines. Promote a build from staging to production, and the production ConfigMap automatically restricts the destructive tools - no human intervention required.
Implementation tip: Parse DISABLED_TOOLS once at startup into a Set for O(1) lookup. Don't re-parse on every tool call.
Security: Kill Switches as Defense Against Tool Poisoning
The principle of least privilege applies directly to MCP tools. Only expose what the current task actually needs. (Kill switches are one line item on the broader MCP security checklist.)
Why whitelists beat blacklists in production
A disabledTools blacklist assumes you know every dangerous tool in advance. You probably don't - especially with third-party MCP servers that add new tools in updates.
An enabledTools whitelist flips the default: everything is off unless you explicitly turn it on. A new tool added to the GitHub MCP server in a future release won't silently appear in your production agent's context.
Kill switches reduce tool poisoning exposure
Invariant Labs' April 2025 disclosure showed that tool poisoning attacks work by embedding malicious instructions in tool descriptions - instructions the LLM reads and acts on, invisible to the user. The attack surface is every tool description your LLM can see.
Fewer active tools = fewer poisoned descriptions your LLM can be manipulated by.
If you've whitelisted only search_code, search_repositories, and get_file_contents, a malicious add tool from a compromised server simply doesn't exist in your LLM's context. The attack vector is gone.
Read-only mode pattern
For code review, analysis, or browsing tasks, disable every write and delete tool:
{
"mcpServers": {
"github": {
"command": "npx -y @modelcontextprotocol/server-github",
"enabledTools": [
"search_code",
"search_repositories",
"get_file_contents",
"list_commits"
]
}
}
}
Your LLM can read everything. It can't push, delete, or modify anything. That's a meaningful security boundary with four lines of JSON.
Audit surface reduction
Fewer active tools also means a smaller audit log. When something goes wrong, you're tracing through 3 tools instead of 91. That's not a minor convenience - it's the difference between a 10-minute incident review and a 3-hour one. (For the logging side of this, see our guide to auditing tool invocations.)
Real-World Use Cases
| Use Case | Approach | Tools Disabled |
|---|---|---|
| Read-only code review | enabledTools whitelist |
All write/push/delete tools |
| Production deployment | disabledTools blacklist |
Destructive infra tools (Terraform, migrations) |
| Multi-tenant SaaS | Server-side role check | Per-user tool set based on subscription tier |
| Compliance audit | Env var kill switch | Data export and reporting tools |
| Context optimization | enabledTools whitelist |
All non-task-relevant tools |
| Security hardening | enabledTools whitelist |
Everything except explicitly vetted tools |
Choosing the Right Method
Not sure which approach fits your situation? Use this decision guide:
- No code changes needed, personal/team setup → Client-side config (
disabledTools/enabledTools) - Quick toggle during active development → Cursor UI toggle (v0.50+)
- Need role-based access control → Server-side dynamic registration
- CI/CD pipeline, per-environment control → Environment variable kill switch
- Multi-tenant SaaS with per-user tool sets → Server-side dynamic registration + session context
- Maximum security in production →
enabledToolswhitelist (client-side or server-side)
The methods aren't mutually exclusive. A common production pattern: use server-side role filtering to handle access control, and use client-side enabledTools as a second layer for context optimization.
Key Takeaways
📦 Key Takeaways
- MCP per-tool kill switches let you disable individual tools at runtime - no server restart, no downtime.
enabledTools(whitelist) is safer thandisabledTools(blacklist) in production - new tools don't silently appear.- Client-side config (Cursor, VS Code, Claude Desktop) requires zero server code changes and works today.
- Server-side dynamic registration via
notifications/tools/list_changedenables role-based and session-aware tool filtering.- Environment variables are the DevOps-friendly path for per-environment tool sets in CI/CD pipelines.
- Reducing active tools directly reduces your tool poisoning attack surface - a smaller tool list means fewer malicious descriptions your LLM can be manipulated by.
FAQ
What is an MCP per-tool kill switch?
An MCP per-tool kill switch is a mechanism that disables a specific tool on a running MCP server without stopping the server or restarting the session. It can be implemented via client-side config (disabledTools / enabledTools), server-side conditional registration, or environment variables. The LLM simply stops seeing - and calling - the disabled tool.
Does disabling a tool require restarting the MCP server?
No. Client-side config changes take effect when the client reloads its config (typically on next session start, or immediately via a UI toggle in Cursor v0.50+). Server-side dynamic registration can disable tools mid-session using the notifications/tools/list_changed notification, though client support for mid-session updates varies.
What's the difference between enabledTools and disabledTools?
disabledTools is a blacklist - all tools are on by default, and you list the ones to turn off. enabledTools is a whitelist - all tools are off by default, and you list the only ones to turn on. If both are specified, enabledTools takes precedence. For production security, enabledTools is the safer choice because new tools added to a server won't silently become available.
Which MCP clients support per-tool kill switches?
As of June 2026, Cursor (v0.50+ with UI toggle, earlier versions via ~/.cursor/mcp.json), VS Code (via .vscode/mcp.json), and Claude Desktop (via claude_desktop_config.json) all support disabledTools and enabledTools in their MCP configuration. Support for mid-session notifications/tools/list_changed is inconsistent - check your client's release notes.
How do I disable tools server-side without client config changes?
Use server-side dynamic registration: conditionally call mcp.add_tool() (or the equivalent in your framework) based on user role, session context, or environment variables. Declare capabilities.tools.listChanged: true in your server's capabilities, and send notifications/tools/list_changed when the tool list changes. The client will re-fetch the tool list without reconnecting.
Are per-tool kill switches supported in the official MCP spec?
Yes. The official MCP specification (2025-06-18) defines the notifications/tools/list_changed notification and the listChanged capability for dynamic tool management. Client-side enabledTools / disabledTools config properties are implemented by major clients (Cursor, VS Code, Claude Desktop) as a standard extension of the MCP server configuration format.
What is tool poisoning in MCP and how do kill switches help?
Tool poisoning (formally disclosed by Invariant Labs in April 2025) is a form of indirect prompt injection where malicious instructions are hidden inside tool descriptions - invisible to users but fully visible to the LLM. The model reads them as trusted context and may execute unauthorized actions silently. Kill switches help by reducing the number of tool descriptions your LLM is exposed to. An enabledTools whitelist means a poisoned tool from a compromised server simply doesn't appear in the LLM's context - the attack vector is eliminated before it can fire.
Useful Sources
- Model Context Protocol Specification - Tools (2025-06-18) - official spec covering
tools/list,tools/call, andnotifications/tools/list_changed - Invariant Labs: MCP Security Notification - Tool Poisoning Attacks - original April 2025 disclosure of Tool Poisoning Attacks
- Black Hills InfoSec: MCP Security Guidelines - practitioner-focused MCP security guidance
- Speakeasy: Dynamic Tool Discovery in MCP - deep dive on
notifications/tools/list_changedimplementation
Keep reading
How to Audit Third-Party MCP Servers Using mcp-scan
A step-by-step guide to auditing third-party MCP servers with mcp-scan — installation, CLI commands, threat types, tool pinning, CI/CD integration, and security best practices.
Multi-Tenant MCP: How to Isolate Agent Access Across Clients
Running multiple clients through a single MCP server without proper isolation is a data breach waiting to happen. Here's how to architect tenant boundaries that hold.
Token Rotation in MCP: Limiting the Blast Radius of Leaked Credentials
One leaked static MCP token can silently touch GitHub, AWS, Slack, and your database simultaneously - for months. Here's how token rotation shrinks that to minutes.



