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,21 @@
1
+ from .tool_response import ToolResponse
2
+ from .users import (
3
+ CreateUserInput,
4
+ DeleteUserInput,
5
+ UpdateUserInput,
6
+ AdvancedSearchUserInput,
7
+ ExportCSVInput,
8
+ BulkAddUsersInput,
9
+ BulkEditUsersInput,
10
+ )
11
+
12
+ __all__ = [
13
+ "ToolResponse",
14
+ "CreateUserInput",
15
+ "DeleteUserInput",
16
+ "UpdateUserInput",
17
+ "AdvancedSearchUserInput",
18
+ "ExportCSVInput",
19
+ "BulkAddUsersInput",
20
+ "BulkEditUsersInput",
21
+ ]
@@ -0,0 +1,158 @@
1
+ from typing import Optional, Union, List, Dict, Any
2
+ from pydantic import BaseModel, Field, model_validator
3
+
4
+
5
+ # ============================================================================
6
+ # Access Group Schemas
7
+ # ============================================================================
8
+
9
+ class GetAccessGroupsInput(BaseModel):
10
+ """Input schema for get-access-groups tool"""
11
+ pass # No required fields
12
+
13
+
14
+ class GetAccessGroupInput(BaseModel):
15
+ """Input schema for get-access-group tool"""
16
+ group_id: int = Field(..., description="ID of the access group to retrieve")
17
+
18
+
19
+ class CreateAccessGroupInput(BaseModel):
20
+ """Input schema for create-access-group tool"""
21
+ name: str = Field(..., description="Access group name")
22
+ description: Optional[str] = Field(default="", description="Description for the access group")
23
+ user_ids: Optional[List[Union[str, int]]] = Field(None, description="Explicit list of user ids to include. Do NOT auto-select.")
24
+ access_level_ids: Optional[List[Union[str, int]]] = Field(None, description="Existing access level ids to assign to this group.")
25
+ user_group_ids: Optional[List[Union[int, str]]] = Field(None, description="Explicit user group ids to include (validated against GET /api/user_groups)")
26
+ user_group_search_text: Optional[str] = Field(None, description="Substring to search user group name (3-case resolution)")
27
+
28
+
29
+ class UpdateAccessGroupInput(BaseModel):
30
+ """Input schema for update-access-group tool"""
31
+ group_id: int = Field(..., description="ID of the access group to update")
32
+ name: Optional[str] = Field(None, description="New name")
33
+ description: Optional[str] = Field(None, description="New description")
34
+
35
+ # Users
36
+ user_ids: Optional[List[Union[str, int]]] = Field(None, description="Final set of user ids (replacement).")
37
+ new_users: Optional[List[Union[str, int]]] = Field(None, description="User ids to add (delta).")
38
+ delete_users: Optional[List[Union[str, int]]] = Field(None, description="User ids to remove (delta).")
39
+
40
+ # Access Levels (replacement)
41
+ access_level_ids: Optional[List[Union[str, int]]] = Field(None, description="Replace the group's access_levels with these ids.")
42
+
43
+ # User Groups - Replacement
44
+ user_group_ids: Optional[List[Union[int, str]]] = Field(None, description="Replace user_groups with these ids. If [], clears all.")
45
+ user_group_search_text: Optional[str] = Field(None, description="(Legacy) Single-match replacement by name.")
46
+
47
+ # User Groups - Delta
48
+ add_user_group_ids: Optional[List[Union[int, str]]] = Field(None, description="Add these user group ids (delta).")
49
+ remove_user_group_ids: Optional[List[Union[int, str]]] = Field(None, description="Remove these user group ids (delta).")
50
+ add_user_group_search_text: Optional[str] = Field(None, description="Add by name (3-case resolution).")
51
+ remove_user_group_search_text: Optional[str] = Field(None, description="Remove by name (3-case resolution).")
52
+
53
+
54
+ class DeleteAccessGroupInput(BaseModel):
55
+ """Input schema for delete-access-group tool"""
56
+ group_id: int = Field(..., description="ID of the access group to delete")
57
+
58
+
59
+ class SearchAccessGroupsInput(BaseModel):
60
+ """Input schema for search-access-groups tool"""
61
+ limit: int = Field(..., description="To limit result by n record(s), 0 to show all")
62
+ order_by: str = Field(..., description="Order by string like 'id:false' or 'name:true'")
63
+
64
+
65
+ # ============================================================================
66
+ # Access Level Schemas
67
+ # ============================================================================
68
+
69
+ class GetAccessLevelsInput(BaseModel):
70
+ """Input schema for get-access-levels tool"""
71
+ limit: Optional[int] = Field(default=50, description="Limit number of records (0 to show all)")
72
+ offset: Optional[int] = Field(default=0, description="Offset/skip records")
73
+ order_by: Optional[str] = Field(default="id:false", description="Order by (e.g., 'id:false')")
74
+
75
+
76
+ class GetAccessLevelInput(BaseModel):
77
+ """Input schema for get-access-level tool"""
78
+ level_id: int = Field(..., description="ID of the access level to retrieve")
79
+
80
+
81
+ class AccessLevelItemInput(BaseModel):
82
+ """Input schema for access_level_items array item"""
83
+ doors: List[Union[int, str]] = Field(..., min_length=1, description="Door IDs or names; de-duplicated per item.")
84
+
85
+
86
+ class CreateAccessLevelInput(BaseModel):
87
+ """Input schema for create-access-level tool"""
88
+ name: str = Field(..., description="Unique Access Level name.")
89
+ description: Optional[str] = Field(default="", description="Description for the Access Level.")
90
+ confirm: Optional[bool] = Field(default=True, description="If true, create the Access Level; otherwise preview only. Defaults to True for automatic creation.")
91
+ door_ids: Optional[List[int]] = Field(None, description="🆕 Simple mode: List of door IDs to include (e.g., [177, 178, 179]). Mutually exclusive with access_level_items.")
92
+ door_names: Optional[List[str]] = Field(None, description="🆕 Simple mode: List of door names to include (e.g., ['Main Entrance', 'CEO Office']). Will be resolved to IDs. Mutually exclusive with access_level_items.")
93
+ auto_update_on_exist: Optional[bool] = Field(default=True, description="🆕 If true (default), automatically update if Access Level with same name exists. If false, skip creation and return existing info.")
94
+ access_level_items: Optional[List[AccessLevelItemInput]] = Field(None, min_length=1, description="Advanced mode: Array tying doors (1..n) to the enforced 'Always' schedule (applied internally). Each item must include `doors`. Mutually exclusive with door_ids/door_names.")
95
+
96
+ @model_validator(mode="after")
97
+ def validate_door_specification(self):
98
+ """Ensure at least one door specification method is provided"""
99
+ has_door_ids = self.door_ids is not None and len(self.door_ids) > 0
100
+ has_door_names = self.door_names is not None and len(self.door_names) > 0
101
+ has_access_level_items = self.access_level_items is not None and len(self.access_level_items) > 0
102
+
103
+ if not (has_door_ids or has_door_names or has_access_level_items):
104
+ raise ValueError("At least one of door_ids, door_names, or access_level_items must be provided")
105
+
106
+ # Check mutual exclusivity
107
+ provided = sum([has_door_ids, has_door_names, has_access_level_items])
108
+ if provided > 1:
109
+ raise ValueError("Only one of door_ids, door_names, or access_level_items can be provided")
110
+
111
+ return self
112
+
113
+
114
+ class UpdateAccessLevelInput(BaseModel):
115
+ """Input schema for update-access-level tool"""
116
+ level_id: Optional[int] = Field(None, description="ID of the access level to update")
117
+ level_name: Optional[str] = Field(None, description="Exact name of the access level to update")
118
+ search_text: Optional[str] = Field(None, description="Fuzzy search text (name/description contains)")
119
+ name: Optional[str] = Field(None, description="New name (optional)")
120
+ description: Optional[str] = Field(None, description="New description (optional; empty string clears)")
121
+ set_doors: Optional[List[Union[int, str]]] = Field(None, description="Full replacement of door ids (takes precedence over add/remove)")
122
+ add_doors: Optional[List[Union[int, str]]] = Field(None, description="Door ids to add")
123
+ remove_doors: Optional[List[Union[int, str]]] = Field(None, description="Door ids to remove (ignored if not currently assigned)")
124
+ dry_run: Optional[bool] = Field(default=False, description="Preview changes without calling the API")
125
+
126
+
127
+ class DeleteAccessLevelInput(BaseModel):
128
+ """Input schema for delete-access-level tool"""
129
+ level_id: int = Field(..., description="ID of the access level to delete")
130
+
131
+
132
+ class AddAccessLevelToGroupInput(BaseModel):
133
+ """Input schema for add-access-level-to-group tool"""
134
+ access_group_id: int = Field(..., description="Target access group ID")
135
+ access_level_id: Optional[int] = Field(None, description="An access level id to add (single)")
136
+ access_level_ids: Optional[List[Union[int, str]]] = Field(None, description="Access level ids to add (one or many)")
137
+
138
+ @model_validator(mode="after")
139
+ def validate_access_level_specification(self):
140
+ """Ensure at least one access level is specified"""
141
+ if self.access_level_id is None and (self.access_level_ids is None or len(self.access_level_ids) == 0):
142
+ raise ValueError("Either access_level_id or access_level_ids must be provided")
143
+ return self
144
+
145
+
146
+ class RemoveAccessLevelFromGroupInput(BaseModel):
147
+ """Input schema for remove-access-level-from-group tool"""
148
+ access_group_id: int = Field(..., description="Target access group ID")
149
+ access_level_id: Optional[int] = Field(None, description="An access level id to remove (single)")
150
+ access_level_ids: Optional[List[Union[int, str]]] = Field(None, description="Access level ids to remove (one or many)")
151
+
152
+ @model_validator(mode="after")
153
+ def validate_access_level_specification(self):
154
+ """Ensure at least one access level is specified"""
155
+ if self.access_level_id is None and (self.access_level_ids is None or len(self.access_level_ids) == 0):
156
+ raise ValueError("Either access_level_id or access_level_ids must be provided")
157
+ return self
158
+
@@ -0,0 +1,73 @@
1
+ from typing import Optional, List
2
+ from pydantic import BaseModel, Field
3
+
4
+
5
+ # ============================================================================
6
+ # Audit Condition Schemas (nested)
7
+ # ============================================================================
8
+
9
+ class AuditConditionInput(BaseModel):
10
+ """Input schema for audit condition item"""
11
+ column: str = Field(..., description="Column to search (e.g., 'datetime', 'user_id', 'target_type')")
12
+ operator: int = Field(..., description="Search operator (0=EQUAL, 1=NOT_EQUAL, 2=CONTAINS, 3=BETWEEN, 5=GREATER, 6=LESS)")
13
+ values: List[str] = Field(..., description="Values to search for")
14
+
15
+
16
+ # ============================================================================
17
+ # Audit Search Schemas
18
+ # ============================================================================
19
+
20
+ class AuditSearchInput(BaseModel):
21
+ """Input schema for audit-search tool"""
22
+ conditions: Optional[List[AuditConditionInput]] = Field(None, description="Search conditions for audit trail")
23
+ limit: Optional[int] = Field(default=100, description="Maximum number of results")
24
+ offset: Optional[int] = Field(default=0, description="Offset for pagination")
25
+
26
+
27
+ class AuditSearchUserInput(BaseModel):
28
+ """Input schema for audit-search-user tool"""
29
+ search: Optional[str] = Field(None, description="Search string for user name")
30
+ limit: Optional[int] = Field(default=201, description="Maximum number of results")
31
+ offset: Optional[int] = Field(default=0, description="Offset for pagination")
32
+
33
+
34
+ class AuditSearchOperatorLevelInput(BaseModel):
35
+ """Input schema for audit-search-operator-level tool"""
36
+ search: Optional[str] = Field(None, description="Search string for permission level")
37
+ limit: Optional[int] = Field(default=201, description="Maximum number of results")
38
+ offset: Optional[int] = Field(default=0, description="Offset for pagination")
39
+
40
+
41
+ class AuditSearchIPListInput(BaseModel):
42
+ """Input schema for audit-search-ip-list tool"""
43
+ search: Optional[str] = Field(None, description="Search string for IP address")
44
+ limit: Optional[int] = Field(default=201, description="Maximum number of results")
45
+ offset: Optional[int] = Field(default=0, description="Offset for pagination")
46
+
47
+
48
+ class AuditSearchTargetListInput(BaseModel):
49
+ """Input schema for audit-search-target-list tool"""
50
+ search: Optional[str] = Field(None, description="Search string for target")
51
+ limit: Optional[int] = Field(default=201, description="Maximum number of results")
52
+ offset: Optional[int] = Field(default=0, description="Offset for pagination")
53
+
54
+
55
+ class AuditCSVExportInput(BaseModel):
56
+ """Input schema for audit-csv-export tool"""
57
+ conditions: Optional[List[AuditConditionInput]] = Field(None, description="Filter conditions (use 'DATE' for time).")
58
+ columns: Optional[List[str]] = Field(
59
+ default=["DATE","USRID","PERM","IP","MENU","TARGET","METHOD","CONTENT"],
60
+ description="CSV columns (UPPERCASE per API spec)."
61
+ )
62
+ headers: Optional[List[str]] = Field(
63
+ default=["Datetime","User","Operator Level","IP","Category","Target","Action","Modification"],
64
+ description="CSV header names (same length as columns)."
65
+ )
66
+ offset: Optional[int] = Field(default=0, description="Offset for export pagination.")
67
+ time_offset_minutes: Optional[int] = Field(default=0, description="Timezone offset in minutes (e.g., UTC+8 => 480).")
68
+ start_datetime: Optional[str] = Field(None, description="Convenience: start time (ISO). Builds a DATE condition if 'conditions' omitted.")
69
+ end_datetime: Optional[str] = Field(None, description="Convenience: end time (ISO). Builds a DATE condition if 'conditions' omitted.")
70
+ copy_to_downloads: Optional[bool] = Field(default=True, description="Copy exported CSV to Windows Downloads folder.")
71
+ dest_dir: Optional[str] = Field(None, description="Optional absolute destination directory (overrides Downloads).")
72
+ target_username: Optional[str] = Field(None, description="If provided, copies to C:\\Users\\<name>\\Downloads.")
73
+
@@ -0,0 +1,24 @@
1
+ from typing import Optional
2
+ from pydantic import BaseModel, Field
3
+
4
+
5
+ class LoginInput(BaseModel):
6
+ """Input schema for login tool"""
7
+ username: Optional[str] = Field(None, description="Optional: BioStar 2 username (defaults to BIOSTAR_USERNAME from .env)")
8
+ password: Optional[str] = Field(None, description="Optional: BioStar 2 password (defaults to BIOSTAR_PASSWORD from .env)")
9
+
10
+
11
+ class LogoutInput(BaseModel):
12
+ """Input schema for logout tool"""
13
+ pass # No required fields
14
+
15
+
16
+ class GetSessionInfoInput(BaseModel):
17
+ """Input schema for get-session-info tool"""
18
+ pass # No required fields
19
+
20
+
21
+ class GetServerPreferencesInput(BaseModel):
22
+ """Input schema for get-server-preferences tool"""
23
+ pass # No required fields
24
+
@@ -0,0 +1,128 @@
1
+ from typing import Optional, Union
2
+ from pydantic import BaseModel, Field, model_validator
3
+
4
+
5
+ # ============================================================================
6
+ # Card Creation Schemas
7
+ # ============================================================================
8
+
9
+ class CreateCardCSNInput(BaseModel):
10
+ """Input schema for create-card-csn tool"""
11
+ card_id: Union[str, int] = Field(..., description="Value shown/read when scanning the card")
12
+ card_type_type: Optional[int] = Field(None, description="Type allocation of the card type; auto-resolved if omitted")
13
+ assign_to_user_id: Optional[Union[str, int]] = Field(None, description="If provided, assign the created card to this user immediately")
14
+ skip_availability_check: Optional[bool] = Field(default=False, description="Skip server-side availability check before create (default: false)")
15
+ dry_run: Optional[bool] = Field(default=False, description="Return the request body without calling the API (default: false)")
16
+
17
+
18
+ class CreateCardWiegandInput(BaseModel):
19
+ """Input schema for create-card-wiegand tool"""
20
+ card_id: Union[str, int] = Field(..., description="Numeric required")
21
+ facility_code: int = Field(..., description="Facility Code (required)")
22
+ card_number: int = Field(..., description="Card Number (required)")
23
+ wiegand_format_id: Optional[int] = Field(None, description="Format id (e.g., 0=26bit/H10301, 1=HID37/H10302)")
24
+ wiegand_format: Optional[str] = Field(None, description="Format alias (e.g., '26bit', 'H10301', 'HID37')")
25
+ display_card_id: Optional[str] = Field(None, description="Override display (must be 'FC-ID', e.g., '12-3456')")
26
+ card_type_type: Optional[int] = None
27
+ assign_to_user_id: Optional[Union[str, int]] = None
28
+ skip_availability_check: Optional[bool] = None
29
+ dry_run: Optional[bool] = None
30
+
31
+ @model_validator(mode="after")
32
+ def validate_wiegand_format(self):
33
+ """Ensure either wiegand_format_id or wiegand_format is provided"""
34
+ if self.wiegand_format_id is None and self.wiegand_format is None:
35
+ raise ValueError("Either wiegand_format_id or wiegand_format must be provided")
36
+ return self
37
+
38
+
39
+ class CreateCardSecureCredentialInput(BaseModel):
40
+ """Input schema for create-card-secure-credential tool"""
41
+ card_id: Union[str, int] = Field(..., description="Card ID")
42
+ card_type_type: Optional[int] = None
43
+ assign_to_user_id: Optional[Union[str, int]] = None
44
+ skip_availability_check: Optional[bool] = None
45
+ dry_run: Optional[bool] = None
46
+
47
+
48
+ class CreateCardAccessOnCardInput(BaseModel):
49
+ """Input schema for create-card-access-on-card tool"""
50
+ card_id: Union[str, int] = Field(..., description="Card ID")
51
+ card_type_type: Optional[int] = None
52
+ assign_to_user_id: Optional[Union[str, int]] = None
53
+ skip_availability_check: Optional[bool] = None
54
+ dry_run: Optional[bool] = None
55
+
56
+
57
+ class CreateCardMobileCSNInput(BaseModel):
58
+ """Input schema for create-card-mobile-csn tool"""
59
+ card_id: Union[str, int] = Field(..., description="Card ID")
60
+ card_type_type: Optional[int] = None
61
+ assign_to_user_id: Optional[Union[str, int]] = None
62
+ isUserPhoto: Optional[bool] = None
63
+ isDepartment: Optional[bool] = None
64
+ isTitle: Optional[bool] = None
65
+ start_datetime: Optional[str] = None
66
+ expiry_datetime: Optional[str] = None
67
+ display_card_id: Optional[str] = None
68
+ skip_availability_check: Optional[bool] = None
69
+ dry_run: Optional[bool] = None
70
+
71
+
72
+ class CreateCardWiegandMobileInput(BaseModel):
73
+ """Input schema for create-card-wiegand-mobile tool"""
74
+ card_id: Union[str, int] = Field(..., description="Card ID")
75
+ facility_code: int = Field(..., description="Facility Code (required)")
76
+ card_number: int = Field(..., description="Card Number (required)")
77
+ wiegand_format_id: Optional[int] = Field(None, description="Format id")
78
+ wiegand_format: Optional[str] = Field(None, description="Format alias")
79
+ display_card_id: Optional[str] = None
80
+ card_type_type: Optional[int] = None
81
+ assign_to_user_id: Optional[Union[str, int]] = None
82
+ skip_availability_check: Optional[bool] = None
83
+ dry_run: Optional[bool] = None
84
+
85
+ @model_validator(mode="after")
86
+ def validate_wiegand_format(self):
87
+ """Ensure either wiegand_format_id or wiegand_format is provided"""
88
+ if self.wiegand_format_id is None and self.wiegand_format is None:
89
+ raise ValueError("Either wiegand_format_id or wiegand_format must be provided")
90
+ return self
91
+
92
+
93
+ class CreateCardQRBarcodeInput(BaseModel):
94
+ """Input schema for create-card-qr-barcode tool"""
95
+ card_id: Union[str, int] = Field(..., description="Card ID")
96
+ card_type_type: Optional[int] = None
97
+ assign_to_user_id: Optional[Union[str, int]] = None
98
+ skip_availability_check: Optional[bool] = None
99
+ dry_run: Optional[bool] = None
100
+
101
+
102
+ class CreateCardBioStar2QRInput(BaseModel):
103
+ """Input schema for create-card-biostar2-qr tool"""
104
+ card_id: Union[str, int] = Field(..., description="Card ID")
105
+ card_type_type: Optional[int] = None
106
+ assign_to_user_id: Optional[Union[str, int]] = None
107
+ skip_availability_check: Optional[bool] = None
108
+ dry_run: Optional[bool] = None
109
+
110
+
111
+ # ============================================================================
112
+ # Supporting Tool Schemas
113
+ # ============================================================================
114
+
115
+ class GetCardTypesInput(BaseModel):
116
+ """Input schema for get-card-types tool"""
117
+ pass # No required fields
118
+
119
+
120
+ class CheckCardAvailabilityInput(BaseModel):
121
+ """Input schema for check-card-availability tool"""
122
+ card_id: Union[str, int] = Field(..., description="Card ID to check")
123
+
124
+
125
+ class GetWiegandFormatPresetsInput(BaseModel):
126
+ """Input schema for get-wiegand-format-presets tool"""
127
+ pass # No required fields
128
+