Read-side MCP for error tracking: letting an AI agent triage your tracker
Self-hosted observability stops being a dashboard the moment an AI agent can read from it. A read-side MCP server turns your error tracker into a set of tools Claude or Cursor can call during triage — list issues, fetch stack traces, group by release — without ever writing back.
20 seconds. Wrap your error tracker's read endpoints in an MCP server. The agent gets list_issues, get_issue, list_events, search_issues as tools. No resolve_issue, no delete_event, no token rotation. Triage becomes a single Claude or Cursor session against real data; humans still own resolution.
60 seconds. The trend showing up across X and Hacker News this month is the same one: people want their AI agent to read from their self-hosted observability stack, not just write to it. A read-side MCP is the cheapest way to do that for an error tracker. It is one Python file in front of a project-scoped API token. The agent loads it, the user asks "what broke on this release," and the agent walks the issue list, opens stack traces, correlates them with the previous deploy, and proposes a hypothesis. Resolution stays in the UI under a human name. The four guardrails — read-only tokens, payload redaction, rate limits, project scoping — keep the cost of a bad day low.
This guide covers the design of the server, the four tools to expose first, a working minimal implementation against the urgentry API, what the agent should and should not see, and the cases where this still falls short.
What people are asking for
The X thread that nudged this guide came from an operator running self-hosted observability with OIDC and a small fleet of containers. The sentiment was uncomplicated: now that I own my metrics and logs, I want my AI agent to read them. A reply from a different operator put the same wish more directly: self-hosted observability already pencils on cost and ownership, but the upside grows once an agent can poke at it during an incident.
That is what a read-side MCP server is for. You already have a tracker. You already have a token. You want the agent that lives next to your editor to use both, the same way it uses your filesystem and your shell. The shape that actually works in production is narrow: read endpoints only, payload redaction on the way out, a token scoped to the projects you trust the agent to see.
Why read-side, and only read-side
The first instinct when wiring an agent to your error tracker is to expose everything. The agent could resolve, ignore, merge, assign, snooze. None of that should ship in the first version.
Resolution is a decision with consequences. A resolved issue stops paging, drops out of dashboards, and earns a closed-as-fixed badge that affects how the next regression gets handled. That decision should sit with a human whose name is in the audit log. If you put a write tool on the MCP server, the audit log starts saying things like "resolved by claude-agent-token-v3" and you lose the thread of who actually closed what.
Read-only also makes the blast radius of a bad day tractable. The worst the agent can do with read tools is leak data. That risk is real, and the guardrails below address it. The worst it can do with write tools is silently change the state of your incident response while you are not looking. The first failure mode is recoverable; the second is not.
Read-only also matches the actual job. The thing an agent is good at — reading three stack traces, noticing they share a frame, asking what changed in the last deploy — is a read job. The thing it is bad at — owning the call about whether a one-off counts as resolved — is a write job. Match the shape to the strength.
The four tools to expose first
The tool surface that covers the bulk of useful triage is small. Start here, add the fifth or sixth only when you watch the agent miss them.
list_issues. Returns the open issues for a project, ranked by recency or frequency. Parameters: project, time window, environment, optional release filter. The agent uses this to answer "what is currently broken?"get_issue. Returns the metadata for one issue: title, level, count, first seen, last seen, assignee if any, the canonical exception, and the most recent event's stack trace. The agent uses this to dig into a specific incident.list_events. Returns the latest N events for an issue, with payloads already redacted. The agent uses this to confirm a hypothesis: is the failure intermittent, environment-specific, tied to one release?search_issues. Free-text search across issue titles and exception messages, scoped to a project and a window. The agent uses this to ask "have we seen this stack frame before?"
That is the minimum. Almost every triage transcript I have looked at fits inside those four. Resist the temptation to add a fifth until you have watched five real sessions and seen the same gap twice.
A minimal server against the urgentry API
urgentry exposes the Sentry-compatible REST API at /api/0/. An MCP server in front of it is one file. The example below uses the official Python MCP SDK; the JavaScript SDK has the same shape.
from mcp.server.fastmcp import FastMCP
import httpx, os, re
URGENTRY_HOST = os.environ["URGENTRY_HOST"]
URGENTRY_TOKEN = os.environ["URGENTRY_TOKEN"]
ORG = os.environ["URGENTRY_ORG"]
client = httpx.Client(
base_url=f"{URGENTRY_HOST}/api/0",
headers={"Authorization": f"Bearer {URGENTRY_TOKEN}"},
timeout=10.0,
)
PII_FIELDS = re.compile(r"(?i)(email|phone|ssn|api[_-]?key|authorization|cookie)")
def redact(payload: dict) -> dict:
if not isinstance(payload, dict):
return payload
return {
k: ("[REDACTED]" if PII_FIELDS.search(k) else redact(v))
for k, v in payload.items()
}
mcp = FastMCP("urgentry-read")
@mcp.tool()
def list_issues(project: str, hours: int = 24, environment: str | None = None) -> list:
"List recent open issues for a project."
params = {"statsPeriod": f"{hours}h", "query": "is:unresolved"}
if environment:
params["environment"] = environment
r = client.get(f"/projects/{ORG}/{project}/issues/", params=params)
return [
{"id": i["id"], "title": i["title"], "count": i["count"],
"lastSeen": i["lastSeen"], "level": i["level"]}
for i in r.json()
]
@mcp.tool()
def get_issue(issue_id: str) -> dict:
"Get metadata and the latest event for one issue."
issue = client.get(f"/issues/{issue_id}/").json()
latest = client.get(f"/issues/{issue_id}/events/latest/").json()
return {
"id": issue["id"],
"title": issue["title"],
"firstSeen": issue["firstSeen"],
"lastSeen": issue["lastSeen"],
"assignedTo": issue.get("assignedTo"),
"stacktrace": redact(latest.get("entries", [])),
}
@mcp.tool()
def list_events(issue_id: str, limit: int = 10) -> list:
"Recent events for an issue, with payloads redacted."
r = client.get(f"/issues/{issue_id}/events/", params={"limit": limit})
return [redact(e) for e in r.json()]
@mcp.tool()
def search_issues(project: str, query: str, hours: int = 168) -> list:
"Free-text search across issue titles and exceptions."
r = client.get(
f"/projects/{ORG}/{project}/issues/",
params={"query": query, "statsPeriod": f"{hours}h"},
)
return [{"id": i["id"], "title": i["title"], "count": i["count"]} for i in r.json()]
if __name__ == "__main__":
mcp.run()
That is the whole server. You point Claude Code at it with an mcp.json entry and the four tools show up in the next session. The redaction pass is deliberately conservative; tighten the regex once you know which fields your stack actually carries.
What the agent should and should not see
The threat model is straightforward. An error event captures more personal data than most teams realize: query strings, form bodies on failed logins, exception messages that interpolate user input. An MCP server that proxies events into an agent's context is the last place you want to find any of it.
The defense is layered. Strip at ingest with Relay or the OTel Collector — covered in the PII scrubbing guide — so the worst material never lands. Strip again at the read-side MCP server, because you cannot trust that the first pass caught everything. The cost of the second pass is one regex per field; the cost of leaking a customer email into a context window that gets logged is measurable in apology emails. Run both.
Scope the API token to the smallest set of projects the agent needs. urgentry tokens, like Sentry tokens, can be project-scoped. A token for a triage agent that helps with the API service should not also see the internal HR app. If the token is wider than the audience, you have a leak waiting to happen.
The four guardrails
Four guardrails make this safe enough to leave running.
- Read-only tokens. Generate a token with only the
project:readandevent:readscopes. The MCP server cannot issue a write request because the token will not authorize one. - Payload redaction at the boundary. The
redactfunction above runs on every event payload before it leaves the server. Tighten the field list to match your stack. Treat it as the second line, not the only line. - Rate limit the server. An agent that loops on
list_eventswith no limit can pull tens of thousands of events in a minute. Cap requests per minute at the MCP layer, not just upstream, so a runaway loop fails closed. - Project scoping. One token per agent context, scoped to the smallest project set that makes sense. If the same human uses two agents for two products, give them two tokens.
What a real triage session looks like
The session that motivated this guide had a pattern that repeated three times in a row. The user opened Claude Code in the repo, typed "what broke after this morning's deploy on the api project," and let the agent run. The agent called list_issues with the right environment, noticed three issues whose firstSeen sat after the deploy, called get_issue on each, recognized two of them shared a frame in the same handler, and proposed a one-line change. The human read the proposal, made the change, deployed, and only then resolved the issues in the urgentry UI under their own name.
That whole loop took eight minutes. None of it required a write tool on the MCP server. The agent's value was in the reading and the correlation; the write decisions stayed where they belong.
Where this still falls short
The honest gaps, in the order they bite.
Source links are weak. The agent can read a stack trace but cannot always map a frame back to a specific commit in your local repo. The fix is to send releases with commit SHAs from your CI, which most teams already do for source maps, and have the agent cross-reference. The MCP server itself does not need to change.
Correlation across signals is missing. An error often has a corresponding log line or a trace span that explains it. A pure error-tracking MCP cannot see those. The pattern that works is to run a second MCP server that reads from your logs and a third that reads from your traces; the agent calls all three. Each server stays small and read-only.
The agent will hallucinate hypotheses on thin evidence. Read tools do not stop the agent from confidently proposing a fix to a bug it does not understand. The mitigation is human review, not better tooling. Build the loop assuming the agent's first idea is wrong and that the value is in the second one after you push back.
What to ship this week
One Python file, four tools, a project-scoped token, a redaction pass. That is the entire first version. Point Claude Code or Cursor at it tomorrow morning, run one triage session, and see what the agent reaches for that you did not expose. Add the fifth tool when it shows up twice.
The version after that is the same shape but for logs and for traces. Three small read-side servers beats one big do-everything server, because the threat model and the redaction rules for each signal are different, and because when one breaks the other two keep working.
Sources
- Model Context Protocol specification — the protocol the example server implements, including the tool, resource, and prompt primitives the agent sees.
- modelcontextprotocol/python-sdk on GitHub — the Python SDK used in the worked example;
FastMCPships in this repo. - Sentry REST API reference — the read endpoints (
/projects/{org}/{project}/issues/,/issues/{id}/,/issues/{id}/events/) that urgentry implements with the same shape. - urgentry guide: PII scrubbing for error events — the upstream pass that pairs with the redaction step in the MCP server.
- urgentry compatibility matrix — the source-scanned list of 218 Sentry REST API operations the MCP server can call against urgentry without modification.
Frequently asked questions
What is a read-side MCP server for an error tracker?
It is a small MCP server that exposes your error tracker's query endpoints as MCP tools so an AI agent like Claude Code or Cursor can list issues, read stack traces, and pull recent events. It exposes only retrieval, never mutation. The agent triages and proposes fixes; humans still resolve issues in the UI.
Why read-side only? Why not let the agent resolve issues too?
Resolution decisions need stable accountability and an audit trail tied to a human. A read-side server keeps the agent fast and useful for triage while keeping write authority on the human reviewer. The same shape works for the first month of any AI-on-observability project, and you can add narrow write tools later once you know what you trust the agent to do.
Does urgentry ship this MCP server out of the box?
Not yet. urgentry exposes the Sentry-compatible REST API; the MCP server is a thin wrapper you run beside the agent. The guide includes a minimal Python implementation against urgentry's API token. A first-party server is on the roadmap once the tool surface stabilizes.
What stops the agent from leaking PII through this server?
Three things, in order. First, scrub PII at ingest with Relay or the OTel Collector so it never enters storage. Second, the read-side MCP server applies a redaction pass on event payloads before returning them, dropping headers like Authorization and any field matching common PII patterns. Third, scope the API token to the smallest project set the agent needs.
How is this different from agents producing errors with OTel?
Producing errors is the write side: your agent ships its own crashes and tool-call spans to a tracker. The read-side MCP is the opposite direction: the agent reads the tracker's existing data to help a human triage someone else's bugs. Most teams need both, but they are separate plumbing with separate threat models.
An error tracker your agent can actually read.
urgentry exposes the Sentry-compatible REST API on a single Go binary. Point a read-side MCP server at it tomorrow morning and let Claude or Cursor triage with you. 218 API operations covered, $5 VPS minimum, your data, your token.