

Also from Kynth Studios


Also from Kynth Studios


Also from Kynth Studios
1# Cline Rules for UniFi MCP Server23## Project Overview4This is a Python-based MCP (Model Context Protocol) server providing access to UniFi Network Controller API.5- **Tech Stack**: Python 3.10+, FastMCP, asyncio, Pydantic, Redis (optional caching), agnost.ai (optional tracking)6- **Architecture**: Async-first MCP server with 40+ tools, 4 resource endpoints, webhook support7- **Purpose**: Enable AI agents to interact with UniFi network infrastructure via standardized MCP8- **API Status**: Uses UniFi Early Access API (read-only operations currently; write operations available in v1 Stable)910## Core Development Principles1112### 1. Documentation and Code Quality13- **ALWAYS** update docstrings when modifying functions14- Follow Google-style docstrings for all public APIs15- Maintain comprehensive inline comments for complex logic16- Update relevant documentation files (README.md, API.md, CONTRIBUTING.md) when adding features17- All code changes MUST pass pre-commit hooks before committing1819### 2. Async Programming Standards20- Use `async/await` for ALL I/O-bound operations (API calls, database queries, file operations)21- **NEVER** use blocking synchronous calls in async contexts22- Leverage `asyncio.gather()` for concurrent operations23- Implement proper exception handling with `try/except` blocks in async functions24- Use `async with` for context managers (HTTP clients, database connections)2526### 3. Type Safety and Validation27- **ALWAYS** use type hints for function parameters and return values28- Leverage Pydantic models for complex data structures and validation29- Use `Optional[T]` or `T | None` for nullable parameters30- Implement input validation at tool boundaries using Pydantic31- Run `mypy` type checking before submitting PRs3233### 4. Testing Requirements34- Target: 80%+ code coverage for all new features35- Write unit tests for individual functions using pytest36- Create integration tests for API interactions (mark with `@pytest.mark.integration`)37- Mock external dependencies (UniFi API, Redis) in unit tests38- Use fixtures for common test setup and teardown39- **NEVER** commit code that breaks existing tests4041### 5. Security and Safety42- **CRITICAL**: All mutating operations MUST require `confirm=True` parameter43- Implement dry-run mode (`dry_run=True`) for preview-before-apply functionality44- Log all operations to `audit.log` with timestamps and user context45- Mask sensitive data (passwords, API keys) in logs using utility functions46- Validate all user inputs at API boundaries47- Follow principle of least privilege for API access4849## Project Structure Conventions5051### Directory Organization52```53src/54├── main.py # MCP server entry point (registers 40+ tools)55├── config/ # Configuration management (Pydantic Settings)56├── api/ # UniFi API client (rate limiting, retries, auth)57├── models/ # Pydantic models for data structures58│ ├── device.py # Device models59│ ├── client.py # Client models60│ ├── network.py # Network models61│ ├── site.py # Site models62│ └── ... # Additional domain models63├── tools/ # MCP tool implementations (40+ tools)64│ ├── clients.py # Client query tools65│ ├── devices.py # Device management66│ ├── networks.py # Network configuration67│ ├── firewall.py # Firewall rules68│ ├── wifi.py # WiFi/SSID management69│ ├── dpi.py # DPI statistics70│ └── ... # Additional tool modules71├── resources/ # MCP resource endpoints (4 resources)72├── webhooks/ # Webhook handlers (HMAC verification)73├── utils/ # Utility functions, validators, exceptions74│ ├── exceptions.py # Custom exception classes75│ ├── validators.py # Input validation helpers76│ ├── audit.py # Audit logging77│ └── logger.py # Logging utilities78└── cache.py # Redis caching implementation79tests/80├── unit/ # Unit tests (fast, no external deps)81└── integration/ # Integration tests (require UniFi controller)82```8384### File Naming85- Use snake_case for Python files: `device_control.py`, `network_config.py`86- Group related tools in single files (e.g., all device tools in `devices.py`)87- Test files mirror source structure: `tests/unit/tools/test_devices.py`8889## Coding Standards9091### Function Design92- Keep functions focused on single responsibility93- Prefer small, composable functions over large monoliths94- Use descriptive function names: `create_firewall_rule()` not `create()`95- Limit function parameters (max 5-7); use Pydantic models for complex inputs96- Return explicit types; avoid returning `Any` or untyped dicts9798### Error Handling99```python100from src.utils.exceptions import (101 APIError,102 AuthenticationError,103 RateLimitError,104 ResourceNotFoundError,105 ValidationError,106 NetworkError,107 ConfirmationRequiredError108)109110# GOOD: Specific exception handling with context111try:112 result = await unifi_api.get_devices(site_id)113except RateLimitError as e:114 logger.warning(f"Rate limit exceeded, retry after {e.retry_after}s")115 raise # Re-raise for MCP protocol handling116except AuthenticationError as e:117 logger.error(f"Authentication failed: {e}")118 raise # Invalid API credentials119except ResourceNotFoundError as e:120 logger.error(f"Site not found: {e.resource_id}")121 raise # Resource doesn't exist122except NetworkError as e:123 logger.error(f"Network error: {e}")124 raise APIError(f"Failed to connect to UniFi controller: {str(e)}")125except APIError as e:126 logger.error(f"API error {e.status_code}: {e.message}")127 raise # Re-raise API errors128```129130### MCP Tool Implementation Pattern131```python132from typing import Optional133from fastmcp import FastMCP134from src.utils.exceptions import (135 ConfirmationRequiredError,136 ValidationError,137 APIError138)139from src.utils import get_logger140from src.cache import cache141142mcp = FastMCP("UniFi MCP Server")143logger = get_logger(__name__)144145@mcp.tool()146async def example_tool(147 site_id: str,148 param1: str,149 optional_param: Optional[int] = None,150 confirm: bool = False,151 dry_run: bool = False152) -> dict:153 """154 Brief description of what the tool does.155156 Args:157 site_id: UniFi site identifier158 param1: Description of param1159 optional_param: Description of optional parameter160 confirm: Required True for mutating operations161 dry_run: Preview changes without applying162163 Returns:164 Dictionary containing operation results165166 Raises:167 ConfirmationRequiredError: If mutating operation lacks confirm=True168 ValidationError: If input validation fails169 APIError: If UniFi API request fails170 """171 # Validate inputs172 if not site_id or not param1:173 raise ValidationError("site_id and param1 are required")174175 # Check confirmation for mutating operations176 if not confirm and not dry_run:177 raise ConfirmationRequiredError("example_tool")178179 # Log operation (audit logging for mutating operations)180 logger.info(f"Executing example_tool: site={site_id}, dry_run={dry_run}")181182 # Implement logic183 if dry_run:184 preview = await generate_preview(site_id, param1)185 return {"status": "preview", "changes": preview}186187 try:188 result = await perform_operation(site_id, param1)189 except APIError as e:190 logger.error(f"API operation failed: {e}")191 raise192193 # Invalidate cache if needed194 await cache.invalidate(f"cache_key:{site_id}")195196 return {"status": "success", "data": result}197```198199### Pydantic Model Usage200```python201from pydantic import BaseModel, Field, validator202from src.models.device import Device203204class DeviceResponse(BaseModel):205 """Response model for device operations."""206 device: Device207 site_id: str = Field(..., description="Site identifier")208 updated_at: str = Field(..., description="ISO timestamp")209210 @validator('site_id')211 def validate_site_id(cls, v):212 if not v or len(v) < 1:213 raise ValueError('site_id cannot be empty')214 return v215```216217### Caching Strategy218- Use Redis for caching when `REDIS_HOST` is configured219- Set appropriate TTLs per resource type:220 - Sites: 300s (5 minutes)221 - Devices: 60s (1 minute)222 - Clients: 30s (30 seconds - frequent changes)223 - Networks: 300s (5 minutes)224 - WLANs: 300s (5 minutes)225 - Firewall rules: 300s (5 minutes)226 - DPI stats: 120s (2 minutes)227 - Alerts/Events: 30s (30 seconds - time-sensitive)228- Invalidate cache on mutating operations229- Handle cache misses gracefully (fallback to API)230- Graceful degradation: server works without Redis (caching disabled)231232## Git Workflow233234### Commit Messages235Follow Conventional Commits:236```237feat: add WiFi statistics tool238fix: correct device restart timeout handling239docs: update API.md with new firewall endpoints240test: add integration tests for port forwarding241refactor: simplify client management code242```243244### Branch Naming245- Feature branches: `feature/wifi-statistics`246- Bug fixes: `fix/device-restart-timeout`247- Documentation: `docs/api-reference-update`248249### Pull Request Requirements2501. All tests pass (`pytest`)2512. Code coverage meets 80% threshold2523. Type checking passes (`mypy src/`)2534. Pre-commit hooks pass (black, isort, ruff, bandit)2545. Documentation updated (if applicable)2556. CHANGELOG.md updated for user-facing changes256257## Development Tools258259### Required Commands260```bash261# Install dependencies262uv pip install -e ".[dev]"263264# Run tests265pytest # All tests266pytest -m unit # Unit tests only267pytest -m integration # Integration tests only268pytest --cov=src --cov-report=html --cov-report=term-missing # With coverage269270# Code quality271black src/ tests/ # Format code272isort src/ tests/ # Sort imports273ruff check src/ tests/ --fix # Lint and fix274mypy src/ # Type check275bandit -r src/ # Security scan276277# Development server278uv run mcp dev src/main.py # With MCP Inspector (http://localhost:5173)279280# Production server281uv run python src/main.py # Standard MCP server282```283284### Testing with MCP Inspector285```bash286# Start development server287uv run mcp dev src/main.py288289# MCP Inspector opens at http://localhost:5173290# Use to interactively test tools and resources291```292293### Pre-commit Hooks294```bash295# Install all hooks (including commit-msg for conventional commits)296pre-commit install297pre-commit install --hook-type commit-msg298299# Manual run (run before committing)300pre-commit run --all-files301302# Run specific hook303pre-commit run black --all-files304pre-commit run ruff --all-files305pre-commit run mypy --all-files306```307308**Hooks include:**309- `black` - Code formatting310- `isort` - Import sorting311- `ruff` - Linting and auto-fixing312- `mypy` - Type checking313- `bandit` - Security scanning314- `detect-secrets` - Secret detection315- `commitlint` - Conventional commit validation316317## Common Patterns318319### API Client Usage320```python321from src.api.client import UniFiClient322from src.config import Settings323324# Load settings from environment325settings = Settings()326327# Create client (handles rate limiting, retries, authentication)328async with UniFiClient(329 api_key=settings.unifi_api_key,330 host=settings.unifi_host,331 api_type=settings.unifi_api_type,332 site=settings.unifi_site333) as client:334 devices = await client.get_devices(site_id="default")335 # Client handles:336 # - Rate limiting (respects UNIFI_RATE_LIMIT)337 # - Automatic retries (UNIFI_MAX_RETRIES)338 # - Authentication (X-API-Key header)339 # - Error handling (APIError exceptions)340 # - Timeout management (UNIFI_TIMEOUT)341```342343### Resource Definition344```python345import json346from fastmcp import FastMCP347348mcp = FastMCP("UniFi MCP Server")349350@mcp.resource("sites://{site_id}/devices")351async def list_devices_resource(site_id: str) -> str:352 """Expose devices as MCP resource.353354 Args:355 site_id: UniFi site identifier356357 Returns:358 JSON string of devices list359 """360 # Check cache first361 cached = await cache.get(f"devices:{site_id}")362 if cached:363 return cached364365 devices = await get_devices(site_id)366 result = json.dumps(devices, indent=2)367368 # Cache result369 await cache.set(f"devices:{site_id}", result, ttl=60)370371 return result372```373374### Webhook Handler375```python376import hmac377import hashlib378from src.webhooks.receiver import WebhookReceiver379from src.webhooks.handlers import handle_device_event380381# Webhook receiver with HMAC verification382receiver = WebhookReceiver(383 secret=settings.webhook_secret,384 logger=logger385)386387@receiver.handler("device.connected")388async def on_device_connected(event: dict):389 """Handle device connection events.390391 Args:392 event: Webhook event payload (validated HMAC)393 """394 logger.info(f"Device connected: {event.get('device_mac')}")395396 # Invalidate relevant caches397 site_id = event.get('site_id', 'default')398 await cache.invalidate(f"devices:{site_id}")399 await cache.invalidate(f"clients:{site_id}")400401 # Process event402 await handle_device_event(event)403```404405### Webhook Security406- **ALWAYS** verify HMAC signatures when `WEBHOOK_SECRET` is set407- Use `hmac.compare_digest()` for constant-time comparison408- Log all webhook events for audit purposes409- Handle webhook failures gracefully (don't crash server)410411## AI Agent Guidance412413### When Adding New Features4141. Review existing patterns in similar tools (e.g., check `devices.py` when adding device features)4152. Check `src/models/` for existing Pydantic models - reuse or extend them4163. Follow the MCP tool implementation pattern above4174. Add comprehensive tests (both unit and integration) targeting 80%+ coverage4185. Update API.md with new tool documentation4196. Add examples to docstrings4207. **Important**: If adding write operations, remember Early Access API is read-only; document limitation421422### When Fixing Bugs4231. Write a failing test that reproduces the bug4242. Fix the bug using minimal changes4253. Verify all tests pass4264. Add regression test if needed4275. Check exception types match expected behavior (use custom exceptions from `src.utils.exceptions`)428429### When Refactoring4301. Ensure tests provide adequate coverage before refactoring4312. Refactor in small, atomic commits4323. Run tests frequently during refactoring4334. Update documentation if interfaces change4345. Ensure Pydantic models remain backward compatible or version them435436### API Read-Only Limitation437**IMPORTANT**: The UniFi Early Access API is currently read-only for write operations. When implementing mutating tools:438- Document that write operations may not be available until v1 Stable439- Implement preview mode (`dry_run=True`) to show what would change440- Return clear error messages if API returns 403 Forbidden441- Monitor UniFi API release notes for v1 Stable availability442443## Critical Reminders444445### Security & Safety446- **NEVER** commit API keys, passwords, or sensitive data447- **ALWAYS** use `confirm=True` for operations that modify network state448- **NEVER** expose sensitive data in logs (use masking utilities from `src.utils`)449- **ALWAYS** verify webhook HMAC signatures when `WEBHOOK_SECRET` is set450- **NEVER** hardcode credentials - always use environment variables451452### Code Quality453- **ALWAYS** write tests for new features (target 80%+ coverage)454- **NEVER** merge code that fails type checking (`mypy`) or linting (`ruff`)455- **ALWAYS** update documentation (API.md, docstrings) alongside code changes456- **NEVER** use synchronous blocking calls in async contexts457- **ALWAYS** validate inputs using Pydantic models (from `src.models/`)458- **ALWAYS** use custom exception types from `src.utils.exceptions`459460### Development Workflow461- **ALWAYS** run pre-commit hooks before committing462- **ALWAYS** run full test suite before pushing463- **ALWAYS** use conventional commit messages464- **NEVER** bypass CI/CD checks465- **ALWAYS** request human review for security-related changes466467### API Considerations468- **REMEMBER** Early Access API is read-only for write operations469- **ALWAYS** implement `dry_run=True` for mutating operations470- **ALWAYS** respect rate limits (100 req/min for Early Access)471- **ALWAYS** handle rate limit errors gracefully (429 with Retry-After)472473## Environment Variables474475Reference `.env.example` for all configuration options:476477### Required Variables478```479UNIFI_API_KEY=<required> # UniFi API key from unifi.ui.com480```481482### UniFi API Configuration483```484UNIFI_API_TYPE=cloud # or "local" for gateway proxy485UNIFI_HOST=api.ui.com # or local gateway IP (e.g., 192.168.2.1)486UNIFI_PORT=443 # API port487UNIFI_VERIFY_SSL=true # SSL verification (false for self-signed)488UNIFI_SITE=default # Default site identifier489UNIFI_RATE_LIMIT=100 # Max requests per minute (Early Access: 100)490UNIFI_TIMEOUT=30 # Request timeout in seconds491UNIFI_MAX_RETRIES=3 # Maximum retry attempts492```493494### Redis Caching (Optional)495```496REDIS_HOST=localhost # Redis host (optional)497REDIS_PORT=6379 # Redis port498REDIS_DB=0 # Redis database number499REDIS_PASSWORD= # Redis password (if required)500```501502### Webhook Support (Optional)503```504WEBHOOK_SECRET=<secret> # HMAC secret for webhook verification505```506507### Performance Tracking with agnost.ai (Optional)508```509AGNOST_ENABLED=false # Enable agnost.ai tracking510AGNOST_ORG_ID= # Organization ID from app.agnost.ai511AGNOST_ENDPOINT=https://api.agnost.ai # Agnost API endpoint512AGNOST_DISABLE_INPUT=false # Disable input parameter tracking513AGNOST_DISABLE_OUTPUT=false # Disable output/result tracking514```515516### Logging517```518LOG_LEVEL=INFO # Logging level (DEBUG, INFO, WARNING, ERROR)519```520521## Additional Resources522523### Project Documentation524- **README.md** - Project overview, installation, usage525- **API.md** - Complete API documentation (40+ tools, 4 resources)526- **CONTRIBUTING.md** - Contribution guidelines527- **SECURITY.md** - Security policy and best practices528- **AGENTS.md** - AI agent-specific guidelines529- **MCP_TOOLBOX.md** - MCP Toolbox analytics dashboard guide530531### External Resources532- **UniFi API**: [Official UniFi API Documentation](https://developer.ui.com/site-manager-api/gettingstarted)533- **MCP Specification**: https://modelcontextprotocol.io/534- **FastMCP Documentation**: https://gofastmcp.com/535- **Pydantic Documentation**: https://docs.pydantic.dev/536- **agnost.ai**: https://docs.agnost.ai (performance tracking)537538### Docker Deployment539```bash540# Using Docker Compose (recommended - includes Redis + MCP Toolbox)541docker-compose up -d542543# Standalone Docker container544docker run -i \545 -e UNIFI_API_KEY=your-key \546 -e UNIFI_API_TYPE=cloud \547 ghcr.io/enuno/unifi-mcp-server:latest548549# Note: Don't use -d flag for MCP client integration (needs stdin/stdout)550```551552### Tool Count553- **40+ MCP Tools** organized across:554 - Device management (query, control, statistics)555 - Network configuration (create, update, delete)556 - Client management (query, block, reconnect)557 - Firewall rules (CRUD operations)558 - WiFi/SSID management (create, update, statistics)559 - Port forwarding (create, delete, list)560 - DPI statistics (site-wide, top apps, per-client)561 - ACLs, Firewall Zones, WANs, Vouchers, Applications562- **4 MCP Resources**: sites, devices, clients, networks563564---565Generated for Cline AI coding assistant566Version: 0.1.3 (Jan 2025)567
One repository carrying more than one format is the comparison this product exists for: does anyone actually write different content in each file, or is one a copy of the other?
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| enuno/unifi-mcp-server.claude/skills/fastmcp/CLAUDE.md · 226 | CLAUDE.md | no sections | 25/100 | today | |
| enuno/unifi-mcp-server.cursor/rules/common-mistakes.mdc · 226 | Cursor rules | testlint-formatgitdo-not | 93/100 | today | |
| enuno/unifi-mcp-server.cursor/rules/core-principles.mdc · 226 | Cursor rules | teststyletypestesting-strategy+3 | 59/100 | today | |
| enuno/unifi-mcp-server.cursor/rules/environment-setup.mdc · 226 | Cursor rules | setupgitsecurityapi+2 | 57/100 | today | |
| enuno/unifi-mcp-server.cursor/rules/mcp-tools.mdc · 226 | Cursor rules | no sections | 45/100 | today | |
| enuno/unifi-mcp-server.cursor/rules/project-context.mdc · 226 | Cursor rules | testlint-formatstylearch+2 | 73/100 | today | |
| enuno/unifi-mcp-server.cursor/rules/unifi-api.mdc · 226 | Cursor rules | setupsecurityapiui+1 | 62/100 | today | |
| enuno/unifi-mcp-server.cursor/rules/workflow.mdc · 226 | Cursor rules | testlint-formatgitagent-behaviour | 86/100 | today | |
| enuno/unifi-mcp-server.cursorrules · 226 | .cursorrules | setuptestlint-formatstyle+7 | 93/100 | today | |
| enuno/unifi-mcp-serverAGENTS.md · 226 | AGENTS.md | setuptestlint-formatstyle+10 | 84/100 | today | |
| enuno/unifi-mcp-serverCLAUDE.md · 226 | CLAUDE.md | testlint-formatarchtesting-strategy+4 | 90/100 | today | |
| enuno/unifi-mcp-serverGEMINI.md · 226 | GEMINI.md | setuptestlint-formatstyle+8 | 89/100 | today |
Same format, overlapping stack, ranked by quality.
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| JCodesMore/ai-website-cloner-template.clinerules · 32k | Cline rules | buildlint-formatstylearch+3 | 97/100 | 7 days ago | |
| BryaanF/LiantPortfolio.clinerules/project-guidelines.md · 0 | Cline rules | buildstylearchgit+2 | 96/100 | 14 days ago | |
| u9401066/pubmed-search-mcp.clinerules/50-pubmed-project.md · 25 | Cline rules | testlint-formatstylearch+1 | 94/100 | 14 days ago | |
| u9401066/zotero-keeper.clinerules/50-pubmed-project.md · 6 | Cline rules | testlint-formatstylearch+1 | 94/100 | 14 days ago | |
| u9401066/zotero-keepervscode-extension/resources/repo-assets/pubmed-search-mcp/.clinerules/50-pubmed-project.md · 6 | Cline rules | testlint-formatstylearch+1 | 94/100 | 14 days ago | |
| ryok/python-boilerplate.clinerules/common-commands.md · 0 | Cline rules | setupbuildtestlint-format+3 | 90/100 | 13 days ago | |
| gasbasd/mtg-utils.clinerules/01-project-overview.md · 0 | Cline rules | setuptestlint-formatarch | 89/100 | 8 days ago | |
| u9401066/pubmed-search-mcp.clinerules/00-project.md · 25 | Cline rules | testlint-formatstylearch+1 | 86/100 | 14 days ago |
A badge carrying the measured quality of the strongest agent config file in this repository, out of 100. It reads from this index every time somebody loads your page, so it changes when the measurement changes and there is nothing to keep up to date. Free, no account, and the value is not something you or we can set by hand.
[](https://rulestack.kynth.studio/configs/enuno-unifi-mcp-server-clinerules)Would rather not hotlink us? Every badge is also served in shields.io’s endpoint schema, so shields renders the image and your readers never talk to our domain:
Published by Toolproof, the masthead over this index and eight others. The method behind the number is at toolproof.kynth.studio/methodology, and the whole thing is readable as JSON with no key at /api.