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.
Files changed (61) hide show
  1. biostar_x_mcp_server/__init__.py +25 -0
  2. biostar_x_mcp_server/__main__.py +15 -0
  3. biostar_x_mcp_server/config.py +87 -0
  4. biostar_x_mcp_server/handlers/__init__.py +35 -0
  5. biostar_x_mcp_server/handlers/access_handler.py +2162 -0
  6. biostar_x_mcp_server/handlers/audit_handler.py +489 -0
  7. biostar_x_mcp_server/handlers/auth_handler.py +216 -0
  8. biostar_x_mcp_server/handlers/base_handler.py +228 -0
  9. biostar_x_mcp_server/handlers/card_handler.py +746 -0
  10. biostar_x_mcp_server/handlers/device_handler.py +4344 -0
  11. biostar_x_mcp_server/handlers/door_handler.py +3969 -0
  12. biostar_x_mcp_server/handlers/event_handler.py +1331 -0
  13. biostar_x_mcp_server/handlers/file_handler.py +212 -0
  14. biostar_x_mcp_server/handlers/help_web_handler.py +379 -0
  15. biostar_x_mcp_server/handlers/log_handler.py +1051 -0
  16. biostar_x_mcp_server/handlers/navigation_handler.py +109 -0
  17. biostar_x_mcp_server/handlers/occupancy_handler.py +541 -0
  18. biostar_x_mcp_server/handlers/user_handler.py +3568 -0
  19. biostar_x_mcp_server/schemas/__init__.py +21 -0
  20. biostar_x_mcp_server/schemas/access.py +158 -0
  21. biostar_x_mcp_server/schemas/audit.py +73 -0
  22. biostar_x_mcp_server/schemas/auth.py +24 -0
  23. biostar_x_mcp_server/schemas/cards.py +128 -0
  24. biostar_x_mcp_server/schemas/devices.py +496 -0
  25. biostar_x_mcp_server/schemas/doors.py +306 -0
  26. biostar_x_mcp_server/schemas/events.py +104 -0
  27. biostar_x_mcp_server/schemas/files.py +7 -0
  28. biostar_x_mcp_server/schemas/help.py +29 -0
  29. biostar_x_mcp_server/schemas/logs.py +33 -0
  30. biostar_x_mcp_server/schemas/occupancy.py +19 -0
  31. biostar_x_mcp_server/schemas/tool_response.py +29 -0
  32. biostar_x_mcp_server/schemas/users.py +166 -0
  33. biostar_x_mcp_server/server.py +335 -0
  34. biostar_x_mcp_server/session.py +221 -0
  35. biostar_x_mcp_server/tool_manager.py +172 -0
  36. biostar_x_mcp_server/tools/__init__.py +45 -0
  37. biostar_x_mcp_server/tools/access.py +510 -0
  38. biostar_x_mcp_server/tools/audit.py +227 -0
  39. biostar_x_mcp_server/tools/auth.py +59 -0
  40. biostar_x_mcp_server/tools/cards.py +269 -0
  41. biostar_x_mcp_server/tools/categories.py +197 -0
  42. biostar_x_mcp_server/tools/devices.py +1552 -0
  43. biostar_x_mcp_server/tools/doors.py +865 -0
  44. biostar_x_mcp_server/tools/events.py +305 -0
  45. biostar_x_mcp_server/tools/files.py +28 -0
  46. biostar_x_mcp_server/tools/help.py +80 -0
  47. biostar_x_mcp_server/tools/logs.py +123 -0
  48. biostar_x_mcp_server/tools/navigation.py +89 -0
  49. biostar_x_mcp_server/tools/occupancy.py +91 -0
  50. biostar_x_mcp_server/tools/users.py +1113 -0
  51. biostar_x_mcp_server/utils/__init__.py +31 -0
  52. biostar_x_mcp_server/utils/category_mapper.py +206 -0
  53. biostar_x_mcp_server/utils/decorators.py +101 -0
  54. biostar_x_mcp_server/utils/language_detector.py +51 -0
  55. biostar_x_mcp_server/utils/search.py +42 -0
  56. biostar_x_mcp_server/utils/timezone.py +122 -0
  57. suprema_biostar_mcp-1.0.1.dist-info/METADATA +163 -0
  58. suprema_biostar_mcp-1.0.1.dist-info/RECORD +61 -0
  59. suprema_biostar_mcp-1.0.1.dist-info/WHEEL +4 -0
  60. suprema_biostar_mcp-1.0.1.dist-info/entry_points.txt +2 -0
  61. suprema_biostar_mcp-1.0.1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,306 @@
1
+ from typing import Optional, Union, List
2
+ from pydantic import BaseModel, Field, model_validator
3
+
4
+
5
+ # ============================================================================
6
+ # Door Schemas
7
+ # ============================================================================
8
+
9
+ class GetDoorsInput(BaseModel):
10
+ """Input schema for get-doors tool"""
11
+ limit: Optional[int] = Field(None, description="Number of records (0 = all). Default: 0.")
12
+ order_by: Optional[str] = Field(None, description="Sort order, e.g., 'id:true' (asc) or 'id:false' (desc).")
13
+ ids: Optional[List[int]] = Field(None, description="Filter by specific door IDs.")
14
+ name: Optional[str] = Field(None, description="Exact door name match.")
15
+ name_contains: Optional[str] = Field(None, description="Case-insensitive substring match for door name.")
16
+ group_id: Optional[int] = Field(None, description="Filter by door group ID.")
17
+ group_name: Optional[str] = Field(None, description="Case-insensitive substring match for door group name.")
18
+ status: Optional[str] = Field(None, description="Filter by status value (as returned by API), e.g., '0'.")
19
+ include_raw: Optional[bool] = Field(default=False, description="Attach raw API row for each door (debugging). Default: false.")
20
+ minimal: Optional[bool] = Field(default=False, description="Return only id and name (for access level creation). Reduces response size from 13KB to <1KB. Default: false.")
21
+
22
+
23
+ class GetDoorInput(BaseModel):
24
+ """Input schema for get-door tool"""
25
+ door_id: Optional[int] = Field(None, description="ID of the door to retrieve. If provided, the tool skips search and fetches details.")
26
+ search_text: Optional[str] = Field(None, description="Free-text query for door search (POST /api/v2/doors/search).")
27
+ door_group_id: Optional[int] = Field(None, description="Filter by door group ID in search (POST /api/v2/doors/search).")
28
+ limit: Optional[int] = Field(None, description="Max results for search (default: 50).")
29
+
30
+ @model_validator(mode="after")
31
+ def validate_identifier(self):
32
+ """Ensure at least one identifier is provided"""
33
+ if not any([self.door_id, self.search_text, self.door_group_id]):
34
+ raise ValueError("At least one of door_id, search_text, or door_group_id must be provided")
35
+ return self
36
+
37
+
38
+ class CreateDoorInput(BaseModel):
39
+ """Input schema for create-door tool"""
40
+ name: str = Field(..., description="Door name")
41
+ entry_device_id: int = Field(..., description="Device id to control the lock (1:1 mapping enforced).")
42
+ exit_device_id: int = Field(..., description="Exit device id")
43
+ exit_input_index: int = Field(..., description="Exit input index")
44
+ exit_input_type: int = Field(..., description="1=NC, 0=NO")
45
+ sensor_device_id: int = Field(..., description="Sensor device id")
46
+ sensor_input_index: int = Field(..., description="Sensor input index")
47
+ sensor_input_type: int = Field(..., description="1=NC, 0=NO")
48
+ apb_use_door_sensor: int = Field(..., description="APB use door sensor")
49
+ confirm: Optional[bool] = Field(default=False, description="If true, actually create the door. If false, return a preview.")
50
+ default_group_id: Optional[int] = Field(default=1, description="Fallback group id when door_group_id is not provided.")
51
+ description: Optional[str] = Field(None, description="Door description")
52
+ door_group_id: Optional[int] = Field(None, description="Optional group id at creation time. If omitted, default_group_id is used.")
53
+ open_timeout: Optional[int] = Field(default=10, description="Open timeout")
54
+ open_duration: Optional[int] = Field(default=5, description="Open duration")
55
+ open_once: Optional[bool] = Field(default=True, description="Open once")
56
+ unconditional_lock: Optional[bool] = Field(default=True, description="Unconditional lock")
57
+ relay_index: Optional[int] = Field(None, description="Relay index on the entry device. If omitted, the tool returns relay list and exits.")
58
+
59
+
60
+ class UpdateDoorInput(BaseModel):
61
+ """Input schema for update-door tool"""
62
+ door_id: int = Field(..., description="ID of the door to update")
63
+ name: Optional[str] = Field(None, description="New name for the door")
64
+ open_duration: Optional[int] = Field(None, description="Door open duration in seconds")
65
+ open_timeout: Optional[int] = Field(None, description="Door open timeout in seconds")
66
+ dual_auth_required: Optional[bool] = Field(None, description="Whether dual authentication is required")
67
+
68
+
69
+ class DeleteDoorInput(BaseModel):
70
+ """Input schema for delete-door tool"""
71
+ door_id: int = Field(..., description="ID of the door to delete")
72
+
73
+
74
+ class ControlDoorInput(BaseModel):
75
+ """Input schema for control-door tool"""
76
+ action: str = Field(..., description="Action to perform. 'release' clears manual lock/unlock.", pattern="^(open|release|lock|unlock)$")
77
+ door_ids: Optional[List[Union[int, str]]] = Field(None, description="Door IDs to control (validated against GET /api/doors).")
78
+ door_names: Optional[List[str]] = Field(None, description="Door names to control (EXACT, case-sensitive). No autocorrect, no fuzzy.")
79
+
80
+
81
+ class GetDoorsStatusInput(BaseModel):
82
+ """Input schema for get-doors-status tool"""
83
+ monitoring_permission: Optional[bool] = Field(default=True, description="Pass-through flag for the status API. Default: true.")
84
+ include_enrichment: Optional[bool] = Field(default=True, description="If true, joins names/groups from GET /api/doors and enables group/name filters. Default: true.")
85
+ include_device_response: Optional[bool] = Field(default=True, description="Include normalized DeviceResponse with error summary. Default: true.")
86
+ include_raw: Optional[bool] = Field(default=False, description="Attach raw status row for debugging. Default: false.")
87
+ door_ids: Optional[List[int]] = Field(None, description="Filter to specific door IDs.")
88
+ is_open: Optional[bool] = Field(None, description="Filter by open state.")
89
+ is_unlocked: Optional[bool] = Field(None, description="Filter by unlocked state.")
90
+ has_alarm: Optional[bool] = Field(None, description="Filter by alarm state.")
91
+ status_in: Optional[List[str]] = Field(None, description="Filter by raw status code values (as strings).")
92
+ group_id: Optional[int] = Field(None, description="Filter by door group ID (requires include_enrichment=true).")
93
+ group_name: Optional[str] = Field(None, description="Substring match for group name (requires include_enrichment=true).")
94
+ name_contains: Optional[str] = Field(None, description="Substring match for door name (requires include_enrichment=true).")
95
+ sort_by: Optional[str] = Field(default="door_id", description="Client-side sort key. Default: 'door_id'.")
96
+ sort_desc: Optional[bool] = Field(default=False, description="Sort descending when true. Default: false.")
97
+
98
+
99
+ # ============================================================================
100
+ # Door Group Schemas
101
+ # ============================================================================
102
+
103
+ class GetDoorGroupsInput(BaseModel):
104
+ """Input schema for get-door-groups tool"""
105
+ include_hierarchy: Optional[bool] = Field(default=True, description="Enrich with /api/v2/door_groups/only_permission_item/search (depth/parent/children/path). Default: true")
106
+ include_doors_scan: Optional[bool] = Field(default=True, description="Enrich with /api/doors to fill door_ids and door_count when absent. Default: true")
107
+
108
+
109
+ class GetDoorGroupInput(BaseModel):
110
+ """Input schema for get-door-group tool"""
111
+ group_id: Optional[Union[int, str]] = Field(None, description="ID of the door group to retrieve")
112
+ group_name: Optional[str] = Field(None, description="Name of the door group to resolve (supports exact/iexact/icontains via name_match_mode)")
113
+ name_match_mode: Optional[str] = Field(default="auto", description="Name matching strategy when group_name is provided")
114
+ include_hierarchy: Optional[bool] = Field(default=True, description="Include depth/parent/children/path if available")
115
+ include_doors: Optional[bool] = Field(default=True, description="Include door_ids (scan /api/doors if missing)")
116
+ include_doors_detail: Optional[bool] = Field(default=False, description="Also include brief door objects (id, name, description, group)")
117
+ include_raw: Optional[bool] = Field(default=False, description="Attach raw DoorGroup payload")
118
+
119
+ @model_validator(mode="after")
120
+ def validate_identifier(self):
121
+ """Ensure at least one identifier is provided"""
122
+ if not any([self.group_id, self.group_name]):
123
+ raise ValueError("Either group_id or group_name must be provided")
124
+ return self
125
+
126
+
127
+ class CreateDoorGroupInput(BaseModel):
128
+ """Input schema for create-door-group tool"""
129
+ name: str = Field(..., description="Name of the door group (e.g., 'Ground Floor', '1st Floor', '2nd Floor')")
130
+ parent_id: Optional[int] = Field(default=1, description="Parent door group ID (default: 1 for root group)")
131
+ depth: Optional[int] = Field(default=1, ge=1, le=8, description="Depth level of the door group (1-8, default: 1)")
132
+
133
+
134
+ class UpdateDoorGroupInput(BaseModel):
135
+ """Input schema for update-door-group tool"""
136
+ group_id: int = Field(..., description="ID of the door group to update")
137
+ name: Optional[str] = Field(None, description="New name for the door group")
138
+ parent_id: Optional[int] = Field(None, description="New parent door group ID")
139
+
140
+
141
+ class DeleteDoorGroupInput(BaseModel):
142
+ """Input schema for delete-door-group tool"""
143
+ group_id: int = Field(..., description="ID of the door group to delete")
144
+
145
+
146
+ class AddDoorsToGroupInput(BaseModel):
147
+ """Input schema for add-doors-to-group tool"""
148
+ group_id: Optional[int] = Field(None, description="Target door group ID")
149
+ group_name: Optional[str] = Field(None, description="Target door group name (used when group_id is not provided)")
150
+ door_ids: Optional[List[int]] = Field(None, description="List of door IDs to assign")
151
+ door_names: Optional[List[str]] = Field(None, description="List of door names to assign (exact match)")
152
+ force: Optional[bool] = Field(default=False, description="Overwrite an existing single-group assignment if different.")
153
+
154
+
155
+ class RemoveDoorsFromGroupInput(BaseModel):
156
+ """Input schema for remove-doors-from-group tool"""
157
+ door_ids: Optional[List[int]] = Field(None, description="Door IDs")
158
+ door_names: Optional[List[str]] = Field(None, description="Door names")
159
+ group_id: Optional[int] = Field(None, description="Remove from this group id")
160
+ group_name: Optional[str] = Field(None, description="Or remove from this group name")
161
+ fallback_mode: Optional[str] = Field(default="auto", description="What to do if removal would leave no group")
162
+ fallback_group_id: Optional[int] = Field(None, description="Required when fallback_mode='assign'")
163
+
164
+
165
+ class UpdateDoorDescriptionInput(BaseModel):
166
+ """Input schema for update-door-description tool"""
167
+ door_id: int = Field(..., description="ID of the door to update")
168
+ description: str = Field(..., description="New description to set. Pass an empty string to clear.")
169
+
170
+
171
+ class SetDoorAutoModeInput(BaseModel):
172
+ """Input schema for set-door-auto-mode tool"""
173
+ door_id: Optional[int] = Field(None, description="Door ID")
174
+ door_name: Optional[str] = Field(None, description="Exact door name (alternative to door_id)")
175
+ enable: Optional[bool] = Field(None, description="true to enable, false to disable")
176
+
177
+ @model_validator(mode="after")
178
+ def validate_identifier(self):
179
+ """Ensure door identifier and enable are provided"""
180
+ if not any([self.door_id, self.door_name]):
181
+ raise ValueError("Either door_id or door_name must be provided")
182
+ if self.enable is None:
183
+ raise ValueError("enable must be provided")
184
+ return self
185
+
186
+
187
+ class SetDoorRelockOnCloseInput(BaseModel):
188
+ """Input schema for set-door-relock-on-close tool"""
189
+ door_id: Optional[int] = Field(None, description="Door ID")
190
+ door_name: Optional[str] = Field(None, description="Exact door name (alternative to door_id)")
191
+ enable: Optional[bool] = Field(None, description="true to enable, false to disable")
192
+
193
+ @model_validator(mode="after")
194
+ def validate_identifier(self):
195
+ """Ensure door identifier and enable are provided"""
196
+ if not any([self.door_id, self.door_name]):
197
+ raise ValueError("Either door_id or door_name must be provided")
198
+ if self.enable is None:
199
+ raise ValueError("enable must be provided")
200
+ return self
201
+
202
+
203
+ class SetDoorOpenDurationInput(BaseModel):
204
+ """Input schema for set-door-open-duration tool"""
205
+ door_id: Optional[int] = Field(None, description="ID of the door to update")
206
+ door_name: Optional[str] = Field(None, description="Exact door name (alternative to door_id)")
207
+ seconds: Optional[int] = Field(None, ge=1, le=900, description="New open duration in seconds (sent as string). Range: 1–900 (inclusive).")
208
+
209
+ @model_validator(mode="after")
210
+ def validate_identifier(self):
211
+ """Ensure door identifier and seconds are provided"""
212
+ if not any([self.door_id, self.door_name]):
213
+ raise ValueError("Either door_id or door_name must be provided")
214
+ if self.seconds is None:
215
+ raise ValueError("seconds must be provided")
216
+ return self
217
+
218
+
219
+ class SetDoorTimedAPBInput(BaseModel):
220
+ """Input schema for set-door-timed-apb tool"""
221
+ door_id: Optional[int] = Field(None, description="Door ID (optional). Either door_id or door_name is required.")
222
+ door_name: Optional[str] = Field(None, description="Exact door name (optional). Either door_id or door_name is required.")
223
+ mode: Optional[Union[str, int]] = Field(None, description="APB mode: off | entry | exit (also accepts 0/1/2).")
224
+ reset_time: Optional[int] = Field(None, ge=0, le=60, description="APB reset time in minutes. 0..60 inclusive. Required when mode != off.")
225
+ bypass_group_ids: Optional[List[int]] = Field(None, description="Optional: Access Group IDs for timed APB bypass. If omitted, preserves current.")
226
+
227
+ @model_validator(mode="after")
228
+ def validate_identifier(self):
229
+ """Ensure door identifier and mode are provided"""
230
+ if not any([self.door_id, self.door_name]):
231
+ raise ValueError("Either door_id or door_name must be provided")
232
+ if self.mode is None:
233
+ raise ValueError("mode must be provided")
234
+ return self
235
+
236
+
237
+ class SetDoorClassicAPBInput(BaseModel):
238
+ """Input schema for set-door-classic-apb tool"""
239
+ door_id: Optional[int] = Field(None, description="Door ID (optional). Either door_id or door_name is required.")
240
+ door_name: Optional[str] = Field(None, description="Exact door name (optional). Either door_id or door_name is required.")
241
+ apb_type: Optional[Union[str, int]] = Field(None, description="APB type: none | soft | hard (also accepts 0/1/2).")
242
+
243
+ @model_validator(mode="after")
244
+ def validate_identifier(self):
245
+ """Ensure door identifier and apb_type are provided"""
246
+ if not any([self.door_id, self.door_name]):
247
+ raise ValueError("Either door_id or door_name must be provided")
248
+ if self.apb_type is None:
249
+ raise ValueError("apb_type must be provided")
250
+ return self
251
+
252
+
253
+ class SetDoorIOInput(BaseModel):
254
+ """Input schema for set-door-io tool"""
255
+ door_id: Optional[int] = Field(None, description="ID of the door to update.")
256
+ door_name: Optional[str] = Field(None, description="Exact door name to update.")
257
+ entry_device_id: Optional[Union[int, None]] = Field(None, description="Device id for entry reader, or null to clear.")
258
+ entry_device_name: Optional[Union[str, None]] = Field(None, description="Device name for entry reader; 'none' to clear.")
259
+ relay_device_id: Optional[int] = Field(None, description="Device id hosting the relay.")
260
+ relay_device_name: Optional[str] = Field(None, description="Device name hosting the relay.")
261
+ relay_index: Optional[int] = Field(None, description="Relay index on the device.")
262
+ exit_button_device_id: Optional[int] = Field(None, description="Device id for the exit button input.")
263
+ exit_button_device_name: Optional[str] = Field(None, description="Device name for the exit button input.")
264
+ exit_button_input_index: Optional[int] = Field(None, description="Input index (e.g., 0 or 1).")
265
+ exit_button_input_type: Optional[int] = Field(None, description="Input type: 1=NC, 0=NO")
266
+ sensor_device_id: Optional[int] = Field(None, description="Device id for the door sensor input.")
267
+ sensor_device_name: Optional[str] = Field(None, description="Device name for the door sensor input.")
268
+ sensor_input_index: Optional[int] = Field(None, description="Input index (e.g., 0 or 1).")
269
+ sensor_input_type: Optional[int] = Field(None, description="Input type: 1=NC, 0=NO")
270
+ sensor_apb_use: Optional[Union[bool, int, str]] = Field(None, description="Whether to use the door sensor for APB (apb_use_door_sensor). Accepts boolean or 0/1/'0'/'1'.")
271
+
272
+ @model_validator(mode="after")
273
+ def validate_identifier(self):
274
+ """Ensure door identifier is provided"""
275
+ if not any([self.door_id, self.door_name]):
276
+ raise ValueError("Either door_id or door_name must be provided")
277
+ return self
278
+
279
+
280
+ class BulkCreateDoorItemInput(BaseModel):
281
+ """Input schema for bulk-create-doors door item"""
282
+ name: str = Field(..., description="Door name")
283
+ entry_device_id: int = Field(..., description="Device id to control the lock (1:1 mapping enforced).")
284
+ exit_device_id: int = Field(..., description="Exit button device id.")
285
+ exit_input_index: int = Field(..., description="Exit input index.")
286
+ exit_input_type: int = Field(..., description="Exit input type.")
287
+ sensor_device_id: int = Field(..., description="Door sensor device id.")
288
+ sensor_input_index: int = Field(..., description="Sensor input index.")
289
+ sensor_input_type: int = Field(..., description="Sensor input type.")
290
+ apb_use_door_sensor: int = Field(..., description="Use door sensor for APB.")
291
+ confirm: Optional[bool] = Field(default=False, description="If true and all required fields are valid, creation proceeds.")
292
+ default_group_id: Optional[int] = Field(default=1, description="Fallback door group id when 'door_group_id' is not provided.")
293
+ description: Optional[str] = Field(None, description="Door description")
294
+ door_group_id: Optional[int] = Field(None, description="Door group id to assign at creation time")
295
+ open_timeout: Optional[int] = Field(default=10, description="Door open timeout (sec).")
296
+ open_duration: Optional[int] = Field(default=5, description="Door open duration (sec).")
297
+ open_once: Optional[bool] = Field(default=True, description="Open once option.")
298
+ unconditional_lock: Optional[bool] = Field(default=True, description="Unconditional lock option.")
299
+ relay_index: Optional[int] = Field(None, description="Required to actually create. If missing, tool returns relay options and stops.")
300
+
301
+
302
+ class BulkCreateDoorsInput(BaseModel):
303
+ """Input schema for bulk-create-doors tool"""
304
+ doors: List[BulkCreateDoorItemInput] = Field(..., description="List of door payloads. Each item uses the same schema as 'create-door'.")
305
+ continue_on_error: Optional[bool] = Field(default=True, description="If false, stop at the first failure. Default: true.")
306
+
@@ -0,0 +1,104 @@
1
+ from typing import Optional, List, Union
2
+ from pydantic import BaseModel, Field
3
+
4
+
5
+ # ============================================================================
6
+ # Event Condition Schemas (nested)
7
+ # ============================================================================
8
+
9
+ class EventConditionInput(BaseModel):
10
+ """Input schema for event condition item"""
11
+ column: str = Field(..., description="Column name")
12
+ operator: int = Field(..., ge=0, le=6, description="Operator (0=EQUAL, 1=NOT_EQUAL, 2=CONTAINS, 3=BETWEEN, 5=GREATER, 6=LESS)")
13
+ value: Optional[List[Union[str, int, float]]] = Field(None, description="Value array")
14
+ values: Optional[List[Union[str, int, float]]] = Field(None, description="Values array")
15
+
16
+
17
+ # ============================================================================
18
+ # Event Search Schemas
19
+ # ============================================================================
20
+
21
+ class GetEventTypesInput(BaseModel):
22
+ """Input schema for get-event-types tool"""
23
+ is_break_glass: Optional[bool] = Field(default=False, description="If true, list user-renamed (break-glass) names. When true and others not specified, setting_alert/setting_all default to false.")
24
+ setting_alert: Optional[bool] = Field(default=True, description="If true, list alertable event types (server-side filter).")
25
+ setting_all: Optional[bool] = Field(default=True, description="If true, list all event types (server-side filter).")
26
+ search: Optional[str] = Field(None, description="Case-insensitive contains across code, name, and description.")
27
+ codes: Optional[List[Union[str, int]]] = Field(None, description="Restrict to specific event code(s).")
28
+ only_alertable: Optional[bool] = Field(default=False, description="Keep only items with alertable == true.")
29
+ only_enable_alert: Optional[bool] = Field(default=False, description="Keep only items with enable_alert == true.")
30
+ sort_by: Optional[str] = Field(default="code", description="Sort field (default: code).")
31
+ sort_desc: Optional[bool] = Field(default=False, description="Descending sort if true.")
32
+ offset: Optional[int] = Field(default=0, description="Start index for slicing.")
33
+ limit: Optional[int] = Field(None, description="Maximum number of items to return (client-side slicing).")
34
+ group_by: Optional[str] = Field(default="none", description="Group the result by a boolean field.")
35
+
36
+
37
+ class SearchEventsInput(BaseModel):
38
+ """Input schema for search-events tool"""
39
+ conditions: Optional[List[EventConditionInput]] = Field(None, description="Array of search conditions (for advanced filters only, do NOT use for datetime or user_id)")
40
+ user_id: Optional[Union[str, int]] = Field(None, description="User ID to filter events (e.g., '61061'). Use this instead of conditions array.")
41
+ user_name: Optional[str] = Field(None, description="User name to filter events. Server will resolve name to user_id.")
42
+ start_datetime: Optional[str] = Field(None, description="Start datetime: 'YYYY-MM-DDTHH:MM:SS' (e.g., '2025-11-07T00:00:00'). WITHOUT timezone. Server converts to UTC.")
43
+ end_datetime: Optional[str] = Field(None, description="End datetime: 'YYYY-MM-DDTHH:MM:SS' (e.g., '2025-11-07T23:59:59'). WITHOUT timezone. Server converts to UTC.")
44
+ limit: Optional[int] = Field(default=100, description="Limit")
45
+ offset: Optional[int] = Field(default=0, description="Offset")
46
+ order_by: Optional[str] = Field(default="datetime", description="Order by")
47
+ order_type: Optional[str] = Field(default="desc", description="Order type")
48
+
49
+
50
+ class GetRealtimeEventsInput(BaseModel):
51
+ """Input schema for get-realtime-events tool"""
52
+ duration: Optional[int] = Field(default=60, le=300, description="Duration to monitor events in seconds (max: 300)")
53
+ event_types: Optional[List[str]] = Field(None, description="Filter real-time events by specific event type IDs")
54
+
55
+
56
+ class GetAccessLogsInput(BaseModel):
57
+ """Input schema for get-access-logs tool"""
58
+ start_datetime: Optional[str] = Field(None, description="Start datetime in ISO 8601 format (e.g., \"2025-10-27T00:00:00.000Z\"). If not provided, defaults to 24 hours ago from NOW. NEVER use hardcoded dates.")
59
+ end_datetime: Optional[str] = Field(None, description="End datetime in ISO 8601 format (e.g., \"2025-10-27T23:59:59.999Z\"). If not provided, defaults to NOW. NEVER use hardcoded dates.")
60
+ user_name: Optional[str] = Field(None, description="Filter by user name (partial match)")
61
+ success_only: Optional[bool] = Field(default=False, description="Show only successful access attempts")
62
+ limit: Optional[int] = Field(default=100, description="Maximum number of logs to retrieve")
63
+
64
+
65
+ class EventExportConditionInput(BaseModel):
66
+ """Input schema for export-events-csv condition item"""
67
+ column: str = Field(..., description="Column name")
68
+ operator: int = Field(..., description="Operator")
69
+ values: List[Union[str, int, float]] = Field(..., description="Values array")
70
+
71
+
72
+ class ExportEventsCSVInput(BaseModel):
73
+ """Input schema for export-events-csv tool"""
74
+ start_datetime: str = Field(..., description="Start datetime (ISO 8601 with Z). If 'conditions' omitted, this and end_datetime build a BETWEEN.")
75
+ end_datetime: str = Field(..., description="End datetime (ISO 8601 with Z).")
76
+ conditions: Optional[List[EventExportConditionInput]] = Field(None, description="Custom conditions for the Query. If provided, takes precedence.")
77
+ user_id: Optional[Union[str, int]] = Field(None, description="Convenience filter: constrains to this user_id (mapped to column 'user_id.user_id').")
78
+ user_name: Optional[str] = Field(None, description="Convenience filter: resolve name → user_id and filter (exact match preferred).")
79
+ event_types: Optional[List[Union[str, int]]] = Field(None, description="Convenience filter: adds EQUAL condition on event_type_id with provided values.")
80
+ columns: Optional[List[str]] = Field(
81
+ default=["datetime","door_id.name","device_id.id","device_id.name","user_group_id","user_id","temperature","event_type_id","tna_key"],
82
+ description="Columns to export (default matches BioStar example)."
83
+ )
84
+ headers: Optional[List[str]] = Field(
85
+ default=["Date","Door","Device ID","Device","User Group","User","Temperature","Event","TNA Key"],
86
+ description="CSV headers (must match columns length)."
87
+ )
88
+ offset: Optional[int] = Field(default=0, description="Offset for pagination.")
89
+ time_offset_minutes: Optional[int] = Field(default=0, description="Timezone offset in minutes, e.g., 480 for UTC+8.")
90
+ use_centigrade: Optional[bool] = Field(default=True, description="Whether to export temperature in centigrade.")
91
+ copy_to_downloads: Optional[bool] = Field(default=True, description="Copy exported CSV to Windows Downloads.")
92
+ dest_dir: Optional[str] = Field(None, description="Optional absolute destination directory (overrides Downloads).")
93
+ target_username: Optional[str] = Field(None, description="If provided, copies to C:\\Users\\<name>\\Downloads.")
94
+ filename: Optional[str] = Field(default="events_export.csv", description="Legacy/unused: server decides filename. Returned as 'filename' in response.")
95
+
96
+
97
+ class GetTemperatureLogsInput(BaseModel):
98
+ """Input schema for get-temperature-logs tool"""
99
+ start_datetime: Optional[str] = Field(None, description="Start datetime for temperature log search in ISO 8601 format with Z suffix (e.g., \"2025-07-26T15:00:00.000Z\")")
100
+ end_datetime: Optional[str] = Field(None, description="End datetime for temperature log search in ISO 8601 format with Z suffix (e.g., \"2025-08-02T14:59:59.000Z\")")
101
+ user_id: Optional[str] = Field(None, description="Filter by specific user ID")
102
+ abnormal_only: Optional[bool] = Field(default=False, description="Show only abnormal temperature readings")
103
+ temperature_threshold: Optional[float] = Field(None, ge=35.0, le=42.0, description="Temperature threshold in Celsius (e.g., 37.5)")
104
+
@@ -0,0 +1,7 @@
1
+ from pydantic import BaseModel, Field
2
+
3
+
4
+ class ReadUploadedFileInput(BaseModel):
5
+ """Input schema for read-uploaded-file tool"""
6
+ filename: str = Field(..., description="Name of the uploaded file to read (e.g., 'users.csv')")
7
+
@@ -0,0 +1,29 @@
1
+ from typing import Optional
2
+ from pydantic import BaseModel, Field
3
+
4
+
5
+ class VectorSearchHelpInput(BaseModel):
6
+ """Input schema for vector-search-help tool"""
7
+ query: str = Field(..., description="Question or keyword to search (e.g., 'how to create user', 'door API usage')")
8
+ tool_name: Optional[str] = Field(None, description="Specific tool name (optional, e.g., 'create-user')")
9
+ language: Optional[str] = Field(default="ko", description="Language selection (default: ko)")
10
+
11
+
12
+ class VectorSearchAPIDocsInput(BaseModel):
13
+ """Input schema for vector-search-api-docs tool"""
14
+ query: str = Field(..., description="API-related question to search (e.g., 'list users', 'door unlock API')")
15
+ limit: Optional[int] = Field(default=10, ge=1, le=20, description="Number of results to return (default: 10, increased for better LLM selection)")
16
+
17
+
18
+ class VectorSearchManualInput(BaseModel):
19
+ """Input schema for vector-search-manual tool"""
20
+ query: str = Field(..., description="Question to search (e.g., 'user management', 'access permission settings')")
21
+ language: Optional[str] = Field(default="ko", description="Language selection (default: ko)")
22
+ limit: Optional[int] = Field(default=10, ge=1, le=20, description="Number of results to return (default: 10, increased for better LLM selection)")
23
+
24
+
25
+ class VectorGetToolHelpInput(BaseModel):
26
+ """Input schema for vector-get-tool-help tool"""
27
+ tool_name: str = Field(..., description="Tool name (e.g., 'create-user', 'list-doors')")
28
+ language: Optional[str] = Field(default="ko", description="Language selection (default: ko)")
29
+
@@ -0,0 +1,33 @@
1
+ from typing import Optional
2
+ from pydantic import BaseModel, Field
3
+
4
+
5
+ class AnalyzeServerLogsInput(BaseModel):
6
+ """Input schema for analyze_server_logs tool"""
7
+ log_path: Optional[str] = Field(default="C:\\Program Files\\BioStar X\\logs", description="로그 파일 경로 (기본값: C:\\Program Files\\BioStar X\\logs)")
8
+ lines_to_read: Optional[int] = Field(default=50, ge=20, le=100, description="각 로그 파일에서 읽을 줄 수 (기본값: 50)")
9
+
10
+
11
+ class GetLogFileInfoInput(BaseModel):
12
+ """Input schema for get_log_file_info tool"""
13
+ log_path: Optional[str] = Field(default="C:\\Program Files\\BioStar X\\logs", description="로그 파일 경로 (기본값: C:\\Program Files\\BioStar X\\logs)")
14
+
15
+
16
+ class ReadSpecificLogInput(BaseModel):
17
+ """Input schema for read_specific_log tool"""
18
+ log_file_path: str = Field(..., description="읽을 로그 파일의 전체 경로")
19
+ lines_to_read: Optional[int] = Field(default=50, ge=20, le=100, description="읽을 줄 수 (기본값: 50)")
20
+ from_end: Optional[bool] = Field(default=True, description="파일 끝에서부터 읽을지 여부 (기본값: true)")
21
+
22
+
23
+ class GetSystemResourcesInput(BaseModel):
24
+ """Input schema for get_system_resources tool"""
25
+ include_all_processes: Optional[bool] = Field(default=False, description="모든 프로세스 정보 포함 여부 (기본값: false, BioStar 관련만)")
26
+
27
+
28
+ class AnalyzeServerStatusInput(BaseModel):
29
+ """Input schema for analyze_server_status tool"""
30
+ log_path: Optional[str] = Field(default="C:\\Program Files\\BioStar X\\logs", description="로그 파일 경로 (기본값: C:\\Program Files\\BioStar X\\logs)")
31
+ lines_to_read: Optional[int] = Field(default=50, ge=20, le=100, description="각 로그 파일에서 읽을 줄 수 (기본값: 50)")
32
+ include_system_resources: Optional[bool] = Field(default=True, description="시스템 리소스 정보 포함 여부 (기본값: true)")
33
+
@@ -0,0 +1,19 @@
1
+ from typing import Optional
2
+ from pydantic import BaseModel, Field
3
+
4
+
5
+ class GetOccupancyStatusInput(BaseModel):
6
+ """Input schema for get-occupancy-status tool"""
7
+ door_name: Optional[str] = Field(None, description="출입문 이름 (예: '12층 메인 출입문'). If not provided, will analyze all doors.")
8
+ door_id: Optional[str] = Field(None, description="출입문 ID (door_name 대신 사용 가능)")
9
+ hours: Optional[int] = Field(default=24, description="분석할 시간 범위 (시간 단위, 기본값: 24시간)")
10
+ include_details: Optional[bool] = Field(default=True, description="상세 이벤트 정보 포함 여부 (기본값: true)")
11
+
12
+
13
+ class AnalyzeDoorTrafficInput(BaseModel):
14
+ """Input schema for analyze-door-traffic tool"""
15
+ door_name: Optional[str] = Field(None, description="출입문 이름")
16
+ door_id: Optional[str] = Field(None, description="출입문 ID")
17
+ start_date: Optional[str] = Field(None, description="시작 날짜 (YYYY-MM-DD)")
18
+ end_date: Optional[str] = Field(None, description="종료 날짜 (YYYY-MM-DD)")
19
+
@@ -0,0 +1,29 @@
1
+ from typing import Optional, List, Dict, Any
2
+ from pydantic import BaseModel
3
+
4
+
5
+ class NavigationAction(BaseModel):
6
+ """Navigation action to perform on target page"""
7
+ type: str # "click" or "scroll"
8
+ ng_label_key: str # Angular label key to target
9
+
10
+
11
+ class NavigationInfo(BaseModel):
12
+ """Frontend navigation information"""
13
+ enabled: bool = True
14
+ page_type: str # user_list, user_detail, door_list, door_detail, device_list, device_detail, monitoring, custom_url
15
+ resource_id: Optional[str] = None # For detail pages: user_id, door_id, device_id
16
+ custom_url: Optional[str] = None # For custom_url page type
17
+ action: Optional[NavigationAction] = None # Optional action on target page
18
+ reason: Optional[str] = None # Why navigating to this page
19
+
20
+
21
+ class ToolResponse(BaseModel):
22
+ message: str
23
+ method: str
24
+ navigate_to: Optional[str] = None # Deprecated: use navigation instead
25
+ navigation: Optional[NavigationInfo] = None # New structured navigation
26
+ query: Optional[Dict[str, Any]] = None
27
+ users: Optional[List[Dict[str, Any]]] = None
28
+ download_url: Optional[str] = None
29
+ filename: Optional[str] = None