feat: enforce ENABLED_BROKERS and DEFAULT_BROKER at runtime#1007
feat: enforce ENABLED_BROKERS and DEFAULT_BROKER at runtime#1007jwesleye wants to merge 2 commits into
Conversation
Install a broker-availability filter on the MCP server's call_tool and list_tools dispatch so tools for disabled brokers are blocked from execution and hidden from the tool listing. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Claude finished @jwesleye's task —— View job Code Review Complete ✅I'll provide comprehensive feedback on code quality, potential issues, performance considerations, security concerns, and test coverage. Todo List
Review SummaryOverall Assessment: APPROVED with minor suggestions This is a well-implemented feature that cleanly enforces broker filtering at runtime. The code follows the existing patterns, has excellent test coverage, and addresses a clear functional requirement. ✅ StrengthsArchitecture & Design
Code Quality
Test Coverage
🔍 Code Review Detailsbroker_filter.py:36-48 - Broker Mapping Logicdef _tool_broker(tool_name: str) -> str | None:
if tool_name in _BROKER_AGNOSTIC_TOOLS:
return None
if "schwab" in tool_name:
return "schwab"
return "robinhood"✅ Well-designed: Simple, predictable logic that's easy to extend for future brokers. broker_filter.py:61-63 - Idempotency Implementationif getattr(mcp_server, _WRAPPER_ATTR, False):
setattr(mcp_server, _ENABLED_ATTR, list(enabled_brokers))
return✅ Smart approach: Updates enabled broker list without re-wrapping, preventing performance degradation. broker_filter.py:82-87 - Error Response Formatreturn mcp.types.CallToolResult(
content=[mcp.types.TextContent(type="text", text=json.dumps(error_data))],
isError=True,
)✅ Consistent: Follows MCP standards and project's JSON response pattern. app.py:2304 - Integration Pointinstall_broker_filter(server, config.brokers.enabled_brokers)✅ Perfect placement: Called after 🛡️ Security Assessment
⚡ Performance Considerations
📝 Minor Suggestions1. Documentation Enhancement (Optional)Consider adding a brief docstring example to def _tool_broker(tool_name: str) -> str | None:
"""Return the broker name required by *tool_name*, or ``None`` if agnostic.
Examples:
>>> _tool_broker("schwab_quote")
"schwab"
>>> _tool_broker("portfolio")
"robinhood"
>>> _tool_broker("broker_status")
None
"""2. Future-Proofing (Optional)The substring matching for Schwab ( 🧪 Test Quality AssessmentExcellent test suite with these highlights:
🚀 Integration AssessmentPerfect integration approach:
Final Recommendation✅ APPROVE - This PR is ready for merge. It's a clean, well-tested implementation that follows the project's patterns and successfully addresses the issue requirements. The code quality is high, test coverage is comprehensive, and the integration is seamless. |
unseriousAI
left a comment
There was a problem hiding this comment.
AGENTSHORE_CODE_REVIEW
head_sha: 2fcc70a
status: BLOCK
spec_compliance: BLOCK
blocking_findings: 1
non_blocking_findings: 0
Summary
REQUEST_CHANGES: issue #991 requires runtime enforcement for both ENABLED_BROKERS and DEFAULT_BROKER, but this PR only enforces ENABLED_BROKERS.
Blocking
- src/open_stocks_mcp/server/broker_filter.py:34-46, 49-101; src/open_stocks_mcp/server/app.py:2300-2304 — Spec mismatch with issue #991. The issue explicitly requires runtime enforcement of both ENABLED_BROKERS and DEFAULT_BROKER, and says these values should gate which broker tools are registered and active at server start. The implementation adds dispatch-time filtering for enabled brokers only; there is no logic that reads or enforces DEFAULT_BROKER behavior, and no tests covering DEFAULT_BROKER outcomes. Suggested fix: define and implement DEFAULT_BROKER runtime semantics (for example, default routing for broker-agnostic calls and/or startup registration policy), then add focused unit/integration tests proving that behavior.
Suggestions
None
|
Addressed the blocking review feedback around runtime DEFAULT_BROKER enforcement. What changed:
Validation evidence:
tests/unit/test_broker_filter.py ..................... [100%] =============================== warnings summary =============================== .venv/lib/python3.11/site-packages/websockets/legacy/init.py:6 -- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
|
Addressed in 244851c: implemented DEFAULT_BROKER-aware runtime broker filter and added focused tests
|
Claude finished @jwesleye's task —— View job Code Review Complete ✅I'll provide comprehensive feedback on code quality, potential issues, performance considerations, security concerns, and test coverage. Todo List
Review SummaryOverall Assessment: APPROVED with minor suggestions This is a well-implemented feature that cleanly enforces broker filtering at runtime. The code follows the existing patterns, has excellent test coverage, and successfully addresses both ENABLED_BROKERS and DEFAULT_BROKER enforcement requirements from issue #991. ✅ StrengthsArchitecture & Design
Code Quality
Test Coverage
🔍 Code Review Detailsbroker_filter.py:37-49 - Enhanced Broker Mapping Logicdef _tool_broker(tool_name: str, default_broker: str) -> str | None:
if tool_name in _BROKER_AGNOSTIC_TOOLS:
return None
if "schwab" in tool_name:
return "schwab"
return default_broker✅ Excellent enhancement: Now properly honors configured broker_filter.py:64-66 - Default Broker Normalizationnormalized_default = (default_broker or "robinhood").strip().lower()
if normalized_default not in {"robinhood", "schwab"}:
normalized_default = "robinhood"✅ Robust input handling: Safely handles None, invalid values, and provides sensible fallback. broker_filter.py:68-71 - Enhanced Idempotencyif getattr(mcp_server, _WRAPPER_ATTR, False):
setattr(mcp_server, _ENABLED_ATTR, list(enabled_brokers))
setattr(mcp_server, _DEFAULT_ATTR, normalized_default)
return✅ Smart approach: Now updates both enabled brokers AND default broker on re-installation, ensuring full runtime reconfiguration. broker_filter.py:78-98 - Runtime Configuration Accessasync def _filtered_call_tool(tool_name: str, arguments: dict[str, Any]) -> Any:
active_enabled: list[str] = getattr(mcp_server, _ENABLED_ATTR, ["robinhood"])
active_default: str = getattr(mcp_server, _DEFAULT_ATTR, "robinhood")
required = _tool_broker(tool_name, active_default)✅ Proper runtime lookup: Correctly retrieves current configuration on each call, enabling dynamic reconfiguration. app.py:2304-2308 - Integration Pointinstall_broker_filter(
server,
config.brokers.enabled_brokers,
default_broker=config.brokers.default_broker,
)✅ Perfect placement: Called after 🛡️ Security Assessment
⚡ Performance Considerations
📝 Minor Suggestions1. Documentation Enhancement (Optional)Consider adding a brief docstring example to def _tool_broker(tool_name: str, default_broker: str) -> str | None:
"""Return the broker name required by *tool_name*, or ``None`` if agnostic.
Examples:
>>> _tool_broker("schwab_quote", "robinhood")
"schwab"
>>> _tool_broker("portfolio", "robinhood")
"robinhood"
>>> _tool_broker("portfolio", "schwab")
"schwab"
>>> _tool_broker("broker_status", "robinhood")
None
"""2. Future-Proofing (Optional)The substring matching for Schwab ( 🧪 Test Quality AssessmentExcellent test suite with these highlights:
🚀 Integration AssessmentPerfect integration approach:
Final Recommendation✅ APPROVE - This PR is ready for merge. The implementation successfully addresses all requirements from issue #991, including the previously missing DEFAULT_BROKER enforcement. The code quality is high, test coverage is comprehensive (21 passing tests), and the integration is seamless. The latest changes properly honor the configured default broker for tool routing, making this a complete solution. |
Closes #991
Changes
src/open_stocks_mcp/server/broker_filter.py— a new module following the samecall_tool-wrapping pattern astool_execution_limits.py. It provides:_tool_broker(name)— maps any tool name to its required broker ("schwab"for names containing"schwab",Nonefor meta/cross-broker tools,"robinhood"for everything else)install_broker_filter(mcp_server, enabled_brokers)— wrapscall_toolto return a structured error for disabled-broker tools, and wrapslist_toolsto omit them from the listinginstall_broker_filterintomain()inapp.py, called once aftersetup_brokers()completes, just before the transport starts accepting connectionstests/unit/test_broker_filter.pycovering mapping, blocking, list-filtering, and idempotencyBehaviour
ENABLED_BROKERS=robinhood(default), callschwab_quotebroker disablederrorENABLED_BROKERS=robinhood, calllist_toolsENABLED_BROKERS=robinhood,schwabbroker_status,list_brokers, etc.)Test plan
uv run pytest tests/unit/test_broker_filter.py -v— 17 unit tests, all passuv run pytest -m "not slow and not exception_test and not integration and not rate_limited and not performance" -q— 1479 passeduv run ruff check . && uv run ruff format . && uv run mypy src/— all clean