# MCP Canary > Synthetic monitoring for MCP servers. A coding agent installs one native binary (`canary`) and sets up checks with shell commands only: no browser, no signup, no login, no runtime. A check connects to an MCP endpoint (Streamable HTTP or legacy HTTP+SSE), lists the tools, optionally calls one read-only tool, and evaluates assertions on the result. Scheduled checks open incidents and send signed webhooks. `canary monitor test` is a CI gate with stable exit codes. ## Install - macOS / Linux: `which canary || curl -fsSL https://mcpcanary.com/install.sh | sh` - Windows PowerShell: `irm https://mcpcanary.com/install.ps1 | iex` - If the installer says the directory is not on PATH, run `export PATH="$HOME/.local/bin:$PATH"` or call `~/.local/bin/canary`. ## Set up a monitor 1. `canary inspect --json` lists tools with `side_effects`: `read_only`, `possibly_mutating`, `explicitly_mutating`, `unknown`. It never calls a tool. 2. Pick a `read_only` tool and arguments with a deterministic answer (for example a known test record). Never pass `--allow-side-effects` unless the human asked for it. With no safe tool, leave out `--tool`: the check then only connects and lists tools. 3. `canary monitor set --url --tool --args '' --assert '' --interval 5m --json` - Creates or updates by name (idempotent). The first call creates an anonymous workspace, saves the token locally, and starts the 72-hour trial. - Name: 2-63 chars, lowercase letters, digits, dashes. - Target secrets only from env vars: `--bearer-env VAR`, `--header-env Name=VAR`. 4. Keep the configuration in the repository: add the same `canary monitor set ...` line and `canary monitor test --json` to the deploy workflow after the deploy step. Give CI the token as `MCP_CANARY_TOKEN`. 5. Verify: `canary monitor test --json`, exit 0 = passed. ## Assertions (`--assert`, repeatable) - `'$.a.b == "x"'` (right side is JSON: strings need quotes), `'$.n != 0'`, `'$.items exists'`, `'$.name contains "acme"'`, `'$.id matches "^cus_"'`, `'duration < 3000ms'` - `$` = `structuredContent`, else the first text block parsed as JSON. Paths: `.name`, `['name']`, `[0]`. ## Exit codes 0 success / passed · 2 saved, first check failed · 3 target unhealthy · 10 auth failed · 11 permission denied · 20 invalid input · 21 not found · 30 MCP Canary unavailable, retry · 31 target unreachable or timeout · 40 payment required ## JSON With `--json`, stdout is only the API's JSON. Errors: `{ "ok": false, "code", "message", "retryable", "details", "offers" }`. A failed check is `"status": "failed"` with `failure.code` and `failure.details`, not an error. ## Payment Trial: 72 h, 3 monitors, checks every 5 minutes, no card. Paid plans: Solo $24/month (10 monitors, checks every minute); Pro $49/month (50 monitors, checks every 30 seconds). On exit 40 the error has `offers[].checkout_url`. Do not pay. Give the link to the human and continue when they say it is paid. `canary entitlement --json` shows the current plan. ## Commands ``` canary inspect URL canary monitor set|list|get|delete|test|checks canary incident list|get canary webhook set|list|get|delete|deliveries canary pricing | entitlement | billing upgrade --plan solo|pro | billing portal | subscription cancel canary --help ``` ## Links - Docs: https://mcpcanary.com/docs/ - OpenAPI: https://api.mcpcanary.com/openapi.json - Releases: https://github.com/xakpc-dev-labs/canary/releases