Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 5 additions & 8 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,11 @@ classifiers = [
]

dependencies = [
# Floor bumped to 1.0.0: aligns with the GA release of the Python SDK
# cut alongside this server's 1.0.0. The 1.0.0 SDK includes every
# contract the server currently relies on (`managedBy` on resource
# DTOs, the monitor validation contract — frequencySeconds bounds,
# dynamic region whitelist) and commits to semver going forward, so
# `>=1.0.0` lets us pull in compatible patch/minor bumps without
# explicit floor bumps each time.
"devhelm>=1.0.0",
# Floor bumped to 1.3.0: first SDK release with the full
# `client.services` catalog resource and the extended
# `client.dependencies` (component-level track + alert sensitivity)
# that the services/dependencies tool modules call directly.
"devhelm>=1.3.0",
"fastmcp>=2.0.0",
]

Expand Down
7 changes: 5 additions & 2 deletions src/devhelm_mcp/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
notification_policies,
resource_groups,
secrets,
services,
status,
status_pages,
tags,
Expand Down Expand Up @@ -77,8 +78,9 @@ def _package_version() -> str:
"DevHelm MCP server for monitoring infrastructure. "
"Use these tools to manage uptime monitors, incidents, alert channels, "
"notification policies, environments, secrets, tags, resource groups, "
"webhooks, API keys, service dependencies, deploy locks, maintenance "
"windows, status pages, and view dashboard status. "
"webhooks, API keys, service dependencies, the third-party service "
"catalog (search services, live status, uptime, incidents), deploy "
"locks, maintenance windows, status pages, and view dashboard status. "
"Authentication is handled at the transport layer: "
"`Authorization: Bearer <token>` for the hosted endpoint "
"(https://mcp.devhelm.io/mcp/), or the `DEVHELM_API_TOKEN` environment "
Expand All @@ -100,6 +102,7 @@ def _package_version() -> str:
webhooks,
api_keys,
dependencies,
services,
deploy_lock,
maintenance_windows,
status,
Expand Down
42 changes: 39 additions & 3 deletions src/devhelm_mcp/tools/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,46 @@ def get_dependency(dependency_id: str, api_token: str | None = None) -> ToolResu
raise_tool_error(e)

@mcp.tool()
def track_dependency(slug: str, api_token: str | None = None) -> ToolResult:
"""Start tracking a service dependency by its slug (e.g. 'github', 'aws')."""
def track_dependency(
slug: str,
component_id: str | None = None,
alert_sensitivity: str | None = None,
api_token: str | None = None,
) -> ToolResult:
"""Start tracking a service dependency by its slug (e.g. 'github', 'aws').

Optionally track a single component via `component_id` (see
list_service_components) and set `alert_sensitivity`: AWARENESS
(silent tracking, default), INCIDENTS_ONLY, MAJOR_ONLY, or ALL."""
try:
return serialize(
get_client(api_token).dependencies.track(
slug,
component_id=component_id,
alert_sensitivity=alert_sensitivity,
)
)
except DevhelmError as e:
raise_tool_error(e)

@mcp.tool()
def update_dependency_alert_sensitivity(
subscription_id: str,
alert_sensitivity: str,
api_token: str | None = None,
) -> ToolResult:
"""Change how loudly a tracked dependency alerts you.

Levels: AWARENESS (silent tracking, default — status visible on the
dashboard but no notifications), INCIDENTS_ONLY (notify on any
incident), MAJOR_ONLY (notify only on major/critical incidents), and
ALL (every status change, including maintenance)."""
try:
return serialize(get_client(api_token).dependencies.track(slug))
return serialize(
get_client(api_token).dependencies.update_alert_sensitivity(
subscription_id, alert_sensitivity
)
)
except DevhelmError as e:
raise_tool_error(e)

Expand Down
182 changes: 182 additions & 0 deletions src/devhelm_mcp/tools/services.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
"""Service catalog tools — browse third-party services and their status."""

from __future__ import annotations

from devhelm import DevhelmError
from fastmcp import FastMCP

from devhelm_mcp.client import ToolResult, get_client, raise_tool_error, serialize


def register(mcp: FastMCP) -> None:
@mcp.tool()
def search_services(
query: str | None = None,
category: str | None = None,
limit: int = 20,
api_token: str | None = None,
) -> ToolResult:
"""Search the catalog of third-party services (Stripe, GitHub, AWS, ...)
that can be tracked as dependencies.

Use `query` for free-text search by name (e.g. 'stripe', 'cloudflare')
and `category` to filter by catalog category (see
list_service_categories). Results are paginated; raise `limit`
(default 20) for broader sweeps."""
try:
return serialize(
get_client(api_token).services.list(
search=query, category=category, limit=limit
)
)
except DevhelmError as e:
raise_tool_error(e)

@mcp.tool()
def get_service(slug: str, api_token: str | None = None) -> ToolResult:
"""Get a catalog service's summary by slug (e.g. 'github'), including
its current status, categories, and component overview."""
try:
return serialize(get_client(api_token).services.get(slug, summary=True))
except DevhelmError as e:
raise_tool_error(e)

@mcp.tool()
def get_service_live_status(slug: str, api_token: str | None = None) -> ToolResult:
"""Get the live (real-time) operational status of a catalog service,
fetched from its upstream status page. Use this when freshness matters
more than latency — e.g. 'is Stripe down right now?'."""
try:
return serialize(get_client(api_token).services.live_status(slug))
except DevhelmError as e:
raise_tool_error(e)

@mcp.tool()
def get_services_summary(api_token: str | None = None) -> ToolResult:
"""Get the global status summary across the entire service catalog —
counts of operational / degraded / outage services. Use this for a
quick 'is anything broken on the internet right now?' overview before
drilling into a specific service."""
try:
return serialize(get_client(api_token).services.summary())
except DevhelmError as e:
raise_tool_error(e)

@mcp.tool()
def list_service_categories(api_token: str | None = None) -> ToolResult:
"""List all service catalog categories (e.g. cloud, payments, devtools)
usable as the `category` filter in search_services."""
try:
return serialize(get_client(api_token).services.categories())
except DevhelmError as e:
raise_tool_error(e)

@mcp.tool()
def list_service_components(slug: str, api_token: str | None = None) -> ToolResult:
"""List a catalog service's components (e.g. 'API', 'Dashboard',
'Webhooks') with their individual statuses. Component IDs can be used
to track a single component via track_dependency."""
try:
return serialize(get_client(api_token).services.components(slug))
except DevhelmError as e:
raise_tool_error(e)

@mcp.tool()
def get_service_uptime(
slug: str, period: str = "30d", api_token: str | None = None
) -> ToolResult:
"""Get historical uptime stats for a catalog service over a period
(e.g. '7d', '30d', '90d'; default '30d')."""
try:
return serialize(get_client(api_token).services.uptime(slug, period=period))
except DevhelmError as e:
raise_tool_error(e)

@mcp.tool()
def list_service_incidents(
slug: str | None = None,
status: str | None = None,
api_token: str | None = None,
) -> ToolResult:
"""List incidents for a catalog service, or across all services when
`slug` is omitted. Filter by `status` (e.g. 'active', 'resolved') to
answer questions like 'which of my dependencies have open incidents?'."""
try:
return serialize(
get_client(api_token).services.incidents(slug_or_id=slug, status=status)
)
except DevhelmError as e:
raise_tool_error(e)

@mcp.tool()
def get_service_incident(
slug: str, incident_id: str, api_token: str | None = None
) -> ToolResult:
"""Get one vendor incident in full detail, including the vendor's
timeline of status updates (investigating → identified → resolved).
Get incident IDs from list_service_incidents."""
try:
return serialize(get_client(api_token).services.incident(slug, incident_id))
except DevhelmError as e:
raise_tool_error(e)

@mcp.tool()
def get_service_day_rollup(
slug: str, date: str, api_token: str | None = None
) -> ToolResult:
"""Get a one-day rollup for a catalog service on a UTC calendar day
(ISO YYYY-MM-DD): aggregated uptime, per-component impact windows,
and the incidents that overlapped that day. Use this to answer
'what happened to Stripe on 2026-06-01?'."""
try:
return serialize(get_client(api_token).services.day(slug, date))
except DevhelmError as e:
raise_tool_error(e)

@mcp.tool()
def get_component_uptime(
slug: str,
component_id: str,
period: str = "30d",
api_token: str | None = None,
) -> ToolResult:
"""Get daily uptime history for a single component of a catalog
service (e.g. just the 'API' component of Stripe) over a period
('7d', '30d', '90d', '1y'; default '30d'). Get component IDs from
list_service_components."""
try:
return serialize(
get_client(api_token).services.component_uptime(
slug, component_id, period=period
)
)
except DevhelmError as e:
raise_tool_error(e)

@mcp.tool()
def get_all_components_uptime(
slug: str, period: str = "30d", api_token: str | None = None
) -> ToolResult:
"""Get daily uptime history for every leaf component of a catalog
service in one call, keyed by component ID, over a period ('7d',
'30d', '90d', '1y'; default '30d'). Prefer this over repeated
get_component_uptime calls when comparing components."""
try:
return serialize(
get_client(api_token).services.batch_component_uptime(
slug, period=period
)
)
except DevhelmError as e:
raise_tool_error(e)

@mcp.tool()
def list_service_maintenances(
slug: str, api_token: str | None = None
) -> ToolResult:
"""List scheduled and past maintenance windows announced by a catalog
service (e.g. upcoming AWS maintenance that could affect you)."""
try:
return serialize(get_client(api_token).services.maintenances(slug))
except DevhelmError as e:
raise_tool_error(e)
Loading
Loading