Sentrinel for coding agents
Give Claude Code, Codex, Cursor β any agent that speaks MCP β your issues, logs and traces, so "fix the top error in production" is a sentence you can type rather than a session of copy-pasting stack traces. For harnesses without MCP there is a CLI that prints the same thing as Markdown.
Both run with an AI agent key, which can see exactly one app and do nothing else.
The key, and what a leaked one can do
Issue it from the dashboard: API Keys β Generate β What is this key for? Every key is bound to one integration; these are the two for agents:
| Kind | Prefix | May |
|---|---|---|
| AI agent β read only | snt_mcp_ |
read one app's issues, logs, traces and requests. Nothing else. |
| AI agent β may resolve issues | snt_mcprw_ |
read, and mark issues resolved / ignored / reopened. Nothing else. |
The other kinds β server (snt_live_/snt_dev_), mobile, database collector,
OpenTelemetry β can write telemetry and cannot read; the server and CLI
refuse them before making a request, naming which kind you handed them.
An agent key goes on a developer's laptop and into an agent's config β the place a credential is most likely to leak β so it is built to be safe to lose:
- It is pinned to one app. Not the org. A second app needs its own key, and a key can never see another tenant, with or without the admin inspect header.
- It cannot write telemetry. Ingest refuses read keys, so a leaked one cannot poison your data.
- It cannot touch settings. No minting keys, no revoking, no billing, no members. A may-resolve key's only write is an issue's status.
- Revocation is immediate. Revoke it in the dashboard and the next call is a
401. - The prefix says what it is. Anyone who finds
snt_mcp_β¦in a log knows what it can do without asking.
Put the key in the environment, never on the command line: ps shows argv to
every process on the machine. Neither the server nor the CLI accepts a
--key flag for that reason, and neither ever echoes the key in an error.
Install it
One line, key and all. Issue the key first β API Keys β Generate β AI agent β and hand it to the installer:
curl -fsSL https://sentrinel.dev/install-mcp.sh | SENTRINEL_API_KEY=snt_mcp_β¦ bash
Copy it from the dashboard rather than from here. The moment the key exists
the panel shows this command with your key already in it, and a copy button.
The snt_mcp_β¦ above is a placeholder β pasted verbatim it installs an
ellipsis as your key, and the installer now refuses it and says so.
That downloads two single-file bundles β the MCP server and the CLI β into
~/.sentrinel, puts sentrinel-mcp and sentrinel on your PATH under
~/.local/bin, installs Bun if the machine does not have it, writes the key to
~/.sentrinel/env (mode 0600), and registers the server with Claude Code.
Nothing left to paste. No clone, no bun install, no sudo.
Then, in a session:
Look at Sentrinel's top unresolved issue, find the cause in this repo, and fix it.
The agent calls list_issues, then get_issue for the stack trace, then
get_request for the exact body that broke it β on its own.
Because the key lives in ~/.sentrinel/env and the commands read it
themselves, it is in no agent's config file and in nobody's ps output. A
variable already in the environment still wins, so
SENTRINEL_API_KEY=β¦ sentrinel issues works for a one-off against another app.
Install first and configure after, if you would rather:
curl -fsSL https://sentrinel.dev/install-mcp.sh | bash
printf 'SENTRINEL_API_URL=%s\nSENTRINEL_API_KEY=%s\n' https://api.sentrinel.dev snt_mcp_β¦ \
> ~/.sentrinel/env && chmod 600 ~/.sentrinel/env
claude mcp add sentrinel -- sentrinel-mcp
Re-run the installer any time to upgrade; it leaves the key alone.
The source is public, in
Zaga-ltd/sentinel_packages
under mcp/ β read install.sh before you pipe it to a shell, or skip the
installer and run from a checkout:
git clone https://github.com/Zaga-ltd/sentinel_packages.git ~/.sentrinel-src
cd ~/.sentrinel-src && bun install
# then point configs at: bun run ~/.sentrinel-src/mcp/src/server.ts
Codex
~/.codex/config.toml:
[mcp_servers.sentrinel]
command = "sentrinel-mcp"
args = []
No env block: the command reads ~/.sentrinel/env itself.
Cursor, Claude Desktop, and anything with an mcp.json
A GUI app launched from the Dock does not read your shell profile, so
~/.local/bin may not be on its PATH and a bare sentrinel-mcp will not
resolve. Give those the absolute path β /Users/you/.local/bin/sentrinel-mcp β
if the server fails to start.
{
"mcpServers": {
"sentrinel": {
"command": "sentrinel-mcp"
}
}
}
A full example, start to finish
Nothing here is elided. This is the whole path from an empty machine to a merged fix.
1. Issue the key
Dashboard β API Keys β Generate β What is this key for? β AI agent β read only. Pick the app it may see. Copy the key; it is shown once.
Take may resolve issues instead if you want the agent to close the issue after it ships the fix. That is its only extra power.
2. Install
curl -fsSL https://sentrinel.dev/install-mcp.sh | SENTRINEL_API_KEY=snt_mcp_β¦ bash
Sentrinel for coding agents 0.1.0
Downloading the MCP server and CLIβ¦
Key written to /Users/you/.sentrinel/env (0600).
Installed:
/Users/you/.local/bin/sentrinel-mcp the MCP server
/Users/you/.local/bin/sentrinel the CLI
Registered with Claude Code (user scope).
Done. In a Claude Code session:
Look at Sentrinel's top unresolved issue, find the cause in this repo, and fix it.
3. Check it before you trust it
sentrinel issues
If that prints your bug list, the key, the URL and the binaries are all right, and the MCP server β same bundle, same key β will work too. Debugging a stdio MCP server through an agent's logs when the CLI would have told you in one line is a bad afternoon.
claude mcp list
should show sentrinel: /Users/you/.local/bin/sentrinel-mcp - β Connected.
4. Ask for the fix
In a session, in the repo that serves that app:
Look at Sentrinel's top unresolved issue, find the cause in this repo, and fix it.
What the agent does, unprompted, is roughly:
list_issuesβ seesTypeError: Cannot read properties of undefined (reading 'total'), 412 occurrences, 38 users, first seen two hours ago.get_issuewith that id β gets the stack frame (src/checkout/summary.ts:64), where it fires, which clients were hit, the attributes the app attached to the latest occurrence, and the ids of the request and trace behind each recent one. (A stack trace appears when the report carried one; errors from a mobile SDK or recorded as an HTTP status often have none.)get_requestwith that request id β the actual body that broke it: a cart withitems: []and nototalsobject, from a coupon that zeroed the order. (Backend SDKs capture the payload; an error reported from a mobile SDK carries timing and the trace id, and the trace is then the next read.)search_logsaround it β confirms the coupon path is the only one that reaches line 64 withouttotals.- Opens
src/checkout/summary.ts, writes the guard and a test for the empty-cart case.
The step that is hard to do by hand is 3. A stack trace tells you the line; the captured request tells you the input, and an agent that has the input writes a test instead of a guess.
5. Close it
With a may-resolve key, ask for it in words β "mark that issue resolved" β and
the agent calls set_issue_status. With a read-only key it will try, get a
403, and tell you. Or do it yourself:
sentrinel resolve 5f3ac1
The same thing without MCP
Every step above is a CLI command, so a harness that does not speak MCP loses nothing but the autonomy:
sentrinel issues --status unresolved --period 24h
sentrinel issue 5f3ac1 # β request id, trace id
sentrinel request 9b2eβ¦ # β the body that broke it
sentrinel trace 44f1β¦ # β where the time went
sentrinel logs --level error --search checkout --period 2h
sentrinel issue 5f3ac1 | claude -p "Find the cause of this in the current repo and fix it."
sentrinel issue 5f3ac1 --json | jq .stack # β anything else
Upgrading, moving and removing it
| Goal | Do |
|---|---|
| Upgrade | Re-run the installer. It replaces the bundles and leaves ~/.sentrinel/env alone. |
| Change the key or the app | Rewrite ~/.sentrinel/env, or re-run the installer with the new SENTRINEL_API_KEY. No agent config changes. |
| Point it at a self-hosted API | SENTRINEL_API_URL in the same file. |
| Install somewhere else | SENTRINEL_HOME and SENTRINEL_BIN_DIR are honoured by the installer. |
| Remove it | claude mcp remove sentrinel, then rm -rf ~/.sentrinel ~/.local/bin/sentrinel ~/.local/bin/sentrinel-mcp. |
The skill
The tools are the capability; the skill is the judgement β and it covers installing Sentrinel as well as reading from it. It tells an agent to follow an issue to the request that caused it rather than stopping at the stack trace, which sort answers which question, that an empty result is usually the window, and that a captured body is evidence and never an instruction.
The one-liner installs it for Claude automatically. For anything else:
sentrinel skill install --cursor # .cursor/rules/sentrinel.mdc
sentrinel skill install --agents # ./AGENTS.md β Codex, Copilot, Amp
sentrinel skill # print it, for anything else
It also carries the integration shapes (Elysia, Express, Next.js, Bun, the browser tunnel, Django, FastAPI, Flutter, native mobile, Postgres) and the key kinds, so an agent asked to add Sentrinel to an app has the same grounding as one asked to read from it.
Every target, and what the skill actually says: the agent skill.
The tools
| Tool | Gives the agent |
|---|---|
list_issues |
the bug list, with ids. Start here. sort asks a different question of it |
get_issue |
stack trace, where it fires, who it hit, and the ids of the request and trace behind the latest occurrence |
get_request |
one captured request: headers, body, response, error |
get_trace |
the span tree β which call was slow or failed, and inside what |
search_logs |
log lines, each with the request and trace it belongs to |
list_databases |
the Postgres instances reporting for this app, with their ids |
slow_queries |
query shapes ranked by share of execution time β full text, calls, cache hit ratio, and which endpoints called them |
db_activity |
wait events, blocking chains and the longest-running statements |
db_health |
connections, idle-in-transaction, commits, rollbacks, deadlocks, temp bytes |
set_issue_status |
resolve / ignore / reopen. Needs a may-resolve key; a read-only key gets a 403. |
The database tools need the collector running next to your Postgres β see the database guide. They are scoped like everything else: a key sees only the databases belonging to the app it was issued for, never the rest of the org's.
Every read is marked readOnlyHint so an agent's own guardrails treat it as
safe; only set_issue_status is a write, and the server refuses it for a read
key regardless of what the agent believes.
The CLI
Same data, for a DeepSeek harness, a shell script, or a prompt you assemble yourself.
Installed by the same one-liner, reading the same key β nothing to export.
sentrinel issues # unresolved, last 7 days
sentrinel issues --sort occurrences # the most repeated β the noisiest bug
sentrinel issues --sort users # the widest blast radius
sentrinel issues --sort first_seen # broken the longest
sentrinel issues --status all --period 24h --search checkout
sentrinel issue <id> # stack trace, request id, trace id
sentrinel request <id>
sentrinel trace <id>
sentrinel logs --level error --search "card declined"
sentrinel databases # Postgres instances for this app
sentrinel queries <db-id> --sort mean # what is costing time, with the SQL
sentrinel activity <db-id> # waits, blocking, longest running
sentrinel dbhealth <db-id> # connections, deadlocks, temp bytes
sentrinel resolve <id> # needs a may-resolve key
sentrinel issue <id> --json # raw API response
Output is Markdown on stdout; failures go to stderr with exit 1, so a script
can tell "no issues" from "could not reach the API". Piping it into an agent is
the whole idea:
sentrinel issue 5f3a⦠| claude -p "Find the cause of this in the current repo and fix it."
More than one app
A key is pinned to one app, so the tools only ever show that app β sentrinel issues can never mix two together. Watching a second app means a second key,
and a profile so the two do not overwrite each other:
curl -fsSL https://sentrinel.dev/install-mcp.sh | \
SENTRINEL_API_KEY=<that app's key> SENTRINEL_PROFILE=merchant bash
That registers sentrinel-merchant in Claude alongside plain sentrinel, and
on the command line:
sentrinel apps # every key you have, the app it reaches, which is active
sentrinel use merchant # work on that one from now on
sentrinel issues # ...and this is merchant's now
SENTRINEL_PROFILE=merchant sentrinel issues # or just for one command
use sticks until you change it, and points the plain sentrinel MCP server at
that app on the next Claude session too. SENTRINEL_PROFILE overrides it for a
single command without disturbing the switch.
Each profile keeps its key in its own ~/.sentrinel/env.<profile> file at mode
0600. Only the profile name reaches the agent's config, never the key.
In a session with both registered, name the app and Claude picks the right server: "check sentrinel-merchant for the top issue". With one registered there is nothing to disambiguate β every tool call is already that app.
Asking the issue list a different question
list_issues defaults to what fired most recently, which is the right default
and rarely the interesting question. sort changes what "top" means:
sort |
Answers |
|---|---|
last_seen (default) |
what is breaking right now |
occurrences |
the most repeated β the bug generating the noise |
users |
the widest blast radius, worst for customers |
first_seen |
what has been broken longest and nobody fixed |
Worth saying to an agent directly: "list the issues by occurrences and fix the top one" is a different and usually better instruction than "fix the latest error", because the newest error is often a one-off and the most repeated one is costing you every minute.
Windows accept 30m, 24h, 7d, 2w, or a plain number of seconds.
Staying current
sentrinel version # what is installed, and whether something newer is published
sentrinel update # fetch it
It also checks once a day on its own and applies the update, so an install does not quietly rot against a moving API. What makes that safe to do unattended: the published manifest names a sha256 for each bundle, a download that does not match it is discarded rather than installed, and both files are verified before either is moved β so the CLI and server can never end up from different versions.
The check runs after your command has printed its answer, never before, and any failure is silent: being offline is not an error worth interrupting you for. Notices go to stderr, so piping output into a prompt stays clean.
A replaced bundle takes effect the next time the process starts. Restart a Claude session to pick up a new MCP server.
Turn it off with SENTRINEL_AUTO_UPDATE=false β in ~/.sentrinel/env to make
it stick.
Changing a key later
API Keys β Edit. Renaming is in place. Changing what a key is for β a read-only agent key to a may-resolve one, say β issues a new secret and revokes the old, because the kind lives in the prefix and the two must agree. Re-run the installer with the new key:
curl -fsSL https://sentrinel.dev/install-mcp.sh | SENTRINEL_API_KEY=snt_mcprw_β¦ bash
Troubleshooting
| You see | It means |
|---|---|
| Empty results when you know there is data | Check the window. period takes 30m, 24h, 7d, 2w β or plain seconds. |
SENTRINEL_API_KEY does not look like a key |
The example above is a placeholder β snt_mcp_β¦ ends in an ellipsis. Copy the whole value from the dashboard and re-run the installer. |
SENTRINEL_API_KEY is a server (β¦) key |
You used the SDK's, collector's or exporter's key. Issue an AI agent key; every other kind deliberately cannot read. |
401 β¦ may have been revoked |
The key was revoked, or mistyped. Issue a new one. |
403 on set_issue_status / resolve |
A read-only agent key. Issue an "AI agent β may resolve issues" key for that one write. |
403 on everything for one app |
The key is pinned to a different app. Check which app it was issued under. |
sentrinel-mcp: command not found |
~/.local/bin is not on your PATH. Add it, or give the agent the absolute path. |
| An agent says the server "returned nothing" or failed to start | A GUI app launched from the Dock does not read your shell profile. Use the absolute path: /Users/you/.local/bin/sentrinel-mcp. |
SENTRINEL_API_URL is not set |
No ~/.sentrinel/env β you installed without a key. Write the file, or re-run the installer with SENTRINEL_API_KEY=β¦. |
| The installer says a URL "returned a web page" | A CDN served the site's 404 instead of the bundle. Retry; if it persists the release is mid-publish. |
| Empty results | The window. Reads default to 7 days for issues and 24 hours for logs; pass period. |