Skip to main content
Glama

Cisco Catalyst Center (DNAC) MCP Server

CONTRIBUTING.md9.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! 🎉

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/RobertBergman/dnac-mcp'

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