devhelm-mcp-server 0.1.0__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.
- devhelm_mcp/__init__.py +1 -0
- devhelm_mcp/client.py +34 -0
- devhelm_mcp/server.py +150 -0
- devhelm_mcp/tools/__init__.py +0 -0
- devhelm_mcp/tools/alert_channels.py +70 -0
- devhelm_mcp/tools/api_keys.py +49 -0
- devhelm_mcp/tools/dependencies.py +45 -0
- devhelm_mcp/tools/deploy_lock.py +50 -0
- devhelm_mcp/tools/environments.py +56 -0
- devhelm_mcp/tools/incidents.py +61 -0
- devhelm_mcp/tools/monitors.py +112 -0
- devhelm_mcp/tools/notification_policies.py +69 -0
- devhelm_mcp/tools/resource_groups.py +86 -0
- devhelm_mcp/tools/secrets.py +49 -0
- devhelm_mcp/tools/status.py +21 -0
- devhelm_mcp/tools/tags.py +56 -0
- devhelm_mcp/tools/webhooks.py +64 -0
- devhelm_mcp_server-0.1.0.dist-info/METADATA +99 -0
- devhelm_mcp_server-0.1.0.dist-info/RECORD +22 -0
- devhelm_mcp_server-0.1.0.dist-info/WHEEL +4 -0
- devhelm_mcp_server-0.1.0.dist-info/entry_points.txt +2 -0
- devhelm_mcp_server-0.1.0.dist-info/licenses/LICENSE +21 -0
devhelm_mcp/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""DevHelm MCP Server — AI agent access to monitors, incidents, alerting, and more."""
|
devhelm_mcp/client.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""SDK client helpers for tool implementations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from devhelm import Devhelm, DevhelmError
|
|
9
|
+
|
|
10
|
+
API_BASE_URL = os.getenv("DEVHELM_API_URL", "https://api.devhelm.io")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_client(api_token: str) -> Devhelm:
|
|
14
|
+
"""Build a Devhelm SDK client from the user's API token."""
|
|
15
|
+
return Devhelm(token=api_token, base_url=API_BASE_URL)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def format_error(err: DevhelmError) -> str:
|
|
19
|
+
"""Format a DevhelmError into a human-readable message for the LLM."""
|
|
20
|
+
parts = [f"Error ({err.code}): {err.message}"]
|
|
21
|
+
if err.detail:
|
|
22
|
+
parts.append(f"Detail: {err.detail}")
|
|
23
|
+
return " | ".join(parts)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def serialize(data: Any) -> Any:
|
|
27
|
+
"""Serialize Pydantic models or other objects to JSON-safe dicts."""
|
|
28
|
+
if hasattr(data, "model_dump"):
|
|
29
|
+
return data.model_dump(mode="json")
|
|
30
|
+
if isinstance(data, list):
|
|
31
|
+
return [serialize(item) for item in data]
|
|
32
|
+
if isinstance(data, dict):
|
|
33
|
+
return {k: serialize(v) for k, v in data.items()}
|
|
34
|
+
return data
|
devhelm_mcp/server.py
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""DevHelm MCP Server.
|
|
2
|
+
|
|
3
|
+
Supports two connection modes:
|
|
4
|
+
1. Bearer auth at /mcp — standard MCP client with Authorization header
|
|
5
|
+
2. API key in path at /{api_key}/mcp — for clients that only accept a URL
|
|
6
|
+
|
|
7
|
+
Both resolve the token to a Devhelm SDK client per-request.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
from devhelm import Devhelm, DevhelmError
|
|
16
|
+
from fastmcp import FastMCP
|
|
17
|
+
from starlette.requests import Request
|
|
18
|
+
from starlette.responses import JSONResponse
|
|
19
|
+
|
|
20
|
+
from devhelm_mcp.tools import (
|
|
21
|
+
alert_channels,
|
|
22
|
+
api_keys,
|
|
23
|
+
dependencies,
|
|
24
|
+
deploy_lock,
|
|
25
|
+
environments,
|
|
26
|
+
incidents,
|
|
27
|
+
monitors,
|
|
28
|
+
notification_policies,
|
|
29
|
+
resource_groups,
|
|
30
|
+
secrets,
|
|
31
|
+
status,
|
|
32
|
+
tags,
|
|
33
|
+
webhooks,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
API_BASE_URL = os.getenv("DEVHELM_API_URL", "https://api.devhelm.io")
|
|
37
|
+
|
|
38
|
+
mcp = FastMCP(
|
|
39
|
+
"DevHelm",
|
|
40
|
+
instructions=(
|
|
41
|
+
"DevHelm MCP server for monitoring infrastructure. "
|
|
42
|
+
"Use these tools to manage uptime monitors, incidents, alert channels, "
|
|
43
|
+
"notification policies, environments, secrets, tags, resource groups, "
|
|
44
|
+
"webhooks, API keys, service dependencies, deploy locks, and view "
|
|
45
|
+
"dashboard status. All operations require a valid DevHelm API token."
|
|
46
|
+
),
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
ALL_TOOL_MODULES = [
|
|
50
|
+
monitors,
|
|
51
|
+
incidents,
|
|
52
|
+
alert_channels,
|
|
53
|
+
notification_policies,
|
|
54
|
+
environments,
|
|
55
|
+
secrets,
|
|
56
|
+
tags,
|
|
57
|
+
resource_groups,
|
|
58
|
+
webhooks,
|
|
59
|
+
api_keys,
|
|
60
|
+
dependencies,
|
|
61
|
+
deploy_lock,
|
|
62
|
+
status,
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
for mod in ALL_TOOL_MODULES:
|
|
66
|
+
mod.register(mcp)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _get_client(token: str) -> Devhelm:
|
|
70
|
+
return Devhelm(token=token, base_url=API_BASE_URL)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _error_response(err: DevhelmError) -> dict[str, Any]:
|
|
74
|
+
return {"error": err.code, "message": err.message, "status": err.status}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@mcp.custom_route("/health", methods=["GET"])
|
|
78
|
+
async def health(request: Request) -> JSONResponse:
|
|
79
|
+
return JSONResponse({"status": "healthy", "service": "devhelm-mcp-server"})
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _get_app():
|
|
83
|
+
"""Build the ASGI app with path-based auth routing."""
|
|
84
|
+
from starlette.applications import Starlette
|
|
85
|
+
from starlette.middleware import Middleware
|
|
86
|
+
from starlette.middleware.cors import CORSMiddleware
|
|
87
|
+
from starlette.routing import Mount, Route
|
|
88
|
+
|
|
89
|
+
mcp_app = mcp.http_app(path="/mcp")
|
|
90
|
+
|
|
91
|
+
async def health_handler(request: Request) -> JSONResponse:
|
|
92
|
+
return JSONResponse({"status": "healthy", "service": "devhelm-mcp-server"})
|
|
93
|
+
|
|
94
|
+
async def path_auth_handler(request: Request) -> JSONResponse:
|
|
95
|
+
"""Handle /{api_key}/mcp/* — extract token from path, proxy to MCP app."""
|
|
96
|
+
api_key = request.path_params["api_key"]
|
|
97
|
+
scope = dict(request.scope)
|
|
98
|
+
original_path: str = scope.get("path", "")
|
|
99
|
+
prefix = f"/{api_key}"
|
|
100
|
+
if original_path.startswith(prefix):
|
|
101
|
+
scope["path"] = original_path[len(prefix) :]
|
|
102
|
+
scope["headers"] = [
|
|
103
|
+
*[(k, v) for k, v in scope.get("headers", []) if k != b"authorization"],
|
|
104
|
+
(b"authorization", f"Bearer {api_key}".encode()),
|
|
105
|
+
]
|
|
106
|
+
from starlette.requests import Request as StarletteRequest
|
|
107
|
+
|
|
108
|
+
inner_request = StarletteRequest(scope, request.receive)
|
|
109
|
+
response = await mcp_app(inner_request.scope, inner_request.receive, None) # type: ignore[arg-type]
|
|
110
|
+
return response # type: ignore[return-value]
|
|
111
|
+
|
|
112
|
+
middleware = [
|
|
113
|
+
Middleware(
|
|
114
|
+
CORSMiddleware,
|
|
115
|
+
allow_origins=["*"],
|
|
116
|
+
allow_methods=["*"],
|
|
117
|
+
allow_headers=["*"],
|
|
118
|
+
),
|
|
119
|
+
]
|
|
120
|
+
|
|
121
|
+
app = Starlette(
|
|
122
|
+
routes=[
|
|
123
|
+
Route("/health", health_handler, methods=["GET"]),
|
|
124
|
+
Mount("/mcp", app=mcp_app),
|
|
125
|
+
Mount("/{api_key}/mcp", app=mcp_app),
|
|
126
|
+
],
|
|
127
|
+
middleware=middleware,
|
|
128
|
+
)
|
|
129
|
+
return app
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
app = _get_app()
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def main() -> None:
|
|
136
|
+
"""Entry point for production: uvicorn devhelm_mcp.server:app"""
|
|
137
|
+
import uvicorn
|
|
138
|
+
|
|
139
|
+
host = os.getenv("HOST", "0.0.0.0")
|
|
140
|
+
port = int(os.getenv("PORT", "8000"))
|
|
141
|
+
uvicorn.run(
|
|
142
|
+
"devhelm_mcp.server:app",
|
|
143
|
+
host=host,
|
|
144
|
+
port=port,
|
|
145
|
+
log_level="info",
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
if __name__ == "__main__":
|
|
150
|
+
main()
|
|
File without changes
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Alert channel tools — Slack, email, webhook, and other notification channels."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from devhelm import DevhelmError
|
|
8
|
+
from fastmcp import FastMCP
|
|
9
|
+
|
|
10
|
+
from devhelm_mcp.client import format_error, get_client, serialize
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def register(mcp: FastMCP) -> None:
|
|
14
|
+
@mcp.tool()
|
|
15
|
+
def list_alert_channels(api_token: str) -> Any:
|
|
16
|
+
"""List all alert channels configured in the workspace."""
|
|
17
|
+
try:
|
|
18
|
+
return serialize(get_client(api_token).alert_channels.list())
|
|
19
|
+
except DevhelmError as e:
|
|
20
|
+
return format_error(e)
|
|
21
|
+
|
|
22
|
+
@mcp.tool()
|
|
23
|
+
def get_alert_channel(api_token: str, channel_id: str) -> Any:
|
|
24
|
+
"""Get an alert channel by ID."""
|
|
25
|
+
try:
|
|
26
|
+
return serialize(get_client(api_token).alert_channels.get(channel_id))
|
|
27
|
+
except DevhelmError as e:
|
|
28
|
+
return format_error(e)
|
|
29
|
+
|
|
30
|
+
@mcp.tool()
|
|
31
|
+
def create_alert_channel(api_token: str, body: dict[str, Any]) -> Any:
|
|
32
|
+
"""Create a new alert channel.
|
|
33
|
+
|
|
34
|
+
Required: name, type, config (type-specific).
|
|
35
|
+
Types: SLACK, EMAIL, WEBHOOK, PAGERDUTY, OPSGENIE,
|
|
36
|
+
TELEGRAM, DISCORD, MSTEAMS.
|
|
37
|
+
"""
|
|
38
|
+
try:
|
|
39
|
+
return serialize(get_client(api_token).alert_channels.create(body))
|
|
40
|
+
except DevhelmError as e:
|
|
41
|
+
return format_error(e)
|
|
42
|
+
|
|
43
|
+
@mcp.tool()
|
|
44
|
+
def update_alert_channel(
|
|
45
|
+
api_token: str, channel_id: str, body: dict[str, Any]
|
|
46
|
+
) -> Any:
|
|
47
|
+
"""Update an existing alert channel."""
|
|
48
|
+
try:
|
|
49
|
+
return serialize(
|
|
50
|
+
get_client(api_token).alert_channels.update(channel_id, body)
|
|
51
|
+
)
|
|
52
|
+
except DevhelmError as e:
|
|
53
|
+
return format_error(e)
|
|
54
|
+
|
|
55
|
+
@mcp.tool()
|
|
56
|
+
def delete_alert_channel(api_token: str, channel_id: str) -> str:
|
|
57
|
+
"""Delete an alert channel."""
|
|
58
|
+
try:
|
|
59
|
+
get_client(api_token).alert_channels.delete(channel_id)
|
|
60
|
+
return "Alert channel deleted successfully."
|
|
61
|
+
except DevhelmError as e:
|
|
62
|
+
return format_error(e)
|
|
63
|
+
|
|
64
|
+
@mcp.tool()
|
|
65
|
+
def test_alert_channel(api_token: str, channel_id: str) -> Any:
|
|
66
|
+
"""Send a test notification to an alert channel to verify it works."""
|
|
67
|
+
try:
|
|
68
|
+
return serialize(get_client(api_token).alert_channels.test(channel_id))
|
|
69
|
+
except DevhelmError as e:
|
|
70
|
+
return format_error(e)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""API key tools — manage API keys for programmatic access."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from devhelm import DevhelmError
|
|
8
|
+
from fastmcp import FastMCP
|
|
9
|
+
|
|
10
|
+
from devhelm_mcp.client import format_error, get_client, serialize
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def register(mcp: FastMCP) -> None:
|
|
14
|
+
@mcp.tool()
|
|
15
|
+
def list_api_keys(api_token: str) -> Any:
|
|
16
|
+
"""List all API keys in the workspace."""
|
|
17
|
+
try:
|
|
18
|
+
return serialize(get_client(api_token).api_keys.list())
|
|
19
|
+
except DevhelmError as e:
|
|
20
|
+
return format_error(e)
|
|
21
|
+
|
|
22
|
+
@mcp.tool()
|
|
23
|
+
def create_api_key(api_token: str, body: dict[str, Any]) -> Any:
|
|
24
|
+
"""Create a new API key. The key value is returned only once.
|
|
25
|
+
|
|
26
|
+
Required fields: name. Optional: expiresAt.
|
|
27
|
+
"""
|
|
28
|
+
try:
|
|
29
|
+
return serialize(get_client(api_token).api_keys.create(body))
|
|
30
|
+
except DevhelmError as e:
|
|
31
|
+
return format_error(e)
|
|
32
|
+
|
|
33
|
+
@mcp.tool()
|
|
34
|
+
def revoke_api_key(api_token: str, key_id: str) -> str:
|
|
35
|
+
"""Revoke an API key (disables it without deleting)."""
|
|
36
|
+
try:
|
|
37
|
+
get_client(api_token).api_keys.revoke(key_id)
|
|
38
|
+
return "API key revoked successfully."
|
|
39
|
+
except DevhelmError as e:
|
|
40
|
+
return format_error(e)
|
|
41
|
+
|
|
42
|
+
@mcp.tool()
|
|
43
|
+
def delete_api_key(api_token: str, key_id: str) -> str:
|
|
44
|
+
"""Delete an API key permanently."""
|
|
45
|
+
try:
|
|
46
|
+
get_client(api_token).api_keys.delete(key_id)
|
|
47
|
+
return "API key deleted successfully."
|
|
48
|
+
except DevhelmError as e:
|
|
49
|
+
return format_error(e)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Dependency tools — track third-party service dependencies."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from devhelm import DevhelmError
|
|
8
|
+
from fastmcp import FastMCP
|
|
9
|
+
|
|
10
|
+
from devhelm_mcp.client import format_error, get_client, serialize
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def register(mcp: FastMCP) -> None:
|
|
14
|
+
@mcp.tool()
|
|
15
|
+
def list_dependencies(api_token: str) -> Any:
|
|
16
|
+
"""List all tracked service dependencies."""
|
|
17
|
+
try:
|
|
18
|
+
return serialize(get_client(api_token).dependencies.list())
|
|
19
|
+
except DevhelmError as e:
|
|
20
|
+
return format_error(e)
|
|
21
|
+
|
|
22
|
+
@mcp.tool()
|
|
23
|
+
def get_dependency(api_token: str, dependency_id: str) -> Any:
|
|
24
|
+
"""Get a tracked dependency by ID."""
|
|
25
|
+
try:
|
|
26
|
+
return serialize(get_client(api_token).dependencies.get(dependency_id))
|
|
27
|
+
except DevhelmError as e:
|
|
28
|
+
return format_error(e)
|
|
29
|
+
|
|
30
|
+
@mcp.tool()
|
|
31
|
+
def track_dependency(api_token: str, slug: str) -> Any:
|
|
32
|
+
"""Start tracking a service dependency by its slug (e.g. 'github', 'aws')."""
|
|
33
|
+
try:
|
|
34
|
+
return serialize(get_client(api_token).dependencies.track(slug))
|
|
35
|
+
except DevhelmError as e:
|
|
36
|
+
return format_error(e)
|
|
37
|
+
|
|
38
|
+
@mcp.tool()
|
|
39
|
+
def delete_dependency(api_token: str, dependency_id: str) -> str:
|
|
40
|
+
"""Stop tracking a service dependency."""
|
|
41
|
+
try:
|
|
42
|
+
get_client(api_token).dependencies.delete(dependency_id)
|
|
43
|
+
return "Dependency removed successfully."
|
|
44
|
+
except DevhelmError as e:
|
|
45
|
+
return format_error(e)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Deploy lock tools — coordinate safe deployments."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from devhelm import DevhelmError
|
|
8
|
+
from fastmcp import FastMCP
|
|
9
|
+
|
|
10
|
+
from devhelm_mcp.client import format_error, get_client, serialize
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def register(mcp: FastMCP) -> None:
|
|
14
|
+
@mcp.tool()
|
|
15
|
+
def acquire_deploy_lock(api_token: str, body: dict[str, Any]) -> Any:
|
|
16
|
+
"""Acquire a deploy lock to prevent concurrent deployments.
|
|
17
|
+
|
|
18
|
+
Required fields: reason. Optional: ttlSeconds.
|
|
19
|
+
"""
|
|
20
|
+
try:
|
|
21
|
+
return serialize(get_client(api_token).deploy_lock.acquire(body))
|
|
22
|
+
except DevhelmError as e:
|
|
23
|
+
return format_error(e)
|
|
24
|
+
|
|
25
|
+
@mcp.tool()
|
|
26
|
+
def get_current_deploy_lock(api_token: str) -> Any:
|
|
27
|
+
"""Get the currently active deploy lock, or null if unlocked."""
|
|
28
|
+
try:
|
|
29
|
+
result = get_client(api_token).deploy_lock.current()
|
|
30
|
+
return serialize(result) if result else None
|
|
31
|
+
except DevhelmError as e:
|
|
32
|
+
return format_error(e)
|
|
33
|
+
|
|
34
|
+
@mcp.tool()
|
|
35
|
+
def release_deploy_lock(api_token: str, lock_id: str) -> str:
|
|
36
|
+
"""Release a deploy lock by ID."""
|
|
37
|
+
try:
|
|
38
|
+
get_client(api_token).deploy_lock.release(lock_id)
|
|
39
|
+
return "Deploy lock released."
|
|
40
|
+
except DevhelmError as e:
|
|
41
|
+
return format_error(e)
|
|
42
|
+
|
|
43
|
+
@mcp.tool()
|
|
44
|
+
def force_release_deploy_lock(api_token: str) -> str:
|
|
45
|
+
"""Force-release any active deploy lock (admin action)."""
|
|
46
|
+
try:
|
|
47
|
+
get_client(api_token).deploy_lock.force_release()
|
|
48
|
+
return "Deploy lock force-released."
|
|
49
|
+
except DevhelmError as e:
|
|
50
|
+
return format_error(e)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Environment tools — prod, staging, and custom environment grouping."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from devhelm import DevhelmError
|
|
8
|
+
from fastmcp import FastMCP
|
|
9
|
+
|
|
10
|
+
from devhelm_mcp.client import format_error, get_client, serialize
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def register(mcp: FastMCP) -> None:
|
|
14
|
+
@mcp.tool()
|
|
15
|
+
def list_environments(api_token: str) -> Any:
|
|
16
|
+
"""List all environments in the workspace."""
|
|
17
|
+
try:
|
|
18
|
+
return serialize(get_client(api_token).environments.list())
|
|
19
|
+
except DevhelmError as e:
|
|
20
|
+
return format_error(e)
|
|
21
|
+
|
|
22
|
+
@mcp.tool()
|
|
23
|
+
def get_environment(api_token: str, slug: str) -> Any:
|
|
24
|
+
"""Get an environment by slug (e.g. 'production', 'staging')."""
|
|
25
|
+
try:
|
|
26
|
+
return serialize(get_client(api_token).environments.get(slug))
|
|
27
|
+
except DevhelmError as e:
|
|
28
|
+
return format_error(e)
|
|
29
|
+
|
|
30
|
+
@mcp.tool()
|
|
31
|
+
def create_environment(api_token: str, body: dict[str, Any]) -> Any:
|
|
32
|
+
"""Create an environment.
|
|
33
|
+
|
|
34
|
+
Required fields: name, slug, color.
|
|
35
|
+
"""
|
|
36
|
+
try:
|
|
37
|
+
return serialize(get_client(api_token).environments.create(body))
|
|
38
|
+
except DevhelmError as e:
|
|
39
|
+
return format_error(e)
|
|
40
|
+
|
|
41
|
+
@mcp.tool()
|
|
42
|
+
def update_environment(api_token: str, slug: str, body: dict[str, Any]) -> Any:
|
|
43
|
+
"""Update an environment by slug."""
|
|
44
|
+
try:
|
|
45
|
+
return serialize(get_client(api_token).environments.update(slug, body))
|
|
46
|
+
except DevhelmError as e:
|
|
47
|
+
return format_error(e)
|
|
48
|
+
|
|
49
|
+
@mcp.tool()
|
|
50
|
+
def delete_environment(api_token: str, slug: str) -> str:
|
|
51
|
+
"""Delete an environment by slug."""
|
|
52
|
+
try:
|
|
53
|
+
get_client(api_token).environments.delete(slug)
|
|
54
|
+
return "Environment deleted successfully."
|
|
55
|
+
except DevhelmError as e:
|
|
56
|
+
return format_error(e)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Incident tools — manual and auto-detected incidents."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from devhelm import DevhelmError
|
|
8
|
+
from fastmcp import FastMCP
|
|
9
|
+
|
|
10
|
+
from devhelm_mcp.client import format_error, get_client, serialize
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def register(mcp: FastMCP) -> None:
|
|
14
|
+
@mcp.tool()
|
|
15
|
+
def list_incidents(api_token: str) -> Any:
|
|
16
|
+
"""List all incidents in the workspace."""
|
|
17
|
+
try:
|
|
18
|
+
return serialize(get_client(api_token).incidents.list())
|
|
19
|
+
except DevhelmError as e:
|
|
20
|
+
return format_error(e)
|
|
21
|
+
|
|
22
|
+
@mcp.tool()
|
|
23
|
+
def get_incident(api_token: str, incident_id: str) -> Any:
|
|
24
|
+
"""Get a single incident by ID with full details."""
|
|
25
|
+
try:
|
|
26
|
+
return serialize(get_client(api_token).incidents.get(incident_id))
|
|
27
|
+
except DevhelmError as e:
|
|
28
|
+
return format_error(e)
|
|
29
|
+
|
|
30
|
+
@mcp.tool()
|
|
31
|
+
def create_incident(api_token: str, body: dict[str, Any]) -> Any:
|
|
32
|
+
"""Create a manual incident.
|
|
33
|
+
|
|
34
|
+
Required fields: monitorId, severity (CRITICAL/HIGH/MEDIUM/LOW).
|
|
35
|
+
Optional: message.
|
|
36
|
+
"""
|
|
37
|
+
try:
|
|
38
|
+
return serialize(get_client(api_token).incidents.create(body))
|
|
39
|
+
except DevhelmError as e:
|
|
40
|
+
return format_error(e)
|
|
41
|
+
|
|
42
|
+
@mcp.tool()
|
|
43
|
+
def resolve_incident(
|
|
44
|
+
api_token: str, incident_id: str, message: str | None = None
|
|
45
|
+
) -> Any:
|
|
46
|
+
"""Resolve an active incident, optionally with a resolution message."""
|
|
47
|
+
try:
|
|
48
|
+
return serialize(
|
|
49
|
+
get_client(api_token).incidents.resolve(incident_id, message)
|
|
50
|
+
)
|
|
51
|
+
except DevhelmError as e:
|
|
52
|
+
return format_error(e)
|
|
53
|
+
|
|
54
|
+
@mcp.tool()
|
|
55
|
+
def delete_incident(api_token: str, incident_id: str) -> str:
|
|
56
|
+
"""Delete an incident permanently."""
|
|
57
|
+
try:
|
|
58
|
+
get_client(api_token).incidents.delete(incident_id)
|
|
59
|
+
return "Incident deleted successfully."
|
|
60
|
+
except DevhelmError as e:
|
|
61
|
+
return format_error(e)
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""Monitor tools — HTTP, DNS, TCP, ICMP, MCP, and Heartbeat monitors."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from devhelm import DevhelmError
|
|
8
|
+
from fastmcp import FastMCP
|
|
9
|
+
|
|
10
|
+
from devhelm_mcp.client import format_error, get_client, serialize
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def register(mcp: FastMCP) -> None:
|
|
14
|
+
@mcp.tool()
|
|
15
|
+
def list_monitors(api_token: str) -> Any:
|
|
16
|
+
"""List all uptime monitors in the workspace."""
|
|
17
|
+
try:
|
|
18
|
+
return serialize(get_client(api_token).monitors.list())
|
|
19
|
+
except DevhelmError as e:
|
|
20
|
+
return format_error(e)
|
|
21
|
+
|
|
22
|
+
@mcp.tool()
|
|
23
|
+
def get_monitor(api_token: str, monitor_id: str) -> Any:
|
|
24
|
+
"""Get a single monitor by ID, including its full configuration."""
|
|
25
|
+
try:
|
|
26
|
+
return serialize(get_client(api_token).monitors.get(monitor_id))
|
|
27
|
+
except DevhelmError as e:
|
|
28
|
+
return format_error(e)
|
|
29
|
+
|
|
30
|
+
@mcp.tool()
|
|
31
|
+
def create_monitor(api_token: str, body: dict[str, Any]) -> Any:
|
|
32
|
+
"""Create a new uptime monitor.
|
|
33
|
+
|
|
34
|
+
Required fields: name, type (HTTP/DNS/TCP/ICMP/MCP/HEARTBEAT),
|
|
35
|
+
config (type-specific), frequencySeconds (30-86400).
|
|
36
|
+
"""
|
|
37
|
+
try:
|
|
38
|
+
return serialize(get_client(api_token).monitors.create(body))
|
|
39
|
+
except DevhelmError as e:
|
|
40
|
+
return format_error(e)
|
|
41
|
+
|
|
42
|
+
@mcp.tool()
|
|
43
|
+
def update_monitor(api_token: str, monitor_id: str, body: dict[str, Any]) -> Any:
|
|
44
|
+
"""Update an existing monitor's configuration."""
|
|
45
|
+
try:
|
|
46
|
+
return serialize(get_client(api_token).monitors.update(monitor_id, body))
|
|
47
|
+
except DevhelmError as e:
|
|
48
|
+
return format_error(e)
|
|
49
|
+
|
|
50
|
+
@mcp.tool()
|
|
51
|
+
def delete_monitor(api_token: str, monitor_id: str) -> str:
|
|
52
|
+
"""Delete a monitor permanently."""
|
|
53
|
+
try:
|
|
54
|
+
get_client(api_token).monitors.delete(monitor_id)
|
|
55
|
+
return "Monitor deleted successfully."
|
|
56
|
+
except DevhelmError as e:
|
|
57
|
+
return format_error(e)
|
|
58
|
+
|
|
59
|
+
@mcp.tool()
|
|
60
|
+
def pause_monitor(api_token: str, monitor_id: str) -> Any:
|
|
61
|
+
"""Pause a monitor (stops checking until resumed)."""
|
|
62
|
+
try:
|
|
63
|
+
return serialize(get_client(api_token).monitors.pause(monitor_id))
|
|
64
|
+
except DevhelmError as e:
|
|
65
|
+
return format_error(e)
|
|
66
|
+
|
|
67
|
+
@mcp.tool()
|
|
68
|
+
def resume_monitor(api_token: str, monitor_id: str) -> Any:
|
|
69
|
+
"""Resume a paused monitor."""
|
|
70
|
+
try:
|
|
71
|
+
return serialize(get_client(api_token).monitors.resume(monitor_id))
|
|
72
|
+
except DevhelmError as e:
|
|
73
|
+
return format_error(e)
|
|
74
|
+
|
|
75
|
+
@mcp.tool()
|
|
76
|
+
def test_monitor(api_token: str, monitor_id: str) -> Any:
|
|
77
|
+
"""Trigger an ad-hoc test run for a monitor and return the result."""
|
|
78
|
+
try:
|
|
79
|
+
return serialize(get_client(api_token).monitors.test(monitor_id))
|
|
80
|
+
except DevhelmError as e:
|
|
81
|
+
return format_error(e)
|
|
82
|
+
|
|
83
|
+
@mcp.tool()
|
|
84
|
+
def list_monitor_results(
|
|
85
|
+
api_token: str,
|
|
86
|
+
monitor_id: str,
|
|
87
|
+
cursor: str | None = None,
|
|
88
|
+
limit: int | None = None,
|
|
89
|
+
) -> Any:
|
|
90
|
+
"""List recent check results for a monitor (cursor-paginated)."""
|
|
91
|
+
try:
|
|
92
|
+
page = get_client(api_token).monitors.results(
|
|
93
|
+
monitor_id, cursor=cursor, limit=limit
|
|
94
|
+
)
|
|
95
|
+
return serialize({"items": page.items, "next_cursor": page.next_cursor})
|
|
96
|
+
except DevhelmError as e:
|
|
97
|
+
return format_error(e)
|
|
98
|
+
|
|
99
|
+
@mcp.tool()
|
|
100
|
+
def list_monitor_versions(
|
|
101
|
+
api_token: str, monitor_id: str, page: int = 0, size: int = 20
|
|
102
|
+
) -> Any:
|
|
103
|
+
"""List version history for a monitor."""
|
|
104
|
+
try:
|
|
105
|
+
result = get_client(api_token).monitors.versions(
|
|
106
|
+
monitor_id, page=page, size=size
|
|
107
|
+
)
|
|
108
|
+
return serialize(
|
|
109
|
+
{"items": result.items, "page": result.page, "total": result.total}
|
|
110
|
+
)
|
|
111
|
+
except DevhelmError as e:
|
|
112
|
+
return format_error(e)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""Notification policy tools — routing rules for alerts."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from devhelm import DevhelmError
|
|
8
|
+
from fastmcp import FastMCP
|
|
9
|
+
|
|
10
|
+
from devhelm_mcp.client import format_error, get_client, serialize
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def register(mcp: FastMCP) -> None:
|
|
14
|
+
@mcp.tool()
|
|
15
|
+
def list_notification_policies(api_token: str) -> Any:
|
|
16
|
+
"""List all notification policies in the workspace."""
|
|
17
|
+
try:
|
|
18
|
+
return serialize(get_client(api_token).notification_policies.list())
|
|
19
|
+
except DevhelmError as e:
|
|
20
|
+
return format_error(e)
|
|
21
|
+
|
|
22
|
+
@mcp.tool()
|
|
23
|
+
def get_notification_policy(api_token: str, policy_id: str) -> Any:
|
|
24
|
+
"""Get a notification policy by ID."""
|
|
25
|
+
try:
|
|
26
|
+
return serialize(get_client(api_token).notification_policies.get(policy_id))
|
|
27
|
+
except DevhelmError as e:
|
|
28
|
+
return format_error(e)
|
|
29
|
+
|
|
30
|
+
@mcp.tool()
|
|
31
|
+
def create_notification_policy(api_token: str, body: dict[str, Any]) -> Any:
|
|
32
|
+
"""Create a notification policy.
|
|
33
|
+
|
|
34
|
+
Required fields: name, monitorIds, channelIds, severity.
|
|
35
|
+
"""
|
|
36
|
+
try:
|
|
37
|
+
return serialize(get_client(api_token).notification_policies.create(body))
|
|
38
|
+
except DevhelmError as e:
|
|
39
|
+
return format_error(e)
|
|
40
|
+
|
|
41
|
+
@mcp.tool()
|
|
42
|
+
def update_notification_policy(
|
|
43
|
+
api_token: str, policy_id: str, body: dict[str, Any]
|
|
44
|
+
) -> Any:
|
|
45
|
+
"""Update a notification policy."""
|
|
46
|
+
try:
|
|
47
|
+
return serialize(
|
|
48
|
+
get_client(api_token).notification_policies.update(policy_id, body)
|
|
49
|
+
)
|
|
50
|
+
except DevhelmError as e:
|
|
51
|
+
return format_error(e)
|
|
52
|
+
|
|
53
|
+
@mcp.tool()
|
|
54
|
+
def delete_notification_policy(api_token: str, policy_id: str) -> str:
|
|
55
|
+
"""Delete a notification policy."""
|
|
56
|
+
try:
|
|
57
|
+
get_client(api_token).notification_policies.delete(policy_id)
|
|
58
|
+
return "Notification policy deleted successfully."
|
|
59
|
+
except DevhelmError as e:
|
|
60
|
+
return format_error(e)
|
|
61
|
+
|
|
62
|
+
@mcp.tool()
|
|
63
|
+
def test_notification_policy(api_token: str, policy_id: str) -> str:
|
|
64
|
+
"""Send a test dispatch to verify a notification policy's routing."""
|
|
65
|
+
try:
|
|
66
|
+
get_client(api_token).notification_policies.test(policy_id)
|
|
67
|
+
return "Test dispatch sent successfully."
|
|
68
|
+
except DevhelmError as e:
|
|
69
|
+
return format_error(e)
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""Resource group tools — logical grouping of monitors."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from devhelm import DevhelmError
|
|
8
|
+
from fastmcp import FastMCP
|
|
9
|
+
|
|
10
|
+
from devhelm_mcp.client import format_error, get_client, serialize
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def register(mcp: FastMCP) -> None:
|
|
14
|
+
@mcp.tool()
|
|
15
|
+
def list_resource_groups(api_token: str) -> Any:
|
|
16
|
+
"""List all resource groups in the workspace."""
|
|
17
|
+
try:
|
|
18
|
+
return serialize(get_client(api_token).resource_groups.list())
|
|
19
|
+
except DevhelmError as e:
|
|
20
|
+
return format_error(e)
|
|
21
|
+
|
|
22
|
+
@mcp.tool()
|
|
23
|
+
def get_resource_group(api_token: str, group_id: str) -> Any:
|
|
24
|
+
"""Get a resource group by ID."""
|
|
25
|
+
try:
|
|
26
|
+
return serialize(get_client(api_token).resource_groups.get(group_id))
|
|
27
|
+
except DevhelmError as e:
|
|
28
|
+
return format_error(e)
|
|
29
|
+
|
|
30
|
+
@mcp.tool()
|
|
31
|
+
def create_resource_group(api_token: str, body: dict[str, Any]) -> Any:
|
|
32
|
+
"""Create a resource group.
|
|
33
|
+
|
|
34
|
+
Required fields: name. Optional: description.
|
|
35
|
+
"""
|
|
36
|
+
try:
|
|
37
|
+
return serialize(get_client(api_token).resource_groups.create(body))
|
|
38
|
+
except DevhelmError as e:
|
|
39
|
+
return format_error(e)
|
|
40
|
+
|
|
41
|
+
@mcp.tool()
|
|
42
|
+
def update_resource_group(
|
|
43
|
+
api_token: str, group_id: str, body: dict[str, Any]
|
|
44
|
+
) -> Any:
|
|
45
|
+
"""Update a resource group."""
|
|
46
|
+
try:
|
|
47
|
+
return serialize(
|
|
48
|
+
get_client(api_token).resource_groups.update(group_id, body)
|
|
49
|
+
)
|
|
50
|
+
except DevhelmError as e:
|
|
51
|
+
return format_error(e)
|
|
52
|
+
|
|
53
|
+
@mcp.tool()
|
|
54
|
+
def delete_resource_group(api_token: str, group_id: str) -> str:
|
|
55
|
+
"""Delete a resource group."""
|
|
56
|
+
try:
|
|
57
|
+
get_client(api_token).resource_groups.delete(group_id)
|
|
58
|
+
return "Resource group deleted successfully."
|
|
59
|
+
except DevhelmError as e:
|
|
60
|
+
return format_error(e)
|
|
61
|
+
|
|
62
|
+
@mcp.tool()
|
|
63
|
+
def add_resource_group_member(
|
|
64
|
+
api_token: str, group_id: str, body: dict[str, Any]
|
|
65
|
+
) -> Any:
|
|
66
|
+
"""Add a monitor to a resource group.
|
|
67
|
+
|
|
68
|
+
Required fields: monitorId.
|
|
69
|
+
"""
|
|
70
|
+
try:
|
|
71
|
+
return serialize(
|
|
72
|
+
get_client(api_token).resource_groups.add_member(group_id, body)
|
|
73
|
+
)
|
|
74
|
+
except DevhelmError as e:
|
|
75
|
+
return format_error(e)
|
|
76
|
+
|
|
77
|
+
@mcp.tool()
|
|
78
|
+
def remove_resource_group_member(
|
|
79
|
+
api_token: str, group_id: str, member_id: str
|
|
80
|
+
) -> str:
|
|
81
|
+
"""Remove a monitor from a resource group."""
|
|
82
|
+
try:
|
|
83
|
+
get_client(api_token).resource_groups.remove_member(group_id, member_id)
|
|
84
|
+
return "Member removed from resource group."
|
|
85
|
+
except DevhelmError as e:
|
|
86
|
+
return format_error(e)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Secret tools — encrypted secrets for monitor authentication."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from devhelm import DevhelmError
|
|
8
|
+
from fastmcp import FastMCP
|
|
9
|
+
|
|
10
|
+
from devhelm_mcp.client import format_error, get_client, serialize
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def register(mcp: FastMCP) -> None:
|
|
14
|
+
@mcp.tool()
|
|
15
|
+
def list_secrets(api_token: str) -> Any:
|
|
16
|
+
"""List all secrets (metadata only, values are never returned)."""
|
|
17
|
+
try:
|
|
18
|
+
return serialize(get_client(api_token).secrets.list())
|
|
19
|
+
except DevhelmError as e:
|
|
20
|
+
return format_error(e)
|
|
21
|
+
|
|
22
|
+
@mcp.tool()
|
|
23
|
+
def create_secret(api_token: str, body: dict[str, Any]) -> Any:
|
|
24
|
+
"""Create an encrypted secret.
|
|
25
|
+
|
|
26
|
+
Required fields: key, value. The value is encrypted at rest
|
|
27
|
+
and can be referenced in monitor auth configs as {{secrets.KEY}}.
|
|
28
|
+
"""
|
|
29
|
+
try:
|
|
30
|
+
return serialize(get_client(api_token).secrets.create(body))
|
|
31
|
+
except DevhelmError as e:
|
|
32
|
+
return format_error(e)
|
|
33
|
+
|
|
34
|
+
@mcp.tool()
|
|
35
|
+
def update_secret(api_token: str, key: str, body: dict[str, Any]) -> Any:
|
|
36
|
+
"""Update a secret's value by key."""
|
|
37
|
+
try:
|
|
38
|
+
return serialize(get_client(api_token).secrets.update(key, body))
|
|
39
|
+
except DevhelmError as e:
|
|
40
|
+
return format_error(e)
|
|
41
|
+
|
|
42
|
+
@mcp.tool()
|
|
43
|
+
def delete_secret(api_token: str, key: str) -> str:
|
|
44
|
+
"""Delete a secret by key."""
|
|
45
|
+
try:
|
|
46
|
+
get_client(api_token).secrets.delete(key)
|
|
47
|
+
return "Secret deleted successfully."
|
|
48
|
+
except DevhelmError as e:
|
|
49
|
+
return format_error(e)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Status tools — dashboard overview."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from devhelm import DevhelmError
|
|
8
|
+
from fastmcp import FastMCP
|
|
9
|
+
|
|
10
|
+
from devhelm_mcp.client import format_error, get_client, serialize
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def register(mcp: FastMCP) -> None:
|
|
14
|
+
@mcp.tool()
|
|
15
|
+
def get_status_overview(api_token: str) -> Any:
|
|
16
|
+
"""Get the dashboard overview with monitor counts,
|
|
17
|
+
incident summary, and uptime stats."""
|
|
18
|
+
try:
|
|
19
|
+
return serialize(get_client(api_token).status.overview())
|
|
20
|
+
except DevhelmError as e:
|
|
21
|
+
return format_error(e)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Tag tools — organize monitors with tags."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from devhelm import DevhelmError
|
|
8
|
+
from fastmcp import FastMCP
|
|
9
|
+
|
|
10
|
+
from devhelm_mcp.client import format_error, get_client, serialize
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def register(mcp: FastMCP) -> None:
|
|
14
|
+
@mcp.tool()
|
|
15
|
+
def list_tags(api_token: str) -> Any:
|
|
16
|
+
"""List all tags in the workspace."""
|
|
17
|
+
try:
|
|
18
|
+
return serialize(get_client(api_token).tags.list())
|
|
19
|
+
except DevhelmError as e:
|
|
20
|
+
return format_error(e)
|
|
21
|
+
|
|
22
|
+
@mcp.tool()
|
|
23
|
+
def get_tag(api_token: str, tag_id: str) -> Any:
|
|
24
|
+
"""Get a tag by ID."""
|
|
25
|
+
try:
|
|
26
|
+
return serialize(get_client(api_token).tags.get(tag_id))
|
|
27
|
+
except DevhelmError as e:
|
|
28
|
+
return format_error(e)
|
|
29
|
+
|
|
30
|
+
@mcp.tool()
|
|
31
|
+
def create_tag(api_token: str, body: dict[str, Any]) -> Any:
|
|
32
|
+
"""Create a tag.
|
|
33
|
+
|
|
34
|
+
Required fields: name. Optional: color.
|
|
35
|
+
"""
|
|
36
|
+
try:
|
|
37
|
+
return serialize(get_client(api_token).tags.create(body))
|
|
38
|
+
except DevhelmError as e:
|
|
39
|
+
return format_error(e)
|
|
40
|
+
|
|
41
|
+
@mcp.tool()
|
|
42
|
+
def update_tag(api_token: str, tag_id: str, body: dict[str, Any]) -> Any:
|
|
43
|
+
"""Update a tag."""
|
|
44
|
+
try:
|
|
45
|
+
return serialize(get_client(api_token).tags.update(tag_id, body))
|
|
46
|
+
except DevhelmError as e:
|
|
47
|
+
return format_error(e)
|
|
48
|
+
|
|
49
|
+
@mcp.tool()
|
|
50
|
+
def delete_tag(api_token: str, tag_id: str) -> str:
|
|
51
|
+
"""Delete a tag."""
|
|
52
|
+
try:
|
|
53
|
+
get_client(api_token).tags.delete(tag_id)
|
|
54
|
+
return "Tag deleted successfully."
|
|
55
|
+
except DevhelmError as e:
|
|
56
|
+
return format_error(e)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Webhook tools — outgoing webhook endpoint management."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from devhelm import DevhelmError
|
|
8
|
+
from fastmcp import FastMCP
|
|
9
|
+
|
|
10
|
+
from devhelm_mcp.client import format_error, get_client, serialize
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def register(mcp: FastMCP) -> None:
|
|
14
|
+
@mcp.tool()
|
|
15
|
+
def list_webhooks(api_token: str) -> Any:
|
|
16
|
+
"""List all webhook endpoints in the workspace."""
|
|
17
|
+
try:
|
|
18
|
+
return serialize(get_client(api_token).webhooks.list())
|
|
19
|
+
except DevhelmError as e:
|
|
20
|
+
return format_error(e)
|
|
21
|
+
|
|
22
|
+
@mcp.tool()
|
|
23
|
+
def get_webhook(api_token: str, webhook_id: str) -> Any:
|
|
24
|
+
"""Get a webhook endpoint by ID."""
|
|
25
|
+
try:
|
|
26
|
+
return serialize(get_client(api_token).webhooks.get(webhook_id))
|
|
27
|
+
except DevhelmError as e:
|
|
28
|
+
return format_error(e)
|
|
29
|
+
|
|
30
|
+
@mcp.tool()
|
|
31
|
+
def create_webhook(api_token: str, body: dict[str, Any]) -> Any:
|
|
32
|
+
"""Create a webhook endpoint.
|
|
33
|
+
|
|
34
|
+
Required fields: url, events (list of event types to subscribe to).
|
|
35
|
+
"""
|
|
36
|
+
try:
|
|
37
|
+
return serialize(get_client(api_token).webhooks.create(body))
|
|
38
|
+
except DevhelmError as e:
|
|
39
|
+
return format_error(e)
|
|
40
|
+
|
|
41
|
+
@mcp.tool()
|
|
42
|
+
def update_webhook(api_token: str, webhook_id: str, body: dict[str, Any]) -> Any:
|
|
43
|
+
"""Update a webhook endpoint."""
|
|
44
|
+
try:
|
|
45
|
+
return serialize(get_client(api_token).webhooks.update(webhook_id, body))
|
|
46
|
+
except DevhelmError as e:
|
|
47
|
+
return format_error(e)
|
|
48
|
+
|
|
49
|
+
@mcp.tool()
|
|
50
|
+
def delete_webhook(api_token: str, webhook_id: str) -> str:
|
|
51
|
+
"""Delete a webhook endpoint."""
|
|
52
|
+
try:
|
|
53
|
+
get_client(api_token).webhooks.delete(webhook_id)
|
|
54
|
+
return "Webhook deleted successfully."
|
|
55
|
+
except DevhelmError as e:
|
|
56
|
+
return format_error(e)
|
|
57
|
+
|
|
58
|
+
@mcp.tool()
|
|
59
|
+
def test_webhook(api_token: str, webhook_id: str) -> Any:
|
|
60
|
+
"""Send a test event to a webhook endpoint to verify it works."""
|
|
61
|
+
try:
|
|
62
|
+
return serialize(get_client(api_token).webhooks.test(webhook_id))
|
|
63
|
+
except DevhelmError as e:
|
|
64
|
+
return format_error(e)
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: devhelm-mcp-server
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: DevHelm MCP server — AI agent access to monitors, incidents, alerting, and more
|
|
5
|
+
Project-URL: Homepage, https://devhelm.io
|
|
6
|
+
Project-URL: Repository, https://github.com/devhelmhq/mcp-server
|
|
7
|
+
Project-URL: Documentation, https://docs.devhelm.io
|
|
8
|
+
Author-email: DevHelm <hello@devhelm.io>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: ai-agent,devhelm,mcp,model-context-protocol,monitoring
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
20
|
+
Requires-Python: >=3.11
|
|
21
|
+
Requires-Dist: devhelm>=0.1.0
|
|
22
|
+
Requires-Dist: fastmcp>=2.0.0
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# DevHelm MCP Server
|
|
26
|
+
|
|
27
|
+
[Model Context Protocol](https://modelcontextprotocol.io) (MCP) server for [DevHelm](https://devhelm.io) — gives AI coding assistants (Cursor, Claude Desktop, Windsurf, etc.) access to your uptime monitors, incidents, alerting, and more.
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
### Hosted (recommended)
|
|
32
|
+
|
|
33
|
+
Use the hosted server at `mcp.devhelm.io`. Two connection modes:
|
|
34
|
+
|
|
35
|
+
**Bearer auth:**
|
|
36
|
+
```
|
|
37
|
+
URL: https://mcp.devhelm.io/mcp
|
|
38
|
+
Authorization: Bearer <your-api-token>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**API key in URL** (for clients that only accept a URL):
|
|
42
|
+
```
|
|
43
|
+
URL: https://mcp.devhelm.io/<your-api-token>/mcp
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Local (stdio)
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pip install devhelm-mcp-server
|
|
50
|
+
export DEVHELM_API_TOKEN=your-token
|
|
51
|
+
devhelm-mcp
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Cursor / Claude Desktop
|
|
55
|
+
|
|
56
|
+
Add to your MCP config:
|
|
57
|
+
|
|
58
|
+
```json
|
|
59
|
+
{
|
|
60
|
+
"mcpServers": {
|
|
61
|
+
"devhelm": {
|
|
62
|
+
"url": "https://mcp.devhelm.io/<your-api-token>/mcp"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Available Tools
|
|
69
|
+
|
|
70
|
+
| Category | Tools |
|
|
71
|
+
|----------|-------|
|
|
72
|
+
| **Monitors** | list, get, create, update, delete, pause, resume, test, results, versions |
|
|
73
|
+
| **Incidents** | list, get, create, resolve, delete |
|
|
74
|
+
| **Alert Channels** | list, get, create, update, delete, test |
|
|
75
|
+
| **Notification Policies** | list, get, create, update, delete, test |
|
|
76
|
+
| **Environments** | list, get, create, update, delete |
|
|
77
|
+
| **Secrets** | list, create, update, delete |
|
|
78
|
+
| **Tags** | list, get, create, update, delete |
|
|
79
|
+
| **Resource Groups** | list, get, create, update, delete, add member, remove member |
|
|
80
|
+
| **Webhooks** | list, get, create, update, delete, test |
|
|
81
|
+
| **API Keys** | list, create, revoke, delete |
|
|
82
|
+
| **Dependencies** | list, get, track, delete |
|
|
83
|
+
| **Deploy Lock** | acquire, current, release, force-release |
|
|
84
|
+
| **Status** | overview |
|
|
85
|
+
|
|
86
|
+
## Development
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
uv sync
|
|
90
|
+
make dev # Start with MCP Inspector (stdio)
|
|
91
|
+
make serve # Start HTTP server on :8000
|
|
92
|
+
make test # Run unit tests
|
|
93
|
+
make lint # Check formatting
|
|
94
|
+
make typecheck # Run mypy
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## License
|
|
98
|
+
|
|
99
|
+
MIT
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
devhelm_mcp/__init__.py,sha256=oeWh01V0ettPmXAYkfEbHIp7OjSnjbcn8FarAiJeizk,89
|
|
2
|
+
devhelm_mcp/client.py,sha256=hOnPosQwzQA5jdktPIvX6FepkjSDa9iBbSGx3H9af3w,1043
|
|
3
|
+
devhelm_mcp/server.py,sha256=tX2y32fDN7HYOm1NwKCuaNbmgcyzB4_5_tPhbao4EG4,4140
|
|
4
|
+
devhelm_mcp/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
devhelm_mcp/tools/alert_channels.py,sha256=Sm26AGa2KLV3-AtkNZ85AB2m7lcQZbGwVvMzldqsAWY,2389
|
|
6
|
+
devhelm_mcp/tools/api_keys.py,sha256=W9l53NYOfAwzCOqL2KWI8_GwgTQdmb4v7w_Ao9-5QM4,1577
|
|
7
|
+
devhelm_mcp/tools/dependencies.py,sha256=hoM7-bw2GW3pvtnLoTkgxxBiGROqiyW8GH3zNEW837o,1528
|
|
8
|
+
devhelm_mcp/tools/deploy_lock.py,sha256=6NGSMWonp3_k2UmBJWqwrdq1B6cecL7y-teveOtzJhU,1670
|
|
9
|
+
devhelm_mcp/tools/environments.py,sha256=TO2giEYoO3kmjuQWjZQgetqjNRb3Ky6-AM0JVqTstm0,1858
|
|
10
|
+
devhelm_mcp/tools/incidents.py,sha256=AeyxqHrpU69EO_R0UEPPYn_Q5gu0n-WRBi4-qCIgZqU,1987
|
|
11
|
+
devhelm_mcp/tools/monitors.py,sha256=YkwDIWo_CIIoPDqnM48vIiaNg3RKNOaxmq8_TMLhP6s,3929
|
|
12
|
+
devhelm_mcp/tools/notification_policies.py,sha256=Q0DXTgylzuAprVH6Y2fmRz2LFrfi0hCNPp51PER4vLQ,2397
|
|
13
|
+
devhelm_mcp/tools/resource_groups.py,sha256=b58FsTRx9UGi4kCPx9MOeC9Ncapn3j63eSr7zsTBfMc,2745
|
|
14
|
+
devhelm_mcp/tools/secrets.py,sha256=2Ql5Ijk7VTXpcBToQwhQ5bbK71Eak7XlbG5Sqzga0gI,1614
|
|
15
|
+
devhelm_mcp/tools/status.py,sha256=0aUXHnhVa5i0DrmTHR358eNLxxO3MQqmqONVpE23_Lo,597
|
|
16
|
+
devhelm_mcp/tools/tags.py,sha256=dNfsT6GC49MTahR48V0ztgCXOfs296QnUfvyVaHFZZc,1666
|
|
17
|
+
devhelm_mcp/tools/webhooks.py,sha256=PGesOmYP7EggC7LP66hlNULF4pQGVZK2HISa4I3bEXc,2153
|
|
18
|
+
devhelm_mcp_server-0.1.0.dist-info/METADATA,sha256=2mYubQ1qYE9X93Qq2iftZ-worDYr3HRgOZtX7TTlMZs,2938
|
|
19
|
+
devhelm_mcp_server-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
20
|
+
devhelm_mcp_server-0.1.0.dist-info/entry_points.txt,sha256=_6LMWTOaUxcHfZrXbMpfVarUNYdksit4lQmiMD1_Fo8,56
|
|
21
|
+
devhelm_mcp_server-0.1.0.dist-info/licenses/LICENSE,sha256=z9qxDQi8GcvrzblluNus8VeTsTHcq4tLnTcoh5pTtAk,1064
|
|
22
|
+
devhelm_mcp_server-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 DevHelm
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|