iflow-mcp_enuno-unifi-mcp-server 0.2.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. iflow_mcp_enuno_unifi_mcp_server-0.2.1.dist-info/METADATA +1282 -0
  2. iflow_mcp_enuno_unifi_mcp_server-0.2.1.dist-info/RECORD +81 -0
  3. iflow_mcp_enuno_unifi_mcp_server-0.2.1.dist-info/WHEEL +4 -0
  4. iflow_mcp_enuno_unifi_mcp_server-0.2.1.dist-info/entry_points.txt +2 -0
  5. iflow_mcp_enuno_unifi_mcp_server-0.2.1.dist-info/licenses/LICENSE +201 -0
  6. src/__init__.py +3 -0
  7. src/__main__.py +6 -0
  8. src/api/__init__.py +5 -0
  9. src/api/client.py +727 -0
  10. src/api/site_manager_client.py +176 -0
  11. src/cache.py +483 -0
  12. src/config/__init__.py +5 -0
  13. src/config/config.py +321 -0
  14. src/main.py +2234 -0
  15. src/models/__init__.py +126 -0
  16. src/models/acl.py +41 -0
  17. src/models/backup.py +272 -0
  18. src/models/client.py +74 -0
  19. src/models/device.py +53 -0
  20. src/models/dpi.py +50 -0
  21. src/models/firewall_policy.py +123 -0
  22. src/models/firewall_zone.py +28 -0
  23. src/models/network.py +62 -0
  24. src/models/qos_profile.py +458 -0
  25. src/models/radius.py +141 -0
  26. src/models/reference_data.py +34 -0
  27. src/models/site.py +59 -0
  28. src/models/site_manager.py +120 -0
  29. src/models/topology.py +138 -0
  30. src/models/traffic_flow.py +137 -0
  31. src/models/traffic_matching_list.py +56 -0
  32. src/models/voucher.py +42 -0
  33. src/models/vpn.py +73 -0
  34. src/models/wan.py +48 -0
  35. src/models/zbf_matrix.py +49 -0
  36. src/resources/__init__.py +8 -0
  37. src/resources/clients.py +111 -0
  38. src/resources/devices.py +102 -0
  39. src/resources/networks.py +93 -0
  40. src/resources/site_manager.py +64 -0
  41. src/resources/sites.py +86 -0
  42. src/tools/__init__.py +25 -0
  43. src/tools/acls.py +328 -0
  44. src/tools/application.py +42 -0
  45. src/tools/backups.py +1173 -0
  46. src/tools/client_management.py +505 -0
  47. src/tools/clients.py +203 -0
  48. src/tools/device_control.py +325 -0
  49. src/tools/devices.py +354 -0
  50. src/tools/dpi.py +241 -0
  51. src/tools/dpi_tools.py +89 -0
  52. src/tools/firewall.py +417 -0
  53. src/tools/firewall_policies.py +430 -0
  54. src/tools/firewall_zones.py +515 -0
  55. src/tools/network_config.py +388 -0
  56. src/tools/networks.py +190 -0
  57. src/tools/port_forwarding.py +263 -0
  58. src/tools/qos.py +1070 -0
  59. src/tools/radius.py +763 -0
  60. src/tools/reference_data.py +107 -0
  61. src/tools/site_manager.py +466 -0
  62. src/tools/site_vpn.py +95 -0
  63. src/tools/sites.py +187 -0
  64. src/tools/topology.py +406 -0
  65. src/tools/traffic_flows.py +1062 -0
  66. src/tools/traffic_matching_lists.py +371 -0
  67. src/tools/vouchers.py +249 -0
  68. src/tools/vpn.py +76 -0
  69. src/tools/wans.py +30 -0
  70. src/tools/wifi.py +498 -0
  71. src/tools/zbf_matrix.py +326 -0
  72. src/utils/__init__.py +88 -0
  73. src/utils/audit.py +213 -0
  74. src/utils/exceptions.py +114 -0
  75. src/utils/helpers.py +159 -0
  76. src/utils/logger.py +105 -0
  77. src/utils/sanitize.py +244 -0
  78. src/utils/validators.py +160 -0
  79. src/webhooks/__init__.py +6 -0
  80. src/webhooks/handlers.py +196 -0
  81. src/webhooks/receiver.py +290 -0
src/config/config.py ADDED
@@ -0,0 +1,321 @@
1
+ """Configuration management for UniFi MCP Server using Pydantic Settings."""
2
+
3
+ from enum import Enum
4
+ from typing import Literal
5
+
6
+ from pydantic import Field, field_validator, model_validator
7
+ from pydantic_settings import BaseSettings, SettingsConfigDict
8
+
9
+
10
+ class APIType(str, Enum):
11
+ """API connection type enumeration."""
12
+
13
+ CLOUD_V1 = "cloud-v1" # Official stable v1 API
14
+ CLOUD_EA = "cloud-ea" # Early Access API
15
+ LOCAL = "local" # Direct gateway access
16
+
17
+ # Legacy alias for backward compatibility (defaults to EA)
18
+ CLOUD = "cloud-ea"
19
+
20
+
21
+ class Settings(BaseSettings):
22
+ """Application settings loaded from environment variables and .env file."""
23
+
24
+ model_config = SettingsConfigDict(
25
+ env_file=".env",
26
+ env_file_encoding="utf-8",
27
+ case_sensitive=False,
28
+ extra="ignore",
29
+ )
30
+
31
+ # API Configuration
32
+ api_key: str = Field(
33
+ ...,
34
+ description="UniFi API key (X-API-Key header)",
35
+ validation_alias="UNIFI_API_KEY",
36
+ )
37
+
38
+ api_type: APIType = Field(
39
+ default=APIType.CLOUD_EA,
40
+ description="API connection type: 'cloud-v1' (stable), 'cloud-ea' (early access), or 'local' (gateway)",
41
+ validation_alias="UNIFI_API_TYPE",
42
+ )
43
+
44
+ # Cloud API Configuration
45
+ cloud_api_url: str = Field(
46
+ default="https://api.ui.com",
47
+ description="UniFi Cloud API base URL",
48
+ validation_alias="UNIFI_CLOUD_API_URL",
49
+ )
50
+
51
+ # Local API Configuration
52
+ local_host: str | None = Field(
53
+ default=None,
54
+ description="Local UniFi controller hostname/IP",
55
+ validation_alias="UNIFI_LOCAL_HOST",
56
+ )
57
+
58
+ local_port: int = Field(
59
+ default=443,
60
+ description="Local UniFi controller port",
61
+ validation_alias="UNIFI_LOCAL_PORT",
62
+ )
63
+
64
+ local_verify_ssl: bool = Field(
65
+ default=True,
66
+ description="Verify SSL certificates for local controller",
67
+ validation_alias="UNIFI_LOCAL_VERIFY_SSL",
68
+ )
69
+
70
+ # Site Configuration
71
+ default_site: str = Field(
72
+ default="default",
73
+ description="Default site ID to use",
74
+ validation_alias="UNIFI_DEFAULT_SITE",
75
+ )
76
+
77
+ # Site Manager API Configuration
78
+ site_manager_enabled: bool = Field(
79
+ default=False,
80
+ description="Enable Site Manager API (multi-site management)",
81
+ validation_alias="UNIFI_SITE_MANAGER_ENABLED",
82
+ )
83
+
84
+ # Rate Limiting Configuration
85
+ rate_limit_requests: int = Field(
86
+ default=100,
87
+ description="Maximum requests per minute (EA tier: 100, v1 tier: 10000)",
88
+ validation_alias="UNIFI_RATE_LIMIT_REQUESTS",
89
+ )
90
+
91
+ rate_limit_period: int = Field(
92
+ default=60,
93
+ description="Rate limit period in seconds",
94
+ validation_alias="UNIFI_RATE_LIMIT_PERIOD",
95
+ )
96
+
97
+ # Retry Configuration
98
+ max_retries: int = Field(
99
+ default=3,
100
+ description="Maximum number of retry attempts for failed requests",
101
+ validation_alias="UNIFI_MAX_RETRIES",
102
+ )
103
+
104
+ retry_backoff_factor: float = Field(
105
+ default=2.0,
106
+ description="Exponential backoff factor for retries",
107
+ validation_alias="UNIFI_RETRY_BACKOFF_FACTOR",
108
+ )
109
+
110
+ # Timeout Configuration
111
+ request_timeout: int = Field(
112
+ default=30,
113
+ description="Request timeout in seconds",
114
+ validation_alias="UNIFI_REQUEST_TIMEOUT",
115
+ )
116
+
117
+ # Caching Configuration
118
+ cache_enabled: bool = Field(
119
+ default=True,
120
+ description="Enable response caching",
121
+ validation_alias="UNIFI_CACHE_ENABLED",
122
+ )
123
+
124
+ cache_ttl: int = Field(
125
+ default=300,
126
+ description="Cache TTL in seconds (default: 5 minutes)",
127
+ validation_alias="UNIFI_CACHE_TTL",
128
+ )
129
+
130
+ # Logging Configuration
131
+ log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = Field(
132
+ default="INFO",
133
+ description="Logging level",
134
+ validation_alias="LOG_LEVEL",
135
+ )
136
+
137
+ log_api_requests: bool = Field(
138
+ default=True,
139
+ description="Log all API requests",
140
+ validation_alias="LOG_API_REQUESTS",
141
+ )
142
+
143
+ # Audit Logging
144
+ audit_log_enabled: bool = Field(
145
+ default=True,
146
+ description="Enable audit logging for mutating operations",
147
+ validation_alias="UNIFI_AUDIT_LOG_ENABLED",
148
+ )
149
+
150
+ @field_validator("api_type", mode="before")
151
+ @classmethod
152
+ def validate_api_type(cls, v: str) -> APIType:
153
+ """Validate and convert API type to enum.
154
+
155
+ Args:
156
+ v: API type string
157
+
158
+ Returns:
159
+ APIType enum value
160
+ """
161
+ if isinstance(v, APIType):
162
+ return v
163
+ return APIType(v.lower())
164
+
165
+ @field_validator("local_port")
166
+ @classmethod
167
+ def validate_port(cls, v: int) -> int:
168
+ """Validate port number is in valid range.
169
+
170
+ Args:
171
+ v: Port number
172
+
173
+ Returns:
174
+ Validated port number
175
+
176
+ Raises:
177
+ ValueError: If port is invalid
178
+ """
179
+ if not 1 <= v <= 65535:
180
+ raise ValueError(f"Port must be between 1 and 65535, got {v}")
181
+ return v
182
+
183
+ @model_validator(mode="after")
184
+ def validate_local_configuration(self) -> "Settings":
185
+ """Validate that local API has required configuration.
186
+
187
+ Returns:
188
+ Validated settings instance
189
+
190
+ Raises:
191
+ ValueError: If local API is selected but host is not provided
192
+ """
193
+ if self.api_type == APIType.LOCAL and not self.local_host:
194
+ raise ValueError("local_host is required when api_type is 'local'")
195
+ return self
196
+
197
+ @property
198
+ def base_url(self) -> str:
199
+ """Get the appropriate base URL based on API type.
200
+
201
+ Returns:
202
+ Base URL for API requests
203
+ """
204
+ if self.api_type in (APIType.CLOUD_V1, APIType.CLOUD_EA):
205
+ return self.cloud_api_url
206
+ else:
207
+ # Always use HTTPS for local gateways (port 443)
208
+ # SSL verification is controlled separately via verify_ssl property
209
+ return f"https://{self.local_host}:{self.local_port}"
210
+
211
+ @property
212
+ def verify_ssl(self) -> bool:
213
+ """Get SSL verification setting based on API type.
214
+
215
+ Returns:
216
+ Whether to verify SSL certificates
217
+ """
218
+ if self.api_type in (APIType.CLOUD_V1, APIType.CLOUD_EA):
219
+ return True
220
+ return self.local_verify_ssl
221
+
222
+ def get_integration_path(self, endpoint: str) -> str:
223
+ """Get the correct integration API endpoint path based on API type.
224
+
225
+ For Cloud V1 API: Returns /v1/{endpoint}
226
+ For Cloud EA API: Returns /integration/v1/{endpoint} (ZBF not supported on Cloud)
227
+ For Local API: Returns /proxy/network/integration/v1/{endpoint}
228
+
229
+ Args:
230
+ endpoint: The endpoint path starting with /sites/... (e.g., "/sites/default/firewall/zones")
231
+
232
+ Returns:
233
+ Complete endpoint path with correct prefix
234
+
235
+ Example:
236
+ >>> settings.get_integration_path("/sites/abc/firewall/zones")
237
+ # Cloud V1: "/v1/sites/abc/firewall/zones"
238
+ # Cloud EA: "/integration/v1/sites/abc/firewall/zones"
239
+ # Local: "/proxy/network/integration/v1/sites/abc/firewall/zones"
240
+ """
241
+ # Remove leading slash if present for consistency
242
+ endpoint = endpoint.lstrip("/")
243
+
244
+ if self.api_type == APIType.CLOUD_V1:
245
+ return f"/v1/{endpoint}"
246
+ elif self.api_type == APIType.CLOUD_EA:
247
+ return f"/integration/v1/{endpoint}"
248
+ else:
249
+ # Local gateways require /proxy/network/ prefix
250
+ return f"/proxy/network/integration/v1/{endpoint}"
251
+
252
+ def get_site_api_path(self, site_id: str, endpoint: str) -> str:
253
+ """Get the correct standard UniFi API endpoint path based on API type.
254
+
255
+ For Cloud V1 API: Returns /v1/{endpoint} (site-less endpoints like /hosts)
256
+ For Cloud EA API: Returns /ea/sites/{site_id}/{endpoint}
257
+ For Local API: Returns /proxy/network/api/s/{site_id}/{endpoint}
258
+
259
+ Args:
260
+ site_id: The site ID (may be unused for Cloud V1 top-level endpoints)
261
+ endpoint: The endpoint path (e.g., "devices", "sta", "rest/networkconf")
262
+
263
+ Returns:
264
+ Complete endpoint path with correct prefix
265
+
266
+ Example:
267
+ >>> settings.get_site_api_path("default", "devices")
268
+ # Cloud V1: "/v1/hosts" (devices are under hosts endpoint)
269
+ # Cloud EA: "/ea/sites/default/devices"
270
+ # Local: "/proxy/network/api/s/default/devices"
271
+ """
272
+ # Remove leading slash if present for consistency
273
+ endpoint = endpoint.lstrip("/")
274
+
275
+ if self.api_type == APIType.CLOUD_V1:
276
+ # V1 API uses top-level endpoints without site_id in path
277
+ # Note: For v1, endpoints like "devices" are accessed via /v1/hosts
278
+ return f"/v1/{endpoint}"
279
+ elif self.api_type == APIType.CLOUD_EA:
280
+ return f"/ea/sites/{site_id}/{endpoint}"
281
+ else:
282
+ # Local gateways use /proxy/network/api/s/ prefix
283
+ return f"/proxy/network/api/s/{site_id}/{endpoint}"
284
+
285
+ def get_v2_api_path(self, site_id: str) -> str:
286
+ """Get the v2 API endpoint path for local gateway access.
287
+
288
+ The v2 API is only available on local gateways and provides access to
289
+ features like firewall policies that are not available via the cloud API.
290
+
291
+ Args:
292
+ site_id: The site identifier
293
+
294
+ Returns:
295
+ Complete endpoint path: /proxy/network/v2/api/site/{site_id}
296
+
297
+ Raises:
298
+ NotImplementedError: If api_type is not LOCAL (v2 API only works locally)
299
+
300
+ Example:
301
+ >>> settings.get_v2_api_path("default")
302
+ # Local: "/proxy/network/v2/api/site/default"
303
+ """
304
+ if self.api_type != APIType.LOCAL:
305
+ raise NotImplementedError(
306
+ "v2 API is only available with local gateway access. "
307
+ "Set UNIFI_API_TYPE=local and configure UNIFI_LOCAL_HOST."
308
+ )
309
+ return f"/proxy/network/v2/api/site/{site_id}"
310
+
311
+ def get_headers(self) -> dict[str, str]:
312
+ """Get HTTP headers for API requests.
313
+
314
+ Returns:
315
+ Dictionary of HTTP headers
316
+ """
317
+ return {
318
+ "X-API-KEY": self.api_key, # UniFi API expects all caps
319
+ "Content-Type": "application/json",
320
+ "Accept": "application/json",
321
+ }