Skip to main content
Glama
validation-framework-summary.md12.8 kB
# Validation Framework Implementation Summary ## Overview This document summarizes the validation framework added to `src/config.py` for Phase 1 of the hot config reload feature. ## What Was Added ### 1. Validation Functions #### `validate_mcp_config(config: dict) -> tuple[bool, Optional[str]]` Validates MCP server configuration structure without loading from file. **Validates:** - Top-level structure (must be dict with "mcpServers" key) - Each server must be a dict with either "command" or "url" (not both) - Stdio transport validation: - `command` must be string - `args` must be array of strings - `env` must be dict with string values - HTTP transport validation: - `url` must be string starting with http:// or https:// - `headers` must be dict with string values **Returns:** - `(True, None)` if valid - `(False, error_message)` if invalid with helpful error message **Example:** ```python config = {"mcpServers": {"test": {"command": "npx"}}} valid, error = validate_mcp_config(config) # Returns: (True, None) config = {"wrong_key": {}} valid, error = validate_mcp_config(config) # Returns: (False, 'Missing required key "mcpServers"') ``` #### `validate_gateway_rules(rules: dict) -> tuple[bool, Optional[str]]` Validates gateway rules configuration structure without loading from file. **Validates:** - Top-level structure (must be dict, can have "agents" and "defaults") - Agent ID format (alphanumeric, dot, hyphen, underscore) - Allow/deny sections structure - Server lists (strings, wildcard "*" only alone) - Tool patterns (strings, single wildcard at start/end/alone) - Defaults section (deny_on_missing_agent must be boolean) **Returns:** - `(True, None)` if valid - `(False, error_message)` if invalid with helpful error message **Example:** ```python rules = { "agents": { "researcher": { "allow": { "servers": ["brave-search"], "tools": {"brave-search": ["*"]} } } } } valid, error = validate_gateway_rules(rules) # Returns: (True, None) rules = {"agents": {"agent@invalid": {"allow": {}}}} valid, error = validate_gateway_rules(rules) # Returns: (False, 'Agent ID "agent@invalid" contains invalid characters...') ``` ### 2. Reload Function #### `reload_configs(mcp_config_path: str, gateway_rules_path: str) -> tuple[Optional[dict], Optional[dict], Optional[str]]` Loads and validates both configuration files without applying them to the running system. **Process:** 1. Loads MCP config from file 2. Validates MCP config structure 3. Loads gateway rules from file 4. Validates gateway rules structure 5. Cross-validates that servers referenced in rules exist in config **Returns:** - Success: `(mcp_config_dict, gateway_rules_dict, None)` - Failure: `(None, None, error_message)` **Important Notes:** - Does NOT perform environment variable substitution (that's for `load_mcp_config()`) - Does NOT apply configs to running system (just validates) - Fails fast on first error encountered - Includes cross-validation warnings in error message **Example:** ```python # Valid configs mcp_config, gateway_rules, error = reload_configs( "/path/to/.mcp.json", "/path/to/.mcp-gateway-rules.json" ) if error is None: print(f"Loaded {len(mcp_config['mcpServers'])} servers") print(f"Loaded {len(gateway_rules['agents'])} agents") else: print(f"Validation failed: {error}") ``` ### 3. Config Path Storage #### Global Variables ```python _mcp_config_path: Optional[str] = None _gateway_rules_path: Optional[str] = None ``` Stores the absolute paths of loaded configs for future reloading. #### `get_stored_config_paths() -> tuple[Optional[str], Optional[str]]` Returns the stored config file paths. **Returns:** - `(mcp_config_path, gateway_rules_path)` - Either or both may be None **Updated Functions:** - `load_mcp_config()` now stores its path in `_mcp_config_path` - `load_gateway_rules()` now stores its path in `_gateway_rules_path` ## Validation Details ### MCP Config Validation Rules 1. **Structure:** - Must be a JSON object - Must contain "mcpServers" key - "mcpServers" must be an object 2. **Server Configuration:** - Each server must be an object - Must have either "command" (stdio) OR "url" (HTTP), not both - Must not have neither 3. **Stdio Transport:** - "command": string (required) - "args": array of strings (optional) - "env": object with string values (optional) 4. **HTTP Transport:** - "url": string starting with http:// or https:// (required) - "headers": object with string values (optional) ### Gateway Rules Validation Rules 1. **Structure:** - Must be a JSON object - Can contain "agents" and/or "defaults" 2. **Agent Configuration:** - Agent IDs: non-empty strings, alphanumeric + dot/hyphen/underscore - Each agent must be an object - Can have "allow" and/or "deny" sections 3. **Allow/Deny Sections:** - Must be objects - Can contain "servers" (array) and/or "tools" (object) 4. **Servers List:** - Array of strings - Wildcard "*" can only be used alone (not "db-*") 5. **Tools Mapping:** - Object with server names as keys - Values are arrays of string patterns - Wildcard rules: - Only one "*" per pattern - Must be alone ("*") or at start ("get_*") or end ("*_query") - Cannot be in middle ("get*data") or multiple ("get_*_all") 6. **Defaults Section:** - Must be an object - "deny_on_missing_agent" must be boolean ### Cross-Validation The `reload_configs()` function performs cross-validation using the existing `validate_rules_against_servers()` function: - Checks that all servers referenced in rules exist in MCP config - Checks both "allow.servers" and "deny.servers" - Checks all servers in "allow.tools" and "deny.tools" - Wildcard "*" is allowed (matches any server) - Returns descriptive error if undefined servers found ## Edge Cases Handled 1. **Empty but valid configs:** - `{"mcpServers": {}}` is valid (no servers configured) - `{"agents": {}}` is valid (no agents configured) 2. **Complex wildcard patterns:** - Multiple patterns per server: `["*", "get_*", "*_query", "list_*"]` ✓ - Multiple wildcards in pattern: `["get_*_all"]` ✗ 3. **Hierarchical agent names:** - Dot notation: `"team.backend"`, `"team.frontend"` ✓ - Underscore: `"org_admin"` ✓ - Hyphen: `"sub-agent"` ✓ - Special chars: `"agent@invalid"` ✗ 4. **Path expansion:** - Handles `~` for home directory - Converts relative paths to absolute - Validates file existence before loading 5. **JSON syntax errors:** - Returns clear error with position info - Distinguishes between file not found, JSON syntax, and validation errors 6. **Cross-validation warnings:** - Lists all undefined servers (not just first) - Provides agent ID and section context - Formatted for easy debugging ## Testing ### Test Coverage **Total Tests:** 96 (43 existing + 53 new) - **43 existing tests:** All pass unchanged (validates backward compatibility) - **53 new tests:** Comprehensive validation and reload testing **New Test File:** `tests/test_validation_and_reload.py` **Test Categories:** 1. `TestValidateMCPConfig` (19 tests) - Valid configs (minimal, with args, with env, HTTP, with headers) - Invalid structure (missing key, wrong types) - Invalid transport (both, neither, invalid values) - Invalid field types (command, args, env, url, headers) 2. `TestValidateGatewayRules` (22 tests) - Valid rules (minimal, with tools, with deny, with defaults, hierarchical) - Invalid structure (wrong types, missing fields) - Invalid agent IDs (empty, special characters) - Invalid wildcards (multiple, in middle, in server names) - Invalid field types (servers, tools, defaults) 3. `TestReloadConfigs` (9 tests) - Valid reload - File not found (both MCP and rules) - Invalid JSON (both MCP and rules) - Invalid structure (both MCP and rules) - Undefined servers in rules - Path expansion 4. `TestGetStoredConfigPaths` (3 tests) - Initially None - Stored after loading MCP config - Stored after loading gateway rules ### Running Tests ```bash # All config tests uv run pytest tests/test_config.py tests/test_validation_and_reload.py -v # Just new validation tests uv run pytest tests/test_validation_and_reload.py -v # All tests (including integration) uv run pytest tests/ -v ``` ### Demo Script A demo script is provided at `tests/demo_validation.py` that demonstrates: 1. Validation functions with valid and invalid configs 2. Reload functionality with various error scenarios 3. Edge cases (empty configs, wildcards, hierarchical agents) Run with: ```bash uv run python -c "import sys; sys.path.insert(0, '.'); from tests.demo_validation import *; demo_validation(); demo_reload(); demo_edge_cases()" ``` ## Backward Compatibility **No Breaking Changes:** - All existing functionality preserved - Existing `load_mcp_config()` and `load_gateway_rules()` work exactly as before - Only additions: new validation functions and path storage - All 43 existing tests pass unchanged **New Features:** - Validation can now be performed without loading from files - Config paths are stored for future reload operations - `reload_configs()` provides one-step validation of both configs ## Implementation Quality ### Type Hints All new functions have complete type hints: ```python def validate_mcp_config(config: dict) -> tuple[bool, Optional[str]]: ... def validate_gateway_rules(rules: dict) -> tuple[bool, Optional[str]]: ... def reload_configs(mcp_config_path: str, gateway_rules_path: str) -> tuple[Optional[dict], Optional[dict], Optional[str]]: ... def get_stored_config_paths() -> tuple[Optional[str], Optional[str]]: ... ``` ### Docstrings All new functions have comprehensive docstrings with: - Purpose description - Args documentation - Returns documentation - Usage examples where appropriate ### Error Messages Error messages are: - Descriptive (identify exact problem) - Contextual (include agent ID, server name, field path) - Actionable (tell user what to fix) **Examples:** - `'Server "test" must specify either "command" (stdio) or "url" (HTTP) transport'` - `'Agent "test" allow.tools["db"][0]: wildcard in pattern "get_*_all" must be at start, end, or alone'` - `'Gateway rules reference undefined servers:\n - Agent "backend" allow.servers references undefined server "nonexistent"'` ## Next Steps (Not Implemented) This implementation covers **Phase 1: Validation Framework** only. Future phases will add: **Phase 2: File Watching** - Monitor config files for changes - Trigger reload on modification - Debounce rapid changes **Phase 3: Atomic Application** - Apply validated configs to running gateway - Handle downstream server lifecycle - Maintain active sessions **Phase 4: Rollback & Monitoring** - Track config versions - Rollback on application failure - Emit reload metrics and events ## Summary ### What Was Added 1. ✅ `validate_mcp_config()` - Validate MCP config structure 2. ✅ `validate_gateway_rules()` - Validate gateway rules structure 3. ✅ `reload_configs()` - Load and validate both configs 4. ✅ `get_stored_config_paths()` - Retrieve stored config paths 5. ✅ Config path storage in `load_mcp_config()` and `load_gateway_rules()` 6. ✅ 53 comprehensive tests 7. ✅ Demo script ### What They Validate - **MCP Config:** Structure, transport types, field types, URLs, required fields - **Gateway Rules:** Structure, agent IDs, wildcards, tool patterns, defaults - **Cross-validation:** Servers referenced in rules exist in config ### How reload_configs() Works 1. Loads MCP config from disk (no env var substitution) 2. Validates MCP config structure 3. Loads gateway rules from disk 4. Validates gateway rules structure 5. Cross-validates server references 6. Returns both configs if valid, error message if not ### Edge Cases Handled - Empty but valid configs - Complex wildcard patterns - Hierarchical agent names - Path expansion - JSON syntax errors - Cross-validation with detailed warnings - Multiple validation errors reported clearly ### Test Results - **96 total tests pass** (43 existing + 53 new) - **100% backward compatibility** - all existing tests pass unchanged - **Comprehensive coverage** - valid cases, invalid cases, edge cases - **Clear assertions** - error messages validated for clarity ## Files Modified 1. `src/config.py` - Added: 4 new functions - Added: 2 global variables - Modified: 2 existing functions (added path storage) - Lines added: ~330 2. `tests/test_validation_and_reload.py` - New file: 53 tests - Lines: ~700 3. `tests/demo_validation.py` - New file: Demonstration script - Lines: ~200

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/roddutra/agent-mcp-gateway'

If you have feedback or need assistance with the MCP directory API, please join our Discord server