get_policy_snapshot
Retrieve a historical detection policy snapshot by its content-addressed SHA-256 hash, ensuring accurate review of policy state at a specific point in time.
Instructions
Fetch a policy snapshot by its content-addressed SHA-256 hash.
Useful for inspecting the exact detection policy that was active when a specific evaluation or transition happened — the hash is stable, so historical data keeps pointing at the right policy even if the monitor has been edited since.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| hash_hex | Yes |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
| result | Yes |
Implementation Reference
- src/devhelm_mcp/tools/forensics.py:59-71 (handler)The handler function for the 'get_policy_snapshot' tool. It accepts a SHA-256 hash_hex (and optional api_token), calls the SDK's forensics.policy_snapshot method, and serializes the result. Registered via @mcp.tool() decorator.
@mcp.tool() def get_policy_snapshot(hash_hex: str, api_token: str | None = None) -> ToolResult: """Fetch a policy snapshot by its content-addressed SHA-256 hash. Useful for inspecting the exact detection policy that was active when a specific evaluation or transition happened — the hash is stable, so historical data keeps pointing at the right policy even if the monitor has been edited since. """ try: return serialize(get_client(api_token).forensics.policy_snapshot(hash_hex)) except DevhelmError as e: raise_tool_error(e) - src/devhelm_mcp/server.py:109-110 (registration)Tool registration: The 'forensics' module (containing get_policy_snapshot) is registered via server.py's ALL_TOOL_MODULES list which calls forensics.register(mcp).
for mod in ALL_TOOL_MODULES: mod.register(mcp) - src/devhelm_mcp/client.py:131-136 (helper)get_client() helper — builds a Devhelm SDK client used by the handler to call forensics.policy_snapshot().
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-292 (helper)serialize() helper — converts the SDK response into JSON-safe dicts for the MCP tool output.
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__}." ) - src/devhelm_mcp/client.py:205-222 (helper)raise_tool_error helper — converts SDK exceptions into FastMCP ToolError for proper error reporting.
def raise_tool_error(err: DevhelmError) -> NoReturn: """Convert an SDK error into a FastMCP ``ToolError`` so ``isError=true``. Per the MCP spec, upstream API failures must surface as ``CallToolResult.isError = true`` so the LLM can distinguish a tool that *ran but failed* from one that *succeeded with an error message in the response* — those have the same shape on the wire otherwise. The previous behavior returned ``format_error(err)`` as a regular tool return value (``isError = false``), which caused agents to confidently report success after a 4xx/5xx (silent-corruption bug from the round-3 DevEx audit). FastMCP catches the ``ToolError`` raised here and serializes it into ``CallToolResult(isError=True, content=[...])``, preserving the human-readable formatted message for the LLM. See https://modelcontextprotocol.io/specification/server/tools#error-handling. """ raise ToolError(format_error(err)) from err