Docs
Everything an agent or a human needs to run MCP Canary. The same text for agents is at /llms.txt.
Install
# macOS, Linux
curl -fsSL https://mcpcanary.com/install.sh | sh
# Windows PowerShell
irm https://mcpcanary.com/install.ps1 | iex
The installer never asks a question and never needs sudo. It verifies the SHA-256 checksum and installs to the first writable directory on your PATH (~/.local/bin, ~/bin, /usr/local/bin, /opt/homebrew/bin), else to ~/.local/bin. Set CANARY_INSTALL_DIR or CANARY_VERSION=v0.1.0 to change that. On Windows it installs to %LOCALAPPDATA%\Programs\McpCanary and adds it to the user PATH.
Builds: macOS arm64 and x64, Linux x64 and arm64 (glibc 2.34 or later; Alpine is not supported yet), Windows x64. Releases: github.com/xakpc-dev-labs/canary/releases.
Quick start
canary inspect https://api.example.com/mcp
canary monitor set customer-search-prod --url https://api.example.com/mcp \
--tool search_customer --args '{"email":"canary@example.com"}' \
--assert '$.customers[0].email == "canary@example.com"' --interval 5m
canary monitor test customer-search-prod
inspect connects, lists the tools with their safety class, and never calls a tool. The first monitor set creates an anonymous workspace, saves its token, starts the 72-hour trial, and runs the first check.
To keep the configuration in the repository, put the monitor set line and a monitor test line in your deploy workflow. monitor set is idempotent: the same command again reports "changed": false.
Commands
canary inspect URL [--bearer-env VAR] [--header-env Name=VAR]...
canary monitor set NAME --url URL [--tool T] [--args JSON] [--assert EXPR]... [--interval 5m] [--timeout 30s]
[--bearer-env VAR] [--header-env Name=VAR]... [--allow-side-effects] [--alert-on-change]
canary monitor list | get ID | delete ID | test ID [--timeout 90s] | checks ID [--limit N]
canary incident list [--status open|resolved] [--monitor ID] [--limit N] | get ID
canary webhook set NAME --url URL [--event E]... | list | get ID | delete ID | deliveries ID [--limit N]
canary pricing | entitlement
canary billing upgrade --plan solo|pro | billing portal
canary subscription cancel
ID is a name or an id. Every command takes --json. monitor set gives the full desired state: an option you leave out goes back to its default. Durations: 90, 30s, 15m, 1h.
Safety
A check calls a tool only when the server declares readOnlyHint: true for it. A tool that is mutating, possibly mutating, or has no hints needs --allow-side-effects. The rule is checked on every check, so a server that changes its hints later is not called. Without --tool, a check only connects and lists tools.
Target auth
Secrets come only from environment variables, never from command-line values:
MCP_TOKEN=... canary monitor set prod --url https://api.example.com/mcp --bearer-env MCP_TOKEN
X_KEY=... canary monitor set prod --url https://api.example.com/mcp --header-env X-Api-Key=X_KEY
Values are encrypted at rest and never returned. A monitor set without these options keeps the stored headers. OAuth is not supported yet.
Assertions
'$.customers[0].email == "a@b.c"' equals (the right side is JSON, so strings need quotes)
'$.count != 0' not equals
'$.items exists' the path is present
'$.name contains "acme"' substring, or an array element
'$.id matches "^cus_"' regular expression
'duration < 3000ms' tool call time (ms, s, m, h)
$ is the tool's structuredContent, else its first text block parsed as JSON. Paths support .name, ['name'], and [0]. A tool result with isError: true always fails the check.
Exit codes
| Code | Meaning |
|---|---|
| 0 | Success. For inspect and monitor test: the check passed. |
| 2 | monitor set saved the monitor, but the first check failed. |
| 3 | The target is unhealthy: assertion failed, tool error, auth or protocol problem. |
| 10 | No token or a bad token. |
| 11 | Permission denied, or a limit that no plan allows. |
| 20 | Invalid input (bad flag, bad JSON, missing environment variable). |
| 21 | Not found. |
| 30 | MCP Canary is unavailable or rate limited. Retry later. |
| 31 | The target is unreachable or timed out. |
| 40 | Payment required. The error lists offers with checkout links. |
JSON output and errors
With --json, stdout has only the API's JSON, also for errors, and stderr is empty. Every error has the same shape:
{ "ok": false, "code": "PLAN_LIMIT_REACHED", "retryable": false,
"message": "Monitor limit reached: the trial plan allows 1 monitors. ...",
"details": { "limit": "monitors", "current_plan": "trial", "required_plan": "solo" },
"offers": [ { "plan": "solo", "amount": 2400, "currency": "USD", "interval": "month", "checkout_url": "https://..." } ] }
A failed check is not an error: inspect and monitor test return "status": "failed" with a failure object (code, message, details). Failure codes include TARGET_UNAVAILABLE, TARGET_TIMEOUT, TOOL_RETURNED_ERROR, ASSERTION_FAILED, and SIDE_EFFECTS_NOT_ALLOWED.
Incidents and contract changes
Only scheduled checks open incidents, and only after a confirming re-check. The next passing scheduled check resolves it. monitor test never opens or resolves one, so a CI run does not page anyone.
Each check compares the tool contract (names and schemas, not descriptions) with the last one and records the difference: tool_added, tool_removed, argument_added, argument_removed, argument_type_changed, and more. With --alert-on-change, a change also opens an incident.
Webhooks
canary webhook set ops --url https://hooks.example.com/canary # default events
canary webhook set ops --url https://hooks.example.com/canary --event '*'
Events: monitor.created, monitor.updated, monitor.deleted, check.passed, check.failed, contract.changed, incident.opened, incident.resolved, entitlement.activated, entitlement.expiring, entitlement.expired. Default: contract.changed, incident.*, entitlement.expiring, entitlement.expired.
The signing secret is shown once, on create. Each request has X-McpCanary-Signature: t=<unix seconds>,v1=<hex>, where v1 is HMAC-SHA256 of t + "." + body with the secret. Compare in constant time and reject old timestamps. Failed deliveries are retried with the same event id.
Plans and payment
canary pricing shows the plans; canary entitlement shows yours. When a write needs a plan, the error lists offers with a checkout_url: give it to a human. A link is valid for 7 days. canary subscription cancel cancels at the end of the paid period. When access ends, checks pause; nothing is deleted.
Configuration
| Variable | Use |
|---|---|
MCP_CANARY_TOKEN |
Workspace token. Wins over the saved token. |
MCP_CANARY_API_URL |
API base URL. Default https://api.mcpcanary.com. |
The token is saved per API URL in ~/Library/Application Support/McpCanary/credentials.json (macOS), ~/.config/mcpcanary/credentials.json (Linux), or %LOCALAPPDATA%\McpCanary\credentials.json (Windows), with mode 0600.
HTTP API
The CLI is a thin client: everything it does is an HTTP call to https://api.mcpcanary.com/v1/... with Authorization: Bearer <token>. The OpenAPI document is at https://api.mcpcanary.com/openapi.json.