GUIDE 15 March 2026 5 min read

How to Restrict MCP Tools Per Subagent Without Breaking Your Agent Workflow

SkillShield Research Team

Security Research

Agent builders are hitting the same wall: MCP tools inherited by every subagent create privilege escalation risks that are hard to audit and harder to contain. The docs describe scope levels—local, project, user—but stitching them into a strict least-privilege workflow requires more than reading the manual.

This guide consolidates the patterns emerging from production Claude Code and OpenAI Agents deployments so you can isolate tools per subagent without grinding your workflow to a halt.

The Problem: Default Tool Inheritance Is Too Permissive

By default, MCP servers registered in your environment are visible to any agent or subagent that runs. In Claude Code, a subagent spawned with the agent tool inherits the parent's MCP context unless explicitly restricted. In OpenAI Agents Python, the MCPServer connection is typically global unless you architect around it.

This creates three concrete risks:

  1. Privilege escalation: A subagent meant to lint code can call a production deployment tool
  2. Audit blind spots: You cannot trace which specific agent invoked a sensitive operation
  3. Scope leakage: user-scoped tools (personal API keys) bleed into project-scoped subagents

Solution Architecture: Agent-Scoped MCP Isolation

The fix is to bind MCP servers to specific agent lifecycles rather than inheriting them implicitly. Here's how to implement this across both major frameworks.

Claude Code: Subagent-Scoped MCP Patterns

Claude Code's subagent system supports explicit tool filtering through the agent tool's configuration.

Pattern 1: Deny-list approach for sensitive tools

{
  "mcpServers": {
    "production-deploy": {
      "command": "npx",
      "args": ["-y", "deploy-tool"],
      "env": {
        "DEPLOY_TOKEN": "$DEPLOY_TOKEN"
      }
    },
    "code-linter": {
      "command": "npx",
      "args": ["-y", "eslint-mcp"]
    }
  }
}

When spawning a read-only analysis subagent:

/agent spawn analysis-task --deny-mcp production-deploy

Pattern 2: Allow-list approach for defense-in-depth For high-risk workflows, invert the model: subagents start with zero MCP access and you explicitly grant specific servers:

/agent spawn limited-task --allow-mcp code-linter --allow-mcp github-read

Pattern 3: Scoped configuration via environment separation Claude Code respects CLAUDE_MCP_CONFIG for per-session MCP configuration. Create isolated config files:

# .claude/mcp-safe.json - no production tools
{
  "mcpServers": {
    "github-read": { "command": "npx", "args": ["-y", "github-mcp"] }
  }
}

# Launch subagent with restricted config
CLAUDE_MCP_CONFIG=.claude/mcp-safe.json claude /agent spawn safe-task

Pattern 4: Project-level isolation with .claude/ The .claude/ directory in your project root can contain CLAUDE.md instructions and MCP configurations that only apply within that project boundary—preventing user-scoped tools from leaking into project-specific subagents.

OpenAI Agents: Per-Agent MCP Lifecycle Management

The OpenAI Agents Python SDK creates MCP clients per Agent instance by default, but the server connection is often shared globally. To isolate:

Pattern 1: Explicit MCP server attachment

from agents import Agent, MCPServer

# Create isolated server instance for this agent only
deploy_server = MCPServer(
    name="deploy-tools",
    command=["npx", "-y", "deploy-mcp"],
    env={"TOKEN": os.getenv("DEPLOY_TOKEN")}
)

# Attach only to deployment agent
deploy_agent = Agent(
    name="deploy_agent",
    instructions="Handle production deployments",
    mcp_servers=[deploy_server]  # Explicit attachment
)

# Analysis agent has no deployment tools
analysis_agent = Agent(
    name="analysis_agent",
    instructions="Analyze code only",
    mcp_servers=[]  # Empty = no MCP access
)

Pattern 2: Runtime credential injection Instead of global environment variables, inject credentials per-agent:

import asyncio
from agents import Agent, MCPServerStdio

async def spawn_limited_agent(api_key: str):
    # Server defined inline with scoped credentials
    server = MCPServerStdio(
        name="github-readonly",
        params={
            "command": "npx",
            "args": ["-y", "@github/mcp-server"],
            "env": {"GITHUB_TOKEN": api_key}  # Scoped, not global
        }
    )
    
    return Agent(
        name=f"github_agent_{api_key[:8]}",
        instructions="Read GitHub data only",
        mcp_servers=[server]
    )

Pattern 3: Tool-filtering wrapper For finer control, wrap the MCP server with explicit tool allow-lists:

class FilteredMCPServer:
    def __init__(self, server, allowed_tools: list[str]):
        self.server = server
        self.allowed_tools = allowed_tools
    
    async def list_tools(self):
        all_tools = await self.server.list_tools()
        return [t for t in all_tools if t.name in self.allowed_tools]
    
    async def call_tool(self, name: str, arguments: dict):
        if name not in self.allowed_tools:
            raise PermissionError(f"Tool {name} not in allowed set")
        return await self.server.call_tool(name, arguments)

Verification: Testing Your Isolation

After implementing patterns above, verify isolation works:

Test 1: Subagent tool visibility

# In Claude Code, spawn subagent and ask it to list available MCP tools
/agent spawn test --allow-mcp code-linter
# Then query: "What MCP tools do you have access to?"
# Expected: Only code-linter tools listed

Test 2: Negative verification

# Spawn restricted subagent and attempt denied operation
/agent spawn test --deny-mcp production-deploy
# Query: "Deploy the current branch to production"
# Expected: "I don't have access to deployment tools"

Test 3: Environment leak check

# Verify parent env vars aren't visible in subagent
# Set a sensitive var in parent, spawn subagent, check if accessible
export SUPER_SECRET=test123
claude /agent spawn env-check --query "Print environment variables containing SECRET"
# Expected: Empty or not present

Common Failure Modes and Fixes

Issue: Subagent still inherits parent's MCP servers

  • Cause: Using default agent spawn without explicit --deny-mcp or --allow-mcp
  • Fix: Always specify MCP restrictions explicitly; never rely on defaults for security boundaries

Issue: OpenAI Agents share MCP state across async contexts

  • Cause: Creating MCPServer at module level instead of per-agent instantiation
  • Fix: Instantiate servers within agent factory functions, not globally

Issue: Project-scoped tools leak into user context

  • Cause: .claude/CLAUDE.md or MCP config in home directory overrides project settings
  • Fix: Audit ~/.claude/ vs .claude/ precedence; use CLAUDE_MCP_CONFIG env var for explicit overrides

Issue: Credential rotation breaks running subagents

  • Cause: Tokens rotated while long-running subagent holds reference
  • ** Fix: Implement short-lived credentials with automatic refresh (see API Secure guide on request-scoped keys)

Migration Path: Gradual Lockdown

Don't break existing workflows. Migrate in phases:

Phase 1: Audit current exposure Run this in Claude Code to map current MCP exposure:

List all MCP servers I have access to, then spawn a subagent and ask it to do the same. Report any discrepancies.

Phase 2: Implement deny-lists for high-risk tools Identify production/deployment tools and add --deny-mcp to all non-deployment subagents.

Phase 3: Convert to allow-lists for sensitive workflows For financial, deployment, or PII-handling agents, switch to --allow-mcp with explicit minimal sets.

Phase 4: Runtime credential isolation Move from global env vars to per-agent credential injection (see API Secure's short-lived key guide).

Conclusion

Agent-scoped MCP isolation isn't a single toggle—it's a composition of configuration boundaries, explicit tool filtering, and runtime credential management. The patterns above give you a defense-in-depth approach that works with both Claude Code and OpenAI Agents today.

The tooling will evolve (better native isolation is actively discussed), but these patterns will remain valid as fallback controls even after first-party features arrive. Want to verify your implementation? Try our MCP isolation audit checklist or explore short-lived API key patterns to complete your least-privilege agent architecture.

Catch risky skills before they run.

SkillShield scans skills, MCP servers, and prompt-bearing tool surfaces before they reach production.

Get early access