suprema-biostar-mcp 1.0.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.
- biostar_x_mcp_server/__init__.py +25 -0
- biostar_x_mcp_server/__main__.py +15 -0
- biostar_x_mcp_server/config.py +87 -0
- biostar_x_mcp_server/handlers/__init__.py +35 -0
- biostar_x_mcp_server/handlers/access_handler.py +2162 -0
- biostar_x_mcp_server/handlers/audit_handler.py +489 -0
- biostar_x_mcp_server/handlers/auth_handler.py +216 -0
- biostar_x_mcp_server/handlers/base_handler.py +228 -0
- biostar_x_mcp_server/handlers/card_handler.py +746 -0
- biostar_x_mcp_server/handlers/device_handler.py +4344 -0
- biostar_x_mcp_server/handlers/door_handler.py +3969 -0
- biostar_x_mcp_server/handlers/event_handler.py +1331 -0
- biostar_x_mcp_server/handlers/file_handler.py +212 -0
- biostar_x_mcp_server/handlers/help_web_handler.py +379 -0
- biostar_x_mcp_server/handlers/log_handler.py +1051 -0
- biostar_x_mcp_server/handlers/navigation_handler.py +109 -0
- biostar_x_mcp_server/handlers/occupancy_handler.py +541 -0
- biostar_x_mcp_server/handlers/user_handler.py +3568 -0
- biostar_x_mcp_server/schemas/__init__.py +21 -0
- biostar_x_mcp_server/schemas/access.py +158 -0
- biostar_x_mcp_server/schemas/audit.py +73 -0
- biostar_x_mcp_server/schemas/auth.py +24 -0
- biostar_x_mcp_server/schemas/cards.py +128 -0
- biostar_x_mcp_server/schemas/devices.py +496 -0
- biostar_x_mcp_server/schemas/doors.py +306 -0
- biostar_x_mcp_server/schemas/events.py +104 -0
- biostar_x_mcp_server/schemas/files.py +7 -0
- biostar_x_mcp_server/schemas/help.py +29 -0
- biostar_x_mcp_server/schemas/logs.py +33 -0
- biostar_x_mcp_server/schemas/occupancy.py +19 -0
- biostar_x_mcp_server/schemas/tool_response.py +29 -0
- biostar_x_mcp_server/schemas/users.py +166 -0
- biostar_x_mcp_server/server.py +335 -0
- biostar_x_mcp_server/session.py +221 -0
- biostar_x_mcp_server/tool_manager.py +172 -0
- biostar_x_mcp_server/tools/__init__.py +45 -0
- biostar_x_mcp_server/tools/access.py +510 -0
- biostar_x_mcp_server/tools/audit.py +227 -0
- biostar_x_mcp_server/tools/auth.py +59 -0
- biostar_x_mcp_server/tools/cards.py +269 -0
- biostar_x_mcp_server/tools/categories.py +197 -0
- biostar_x_mcp_server/tools/devices.py +1552 -0
- biostar_x_mcp_server/tools/doors.py +865 -0
- biostar_x_mcp_server/tools/events.py +305 -0
- biostar_x_mcp_server/tools/files.py +28 -0
- biostar_x_mcp_server/tools/help.py +80 -0
- biostar_x_mcp_server/tools/logs.py +123 -0
- biostar_x_mcp_server/tools/navigation.py +89 -0
- biostar_x_mcp_server/tools/occupancy.py +91 -0
- biostar_x_mcp_server/tools/users.py +1113 -0
- biostar_x_mcp_server/utils/__init__.py +31 -0
- biostar_x_mcp_server/utils/category_mapper.py +206 -0
- biostar_x_mcp_server/utils/decorators.py +101 -0
- biostar_x_mcp_server/utils/language_detector.py +51 -0
- biostar_x_mcp_server/utils/search.py +42 -0
- biostar_x_mcp_server/utils/timezone.py +122 -0
- suprema_biostar_mcp-1.0.1.dist-info/METADATA +163 -0
- suprema_biostar_mcp-1.0.1.dist-info/RECORD +61 -0
- suprema_biostar_mcp-1.0.1.dist-info/WHEEL +4 -0
- suprema_biostar_mcp-1.0.1.dist-info/entry_points.txt +2 -0
- suprema_biostar_mcp-1.0.1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from typing import Sequence, Dict, Any
|
|
3
|
+
from mcp.types import TextContent
|
|
4
|
+
import httpx
|
|
5
|
+
from .base_handler import BaseHandler
|
|
6
|
+
from ..utils import get_timezone_offset, get_timezone_string
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class AuthHandler(BaseHandler):
|
|
12
|
+
"""Handle authentication-related operations."""
|
|
13
|
+
|
|
14
|
+
async def login(self, args: Dict[str, Any]) -> Sequence[TextContent]:
|
|
15
|
+
"""Login to BioStar 2."""
|
|
16
|
+
try:
|
|
17
|
+
# Get credentials from args or config
|
|
18
|
+
username = args.get("username") or self.session.config.biostar_username
|
|
19
|
+
password = args.get("password") or self.session.config.biostar_password
|
|
20
|
+
|
|
21
|
+
if not username or not password:
|
|
22
|
+
return self.error_response(
|
|
23
|
+
"Missing credentials",
|
|
24
|
+
{"message": "Username and password are required. Set them in .env or provide as arguments."}
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
# Make direct login API call
|
|
28
|
+
logger.info(f"Attempting to login to BioStar at: {self.session.config.biostar_url}")
|
|
29
|
+
|
|
30
|
+
# Create client with SSL settings
|
|
31
|
+
transport = None
|
|
32
|
+
if not self.session.config.verify_ssl:
|
|
33
|
+
import ssl
|
|
34
|
+
ssl_context = ssl.create_default_context()
|
|
35
|
+
ssl_context.check_hostname = False
|
|
36
|
+
ssl_context.verify_mode = ssl.CERT_NONE
|
|
37
|
+
transport = httpx.AsyncHTTPTransport(verify=ssl_context)
|
|
38
|
+
|
|
39
|
+
api_base = self.session.config.biostar_url
|
|
40
|
+
# api_base = "https://127.0.0.1:443"
|
|
41
|
+
async with httpx.AsyncClient(
|
|
42
|
+
transport=transport,
|
|
43
|
+
verify=False,
|
|
44
|
+
follow_redirects=True
|
|
45
|
+
) as client:
|
|
46
|
+
logger.info(f"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx Login with: {username} and {password} xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")
|
|
47
|
+
response = await client.post(
|
|
48
|
+
f"{api_base}/api/login",
|
|
49
|
+
json={
|
|
50
|
+
"User": {
|
|
51
|
+
"login_id": username,
|
|
52
|
+
"password": password
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
headers={
|
|
56
|
+
"Content-Type": "application/json"
|
|
57
|
+
},
|
|
58
|
+
timeout=self.session.config.api_timeout
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
if response.status_code != 200:
|
|
62
|
+
return self.error_response(
|
|
63
|
+
f"Login failed: {response.status_code}",
|
|
64
|
+
{"response": response.text}
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
# Extract session ID and user info
|
|
68
|
+
session_id = response.headers.get("bs-session-id")
|
|
69
|
+
data = response.json()
|
|
70
|
+
user_info = data.get("User", {})
|
|
71
|
+
|
|
72
|
+
# Store session info
|
|
73
|
+
self.session.session_id = session_id
|
|
74
|
+
self.session.user_id = user_info.get("user_id")
|
|
75
|
+
|
|
76
|
+
# Update the session client to be authenticated
|
|
77
|
+
if not self.session.client:
|
|
78
|
+
await self.session.connect()
|
|
79
|
+
if session_id:
|
|
80
|
+
self.session.client.headers["bs-session-id"] = session_id
|
|
81
|
+
|
|
82
|
+
logger.info(f"Successfully logged in as user {self.session.user_id}")
|
|
83
|
+
|
|
84
|
+
return self.success_response({
|
|
85
|
+
"user_id": user_info.get("user_id"),
|
|
86
|
+
"name": user_info.get("name"),
|
|
87
|
+
"email": user_info.get("email"),
|
|
88
|
+
"session_id": session_id,
|
|
89
|
+
"message": "Successfully logged in to BioStar 2"
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
except httpx.ConnectTimeout as e:
|
|
93
|
+
return self.error_response(
|
|
94
|
+
"Connection timeout",
|
|
95
|
+
{
|
|
96
|
+
"message": f"Failed to connect to BioStar server at {self.session.config.biostar_url}",
|
|
97
|
+
"timeout": f"{self.session.config.api_timeout} seconds",
|
|
98
|
+
"suggestions": [
|
|
99
|
+
"1. Check if the BioStar server URL is correct in your .env file",
|
|
100
|
+
"2. Ensure the server is running and accessible",
|
|
101
|
+
"3. Check network connectivity to the server",
|
|
102
|
+
"4. Try increasing API_TIMEOUT in .env file",
|
|
103
|
+
"5. If using HTTPS with self-signed certificate, set VERIFY_SSL=false"
|
|
104
|
+
]
|
|
105
|
+
}
|
|
106
|
+
)
|
|
107
|
+
except httpx.ConnectError as e:
|
|
108
|
+
return self.error_response(
|
|
109
|
+
"Connection error",
|
|
110
|
+
{
|
|
111
|
+
"message": f"Cannot connect to {self.session.config.biostar_url}",
|
|
112
|
+
"error": str(e),
|
|
113
|
+
"verify_ssl": self.session.config.verify_ssl
|
|
114
|
+
}
|
|
115
|
+
)
|
|
116
|
+
except Exception as e:
|
|
117
|
+
return await self.handle_api_error(e)
|
|
118
|
+
|
|
119
|
+
async def logout(self, args: Dict[str, Any]) -> Sequence[TextContent]:
|
|
120
|
+
"""Logout from BioStar 2."""
|
|
121
|
+
try:
|
|
122
|
+
if not self.session.is_authenticated():
|
|
123
|
+
return self.error_response("Not authenticated")
|
|
124
|
+
|
|
125
|
+
# Make logout API call
|
|
126
|
+
async with httpx.AsyncClient(verify=self.session.config.verify_ssl) as client:
|
|
127
|
+
response = await client.post(
|
|
128
|
+
f"{self.session.config.biostar_url}/api/logout",
|
|
129
|
+
headers={"bs-session-id": self.session.session_id},
|
|
130
|
+
timeout=self.session.config.api_timeout
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
# Clear session info regardless of response
|
|
134
|
+
self.session.session_id = None
|
|
135
|
+
self.session.user_id = None
|
|
136
|
+
if self.session.client:
|
|
137
|
+
self.session.client.headers.pop("bs-session-id", None)
|
|
138
|
+
|
|
139
|
+
return self.success_response({
|
|
140
|
+
"message": "Successfully logged out from BioStar 2"
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
except Exception as e:
|
|
144
|
+
return await self.handle_api_error(e)
|
|
145
|
+
|
|
146
|
+
async def get_session_info(self, args: Dict[str, Any]) -> Sequence[TextContent]:
|
|
147
|
+
"""Get current session information."""
|
|
148
|
+
try:
|
|
149
|
+
self.check_auth()
|
|
150
|
+
|
|
151
|
+
headers = {"bs-session-id": self.get_session_id()}
|
|
152
|
+
|
|
153
|
+
async with httpx.AsyncClient(verify=False) as client:
|
|
154
|
+
response = await client.get(
|
|
155
|
+
f"{self.session.config.biostar_url}/api/session",
|
|
156
|
+
headers=headers
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
if response.status_code == 200:
|
|
160
|
+
self.session.session_id = None
|
|
161
|
+
return self.success_response({
|
|
162
|
+
"message": "Session information retrieved successfully",
|
|
163
|
+
"session_data": response.json()
|
|
164
|
+
})
|
|
165
|
+
else:
|
|
166
|
+
return self.error_response(f"Failed to get session info: {response.status_code}")
|
|
167
|
+
|
|
168
|
+
except Exception as e:
|
|
169
|
+
return await self.handle_api_error(e)
|
|
170
|
+
|
|
171
|
+
async def get_server_preferences(self, args: Dict[str, Any]) -> Sequence[TextContent]:
|
|
172
|
+
"""Get BioStar server preferences including timezone."""
|
|
173
|
+
try:
|
|
174
|
+
self.check_auth()
|
|
175
|
+
|
|
176
|
+
headers = {
|
|
177
|
+
"bs-session-id": self.get_session_id(),
|
|
178
|
+
"Content-Type": "application/json"
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async with httpx.AsyncClient(verify=False) as client:
|
|
182
|
+
response = await client.get(
|
|
183
|
+
f"{self.session.config.biostar_url}/api/preferences/1",
|
|
184
|
+
headers=headers
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
if response.status_code != 200:
|
|
188
|
+
return self.error_response(f"Failed to get server preferences: {response.status_code} - {response.text}")
|
|
189
|
+
|
|
190
|
+
data = response.json()
|
|
191
|
+
preference = data.get("Preference", {})
|
|
192
|
+
|
|
193
|
+
# Get timezone info
|
|
194
|
+
timezone_id = preference.get("time_zone", "26") # Default to UTC+9 (Seoul)
|
|
195
|
+
timezone_offset_minutes = get_timezone_offset(timezone_id, default=540) # Default to +540 minutes (UTC+9)
|
|
196
|
+
timezone_offset_hours = timezone_offset_minutes / 60
|
|
197
|
+
tz_str = get_timezone_string(timezone_id, default=540)
|
|
198
|
+
|
|
199
|
+
return self.success_response({
|
|
200
|
+
"message": "Server preferences retrieved successfully",
|
|
201
|
+
"language": preference.get("language", "en"),
|
|
202
|
+
"timezone_id": timezone_id,
|
|
203
|
+
"timezone_offset_minutes": timezone_offset_minutes,
|
|
204
|
+
"timezone_offset_hours": timezone_offset_hours,
|
|
205
|
+
"timezone_string": tz_str,
|
|
206
|
+
"date_format": preference.get("date_format", "yyyy/MM/dd"),
|
|
207
|
+
"time_format": preference.get("time_format", "HH:mm"),
|
|
208
|
+
"user": {
|
|
209
|
+
"user_id": preference.get("user_id", {}).get("user_id"),
|
|
210
|
+
"name": preference.get("user_id", {}).get("name")
|
|
211
|
+
}
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
except Exception as e:
|
|
215
|
+
logger.exception("get_server_preferences failed")
|
|
216
|
+
return await self.handle_api_error(e)
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base Handler for BioStar X MCP Server
|
|
3
|
+
Provides common functionality for all API handlers
|
|
4
|
+
"""
|
|
5
|
+
import logging
|
|
6
|
+
import json
|
|
7
|
+
from typing import Sequence, Dict, Any, Optional
|
|
8
|
+
from mcp.types import TextContent
|
|
9
|
+
from pydantic import ValidationError
|
|
10
|
+
import httpx
|
|
11
|
+
from ..session import BioStarSession
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class BaseHandler:
|
|
17
|
+
"""Base handler class with common functionality for all handlers."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, session: BioStarSession):
|
|
20
|
+
self.session = session
|
|
21
|
+
self.client_session_id = None
|
|
22
|
+
|
|
23
|
+
def get_session_id(self) -> str:
|
|
24
|
+
"""Get the session ID to use for API calls."""
|
|
25
|
+
if self.client_session_id:
|
|
26
|
+
logger.info(f"Using client session ID: {self.client_session_id}")
|
|
27
|
+
return self.client_session_id
|
|
28
|
+
elif self.session.session_id:
|
|
29
|
+
logger.info(f"Using internal session ID: {self.session.session_id}")
|
|
30
|
+
return self.session.session_id
|
|
31
|
+
else:
|
|
32
|
+
raise RuntimeError("No session ID available")
|
|
33
|
+
|
|
34
|
+
def check_auth(self) -> None:
|
|
35
|
+
"""Ensure session is authenticated."""
|
|
36
|
+
if self.client_session_id:
|
|
37
|
+
logger.info("Using client session ID, skipping internal auth check")
|
|
38
|
+
return
|
|
39
|
+
|
|
40
|
+
if not self.session.is_authenticated():
|
|
41
|
+
raise RuntimeError("Not authenticated. Please login first.")
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def api_headers(self) -> Dict[str, str]:
|
|
45
|
+
"""Get standard API headers with session ID."""
|
|
46
|
+
return {
|
|
47
|
+
"bs-session-id": self.get_session_id(),
|
|
48
|
+
"Content-Type": "application/json"
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async def api_get(self, endpoint: str, params: Optional[Dict[str, Any]] = None, **kwargs) -> httpx.Response:
|
|
52
|
+
"""Make GET request to BioStar API."""
|
|
53
|
+
async with httpx.AsyncClient(verify=False) as client:
|
|
54
|
+
response = await client.get(
|
|
55
|
+
f"{self.session.config.biostar_url}{endpoint}",
|
|
56
|
+
headers=self.api_headers,
|
|
57
|
+
params=params,
|
|
58
|
+
**kwargs
|
|
59
|
+
)
|
|
60
|
+
return response
|
|
61
|
+
|
|
62
|
+
async def api_post(self, endpoint: str, json: Optional[Dict[str, Any]] = None, **kwargs) -> httpx.Response:
|
|
63
|
+
"""Make POST request to BioStar API."""
|
|
64
|
+
async with httpx.AsyncClient(verify=False) as client:
|
|
65
|
+
response = await client.post(
|
|
66
|
+
f"{self.session.config.biostar_url}{endpoint}",
|
|
67
|
+
headers=self.api_headers,
|
|
68
|
+
json=json,
|
|
69
|
+
**kwargs
|
|
70
|
+
)
|
|
71
|
+
return response
|
|
72
|
+
|
|
73
|
+
async def api_put(self, endpoint: str, json: Optional[Dict[str, Any]] = None, **kwargs) -> httpx.Response:
|
|
74
|
+
"""Make PUT request to BioStar API."""
|
|
75
|
+
async with httpx.AsyncClient(verify=False) as client:
|
|
76
|
+
response = await client.put(
|
|
77
|
+
f"{self.session.config.biostar_url}{endpoint}",
|
|
78
|
+
headers=self.api_headers,
|
|
79
|
+
json=json,
|
|
80
|
+
**kwargs
|
|
81
|
+
)
|
|
82
|
+
return response
|
|
83
|
+
|
|
84
|
+
async def api_delete(self, endpoint: str, **kwargs) -> httpx.Response:
|
|
85
|
+
"""Make DELETE request to BioStar API."""
|
|
86
|
+
async with httpx.AsyncClient(verify=False) as client:
|
|
87
|
+
response = await client.delete(
|
|
88
|
+
f"{self.session.config.biostar_url}{endpoint}",
|
|
89
|
+
headers=self.api_headers,
|
|
90
|
+
**kwargs
|
|
91
|
+
)
|
|
92
|
+
return response
|
|
93
|
+
|
|
94
|
+
def success_response(self, data: Any, message: Optional[str] = None) -> list[TextContent]:
|
|
95
|
+
"""Create a success response."""
|
|
96
|
+
content = {
|
|
97
|
+
"status": "success",
|
|
98
|
+
"data": data
|
|
99
|
+
}
|
|
100
|
+
if message:
|
|
101
|
+
content["message"] = message
|
|
102
|
+
|
|
103
|
+
return [TextContent(
|
|
104
|
+
type="text",
|
|
105
|
+
text=json.dumps(content, ensure_ascii=False, indent=2)
|
|
106
|
+
)]
|
|
107
|
+
|
|
108
|
+
def error_response(self, error: str, details: Optional[Dict[str, Any]] = None) -> list[TextContent]:
|
|
109
|
+
"""Create an error response."""
|
|
110
|
+
content = {
|
|
111
|
+
"status": "error",
|
|
112
|
+
"error": error
|
|
113
|
+
}
|
|
114
|
+
if details:
|
|
115
|
+
content["details"] = details
|
|
116
|
+
|
|
117
|
+
return [TextContent(
|
|
118
|
+
type="text",
|
|
119
|
+
text=json.dumps(content, ensure_ascii=False, indent=2)
|
|
120
|
+
)]
|
|
121
|
+
|
|
122
|
+
async def handle_api_error(self, error: Exception) -> list[TextContent]:
|
|
123
|
+
"""Handle API errors consistently."""
|
|
124
|
+
logger.error(f"API error: {error}", exc_info=True)
|
|
125
|
+
|
|
126
|
+
error_type = type(error).__name__
|
|
127
|
+
|
|
128
|
+
if hasattr(error, 'response'):
|
|
129
|
+
return self.error_response(
|
|
130
|
+
f"API error: {error.response.status_code}",
|
|
131
|
+
{"message": str(error), "response": error.response.text}
|
|
132
|
+
)
|
|
133
|
+
elif "TimeoutError" in str(error) or "ConnectTimeout" in str(error):
|
|
134
|
+
return self.error_response(
|
|
135
|
+
"Connection timeout",
|
|
136
|
+
{
|
|
137
|
+
"message": "Failed to connect to BioStar server. Please check:",
|
|
138
|
+
"suggestions": [
|
|
139
|
+
"1. BioStar server URL in .env file (BIOSTAR_URL)",
|
|
140
|
+
"2. Server is running and accessible",
|
|
141
|
+
"3. Network connectivity",
|
|
142
|
+
"4. Firewall settings"
|
|
143
|
+
],
|
|
144
|
+
"error_type": error_type,
|
|
145
|
+
"details": str(error)
|
|
146
|
+
}
|
|
147
|
+
)
|
|
148
|
+
elif "ConnectionRefused" in str(error):
|
|
149
|
+
return self.error_response(
|
|
150
|
+
"Connection refused",
|
|
151
|
+
{
|
|
152
|
+
"message": "BioStar server refused the connection",
|
|
153
|
+
"suggestions": [
|
|
154
|
+
"1. Check if BioStar server is running",
|
|
155
|
+
"2. Verify the server URL and port",
|
|
156
|
+
"3. Check server logs"
|
|
157
|
+
],
|
|
158
|
+
"error_type": error_type,
|
|
159
|
+
"details": str(error)
|
|
160
|
+
}
|
|
161
|
+
)
|
|
162
|
+
else:
|
|
163
|
+
return self.error_response(
|
|
164
|
+
f"Error: {error_type}",
|
|
165
|
+
{"message": str(error), "type": error_type}
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
def handle_validation_error(self, error: ValidationError) -> list[TextContent]:
|
|
169
|
+
"""Handle Pydantic validation errors consistently."""
|
|
170
|
+
errors = []
|
|
171
|
+
for err in error.errors():
|
|
172
|
+
field = ".".join(str(loc) for loc in err["loc"])
|
|
173
|
+
errors.append({
|
|
174
|
+
"field": field,
|
|
175
|
+
"message": err["msg"],
|
|
176
|
+
"type": err["type"]
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
return self.error_response(
|
|
180
|
+
"Validation error",
|
|
181
|
+
{"errors": errors}
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
def validate_required_params(self, params: Dict[str, Any], required: list[str]) -> Optional[list[TextContent]]:
|
|
185
|
+
"""Validate required parameters are present."""
|
|
186
|
+
missing = [param for param in required if param not in params]
|
|
187
|
+
if missing:
|
|
188
|
+
return self.error_response(
|
|
189
|
+
"Missing required parameters",
|
|
190
|
+
{"missing": missing}
|
|
191
|
+
)
|
|
192
|
+
return None
|
|
193
|
+
|
|
194
|
+
def format_user_info(self, user_data: Dict[str, Any]) -> Dict[str, Any]:
|
|
195
|
+
"""Format user information for display."""
|
|
196
|
+
return {
|
|
197
|
+
"user_id": user_data.get("user_id"),
|
|
198
|
+
"name": user_data.get("name"),
|
|
199
|
+
"email": user_data.get("email"),
|
|
200
|
+
"phone_number": user_data.get("phone_number"),
|
|
201
|
+
"user_group_id": user_data.get("user_group_id", {}).get("id"),
|
|
202
|
+
"disabled": user_data.get("disabled", False),
|
|
203
|
+
"start_datetime": user_data.get("start_datetime"),
|
|
204
|
+
"expiry_datetime": user_data.get("expiry_datetime")
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
def format_door_info(self, door_data: Dict[str, Any]) -> Dict[str, Any]:
|
|
208
|
+
"""Format door information for display."""
|
|
209
|
+
return {
|
|
210
|
+
"id": door_data.get("id"),
|
|
211
|
+
"name": door_data.get("name"),
|
|
212
|
+
"description": door_data.get("description"),
|
|
213
|
+
"open_duration": door_data.get("open_duration"),
|
|
214
|
+
"open_status": door_data.get("open_status"),
|
|
215
|
+
"lock_status": door_data.get("lock_status"),
|
|
216
|
+
"unlocked": door_data.get("unlocked")
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
def format_device_info(self, device_data: Dict[str, Any]) -> Dict[str, Any]:
|
|
220
|
+
"""Format device information for display."""
|
|
221
|
+
return {
|
|
222
|
+
"id": device_data.get("id"),
|
|
223
|
+
"name": device_data.get("name"),
|
|
224
|
+
"device_type": device_data.get("device_type", {}).get("name"),
|
|
225
|
+
"ip": device_data.get("ip"),
|
|
226
|
+
"status": device_data.get("status", {}).get("online"),
|
|
227
|
+
"enabled": device_data.get("enabled")
|
|
228
|
+
}
|