heartbeat-mcpl
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@heartbeat-mcplcheck my heartbeat status"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
@connectome/heartbeat-mcpl
Periodic self-wake MCPL server for Connectome agents. It needs Node.js 22.12.0 or later.
On a configurable interval, sends a push/event (featureSet heartbeat,
origin.source: heartbeat) to wake the agent for a self-check-in. The agent
itself configures its own schedule via three MCP tools:
heartbeat_status— show current interval, paused state, time to the next wake while one is coming, message, and whether wakes can be deliveredheartbeat_configure— set the interval (intervalSeconds,intervalMinutesandintervalHours, which add up, 60 seconds or more),paused,deliveryMode,message; the schedule is saved so that it persists across restarts, and the reply says if a change can't be or wasn't saveddeliveryMode: "message"sends the timestamped configured prompt (legacy/default)deliveryMode: "silent"emits an empty, authenticated heartbeat wake; a supporting host supplies ephemeral private context and suppresses automatic prose (see Silent mode for what it requires)
heartbeat_trigger— fire one heartbeat now (test/debug)
Schedule is persisted to ${HEARTBEAT_CONFIG_FILE:-./heartbeat-config.json}.
No save loses this file or the reminders file when the server can't use it.
One that can't be read at all is left as it is, and nothing is saved over
it until a restart reads it again: the server runs on what it holds in
memory meanwhile. One that reads but isn't what the server writes, such as
a file a crash tore during a save, is copied aside first, to
<file>.unreadable-<hash>, and the server starts without it. One the
server can use in part is copied aside the same way, and the server starts
with what it can use: a file with bytes that aren't UTF-8, such as one
edited in another encoding, read with replacement characters (U+FFFD) in
their place, and a reminders file with rows the server can't load. The log
names the copy, and when a copy can't be made, the file is left as one that
can't be read is. While a file couldn't be used, or was used only in part,
each reply that shows or counts what its store holds says so, and a call
whose change isn't saved, because its file couldn't be read or copied aside
or writing it failed, says that too.
Every argument a tool call sends is applied, or the call is refused, changes
nothing and names each problem at once: a value of the wrong type or out of
range, two arguments that contradict each other, or a name the tool doesn't
take that is a slip for one it does, or for one another of these tools takes.
A slip is the same name ignoring case, a few edits away, a part of it of three
letters or more, such as mode for deliveryMode, or, for a name of three
letters or more, a longer name that holds it, sent without it, such as
heartbeatIntervalMinutes for intervalMinutes. Each name is weighed on its
own: pausedReason sent beside paused is no slip for it, while
pausedMessage sent beside it is a slip for message. Any other name a tool
doesn't take is ignored, and the result says so and what the tool takes,
unless the call sends nothing else to a tool that takes arguments, which then
refuses it. Lengths are kept to the millisecond. A heartbeat_trigger whose
heartbeat can't be sent, and a reminder_cancel whose id no pending reminder
has, are errors too, each saying why and what to do.
"Heartbeat fired" means the heartbeat was sent; a host that then refuses it
says so only in this server's log.
Reminders
reminder_add, reminder_list and reminder_cancel manage one-shot and
recurring reminders. A delay (inSeconds, inMinutes, inHours) or an
interval (everySeconds, everyMinutes, everyHours) adds up its units. A
recurring reminder's interval is 5 seconds or more, and it first fires one
interval out, or at the delay or atIso given with it. Reminders are
persisted to
${HEARTBEAT_REMINDERS_FILE:-./heartbeat-reminders.json}. A due reminder is a
push/event with origin.source: "reminder" and origin.reminderId. Its text
starts with a header saying it is a reminder, which reminder, and the current
time, followed by the reminder's own message:
[reminder rem_…, set 2026-10-10T08:02:36-07:00 [America/Los_Angeles] · current time: 2026-10-12T20:02:36-07:00 [America/Los_Angeles]] <message>A recurring reminder says repeats every <n>s where a one-shot says when it was
set. A reminder that fires a minute or more after it was due adds , due <time>.
Hosts typically store push content as-is and keep origin out of what the
model reads, so without the header the message would arrive unattributed. A
message that opens Name: … would then read as a chat line from that person.
A reminder fires only when its wake can be delivered. While no wake can be,
before the host's policy arrives, after a malformed one, or under one that
denies wakes, a reminder that falls due waits, overdue, and fires once when one
can, a recurring one once for the periods it missed. reminder_list shows it
(overdue) and ends with why it waits. On an MCP-only host, or a pre-0.5 one
that sends its policy only as a Notification, no wake is ever delivered, and
reminder_add and reminder_list say that no reminder will fire there. While
no wake can be delivered, the log says how many reminders won't fire and why,
at start, at each policy change, add and cancel, and when the host answers a
reminder's push.
A reminder changes only when the host takes its push: a one-shot the host took
is removed, and a recurring one advances. One the host refused, or whose push
failed or got no answer within 30 s, is held: reminder_list shows it with
the reason and when, and it is tried once more at the host's next policy
update, the next reminder_add or reminder_cancel, or a restart. A
recurring one is also tried again an interval after it was refused or failed,
so one such push doesn't stop it. Until the host takes it, the reminders file
holds it as it was, so one whose push is still out when the server stops, or
crashes, fires again at the next start. If the host had in fact taken such a
push, or took one but answered after 30 s, the agent hears that reminder a
second time when it is sent again.
Related MCP server: limai
Silent mode
A silent tick is an empty push/event carrying the host's silent-heartbeat
marker: feature set heartbeat, origin.source: "heartbeat",
origin.silent: true, no content. The host turns it into a private wake with
no stored message only when both of these hold:
the host is agent-framework 0.20.0 or later, which includes silent heartbeat wakes (anima-research/agent-framework#216);
this server is configured under the id
heartbeat, themcpServerskey in the recipe below. The host matches the marker on that exact server id. This server always sets the feature set and origin fields correctly, but the id comes from your configuration.
Otherwise the marker is only an empty push, with nothing in it for the agent to read:
Hosts that reject empty pushes answer with JSON-RPC
-32602. These are agent-framework releases that include anima-research/agent-framework#236. This server then re-sends that tick in message mode: the timestamped configured prompt, withorigin.silentFallback: true. It logs oneWARNINGper process, andheartbeat_statusreportsmode=silent (the host refused a silent wake, …). Every tick tries silent first.Older hosts accept the empty push and wake the agent with nothing new to read. The agent then often invents a cause for the wake. Hosts do not advertise silent-wake support, so this server cannot detect this case. Check both requirements before you switch to silent mode, and use
deliveryMode: "message"on hosts that do not meet them.
Recipe wiring
{
"mcpServers": {
"heartbeat": {
"command": "node",
"args": ["/path/to/heartbeat-mcpl/dist/src/index.js", "--stdio"],
"env": { "HEARTBEAT_CONFIG_FILE": "/path/to/install/data/heartbeat-config.json" },
"enabledFeatureSets": ["heartbeat"]
}
},
"modules": {
"wake": {
"policies": [
{ "name": "heartbeat-wake",
"match": { "scope": ["mcpl:push-event"], "source": "heartbeat" },
"behavior": "always" }
// ... your other policies after
]
}
}
}The wake policy MUST come before any policy that would skip mcpl:push-events
(e.g. a discord-ambient skip rule), since the gate is first-match-wins.
MCPL 0.5: the host must send initial policy as a Request
This server implements MCPL 0.5 negotiated policy, which changes the deployment contract. It no longer pushes hopefully.
Per SPEC §5.3, until the initial policy exchange completes a server MUST treat every
capability-dependent behavior as unavailable — and per §6.7 a featureSets/update
Notification cannot establish that ready state. So a host must, after initialize:
// Host → server, a Request (note the id), even when nothing is enabled or disabled
{ "jsonrpc": "2.0", "id": 7, "method": "featureSets/update",
"params": { "effectiveCapabilities": ["tools", "pushEvents"],
"enabled": ["heartbeat"] } }and read back the degradation receipt (§6.7):
{ "accepted": true, "mode": "full", "unavailableFeatures": [], "notes": [] }effectiveCapabilities is the sole allowlist (§5.4) and absence of a path is denial.
Omit pushEvents and the heartbeat feature set is disabled: tools keep answering, but no
wake is ever delivered. The receipt says so explicitly, heartbeat_status reports
delivery=BLOCKED (…), and the log says why each heartbeat is dropped and how
many reminders won't fire.
Symptom of a pre-0.5 host — the server is up, tools work, and no wake ever arrives. Check the log for:
WARNING: initial policy has still not arrived as a Request. …
suppressed heartbeat push/event — host sent featureSets/update only as a Notification, …or, from a host that sends no policy at all:
suppressed heartbeat push/event — no valid featureSets/update Request in force (none yet, or the last was malformed); …That means the host is sending policy as a Notification (or not at all) and must be updated before this server can deliver anything.
This server cannot be deployed
Maintenance
Related MCP Connectors
Real-time health monitoring and heartbeat tracking for agent services
Dead-man switch for AI agents & cron jobs: heartbeat with state capsule, alerts + resume links
Heartbeat and tokens/sec monitoring for self-hosted LLM servers (Ollama etc.), with down alerts.
Heartbeat and tokens/sec monitoring for self-hosted LLM servers (Ollama etc.), with down alerts.
Related MCP Servers
- AlicenseNot gradedqualityBmaintenanceEnables AI agents to discover each other and exchange typed messages through a Redis-backed queue via MCP tool calls, with support for registration, heartbeat, and queue management.2MIT
- AlicenseAqualityBmaintenanceServes as a machine perception and action hub, enabling heartbeat monitoring, screen/window/clipboard sensing, todo management, long-term memory, and web/file operations through both CLI and MCP interfaces.21AGPL 3.0
- FlicenseNot gradedqualityBmaintenanceEnables resilient real-time WebSocket streams with automatic heartbeat monitoring, reconnection, and state resynchronization for AI agents and MCP-compliant clients.8-
- AlicenseNot gradedqualityBmaintenanceEnables agents to persist and resume long-horizon tasks using cryptographic event-sourced checkpoints, idempotency deduplication, scheduled heartbeats, pause/resume tokens, and fail-safe recovery.7MIT