mcp-eregistrations-bpa 0.8.5__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.
Potentially problematic release.
This version of mcp-eregistrations-bpa might be problematic. Click here for more details.
- mcp_eregistrations_bpa/__init__.py +121 -0
- mcp_eregistrations_bpa/__main__.py +6 -0
- mcp_eregistrations_bpa/arazzo/__init__.py +21 -0
- mcp_eregistrations_bpa/arazzo/expression.py +379 -0
- mcp_eregistrations_bpa/audit/__init__.py +56 -0
- mcp_eregistrations_bpa/audit/context.py +66 -0
- mcp_eregistrations_bpa/audit/logger.py +236 -0
- mcp_eregistrations_bpa/audit/models.py +131 -0
- mcp_eregistrations_bpa/auth/__init__.py +64 -0
- mcp_eregistrations_bpa/auth/callback.py +391 -0
- mcp_eregistrations_bpa/auth/cas.py +409 -0
- mcp_eregistrations_bpa/auth/oidc.py +252 -0
- mcp_eregistrations_bpa/auth/permissions.py +162 -0
- mcp_eregistrations_bpa/auth/token_manager.py +348 -0
- mcp_eregistrations_bpa/bpa_client/__init__.py +84 -0
- mcp_eregistrations_bpa/bpa_client/client.py +740 -0
- mcp_eregistrations_bpa/bpa_client/endpoints.py +193 -0
- mcp_eregistrations_bpa/bpa_client/errors.py +276 -0
- mcp_eregistrations_bpa/bpa_client/models.py +203 -0
- mcp_eregistrations_bpa/config.py +349 -0
- mcp_eregistrations_bpa/db/__init__.py +21 -0
- mcp_eregistrations_bpa/db/connection.py +64 -0
- mcp_eregistrations_bpa/db/migrations.py +168 -0
- mcp_eregistrations_bpa/exceptions.py +39 -0
- mcp_eregistrations_bpa/py.typed +0 -0
- mcp_eregistrations_bpa/rollback/__init__.py +19 -0
- mcp_eregistrations_bpa/rollback/manager.py +616 -0
- mcp_eregistrations_bpa/server.py +152 -0
- mcp_eregistrations_bpa/tools/__init__.py +372 -0
- mcp_eregistrations_bpa/tools/actions.py +155 -0
- mcp_eregistrations_bpa/tools/analysis.py +352 -0
- mcp_eregistrations_bpa/tools/audit.py +399 -0
- mcp_eregistrations_bpa/tools/behaviours.py +1042 -0
- mcp_eregistrations_bpa/tools/bots.py +627 -0
- mcp_eregistrations_bpa/tools/classifications.py +575 -0
- mcp_eregistrations_bpa/tools/costs.py +765 -0
- mcp_eregistrations_bpa/tools/debug_strategies.py +351 -0
- mcp_eregistrations_bpa/tools/debugger.py +1230 -0
- mcp_eregistrations_bpa/tools/determinants.py +2235 -0
- mcp_eregistrations_bpa/tools/document_requirements.py +670 -0
- mcp_eregistrations_bpa/tools/export.py +899 -0
- mcp_eregistrations_bpa/tools/fields.py +162 -0
- mcp_eregistrations_bpa/tools/form_errors.py +36 -0
- mcp_eregistrations_bpa/tools/formio_helpers.py +971 -0
- mcp_eregistrations_bpa/tools/forms.py +1269 -0
- mcp_eregistrations_bpa/tools/jsonlogic_builder.py +466 -0
- mcp_eregistrations_bpa/tools/large_response.py +163 -0
- mcp_eregistrations_bpa/tools/messages.py +523 -0
- mcp_eregistrations_bpa/tools/notifications.py +241 -0
- mcp_eregistrations_bpa/tools/registration_institutions.py +680 -0
- mcp_eregistrations_bpa/tools/registrations.py +897 -0
- mcp_eregistrations_bpa/tools/role_status.py +447 -0
- mcp_eregistrations_bpa/tools/role_units.py +400 -0
- mcp_eregistrations_bpa/tools/roles.py +1236 -0
- mcp_eregistrations_bpa/tools/rollback.py +335 -0
- mcp_eregistrations_bpa/tools/services.py +674 -0
- mcp_eregistrations_bpa/tools/workflows.py +2487 -0
- mcp_eregistrations_bpa/tools/yaml_transformer.py +991 -0
- mcp_eregistrations_bpa/workflows/__init__.py +28 -0
- mcp_eregistrations_bpa/workflows/loader.py +440 -0
- mcp_eregistrations_bpa/workflows/models.py +336 -0
- mcp_eregistrations_bpa-0.8.5.dist-info/METADATA +965 -0
- mcp_eregistrations_bpa-0.8.5.dist-info/RECORD +66 -0
- mcp_eregistrations_bpa-0.8.5.dist-info/WHEEL +4 -0
- mcp_eregistrations_bpa-0.8.5.dist-info/entry_points.txt +2 -0
- mcp_eregistrations_bpa-0.8.5.dist-info/licenses/LICENSE +86 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
"""MCP tools for BPA notification operations.
|
|
2
|
+
|
|
3
|
+
This module provides tools for listing and creating BPA notifications
|
|
4
|
+
(email/SMS alerts configured for service events).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from mcp.server.fastmcp.exceptions import ToolError
|
|
12
|
+
|
|
13
|
+
from mcp_eregistrations_bpa.audit.context import (
|
|
14
|
+
NotAuthenticatedError,
|
|
15
|
+
get_current_user_email,
|
|
16
|
+
)
|
|
17
|
+
from mcp_eregistrations_bpa.audit.logger import AuditLogger
|
|
18
|
+
from mcp_eregistrations_bpa.bpa_client import BPAClient
|
|
19
|
+
from mcp_eregistrations_bpa.bpa_client.errors import (
|
|
20
|
+
BPAClientError,
|
|
21
|
+
BPANotFoundError,
|
|
22
|
+
translate_error,
|
|
23
|
+
)
|
|
24
|
+
from mcp_eregistrations_bpa.tools.large_response import large_response_handler
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
"notification_list",
|
|
28
|
+
"notification_create",
|
|
29
|
+
"register_notification_tools",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _transform_notification(notification: dict[str, Any]) -> dict[str, Any]:
|
|
34
|
+
"""Transform notification to snake_case format."""
|
|
35
|
+
return {
|
|
36
|
+
"id": notification.get("id"),
|
|
37
|
+
"name": notification.get("name") or notification.get("title"),
|
|
38
|
+
"type": notification.get("type") or notification.get("notificationType"),
|
|
39
|
+
"trigger": notification.get("trigger") or notification.get("triggerEvent"),
|
|
40
|
+
"enabled": notification.get("enabled", True),
|
|
41
|
+
"template": notification.get("template") or notification.get("body"),
|
|
42
|
+
"subject": notification.get("subject"),
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@large_response_handler(
|
|
47
|
+
threshold_bytes=50 * 1024, # 50KB threshold for list tools
|
|
48
|
+
navigation={
|
|
49
|
+
"list_all": "jq '.notifications'",
|
|
50
|
+
"find_by_type": "jq '.notifications[] | select(.type == \"email\")'",
|
|
51
|
+
"find_by_trigger": "jq '.notifications[] | select(.trigger == \"on_submit\")'",
|
|
52
|
+
"enabled_only": "jq '.notifications[] | select(.enabled == true)'",
|
|
53
|
+
},
|
|
54
|
+
)
|
|
55
|
+
async def notification_list(
|
|
56
|
+
service_id: str | int,
|
|
57
|
+
limit: int = 50,
|
|
58
|
+
offset: int = 0,
|
|
59
|
+
) -> dict[str, Any]:
|
|
60
|
+
"""List notifications configured for a service.
|
|
61
|
+
|
|
62
|
+
Large responses (>50KB) are saved to file with navigation hints.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
service_id: The service ID to list notifications for.
|
|
66
|
+
limit: Maximum to return (default: 50).
|
|
67
|
+
offset: Skip count (default: 0).
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
dict with notifications, total, has_more, service_id.
|
|
71
|
+
"""
|
|
72
|
+
# Normalize pagination parameters
|
|
73
|
+
if limit <= 0:
|
|
74
|
+
limit = 50
|
|
75
|
+
if offset < 0:
|
|
76
|
+
offset = 0
|
|
77
|
+
|
|
78
|
+
try:
|
|
79
|
+
async with BPAClient() as client:
|
|
80
|
+
try:
|
|
81
|
+
notifications_data = await client.get_list(
|
|
82
|
+
"/service/{service_id}/notifications",
|
|
83
|
+
path_params={"service_id": service_id},
|
|
84
|
+
resource_type="notification",
|
|
85
|
+
)
|
|
86
|
+
except BPANotFoundError:
|
|
87
|
+
raise ToolError(
|
|
88
|
+
f"Service '{service_id}' not found. "
|
|
89
|
+
"Use 'service_list' to see available services."
|
|
90
|
+
)
|
|
91
|
+
except ToolError:
|
|
92
|
+
raise
|
|
93
|
+
except BPAClientError as e:
|
|
94
|
+
raise translate_error(e, resource_type="notification")
|
|
95
|
+
|
|
96
|
+
# Transform to consistent output format
|
|
97
|
+
all_notifications = [_transform_notification(n) for n in notifications_data]
|
|
98
|
+
|
|
99
|
+
# Sort by name for consistent ordering
|
|
100
|
+
all_notifications.sort(key=lambda n: n.get("name") or "")
|
|
101
|
+
|
|
102
|
+
# Calculate total before pagination
|
|
103
|
+
total = len(all_notifications)
|
|
104
|
+
|
|
105
|
+
# Apply pagination
|
|
106
|
+
paginated = all_notifications[offset : offset + limit]
|
|
107
|
+
|
|
108
|
+
# Calculate has_more
|
|
109
|
+
has_more = (offset + limit) < total
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
"notifications": paginated,
|
|
113
|
+
"total": total,
|
|
114
|
+
"has_more": has_more,
|
|
115
|
+
"service_id": str(service_id),
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
async def notification_create(
|
|
120
|
+
service_id: str | int,
|
|
121
|
+
name: str,
|
|
122
|
+
notification_type: str,
|
|
123
|
+
trigger: str,
|
|
124
|
+
template: str | None = None,
|
|
125
|
+
subject: str | None = None,
|
|
126
|
+
enabled: bool = True,
|
|
127
|
+
) -> dict[str, Any]:
|
|
128
|
+
"""Create a notification for a service. Audited write operation.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
service_id: The service ID to create the notification for.
|
|
132
|
+
name: The notification name.
|
|
133
|
+
notification_type: Type of notification (email, sms).
|
|
134
|
+
trigger: Event that triggers the notification (on_submit, on_approve, etc.).
|
|
135
|
+
template: The notification body template (default: None).
|
|
136
|
+
subject: The email subject (default: None).
|
|
137
|
+
enabled: Whether the notification is enabled (default: True).
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
dict with id, name, type, trigger, enabled, audit_id.
|
|
141
|
+
"""
|
|
142
|
+
# Pre-flight validation
|
|
143
|
+
if not name or not name.strip():
|
|
144
|
+
raise ToolError("Notification name is required.")
|
|
145
|
+
if not notification_type or not notification_type.strip():
|
|
146
|
+
raise ToolError("Notification type is required (e.g., 'email', 'sms').")
|
|
147
|
+
if not trigger or not trigger.strip():
|
|
148
|
+
raise ToolError(
|
|
149
|
+
"Notification trigger is required (e.g., 'on_submit', 'on_approve')."
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
# Get authenticated user for audit
|
|
153
|
+
try:
|
|
154
|
+
user_email = get_current_user_email()
|
|
155
|
+
except NotAuthenticatedError as e:
|
|
156
|
+
raise ToolError(str(e))
|
|
157
|
+
|
|
158
|
+
# Build payload
|
|
159
|
+
payload: dict[str, Any] = {
|
|
160
|
+
"name": name.strip(),
|
|
161
|
+
"type": notification_type.strip(),
|
|
162
|
+
"trigger": trigger.strip(),
|
|
163
|
+
"enabled": enabled,
|
|
164
|
+
}
|
|
165
|
+
if template:
|
|
166
|
+
payload["template"] = template
|
|
167
|
+
if subject:
|
|
168
|
+
payload["subject"] = subject
|
|
169
|
+
|
|
170
|
+
try:
|
|
171
|
+
async with BPAClient() as client:
|
|
172
|
+
# Create audit record BEFORE API call
|
|
173
|
+
audit_logger = AuditLogger()
|
|
174
|
+
audit_id = await audit_logger.record_pending(
|
|
175
|
+
user_email=user_email,
|
|
176
|
+
operation_type="create",
|
|
177
|
+
object_type="notification",
|
|
178
|
+
params={"service_id": str(service_id), **payload},
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
try:
|
|
182
|
+
notification_data = await client.post(
|
|
183
|
+
"/service/{service_id}/notification",
|
|
184
|
+
path_params={"service_id": service_id},
|
|
185
|
+
json=payload,
|
|
186
|
+
resource_type="notification",
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
# Save rollback state (for create, save ID to enable deletion)
|
|
190
|
+
created_id = notification_data.get("id")
|
|
191
|
+
await audit_logger.save_rollback_state(
|
|
192
|
+
audit_id=audit_id,
|
|
193
|
+
object_type="notification",
|
|
194
|
+
object_id=str(created_id),
|
|
195
|
+
previous_state={
|
|
196
|
+
"id": created_id,
|
|
197
|
+
"service_id": str(service_id),
|
|
198
|
+
"name": notification_data.get("name"),
|
|
199
|
+
"_operation": "create",
|
|
200
|
+
},
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
await audit_logger.mark_success(
|
|
204
|
+
audit_id=audit_id,
|
|
205
|
+
result={"id": created_id, "name": notification_data.get("name")},
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
except BPANotFoundError:
|
|
209
|
+
await audit_logger.mark_failed(
|
|
210
|
+
audit_id=audit_id,
|
|
211
|
+
error_message=f"Service '{service_id}' not found",
|
|
212
|
+
)
|
|
213
|
+
raise ToolError(
|
|
214
|
+
f"Service '{service_id}' not found. "
|
|
215
|
+
"Use 'service_list' to see available services."
|
|
216
|
+
)
|
|
217
|
+
except BPAClientError as e:
|
|
218
|
+
await audit_logger.mark_failed(audit_id=audit_id, error_message=str(e))
|
|
219
|
+
raise translate_error(e, resource_type="notification")
|
|
220
|
+
|
|
221
|
+
except ToolError:
|
|
222
|
+
raise
|
|
223
|
+
except BPAClientError as e:
|
|
224
|
+
raise translate_error(e, resource_type="notification")
|
|
225
|
+
|
|
226
|
+
# Transform response
|
|
227
|
+
result = _transform_notification(notification_data)
|
|
228
|
+
result["audit_id"] = audit_id
|
|
229
|
+
result["service_id"] = str(service_id)
|
|
230
|
+
|
|
231
|
+
return result
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def register_notification_tools(mcp: Any) -> None:
|
|
235
|
+
"""Register notification tools with the MCP server.
|
|
236
|
+
|
|
237
|
+
Args:
|
|
238
|
+
mcp: The FastMCP server instance.
|
|
239
|
+
"""
|
|
240
|
+
mcp.tool()(notification_list)
|
|
241
|
+
mcp.tool()(notification_create)
|