Skip to content

Commit 609c18a

Browse files
committed
fix: address review feedback for MCP config feature
- Move test file to tests/strands/experimental/ to mirror src/ structure - Export load_mcp_clients_from_config from strands.experimental.__init__ - Convert JSON schema files to inline Python dicts (eliminates module-level file I/O) - Remove hack by directly referencing MCP_SERVER_CONFIG_SCHEMA in agent config - Delete unused .schema.json files - Update _parse_tool_filters docstring to clarify re.match prefix-match semantics
1 parent dad40f5 commit 609c18a

7 files changed

Lines changed: 204 additions & 117 deletions

File tree

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# Code Review - 2026-04-13
2+
3+
**Commits Reviewed**: dad40f58d835b3e34e49d43ee0bb75285a9f5d10
4+
**Branch:** agent-tasks/482 → main
5+
**Type:** feat
6+
7+
## Summary
8+
9+
This PR adds declarative MCP server configuration support, allowing users to define multiple MCP servers in JSON config files (compatible with Claude Desktop/Cursor format) and load them automatically via `load_mcp_clients_from_config()`. It also extends `config_to_agent()` to accept an `mcp_servers` field. Supports stdio, SSE, and streamable-HTTP transports with JSON schema validation, tool filters (regex), and auto-detection of transport type.
10+
11+
## Recommendation
12+
13+
**Decision:** Request Changes
14+
15+
**Rationale:** The feature design is sound and addresses a real user need. The code is well-structured with good separation of concerns and thorough test coverage. However, there are several outstanding issues from the prior automated review that remain unaddressed, plus a few additional concerns around test placement, export paths, and module-level I/O patterns.
16+
17+
## Quick Stats
18+
19+
- Files Changed: 7
20+
- Lines Added: 957
21+
- Lines Removed: 28
22+
- Test Coverage: yes (unit + integration, 94.69% patch coverage per Codecov)
23+
- Breaking Changes: no
24+
25+
## Critical Issues
26+
27+
No critical issues (security, data loss, breaking changes).
28+
29+
## Important Issues
30+
31+
### Issue 1: Test file placed in wrong directory
32+
- **File:** `tests/strands/tools/mcp/test_mcp_config.py`
33+
- **Problem:** The unit tests for `strands.experimental.mcp_config` are located at `tests/strands/tools/mcp/test_mcp_config.py`. Per AGENTS.md, tests should mirror the `src/` directory structure. The module lives in `src/strands/experimental/mcp_config.py`, so the tests should be in `tests/strands/experimental/`.
34+
- **Suggestion:** Move to `tests/strands/experimental/test_mcp_config.py`.
35+
36+
### Issue 2: `load_mcp_clients_from_config` not exported from `strands.experimental`
37+
- **File:** `src/strands/experimental/__init__.py`
38+
- **Problem:** The new public function `load_mcp_clients_from_config` is not added to `strands.experimental.__init__.py`'s `__all__` or imports. Users must import from the internal module path `strands.experimental.mcp_config`, which is not a stable public API surface. The PR description also still shows the old import path `from strands.tools.mcp import load_mcp_clients_from_config`.
39+
- **Suggestion:** Add to `__init__.py`:
40+
```python
41+
from .mcp_config import load_mcp_clients_from_config
42+
43+
__all__ = ["config_to_agent", "load_mcp_clients_from_config", "tools", "steering"]
44+
```
45+
46+
### Issue 3: Module-level file I/O at import time
47+
- **File:** `src/strands/experimental/mcp_config.py:30-32`, `src/strands/experimental/agent_config.py:25-27`
48+
- **Problem:** Both modules read JSON schema files at import time using `Path(__file__).parent`. This is a new pattern in this codebase (the original `agent_config.py` had the schema inline as a Python dict). It introduces file I/O side effects on import and may not work correctly in zipped/packaged distributions.
49+
- **Suggestion:** Either:
50+
1. Keep schemas as inline Python dicts (consistent with original pattern, zero I/O)
51+
2. Use `importlib.resources` for reading package data files (recommended for packaged distributions)
52+
3. Lazy-load via `functools.lru_cache` on a loader function
53+
54+
### Issue 4: `$ref` in JSON schema manually resolved in Python
55+
- **File:** `src/strands/experimental/agent_config.py:31`, `src/strands/experimental/agent_config.schema.json:31`
56+
- **Problem:** The `agent_config.schema.json` contains `"$ref": "mcp_server_config.schema.json"` which is then manually replaced in Python code: `AGENT_CONFIG_SCHEMA["properties"]["mcp_servers"]["additionalProperties"] = MCP_SERVER_CONFIG_SCHEMA`. This makes the JSON file misleading when read standalone — any IDE or JSON Schema tooling would fail to resolve the `$ref`.
57+
- **Suggestion:** Either inline the MCP server schema directly in `agent_config.schema.json`, or use `jsonschema.RefResolver` to properly resolve `$ref`s. If keeping separate files, add a comment explaining the programmatic resolution.
58+
59+
### Issue 5: Docstring unclear about `re.match` prefix-match semantics
60+
- **File:** `src/strands/experimental/mcp_config.py:40-42`
61+
- **Problem:** The docstring says `Exact-match strings like "^echo$" work correctly as regex since they match themselves.` But the actual matching in `mcp_client.py:989` uses `pattern.match()` (prefix match, not full match). So `"echo"` in a filter will match `"echo_extra"` too. Users writing `"echo"` in their JSON config likely expect exact matching.
62+
- **Suggestion:** Update the docstring to clearly document this behavior:
63+
```
64+
All filter strings are compiled as regex patterns and matched using ``re.match``
65+
(prefix match from start of string). Use ``"^echo$"`` for exact matching.
66+
``"echo"`` will match any tool name starting with "echo".
67+
```
68+
69+
## Suggestions
70+
71+
- The PR description still references the old import path `from strands.tools.mcp import load_mcp_clients_from_config`. Should be updated to reflect the actual location.
72+
- Consider whether `MCP_SERVER_CONFIG_SCHEMA` needs to be a public module-level export from `mcp_config.py`. If it's only used internally by `agent_config.py`, prefix it with `_`.
73+
- The integration tests at `tests_integ/mcp/test_mcp_config.py` are well-placed and thorough.
74+
75+
## Positive Highlights
76+
77+
- Clean separation between `mcp_config.py` (MCP-specific) and `agent_config.py` (agent-level integration)
78+
- All prior review feedback (regex heuristic, private naming, lambda→def, mcpServers wrapper, empty dict simplification) was addressed thoughtfully in the force-push
79+
- Comprehensive test coverage: unit tests for schema validation, tool filter parsing, transport creation, file loading; integration tests with actual echo server
80+
- Good error messages that include server names and JSON schema paths for debugging
81+
- Transport auto-detection from config fields is intuitive and matches user expectations
82+
- JSON schema validation catches config errors early with clear messages
83+
84+
## Files Reviewed
85+
86+
| File | Status | Notes |
87+
|------|--------|-------|
88+
| `src/strands/experimental/agent_config.py` | Modified | Added mcp_servers handling, moved schema to JSON file |
89+
| `src/strands/experimental/agent_config.schema.json` | Added | Agent config JSON schema (extracted from Python dict) |
90+
| `src/strands/experimental/mcp_config.py` | Added | Core MCP config parsing and MCPClient factory |
91+
| `src/strands/experimental/mcp_server_config.schema.json` | Added | MCP server config JSON schema |
92+
| `tests/strands/experimental/test_agent_config.py` | Modified | Added mcp_servers integration tests |
93+
| `tests/strands/tools/mcp/test_mcp_config.py` | Added | Unit tests (wrong directory - should be experimental/) |
94+
| `tests_integ/mcp/test_mcp_config.py` | Added | Integration tests with echo server |
95+
96+
## Checklist
97+
98+
- [x] Type annotations complete
99+
- [ ] Documentation present (not exported from `__init__.py`, PR description outdated)
100+
- [x] Tests included (comprehensive unit + integration)
101+
- [x] No breaking changes
102+
- [ ] Follows project patterns (test file placement, module-level I/O)
103+
- [x] Error handling appropriate
104+
105+
## Additional Notes
106+
107+
- This is the second iteration of the PR. The first round of automated review identified 7 issues, all of which were addressed in the force-push to `dad40f5`. The second round identified 6 more issues, which are the basis for this review.
108+
- The `mcpServers` (camelCase) wrapper key in `load_mcp_clients_from_config` vs `mcp_servers` (snake_case) in agent config is a good design choice — it maintains Python conventions in the agent config while supporting the Claude Desktop/Cursor JSON convention in the standalone loader.
109+
- Codecov reports 94.69% patch coverage with 6 lines missing coverage, primarily in `mcp_config.py` (3 missing + 1 partial) and `agent_config.py` (1 missing + 1 partial).

src/strands/experimental/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@
55

66
from . import steering, tools
77
from .agent_config import config_to_agent
8+
from .mcp_config import load_mcp_clients_from_config
89

9-
__all__ = ["config_to_agent", "tools", "steering"]
10+
__all__ = ["config_to_agent", "load_mcp_clients_from_config", "tools", "steering"]

src/strands/experimental/agent_config.py

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313

1414
import json
1515
import logging
16-
from pathlib import Path
1716
from typing import Any
1817

1918
import jsonschema
@@ -23,12 +22,40 @@
2322

2423
logger = logging.getLogger(__name__)
2524

26-
_SCHEMA_PATH = Path(__file__).parent / "agent_config.schema.json"
27-
with open(_SCHEMA_PATH) as _f:
28-
AGENT_CONFIG_SCHEMA: dict[str, Any] = json.load(_f)
29-
30-
# Resolve the $ref in mcp_servers.additionalProperties to the actual MCP server schema
31-
AGENT_CONFIG_SCHEMA["properties"]["mcp_servers"]["additionalProperties"] = MCP_SERVER_CONFIG_SCHEMA
25+
# JSON Schema for agent configuration
26+
AGENT_CONFIG_SCHEMA: dict[str, Any] = {
27+
"$schema": "http://json-schema.org/draft-07/schema#",
28+
"title": "Agent Configuration",
29+
"description": "Configuration schema for creating agents.",
30+
"type": "object",
31+
"properties": {
32+
"name": {"description": "Name of the agent.", "type": ["string", "null"], "default": None},
33+
"model": {
34+
"description": "The model ID to use for this agent. If not specified, uses the default model.",
35+
"type": ["string", "null"],
36+
"default": None,
37+
},
38+
"prompt": {
39+
"description": "The system prompt for the agent. Provides high level context to the agent.",
40+
"type": ["string", "null"],
41+
"default": None,
42+
},
43+
"tools": {
44+
"description": "List of tools the agent can use. Can be file paths, "
45+
"Python module names, or @tool annotated functions in files.",
46+
"type": "array",
47+
"items": {"type": "string"},
48+
"default": [],
49+
},
50+
"mcp_servers": {
51+
"description": "MCP server configurations. Each key is a server name and the value is "
52+
"a server configuration object with transport-specific settings.",
53+
"type": "object",
54+
"additionalProperties": MCP_SERVER_CONFIG_SCHEMA,
55+
},
56+
},
57+
"additionalProperties": False,
58+
}
3259

3360
# Pre-compile validator for better performance
3461
_VALIDATOR = jsonschema.Draft7Validator(AGENT_CONFIG_SCHEMA)

src/strands/experimental/agent_config.schema.json

Lines changed: 0 additions & 35 deletions
This file was deleted.

src/strands/experimental/mcp_config.py

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
import json
1313
import logging
1414
import re
15-
from pathlib import Path
1615
from typing import Any
1716

1817
import jsonschema
@@ -26,18 +25,72 @@
2625

2726
logger = logging.getLogger(__name__)
2827

29-
_SCHEMA_PATH = Path(__file__).parent / "mcp_server_config.schema.json"
30-
with open(_SCHEMA_PATH) as _f:
31-
MCP_SERVER_CONFIG_SCHEMA: dict[str, Any] = json.load(_f)
28+
MCP_SERVER_CONFIG_SCHEMA: dict[str, Any] = {
29+
"$schema": "http://json-schema.org/draft-07/schema#",
30+
"title": "MCP Server Configuration",
31+
"description": "Configuration for a single MCP server.",
32+
"type": "object",
33+
"properties": {
34+
"transport": {
35+
"description": "Transport type. Auto-detected from 'command' (stdio) or 'url' (sse) if omitted.",
36+
"type": "string",
37+
"enum": ["stdio", "sse", "streamable-http"],
38+
},
39+
"command": {"description": "Command to run for stdio transport.", "type": "string"},
40+
"args": {
41+
"description": "Arguments for the stdio command.",
42+
"type": "array",
43+
"items": {"type": "string"},
44+
"default": [],
45+
},
46+
"env": {
47+
"description": "Environment variables for the stdio command.",
48+
"type": "object",
49+
"additionalProperties": {"type": "string"},
50+
},
51+
"cwd": {"description": "Working directory for the stdio command.", "type": "string"},
52+
"url": {"description": "URL for sse or streamable-http transport.", "type": "string"},
53+
"headers": {
54+
"description": "HTTP headers for sse or streamable-http transport.",
55+
"type": "object",
56+
"additionalProperties": {"type": "string"},
57+
},
58+
"prefix": {"description": "Prefix to apply to tool names from this server.", "type": "string"},
59+
"startup_timeout": {
60+
"description": "Timeout in seconds for server initialization. Defaults to 30.",
61+
"type": "integer",
62+
"default": 30,
63+
},
64+
"tool_filters": {
65+
"description": "Filters for controlling which tools are loaded.",
66+
"type": "object",
67+
"properties": {
68+
"allowed": {
69+
"description": "List of regex patterns for tools to include.",
70+
"type": "array",
71+
"items": {"type": "string"},
72+
},
73+
"rejected": {
74+
"description": "List of regex patterns for tools to exclude.",
75+
"type": "array",
76+
"items": {"type": "string"},
77+
},
78+
},
79+
"additionalProperties": False,
80+
},
81+
},
82+
"additionalProperties": False,
83+
}
3284

3385
_SERVER_VALIDATOR = jsonschema.Draft7Validator(MCP_SERVER_CONFIG_SCHEMA)
3486

3587

3688
def _parse_tool_filters(config: dict[str, Any] | None) -> ToolFilters | None:
3789
"""Parse a tool filter configuration into a ToolFilters instance.
3890
39-
All filter strings are compiled as regex patterns. Exact-match strings like ``"^echo$"``
40-
work correctly as regex since they match themselves.
91+
All filter strings are compiled as regex patterns and matched using ``re.match``
92+
(prefix match from start of string). Use ``"^echo$"`` for exact matching.
93+
``"echo"`` will match any tool name starting with "echo" (e.g. "echo_extra").
4194
4295
Args:
4396
config: Tool filter configuration dict with 'allowed' and/or 'rejected' lists,

src/strands/experimental/mcp_server_config.schema.json

Lines changed: 0 additions & 68 deletions
This file was deleted.
File renamed without changes.

0 commit comments

Comments
 (0)