get_monitor
Retrieve a single monitor's full configuration by providing its unique ID.
Instructions
Get a single monitor by ID, including its full configuration.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| monitor_id | Yes |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
| result | Yes |
Implementation Reference
- src/devhelm_mcp/tools/monitors.py:61-67 (handler)The get_monitor tool handler: takes a monitor_id and optional api_token, calls the DevHelm SDK client's monitors.get() method, and serializes the result. Registered via @mcp.tool() decorator inside the register() function.
@mcp.tool() def get_monitor(monitor_id: str, api_token: str | None = None) -> ToolResult: """Get a single monitor by ID, including its full configuration.""" try: return serialize(get_client(api_token).monitors.get(monitor_id)) except DevhelmError as e: raise_tool_error(e) - src/devhelm_mcp/server.py:90-110 (registration)The monitors module is imported and its register() function is called in the server startup loop (line 109-110: for mod in ALL_TOOL_MODULES: mod.register(mcp)). This is how get_monitor gets registered on the FastMCP instance.
ALL_TOOL_MODULES = [ monitors, incidents, forensics, alert_channels, notification_policies, environments, secrets, tags, resource_groups, webhooks, api_keys, dependencies, deploy_lock, maintenance_windows, status, status_pages, ] for mod in ALL_TOOL_MODULES: mod.register(mcp) - src/devhelm_mcp/tools/monitors.py:52-52 (registration)The register() function in monitors.py that decorates get_monitor (and all other monitor tools) with @mcp.tool() to register them on the FastMCP server.
def register(mcp: FastMCP) -> None: - src/devhelm_mcp/client.py:107-136 (helper)get_client() resolves the API token and creates a DevHelm SDK client. This is used by get_monitor to make the API call.
def get_client(api_token: str | None = None) -> Devhelm: """Build a Devhelm SDK client from the user's API token. Token resolution is delegated to :func:`resolve_api_token`, so callers can pass the value through from a tool argument *or* leave it ``None`` and let the helper pick the token up from the active HTTP request's ``Authorization: Bearer …`` header (hosted ``/mcp``) or from the ``DEVHELM_API_TOKEN`` env var (stdio). This is the single seam every tool goes through, so a missing / mistyped token surfaces in exactly one place. Overrides the SDK's default surface (``sdk-py``) with ``mcp`` so the API attributes traffic to the MCP server rather than to bare-SDK use. The SDK's ``X-DevHelm-Sdk-Name`` header is preserved, so the API can still see *which* SDK version this MCP server release is built on for debugging client-version skew. Detecting the host MCP client (Cursor vs Claude Desktop vs ...) is a follow-up: ``fastmcp.Context.session.client_params.clientInfo`` carries that info, but threading Context through every tool would be a wide surgery against the no-callsite-changes goal of this PR. The wire contract already supports ``X-DevHelm-Mcp-Client`` via ``surface_metadata`` so we can layer it in later without an API change. """ return Devhelm( token=resolve_api_token(api_token), base_url=API_BASE_URL, surface="mcp", surface_version=_server_version(), ) - src/devhelm_mcp/client.py:262-293 (helper)serialize() converts the SDK response (Pydantic models) into JSON-safe dicts for the tool result.
def serialize(data: object) -> dict[str, Any] | list[dict[str, Any]]: """Serialize Pydantic models / dicts / lists to JSON-safe shapes. The signature returns the legacy `dict | list-of-dict` envelope that every MCP tool is wired against, but the recursive work is delegated to `_serialize_value` which is fully typed. We narrow at this single boundary, so removing the `Any` from tool implementations only requires changing this function's return type without touching any callers. """ value = _serialize_value(data) if isinstance(value, dict): return value if isinstance(value, list): # Tools always feed `serialize` a list of model instances or # dicts; the recursive call returns a list of either dicts # (Pydantic / dict items) or scalars (which would be a tool # bug). Reject scalars loudly so the LLM sees the error. out: list[dict[str, Any]] = [] for item in value: if not isinstance(item, dict): raise TypeError( "serialize() expected list items to be dicts or Pydantic " f"models, got {type(item).__name__}." ) out.append(item) return out raise TypeError( "serialize() expected a Pydantic model, dict, or list of those; " f"got {type(data).__name__}." )