CONTRIBUTING.md•9.88 kB
# Contributing to DNAC MCP Server
Thank you for your interest in contributing to DNAC MCP Server! This document provides guidelines and best practices for contributing to the project.
## Development Philosophy
This project follows two core development principles:
### 1. Test-Driven Development (TDD)
We follow the **Red-Green-Refactor** cycle:
1. **RED**: Write a failing test that defines desired functionality
2. **GREEN**: Write minimal code to make the test pass
3. **REFACTOR**: Improve code while keeping tests green
**Example TDD workflow:**
```python
# Step 1: RED - Write failing test
def test_get_client_count_returns_integer():
agent = WirelessClientAgent(mock_client)
count = agent._get_count(ssid="Test")
assert isinstance(count, int)
# Step 2: GREEN - Minimal implementation
def _get_count(self, **filters) -> int:
response = self.dnac.clients.get_count(**filters)
return response.get('count', 0)
# Step 3: REFACTOR - Add error handling, logging, etc.
def _get_count(self, **filters) -> int:
try:
response = self.dnac.clients.get_count(**filters)
count = response.get('response', {}).get('count', 0)
logger.info(f"Total client count: {count}")
return count
except ApiError as e:
logger.error(f"API Error: {e}")
raise Exception(f"Failed to get count: {e}")
```
**TDD Guidelines:**
- Write tests first, before implementation
- Keep tests small and focused (one assertion per test when possible)
- Test behavior, not implementation details
- Use descriptive test names that explain the scenario
- Aim for 80%+ code coverage
- Run tests frequently during development
### 2. SOLID Principles
We adhere to SOLID object-oriented design principles:
#### S - Single Responsibility Principle
Each class/function should have one reason to change.
```python
# GOOD: Separate responsibilities
class WirelessClientAgent: # Responsible for client queries
class ConfigManager: # Responsible for configuration
class MCPServer: # Responsible for MCP protocol
```
#### O - Open/Closed Principle
Open for extension, closed for modification.
```python
# Use abstract base classes for extensibility
from abc import ABC, abstractmethod
class ClientFilter(ABC):
@abstractmethod
def apply(self, clients: List[Dict]) -> List[Dict]:
pass
class SSIDFilter(ClientFilter):
def apply(self, clients: List[Dict]) -> List[Dict]:
return [c for c in clients if c['ssid'] == self.ssid]
```
#### L - Liskov Substitution Principle
Subtypes must be substitutable for their base types.
```python
# All implementations must honor the base contract
class BaseAgent(ABC):
@abstractmethod
def get_clients(self, **filters) -> Dict:
"""Must return dict with 'clients', 'total_count', etc."""
pass
```
#### I - Interface Segregation Principle
Clients should not depend on interfaces they don't use.
```python
# Separate interfaces for different capabilities
class Queryable(Protocol):
def query(self, **filters) -> List[Dict]: ...
class Healthable(Protocol):
def get_health(self, mac: str) -> Dict: ...
```
#### D - Dependency Inversion Principle
Depend on abstractions, not concretions.
```python
# Inject dependencies
class WirelessClientAgent:
def __init__(self, dnac_client: DNACenterAPI):
self.dnac = dnac_client # Depends on SDK interface, not implementation
```
## Getting Started
### 1. Fork and Clone
```bash
git fork https://github.com/robertbergman/dnac-mcp.git
git clone https://github.com/robertbergman/dnac-mcp.git
cd dnac-mcp
```
### 2. Set Up Development Environment
```bash
# Create virtual environment
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
# Install development dependencies
pip install -r requirements-dev.txt
# Install in editable mode
pip install -e .
```
### 3. Run Tests
```bash
# Run all tests
pytest
# Run with coverage
pytest --cov=src/dnac_mcp --cov-report=html
# Run specific test file
pytest tests/test_wireless_client_agent.py
# Run specific test
pytest tests/test_wireless_client_agent.py::TestWirelessClientAgent::test_get_count
```
### 4. Code Quality
```bash
# Format code (required before commit)
black src tests
# Lint code
ruff check src tests
# Type checking
mypy src
```
## Contribution Workflow
### 1. Create a Branch
```bash
git checkout -b feature/your-feature-name
# or
git checkout -b fix/bug-description
```
### 2. Write Tests First (TDD)
```python
# tests/test_new_feature.py
class TestNewFeature:
def test_feature_does_something(self):
"""Should do something specific when conditions are met"""
# Arrange
agent = WirelessClientAgent(mock_client)
# Act
result = agent.new_feature()
# Assert
assert result == expected_value
```
### 3. Implement Feature
Write minimal code to make tests pass, then refactor.
### 4. Document
- Add docstrings to all functions/classes
- Update README.md if adding new features
- Add examples if appropriate
### 5. Format and Lint
```bash
black src tests
ruff check src tests
```
### 6. Run Tests
```bash
pytest
```
### 7. Commit
```bash
git add .
git commit -m "Add feature: description of feature"
```
Follow conventional commit format:
- `feat: add new feature`
- `fix: fix bug in feature`
- `docs: update documentation`
- `test: add tests for feature`
- `refactor: refactor code`
### 8. Push and Create Pull Request
```bash
git push origin feature/your-feature-name
```
Then create a Pull Request on GitHub.
## Pull Request Guidelines
### PR Checklist
- [ ] Tests written using TDD approach
- [ ] All tests pass (`pytest`)
- [ ] Code coverage maintained or improved
- [ ] Code formatted with `black`
- [ ] No linting errors (`ruff`)
- [ ] SOLID principles followed
- [ ] Documentation updated
- [ ] Commit messages follow conventional format
- [ ] PR description explains changes
### PR Template
```markdown
## Description
Brief description of changes
## Type of Change
- [ ] Bug fix
- [ ] New feature
- [ ] Breaking change
- [ ] Documentation update
## TDD Approach
- Tests written first: Yes/No
- Coverage added: X%
## SOLID Compliance
- Single Responsibility: ✓
- Open/Closed: ✓
- Liskov Substitution: ✓
- Interface Segregation: ✓
- Dependency Inversion: ✓
## Testing
- [ ] Unit tests pass
- [ ] Integration tests pass (if applicable)
- [ ] Manual testing completed
## Checklist
- [ ] Code formatted with black
- [ ] Linting passes
- [ ] Documentation updated
- [ ] Examples added (if applicable)
```
## Code Style
### Python Style Guide
- Follow PEP 8
- Use type hints where possible
- Maximum line length: 100 characters
- Use descriptive variable names
- Add docstrings to all public functions/classes
### Example:
```python
from typing import Dict, List, Optional
def query_clients(
base_url: str,
username: str,
password: str,
site_id: Optional[str] = None,
max_results: int = 100
) -> Dict[str, Any]:
"""
Query wireless clients from Catalyst Center.
Args:
base_url: DNAC URL (e.g., "https://dnac.example.com")
username: DNAC username
password: DNAC password
site_id: Optional site UUID to filter by
max_results: Maximum number of clients to return
Returns:
Dictionary containing clients list and metadata
Raises:
Exception: If DNAC connection or query fails
"""
# Implementation
pass
```
## Testing Guidelines
### Test Structure
```python
class TestFeatureName:
"""Test suite for FeatureName"""
@pytest.fixture
def setup_fixture(self):
"""Setup code for tests"""
return SomeObject()
def test_specific_behavior(self, setup_fixture):
"""Should do X when Y happens"""
# Arrange
input_data = "test"
# Act
result = setup_fixture.method(input_data)
# Assert
assert result == expected
```
### Test Naming
Use descriptive names that explain the scenario:
- `test_get_clients_with_valid_ssid_returns_filtered_list`
- `test_get_count_when_api_fails_raises_exception`
- `test_format_result_with_exceeded_limit_includes_guidance`
### Mocking
Use `unittest.mock` for external dependencies:
```python
from unittest.mock import Mock, patch
@patch('dnac_mcp.wireless_client_agent.api.DNACenterAPI')
def test_with_mock(mock_api):
mock_api.return_value.clients.get.return_value = {'response': []}
# Test code
```
## Documentation
### Docstring Format
Use Google-style docstrings:
```python
def function_name(param1: str, param2: int) -> bool:
"""
Brief description of function.
Longer description if needed.
Args:
param1: Description of param1
param2: Description of param2
Returns:
Description of return value
Raises:
ValueError: When param1 is invalid
TypeError: When param2 is not an integer
Example:
>>> function_name("test", 42)
True
"""
```
### README Updates
When adding features, update:
- Features list
- Usage examples
- Configuration options
- API reference
## Release Process
1. Update version in `pyproject.toml` and `src/dnac_mcp/__init__.py`
2. Update CHANGELOG.md
3. Create release branch: `git checkout -b release/v1.x.x`
4. Run full test suite
5. Create tag: `git tag v1.x.x`
6. Push: `git push origin v1.x.x`
## Getting Help
- Open an issue for bugs or feature requests
- Join discussions for questions
- Review existing issues and PRs
## Code of Conduct
- Be respectful and inclusive
- Provide constructive feedback
- Focus on the code, not the person
- Help others learn and grow
## License
By contributing, you agree that your contributions will be licensed under the MIT License.
---
Thank you for contributing to DNAC MCP Server! 🎉