Skip to content

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 class
    • validate_path() - Path security validation
    • execute() - 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:

python
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:

python
class CustomToolHandler(ToolHandler):
    def handle(self, arguments: dict) -> dict:
        # Extract arguments
        # Build command
        # Execute via executor
        # Return result

Registry Usage:

python
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 Response

Error Flow ​

Error occurs anywhere in chain
   ↓
Catch at appropriate level
   ↓
Return error dict/TextContent
   ↓
MCP client receives readable error

Extensibility ​

Adding a New Tool ​

Step 1: Create handler in tool_handlers.py

python
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()

python
self.register("hug_new_tool", HugNewToolHandler(self.executor))

Step 3: Add schema to tool_definitions.py

python
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

python
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 True

Customizing Command Execution ​

Extend CommandExecutor for custom behavior:

python
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:

python
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 tests

Coverage 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 ​

  1. Caching Layer

    • Cache command results
    • Invalidate on repository changes
    • Configurable TTL
  2. Metrics & Observability

    • Command execution times
    • Error rates
    • Tool usage statistics
  3. Async Optimization

    • Parallel command execution
    • Streaming results
    • Progressive updates
  4. 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 extend

After 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 concerns

Contributing Guidelines ​

When contributing to this architecture:

  1. Maintain Separation - Don't mix concerns
  2. Add Tests - 80%+ coverage required
  3. Document Changes - Update this file
  4. Follow Patterns - Use existing handlers as templates
  5. Security First - Validate inputs, handle errors

References ​

  • [MCP Protocol Specification]
  • [Python asyncio]
  • [Subprocess Security]

Released under the Apache 2.0 License.