13 Contributing - samerfarida/mcp-ssh-orchestrator GitHub Wiki

13. Contributing

Purpose: Guide for contributing to MCP SSH Orchestrator development, including setup, coding standards, and submission process.

Overview

We welcome contributions to MCP SSH Orchestrator! This guide covers how to set up a development environment, understand the codebase, and submit contributions.

Development Setup

Prerequisites

Required Software

  • Python 3.11+
  • Docker and Docker Compose
  • Git
  • SSH client
  • Code editor (VS Code recommended)

Optional Tools

  • Poetry (for dependency management)
  • Pre-commit hooks
  • Docker Desktop

Local Development Environment

1. Clone the repository

git clone https://github.com/samerfarida/mcp-ssh-orchestrator.git cd mcp-ssh-orchestrator


### 2. Set up Python environment:

# Create virtual environment
python -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate

# Install dependencies
pip install -e .
pip install -e ".[dev,test]"

3. Set up pre-commit hooks

pre-commit install


### 4. Configure development environment:

# Copy example configurations
cp examples/example-servers.yml config/servers.yml
cp examples/example-credentials.yml config/credentials.yml
cp examples/example-policy.yml config/policy.yml

# Generate SSH keys for testing
ssh-keygen -t ed25519 -f keys/test_key -N ""
chmod 0400 keys/test_key

Docker Development

Development with Docker Compose

Build development image

docker-compose -f compose/docker-compose.dev.yml build

Run development environment

docker-compose -f compose/docker-compose.dev.yml up

Run tests

docker-compose -f compose/docker-compose.dev.yml run mcp-ssh pytest


### Development Container:

# Run development container
docker run -it --rm \
  -v $(pwd):/app \
  -v $(pwd)/config:/app/config:ro \
  -v $(pwd)/keys:/app/keys:ro \
  ghcr.io/samerfarida/mcp-ssh-orchestrator:latest \
  bash

Codebase Structure

Project Layout

src/mcp_ssh/
├── __init__.py          # Package initialization
├── config.py            # Configuration management
├── mcp_server.py        # MCP server implementation
├── policy.py            # Policy engine
├── ssh_client.py        # SSH client wrapper
└── tools/
    ├── __init__.py      # Tools package
    └── utilities.py     # Utility functions

tests/
├── test_config.py                    # Configuration tests
├── test_policy.py                    # Policy engine tests
├── test_ssh.py                       # SSH client tests
├── test_server_tools.py              # MCP server tests
├── test_ssh_coverage.py              # SSH client edge cases and coverage
├── test_ssh_integration.py           # SSH integration tests
├── test_mcp_server_coverage.py      # MCP server coverage tests
├── test_mcp_server_edge_cases.py    # MCP server edge case tests
├── test_mcp_server_on_tag_coverage.py # MCP server tag-based execution tests
├── test_utilities_coverage.py       # Utilities coverage tests
└── test_utilities_coverage.py  # Utilities coverage tests (includes async task management)

Key Components

MCP Server (mcp_server.py)

  • Implements MCP protocol
  • Handles tool registration
  • Manages client sessions
  • Provides audit logging

Policy Engine (policy.py)

  • Evaluates command policies
  • Manages security rules
  • Handles network filtering
  • Provides compliance reporting

SSH Client (ssh_client.py)

  • Wraps SSH connections
  • Manages authentication
  • Handles command execution
  • Provides error handling

Configuration (config.py)

  • Loads YAML configurations
  • Validates settings
  • Manages secrets
  • Provides defaults

Coding Standards

Python Style

Code Formatting

  • Use Black for code formatting
  • Use isort for import sorting
  • Follow PEP 8 guidelines
  • Use type hints

Example

from typing import Dict, List, Optional import logging

logger = logging.getLogger(name)

def evaluate_policy( alias: str, command: str, tags: List[str] ) -> Dict[str, bool]: """Evaluate policy for command execution.

Args:
    alias: Host alias
    command: Command to execute
    tags: Host tags

Returns:
    Policy evaluation result
"""
# Implementation here
pass

### Documentation

### Docstring Format:

def ssh_run(alias: str, command: str) -> Dict[str, Any]:
    """Execute SSH command on target host.

    Args:
        alias: Host alias from servers.yml
        command: Command to execute

    Returns:
        Command execution result with output and exit code

    Raises:
        PolicyViolationError: If command violates policy
        SSHConnectionError: If SSH connection fails
    """
    pass

Inline Comments

Policy evaluation: check if command is allowed

if not policy.evaluate(alias, command, tags): raise PolicyViolationError(f"Command '{command}' not allowed for '{alias}'")

SSH connection: establish secure connection

with SSHClient(host_config) as client: result = client.execute(command)


### Testing

### Test Structure:

import pytest
from unittest.mock import Mock, patch
from mcp_ssh.policy import Policy

class TestPolicy:
    """Test policy engine functionality."""

    def setup_method(self):
        """Set up test fixtures."""
        self.policy = Policy("tests/fixtures/policy.yml")

    def test_allow_command(self):
        """Test command allowance."""
        result = self.policy.evaluate("web1", "uptime", ["production"])
        assert result["allowed"] is True

    def test_deny_command(self):
        """Test command denial."""
        result = self.policy.evaluate("web1", "rm -rf /", ["production"])
        assert result["allowed"] is False

    @patch('mcp_ssh.policy.logger')
    def test_policy_violation_logging(self, mock_logger):
        """Test policy violation logging."""
        self.policy.evaluate("web1", "rm -rf /", ["production"])
        mock_logger.warning.assert_called_once()

Test Data

# tests/fixtures/policy.yml

known_hosts_path: "/app/keys/known_hosts"

limits:
  max_seconds: 30
  max_output_bytes: 131072

rules:
  - action: "allow"
    aliases: ["web1"]
    tags: ["production"]
    commands:
      - "uptime*"
      - "df -h*"

  - action: "deny"
    aliases: ["*"]
    tags: ["*"]
    commands:
      - "rm -rf *"
      - "shutdown*"

Development Workflow

Feature Development

1. Create feature branch

git checkout -b feature/new-policy-rule

2. Implement feature

# Add new policy rule type

class PolicyRule:
    def __init__(self, action: str, conditions: Dict[str, Any]):
        self.action = action
        self.conditions = conditions

    def evaluate(self, alias: str, command: str, tags: List[str]) -> bool:
        # Implementation
        pass

3. Add tests

def test_new_policy_rule():
    """Test new policy rule functionality."""
    rule = PolicyRule("allow", {"aliases": ["web1"]})
    result = rule.evaluate("web1", "uptime", ["production"])
    assert result is True

4. Update documentation

# Update wiki documentation
# Add examples to usage cookbook
# Update API reference

Bug Fixes

1. Create bug fix branch

git checkout -b bugfix/ssh-connection-timeout

2. Reproduce issue

Create test that reproduces the bug

def test_ssh_connection_timeout(): """Test SSH connection timeout handling.""" with pytest.raises(SSHTimeoutError): ssh_client.connect("slow-host", timeout=1)


### 3. Fix the issue:

# Implement fix
def connect(self, host: str, timeout: int = 30):
    """Connect to SSH host with timeout."""
    try:
        self.client.connect(host, timeout=timeout)
    except socket.timeout:
        raise SSHTimeoutError(f"Connection to {host} timed out")

4. Verify fix

Test that fix works

def test_ssh_connection_timeout_fixed(): """Test SSH connection timeout fix.""" with patch('socket.socket') as mock_socket: mock_socket.return_value.connect.side_effect = socket.timeout() with pytest.raises(SSHTimeoutError): ssh_client.connect("slow-host", timeout=1)


### Code Review Process

### 1. Submit pull request:

git push origin feature/new-policy-rule
# Create PR on GitHub

2. Address feedback

Make requested changes

git add . git commit -m "Address review feedback" git push origin feature/new-policy-rule


### 3. Merge after approval:

# Squash commits if needed
git rebase -i main
git push origin feature/new-policy-rule --force-with-lease

Testing Guidelines

Unit Tests

Test Coverage

  • Aim for 90%+ code coverage
  • Test all public methods
  • Test error conditions
  • Test edge cases
  • Recent additions include comprehensive coverage tests:
    • SSH client edge cases (test_ssh_coverage.py, test_ssh_integration.py)
    • MCP server coverage including notification handlers and context logging (test_mcp_server_coverage.py, test_mcp_server_edge_cases.py, test_mcp_server_on_tag_coverage.py)
    • Utilities coverage for async task management (test_utilities_coverage.py)
  • All tests must pass linting (Ruff, Black) before submission

Test Examples

def test_config_validation(): """Test configuration validation.""" config = Config("tests/fixtures/config.yml") assert config.validate() is True

# Test invalid config
with pytest.raises(ConfigError):
    Config("tests/fixtures/invalid-config.yml")

def test_policy_evaluation(): """Test policy evaluation logic.""" policy = Policy("tests/fixtures/policy.yml")

# Test allow case
result = policy.evaluate("web1", "uptime", ["production"])
assert result["allowed"] is True

# Test deny case
result = policy.evaluate("web1", "rm -rf /", ["production"])
assert result["allowed"] is False

### Integration Tests

### End-to-end testing:

def test_ssh_command_execution():
    """Test complete SSH command execution flow."""
    # Set up test environment
    with DockerCompose("tests/docker-compose.yml") as compose:
        # Wait for services
        compose.wait_for_service("test-host", 22)

        # Execute command
        result = ssh_run("test-host", "uptime")

        # Verify result
        assert result["exit_code"] == 0
        assert "load average" in result["output"]

MCP Inspector Testing

Interactive Testing with MCP Inspector

The MCP Inspector provides a web-based interface for testing MCP servers. It's essential for verifying structured output, schema generation, and tool behavior.

Setup

Test with local Python server

cd /path/to/mcp-ssh-orchestrator export MCP_SSH_CONFIG_DIR=$(pwd)/config source venv/bin/activate npx -y @modelcontextprotocol/inspector python -m mcp_ssh.mcp_server

Or test with Docker-based server

npx -y @modelcontextprotocol/inspector docker compose -f compose/docker-compose.dev.yml run --rm -T mcp-ssh python -m mcp_ssh.mcp_server


### What to Verify:

1. **Schema Generation**: All tools should show generated JSON schemas
2. **Structured Output**: Tools return structured dicts (verify "Valid according to output schema")
3. **Error Handling**: Error cases return appropriate responses
4. **Tool Execution**: All tools execute correctly with various inputs

### Best Practices:

- Use MCP Inspector during development to verify tool changes
- Test all tools after major refactoring
- Verify schema generation for new tools
- Test edge cases and error conditions

### Performance Tests

### Load testing:

def test_concurrent_ssh_connections():
    """Test concurrent SSH connections."""
    import concurrent.futures

    def execute_command(host):
        return ssh_run(host, "uptime")

    hosts = ["web1", "web2", "web3"]

    with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
        futures = [executor.submit(execute_command, host) for host in hosts]
        results = [future.result() for future in futures]

    # Verify all commands succeeded
    assert all(result["exit_code"] == 0 for result in results)

Documentation Contributions

Wiki Updates

Documentation Standards

  • Use clear, concise language
  • Include code examples
  • Provide practical use cases
  • Update related sections

Example Update

Add new section to Usage Cookbook

Advanced Policy Rules

Purpose: Demonstrate advanced policy rule configurations for complex scenarios.

Time-based Rules

rules:
  - action: "allow"
    aliases: ["*"]
    tags: ["production"]
    commands: ["uptime*"]
    time_restrictions:
      start_time: "09:00"
      end_time: "17:00"
      timezone: "UTC"

Resource-based Rules

rules:
  - action: "allow"
    aliases: ["*"]
    tags: ["production"]
    commands: ["df -h*"]
    resource_limits:
      max_memory: "1GB"
      max_cpu: "50%"

API Documentation

Function documentation

def ssh_run(alias: str, command: str) -> Dict[str, Any]: """Execute SSH command on target host.

This function provides secure SSH command execution with policy enforcement,
audit logging, and error handling.

Args:
    alias: Host alias from servers.yml configuration
    command: Command to execute on the target host

Returns:
    Dictionary containing:
        - output: Command output (str)
        - error: Error output (str)
        - exit_code: Command exit code (int)
        - execution_time: Execution duration in seconds (float)

Raises:
    PolicyViolationError: If command violates policy rules
    SSHConnectionError: If SSH connection fails
    SSHTimeoutError: If command execution times out

Example:
    >>> result = ssh_run("web1", "uptime")
    >>> print(result["output"])
    10:30:45 up 5 days, 2:15, 1 user, load average: 0.00, 0.01, 0.05
"""
pass

## Release Process

### Version Management

### Semantic Versioning

- MAJOR: Breaking changes
- MINOR: New features
- PATCH: Bug fixes

### Version Bump

# Update version in pyproject.toml

version = "0.2.0"

# Update CHANGELOG.md

## [0.2.0] - 2024-01-15

### Added

- New policy rule types
- Enhanced audit logging
- Performance improvements

### Changed

- Updated MCP protocol version
- Improved error handling

### Fixed

- SSH connection timeout issues
- Policy evaluation bugs

```text

### Release Checklist

### Pre-release

- [ ] All tests passing
- [ ] Documentation updated
- [ ] CHANGELOG.md updated
- [ ] Version bumped
- [ ] Security review completed

### Release

- [ ] Create release tag
- [ ] Build Docker image
- [ ] Push to registry
- [ ] Update GitHub release
- [ ] Announce release

## Community Guidelines

### Code of Conduct

### Our Pledge

- Be respectful and inclusive
- Welcome newcomers
- Focus on constructive feedback
- Respect different viewpoints

### Reporting Issues

- Use GitHub issues for bugs
- Use discussions for questions
- Be specific and provide context
- Include reproduction steps

### Getting Help

### Resources

- GitHub Discussions for questions
- GitHub Issues for bugs
- Wiki documentation
- Code examples

### Contributing

- Start with small fixes
- Ask questions early
- Follow coding standards
- Test your changes

## Next Steps

- **[FAQ](14-FAQ)** - Common development questions
- **[CHANGELOG](../CHANGELOG.md)** - Version history and releases
- **[Security Model](05-Security-Model)** - Security considerations for contributors
- **[Troubleshooting](12-Troubleshooting)** - Development environment issues