Skip to content

feat: add strict_mcp_config option#915

Merged
qing-ant merged 2 commits intomainfrom
feat/strict-mcp-config-option
May 5, 2026
Merged

feat: add strict_mcp_config option#915
qing-ant merged 2 commits intomainfrom
feat/strict-mcp-config-option

Conversation

@qing-ant
Copy link
Copy Markdown
Contributor

@qing-ant qing-ant commented May 5, 2026

Add strict_mcp_config to ClaudeAgentOptions, mapped to the CLI's --strict-mcp-config flag.

When True, the CLI only uses MCP servers passed via mcp_servers (--mcp-config), ignoring all other MCP configurations it would otherwise load — project .mcp.json, user/global settings, and plugin-provided servers. Matches the TypeScript SDK's strictMcpConfig option.

Useful for SDK callers who want a fully deterministic MCP server set independent of the working directory or local settings.

Comment thread src/claude_agent_sdk/types.py Outdated
--strict-mcp-config does not control error handling for invalid MCP
configs; it restricts the CLI to only use MCP servers passed via
mcp_servers/--mcp-config, ignoring filesystem and settings-based MCP
configurations (.mcp.json, user/global settings, plugins). Update the
docstring to describe the actual behavior.
Copy link
Copy Markdown
Contributor

@claude claude Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for fixing the docstring — looks good now. (PR description still mentions "raises on invalid MCP server config", but the shipped docstring is correct.)

Extended reasoning...

Overview

Adds a strict_mcp_config: bool = False field to ClaudeAgentOptions, wires it to the --strict-mcp-config CLI flag in _build_command(), and adds a unit test asserting the flag is emitted only when enabled. Three files touched, ~20 lines net.

Security risks

None. The new option is off by default and only appends a static flag string to the subprocess argv — no user-controlled data, no injection surface, no permission/auth changes.

Level of scrutiny

Low. This is a mechanical pass-through following the exact pattern of neighboring boolean options (include_partial_messages, fork_session). Default False means no behavior change for existing callers.

Other factors

I previously flagged that the docstring misdescribed the flag's semantics (validation-strictness vs. config-source isolation). The author addressed this in commit c93d97e — the docstring now correctly states that it restricts MCP server loading to mcp_servers and ignores filesystem configs, matching the CLI help and the TypeScript SDK. The PR description still has the old wording, but that doesn't ship. Test coverage is adequate for the change. No new bugs found this round.

@qing-ant
Copy link
Copy Markdown
Contributor Author

qing-ant commented May 5, 2026

E2E proof — strict_mcp_config isolates to mcp_servers only

Method: Created a temp cwd with a .mcp.json defining a dummy phantom server, then ran query("hi", options) twice with cwd pointed at that dir and inspected the init system message's mcp_servers list.

Result:

  • strict=False (default, mcp_servers={}): phantom from .mcp.json visible: yes (along with project/user/plugin servers — 60+ loaded)
  • strict=True (mcp_servers={}): phantom from .mcp.json visible: nomcp_servers=[] (expected no)

Also verified the bundled CLI advertises the flag: --strict-mcp-config Only use MCP servers from --mcp-config, ignoring all other MCP configurations.

Verdict:strict_mcp_config=True correctly limits MCP servers to options.mcp_servers only, ignoring .mcp.json and all other sources.

proof.py
import anyio, sys
sys.path.insert(0, "/tmp/e2e-915/src")
from claude_agent_sdk import query, ClaudeAgentOptions
from claude_agent_sdk.types import SystemMessage

async def run(strict):
    opts = ClaudeAgentOptions(
        cwd="/tmp/e2e-915-cwd",   # contains .mcp.json with a "phantom" server
        max_turns=1,
        strict_mcp_config=strict,
        mcp_servers={},
    )
    init = None
    async for m in query(prompt="hi", options=opts):
        if isinstance(m, SystemMessage) and m.subtype == "init":
            init = m.data
            break
    names = [s.get("name") for s in init.get("mcp_servers", [])] if init else []
    print(f"strict={strict}: mcp_servers={names}")
    return names

async def main():
    a = await run(False)
    b = await run(True)
    print("phantom in strict=False:", "phantom" in a)
    print("phantom in strict=True:", "phantom" in b)

anyio.run(main)

Output:

strict=False: mcp_servers=['plugin:serena:serena', 'antpack', 'phantom', ... 60+ more ...]
strict=True: mcp_servers=[]
phantom in strict=False: True
phantom in strict=True: False

@qing-ant qing-ant enabled auto-merge (squash) May 5, 2026 18:10
@qing-ant qing-ant merged commit 32bcc4e into main May 5, 2026
10 checks passed
@qing-ant qing-ant deleted the feat/strict-mcp-config-option branch May 5, 2026 18:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants