Architecture Documentation β
Overview β
The Hug SCM MCP Server follows a modular, layered architecture that separates concerns and promotes maintainability, extensibility, and testability.
Design Principles β
1. Separation of Concerns β
Each module has a single, well-defined responsibility:
- Command Execution - Safe execution of external commands
- Tool Handling - Business logic for individual MCP tools
- Tool Definitions - MCP protocol schemas
- Server Orchestration - MCP protocol implementation
2. Dependency Injection β
The CommandExecutor is injected into tool handlers, making them:
- Easier to test (can mock the executor)
- More flexible (can swap implementations)
- Decoupled from execution details
3. Registry Pattern β
The ToolRegistry allows dynamic tool registration:
- Easy to add new tools
- Extensible by users
- Clear tool discovery mechanism
4. Fail-Safe Design β
All operations are designed to fail gracefully:
- Command timeouts prevent hangs
- Path validation prevents security issues
- Error messages are user-friendly
- No exceptions bubble up to MCP layer
Module Architecture β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β MCP Protocol Layer β
β (stdio transport) β
ββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β server.py β
β ββββββββββββββββ ββββββββββββββββ β
β β list_tools() β β call_tool() β β
β ββββββββ¬ββββββββ βββββββββ¬βββββββ β
β β β β
β β βΌ β
β β βββββββββββββββββββ β
β β β ToolRegistry β β
β β ββββββββββ¬βββββββββ β
β β β β
β βΌ βΌ β
β ββββββββββββββββ ββββββββββββββββ β
β βtool_ β βtool_ β β
β βdefinitions β βhandlers β β
β β β β β β
β β - Schemas β β - Logic β β
β ββββββββββββββββ ββββββββ¬ββββββββ β
β β β
β βΌ β
β ββββββββββββββββ β
β βCommandExecutorβ β
β β β β
β β - Validation β β
β β - Execution β β
β ββββββββ¬ββββββββ β
ββββββββββββββββββββββββββββΌββββββββββββββββββββββββββ
β
βΌ
ββββββββββββββββ
β subprocess β
β β
β Hug Commands β
ββββββββββββββββModule Descriptions β
command_executor.py β
Purpose: Safe execution of Hug SCM commands with security and error handling.
Key Components:
CommandExecutor- Main execution classvalidate_path()- Path security validationexecute()- Command execution with timeouts
Security Features:
- Path resolution to absolute paths
- Directory traversal prevention
- Existence verification
- Type checking (must be directory)
Error Handling:
- Timeout protection (default 30s)
- Command not found
- Invalid paths
- Generic exceptions
Example:
executor = CommandExecutor(timeout=60)
result = executor.execute(["h", "files", "3"], cwd="/path/to/repo")
# result = {"success": bool, "output": str, "error": str?, "exit_code": int}tool_handlers.py β
Purpose: Business logic for each MCP tool, organized by responsibility.
Key Components:
ToolHandler- Abstract base class- Individual handlers (e.g.,
HugHFilesHandler,HugStatusHandler) ToolRegistry- Handler registration and discovery
Handler Pattern:
class CustomToolHandler(ToolHandler):
def handle(self, arguments: dict) -> dict:
# Extract arguments
# Build command
# Execute via executor
# Return resultRegistry Usage:
registry = ToolRegistry(executor)
handler = registry.get_handler("hug_status")
result = handler.handle({"format": "short"})tool_definitions.py β
Purpose: MCP protocol schemas for all tools.
Structure:
- Pure data - no business logic
- JSON Schema compliant
- Complete parameter documentation
- Validation rules
Benefits:
- Separates API contract from implementation
- Easy to review and update
- Can be generated automatically
- Supports tool discovery
server.py β
Purpose: MCP protocol implementation and server orchestration.
Responsibilities:
- MCP protocol compliance
- Tool registration
- Request routing
- Response formatting
Kept Minimal:
- Only 86 lines (was 397)
- No business logic
- Pure orchestration
- Clean async/await
Data Flow β
Tool Execution Flow β
1. MCP Client Request
β
2. server.call_tool(name, arguments)
β
3. registry.get_handler(name)
β
4. handler.handle(arguments)
β
5. executor.validate_path(cwd)
β
6. executor.execute(args, cwd)
β
7. subprocess.run(["hug", ...])
β
8. Return result dict
β
9. Format as TextContent
β
10. MCP Client ResponseError Flow β
Error occurs anywhere in chain
β
Catch at appropriate level
β
Return error dict/TextContent
β
MCP client receives readable errorExtensibility β
Adding a New Tool β
Step 1: Create handler in tool_handlers.py
class HugNewToolHandler(ToolHandler):
"""Handler for new_tool."""
def handle(self, arguments: dict[str, Any]) -> dict[str, Any]:
# Your logic here
args = ["command", "args"]
cwd = arguments.get("cwd")
return self.executor.execute(args, cwd)Step 2: Register in ToolRegistry._register_default_handlers()
self.register("hug_new_tool", HugNewToolHandler(self.executor))Step 3: Add schema to tool_definitions.py
Tool(
name="hug_new_tool",
description="What this tool does",
inputSchema={
"type": "object",
"properties": {
"param": {"type": "string", "description": "Parameter"},
"cwd": {"type": "string", "description": "Working directory"},
},
},
)Step 4: Add tests in tests/test_tool_handlers.py
def test_new_tool_handler(self, temp_git_repo):
executor = CommandExecutor()
handler = HugNewToolHandler(executor)
result = handler.handle({"cwd": str(temp_git_repo)})
assert result["success"] is TrueCustomizing Command Execution β
Extend CommandExecutor for custom behavior:
class CustomExecutor(CommandExecutor):
def execute(self, args, cwd):
# Add logging
logger.info(f"Executing: {args}")
result = super().execute(args, cwd)
# Add metrics
metrics.record(result)
return result
# Use custom executor
executor = CustomExecutor(timeout=60)
registry = ToolRegistry(executor)External Tool Registration β
Users can register their own tools:
from hug_scm_mcp_server.tool_handlers import ToolHandler, ToolRegistry
from hug_scm_mcp_server.command_executor import CommandExecutor
class MyCustomHandler(ToolHandler):
def handle(self, arguments):
# Custom logic
pass
executor = CommandExecutor()
registry = ToolRegistry(executor)
registry.register("my_custom_tool", MyCustomHandler(executor))Testing Strategy β
Unit Tests β
- test_command_executor.py - Executor logic, path validation
- test_tool_handlers.py - Handler logic, registry
- test_server.py - Server integration
Test Organization β
tests/
βββ conftest.py # Shared fixtures
βββ test_command_executor.py # 14 tests
βββ test_tool_handlers.py # 13 tests
βββ test_server.py # 12 testsCoverage Targets β
- command_executor: 87% (critical path)
- tool_handlers: 77% (business logic)
- server: 85% (integration)
- Overall: 81%
Performance Considerations β
Command Timeout β
- Default: 30 seconds
- Configurable per executor
- Prevents hung processes
Path Validation β
- Minimal overhead (~1ms)
- Critical security check
- Cached by OS
Test Execution β
- Isolated temp directories
- Parallel-safe
- Fast cleanup (3.7s total)
Security Architecture β
Defense in Depth β
Layer 1: Input Validation
- Parameter type checking via JSON Schema
- Required field enforcement
Layer 2: Path Validation
- Absolute path resolution
- Symlink resolution
- Existence verification
- Type checking
Layer 3: Command Execution
- Subprocess with timeout
- No shell injection (list args)
- Captured output only
- Error containment
Layer 4: Error Handling
- Graceful degradation
- No sensitive data in errors
- User-friendly messages
Threat Model β
Mitigated:
- Directory traversal β
- Command injection β
- Denial of service (timeout) β
- Information disclosure β
Not Mitigated:
- User permission escalation (by design - runs as user)
- Repository-level attacks (Hug/Git responsibility)
Future Enhancements β
Planned Improvements β
Caching Layer
- Cache command results
- Invalidate on repository changes
- Configurable TTL
Metrics & Observability
- Command execution times
- Error rates
- Tool usage statistics
Async Optimization
- Parallel command execution
- Streaming results
- Progressive updates
Plugin System
- Dynamic tool loading
- Third-party tools
- Tool marketplace
Comparison: Before vs After β
Before Refactoring β
server.py (397 lines)
βββ Everything mixed together
βββ Hard to test
βββ No path validation
βββ Difficult to extendAfter Refactoring β
src/hug_scm_mcp_server/
βββ command_executor.py (113 lines) - Execution + Security
βββ tool_handlers.py (230 lines) - Business Logic
βββ tool_definitions.py (205 lines) - API Schemas
βββ server.py (86 lines) - Orchestration
βββ __init__.py (2 lines) - Package
Benefits:
β
81% test coverage (+12%)
β
5 focused modules (was 1)
β
Path validation implemented
β
22 new tests
β
Easy to extend
β
Clear separation of concernsContributing Guidelines β
When contributing to this architecture:
- Maintain Separation - Don't mix concerns
- Add Tests - 80%+ coverage required
- Document Changes - Update this file
- Follow Patterns - Use existing handlers as templates
- Security First - Validate inputs, handle errors
References β
- [MCP Protocol Specification]
- [Python asyncio]
- [Subprocess Security]