{"server":{"name":"net.hookpulse/hookpulse","$schema":"https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json","remotes":[{"url":"https://hookpulse.net/mcp","type":"streamable-http"}],"version":"1.0.1","repository":{"url":"https://github.com/wrpbr/hookpulse","source":"github"},"websiteUrl":"https://hookpulse.net","description":"Dead-man switch for cron and webhooks: ingest URL, miss detection, alerts and a status feed."},"_meta":{"io.modelcontextprotocol.registry/official":{"status":"active","isLatest":true,"updatedAt":"2026-10-15T01:18:35.039717Z","publishedAt":"2026-10-15T01:18:35.039717Z","statusChangedAt":"2026-10-15T01:18:35.039717Z"},"dev.protogrid/connectability":{"class":"R0","autonomous":true,"human_steps":[],"preferred":{"kind":"remote","idx":0},"remotes":[{"idx":0,"url":"https://hookpulse.net/mcp","transport":"streamable-http","templated":false,"headers":[],"auth":{"type":"none","location":null,"secret_name":null,"oauth":null},"protocol":{"supported_versions":["2025-06-18"],"stateless":true},"health":{"reachable":true,"uptime_30d":0.9652174,"latency_ms_p50":480,"consecutive_failures":0,"last_probe_at":"2026-10-20T01:56:22.432Z","last_ok_at":"2026-10-20T01:56:22.432Z"}}],"packages":[]},"dev.protogrid/tools":[{"name":"api_index","title":"API index","description":"Full index of the HookPulse API.","annotations":{"title":"API index","readOnlyHint":true,"openWorldHint":false,"idempotentHint":true,"destructiveHint":false},"source":"probe","observed_at":"2026-10-20T01:56:22.432Z"},{"name":"check_site","title":"Check site","description":"Check a site monitor now. One call every 10 seconds per destination host. One call every 10 seconds per destination host, for everyone: the button cannot hammer a site. The first state of a new site comes from here, without an alert — you are looking at it. After that, the state and the alerts come from the check that runs every minute.","annotations":{"title":"Check site","readOnlyHint":false,"openWorldHint":true,"idempotentHint":false,"destructiveHint":false},"source":"probe","observed_at":"2026-10-20T01:56:22.432Z"},{"name":"contact","title":"Contact","description":"Write to the people behind the product: a question, or a sponsorship/partnership/advertising proposal. One route for a question and for a sponsorship, partnership or ad proposal (`tipo`, with the placements of `GET /api/partners`). One message every 10 seconds per network: one that arrives sooner waits its turn and then goes out — no error. The message reaches the team by e-mail, with `email` as the reply address. For voice, send multipart/form-data: `dados` is this JSON (up to 32 KiB), and `audio` is one WebM, Ogg or M4A audio file (up to 2 MB). Audio can replace message. The browser records up to two minutes. Audio is attached to the owner's e-mail.","annotations":{"title":"Contact","readOnlyHint":false,"openWorldHint":false,"idempotentHint":false,"destructiveHint":false},"source":"probe","observed_at":"2026-10-20T01:56:22.432Z"},{"name":"create_endpoint","title":"Create endpoint","description":"Create a monitor for a routine. Use cron, tz and grace_sec for a schedule, or interval_sec for a fixed check-in interval. Set max_duration_sec to detect an open run that exceeds its time limit. With `check` the monitor is a site: HookPulse calls `check.url` every `interval_sec` (default 300) and alerts when it goes down — two failed checks in a row, one minute apart, from a server whose own connection is proven — and when it answers again. `{\"check\":{\"url\":\"https://example.com/\"}}` is enough; the name defaults to the host. A site has no `cron`, no `max_duration_sec` and no ingest URL. This response is the only one that shows the monitor's `token` and the `templates` — keep them. A miss alerts at most once per 24h — or per `alert_repeat_sec`, or per interval, whichever is longer. Send `cron`+`tz`+`grace_sec` instead of `interval_sec` for a real schedule: a 03:00 backup is late at 03:01:30, not 24 hours later. `max_duration_sec` catches the other failure: a run that starts and hangs, which plain silence detection only notices at the next scheduled time.","annotations":{"title":"Create endpoint","readOnlyHint":false,"openWorldHint":true,"idempotentHint":false,"destructiveHint":false},"source":"probe","observed_at":"2026-10-20T01:56:22.432Z"},{"name":"create_guest","title":"Create guest","description":"Creates a guest token hp_… Keep the token: without it there is no way back to the monitors, unless you have already tied them to an e-mail.","annotations":{"title":"Create guest","readOnlyHint":false,"openWorldHint":false,"idempotentHint":false,"destructiveHint":false},"source":"probe","observed_at":"2026-10-20T01:56:22.432Z"},{"name":"create_site","title":"Create site","description":"Monitor a website: HookPulse calls check.url every interval_sec (default 300) and alerts when it goes down and when it is back. With `check` the monitor is a site: HookPulse calls `check.url` every `interval_sec` (default 300) and alerts when it goes down — two failed checks in a row, one minute apart, from a server whose own connection is proven — and when it answers again. `{\"check\":{\"url\":\"https://example.com/\"}}` is enough; the name defaults to the host. A site has no `cron`, no `max_duration_sec` and no ingest URL. This response is the only one that shows the monitor's `token` and the `templates` — keep them. A miss alerts at most once per 24h — or per `alert_repeat_sec`, or per interval, whichever is longer. Send `cron`+`tz`+`grace_sec` instead of `interval_sec` for a real schedule: a 03:00 backup is late at 03:01:30, not 24 hours later. `max_duration_sec` catches the other failure: a run that starts and hangs, which plain silence detection only notices at the next scheduled time.","annotations":{"title":"Create site","readOnlyHint":false,"openWorldHint":true,"idempotentHint":false,"destructiveHint":false},"source":"probe","observed_at":"2026-10-20T01:56:22.432Z"},{"name":"delete_endpoint","title":"Delete endpoint","description":"Removes the owner's endpoint.","annotations":{"title":"Delete endpoint","readOnlyHint":false,"openWorldHint":false,"idempotentHint":true,"destructiveHint":true},"source":"probe","observed_at":"2026-10-20T01:56:22.432Z"},{"name":"get_endpoint","title":"Get endpoint","description":"Status of one endpoint (use the endpoint token if you have it). The monitor token only reads: it lets you put the state on a third-party dashboard without handing over the owner's credential. With a schedule it is here that `next_expected_at` says when the next run is due.","annotations":{"title":"Get endpoint","readOnlyHint":true,"openWorldHint":false,"idempotentHint":true,"destructiveHint":false},"source":"probe","observed_at":"2026-10-20T01:56:22.432Z"},{"name":"health","title":"Health","description":"Liveness.","annotations":{"title":"Health","readOnlyHint":true,"openWorldHint":false,"idempotentHint":true,"destructiveHint":false},"source":"probe","observed_at":"2026-10-20T01:56:22.432Z"},{"name":"import_crontab","title":"Import crontab","description":"Reads a crontab and proposes one monitor per scheduled line. Without apply=1 nothing is written; the proposal says what was ignored and why. Passwords in the command are redacted. The body is the crontab itself, as `text/plain`. Without `?apply=1` **nothing is written**: you get the proposal — what would become a monitor, what was ignored and why, and what still fits in the free tier. With `apply=1` it is all or nothing, under the same rules as `POST /api/endpoints`. Comments, variables and `@reboot` are ignored (there is no way to know when to expect a reboot); `@daily` and friends become the equivalent expression. The command becomes the monitor name, with passwords and tokens redacted first — the name travels in the alert e-mail, in the public dashboard and in the badge.","annotations":{"title":"Import crontab","readOnlyHint":false,"openWorldHint":false,"idempotentHint":false,"destructiveHint":false},"source":"probe","observed_at":"2026-10-20T01:56:22.432Z"},{"name":"ingest_key","title":"Ingest key","description":"Returns (minting the first time) the key for /in/<key>/<slug>, where a ping creates its own monitor inside the free tier. Instruments a whole fleet with no setup call per job. One key per owner. The key survives signing in: the crontab you deployed keeps working.","annotations":{"title":"Ingest key","readOnlyHint":true,"openWorldHint":false,"idempotentHint":true,"destructiveHint":false},"source":"probe","observed_at":"2026-10-20T01:56:22.432Z"},{"name":"ingest_key_rotate","title":"Ingest key rotate","description":"Rotates the ingest key; the previous one stops creating monitors. Existing ones keep working. Monitors already created keep working — rotating only revokes the power to create new ones.","annotations":{"title":"Ingest key rotate","readOnlyHint":false,"openWorldHint":false,"idempotentHint":true,"destructiveHint":true},"source":"probe","observed_at":"2026-10-20T01:56:22.432Z"},{"name":"list_endpoints","title":"List endpoints","description":"Lists the guest's/session's endpoints.","annotations":{"title":"List endpoints","readOnlyHint":true,"openWorldHint":false,"idempotentHint":true,"destructiveHint":false},"source":"probe","observed_at":"2026-10-20T01:56:22.432Z"},{"name":"list_events","title":"List events","description":"Latest ingest pings.","annotations":{"title":"List events","readOnlyHint":true,"openWorldHint":false,"idempotentHint":true,"destructiveHint":false},"source":"probe","observed_at":"2026-10-20T01:56:22.432Z"},{"name":"list_sites","title":"List sites","description":"The state of every site you monitor (up, down, waiting, paused), with the last check.","annotations":{"title":"List sites","readOnlyHint":true,"openWorldHint":false,"idempotentHint":true,"destructiveHint":false},"source":"probe","observed_at":"2026-10-20T01:56:22.432Z"},{"name":"list_templates","title":"List templates","description":"Ingest snippets and the JSON HookPulse POSTs on a miss. It exists so nobody guesses the alert format: `miss_json` and `recovery_json` here are the same bodies that arrive at your `alert_url`. Slack and Discord receive only the field they read.","annotations":{"title":"List templates","readOnlyHint":true,"openWorldHint":false,"idempotentHint":true,"destructiveHint":false},"source":"probe","observed_at":"2026-10-20T01:56:22.432Z"},{"name":"ping_ingest","title":"Ping ingest","description":"Simulates the cron: GET on /in/:id. No credential on purpose — the caller is a cron, and the ID is already the secret.","annotations":{"title":"Ping ingest","readOnlyHint":true,"openWorldHint":false,"idempotentHint":true,"destructiveHint":false},"source":"probe","observed_at":"2026-10-20T01:56:22.432Z"},{"name":"ping_slug","title":"Ping slug","description":"Create a monitor on its first check-in. The same slug uses the same monitor. A rejected creation writes no monitor or event. The slug becomes the monitor name and its identity: the same slug always means the same monitor, so a retry never creates a second one. Normalised to lowercase, `[a-z0-9-]`, 40 chars. Works by GET or POST.","annotations":{"title":"Ping slug","readOnlyHint":true,"openWorldHint":false,"idempotentHint":true,"destructiveHint":false},"source":"probe","observed_at":"2026-10-20T01:56:22.432Z"},{"name":"signal_exit_code","title":"Signal exit code","description":"The shell exit status: 0 behaves like a ping, 1-255 record the code as a failure. `curl -fsS $ORIGIN/in/ID/$?` at the end of a script is the whole integration. `0` behaves exactly like a plain ping; 1 to 255 record the code and leave the failure for the cron.","annotations":{"title":"Signal exit code","readOnlyHint":true,"openWorldHint":false,"idempotentHint":true,"destructiveHint":false},"source":"probe","observed_at":"2026-10-20T01:56:22.432Z"},{"name":"signal_fail","title":"Signal fail","description":"Record a failed run. The next five-minute check sends the alert with reason fail. The monitor's alert_repeat_sec controls repeated alerts; the default is 24 hours. Failing is not proof of life either: the clock keeps running. The ingest never sends e-mail and never calls out — it records `alert_pending` and the cron turns it into an alert with `reason: \"fail\"` within 5 minutes, under the same one-per-24h cap. Works by GET or POST.","annotations":{"title":"Signal fail","readOnlyHint":true,"openWorldHint":false,"idempotentHint":true,"destructiveHint":false},"source":"probe","observed_at":"2026-10-20T01:56:22.432Z"},{"name":"signal_slug","title":"Signal slug","description":"Send start, fail or an exit code by name, with the ingest key; the first signal of a new name creates its monitor. The same signals as `/in/:id/start`, `/in/:id/fail` and `/in/:id/:exit_code`, for a fleet that only knows the ingest key: the job sends its name, not the monitor id. The first signal of a new name creates the monitor, with the same rules and the same `cron`, `tz` and `grace_sec` as the plain ping by name.","annotations":{"title":"Signal slug","readOnlyHint":true,"openWorldHint":false,"idempotentHint":true,"destructiveHint":false},"source":"probe","observed_at":"2026-10-20T01:56:22.432Z"},{"name":"signal_start","title":"Signal start","description":"Says the run STARTED. Does not clear the dead-man clock: a routine that starts and hangs still becomes a miss. The next success records the duration. `start` is not proof of life: it does not move `last_event_at` and does not clear the miss counter, so a routine that starts and hangs still becomes a miss at the right time. The next success closes the run and records `last_duration_ms` — pass `?rid=` on both ends when runs can overlap, or the duration is measured against the wrong start. With `max_duration_sec` set on the monitor, a run left open past that ceiling alerts with `reason: \"too_long\"` on the next 5-minute check. Works by GET or POST.","annotations":{"title":"Signal start","readOnlyHint":true,"openWorldHint":false,"idempotentHint":true,"destructiveHint":false},"source":"probe","observed_at":"2026-10-20T01:56:22.432Z"},{"name":"status_feed_rotate","title":"Status feed rotate","description":"Rotates the status feed token; the previous URL stops working.","annotations":{"title":"Status feed rotate","readOnlyHint":false,"openWorldHint":false,"idempotentHint":true,"destructiveHint":true},"source":"probe","observed_at":"2026-10-20T01:56:22.432Z"},{"name":"status_feed_url","title":"Status feed URL","description":"Public URL (JSON and RSS) with the status of all the owner's endpoints. The feed token only reads status: it does not create, change or delete. Whoever has the link sees the dashboard — treat it as a secret.","annotations":{"title":"Status feed URL","readOnlyHint":true,"openWorldHint":false,"idempotentHint":true,"destructiveHint":false},"source":"probe","observed_at":"2026-10-20T01:56:22.432Z"}],"dev.protogrid/trust":{"score":95,"components":{"hygiene":100,"liveness":99,"freshness":100,"provenance":85},"drivers":["+repository","+dns-namespace","+website","+reachable","+uptime-97%","+updated-5d-ago"],"flags":[],"blocked":false,"computed_at":"2026-10-20T06:44:57.172Z","disclaimer":"Derived from observable signals (official registry feed and our own credential-free probes); no code audit performed."},"dev.protogrid/quality":{"score":69,"label":"needs work","components":{"auth":null,"hygiene":86,"protocol":75,"stability":25,"dependencies":null},"checks":[{"id":"protocol.modern","detail":"newest supported version is 2025-06-18; 2026-07-28 not supported, older versions are deprecated until 2027-07-28","status":"warn","category":"protocol"},{"id":"protocol.stateless","detail":"only applies to 2026-07-28 servers","status":"na","category":"protocol"},{"id":"protocol.transport","detail":"streamable HTTP","status":"pass","category":"protocol"},{"id":"protocol.list_ttl","detail":"only applies to 2026-07-28 servers","status":"na","category":"protocol"},{"id":"auth.prm","detail":"no remote uses OAuth","status":"na","category":"auth"},{"id":"auth.as_metadata","detail":"no remote uses OAuth","status":"na","category":"auth"},{"id":"auth.cimd","detail":"no remote uses OAuth","status":"na","category":"auth"},{"id":"auth.secret_in_url","detail":"no templated URL","status":"na","category":"auth"},{"id":"tools.descriptions","detail":"1 of 24 tools have a description under 20 characters","status":"warn","category":"hygiene"},{"id":"tools.description_length","detail":"1 tools have descriptions over 1,024 characters, which crowds the context","status":"warn","category":"hygiene"},{"id":"tools.schemas","detail":"every tool has an object input schema","status":"pass","category":"hygiene"},{"id":"tools.annotations","detail":"24 of 24 tools declare readOnlyHint or destructiveHint","status":"pass","category":"hygiene"},{"id":"tools.directory_hints","detail":"every tool declares readOnlyHint, destructiveHint, idempotentHint and openWorldHint","status":"pass","category":"hygiene"},{"id":"tools.token_cost","detail":"about 4,619 tokens to load every tool","status":"pass","category":"hygiene"},{"id":"tools.api_dump","detail":"tools are not a one-to-one API dump","status":"pass","category":"hygiene"},{"id":"stability.changes","detail":"146 tool changes in 30 days","status":"warn","category":"stability"},{"id":"stability.rug_pull","detail":"1 tools kept their name but changed most of their description in 30 days; review before trusting them","status":"fail","category":"stability"},{"id":"deps.known_vulns","detail":"no npm or PyPI package","status":"na","category":"dependencies"},{"id":"deps.mcp_sdk_version","detail":"no npm or PyPI package","status":"na","category":"dependencies"},{"id":"deps.resolvable","detail":"no npm or PyPI package","status":"na","category":"dependencies"}],"drivers":["-stability.rug_pull","~protocol.modern","~tools.descriptions","~tools.description_length","~stability.changes"],"flags":[],"tool_count":24,"token_estimate":4619,"tools_hash":"0acad41b76d3cec14ae5e81368753570","tools_changed_at":"2026-10-16T02:45:55.434Z","computed_at":"2026-10-20T06:49:33.418Z","disclaimer":"Checks run on what our credential-free, read-only probes observe; tools are never called and no code audit is performed."},"dev.protogrid/identity":{"canonical_id":"net.hookpulse/hookpulse","aliases":[],"alias_count":0,"repository_key":"github:wrpbr/hookpulse","owner":{"verified":false,"method":null,"since":null}}},"next_actions":[{"action":"list_tools","description":"Every current tool with input schemas","href":"/v1/servers/net.hookpulse%2Fhookpulse/tools","arguments":{"name":"net.hookpulse/hookpulse"}},{"action":"get_connection","description":"Minimal connection block for the preferred remote or package","href":"/v1/servers/net.hookpulse%2Fhookpulse/connection?target=mcpServers","arguments":{"name":"net.hookpulse/hookpulse","target":"mcpServers"}},{"action":"search","description":"Find other servers by intent","href":"/v1/search?q={query}","arguments":{"q":"{query}"}}]}