Coding agents fix bugs from a stack trace. Until now, the stack trace had to get to them the way everything else does: someone opens the dashboard, finds the issue, copies the trace, pastes it into a prompt, then goes back for the request body, then back again for the trace. The agent is fast. The person feeding it is the bottleneck.
So we built the other half. Sentrinel now ships an MCP server โ for Claude Code, Codex, Cursor, anything that speaks the protocol โ and a CLI for everything that does not. Both hand the agent the same thing: your issues, logs, traces and requests, on a key that can see one app and do nothing else.
The session now looks like this:
Look at Sentrinel's top unresolved issue, find the cause in this repo, and fix it.
The agent calls list_issues, picks one, calls get_issue for the stack trace and the ids of the request and trace behind the latest occurrence, calls get_request for the exact body that broke it, and starts reading your code. Nobody pastes anything.
What it gets
Six tools, and the order they are used in is the order you would use them in yourself:
| Tool | Gives the agent |
|---|---|
list_issues |
the bug list, newest-firing first, with ids |
get_issue |
the stack trace, where it fires, who it hit, and the request and trace ids of the latest occurrence |
get_request |
one captured request: headers, body, response, the error |
get_trace |
the span tree โ which call was slow or failed, and inside what |
search_logs |
log lines, each carrying the request and trace they belong to |
set_issue_status |
resolve, ignore or reopen โ once the fix has actually shipped |
Every renderer ends with the identifiers the agent needs for its next call. An issue does not just show a trace id; it says get_trace <id>. That sounds cosmetic. It is the difference between an agent that pulls the trace and one that asks you where to find it.
The key is the part we spent the time on
An agent's key lives in a config file on a laptop. That is the single most likely place a credential leaks, so we designed it to be safe to lose:
- It sees exactly one app. Not the org. Not a second app in the same org. Never another tenant โ the admin inspect header is refused too.
- It cannot write telemetry. Ingest refuses it, so a leaked key cannot poison your data.
- It cannot touch settings. No minting or revoking keys, no billing, no members. The "may resolve issues" variant's only write is an issue's status, enforced by an allowlist in the request guard rather than a denylist โ because the failure mode of a denylist is a route nobody remembered to add.
- Revocation is immediate. The next call is a
401. - It never appears on a command line. Neither the server nor the CLI takes a
--keyflag;psshows argv to every process on the machine. It comes from the environment and is never echoed in an error.
Every read tool carries readOnlyHint, so an agent's own guardrails treat them as safe. set_issue_status is the one write, and the server refuses it for a read-only key regardless of what the agent believes about its own permissions.
Hand it the SDK's ingest key by mistake and it refuses to start, naming the kind you gave it โ a 401 from the server would have read as "wrong key" when the truth is "wrong kind of key". The kinds themselves are the subject of the next post.
Setting it up
curl -fsSL https://sentrinel.dev/install-mcp.sh | SENTRINEL_API_KEY=snt_mcp_โฆ bash
That is the whole setup: it installs the server and the CLI, writes the key to
~/.sentrinel/env at 0600, and registers the server with Claude Code. The
key is in no config file and in no ps output, which is most of why it is
worth doing this way rather than pasting --env flags around.
Issue the key from API Keys โ Generate โ AI agent. Codex and Cursor configs, and every tool's arguments, are in the guide.
The CLI, for everything else
Not every harness speaks MCP. The CLI prints the same Markdown to stdout, so it works with a shell script, a DeepSeek harness, or a prompt you assemble by hand:
sentrinel issues # unresolved, last 7 days
sentrinel issue 5f3aโฆ # stack trace, request id, trace id
sentrinel request <id>
sentrinel logs --level error --search "card declined"
sentrinel issue 5f3aโฆ | claude -p "Find the cause of this in the current repo and fix it."
Failures go to stderr with exit 1, so a script can tell "no issues" from "could not reach the API". --json gives the raw response when you want to do something else with it.
One thing that surprised us
The server writes nothing to stdout except protocol frames. A single stray console.log there corrupts the stream, silently, and the agent reports that the server "returned nothing". We test for zero bytes on stderr and exactly the expected frames on stdout, because this is the sort of bug that would ship green and fail only in someone else's editor.
The whole thing is under 700 lines. Most of the design went into the key.