Agent Quick Start
Read this first if you are an AI agent working on Ionhour monitoring for a human — task recipes for connecting, auditing, adding, investigating, querying, and changing monitoring over MCP.
Recipes for AI agents working on Ionhour monitoring for a human. Find the request in the table below.
All docs: https://ionhour.com/llms.txt. Start with the request the human actually made. Do not turn an audit into an implementation, fix a failure during a diagnosis, or create every kind of resource Ionhour supports.
Install the Ionhour skill
The Ionhour skill bundles these recipes with a tool reference, CLI equivalents, and helper scripts. It is published from the ionhour/agent-skills repository; until that repository is public these recipes are only available on this page. Once it is live, install it once:
npx skills add ionhour/agent-skillsClaude Code and Codex users can install the Ionhour plugin instead, which adds the skill and the MCP server together. The plugin's first version connects over stdio with npx -y @ionhour/mcp-server, reading the credentials stored by npx @ionhour/mcp-server login; the hosted https://mcp.ionhour.com path with browser sign-in follows once OAuth ships. In Claude Code:
claude plugin marketplace add ionhour/agent-skills
claude plugin install ionhour@ionhourIn Codex:
codex plugin marketplace add ionhour/agent-skills
codex plugin add ionhour@ionhourOr copy the skill folder into ~/.claude/skills/ionhour.
The Job vs Check mental model
Ionhour has two distinct kinds of monitor. They are separate entities with separate tools. Do not conflate them.
| Monitor | Direction | Who owns the schedule | Create with | Manage with |
|---|---|---|---|---|
| Job | Inbound heartbeat. Your cron, worker, or scheduled script pings Ionhour; Ionhour alerts when a ping is late or missing. | You | register_job (returns a ping token) | list_jobs, get_job_status, get_job_uptime, find_job_by_name, update_job, … |
| Check | Outbound probe. Ionhour probes a URL, DNS name, TCP port, or TLS certificate you own and alerts on failure. | Ionhour | create_check | list_checks, get_check_status, get_check_uptime, find_check_by_name, update_check, run_check_probe, … |
Status-page components can each link a check, a job, a project, or a dependency.
Start with the user's request
| The human wants to… | Follow this recipe | Complete when… |
|---|---|---|
| Set up monitoring for a project | Onboard a project | Every resource the human approved is verified, or marked configured but unverified with the step that remains |
| Connect an account | Connect Ionhour | A read-only call succeeds against the confirmed workspace |
| Understand current coverage | Audit monitoring | You report what is covered, what is not, and the evidence for each conclusion |
| Monitor a workload or endpoint | Add monitoring | Ionhour observes the real workload or performs a successful real probe |
| Route alerts to email, a webhook, Slack, or another channel | Connect an alert channel | The channel exists, is enabled, and is attached to the project's escalation policy |
| Understand an incident or failure | Investigate an incident | You explain the evidence, likely cause, and next action without changing state |
| Understand uptime, reliability, or trends | Query reliability | You report the numbers, the time range, and the monitors they cover |
| Modify an existing resource | Change configuration | The approved change is saved and read back |
| Ship a release without false alerts | Safe deployment | The window was opened, monitors paused, the window ended, and health confirmed |
| Tell customers what is happening | Communicate on a status page | The announcement is published and visible in the page preview |
If a request contains several of these tasks, follow only the recipes needed for that request. A specific instruction to implement a named change authorizes that scope. A broad request such as "evaluate our monitoring" or "what should we monitor?" does not authorize remote writes, dependency installation, or runtime changes. The Onboard a project recipe gets its authority from the human's approval of its proposal, not from the opening request.
Rules shared by every recipe
- Examples in this guide use
tool_name(arguments)notation; they are MCP calls, not shell commands. - Read the live MCP tool schemas before calling tools. They are authoritative when an example in this guide differs. The
ionhour://tools/catalogresource lists every tool grouped by domain. - Use read-only discovery before writes. Resolve names to ids with
find_job_by_nameandfind_check_by_nameand reuse what exists instead of creating duplicates. - Ask before expanding beyond the requested scope or creating public status pages, incidents, announcements, alert destinations, paid resources, or destructive changes. Several deletes are soft and reversible with the
restore_*tools, but signal and incident history is purged on delete and is not recovered. - MCP is the control plane, not the telemetry path. Jobs must send heartbeats directly from the real runtime to the public ping URL
https://signal.ionhour.com/api/signals/ping/{token}(success),…/{token}/fail(failure), or…/{token}/start(run started). The ping URL contains the job token, which is the job's only credential; keep it out of chat transcripts and source control. Read Control plane, not data plane. - Report telemetry with the
curlsnippet fromget_job_integration_guideor the plain ping URL from the real runtime. Official SDKs are documented in the SDK section of the docs as they are published; do not add an SDK dependency that is not listed there. Never callsend_heartbeatfrom the agent session and claim the workload is monitored: a heartbeat from the agent proves nothing about the runtime. Never deliberately fail a production workload to test alerting. - Agents authenticate with an
ionh_API key or the@ionhour/mcp-serverlogin. Never ask the human to paste a key, token, or password into the conversation. Ask them to put it in the client's secret input or an environment variable, copied from Developer Console → Credentials in the dashboard. - Request signal payloads, incident notes, and private status-page configuration only when the task requires them.
- Plan-limit errors return a tool result with
isError: trueand anerrorstring of the form "Your<plan>plan allows a maximum of N<resource>. Please upgrade your plan to add more." Stop, report the message, point the human at Workspace → Billing in the dashboard, and do not retry with different values. Callget_workspacefirst so you know the limits and current usage before bulk creation. - Rate-limit errors are an HTTP
429on the transport with body{ code: "RATE_LIMIT_EXCEEDED_KEY", limit, plan, message }and aRetry-Afterheader. Wait that long and retry once. - Report one of three outcomes:
verified,configured but unverified, orblocked. Creating a monitor is not by itself verification.
Onboard a project
Use this recipe when the human wants to start monitoring a project, for example right after they connect a new workspace, or when a team adds another repository. Onboarding is a conversation. You find what can be monitored, the human decides what to monitor, then you set it up. Do not create resources or change code before the human approves the proposal in step 4.
1. Connect
Follow Connect Ionhour.
2. Discover
Discovery is read-only. In the repository, look for:
- scheduled work, such as crontab files, systemd timers, Kubernetes CronJob manifests, scheduled GitHub Actions, Vercel or Cloudflare cron configuration, Celery beat, Sidekiq, the Laravel scheduler, Airflow DAGs, node-cron, and APScheduler. These are examples, not a complete list. Anything that runs on a schedule or at a regular interval is a candidate for a Job;
- long-running workers and queue consumers (Job candidates with a short interval);
- public URLs and health routes in deploy configuration, environment examples, and the README (Check candidates);
- external services the code depends on, such as payment providers, email APIs, or a hosted database (Dependency candidates);
- existing Ionhour packages, job tokens, and
IONHOUR_*environment variables.
In Ionhour, call whoami({}), get_workspace({}), get_workspace_summary({}), list_projects({}), list_alert_channels({}), and get_alert_routing({}). get_workspace returns the plan's limits and current usage so the proposal fits the plan. If get_workspace_summary reports zero monitors, there is nothing to reconcile. Otherwise match what you found against existing monitors with find_job_by_name and find_check_by_name before you propose anything new.
3. Report and ask
Send one message in this form. Give every question a default so the human can reply "use the defaults".
Found in this repository
- <workload or URL>: <evidence> → <suggested monitor>
Not visible from this repository
- Scheduled work on servers, Windows machines, or clusters that this repository does not define.
Questions
1. Monitor everything listed above? (default: yes)
2. Do you run scheduled jobs outside this repository: Linux cron, Windows Task Scheduler, Kubernetes CronJobs, or another scheduler? (default: no)
3. Outbound checks for <URLs>? (default: yes, every 5 minutes)
4. Which project should these live in? (default: one new project named after the repository)
5. A status page? (default: no)
6. Alerts go to the project's escalation policy. Add an email or webhook destination now? (default: email to the workspace owner)
7. Timezone for schedule discussion? (default: the workspace's timezone)Always ask question 2, even when you found scheduled work in the repository. Schedulers on servers, Windows machines, and clusters are often defined outside the application repository. Offer only alert channel types that MCP can create: email and webhook. Slack, Discord, Microsoft Teams, Telegram, SMS, and PagerDuty are connected from the dashboard under Alerting → Channels; tell the human that step is theirs. If your client has a structured question tool, use it.
4. Propose
Show one table with a row per resource: resource, type (Job or Check), project, interval and grace, how it reports, alert route, repository change, and any step the human must run. Ask for one approval. That approval authorizes everything in the table and nothing else. Ask again if the plan changes.
5. Act
Create resources with MCP. Instrument code following Add monitoring. For hosts outside the repository, give the human the exact commands to run. Connect alert channels with Connect an alert channel.
6. Verify and report
Confirm each resource with a real heartbeat or probe, then report the outcome. Mark anything that waits on a deploy or a host step as configured but unverified, and tell the human which event will confirm it.
Done when: every approved resource is verified, or configured but unverified with the remaining human step named.
If blocked: report the resources that were created, the ones that were not, and why. Do not leave half the proposal applied without saying so.
Connect Ionhour
Pick the connection path in this order and do not ask the human to choose between options that are not available to them:
- Already connected. If Ionhour MCP tools are present in your session, use them. Read the published tool schemas and make one read-only call:
whoami({}). - Your client supports remote MCP and can hold a secret (Claude Code, Codex, Cursor, VS Code). Add the hosted server
https://mcp.ionhour.comwith a Bearerionh_API key supplied through an environment variable or the client's secret input, using the exact steps for that client in Connect your MCP client. The key decides the workspace; every call is scoped to it and you never pass a workspace id. - Your client runs stdio servers. Use the
@ionhour/mcp-serverpackage.npx @ionhour/mcp-server loginopens the browser, signs the human in, and stores a key under~/.config/ionhour/credentials.json;npx @ionhour/mcp-server setupwrites the server entry into detected editors. Confirm withnpx @ionhour/mcp-server whoami. - Neither is possible. Report
blocked, link the client setup section, and continue any repository-only work. Do not fall back to a credential pasted in chat.
Hosted clients that cannot send a private header, such as Claude.ai, Claude Desktop connectors, and ChatGPT, cannot connect to Ionhour today. OAuth sign-in for those clients is planned; until it ships, report blocked for them.
Never ask the human to paste an API key or password into the conversation.
Permission levels
An ionh_ key is either read_only (maps to the viewer role: read tools only) or read_write (view plus mutate, capped at the admin role). Owner-only actions are unreachable over MCP with any key. A key can additionally be restricted to a subset of resources; a denied call returns a structured forbidden error naming the scope, which you report rather than work around.
Once connected, confirm the workspace with a compact inventory:
whoami({})
get_workspace({})
get_workspace_summary({})
list_projects({})Done when: a read-only call succeeds and the workspace is unambiguous.
If blocked: report blocked, name the path you tried and why it failed, and state that remote workspace inspection or changes remain incomplete.
Audit monitoring
An audit is read-only unless the human separately asks you to implement its recommendations.
Inspect the same execution boundaries as Onboard a project, plus existing projects, dependencies, alert routing, deployments, and status pages. Inspect the boundary that runs, not merely the repository's dominant language. A shell command in a Python repository is a curl heartbeat; a long-running service reports from inside its own loop after each useful-work checkpoint.
Use the inventory from the connection recipe, then enumerate:
list_jobs({})
list_checks({})
list_dependencies({})
get_alert_routing({})
list_status_pages({})Match code to existing resources by name, then read one resource in depth:
find_job_by_name({"name": "nightly-backup"})
get_job_status({"jobId": 42})
get_check({"checkId": 7})get_check returns the stored configuration (assertions, thresholds, regions, alerting); get_check_status returns live state and recent signals. For a job, get_job_status returns status plus the most recent heartbeats, which is the evidence that the real runtime is reporting.
Report the audit in this form:
Covered
- <workload or endpoint>: <Ionhour resource and evidence>
Gaps
- <workload or endpoint>: <failure Ionhour cannot currently detect>
Uncertain
- <missing repository, runtime, workspace, or environment evidence>
Recommended next change
- <smallest change that detects the most important uncovered failure>Do not equate a configured monitor with coverage. For jobs, look for recent heartbeats from the real runtime and a status other than NEW. For checks, confirm that Ionhour is probing the intended target from the intended regions and that the assertions match what "healthy" means.
Done when: every conclusion is tied to repository, deployment, or Ionhour evidence, and recommendations are clearly separated from changes actually made.
If blocked: report the observable portion of the audit and list the missing access or evidence. Do not guess that an unobservable runtime is covered.
Add monitoring
Choose the monitor that proves the outcome the human cares about:
| Evidence required | Use | Read next |
|---|---|---|
| A scheduled task ran on time, and optionally how long it took | Job with a heartbeat from the runtime | Jobs |
| A process, device, or loop reached a recurring useful checkpoint | Job with a short interval | Monitoring patterns |
| A website, API, DNS name, TCP port, or TLS certificate works from outside its failure domain | Check | Checks |
| A third-party service your checks depend on is the real root cause | Dependency with a linked check | Dependencies |
| Health and incidents must be communicated to other people | Status page | Status pages |
Before writing, find matching resources and the effective alert route:
find_job_by_name({"name": "nightly-backup"})
get_alert_routing({})
get_escalation_policy({"projectId": 3})For a broad request, propose the change and get approval first: the workload, the resource to create or update, its project, interval and grace, alert recipients, the runtime or probe change, how it will be verified, and how to roll it back. Do not ask again when the human already approved this exact scope; ask when discovery materially changes it.
Both register_job and create_check accept a human-readable interval such as "every 5 minutes", "hourly", or "daily", which takes priority over intervalSeconds. Jobs accept 10 seconds to 7 days; checks accept 10 seconds to 1 hour. If the result is an isError envelope mentioning the plan, the plan limit stopped the create; tell the human.
Example: scheduled command
After the scope is approved, create or reconcile the job:
register_job({
"name": "nightly-backup",
"projectId": 3,
"interval": "daily",
"graceSeconds": 900
})The result includes the job id, its token, and the public ping URL. Have the real scheduler command report to it. The smallest safe integration pings on success and on failure:
0 2 * * * /usr/local/bin/nightly-backup && curl -fsS -m 10 --retry 2 "$IONHOUR_PING_URL" || curl -fsS -m 10 "$IONHOUR_PING_URL/fail"Keep the ping URL in the host's environment or secret store, not in the crontab text you paste into chat. get_job_integration_guide({"jobId": 42}) returns ready-made snippets for shell, cron, CI, Node, and Python using the job's actual token. Add an optional ?duration=<ms> query parameter to record run time, and ping …/start when a run begins if the human wants start-to-finish correlation.
After one safe real run:
get_job_status({"jobId": 42})Confirm the heartbeat came from the intended runtime, not the setup session, and that the status moved from NEW to OK.
Example: useful-work heartbeat
register_job({
"name": "queue-consumer",
"projectId": 3,
"interval": "every 5 minutes",
"graceSeconds": 120
})Have the consumer report to the ping URL only after it completes useful work. A separate timer that pings while the consumer is stuck proves the wrong thing. Verify with get_job_status after a real checkpoint.
Example: external API check
list_regions({})
create_check({
"name": "api-health",
"projectId": 3,
"url": "https://api.example.com/health",
"interval": "every 5 minutes",
"expectedStatusMin": 200,
"expectedStatusMax": 299,
"bodyContains": "ok",
"latencyWarnMs": 2000,
"regions": ["eu-central-1"]
})
run_check_probe({"checkId": 7})
get_check_status({"checkId": 7})Ionhour performs the probe, so no runtime ping is required. Choose regions only from what list_regions returns for this environment; a region that is not served stores but never probes. run_check_probe runs asynchronously and a failing probe counts toward the failure threshold, so read the result back a few seconds later with get_check_status. For DNS, TCP, and SSL checks set checkType and the matching dns, tcp, or ssl object; these types may be disabled for the workspace, in which case the error says so.
Example: status page
Create the page, then add one component per monitor the human chose, then preview it:
create_status_page({"name": "Example Status", "slug": "example-status", "visibility": "PUBLIC"})
add_status_page_component({"statusPageId": 5, "name": "API", "checkId": 7})
add_status_page_component({"statusPageId": 5, "name": "Nightly backup", "jobId": 42})
get_status_page_preview({"statusPageId": 5})The response url is the public address. A public page is visible to anyone, so ask before creating one unless the human already approved it. Publishing an incident to the page is a separate step (publish_incident_to_status_page or create_announcement) and needs its own approval. Password protection and custom domains are configured in the dashboard.
Reuse the repository's package manager and lockfile, preserve return values and exit status, and do not instrument beyond the approved scope.
Done when: the saved resource matches the proposal and Ionhour has observed one safe real heartbeat or probe from the intended integration path.
If blocked: report configured but unverified when configuration is saved but a real observation is unavailable. Report blocked when you cannot change the real runtime or configure the real target. Never manufacture verification.
Connect an alert channel
Creating a notification destination requires explicit approval. In Onboard a project, the approved proposal covers it. Discover first, then write.
- Call
list_alert_channels({})andget_alert_routing({})and reuse a matching destination. - MCP can create two channel types. Email:
create_alert_channel({"type": "email", "name": "Ops inbox", "to": ["ops@example.com"]}). Webhook:create_alert_channel({"type": "webhook", "name": "#alerts bridge", "webhookUrl": "<webhook URL supplied by the human>"}). Never echo a webhook URL that carries a secret. - Slack, Discord, Microsoft Teams, Telegram, SMS, and PagerDuty are connected by the human in the dashboard under Alerting → Channels. Send them there and mark the step
configured but unverifieduntil the channel appears inlist_alert_channels. - Alerts reach a channel through the project's escalation policy. Read it with
get_escalation_policy({"projectId": 3}). To add a step, the MCP-callable path iscreate_escalation_rule({"projectId": 3, "alertChannelId": 11, "delayMinutes": 0}); this is a deprecated alias that adds a single-channel step and remains the only way to do it over MCP. Read the policy back afterwards. - Routing rules that override the policy per status or project are read with
get_alert_routing; editing them is a dashboard task today.
Done when: the channel is listed and enabled, and a read-back of the escalation policy shows it on a step.
If blocked: report configured but unverified while the human still has a dashboard step; report blocked when the channel type cannot be created and the human declines the dashboard step.
Investigate an incident
Diagnosis is read-only. Do not pause, resume, edit, resolve, or delete anything unless the human separately asks for that change.
For a workspace-wide question, start with:
get_workspace_summary({})
list_incidents({"state": "ACTIVE"})For a named monitor, resolve it and start compactly:
find_check_by_name({"name": "api-health"})
get_check_status({"checkId": 7})
get_incident_timeline({"checkId": 7, "limit": 5})For a job use find_job_by_name, get_job_status, and get_incident_timeline({"jobId": 42}). Read get_incident({"incidentId": 99}) for the incident's own fields and notes. get_check shows the stored assertions and thresholds when a probe failure looks like a configuration problem rather than an outage. diagnose_incident produces an AI root-cause summary where the workspace has that feature enabled; treat its output as a hypothesis, not evidence.
Compare Ionhour's saved interval, grace, regions, and assertions, the latest signals, and the incident timeline with the real scheduler and runtime, and separate observed facts from inference. A failing monitor can mean a real failure, a wrong interval or grace, missing telemetry, a probe region that is not served, or stale configuration.
Done when: you report the failure evidence, the most likely cause with confidence and alternatives, and the smallest next action. No remote state has changed.
If blocked: identify the missing signal, runtime, scheduler, or permission evidence. Do not clear the failure with a heartbeat, a probe, or a configuration change.
Query reliability
Reliability questions are read-only. Ionhour exposes uptime, response time, and workspace reliability per monitor; all accept a lookback window in days (default 7, maximum 90).
"What was api-health's uptime last month?" is an uptime question:
get_check_uptime({"checkId": 7, "daysBack": 30})The result contains uptimePercent plus daily buckets. For a job use get_job_uptime({"jobId": 42, "daysBack": 30}). Uptime is time-weighted and incident-based, so a monitor with no incidents in the window reports 100 percent.
"Is api-health getting slower?" is a response-time question:
get_check_response_time({"checkId": 7, "range": "7d"})"How reliable was the workspace this week?" is a rollup question:
get_workspace_reliability({"daysBack": 7})
search_incidents({"state": "RESOLVED", "limit": 20})For "which of my monitors" questions, enumerate with list_checks or list_jobs, query each, then rank. Always state the window the numbers cover. Per-signal history is available through list_signals, but point the human to the monitor's dashboard page when they need raw run logs.
Done when: you report the requested numbers with the window and monitor selection they cover, and clearly separate observed values from interpretation.
If blocked: report an empty result as "no data for this selection and window" rather than zero failures. Report blocked when the monitor cannot be resolved or the window is outside the supported range.
Change configuration
Find and read the existing resource before updating it:
find_job_by_name({"name": "nightly-backup"})
get_job_status({"jobId": 42})Show the requested delta and its alerting effect. Pass only the fields that change; the update tools leave omitted fields untouched. Preserve the job token and the check id.
For example, after approval to extend the grace period:
update_job({"jobId": 42, "graceSeconds": 1200})
get_job_status({"jobId": 42})For a check, update_check accepts the same probe fields as create_check; read the stored result back with get_check. Pausing is a separate action (pause_job, pause_check); for a release use Safe deployment instead of pausing by hand. Maintenance windows, on-call schedules, and teams are not exposed over MCP; use the REST API or the dashboard.
Creating alert destinations, publishing status pages or announcements, changing effective recipients, and destructive operations require explicit approval unless the human's request already names that exact action and scope. Soft deletes can be undone with the matching restore_* tool, but the purged signal and incident history cannot.
Done when: the saved resource is read back, the requested fields match, unrelated fields remain intact, and the effective alerting impact is reported.
If blocked: report the rejected field, permission, plan limit, ambiguous target, or missing approval. Do not replace an update with delete-and-recreate.
Safe deployment
Use this recipe when the human is about to ship and does not want the restart to page anyone. A deployment window pauses the project's monitors on open and resumes them on close, so the sequence must be completed; an abandoned window leaves the project unmonitored.
list_incidents({"state": "ACTIVE"})
create_deployment({"projectId": 3, "name": "api release", "version": "v1.4.2"})Report the deployment id and tell the human the project's monitors are paused. When they confirm the deploy is finished:
end_deployment({"deploymentId": 12})
get_workspace_summary({})For a Check, follow with run_check_probe and read get_check_status. For a Job, wait for the next real heartbeat and confirm with get_job_status; do not send one from the agent. list_deployments({"projectId": 3}) shows the history if the human asks what happened during the window.
Done when: the window is ended, every monitor it paused is back to OK or has a real post-deploy signal, and no new incident is open for the project.
If blocked: if you cannot confirm the deploy finished, say so and leave the window open only with the human's explicit agreement, naming the time at which they should end it. Never end a window to clear a paused state the human did not ask about.
Communicate on a status page
Publishing to a status page is customer-visible. It needs explicit approval for the page, the wording, and the impact level, even when the human asked you to investigate the incident.
Find the page and the incident, then publish:
list_status_pages({})
get_incident({"incidentId": 99})
publish_incident_to_status_page({"incidentId": 99, "statusPageId": 5, "title": "Elevated API errors", "body": "We are investigating elevated error rates on the API."})
get_status_page_preview({"statusPageId": 5})publish_incident_to_status_page creates the announcement from the incident; its status defaults to INVESTIGATING and its impact to a mapping from the incident severity. Post follow-ups with create_incident_update and publish_incident_update, and close with a RESOLVED update when the incident resolves. For planned or non-incident communication use create_announcement({"statusPageId": 5, "title": "…", "body": "…"}) directly. Every announcement should be checked in get_status_page_preview, which renders the page exactly as the public sees it.
Done when: the announcement is visible in the preview with the approved wording and impact, and the human has the public URL.
If blocked: report blocked when no page exists and the human declines to create one, or when the key is read_only. Do not create a public page as a side effect of communicating an incident.
Report the outcome
Finish with a compact, auditable summary:
Result: verified | configured but unverified | blocked
Resources reused, created, or changed:
Runtime or probe integration:
Verification evidence:
Effective alert recipients:
Secrets handled:
Rollback:
Remaining human step:Use verified only when Ionhour independently observed the failure-detection path the human asked for. For topics not linked above, use the MCP reference and the complete documentation index.