hyperping 1.3.0__tar.gz → 1.4.1__tar.gz

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 (74) hide show
  1. {hyperping-1.3.0 → hyperping-1.4.1}/CHANGELOG.md +88 -0
  2. {hyperping-1.3.0 → hyperping-1.4.1}/PKG-INFO +38 -1
  3. {hyperping-1.3.0 → hyperping-1.4.1}/README.md +37 -0
  4. {hyperping-1.3.0 → hyperping-1.4.1}/pyproject.toml +1 -1
  5. hyperping-1.4.1/scripts/verify_endpoints.py +166 -0
  6. {hyperping-1.3.0 → hyperping-1.4.1}/src/hyperping/__init__.py +5 -0
  7. {hyperping-1.3.0 → hyperping-1.4.1}/src/hyperping/_async_client.py +0 -8
  8. {hyperping-1.3.0 → hyperping-1.4.1}/src/hyperping/_async_incidents_mixin.py +3 -9
  9. {hyperping-1.3.0 → hyperping-1.4.1}/src/hyperping/_async_maintenance_mixin.py +3 -9
  10. {hyperping-1.3.0 → hyperping-1.4.1}/src/hyperping/_async_monitors_mixin.py +0 -13
  11. {hyperping-1.3.0 → hyperping-1.4.1}/src/hyperping/_async_outages_mixin.py +0 -31
  12. {hyperping-1.3.0 → hyperping-1.4.1}/src/hyperping/_async_statuspages_mixin.py +15 -13
  13. {hyperping-1.3.0 → hyperping-1.4.1}/src/hyperping/_incidents_mixin.py +3 -1
  14. {hyperping-1.3.0 → hyperping-1.4.1}/src/hyperping/_internals.py +1 -4
  15. {hyperping-1.3.0 → hyperping-1.4.1}/src/hyperping/_maintenance_mixin.py +3 -1
  16. hyperping-1.4.1/src/hyperping/_mcp_transport.py +146 -0
  17. {hyperping-1.3.0 → hyperping-1.4.1}/src/hyperping/_monitors_mixin.py +0 -20
  18. {hyperping-1.3.0 → hyperping-1.4.1}/src/hyperping/_outages_mixin.py +0 -55
  19. {hyperping-1.3.0 → hyperping-1.4.1}/src/hyperping/_statuspages_mixin.py +12 -4
  20. {hyperping-1.3.0 → hyperping-1.4.1}/src/hyperping/_utils.py +2 -5
  21. hyperping-1.4.1/src/hyperping/_version.py +1 -0
  22. {hyperping-1.3.0 → hyperping-1.4.1}/src/hyperping/client.py +0 -8
  23. {hyperping-1.3.0 → hyperping-1.4.1}/src/hyperping/endpoints.py +3 -75
  24. hyperping-1.4.1/src/hyperping/mcp_client.py +222 -0
  25. {hyperping-1.3.0 → hyperping-1.4.1}/src/hyperping/models/_monitor_models.py +3 -8
  26. {hyperping-1.3.0 → hyperping-1.4.1}/tests/unit/test_async_client.py +14 -42
  27. {hyperping-1.3.0 → hyperping-1.4.1}/tests/unit/test_async_preexisting.py +3 -9
  28. {hyperping-1.3.0 → hyperping-1.4.1}/tests/unit/test_healthchecks.py +8 -21
  29. {hyperping-1.3.0 → hyperping-1.4.1}/tests/unit/test_incidents.py +1 -3
  30. hyperping-1.4.1/tests/unit/test_mcp_client.py +161 -0
  31. hyperping-1.4.1/tests/unit/test_mcp_transport.py +172 -0
  32. {hyperping-1.3.0 → hyperping-1.4.1}/tests/unit/test_monitors.py +5 -58
  33. {hyperping-1.3.0 → hyperping-1.4.1}/tests/unit/test_outages.py +4 -101
  34. {hyperping-1.3.0 → hyperping-1.4.1}/tests/unit/test_pagination.py +1 -3
  35. {hyperping-1.3.0 → hyperping-1.4.1}/tests/unit/test_statuspages.py +11 -15
  36. hyperping-1.3.0/src/hyperping/_async_integrations_mixin.py +0 -26
  37. hyperping-1.3.0/src/hyperping/_async_observability_mixin.py +0 -65
  38. hyperping-1.3.0/src/hyperping/_async_oncall_mixin.py +0 -53
  39. hyperping-1.3.0/src/hyperping/_async_reporting_mixin.py +0 -39
  40. hyperping-1.3.0/src/hyperping/_integrations_mixin.py +0 -41
  41. hyperping-1.3.0/src/hyperping/_observability_mixin.py +0 -102
  42. hyperping-1.3.0/src/hyperping/_oncall_mixin.py +0 -88
  43. hyperping-1.3.0/src/hyperping/_reporting_mixin.py +0 -71
  44. hyperping-1.3.0/src/hyperping/_version.py +0 -1
  45. hyperping-1.3.0/tests/unit/test_async_new_mixins.py +0 -262
  46. hyperping-1.3.0/tests/unit/test_integrations.py +0 -98
  47. hyperping-1.3.0/tests/unit/test_observability.py +0 -203
  48. hyperping-1.3.0/tests/unit/test_oncall.py +0 -217
  49. hyperping-1.3.0/tests/unit/test_reporting.py +0 -140
  50. {hyperping-1.3.0 → hyperping-1.4.1}/.gitignore +0 -0
  51. {hyperping-1.3.0 → hyperping-1.4.1}/CONTRIBUTING.md +0 -0
  52. {hyperping-1.3.0 → hyperping-1.4.1}/LICENSE +0 -0
  53. {hyperping-1.3.0 → hyperping-1.4.1}/src/hyperping/_async_healthchecks_mixin.py +0 -0
  54. {hyperping-1.3.0 → hyperping-1.4.1}/src/hyperping/_circuit_breaker.py +0 -0
  55. {hyperping-1.3.0 → hyperping-1.4.1}/src/hyperping/_healthchecks_mixin.py +0 -0
  56. {hyperping-1.3.0 → hyperping-1.4.1}/src/hyperping/_monitor_constants.py +0 -0
  57. {hyperping-1.3.0 → hyperping-1.4.1}/src/hyperping/_protocols.py +0 -0
  58. {hyperping-1.3.0 → hyperping-1.4.1}/src/hyperping/exceptions.py +0 -0
  59. {hyperping-1.3.0 → hyperping-1.4.1}/src/hyperping/models/__init__.py +0 -0
  60. {hyperping-1.3.0 → hyperping-1.4.1}/src/hyperping/models/_healthcheck_models.py +0 -0
  61. {hyperping-1.3.0 → hyperping-1.4.1}/src/hyperping/models/_incident_models.py +0 -0
  62. {hyperping-1.3.0 → hyperping-1.4.1}/src/hyperping/models/_integration_models.py +0 -0
  63. {hyperping-1.3.0 → hyperping-1.4.1}/src/hyperping/models/_maintenance_models.py +0 -0
  64. {hyperping-1.3.0 → hyperping-1.4.1}/src/hyperping/models/_observability_models.py +0 -0
  65. {hyperping-1.3.0 → hyperping-1.4.1}/src/hyperping/models/_oncall_models.py +0 -0
  66. {hyperping-1.3.0 → hyperping-1.4.1}/src/hyperping/models/_outage_models.py +0 -0
  67. {hyperping-1.3.0 → hyperping-1.4.1}/src/hyperping/models/_reporting_models.py +0 -0
  68. {hyperping-1.3.0 → hyperping-1.4.1}/src/hyperping/models/_statuspage_models.py +0 -0
  69. {hyperping-1.3.0 → hyperping-1.4.1}/src/hyperping/py.typed +0 -0
  70. {hyperping-1.3.0 → hyperping-1.4.1}/tests/__init__.py +0 -0
  71. {hyperping-1.3.0 → hyperping-1.4.1}/tests/unit/__init__.py +0 -0
  72. {hyperping-1.3.0 → hyperping-1.4.1}/tests/unit/conftest.py +0 -0
  73. {hyperping-1.3.0 → hyperping-1.4.1}/tests/unit/test_maintenance.py +0 -0
  74. {hyperping-1.3.0 → hyperping-1.4.1}/tests/unit/test_sdk_surface.py +0 -0
@@ -5,6 +5,87 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.4.1] - 2026-04-20
9
+
10
+ ### Fixed
11
+
12
+ - HTTP 403 from MCP server now correctly raises `HyperpingAuthError` (was
13
+ `HyperpingAPIError`). The Hyperping MCP server returns 403 for invalid API keys;
14
+ the transport only handled 401. Now matches REST client behavior.
15
+ - `MCP_URL` defined in single location (`endpoints.py`); removed duplicate in
16
+ `_mcp_transport.py`.
17
+ - MCP handshake version string uses `__version__` instead of hardcoded `"1.4.0"`.
18
+
19
+ ## [1.4.0] - 2026-04-19
20
+
21
+ ### Added
22
+
23
+ - **`HyperpingMcpClient`** -- new client for Hyperping MCP server features not available
24
+ via the REST API. Uses JSON-RPC 2.0 over HTTP at `/v1/mcp` with the same Bearer token
25
+ API key. Provides 16 typed methods: `get_status_summary`, `get_monitor_response_time`,
26
+ `get_monitor_mtta`, `get_monitor_mttr`, `get_monitor_anomalies`, `get_monitor_http_logs`,
27
+ `list_recent_alerts`, `list_on_call_schedules`, `get_on_call_schedule`,
28
+ `list_escalation_policies`, `get_escalation_policy`, `list_team_members`,
29
+ `list_integrations`, `get_integration`, `get_outage_timeline`, `search_monitors_by_name`.
30
+ - **`McpTransport`** -- low-level JSON-RPC 2.0 transport with auto-initialization handshake,
31
+ double-parse response extraction, and error mapping to existing SDK exception types.
32
+ - **`MCP_URL`** constant exported from `hyperping` top-level.
33
+ - **Sync outage methods** -- `create_outage`, `delete_outage`, `get_outage`,
34
+ `unacknowledge_outage` added to `OutagesMixin` (were only in async client).
35
+ - **Verification script** -- `scripts/verify_endpoints.py` for testing endpoints against
36
+ the live API.
37
+
38
+ ### Changed
39
+
40
+ - **Maintenance update** uses `model_dump(include=...)` instead of hard-coded field list.
41
+ - **Incident update error handling** -- `add_incident_update` now provides context when
42
+ the POST succeeds but the follow-up GET fails.
43
+
44
+ ### Removed
45
+
46
+ - **Speculative REST methods** -- 12 methods that called nonexistent REST endpoints
47
+ (on-call, alerts, anomalies, integrations, probe logs, response time, MTTA, status
48
+ summary, outage timeline, monitor search) removed from `HyperpingClient` and
49
+ `AsyncHyperpingClient`. These features are MCP-only; use `HyperpingMcpClient` instead.
50
+ - **8 speculative mixin files** (sync + async) deleted.
51
+ - **8 speculative Endpoint enum entries** removed from `endpoints.py`.
52
+
53
+ ### Fixed
54
+
55
+ - HTTP 403 from MCP server now correctly raises `HyperpingAuthError` (was
56
+ `HyperpingAPIError`). Matches REST client behavior.
57
+ - `MCP_URL` defined in single location (`endpoints.py`), not duplicated.
58
+ - MCP handshake version uses `__version__` instead of hardcoded string.
59
+ - `pytest` bumped to 9.0.3 (CVE-2025-71176).
60
+
61
+ ## [1.3.0] - 2026-04-18 [YANKED]
62
+
63
+ v1.3.0 added 18 speculative REST methods for MCP-discovered features (reporting,
64
+ observability, on-call, integrations). All 12 endpoint paths were guessed from MCP
65
+ tool names and **none of them work via the REST API** (verified: 10x 404, 2x 401).
66
+ These features are only accessible through the MCP server (JSON-RPC 2.0). Superseded
67
+ by v1.4.0 which replaces the broken REST methods with a proper `HyperpingMcpClient`.
68
+
69
+ ## [1.2.1] - 2026-04-17
70
+
71
+ ### Fixed
72
+
73
+ - Bump `pytest` to 9.0.3 (CVE-2025-71176).
74
+
75
+ ## [1.2.0] - 2026-04-17
76
+
77
+ ### Added
78
+
79
+ - **Sync outage methods** -- `create_outage`, `delete_outage`, `get_outage`,
80
+ `unacknowledge_outage` added to `OutagesMixin` for feature parity with async client.
81
+
82
+ ### Changed
83
+
84
+ - **Maintenance update** uses `model_dump(include=...)` instead of hard-coded field
85
+ enumeration for robustness.
86
+ - **Incident update error handling** -- `add_incident_update` now provides context when
87
+ the POST succeeds but the follow-up GET fails.
88
+
8
89
  ## [1.1.0] - 2026-04-09
9
90
 
10
91
  ### Added
@@ -30,6 +111,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
30
111
  - **`collect_all_pages` / `collect_all_pages_async`** helpers in `_utils.py` for
31
112
  transparent multi-page result aggregation.
32
113
 
114
+ ## [1.0.1] - 2026-04-05
115
+
116
+ ### Fixed
117
+
118
+ - Fix version string in `pyproject.toml` (was out of sync with `_version.py`).
119
+ - Fix package metadata: incorrect email address in project config.
120
+
33
121
  ## [1.0.0] - 2026-04-05
34
122
 
35
123
  First stable release. The public API is production-ready and covered by semver
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hyperping
3
- Version: 1.3.0
3
+ Version: 1.4.1
4
4
  Summary: Python SDK for the Hyperping uptime monitoring and incident management API
5
5
  Project-URL: Homepage, https://github.com/develeap/hyperping-python
6
6
  Project-URL: Documentation, https://github.com/develeap/hyperping-python#readme
@@ -181,6 +181,43 @@ sub = client.add_subscriber("sp_uuid", "user@example.com")
181
181
  client.remove_subscriber("sp_uuid", sub.id)
182
182
  ```
183
183
 
184
+ ### MCP Client (on-call, alerts, anomalies, integrations)
185
+
186
+ Some Hyperping features are only available via the MCP server (JSON-RPC 2.0),
187
+ not the REST API. Use `HyperpingMcpClient` for these:
188
+
189
+ ```python
190
+ from hyperping import HyperpingMcpClient
191
+
192
+ with HyperpingMcpClient(api_key="sk_...") as mcp:
193
+ # Status & reporting
194
+ summary = mcp.get_status_summary()
195
+ mtta = mcp.get_monitor_mtta("mon_uuid")
196
+ mttr = mcp.get_monitor_mttr("mon_uuid")
197
+ response_time = mcp.get_monitor_response_time("mon_uuid")
198
+
199
+ # On-call & escalation
200
+ schedules = mcp.list_on_call_schedules()
201
+ policies = mcp.list_escalation_policies()
202
+ members = mcp.list_team_members()
203
+
204
+ # Observability
205
+ anomalies = mcp.get_monitor_anomalies("mon_uuid")
206
+ logs = mcp.get_monitor_http_logs("mon_uuid")
207
+ alerts = mcp.list_recent_alerts()
208
+
209
+ # Integrations
210
+ integrations = mcp.list_integrations()
211
+
212
+ # Outage timeline & monitor search
213
+ timeline = mcp.get_outage_timeline("out_uuid")
214
+ results = mcp.search_monitors_by_name("api")
215
+ ```
216
+
217
+ The MCP client uses the same API key as `HyperpingClient`. All methods return
218
+ plain dicts/lists; use the exported Pydantic models (e.g., `OnCallSchedule`,
219
+ `EscalationPolicy`) for validation if needed.
220
+
184
221
  ### Healthchecks
185
222
 
186
223
  ```python
@@ -144,6 +144,43 @@ sub = client.add_subscriber("sp_uuid", "user@example.com")
144
144
  client.remove_subscriber("sp_uuid", sub.id)
145
145
  ```
146
146
 
147
+ ### MCP Client (on-call, alerts, anomalies, integrations)
148
+
149
+ Some Hyperping features are only available via the MCP server (JSON-RPC 2.0),
150
+ not the REST API. Use `HyperpingMcpClient` for these:
151
+
152
+ ```python
153
+ from hyperping import HyperpingMcpClient
154
+
155
+ with HyperpingMcpClient(api_key="sk_...") as mcp:
156
+ # Status & reporting
157
+ summary = mcp.get_status_summary()
158
+ mtta = mcp.get_monitor_mtta("mon_uuid")
159
+ mttr = mcp.get_monitor_mttr("mon_uuid")
160
+ response_time = mcp.get_monitor_response_time("mon_uuid")
161
+
162
+ # On-call & escalation
163
+ schedules = mcp.list_on_call_schedules()
164
+ policies = mcp.list_escalation_policies()
165
+ members = mcp.list_team_members()
166
+
167
+ # Observability
168
+ anomalies = mcp.get_monitor_anomalies("mon_uuid")
169
+ logs = mcp.get_monitor_http_logs("mon_uuid")
170
+ alerts = mcp.list_recent_alerts()
171
+
172
+ # Integrations
173
+ integrations = mcp.list_integrations()
174
+
175
+ # Outage timeline & monitor search
176
+ timeline = mcp.get_outage_timeline("out_uuid")
177
+ results = mcp.search_monitors_by_name("api")
178
+ ```
179
+
180
+ The MCP client uses the same API key as `HyperpingClient`. All methods return
181
+ plain dicts/lists; use the exported Pydantic models (e.g., `OnCallSchedule`,
182
+ `EscalationPolicy`) for validation if needed.
183
+
147
184
  ### Healthchecks
148
185
 
149
186
  ```python
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "hyperping"
7
- version = "1.3.0"
7
+ version = "1.4.1"
8
8
  description = "Python SDK for the Hyperping uptime monitoring and incident management API"
9
9
  readme = {file = "README.md", content-type = "text/markdown"}
10
10
  license = {text = "MIT"}
@@ -0,0 +1,166 @@
1
+ #!/usr/bin/env python3
2
+ """Verify speculative MCP-discovered endpoint paths against the live Hyperping API.
3
+
4
+ Usage:
5
+ export HYPERPING_API_KEY=sk_...
6
+ python scripts/verify_endpoints.py
7
+
8
+ All calls are read-only (GET only). Safe to run repeatedly.
9
+ """
10
+
11
+ import os
12
+ import sys
13
+ from dataclasses import dataclass
14
+
15
+ import httpx
16
+
17
+ API_BASE = "https://api.hyperping.io"
18
+
19
+
20
+ @dataclass
21
+ class Result:
22
+ method: str
23
+ path: str
24
+ status: int
25
+ ok: bool
26
+ snippet: str
27
+
28
+
29
+ def probe(
30
+ client: httpx.Client,
31
+ method_name: str,
32
+ path: str,
33
+ params: dict | None = None,
34
+ ) -> Result:
35
+ """Send a GET request and return a Result."""
36
+ url = f"{API_BASE}{path}"
37
+ try:
38
+ resp = client.get(url, params=params or {})
39
+ body = resp.text[:120].replace("\n", " ")
40
+ return Result(
41
+ method=method_name,
42
+ path=path,
43
+ status=resp.status_code,
44
+ ok=resp.status_code < 400,
45
+ snippet=body,
46
+ )
47
+ except httpx.HTTPError as exc:
48
+ return Result(
49
+ method=method_name,
50
+ path=path,
51
+ status=0,
52
+ ok=False,
53
+ snippet=f"Connection error: {exc}",
54
+ )
55
+
56
+
57
+ def main() -> int:
58
+ api_key = os.environ.get("HYPERPING_API_KEY", "")
59
+ if not api_key:
60
+ print("ERROR: Set HYPERPING_API_KEY environment variable.")
61
+ return 1
62
+
63
+ headers = {"Authorization": f"Bearer {api_key}"}
64
+ client = httpx.Client(headers=headers, timeout=15.0)
65
+
66
+ # -- Fetch real UUIDs for sub-resource endpoints --
67
+ print("Fetching monitor and outage UUIDs for sub-resource tests...")
68
+ monitor_uuid: str | None = None
69
+ outage_uuid: str | None = None
70
+
71
+ resp = client.get(f"{API_BASE}/v1/monitors")
72
+ if resp.status_code == 200:
73
+ data = resp.json()
74
+ monitors = data if isinstance(data, list) else data.get("monitors", [])
75
+ if monitors:
76
+ monitor_uuid = monitors[0].get("uuid") or monitors[0].get("id")
77
+ print(f" Monitor UUID: {monitor_uuid}")
78
+
79
+ resp = client.get(f"{API_BASE}/v2/outages")
80
+ if resp.status_code == 200:
81
+ data = resp.json()
82
+ outages = data if isinstance(data, list) else data.get("outages", [])
83
+ if outages:
84
+ outage_uuid = outages[0].get("uuid") or outages[0].get("id")
85
+ print(f" Outage UUID: {outage_uuid}")
86
+
87
+ print()
88
+
89
+ # -- Define all speculative endpoints to verify --
90
+ checks: list[tuple[str, str, dict | None]] = [
91
+ # Endpoint enum speculative paths
92
+ ("get_status_summary", "/v2/status-summary", None),
93
+ ("list_recent_alerts", "/v2/alerts", None),
94
+ ("list_on_call_schedules", "/v2/on-call-schedules", None),
95
+ ("list_escalation_policies", "/v2/escalation-policies", None),
96
+ ("list_team_members", "/v2/team-members", None),
97
+ ("list_integrations", "/v2/integrations", None),
98
+ # Monitor search
99
+ ("search_monitors", "/v1/monitors/search", {"query": "test"}),
100
+ ]
101
+
102
+ # Sub-resource paths needing a real UUID
103
+ if monitor_uuid:
104
+ checks.extend([
105
+ (
106
+ "get_monitor_response_time",
107
+ f"/v2/reporting/response-time/{monitor_uuid}",
108
+ {"period": "24h"},
109
+ ),
110
+ (
111
+ "get_monitor_mtta",
112
+ f"/v2/reporting/mtta/{monitor_uuid}",
113
+ {"period": "30d"},
114
+ ),
115
+ (
116
+ "get_monitor_anomalies",
117
+ f"/v1/monitors/{monitor_uuid}/anomalies",
118
+ None,
119
+ ),
120
+ (
121
+ "get_monitor_http_logs",
122
+ f"/v1/monitors/{monitor_uuid}/http-logs",
123
+ {"page": "0", "limit": "5"},
124
+ ),
125
+ ])
126
+ else:
127
+ print("WARNING: No monitors found; skipping monitor sub-resource checks.\n")
128
+
129
+ if outage_uuid:
130
+ checks.append((
131
+ "get_outage_timeline",
132
+ f"/v2/outages/{outage_uuid}/timeline",
133
+ None,
134
+ ))
135
+ else:
136
+ print("WARNING: No outages found; skipping outage sub-resource checks.\n")
137
+
138
+ # -- Run probes --
139
+ results: list[Result] = []
140
+ for method_name, path, params in checks:
141
+ r = probe(client, method_name, path, params)
142
+ results.append(r)
143
+
144
+ client.close()
145
+
146
+ # -- Print results table --
147
+ print(f"{'Method':<30} {'Path':<50} {'Status':<8} {'Result'}")
148
+ print("-" * 120)
149
+ for r in results:
150
+ tag = "OK" if r.ok else ("404" if r.status == 404 else "ERROR")
151
+ print(f"{r.method:<30} {r.path:<50} {r.status:<8} {tag}")
152
+ if not r.ok:
153
+ print(f" {r.snippet}")
154
+
155
+ # -- Summary --
156
+ ok_count = sum(1 for r in results if r.ok)
157
+ fail_count = sum(1 for r in results if not r.ok)
158
+ not_found = sum(1 for r in results if r.status == 404)
159
+ print()
160
+ print(f"Total: {len(results)} | OK: {ok_count} | 404: {not_found} | Other errors: {fail_count - not_found}")
161
+
162
+ return 1 if fail_count > 0 else 0
163
+
164
+
165
+ if __name__ == "__main__":
166
+ sys.exit(main())
@@ -25,6 +25,7 @@ from hyperping.client import (
25
25
  )
26
26
  from hyperping.endpoints import (
27
27
  API_BASE,
28
+ MCP_URL,
28
29
  APIVersion,
29
30
  Endpoint,
30
31
  )
@@ -35,6 +36,7 @@ from hyperping.exceptions import (
35
36
  HyperpingRateLimitError,
36
37
  HyperpingValidationError,
37
38
  )
39
+ from hyperping.mcp_client import HyperpingMcpClient
38
40
  from hyperping.models import (
39
41
  DEFAULT_REGIONS,
40
42
  AddIncidentUpdateRequest,
@@ -91,6 +93,9 @@ __all__ = [
91
93
  # Clients
92
94
  "AsyncHyperpingClient",
93
95
  "HyperpingClient",
96
+ "HyperpingMcpClient",
97
+ # MCP
98
+ "MCP_URL",
94
99
  # Configuration
95
100
  "RetryConfig",
96
101
  "CircuitBreakerConfig",
@@ -21,13 +21,9 @@ from pydantic import SecretStr
21
21
 
22
22
  from hyperping._async_healthchecks_mixin import AsyncHealthchecksMixin
23
23
  from hyperping._async_incidents_mixin import AsyncIncidentsMixin
24
- from hyperping._async_integrations_mixin import AsyncIntegrationsMixin
25
24
  from hyperping._async_maintenance_mixin import AsyncMaintenanceMixin
26
25
  from hyperping._async_monitors_mixin import AsyncMonitorsMixin
27
- from hyperping._async_observability_mixin import AsyncObservabilityMixin
28
- from hyperping._async_oncall_mixin import AsyncOnCallMixin
29
26
  from hyperping._async_outages_mixin import AsyncOutagesMixin
30
- from hyperping._async_reporting_mixin import AsyncReportingMixin
31
27
  from hyperping._async_statuspages_mixin import AsyncStatusPagesMixin
32
28
  from hyperping._circuit_breaker import (
33
29
  CircuitBreaker,
@@ -52,10 +48,6 @@ class AsyncHyperpingClient(
52
48
  AsyncOutagesMixin,
53
49
  AsyncStatusPagesMixin,
54
50
  AsyncHealthchecksMixin,
55
- AsyncReportingMixin,
56
- AsyncObservabilityMixin,
57
- AsyncOnCallMixin,
58
- AsyncIntegrationsMixin,
59
51
  ):
60
52
  """Async client for interacting with the Hyperping API.
61
53
 
@@ -45,9 +45,7 @@ class AsyncIncidentsMixin(_AsyncClientProtocol):
45
45
  if status:
46
46
  params["status"] = status
47
47
 
48
- response = await self._request(
49
- "GET", Endpoint.INCIDENTS, params=params or None
50
- )
48
+ response = await self._request("GET", Endpoint.INCIDENTS, params=params or None)
51
49
  return parse_list(unwrap_list(response, "incidents"), Incident, "incident")
52
50
 
53
51
  async def get_incident(self, incident_id: str) -> Incident:
@@ -114,9 +112,7 @@ class AsyncIncidentsMixin(_AsyncClientProtocol):
114
112
  validate_id(incident_id, "incident_id")
115
113
  payload = update.model_dump(exclude_none=True, by_alias=True)
116
114
  response = expect_dict(
117
- await self._request(
118
- "PUT", f"{Endpoint.INCIDENTS}/{incident_id}", json=payload
119
- ),
115
+ await self._request("PUT", f"{Endpoint.INCIDENTS}/{incident_id}", json=payload),
120
116
  "update_incident",
121
117
  )
122
118
  return Incident.model_validate(response)
@@ -145,9 +141,7 @@ class AsyncIncidentsMixin(_AsyncClientProtocol):
145
141
  await self._request("POST", url, json=payload)
146
142
  return await self.get_incident(incident_id)
147
143
 
148
- async def resolve_incident(
149
- self, incident_id: str, message: str | None = None
150
- ) -> Incident:
144
+ async def resolve_incident(self, incident_id: str, message: str | None = None) -> Incident:
151
145
  """Resolve an incident.
152
146
 
153
147
  Args:
@@ -41,9 +41,7 @@ class AsyncMaintenanceMixin(_AsyncClientProtocol):
41
41
  if status:
42
42
  params["status"] = status
43
43
 
44
- response = await self._request(
45
- "GET", Endpoint.MAINTENANCE, params=params or None
46
- )
44
+ response = await self._request("GET", Endpoint.MAINTENANCE, params=params or None)
47
45
 
48
46
  raw = unwrap_list(response, "maintenanceWindows")
49
47
  if not raw and isinstance(response, dict) and "maintenance" in response:
@@ -64,9 +62,7 @@ class AsyncMaintenanceMixin(_AsyncClientProtocol):
64
62
  HyperpingNotFoundError: If maintenance not found
65
63
  """
66
64
  validate_id(maintenance_id, "maintenance_id")
67
- response = await self._request(
68
- "GET", f"{Endpoint.MAINTENANCE}/{maintenance_id}"
69
- )
65
+ response = await self._request("GET", f"{Endpoint.MAINTENANCE}/{maintenance_id}")
70
66
  return Maintenance.model_validate(expect_dict(response, "get_maintenance"))
71
67
 
72
68
  async def create_maintenance(self, maintenance: MaintenanceCreate) -> Maintenance:
@@ -127,9 +123,7 @@ class AsyncMaintenanceMixin(_AsyncClientProtocol):
127
123
  payload.update(partial)
128
124
 
129
125
  response = expect_dict(
130
- await self._request(
131
- "PUT", f"{Endpoint.MAINTENANCE}/{maintenance_id}", json=payload
132
- ),
126
+ await self._request("PUT", f"{Endpoint.MAINTENANCE}/{maintenance_id}", json=payload),
133
127
  "update_maintenance",
134
128
  )
135
129
  return Maintenance.model_validate(response)
@@ -187,16 +187,3 @@ class AsyncMonitorsMixin(_AsyncClientProtocol):
187
187
  if r.uuid == monitor_id:
188
188
  return r
189
189
  raise HyperpingNotFoundError(f"No report found for monitor: {monitor_id}")
190
-
191
- async def search_monitors_by_name(self, query: str) -> list[Monitor]:
192
- """Search monitors by name (case-insensitive substring match)."""
193
- if not query:
194
- return []
195
- try:
196
- result = await self._request(
197
- "GET", f"{Endpoint.MONITORS}/search", params={"query": query}
198
- )
199
- except HyperpingNotFoundError:
200
- return []
201
- items = result if isinstance(result, list) else []
202
- return parse_list(items, Monitor, "monitor")
@@ -19,7 +19,6 @@ from hyperping._utils import (
19
19
  from hyperping.endpoints import Endpoint
20
20
  from hyperping.exceptions import HyperpingNotFoundError
21
21
  from hyperping.models import Outage, OutageAction
22
- from hyperping.models._outage_models import OutageTimeline, OutageTimelineEvent
23
22
 
24
23
  logger = logging.getLogger(__name__)
25
24
 
@@ -206,33 +205,3 @@ class AsyncOutagesMixin(_AsyncClientProtocol):
206
205
  validate_id(outage_id, "outage_id")
207
206
  result = await self._request("GET", f"{Endpoint.OUTAGES}/{outage_id}")
208
207
  return Outage.model_validate(expect_dict(result, "get_outage"))
209
-
210
- async def get_outage_timeline(self, outage_id: str) -> OutageTimeline:
211
- """Get the lifecycle timeline for an outage."""
212
- validate_id(outage_id, "outage_id")
213
- result = await self._request("GET", f"{Endpoint.OUTAGES}/{outage_id}/timeline")
214
- data = expect_dict(result, "get_outage_timeline")
215
- raw_events = data.get("events", [])
216
- events = parse_list(raw_events, OutageTimelineEvent, "timeline_event")
217
- return OutageTimeline.model_validate({"outageUuid": outage_id, "events": events})
218
-
219
- async def get_monitor_outages(
220
- self,
221
- monitor_uuid: str,
222
- page: int | None = None,
223
- status: str = "all",
224
- ) -> list[Outage]:
225
- """Get outages scoped to a single monitor."""
226
- validate_id(monitor_uuid, "monitor_uuid")
227
- params: dict[str, Any] = {
228
- "monitor_uuid": monitor_uuid,
229
- "status": status,
230
- }
231
- if page is not None:
232
- params["page"] = page
233
- try:
234
- result = await self._request("GET", Endpoint.OUTAGES, params=params)
235
- except HyperpingNotFoundError:
236
- return []
237
- items = result if isinstance(result, list) else []
238
- return parse_list(items, Outage, "outage")
@@ -69,8 +69,12 @@ class AsyncStatusPagesMixin(_AsyncClientProtocol):
69
69
 
70
70
  try:
71
71
  return await collect_all_pages_async(
72
- self._request, Endpoint.STATUSPAGES, "statuspages",
73
- params or None, StatusPage, "status page",
72
+ self._request,
73
+ Endpoint.STATUSPAGES,
74
+ "statuspages",
75
+ params or None,
76
+ StatusPage,
77
+ "status page",
74
78
  )
75
79
  except HyperpingNotFoundError:
76
80
  logger.debug("Status pages endpoint not available (404)")
@@ -131,9 +135,7 @@ class AsyncStatusPagesMixin(_AsyncClientProtocol):
131
135
  validate_id(status_page_id, "status_page_id")
132
136
  payload = update.model_dump(exclude_none=True, by_alias=True)
133
137
  response = expect_dict(
134
- await self._request(
135
- "PUT", f"{Endpoint.STATUSPAGES}/{status_page_id}", json=payload
136
- ),
138
+ await self._request("PUT", f"{Endpoint.STATUSPAGES}/{status_page_id}", json=payload),
137
139
  "update_status_page",
138
140
  )
139
141
  return StatusPage.model_validate(response)
@@ -189,13 +191,15 @@ class AsyncStatusPagesMixin(_AsyncClientProtocol):
189
191
  )
190
192
 
191
193
  return await collect_all_pages_async(
192
- self._request, endpoint, "subscribers",
193
- params or None, StatusPageSubscriber, "subscriber",
194
+ self._request,
195
+ endpoint,
196
+ "subscribers",
197
+ params or None,
198
+ StatusPageSubscriber,
199
+ "subscriber",
194
200
  )
195
201
 
196
- async def add_subscriber(
197
- self, status_page_id: str, email: str
198
- ) -> StatusPageSubscriber:
202
+ async def add_subscriber(self, status_page_id: str, email: str) -> StatusPageSubscriber:
199
203
  """Add a subscriber to a status page.
200
204
 
201
205
  Args:
@@ -225,9 +229,7 @@ class AsyncStatusPagesMixin(_AsyncClientProtocol):
225
229
  )
226
230
  return StatusPageSubscriber.model_validate(response)
227
231
 
228
- async def remove_subscriber(
229
- self, status_page_id: str, subscriber_id: str
230
- ) -> None:
232
+ async def remove_subscriber(self, status_page_id: str, subscriber_id: str) -> None:
231
233
  """Remove a subscriber from a status page.
232
234
 
233
235
  Args:
@@ -48,7 +48,9 @@ class IncidentsMixin(_ClientProtocol):
48
48
  params["status"] = status
49
49
 
50
50
  response = self._request(
51
- "GET", Endpoint.INCIDENTS, params=params or None # M20
51
+ "GET",
52
+ Endpoint.INCIDENTS,
53
+ params=params or None, # M20
52
54
  )
53
55
  return parse_list(unwrap_list(response, "incidents"), Incident, "incident")
54
56
 
@@ -28,7 +28,4 @@ def sanitize_for_log(data: dict[str, Any] | None) -> dict[str, Any] | None:
28
28
  """
29
29
  if data is None:
30
30
  return None
31
- return {
32
- k: "[REDACTED]" if k.lower() in _SENSITIVE_LOG_KEYS else v
33
- for k, v in data.items()
34
- }
31
+ return {k: "[REDACTED]" if k.lower() in _SENSITIVE_LOG_KEYS else v for k, v in data.items()}
@@ -43,7 +43,9 @@ class MaintenanceMixin(_ClientProtocol):
43
43
  params["status"] = status
44
44
 
45
45
  response = self._request(
46
- "GET", Endpoint.MAINTENANCE, params=params or None # M20
46
+ "GET",
47
+ Endpoint.MAINTENANCE,
48
+ params=params or None, # M20
47
49
  )
48
50
 
49
51
  # API returns {"maintenanceWindows": [...]} as of current version