sentinelHow it worksPricingBlogSign in

Start

IntroductionQuickstartAuthentication

Guides

Monitor a contractAdmin changesLarge outflowssBTC and PoX-5Slack and webhooks

Concepts

Watches & alertsBilling

Reference

CLIMCP serverSDKAPI reference
Powered bysecondlayer
Reference / MCP server
Previous← CLINextSDK →

On this page

MCP server

A hosted MCP server for Stacks contract monitoring: call show, when and logs from Claude Code, Codex or Cursor. Use the hosted server, or run it locally.

Hosted

Stateless: each request is signed in on its own, and nothing is kept between calls. Sign in with your browser, no key needed.

Claude Code:

shell
claude mcp add --transport http sentinel https://api.runsentinel.app/mcp

Then run /mcp, pick sentinel and sign in. The browser opens runsentinel.app: sign in with your email link, check the app name and allow it. You can lower each area it asks for. It shows under Settings → API keys as an app; revoke it there.

Cursor, in ~/.cursor/mcp.json:

json
{
  "mcpServers": {
    "sentinel": { "url": "https://api.runsentinel.app/mcp" }
  }
}

For CI or a headless agent, send a key instead (Settings → API keys or sentinel keys create). It looks like sk-snt_…; a secondlayer API key won't work here.

shell
claude mcp add --transport http sentinel https://api.runsentinel.app/mcp \
  --header "Authorization: Bearer sk-snt_…"

The endpoint is POST /mcp (Streamable HTTP). No bearer gets 401 with WWW-Authenticate: Bearer resource_metadata=…, which starts the sign-in (OAuth 2.1 with PKCE, client registration by DCR). GET and DELETE get 405. Each tool call runs through the API with that sign-in's areas, and the 120 requests a minute apply. An app's access token works only on /mcp: it lasts an hour and refreshes for 30 days.

Local

Over stdio, Node 22 or newer.

shell
claude mcp add sentinel \
  --env SENTINEL_API_KEY=sk-snt_… \
  -- npx -y -p @secondlayer/sentinel sentinel-mcp
json
{
  "mcpServers": {
    "sentinel": {
      "command": "npx",
      "args": ["-y", "-p", "@secondlayer/sentinel", "sentinel-mcp"],
      "env": { "SENTINEL_API_KEY": "sk-snt_…" }
    }
  }
}

Without SENTINEL_API_KEY, the local server uses the login sentinel login saved. SENTINEL_API_URL is optional.

First run

No login yet? The server still starts. The first tool call returns sign_in_required with a link and a code (structuredContent: { status, verificationUrl, userCode, expiresAt }). Open the link and confirm the code, then call the tool again: it waits up to 60 s for the approval, saves the login and runs.

Let an agent run it

Let an agent run the reads without asking. Everything else (when, every, at, off, destinations add, remove …) stays a prompt, so a person sees the full line before anything is added, rerouted or stopped. The read verbs refuse --yes and every write flag, so appending flags to an allowed prefix can't widen it. Don't add a blanket sentinel * rule, and don't list keys.

Claude Code, in .claude/settings.json. An ask rule beats an allow rule, so there is none: Claude Code asks for any command that isn't allowed. The mcp__sentinel__ rules assume the server is named sentinel.

json
{
  "permissions": {
    "allow": [
      "Bash(sentinel whoami *)",
      "Bash(sentinel status *)",
      "Bash(sentinel alerts *)",
      "Bash(sentinel ask *)",
      "Bash(sentinel contracts *)",
      "Bash(sentinel describe *)",
      "Bash(sentinel list *)",
      "Bash(sentinel show *)",
      "Bash(sentinel logs *)",
      "Bash(sentinel destinations *)",
      "mcp__sentinel__whoami",
      "mcp__sentinel__status",
      "mcp__sentinel__alerts",
      "mcp__sentinel__ask",
      "mcp__sentinel__contracts",
      "mcp__sentinel__describe",
      "mcp__sentinel__list",
      "mcp__sentinel__show",
      "mcp__sentinel__logs",
      "mcp__sentinel__destinations"
    ]
  }
}

OpenCode, in opencode.json. The last matching rule wins, so the catch-all comes first.

json
{
  "$schema": "https://opencode.ai/config.json",
  "permission": {
    "bash": {
      "sentinel *": "ask",
      "sentinel whoami": "allow",
      "sentinel whoami *": "allow",
      "sentinel status": "allow",
      "sentinel status *": "allow",
      "sentinel alerts": "allow",
      "sentinel alerts *": "allow",
      "sentinel ask": "allow",
      "sentinel ask *": "allow",
      "sentinel contracts": "allow",
      "sentinel contracts *": "allow",
      "sentinel describe": "allow",
      "sentinel describe *": "allow",
      "sentinel list": "allow",
      "sentinel list *": "allow",
      "sentinel show": "allow",
      "sentinel show *": "allow",
      "sentinel logs": "allow",
      "sentinel logs *": "allow",
      "sentinel destinations": "allow",
      "sentinel destinations *": "allow"
    }
  }
}

The agent skill (npx skills add https://runsentinel.app) teaches the same rules; see Quickstart.

Confirm before it happens

The tools are the CLI's verbs, one each (destinations add is destinations_add), and they answer the same way: a write returns { done, willDo, effect, next }, a read its data and next. next is the next command, sentinel <verb> …; as a call, its verb is the tool.

  • Read tools never write.
  • Write tools act at once, and dry_run: true previews any of them. when, every and at answer a cost (a check, a contract over your tier's count) with done: false until confirm: true.
  • Confirm tools (off, remove, destinations_remove, backtest, test_alert) return a preview (done: false) and change nothing until you call them again with confirm: true.

Starts plans

show (on a contract), import, update and plan_linked start plans, which are free. A key with Plans write can start them. Give an agent that area only on purpose.

Tools

A target is a contract (its id, or its name on your account), or an id: a watch w_…, a schedule sch_…, a check run chk_…, an alert alr_….

Read

ToolInputDoesNeeds
describetextA sentence or line as one watch: its line, 90-day backtest, cost. Writes nothing.Monitoring: write
listtarget?, all?Your watches and schedules by id; all: true adds suggestions.Monitoring: read
showid, series?, limit?A watch (w_…), schedule (sch_…), check run (chk_…), alert (alr_…), or a contract: what its code has (starts its free plan). sbtc|pox5 + series reserve|custody|stbtc.Plans: write; Monitoring: read; Alerts: read
logstarget?, only?, since?, tx?, after?, limit?Events, alerts, checks, schedule runs and changes, oldest first. Pass nextCursor as after.Monitoring: read
alertscontract?, after?, before?, watchId?Your alerts, newest first; after (alert id or ISO time): the ones since.Alerts: read
contractsnoneYour contracts: added, or only watched.Plans: read
whoaminoneYour account, tier, live contracts against its limit and the period's check usage.any key
statusnoneIs monitoring up, measured; yours too.any key
askquestionAsk about Sentinel itself, from its docs.any key
destinationsnoneYour alert destinations (what --to picks); defaults get every watch with no --to.Monitoring: read

Write: at once, dry_run previews

ToolInputDoesNeeds
upgradetier?, id?, dry_run?A Stripe link to subscribe (tier: builder|team), or to the portal once subscribed. Show the link to the person; never open or pay it. After they say they're done, pass its id for the status.any key
mutetarget, duration?, dry_run?Mute a watch (w_ id) or an alert's watch; off unmutes.Monitoring: write; Alerts: write
status_watchon, dry_run?A monitoring-is-up line in your digests: on or off.Monitoring: write
readalertId?, all?, dry_run?Mark an alert read, or all.Alerts: write
renametarget, name, dry_run?Your name for a contract ("" clears it).Plans: write
retry_historytarget, dry_run?Read a contract's history again after a failed read.Monitoring: write
redeliveralertId, dry_run?Resend an alert where it failed.Alerts: write
ontarget, deployer?, dry_run?Turn on a watch, a held schedule or a contract's monitoring. Billed once live. deployer arms pre-launch.Monitoring: write
whenline, confirm?, dry_run?, wait?Add/edit watch: call <target> <fn|@admin|@value|@control> [--caller X] [--new-caller] | transfer <target> <stx|token|> --in|--out [--over N|20%] [--within 1h] | mint <token> <name> [--new-minter|--over N] | print <target> <topic|> | state <target> <read> [--drops 20% --within 1h|--crosses N] | deploy <address> | sbtc <stall|payout|mint|rotation|protocol-update|reserve|peg> [--gap N] [--open N] [--after 6h] | pox5 <bond-setup|admin|early-exit>; [--to <ref>] [--note "…"] [--check "<q>"] [--hold]. wait ≤60s.Monitoring: write
everyline, confirm?, dry_run?On a clock: <6h|1d|monday 09:00|cron> [<target>] --digest [--ask "<q>"] | --check "<q>" [--tz Z] [--to <ref>]. No target: all your watches.Monitoring: write
atline, confirm?, dry_run?Once: <iso> [<target>] --digest | --check "<q>" [--to <ref>].Monitoring: write
importurl, only?, all?, commit?, deployer?, hold?, dry_run?A public Clarinet repo onto your account (free plans). deployer arms each for its deploy; hold keeps watches off.Plans: write
grouptarget, group?, add?, remove?, dry_run?A contract's group members and their source; group + add/remove corrects them for you.Monitoring: write
destinations_addkind, target, label?, default?, confirm?, dry_run?Add a Slack #channel, Slack webhook or signed webhook (label), or email. default: watches with no --to go there. Another person's email needs confirm.Monitoring: write
destinations_defaultref, on, dry_run?Make a destination (ref or d_…) a default, or not.Monitoring: write
updatetarget, commit?, dry_run?Plan a pre-launch contract's latest commit (or commit); the preview names its branch.Plans: write
auto_plantarget, on, dry_run?Re-plan a waiting plan on its own once its contracts deploy.Plans: write
linktarget, contract, url, dry_run?Link the code of a contract a waiting plan needs; url null clears.Plans: write
plan_linkedtarget, dry_run?Plan a waiting contract now with its linked code.Plans: write
connect_slackstatus?, remove?, confirm?, dry_run?The Slack app: an "Add to Slack" link; status lists workspaces; remove disconnects one.Monitoring: write

Confirm: previews until confirm

ToolInputDoesNeeds
test_alerttarget, example?, confirm?, dry_run?The sample alert; confirm sends it where the contract's alerts go.Monitoring: write
backtestid, run?, confirm?, dry_run?Run a watch's check on its last 1-5 events now: paid, never delivered.Monitoring: write
offtarget, confirm?, dry_run?Pause a watch or a contract (nothing deleted; on resumes). A schedule id stops it.Monitoring: write
removetarget, confirm?, dry_run?Take a contract off your account. Can't be undone.Plans: write
destinations_removeref, confirm?, dry_run?Remove a destination (ref or d_…).Monitoring: write

Limits

A key without the area a tool needs gets the API's 403 This key needs Monitoring: write. back from the tool. Sign-in, billing and API keys stay on the account page (/account); everything else, a tool does.