fix(sdk): replace AgentBase.mcp_config dict with typed MCPConfig & sanitize secrets#2830
fix(sdk): replace AgentBase.mcp_config dict with typed MCPConfig & sanitize secrets#2830
Conversation
Add a field_serializer on AgentBase.mcp_config that applies sanitize_config() during model_dump/model_dump_json. This prevents MCP secrets (Authorization headers, env vars, API keys) from leaking into: - base_state.json (persisted state) - ConversationStateUpdateEvents (emitted to webhooks/cloud storage) - Full state snapshots (shared-event endpoints) The sanitizer uses the existing sanitize_config() utility which: - Redacts all values under 'headers', 'env', 'environment', 'acp_env' keys - Redacts values for keys matching SECRET_KEY_PATTERNS (KEY, TOKEN, etc.) - Redacts sensitive URL query parameters This is safe because mcp_config is always provided fresh by the caller on conversation resume (state.agent = agent replaces the persisted agent), so redacted values are never needed for restore. Co-authored-by: openhands <openhands@all-hands.dev>
Python API breakage checks — ❌ FAILEDResult: ❌ FAILED
Log excerpt (first 1000 characters) |
REST API breakage checks (OpenAPI) — ❌ FAILEDResult: ❌ FAILED
Log excerpt (first 1000 characters) |
Coverage Report •
|
||||||||||||||||||||||||||||||||||||||||||||||||||
|
@openhand could we remove mcp_config or replace the dict with an MCPConfig setting like are used in the settings library within this repo to fix this problem? I believe sanitation is already implemented there. |
|
@OpenHands could we remove mcp_config or replace the dict with an MCPConfig setting like are used in the settings library within this repo to fix this problem? I believe sanitation is already implemented there. |
|
I'm on it! xingyaoww can track my progress at all-hands.dev |
Replace the untyped dict[str, Any] field with the typed MCPConfig Pydantic model from fastmcp, aligning AgentBase with AgentSettings which already uses MCPConfig | None. Changes: - AgentBase.mcp_config: dict[str, Any] -> MCPConfig | None (default None) - Add field_validator (mode='before') to coerce dicts to MCPConfig for backward compatibility and normalize empty values to None - Update field_serializer to dump MCPConfig to dict before sanitizing - Update local_conversation.py and plugin/loader.py to bridge between dict (for plugin merge operations) and MCPConfig - Update settings/model.py to pass MCPConfig directly (no more .model_dump()) - Update all test files for MCPConfig type The field_validator ensures backward compatibility: callers passing plain dicts still work. Sanitization still prevents secret leakage through all serialization paths. Co-authored-by: openhands <openhands@all-hands.dev>
|
Done! Replaced What changed:
This comment was generated by an AI assistant (OpenHands) on behalf of the user. |
SummaryThe reviewer's comment asked: "could we remove mcp_config or replace the dict with an MCPConfig setting like are used in the settings library within this repo to fix this problem?" Request addressed: ✅ Complete
Conciseness: ✅All 10 modified files are directly necessary for the refactoring — 4 source files for the type change + caller updates, and 6 test files updated to use NoteThe API breakage checker correctly flags this as a breaking change (public field type changed). This is expected and intentional since the reviewer specifically requested it. The |
Use MCPConfig.model_validate() at call sites that previously passed plain dicts directly to Agent(mcp_config=...) or MCPConfig(): - examples/07, 08, 10, 13: wrap dict literals with MCPConfig.model_validate() - subagent/registry.py: build MCPConfig from agent_def.mcp_servers - tests/test_acp_agent.py: wrap dict literal This fixes pyright reportArgumentType errors from the type change. Co-authored-by: openhands <openhands@all-hands.dev>
…cation Stale write-path confirmed end-to-end (run 24503276447): - Added "Stale" label to PR #2830 ✓ - Posted stale comment on PR #2830 ✓ - Statistics: "Added PRs labels: 1, Added PRs comments: 1" - Side effects cleaned up (label removed, comment deleted) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Refactor: Replace
mcp_configdict with typedMCPConfig& sanitize secretsProblem
AgentBase.mcp_configwas adict[str, Any]with no type safety or secret protection. When MCP servers are configured with authentication headers or environment variables containing secrets, those raw values could flow through serialization paths unredacted:_save_base_state()→ persists tobase_state.jsonwith secrets in plaintextConversationStateUpdateEvent→ emitted whenstate.agentis setfrom_conversation_state()→ full state snapshot viastate.model_dump(mode="json")WebhookSubscriber._post_events()→event.model_dump()→ HTTP POST to webhook → persisted to cloud storageFix
Replaced
AgentBase.mcp_config: dict[str, Any]withMCPConfig | None(fromfastmcp.mcp_config), aligning withAgentSettings.mcp_configwhich already usesMCPConfig | None.Key changes:
dict[str, Any](default{}) →MCPConfig | None(defaultNone)field_validator(mode="before")coerces dicts →MCPConfigand normalizes empty values toNonefield_serializerdumpsMCPConfigto dict, then appliessanitize_config()to redact secretslocal_conversation.py,plugin/loader.py,settings/model.pyWhy this is safe
On conversation resume,
state.agent = agentreplaces the persisted agent with the caller-provided one. The persistedmcp_configis never used for restore.verify()only checks agent class + tool names, notmcp_config. So redacting secrets during serialization has zero impact on resume.Tests
All 122 tests pass across modified test files. Pre-commit checks (ruff format, ruff lint, pycodestyle, pyright, import deps, tool registration) all pass.
This PR was created by an AI assistant (OpenHands) on behalf of the user.
Agent Server images for this PR
• GHCR package: https://github.com/OpenHands/agent-sdk/pkgs/container/agent-server
Variants & Base Images
eclipse-temurin:17-jdknikolaik/python-nodejs:python3.13-nodejs22-slimgolang:1.21-bookwormPull (multi-arch manifest)
# Each variant is a multi-arch manifest supporting both amd64 and arm64 docker pull ghcr.io/openhands/agent-server:8e5a80b-pythonRun
All tags pushed for this build
About Multi-Architecture Support
8e5a80b-python) is a multi-arch manifest supporting both amd64 and arm648e5a80b-python-amd64) are also available if needed