devhelm-mcp-server 1.1.0__tar.gz → 1.2.0__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.
- {devhelm_mcp_server-1.1.0 → devhelm_mcp_server-1.2.0}/PKG-INFO +2 -2
- {devhelm_mcp_server-1.1.0 → devhelm_mcp_server-1.2.0}/pyproject.toml +6 -9
- {devhelm_mcp_server-1.1.0 → devhelm_mcp_server-1.2.0}/src/devhelm_mcp/server.py +5 -2
- devhelm_mcp_server-1.2.0/src/devhelm_mcp/tools/dependencies.py +79 -0
- devhelm_mcp_server-1.2.0/src/devhelm_mcp/tools/services.py +182 -0
- devhelm_mcp_server-1.2.0/tests/test_services.py +412 -0
- {devhelm_mcp_server-1.1.0 → devhelm_mcp_server-1.2.0}/tests/test_tools.py +14 -0
- {devhelm_mcp_server-1.1.0 → devhelm_mcp_server-1.2.0}/uv.lock +5 -5
- devhelm_mcp_server-1.1.0/src/devhelm_mcp/tools/dependencies.py +0 -43
- {devhelm_mcp_server-1.1.0 → devhelm_mcp_server-1.2.0}/.github/workflows/ci.yml +0 -0
- {devhelm_mcp_server-1.1.0 → devhelm_mcp_server-1.2.0}/.github/workflows/release.yml +0 -0
- {devhelm_mcp_server-1.1.0 → devhelm_mcp_server-1.2.0}/.github/workflows/spec-check.yml +0 -0
- {devhelm_mcp_server-1.1.0 → devhelm_mcp_server-1.2.0}/.gitignore +0 -0
- {devhelm_mcp_server-1.1.0 → devhelm_mcp_server-1.2.0}/CHANGELOG.md +0 -0
- {devhelm_mcp_server-1.1.0 → devhelm_mcp_server-1.2.0}/LICENSE +0 -0
- {devhelm_mcp_server-1.1.0 → devhelm_mcp_server-1.2.0}/Makefile +0 -0
- {devhelm_mcp_server-1.1.0 → devhelm_mcp_server-1.2.0}/README.md +0 -0
- {devhelm_mcp_server-1.1.0 → devhelm_mcp_server-1.2.0}/docker/Dockerfile +0 -0
- {devhelm_mcp_server-1.1.0 → devhelm_mcp_server-1.2.0}/docs/openapi/monitoring-api.json +0 -0
- {devhelm_mcp_server-1.1.0 → devhelm_mcp_server-1.2.0}/scripts/regen-from.sh +0 -0
- {devhelm_mcp_server-1.1.0 → devhelm_mcp_server-1.2.0}/scripts/release.sh +0 -0
- {devhelm_mcp_server-1.1.0 → devhelm_mcp_server-1.2.0}/server.json +0 -0
- {devhelm_mcp_server-1.1.0 → devhelm_mcp_server-1.2.0}/src/devhelm_mcp/__init__.py +0 -0
- {devhelm_mcp_server-1.1.0 → devhelm_mcp_server-1.2.0}/src/devhelm_mcp/client.py +0 -0
- {devhelm_mcp_server-1.1.0 → devhelm_mcp_server-1.2.0}/src/devhelm_mcp/tools/__init__.py +0 -0
- {devhelm_mcp_server-1.1.0 → devhelm_mcp_server-1.2.0}/src/devhelm_mcp/tools/alert_channels.py +0 -0
- {devhelm_mcp_server-1.1.0 → devhelm_mcp_server-1.2.0}/src/devhelm_mcp/tools/api_keys.py +0 -0
- {devhelm_mcp_server-1.1.0 → devhelm_mcp_server-1.2.0}/src/devhelm_mcp/tools/deploy_lock.py +0 -0
- {devhelm_mcp_server-1.1.0 → devhelm_mcp_server-1.2.0}/src/devhelm_mcp/tools/environments.py +0 -0
- {devhelm_mcp_server-1.1.0 → devhelm_mcp_server-1.2.0}/src/devhelm_mcp/tools/forensics.py +0 -0
- {devhelm_mcp_server-1.1.0 → devhelm_mcp_server-1.2.0}/src/devhelm_mcp/tools/incidents.py +0 -0
- {devhelm_mcp_server-1.1.0 → devhelm_mcp_server-1.2.0}/src/devhelm_mcp/tools/maintenance_windows.py +0 -0
- {devhelm_mcp_server-1.1.0 → devhelm_mcp_server-1.2.0}/src/devhelm_mcp/tools/monitors.py +0 -0
- {devhelm_mcp_server-1.1.0 → devhelm_mcp_server-1.2.0}/src/devhelm_mcp/tools/notification_policies.py +0 -0
- {devhelm_mcp_server-1.1.0 → devhelm_mcp_server-1.2.0}/src/devhelm_mcp/tools/resource_groups.py +0 -0
- {devhelm_mcp_server-1.1.0 → devhelm_mcp_server-1.2.0}/src/devhelm_mcp/tools/secrets.py +0 -0
- {devhelm_mcp_server-1.1.0 → devhelm_mcp_server-1.2.0}/src/devhelm_mcp/tools/status.py +0 -0
- {devhelm_mcp_server-1.1.0 → devhelm_mcp_server-1.2.0}/src/devhelm_mcp/tools/status_pages.py +0 -0
- {devhelm_mcp_server-1.1.0 → devhelm_mcp_server-1.2.0}/src/devhelm_mcp/tools/tags.py +0 -0
- {devhelm_mcp_server-1.1.0 → devhelm_mcp_server-1.2.0}/src/devhelm_mcp/tools/webhooks.py +0 -0
- {devhelm_mcp_server-1.1.0 → devhelm_mcp_server-1.2.0}/tests/__init__.py +0 -0
- {devhelm_mcp_server-1.1.0 → devhelm_mcp_server-1.2.0}/tests/run_mcp.py +0 -0
- {devhelm_mcp_server-1.1.0 → devhelm_mcp_server-1.2.0}/tests/test_auth.py +0 -0
- {devhelm_mcp_server-1.1.0 → devhelm_mcp_server-1.2.0}/tests/test_cli.py +0 -0
- {devhelm_mcp_server-1.1.0 → devhelm_mcp_server-1.2.0}/tests/test_client.py +0 -0
- {devhelm_mcp_server-1.1.0 → devhelm_mcp_server-1.2.0}/tests/test_devex_round3_fixes.py +0 -0
- {devhelm_mcp_server-1.1.0 → devhelm_mcp_server-1.2.0}/tests/test_maintenance_windows.py +0 -0
- {devhelm_mcp_server-1.1.0 → devhelm_mcp_server-1.2.0}/tests/test_typed_schemas.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: devhelm-mcp-server
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: DevHelm MCP server — AI agent access to monitors, incidents, alerting, and more
|
|
5
5
|
Project-URL: Homepage, https://devhelm.io
|
|
6
6
|
Project-URL: Repository, https://github.com/devhelmhq/mcp-server
|
|
@@ -18,7 +18,7 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.13
|
|
19
19
|
Classifier: Topic :: Software Development :: Libraries
|
|
20
20
|
Requires-Python: >=3.11
|
|
21
|
-
Requires-Dist: devhelm>=1.
|
|
21
|
+
Requires-Dist: devhelm>=1.3.0
|
|
22
22
|
Requires-Dist: fastmcp>=2.0.0
|
|
23
23
|
Description-Content-Type: text/markdown
|
|
24
24
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "devhelm-mcp-server"
|
|
3
|
-
version = "1.
|
|
3
|
+
version = "1.2.0"
|
|
4
4
|
description = "DevHelm MCP server — AI agent access to monitors, incidents, alerting, and more"
|
|
5
5
|
authors = [{ name = "DevHelm", email = "hello@devhelm.io" }]
|
|
6
6
|
license = "MIT"
|
|
@@ -19,14 +19,11 @@ classifiers = [
|
|
|
19
19
|
]
|
|
20
20
|
|
|
21
21
|
dependencies = [
|
|
22
|
-
# Floor bumped to 1.
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
|
|
27
|
-
# `>=1.0.0` lets us pull in compatible patch/minor bumps without
|
|
28
|
-
# explicit floor bumps each time.
|
|
29
|
-
"devhelm>=1.0.0",
|
|
22
|
+
# Floor bumped to 1.3.0: first SDK release with the full
|
|
23
|
+
# `client.services` catalog resource and the extended
|
|
24
|
+
# `client.dependencies` (component-level track + alert sensitivity)
|
|
25
|
+
# that the services/dependencies tool modules call directly.
|
|
26
|
+
"devhelm>=1.3.0",
|
|
30
27
|
"fastmcp>=2.0.0",
|
|
31
28
|
]
|
|
32
29
|
|
|
@@ -40,6 +40,7 @@ from devhelm_mcp.tools import (
|
|
|
40
40
|
notification_policies,
|
|
41
41
|
resource_groups,
|
|
42
42
|
secrets,
|
|
43
|
+
services,
|
|
43
44
|
status,
|
|
44
45
|
status_pages,
|
|
45
46
|
tags,
|
|
@@ -77,8 +78,9 @@ mcp = FastMCP(
|
|
|
77
78
|
"DevHelm MCP server for monitoring infrastructure. "
|
|
78
79
|
"Use these tools to manage uptime monitors, incidents, alert channels, "
|
|
79
80
|
"notification policies, environments, secrets, tags, resource groups, "
|
|
80
|
-
"webhooks, API keys, service dependencies,
|
|
81
|
-
"
|
|
81
|
+
"webhooks, API keys, service dependencies, the third-party service "
|
|
82
|
+
"catalog (search services, live status, uptime, incidents), deploy "
|
|
83
|
+
"locks, maintenance windows, status pages, and view dashboard status. "
|
|
82
84
|
"Authentication is handled at the transport layer: "
|
|
83
85
|
"`Authorization: Bearer <token>` for the hosted endpoint "
|
|
84
86
|
"(https://mcp.devhelm.io/mcp/), or the `DEVHELM_API_TOKEN` environment "
|
|
@@ -100,6 +102,7 @@ ALL_TOOL_MODULES = [
|
|
|
100
102
|
webhooks,
|
|
101
103
|
api_keys,
|
|
102
104
|
dependencies,
|
|
105
|
+
services,
|
|
103
106
|
deploy_lock,
|
|
104
107
|
maintenance_windows,
|
|
105
108
|
status,
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Dependency tools — track third-party service dependencies."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from devhelm import DevhelmError
|
|
6
|
+
from fastmcp import FastMCP
|
|
7
|
+
|
|
8
|
+
from devhelm_mcp.client import ToolResult, get_client, raise_tool_error, serialize
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def register(mcp: FastMCP) -> None:
|
|
12
|
+
@mcp.tool()
|
|
13
|
+
def list_dependencies(api_token: str | None = None) -> ToolResult:
|
|
14
|
+
"""List all tracked service dependencies."""
|
|
15
|
+
try:
|
|
16
|
+
return serialize(get_client(api_token).dependencies.list())
|
|
17
|
+
except DevhelmError as e:
|
|
18
|
+
raise_tool_error(e)
|
|
19
|
+
|
|
20
|
+
@mcp.tool()
|
|
21
|
+
def get_dependency(dependency_id: str, api_token: str | None = None) -> ToolResult:
|
|
22
|
+
"""Get a tracked dependency by ID."""
|
|
23
|
+
try:
|
|
24
|
+
return serialize(get_client(api_token).dependencies.get(dependency_id))
|
|
25
|
+
except DevhelmError as e:
|
|
26
|
+
raise_tool_error(e)
|
|
27
|
+
|
|
28
|
+
@mcp.tool()
|
|
29
|
+
def track_dependency(
|
|
30
|
+
slug: str,
|
|
31
|
+
component_id: str | None = None,
|
|
32
|
+
alert_sensitivity: str | None = None,
|
|
33
|
+
api_token: str | None = None,
|
|
34
|
+
) -> ToolResult:
|
|
35
|
+
"""Start tracking a service dependency by its slug (e.g. 'github', 'aws').
|
|
36
|
+
|
|
37
|
+
Optionally track a single component via `component_id` (see
|
|
38
|
+
list_service_components) and set `alert_sensitivity`: AWARENESS
|
|
39
|
+
(silent tracking, default), INCIDENTS_ONLY, MAJOR_ONLY, or ALL."""
|
|
40
|
+
try:
|
|
41
|
+
return serialize(
|
|
42
|
+
get_client(api_token).dependencies.track(
|
|
43
|
+
slug,
|
|
44
|
+
component_id=component_id,
|
|
45
|
+
alert_sensitivity=alert_sensitivity,
|
|
46
|
+
)
|
|
47
|
+
)
|
|
48
|
+
except DevhelmError as e:
|
|
49
|
+
raise_tool_error(e)
|
|
50
|
+
|
|
51
|
+
@mcp.tool()
|
|
52
|
+
def update_dependency_alert_sensitivity(
|
|
53
|
+
subscription_id: str,
|
|
54
|
+
alert_sensitivity: str,
|
|
55
|
+
api_token: str | None = None,
|
|
56
|
+
) -> ToolResult:
|
|
57
|
+
"""Change how loudly a tracked dependency alerts you.
|
|
58
|
+
|
|
59
|
+
Levels: AWARENESS (silent tracking, default — status visible on the
|
|
60
|
+
dashboard but no notifications), INCIDENTS_ONLY (notify on any
|
|
61
|
+
incident), MAJOR_ONLY (notify only on major/critical incidents), and
|
|
62
|
+
ALL (every status change, including maintenance)."""
|
|
63
|
+
try:
|
|
64
|
+
return serialize(
|
|
65
|
+
get_client(api_token).dependencies.update_alert_sensitivity(
|
|
66
|
+
subscription_id, alert_sensitivity
|
|
67
|
+
)
|
|
68
|
+
)
|
|
69
|
+
except DevhelmError as e:
|
|
70
|
+
raise_tool_error(e)
|
|
71
|
+
|
|
72
|
+
@mcp.tool()
|
|
73
|
+
def delete_dependency(dependency_id: str, api_token: str | None = None) -> str:
|
|
74
|
+
"""Stop tracking a service dependency."""
|
|
75
|
+
try:
|
|
76
|
+
get_client(api_token).dependencies.delete(dependency_id)
|
|
77
|
+
return "Dependency removed successfully."
|
|
78
|
+
except DevhelmError as e:
|
|
79
|
+
raise_tool_error(e)
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"""Service catalog tools — browse third-party services and their status."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from devhelm import DevhelmError
|
|
6
|
+
from fastmcp import FastMCP
|
|
7
|
+
|
|
8
|
+
from devhelm_mcp.client import ToolResult, get_client, raise_tool_error, serialize
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def register(mcp: FastMCP) -> None:
|
|
12
|
+
@mcp.tool()
|
|
13
|
+
def search_services(
|
|
14
|
+
query: str | None = None,
|
|
15
|
+
category: str | None = None,
|
|
16
|
+
limit: int = 20,
|
|
17
|
+
api_token: str | None = None,
|
|
18
|
+
) -> ToolResult:
|
|
19
|
+
"""Search the catalog of third-party services (Stripe, GitHub, AWS, ...)
|
|
20
|
+
that can be tracked as dependencies.
|
|
21
|
+
|
|
22
|
+
Use `query` for free-text search by name (e.g. 'stripe', 'cloudflare')
|
|
23
|
+
and `category` to filter by catalog category (see
|
|
24
|
+
list_service_categories). Results are paginated; raise `limit`
|
|
25
|
+
(default 20) for broader sweeps."""
|
|
26
|
+
try:
|
|
27
|
+
return serialize(
|
|
28
|
+
get_client(api_token).services.list(
|
|
29
|
+
search=query, category=category, limit=limit
|
|
30
|
+
)
|
|
31
|
+
)
|
|
32
|
+
except DevhelmError as e:
|
|
33
|
+
raise_tool_error(e)
|
|
34
|
+
|
|
35
|
+
@mcp.tool()
|
|
36
|
+
def get_service(slug: str, api_token: str | None = None) -> ToolResult:
|
|
37
|
+
"""Get a catalog service's summary by slug (e.g. 'github'), including
|
|
38
|
+
its current status, categories, and component overview."""
|
|
39
|
+
try:
|
|
40
|
+
return serialize(get_client(api_token).services.get(slug, summary=True))
|
|
41
|
+
except DevhelmError as e:
|
|
42
|
+
raise_tool_error(e)
|
|
43
|
+
|
|
44
|
+
@mcp.tool()
|
|
45
|
+
def get_service_live_status(slug: str, api_token: str | None = None) -> ToolResult:
|
|
46
|
+
"""Get the live (real-time) operational status of a catalog service,
|
|
47
|
+
fetched from its upstream status page. Use this when freshness matters
|
|
48
|
+
more than latency — e.g. 'is Stripe down right now?'."""
|
|
49
|
+
try:
|
|
50
|
+
return serialize(get_client(api_token).services.live_status(slug))
|
|
51
|
+
except DevhelmError as e:
|
|
52
|
+
raise_tool_error(e)
|
|
53
|
+
|
|
54
|
+
@mcp.tool()
|
|
55
|
+
def get_services_summary(api_token: str | None = None) -> ToolResult:
|
|
56
|
+
"""Get the global status summary across the entire service catalog —
|
|
57
|
+
counts of operational / degraded / outage services. Use this for a
|
|
58
|
+
quick 'is anything broken on the internet right now?' overview before
|
|
59
|
+
drilling into a specific service."""
|
|
60
|
+
try:
|
|
61
|
+
return serialize(get_client(api_token).services.summary())
|
|
62
|
+
except DevhelmError as e:
|
|
63
|
+
raise_tool_error(e)
|
|
64
|
+
|
|
65
|
+
@mcp.tool()
|
|
66
|
+
def list_service_categories(api_token: str | None = None) -> ToolResult:
|
|
67
|
+
"""List all service catalog categories (e.g. cloud, payments, devtools)
|
|
68
|
+
usable as the `category` filter in search_services."""
|
|
69
|
+
try:
|
|
70
|
+
return serialize(get_client(api_token).services.categories())
|
|
71
|
+
except DevhelmError as e:
|
|
72
|
+
raise_tool_error(e)
|
|
73
|
+
|
|
74
|
+
@mcp.tool()
|
|
75
|
+
def list_service_components(slug: str, api_token: str | None = None) -> ToolResult:
|
|
76
|
+
"""List a catalog service's components (e.g. 'API', 'Dashboard',
|
|
77
|
+
'Webhooks') with their individual statuses. Component IDs can be used
|
|
78
|
+
to track a single component via track_dependency."""
|
|
79
|
+
try:
|
|
80
|
+
return serialize(get_client(api_token).services.components(slug))
|
|
81
|
+
except DevhelmError as e:
|
|
82
|
+
raise_tool_error(e)
|
|
83
|
+
|
|
84
|
+
@mcp.tool()
|
|
85
|
+
def get_service_uptime(
|
|
86
|
+
slug: str, period: str = "30d", api_token: str | None = None
|
|
87
|
+
) -> ToolResult:
|
|
88
|
+
"""Get historical uptime stats for a catalog service over a period
|
|
89
|
+
(e.g. '7d', '30d', '90d'; default '30d')."""
|
|
90
|
+
try:
|
|
91
|
+
return serialize(get_client(api_token).services.uptime(slug, period=period))
|
|
92
|
+
except DevhelmError as e:
|
|
93
|
+
raise_tool_error(e)
|
|
94
|
+
|
|
95
|
+
@mcp.tool()
|
|
96
|
+
def list_service_incidents(
|
|
97
|
+
slug: str | None = None,
|
|
98
|
+
status: str | None = None,
|
|
99
|
+
api_token: str | None = None,
|
|
100
|
+
) -> ToolResult:
|
|
101
|
+
"""List incidents for a catalog service, or across all services when
|
|
102
|
+
`slug` is omitted. Filter by `status` (e.g. 'active', 'resolved') to
|
|
103
|
+
answer questions like 'which of my dependencies have open incidents?'."""
|
|
104
|
+
try:
|
|
105
|
+
return serialize(
|
|
106
|
+
get_client(api_token).services.incidents(slug_or_id=slug, status=status)
|
|
107
|
+
)
|
|
108
|
+
except DevhelmError as e:
|
|
109
|
+
raise_tool_error(e)
|
|
110
|
+
|
|
111
|
+
@mcp.tool()
|
|
112
|
+
def get_service_incident(
|
|
113
|
+
slug: str, incident_id: str, api_token: str | None = None
|
|
114
|
+
) -> ToolResult:
|
|
115
|
+
"""Get one vendor incident in full detail, including the vendor's
|
|
116
|
+
timeline of status updates (investigating → identified → resolved).
|
|
117
|
+
Get incident IDs from list_service_incidents."""
|
|
118
|
+
try:
|
|
119
|
+
return serialize(get_client(api_token).services.incident(slug, incident_id))
|
|
120
|
+
except DevhelmError as e:
|
|
121
|
+
raise_tool_error(e)
|
|
122
|
+
|
|
123
|
+
@mcp.tool()
|
|
124
|
+
def get_service_day_rollup(
|
|
125
|
+
slug: str, date: str, api_token: str | None = None
|
|
126
|
+
) -> ToolResult:
|
|
127
|
+
"""Get a one-day rollup for a catalog service on a UTC calendar day
|
|
128
|
+
(ISO YYYY-MM-DD): aggregated uptime, per-component impact windows,
|
|
129
|
+
and the incidents that overlapped that day. Use this to answer
|
|
130
|
+
'what happened to Stripe on 2026-06-01?'."""
|
|
131
|
+
try:
|
|
132
|
+
return serialize(get_client(api_token).services.day(slug, date))
|
|
133
|
+
except DevhelmError as e:
|
|
134
|
+
raise_tool_error(e)
|
|
135
|
+
|
|
136
|
+
@mcp.tool()
|
|
137
|
+
def get_component_uptime(
|
|
138
|
+
slug: str,
|
|
139
|
+
component_id: str,
|
|
140
|
+
period: str = "30d",
|
|
141
|
+
api_token: str | None = None,
|
|
142
|
+
) -> ToolResult:
|
|
143
|
+
"""Get daily uptime history for a single component of a catalog
|
|
144
|
+
service (e.g. just the 'API' component of Stripe) over a period
|
|
145
|
+
('7d', '30d', '90d', '1y'; default '30d'). Get component IDs from
|
|
146
|
+
list_service_components."""
|
|
147
|
+
try:
|
|
148
|
+
return serialize(
|
|
149
|
+
get_client(api_token).services.component_uptime(
|
|
150
|
+
slug, component_id, period=period
|
|
151
|
+
)
|
|
152
|
+
)
|
|
153
|
+
except DevhelmError as e:
|
|
154
|
+
raise_tool_error(e)
|
|
155
|
+
|
|
156
|
+
@mcp.tool()
|
|
157
|
+
def get_all_components_uptime(
|
|
158
|
+
slug: str, period: str = "30d", api_token: str | None = None
|
|
159
|
+
) -> ToolResult:
|
|
160
|
+
"""Get daily uptime history for every leaf component of a catalog
|
|
161
|
+
service in one call, keyed by component ID, over a period ('7d',
|
|
162
|
+
'30d', '90d', '1y'; default '30d'). Prefer this over repeated
|
|
163
|
+
get_component_uptime calls when comparing components."""
|
|
164
|
+
try:
|
|
165
|
+
return serialize(
|
|
166
|
+
get_client(api_token).services.batch_component_uptime(
|
|
167
|
+
slug, period=period
|
|
168
|
+
)
|
|
169
|
+
)
|
|
170
|
+
except DevhelmError as e:
|
|
171
|
+
raise_tool_error(e)
|
|
172
|
+
|
|
173
|
+
@mcp.tool()
|
|
174
|
+
def list_service_maintenances(
|
|
175
|
+
slug: str, api_token: str | None = None
|
|
176
|
+
) -> ToolResult:
|
|
177
|
+
"""List scheduled and past maintenance windows announced by a catalog
|
|
178
|
+
service (e.g. upcoming AWS maintenance that could affect you)."""
|
|
179
|
+
try:
|
|
180
|
+
return serialize(get_client(api_token).services.maintenances(slug))
|
|
181
|
+
except DevhelmError as e:
|
|
182
|
+
raise_tool_error(e)
|
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
"""Tests for service catalog MCP tools + dependency alert-sensitivity tools.
|
|
2
|
+
|
|
3
|
+
The ``client.services`` SDK resource and the extended
|
|
4
|
+
``client.dependencies.track`` / ``update_alert_sensitivity`` signatures ship
|
|
5
|
+
in a parallel SDK release; these tests patch ``get_client`` in the tool
|
|
6
|
+
module's namespace (the same pattern as ``test_devex_round3_fixes.py``) so
|
|
7
|
+
the tools are exercised against the agreed SDK call contract without
|
|
8
|
+
requiring the new SDK methods to be installed.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import asyncio
|
|
14
|
+
from typing import Any
|
|
15
|
+
from unittest.mock import MagicMock, patch
|
|
16
|
+
|
|
17
|
+
import pytest
|
|
18
|
+
from devhelm import DevhelmApiError
|
|
19
|
+
|
|
20
|
+
from devhelm_mcp.server import _strip_internal_schema_fields, mcp
|
|
21
|
+
|
|
22
|
+
RegisteredTools = dict[str, Any]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@pytest.fixture(scope="module")
|
|
26
|
+
def registered_tools() -> RegisteredTools:
|
|
27
|
+
asyncio.run(_strip_internal_schema_fields())
|
|
28
|
+
tools = asyncio.run(mcp.list_tools())
|
|
29
|
+
return {t.name: t for t in tools}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
_SERVICE_TOOLS = [
|
|
33
|
+
"search_services",
|
|
34
|
+
"get_service",
|
|
35
|
+
"get_service_live_status",
|
|
36
|
+
"get_services_summary",
|
|
37
|
+
"list_service_categories",
|
|
38
|
+
"list_service_components",
|
|
39
|
+
"get_service_uptime",
|
|
40
|
+
"list_service_incidents",
|
|
41
|
+
"get_service_incident",
|
|
42
|
+
"get_service_day_rollup",
|
|
43
|
+
"get_component_uptime",
|
|
44
|
+
"get_all_components_uptime",
|
|
45
|
+
"list_service_maintenances",
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
_SAMPLE_SERVICE: dict[str, Any] = {
|
|
49
|
+
"slug": "stripe",
|
|
50
|
+
"name": "Stripe",
|
|
51
|
+
"category": "payments",
|
|
52
|
+
"status": "OPERATIONAL",
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
_SAMPLE_PAGE: dict[str, Any] = {
|
|
56
|
+
"data": [_SAMPLE_SERVICE],
|
|
57
|
+
"nextCursor": None,
|
|
58
|
+
"hasMore": False,
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _call_tool(tool_name: str, arguments: dict[str, Any]) -> Any:
|
|
63
|
+
return asyncio.run(mcp.call_tool(tool_name, arguments))
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _mock_services_client() -> MagicMock:
|
|
67
|
+
"""SDK client stub whose ``services`` / ``dependencies`` methods return
|
|
68
|
+
JSON-safe dicts (``serialize`` rejects bare MagicMocks)."""
|
|
69
|
+
client = MagicMock()
|
|
70
|
+
client.services.list.return_value = _SAMPLE_PAGE
|
|
71
|
+
client.services.get.return_value = _SAMPLE_SERVICE
|
|
72
|
+
client.services.live_status.return_value = {
|
|
73
|
+
"slug": "stripe",
|
|
74
|
+
"status": "OPERATIONAL",
|
|
75
|
+
}
|
|
76
|
+
client.services.categories.return_value = [{"slug": "payments"}]
|
|
77
|
+
client.services.components.return_value = [{"id": "comp_1", "name": "API"}]
|
|
78
|
+
client.services.uptime.return_value = {"period": "30d", "uptime": 99.99}
|
|
79
|
+
client.services.incidents.return_value = [{"id": "inc_1", "status": "active"}]
|
|
80
|
+
client.services.summary.return_value = {
|
|
81
|
+
"totalServices": 1200,
|
|
82
|
+
"operationalCount": 1190,
|
|
83
|
+
}
|
|
84
|
+
client.services.incident.return_value = {
|
|
85
|
+
"id": "inc_1",
|
|
86
|
+
"status": "resolved",
|
|
87
|
+
"updates": [{"status": "investigating"}, {"status": "resolved"}],
|
|
88
|
+
}
|
|
89
|
+
client.services.day.return_value = {
|
|
90
|
+
"date": "2026-06-01",
|
|
91
|
+
"uptime": 99.5,
|
|
92
|
+
"incidents": [{"id": "inc_1"}],
|
|
93
|
+
}
|
|
94
|
+
client.services.component_uptime.return_value = [
|
|
95
|
+
{"date": "2026-06-01", "uptime": 100.0}
|
|
96
|
+
]
|
|
97
|
+
client.services.batch_component_uptime.return_value = {
|
|
98
|
+
"comp_1": [{"date": "2026-06-01", "uptime": 100.0}]
|
|
99
|
+
}
|
|
100
|
+
client.services.maintenances.return_value = [{"id": "mw_1"}]
|
|
101
|
+
client.dependencies.track.return_value = {"id": "sub_1", "slug": "stripe"}
|
|
102
|
+
client.dependencies.update_alert_sensitivity.return_value = {
|
|
103
|
+
"id": "sub_1",
|
|
104
|
+
"alertSensitivity": "MAJOR_ONLY",
|
|
105
|
+
}
|
|
106
|
+
return client
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
# --------------------------------------------------------------------------- #
|
|
110
|
+
# Registration + schema hygiene
|
|
111
|
+
# --------------------------------------------------------------------------- #
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class TestServiceToolsRegistered:
|
|
115
|
+
@pytest.mark.parametrize("name", _SERVICE_TOOLS)
|
|
116
|
+
def test_tool_registered_with_description(
|
|
117
|
+
self, registered_tools: RegisteredTools, name: str
|
|
118
|
+
) -> None:
|
|
119
|
+
assert name in registered_tools, f"Missing tool: {name}"
|
|
120
|
+
desc = registered_tools[name].description
|
|
121
|
+
assert desc and len(desc) >= 10, f"{name} description too short: {desc!r}"
|
|
122
|
+
|
|
123
|
+
def test_search_services_mentions_example_services(
|
|
124
|
+
self, registered_tools: RegisteredTools
|
|
125
|
+
) -> None:
|
|
126
|
+
# The docstring is the LLM's only hint about what lives in the
|
|
127
|
+
# catalog; the Stripe/GitHub/AWS examples anchor that.
|
|
128
|
+
desc = registered_tools["search_services"].description
|
|
129
|
+
for example in ("Stripe", "GitHub", "AWS"):
|
|
130
|
+
assert example in desc, f"search_services should mention {example}"
|
|
131
|
+
|
|
132
|
+
@pytest.mark.parametrize("name", _SERVICE_TOOLS)
|
|
133
|
+
def test_api_token_hidden_from_input_schema(
|
|
134
|
+
self, registered_tools: RegisteredTools, name: str
|
|
135
|
+
) -> None:
|
|
136
|
+
properties = registered_tools[name].parameters.get("properties", {})
|
|
137
|
+
assert "api_token" not in properties, (
|
|
138
|
+
f"{name} leaks api_token into the LLM-facing input schema"
|
|
139
|
+
)
|
|
140
|
+
required = registered_tools[name].parameters.get("required", [])
|
|
141
|
+
assert "api_token" not in required
|
|
142
|
+
|
|
143
|
+
def test_slug_required_on_per_service_tools(
|
|
144
|
+
self, registered_tools: RegisteredTools
|
|
145
|
+
) -> None:
|
|
146
|
+
for name in [
|
|
147
|
+
"get_service",
|
|
148
|
+
"get_service_live_status",
|
|
149
|
+
"list_service_components",
|
|
150
|
+
"get_service_uptime",
|
|
151
|
+
"get_service_incident",
|
|
152
|
+
"get_service_day_rollup",
|
|
153
|
+
"get_component_uptime",
|
|
154
|
+
"get_all_components_uptime",
|
|
155
|
+
"list_service_maintenances",
|
|
156
|
+
]:
|
|
157
|
+
required = registered_tools[name].parameters.get("required", [])
|
|
158
|
+
assert "slug" in required, f"{name} should require slug"
|
|
159
|
+
|
|
160
|
+
def test_cross_service_tools_have_optional_filters(
|
|
161
|
+
self, registered_tools: RegisteredTools
|
|
162
|
+
) -> None:
|
|
163
|
+
# search_services, list_service_incidents, and get_services_summary
|
|
164
|
+
# work with no args at all (browse mode / cross-service sweeps /
|
|
165
|
+
# global overview).
|
|
166
|
+
for name in [
|
|
167
|
+
"search_services",
|
|
168
|
+
"list_service_incidents",
|
|
169
|
+
"get_services_summary",
|
|
170
|
+
]:
|
|
171
|
+
required = registered_tools[name].parameters.get("required", [])
|
|
172
|
+
assert required == [], f"{name} should have no required params"
|
|
173
|
+
|
|
174
|
+
def test_id_params_required_on_detail_tools(
|
|
175
|
+
self, registered_tools: RegisteredTools
|
|
176
|
+
) -> None:
|
|
177
|
+
required_incident = registered_tools["get_service_incident"].parameters.get(
|
|
178
|
+
"required", []
|
|
179
|
+
)
|
|
180
|
+
assert "incident_id" in required_incident
|
|
181
|
+
required_day = registered_tools["get_service_day_rollup"].parameters.get(
|
|
182
|
+
"required", []
|
|
183
|
+
)
|
|
184
|
+
assert "date" in required_day
|
|
185
|
+
required_comp = registered_tools["get_component_uptime"].parameters.get(
|
|
186
|
+
"required", []
|
|
187
|
+
)
|
|
188
|
+
assert "component_id" in required_comp
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
# --------------------------------------------------------------------------- #
|
|
192
|
+
# SDK call contract — each tool forwards args to the agreed SDK signature
|
|
193
|
+
# --------------------------------------------------------------------------- #
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
class TestServiceToolsSdkContract:
|
|
197
|
+
def test_search_services_forwards_query_category_limit(self) -> None:
|
|
198
|
+
client = _mock_services_client()
|
|
199
|
+
with patch("devhelm_mcp.tools.services.get_client", return_value=client):
|
|
200
|
+
_call_tool(
|
|
201
|
+
"search_services",
|
|
202
|
+
{"query": "stripe", "category": "payments", "limit": 5},
|
|
203
|
+
)
|
|
204
|
+
client.services.list.assert_called_once_with(
|
|
205
|
+
search="stripe", category="payments", limit=5
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
def test_search_services_defaults(self) -> None:
|
|
209
|
+
client = _mock_services_client()
|
|
210
|
+
with patch("devhelm_mcp.tools.services.get_client", return_value=client):
|
|
211
|
+
_call_tool("search_services", {})
|
|
212
|
+
client.services.list.assert_called_once_with(
|
|
213
|
+
search=None, category=None, limit=20
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
def test_get_service_uses_summary(self) -> None:
|
|
217
|
+
client = _mock_services_client()
|
|
218
|
+
with patch("devhelm_mcp.tools.services.get_client", return_value=client):
|
|
219
|
+
_call_tool("get_service", {"slug": "stripe"})
|
|
220
|
+
client.services.get.assert_called_once_with("stripe", summary=True)
|
|
221
|
+
|
|
222
|
+
def test_get_service_live_status(self) -> None:
|
|
223
|
+
client = _mock_services_client()
|
|
224
|
+
with patch("devhelm_mcp.tools.services.get_client", return_value=client):
|
|
225
|
+
_call_tool("get_service_live_status", {"slug": "stripe"})
|
|
226
|
+
client.services.live_status.assert_called_once_with("stripe")
|
|
227
|
+
|
|
228
|
+
def test_list_service_categories(self) -> None:
|
|
229
|
+
client = _mock_services_client()
|
|
230
|
+
with patch("devhelm_mcp.tools.services.get_client", return_value=client):
|
|
231
|
+
_call_tool("list_service_categories", {})
|
|
232
|
+
client.services.categories.assert_called_once_with()
|
|
233
|
+
|
|
234
|
+
def test_list_service_components(self) -> None:
|
|
235
|
+
client = _mock_services_client()
|
|
236
|
+
with patch("devhelm_mcp.tools.services.get_client", return_value=client):
|
|
237
|
+
_call_tool("list_service_components", {"slug": "stripe"})
|
|
238
|
+
client.services.components.assert_called_once_with("stripe")
|
|
239
|
+
|
|
240
|
+
def test_get_service_uptime_forwards_period(self) -> None:
|
|
241
|
+
client = _mock_services_client()
|
|
242
|
+
with patch("devhelm_mcp.tools.services.get_client", return_value=client):
|
|
243
|
+
_call_tool("get_service_uptime", {"slug": "stripe", "period": "90d"})
|
|
244
|
+
client.services.uptime.assert_called_once_with("stripe", period="90d")
|
|
245
|
+
|
|
246
|
+
def test_get_service_uptime_default_period(self) -> None:
|
|
247
|
+
client = _mock_services_client()
|
|
248
|
+
with patch("devhelm_mcp.tools.services.get_client", return_value=client):
|
|
249
|
+
_call_tool("get_service_uptime", {"slug": "stripe"})
|
|
250
|
+
client.services.uptime.assert_called_once_with("stripe", period="30d")
|
|
251
|
+
|
|
252
|
+
def test_list_service_incidents_per_service(self) -> None:
|
|
253
|
+
client = _mock_services_client()
|
|
254
|
+
with patch("devhelm_mcp.tools.services.get_client", return_value=client):
|
|
255
|
+
_call_tool("list_service_incidents", {"slug": "stripe", "status": "active"})
|
|
256
|
+
client.services.incidents.assert_called_once_with(
|
|
257
|
+
slug_or_id="stripe", status="active"
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
def test_list_service_incidents_cross_service(self) -> None:
|
|
261
|
+
# Omitting slug must hit the cross-service listing (slug_or_id=None).
|
|
262
|
+
client = _mock_services_client()
|
|
263
|
+
with patch("devhelm_mcp.tools.services.get_client", return_value=client):
|
|
264
|
+
_call_tool("list_service_incidents", {})
|
|
265
|
+
client.services.incidents.assert_called_once_with(slug_or_id=None, status=None)
|
|
266
|
+
|
|
267
|
+
def test_list_service_maintenances(self) -> None:
|
|
268
|
+
client = _mock_services_client()
|
|
269
|
+
with patch("devhelm_mcp.tools.services.get_client", return_value=client):
|
|
270
|
+
_call_tool("list_service_maintenances", {"slug": "aws"})
|
|
271
|
+
client.services.maintenances.assert_called_once_with("aws")
|
|
272
|
+
|
|
273
|
+
def test_get_services_summary(self) -> None:
|
|
274
|
+
client = _mock_services_client()
|
|
275
|
+
with patch("devhelm_mcp.tools.services.get_client", return_value=client):
|
|
276
|
+
_call_tool("get_services_summary", {})
|
|
277
|
+
client.services.summary.assert_called_once_with()
|
|
278
|
+
|
|
279
|
+
def test_get_service_incident(self) -> None:
|
|
280
|
+
client = _mock_services_client()
|
|
281
|
+
with patch("devhelm_mcp.tools.services.get_client", return_value=client):
|
|
282
|
+
_call_tool(
|
|
283
|
+
"get_service_incident", {"slug": "stripe", "incident_id": "inc_1"}
|
|
284
|
+
)
|
|
285
|
+
client.services.incident.assert_called_once_with("stripe", "inc_1")
|
|
286
|
+
|
|
287
|
+
def test_get_service_day_rollup(self) -> None:
|
|
288
|
+
client = _mock_services_client()
|
|
289
|
+
with patch("devhelm_mcp.tools.services.get_client", return_value=client):
|
|
290
|
+
_call_tool(
|
|
291
|
+
"get_service_day_rollup", {"slug": "stripe", "date": "2026-06-01"}
|
|
292
|
+
)
|
|
293
|
+
client.services.day.assert_called_once_with("stripe", "2026-06-01")
|
|
294
|
+
|
|
295
|
+
def test_get_component_uptime_forwards_period(self) -> None:
|
|
296
|
+
client = _mock_services_client()
|
|
297
|
+
with patch("devhelm_mcp.tools.services.get_client", return_value=client):
|
|
298
|
+
_call_tool(
|
|
299
|
+
"get_component_uptime",
|
|
300
|
+
{"slug": "stripe", "component_id": "comp_1", "period": "90d"},
|
|
301
|
+
)
|
|
302
|
+
client.services.component_uptime.assert_called_once_with(
|
|
303
|
+
"stripe", "comp_1", period="90d"
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
def test_get_component_uptime_default_period(self) -> None:
|
|
307
|
+
client = _mock_services_client()
|
|
308
|
+
with patch("devhelm_mcp.tools.services.get_client", return_value=client):
|
|
309
|
+
_call_tool(
|
|
310
|
+
"get_component_uptime", {"slug": "stripe", "component_id": "comp_1"}
|
|
311
|
+
)
|
|
312
|
+
client.services.component_uptime.assert_called_once_with(
|
|
313
|
+
"stripe", "comp_1", period="30d"
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
def test_get_all_components_uptime_forwards_period(self) -> None:
|
|
317
|
+
client = _mock_services_client()
|
|
318
|
+
with patch("devhelm_mcp.tools.services.get_client", return_value=client):
|
|
319
|
+
_call_tool("get_all_components_uptime", {"slug": "stripe", "period": "7d"})
|
|
320
|
+
client.services.batch_component_uptime.assert_called_once_with(
|
|
321
|
+
"stripe", period="7d"
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
def test_get_all_components_uptime_default_period(self) -> None:
|
|
325
|
+
client = _mock_services_client()
|
|
326
|
+
with patch("devhelm_mcp.tools.services.get_client", return_value=client):
|
|
327
|
+
_call_tool("get_all_components_uptime", {"slug": "stripe"})
|
|
328
|
+
client.services.batch_component_uptime.assert_called_once_with(
|
|
329
|
+
"stripe", period="30d"
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
# --------------------------------------------------------------------------- #
|
|
334
|
+
# Dependencies — extended track + alert sensitivity update
|
|
335
|
+
# --------------------------------------------------------------------------- #
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
class TestDependencyTrackExtensions:
|
|
339
|
+
def test_track_dependency_defaults_pass_none(self) -> None:
|
|
340
|
+
client = _mock_services_client()
|
|
341
|
+
with patch("devhelm_mcp.tools.dependencies.get_client", return_value=client):
|
|
342
|
+
_call_tool("track_dependency", {"slug": "stripe"})
|
|
343
|
+
client.dependencies.track.assert_called_once_with(
|
|
344
|
+
"stripe", component_id=None, alert_sensitivity=None
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
def test_track_dependency_forwards_component_and_sensitivity(self) -> None:
|
|
348
|
+
client = _mock_services_client()
|
|
349
|
+
with patch("devhelm_mcp.tools.dependencies.get_client", return_value=client):
|
|
350
|
+
_call_tool(
|
|
351
|
+
"track_dependency",
|
|
352
|
+
{
|
|
353
|
+
"slug": "stripe",
|
|
354
|
+
"component_id": "comp_1",
|
|
355
|
+
"alert_sensitivity": "INCIDENTS_ONLY",
|
|
356
|
+
},
|
|
357
|
+
)
|
|
358
|
+
client.dependencies.track.assert_called_once_with(
|
|
359
|
+
"stripe", component_id="comp_1", alert_sensitivity="INCIDENTS_ONLY"
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
def test_update_dependency_alert_sensitivity(self) -> None:
|
|
363
|
+
client = _mock_services_client()
|
|
364
|
+
with patch("devhelm_mcp.tools.dependencies.get_client", return_value=client):
|
|
365
|
+
_call_tool(
|
|
366
|
+
"update_dependency_alert_sensitivity",
|
|
367
|
+
{"subscription_id": "sub_1", "alert_sensitivity": "MAJOR_ONLY"},
|
|
368
|
+
)
|
|
369
|
+
client.dependencies.update_alert_sensitivity.assert_called_once_with(
|
|
370
|
+
"sub_1", "MAJOR_ONLY"
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
def test_update_sensitivity_docstring_lists_all_levels(
|
|
374
|
+
self, registered_tools: RegisteredTools
|
|
375
|
+
) -> None:
|
|
376
|
+
desc = registered_tools["update_dependency_alert_sensitivity"].description
|
|
377
|
+
for level in ("AWARENESS", "INCIDENTS_ONLY", "MAJOR_ONLY", "ALL"):
|
|
378
|
+
assert level in desc, f"description should document level {level}"
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
# --------------------------------------------------------------------------- #
|
|
382
|
+
# Error surfacing — upstream failures must set isError=true
|
|
383
|
+
# --------------------------------------------------------------------------- #
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
class TestServiceToolErrorsBubbleUp:
|
|
387
|
+
def test_get_service_propagates_api_error(self) -> None:
|
|
388
|
+
async def go() -> Any:
|
|
389
|
+
from fastmcp import Client
|
|
390
|
+
|
|
391
|
+
async with Client(mcp) as client:
|
|
392
|
+
return await client.call_tool(
|
|
393
|
+
"get_service",
|
|
394
|
+
{"slug": "nonexistent"},
|
|
395
|
+
raise_on_error=False,
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
mock_client = MagicMock()
|
|
399
|
+
mock_client.services.get.side_effect = DevhelmApiError(
|
|
400
|
+
"Service not found",
|
|
401
|
+
status=404,
|
|
402
|
+
code="NOT_FOUND",
|
|
403
|
+
request_id="req_svc",
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
with patch("devhelm_mcp.tools.services.get_client", return_value=mock_client):
|
|
407
|
+
result = asyncio.run(go())
|
|
408
|
+
|
|
409
|
+
assert result.is_error is True
|
|
410
|
+
text = result.content[0].text
|
|
411
|
+
assert "ApiError (404 NOT_FOUND)" in text
|
|
412
|
+
assert "request_id=req_svc" in text
|
|
@@ -81,7 +81,21 @@ EXPECTED_TOOLS = [
|
|
|
81
81
|
"list_dependencies",
|
|
82
82
|
"get_dependency",
|
|
83
83
|
"track_dependency",
|
|
84
|
+
"update_dependency_alert_sensitivity",
|
|
84
85
|
"delete_dependency",
|
|
86
|
+
"search_services",
|
|
87
|
+
"get_service",
|
|
88
|
+
"get_service_live_status",
|
|
89
|
+
"get_services_summary",
|
|
90
|
+
"list_service_categories",
|
|
91
|
+
"list_service_components",
|
|
92
|
+
"get_service_uptime",
|
|
93
|
+
"list_service_incidents",
|
|
94
|
+
"get_service_incident",
|
|
95
|
+
"get_service_day_rollup",
|
|
96
|
+
"get_component_uptime",
|
|
97
|
+
"get_all_components_uptime",
|
|
98
|
+
"list_service_maintenances",
|
|
85
99
|
"acquire_deploy_lock",
|
|
86
100
|
"get_current_deploy_lock",
|
|
87
101
|
"release_deploy_lock",
|
|
@@ -388,20 +388,20 @@ wheels = [
|
|
|
388
388
|
|
|
389
389
|
[[package]]
|
|
390
390
|
name = "devhelm"
|
|
391
|
-
version = "1.
|
|
391
|
+
version = "1.3.0"
|
|
392
392
|
source = { registry = "https://pypi.org/simple" }
|
|
393
393
|
dependencies = [
|
|
394
394
|
{ name = "httpx" },
|
|
395
395
|
{ name = "pydantic", extra = ["email"] },
|
|
396
396
|
]
|
|
397
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
397
|
+
sdist = { url = "https://files.pythonhosted.org/packages/2f/2e/317082ad26945cdf815040ce48c45ecde7537ccae89d8011bc02840c9325/devhelm-1.3.0.tar.gz", hash = "sha256:fb270af6919bf6bc426de850b0bbd6251ec72a061f6d3170a79e2ead271283cb", size = 261987, upload-time = "2026-06-10T14:00:21.546Z" }
|
|
398
398
|
wheels = [
|
|
399
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
399
|
+
{ url = "https://files.pythonhosted.org/packages/c5/62/261bc0a6551d8098670e755e4a44a74ea8c032edda472364d9a6bd73992c/devhelm-1.3.0-py3-none-any.whl", hash = "sha256:19fac4faad7af71e384ebcb7afd63c8198ad03367c411f88769ac81a1bcbdea2", size = 88194, upload-time = "2026-06-10T14:00:20.547Z" },
|
|
400
400
|
]
|
|
401
401
|
|
|
402
402
|
[[package]]
|
|
403
403
|
name = "devhelm-mcp-server"
|
|
404
|
-
version = "1.
|
|
404
|
+
version = "1.2.0"
|
|
405
405
|
source = { editable = "." }
|
|
406
406
|
dependencies = [
|
|
407
407
|
{ name = "devhelm" },
|
|
@@ -419,7 +419,7 @@ dev = [
|
|
|
419
419
|
|
|
420
420
|
[package.metadata]
|
|
421
421
|
requires-dist = [
|
|
422
|
-
{ name = "devhelm", specifier = ">=1.
|
|
422
|
+
{ name = "devhelm", specifier = ">=1.3.0" },
|
|
423
423
|
{ name = "fastmcp", specifier = ">=2.0.0" },
|
|
424
424
|
]
|
|
425
425
|
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
"""Dependency tools — track third-party service dependencies."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from devhelm import DevhelmError
|
|
6
|
-
from fastmcp import FastMCP
|
|
7
|
-
|
|
8
|
-
from devhelm_mcp.client import ToolResult, get_client, raise_tool_error, serialize
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def register(mcp: FastMCP) -> None:
|
|
12
|
-
@mcp.tool()
|
|
13
|
-
def list_dependencies(api_token: str | None = None) -> ToolResult:
|
|
14
|
-
"""List all tracked service dependencies."""
|
|
15
|
-
try:
|
|
16
|
-
return serialize(get_client(api_token).dependencies.list())
|
|
17
|
-
except DevhelmError as e:
|
|
18
|
-
raise_tool_error(e)
|
|
19
|
-
|
|
20
|
-
@mcp.tool()
|
|
21
|
-
def get_dependency(dependency_id: str, api_token: str | None = None) -> ToolResult:
|
|
22
|
-
"""Get a tracked dependency by ID."""
|
|
23
|
-
try:
|
|
24
|
-
return serialize(get_client(api_token).dependencies.get(dependency_id))
|
|
25
|
-
except DevhelmError as e:
|
|
26
|
-
raise_tool_error(e)
|
|
27
|
-
|
|
28
|
-
@mcp.tool()
|
|
29
|
-
def track_dependency(slug: str, api_token: str | None = None) -> ToolResult:
|
|
30
|
-
"""Start tracking a service dependency by its slug (e.g. 'github', 'aws')."""
|
|
31
|
-
try:
|
|
32
|
-
return serialize(get_client(api_token).dependencies.track(slug))
|
|
33
|
-
except DevhelmError as e:
|
|
34
|
-
raise_tool_error(e)
|
|
35
|
-
|
|
36
|
-
@mcp.tool()
|
|
37
|
-
def delete_dependency(dependency_id: str, api_token: str | None = None) -> str:
|
|
38
|
-
"""Stop tracking a service dependency."""
|
|
39
|
-
try:
|
|
40
|
-
get_client(api_token).dependencies.delete(dependency_id)
|
|
41
|
-
return "Dependency removed successfully."
|
|
42
|
-
except DevhelmError as e:
|
|
43
|
-
raise_tool_error(e)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{devhelm_mcp_server-1.1.0 → devhelm_mcp_server-1.2.0}/src/devhelm_mcp/tools/alert_channels.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{devhelm_mcp_server-1.1.0 → devhelm_mcp_server-1.2.0}/src/devhelm_mcp/tools/maintenance_windows.py
RENAMED
|
File without changes
|
|
File without changes
|
{devhelm_mcp_server-1.1.0 → devhelm_mcp_server-1.2.0}/src/devhelm_mcp/tools/notification_policies.py
RENAMED
|
File without changes
|
{devhelm_mcp_server-1.1.0 → devhelm_mcp_server-1.2.0}/src/devhelm_mcp/tools/resource_groups.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|