heartbeat-monitor
Dead-man's-switch for cron jobs & AI agents. Import a crontab to arm one silent-miss alert per job.
Should I use this
Quality & Safety
Based on automated analysis of tool definitions and protocol compliance.
Context Cost
This is the approximate number of tokens consumed each time the server's tools are loaded into a model's context. Higher counts reduce the attention available for other tasks.
Install
One-Click Install
Add this to your `claude_desktop_config.json` file:
{
"mcpServers": {
"heartbeat-monitor": {
"url": "https://cronping.cronping-oren.workers.dev/mcp"
}
}
}Remote endpoints
https://cronping.cronping-oren.workers.dev/mcpstreamable-httpWhat it can do
Tool inventory
Tools (7)
🟡create_heartbeat(name, period_seconds, grace_seconds, email, webhook_url, ...)
Create a Cronping heartbeat monitor (dead-man's-switch). Returns a ping_url to call on each successful run of your agent loop, worker, or cron job, plus a claim_token to manage it. If pings stop arriving within period+grace, Cronping raises an alert. No account required. After creating, send one verification ping (call ping_heartbeat with the returned id) to confirm setup — the check flips from 'new' to UP so you know it's wired correctly. Alternatively, pass pull_url to make a PULL check: Cronping fetches that URL on schedule and alerts if it fails — for targets that can't call a ping URL (a health endpoint, status page, or API).
Input Schema
{
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Label for what is monitored, e.g. \"nightly-backup\" or \"research-agent-loop\"."
},
"period_seconds": {
"type": "number",
"description": "How often you expect a ping, in seconds (min 60). Default 86400 (daily)."
},
"grace_seconds": {
"type": "number",
"description": "Extra slack before marking DOWN, in seconds. Default 3600."
},
"email": {
"type": "string",
"description": "Email address for DOWN and recovery alerts. STRONGLY RECOMMENDED — without an email or webhook_url, Cronping tracks status but cannot notify anyone when the job stops, which defeats the purpose. Use your operator's email."
},
"webhook_url": {
"type": "string",
"description": "https webhook (Slack/Discord/generic) for DOWN and recovery alerts. Set this OR email so someone is actually notified when the monitored job goes silent."
},
"stuck_after": {
"type": "number",
"description": "Optional. Flip to STUCK if the progress_token passed to ping_heartbeat stays identical for this many consecutive runs (default 3, 0 disables). Catches zombie runs that keep pinging but make no progress."
},
"pull_url": {
"type": "string",
"description": "Optional. If set, creates a PULL check instead: Cronping itself fetches this URL every period_seconds and alerts if it fails — use for a target that can't call a ping URL (a public health endpoint, status page, or API). No ping needed."
},
"pull_contains": {
"type": "string",
"description": "Optional (pull checks only). The fetched response body must contain this text to count as UP; otherwise Cronping alerts."
}
},
"required": [
"name"
]
}🟡ping_heartbeat(id, signal, progress_token)
Send a heartbeat signal to a Cronping check. signal="success" (default) marks it UP and resets the timer; "start" records that a run began (for duration); "fail" marks it DOWN immediately and alerts. Optionally pass progress_token (a digest of your run's actual output) on success pings: if the same token repeats for several consecutive runs, the check flips to STUCK — catching a "zombie" job that keeps pinging on schedule while its real work has stalled.
Input Schema
{
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "The check id returned by create_heartbeat."
},
"signal": {
"type": "string",
"enum": [
"success",
"start",
"fail"
],
"description": "success | start | fail. Default success."
},
"progress_token": {
"type": "string",
"description": "Optional. A short digest/hash of this run's output (e.g. last-processed id, row count, content hash). If it stays identical across stuck_after consecutive runs, the check flips to STUCK and alerts."
}
},
"required": [
"id"
]
}🟢get_heartbeat_status(id, claim_token)
Get the live status of a Cronping check (up/down/new, last ping time, ping count, and whether an alert channel is set).
Input Schema
{
"type": "object",
"properties": {
"id": {
"type": "string"
},
"claim_token": {
"type": "string",
"description": "The claim_token returned by create_heartbeat."
}
},
"required": [
"id",
"claim_token"
]
}🟡set_heartbeat_alert(id, claim_token, email, webhook_url)
Add or change the alert channel on an EXISTING Cronping check (use this instead of creating a duplicate). Sets the email and/or webhook (Slack/Discord/generic https) that Cronping notifies when the monitor goes silent or recovers. Without an alert channel a check tracks status but can't warn you — call this once per check to make the dead-man's-switch actually protect you.
Input Schema
{
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "The check id returned by create_heartbeat."
},
"claim_token": {
"type": "string",
"description": "The claim_token returned by create_heartbeat."
},
"email": {
"type": "string",
"description": "Email address to notify (e.g. the operator/owner). Pass empty string to clear."
},
"webhook_url": {
"type": "string",
"description": "Optional https webhook (Slack/Discord/generic) to notify. Pass empty string to clear."
}
},
"required": [
"id",
"claim_token"
]
}🔴import_crontab(crontab, email)
Arm a dead-man's-switch for EVERY line of a crontab in one call. Paste raw crontab text; Cronping parses each schedule, derives the expected interval from the cron expression itself (using the longest gap between runs so weekend/twice-daily schedules don't false-alarm), and creates one heartbeat monitor per job. Returns, for each line, a ping_url plus the same command with `&& curl -fsS <ping_url>` already appended so you can drop it straight back into the crontab. Also anchors each new check's first deadline to the schedule's real next run — so a fresh switch neither pages at t=0 nor sleeps a full period. No account required. Ideal when an agent or operator wants to instrument a whole box's scheduled jobs at once. Up to 20 lines per call. Lines with no fixed interval (e.g. @reboot) are skipped.
Input Schema
{
"type": "object",
"properties": {
"crontab": {
"type": "string",
"description": "Raw crontab text, one job per line (comments and FOO=bar env lines are ignored). Standard 5-field expressions plus @daily/@hourly/@weekly/@monthly/@yearly macros and named days/months are supported."
},
"email": {
"type": "string",
"description": "Optional. Email address for DOWN and recovery alerts, applied to every check created. STRONGLY RECOMMENDED — without it the checks track status but cannot notify anyone."
}
},
"required": [
"crontab"
]
}🔴delete_heartbeat(id, claim_token)
Permanently delete a Cronping heartbeat monitor and all its events. Use this to tear down an ephemeral check your agent created (e.g. at shutdown, or after a one-off job) so it stops being monitored and can never raise a stale DOWN alert. Requires the claim_token returned by create_heartbeat. This is irreversible — the id and ping_url stop working immediately. To silence a check temporarily instead of deleting it, use pause_heartbeat.
Input Schema
{
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "The check id returned by create_heartbeat."
},
"claim_token": {
"type": "string",
"description": "The claim_token returned by create_heartbeat."
}
},
"required": [
"id",
"claim_token"
]
}🟡pause_heartbeat(id, claim_token, paused)
Pause or resume monitoring on an existing Cronping check without deleting it. Pause during planned maintenance or a known downtime window so a silent job does not raise a false DOWN alert; resume to re-arm the dead-man's-switch. While paused, missed pings are ignored and no alerts fire. Requires the claim_token returned by create_heartbeat.
Input Schema
{
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "The check id returned by create_heartbeat."
},
"claim_token": {
"type": "string",
"description": "The claim_token returned by create_heartbeat."
},
"paused": {
"type": "boolean",
"description": "true to pause monitoring, false to resume it. Defaults to true."
}
},
"required": [
"id",
"claim_token"
]
}Community
Evidence