stubbedev / sentry-mcp
MCP server for self-hosted Sentry (Go, distributed as a prebuilt binary)
Package info
github.com/stubbedev/sentry-mcp
Language:Go
Type:composer-plugin
pkg:composer/stubbedev/sentry-mcp
Requires
- php: >=8.1
- composer-plugin-api: ^2.0
- ext-openssl: *
Requires (Dev)
None
Suggests
- ext-curl: Downloads the binary; without it allow_url_fopen + openssl are used
- ext-pcntl: Lets bin/sentry-mcp replace itself with the native binary (execve) instead of keeping an idle PHP process for the session
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-05 06:58:07 UTC
README
A Model Context Protocol (MCP) server for self-hosted Sentry, written in Go. Exposes tools for natural-language workflows around issues, events, stack traces, and debug-symbol triage.
Ships as a single static binary (stdlib + one small Go dependency) with a fast cold start and a tiny footprint. Install it whichever way fits your stack — npm (npx), Composer, a one-click Claude Desktop bundle, go install, a prebuilt release binary, or the Nix flake — and structured responses are projected and serialized as TOON — ~26% of the tokens of the raw nested JSON, with _full on every response to fetch back anything that was trimmed.
Note: This server targets self-hosted Sentry installs. It will also work against sentry.io, but the official Sentry MCP is a better fit there.
Tools
Workflow
| Tool | Description |
|---|---|
sentry_get_dev_context |
Master entry point: configured instance + org, your Sentry identity, unresolved issues assigned to you, and recent unresolved issues across the org |
Discovery & read
| Tool | Description |
|---|---|
sentry_search |
Discover resources: issues (default), projects, teams, or users via resource param. Use users to look up valid usernames before assignment. |
sentry_get_issue |
Full details for one issue (by ID or URL) with field/grep/stack-frame filtering to keep responses small |
sentry_get_event |
Full details for one event with smart entry prioritisation and pagination |
sentry_stack_frames |
Structured stack-trace frames only (function/file/line/inApp) — best for debug analysis |
sentry_check_dsym |
Check whether iOS/macOS/Android debug symbols are missing for an event |
sentry_raw_api |
Raw call to any Sentry API endpoint; drill with path, sketch with outline, filter with grepPattern, page with maxChars/charOffset |
Mutation
| Tool | Description |
|---|---|
sentry_mutate_issue |
Update status, assign, and/or add a comment on an issue in one call |
sentry_comment |
Add, update, or delete a comment on an issue (action: add / update / delete) |
Many tools accept project as an alias for projectSlug.
Natural language examples
- "what am I working on?" →
sentry_get_dev_context - "list projects in this org" →
sentry_searchwithresource=projects - "find user alice" →
sentry_searchwithresource=users,query=alice - "show unresolved issues in my-web-app" →
sentry_searchwithprojectSlug,status=unresolved - "what's issue 5217" →
sentry_get_issuewithissueIdOrUrl=5217 - "give me the stack trace for event abc123 in apple-ios" →
sentry_stack_frames - "are dSYMs missing on this crash?" →
sentry_check_dsym - "resolve issue 5217 and leave a comment" →
sentry_mutate_issuewithstatus=resolved,comment=... - "list releases for my org" →
sentry_raw_apiwithendpoint=organizations/<org>/releases/
Setup
1. Create a config file
Create ~/.sentry-mcp.json:
{
"$schema": "https://raw.githubusercontent.com/stubbedev/sentry-mcp/master/sentry-mcp.schema.json",
"sentry": {
"url": "https://sentry.example.com",
"token": "your-sentry-auth-token",
"org": "your-org-slug"
}
}
The $schema field is optional but enables editor autocomplete and validation.
The token needs at minimum: org:read, project:read, event:read. Add project:write and event:admin if you want to mutate issues or comment.
Alternatively, use environment variables (or a .env file in this directory):
SENTRY_URL=https://sentry.example.com SENTRY_AUTH_TOKEN=your-sentry-auth-token SENTRY_ORG_SLUG=your-org-slug
Config is resolved in this order: --config <path> CLI arg → SENTRY_MCP_CONFIG env var → ~/.sentry-mcp.json → $XDG_CONFIG_HOME/sentry-mcp/config.json (defaults to ~/.config/sentry-mcp/config.json) → .sentry-mcp.json in cwd → environment variables. A leading ~ in --config / SENTRY_MCP_CONFIG is expanded by the server, so it also works when a GUI client launches it without a shell.
2. Install
The same server is published to both npm (@stubbedev/sentry-mcp) and Composer (stubbedev/sentry-mcp). Both fetch the prebuilt Go binary for your platform from the matching GitHub release, so neither needs Go: npm through a thin launcher, Composer at install time.
npm (Node 24+):
npx -y @stubbedev/sentry-mcp@latest # no install, always the newest release npm install -g @stubbedev/sentry-mcp # global: puts `sentry-mcp` on your PATH npm install --save-dev @stubbedev/sentry-mcp # per project: node_modules/.bin/sentry-mcp
Composer (PHP 8.1+):
composer global require stubbedev/sentry-mcp # global: $(composer global config home)/vendor/stubbedev/sentry-mcp/bin/sentry-mcp-native composer require --dev stubbedev/sentry-mcp # per project: vendor/stubbedev/sentry-mcp/bin/sentry-mcp-native
The Composer package is a Composer plugin: on composer install / update it downloads the binary for your OS and architecture to vendor/stubbedev/sentry-mcp/bin/sentry-mcp-native (sentry-mcp-native.exe on Windows), and your MCP client runs that directly, with no PHP at runtime. Composer asks once whether to trust the plugin; for non-interactive installs, allow it up front with composer config allow-plugins.stubbedev/sentry-mcp true (or composer global config ...). Set SENTRY_MCP_SKIP_DOWNLOAD=1 to skip the install-time download.
If you declined the plugin, the PHP launcher vendor/bin/sentry-mcp downloads the binary on its first run instead. With the pcntl extension (standard on Linux/macOS CLI builds) it execs the binary and exits; without it, one idle PHP process stays behind for the session.
Every client example below uses npx. To use another install, swap only the command:
| Install | command |
args before the server flags |
|---|---|---|
| npx | npx |
-y, @stubbedev/sentry-mcp@latest |
| npm global | sentry-mcp |
— |
| npm per project | node_modules/.bin/sentry-mcp |
— |
| Composer global | sentry-mcp (with Composer's bin dir on PATH) |
— |
| Composer per project | vendor/stubbedev/sentry-mcp/bin/sentry-mcp-native |
— |
go install / release binary / Nix |
sentry-mcp, or its absolute path |
— |
GUI clients (Claude Desktop, Cursor launched from the dock, and so on) do not inherit your shell's PATH or working directory, so give them an absolute command path — see Claude Desktop.
3. Connect to your AI tool
Which run method should I use?
| Method | Best for | Trade-off |
|---|---|---|
.mcpb bundle |
Claude Desktop — double-click install, credentials entered in a dialog | Claude Desktop only; update by installing the next release's bundle |
go install / prebuilt binary / Nix |
Lowest overhead — the MCP client execs the native binary directly, no Node or PHP process | You manage updates (re-run go install, or nix run re-resolves on each launch) |
npx @latest |
Easiest, zero install, always the newest version | Auto-downloads the matching binary, but leaves one small idle Node process for the session (zero per-call latency — stdio is inherited) |
| Composer | PHP projects — pin the version in composer.json/composer.lock next to the rest of your tooling and share one .mcp.json with the team |
Updates with composer update; needs PHP 8.1+ |
Recommendation: for day-to-day use point your client at the native binary (go install or Nix) for the leanest process; reach for npx when you want zero setup or pinned auto-updates, and Composer when the repo you work in is a PHP project. All the client configs below are interchangeable — swap the command/args for whichever method you picked.
Note: with
npx,--prefer-onlinecan break MCP startup in some clients. Keep the command simple and use the update steps when you want to refresh.
Claude Code
# npm claude mcp add sentry -- npx -y @stubbedev/sentry-mcp@latest --config ~/.sentry-mcp.json # Composer (global) claude mcp add sentry -- sentry-mcp --config ~/.sentry-mcp.json
For a PHP project that has stubbedev/sentry-mcp in require-dev, commit a project-scoped
.mcp.json so everyone on the team gets the server pinned to the version in composer.lock
(Claude Code starts it from the project root, so the relative path resolves):
{
"mcpServers": {
"sentry": {
"command": "vendor/stubbedev/sentry-mcp/bin/sentry-mcp-native"
}
}
}
The same file with "command": "npx", "args": ["-y", "@stubbedev/sentry-mcp@latest"] does the
job for an npm project. Credentials then come from each developer's ~/.sentry-mcp.json.
Claude Desktop
One-click (recommended). Grab the .mcpb bundle for your platform from the
latest release —
sentry-mcp_darwin_arm64.mcpb (Apple Silicon), sentry-mcp_darwin_amd64.mcpb (Intel Mac),
sentry-mcp_windows_amd64.mcpb — then double-click it, drag it onto the Claude Desktop
window, or use Settings → Extensions → Advanced settings → Install Extension…. The
install dialog asks for the Sentry URL, auth token and org slug; the token is stored by
Claude Desktop rather than in a file. Leaving the fields blank reuses an existing
~/.sentry-mcp.json.
The bundle carries the binary, so there is no Node, no npx, no PATH to fix and no JSON
to edit. MCP Bundles are a Claude Desktop
feature today; other clients use the config files below.
Manual config. Claude Desktop is a GUI app: it launches the server with a minimal
PATH, no shell, and / as the working directory. So command must be an absolute
path (a bare npx fails with spawn npx ENOENT), and a .env file or a relative
--config path never resolves — pass credentials as env instead:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"sentry": {
"command": "/absolute/path/to/sentry-mcp",
"env": {
"SENTRY_URL": "https://sentry.example.com",
"SENTRY_AUTH_TOKEN": "your-sentry-auth-token",
"SENTRY_ORG_SLUG": "your-org-slug"
}
}
}
}
To keep npx, set command to the absolute path of your launcher (which npx, e.g.
/opt/homebrew/bin/npx) with "args": ["-y", "@stubbedev/sentry-mcp@latest"]. For a Composer
global install, use the absolute path of the proxy, e.g.
/Users/you/.composer/vendor/bin/sentry-mcp (composer global config bin-dir --absolute
prints the directory).
Server stderr is logged to ~/Library/Logs/Claude/mcp-server-sentry.log (macOS) or
%APPDATA%\Claude\logs\mcp-server-sentry.log (Windows) — read that first when a
connection fails.
Cursor
Add to ~/.cursor/mcp.json (global) or .cursor/mcp.json (project-only):
{
"mcpServers": {
"sentry": {
"command": "npx",
"args": ["-y", "@stubbedev/sentry-mcp@latest", "--config", "/Users/you/.sentry-mcp.json"]
}
}
}
Windsurf
Add to ~/.codeium/windsurf/mcp_config.json:
{
"mcpServers": {
"sentry": {
"command": "npx",
"args": ["-y", "@stubbedev/sentry-mcp@latest", "--config", "/Users/you/.sentry-mcp.json"]
}
}
}
Zed
Add to ~/.config/zed/settings.json:
{
"context_servers": {
"sentry": {
"command": {
"path": "npx",
"args": ["-y", "@stubbedev/sentry-mcp@latest", "--config", "/home/you/.sentry-mcp.json"]
}
}
}
}
OpenCode
Add to opencode.json in your project root (or ~/.config/opencode/opencode.json for global):
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"sentry": {
"type": "local",
"command": ["npx", "-y", "@stubbedev/sentry-mcp@latest", "--config", "/home/you/.sentry-mcp.json"]
}
}
}
environment accepts the SENTRY_* variables if you would rather not keep a config file.
Set "type": "remote" with "url" and "headers" to point at a shared
HTTP server instead.
Codex (CLI, IDE extension, app)
One command — it writes the config all three read:
codex mcp add sentry -- npx -y @stubbedev/sentry-mcp@latest # npm codex mcp add sentry -- sentry-mcp # Composer (global)
Or edit ~/.codex/config.toml directly (.codex/config.toml in a trusted project for a
project-scoped server). Note the TOML table name is mcp_servers, with an underscore:
[mcp_servers.sentry] command = "npx" args = ["-y", "@stubbedev/sentry-mcp@latest", "--config", "/home/you/.sentry-mcp.json"] # Optional — instead of a config file: [mcp_servers.sentry.env] SENTRY_URL = "https://sentry.example.com" SENTRY_AUTH_TOKEN = "…" SENTRY_ORG_SLUG = "your-org-slug"
Codex picks the transport from the keys present: command means stdio, url means
streamable HTTP — so a shared HTTP server is
url = "http://127.0.0.1:8765/mcp" instead of command.
VS Code / GitHub Copilot
# npm code --add-mcp '{"name":"sentry","command":"npx","args":["-y","@stubbedev/sentry-mcp@latest"]}' # Composer (global) code --add-mcp '{"name":"sentry","command":"sentry-mcp"}'
Or commit .vscode/mcp.json with a servers object of the same shape to share it with the
repo.
Go install (no npm)
If you have Go installed and prefer a native binary on your PATH:
go install github.com/stubbedev/sentry-mcp@latest
Then point your MCP client at the sentry-mcp command directly, e.g. for Claude Code:
claude mcp add sentry -- sentry-mcp --config ~/.sentry-mcp.json
Prebuilt binaries for every platform are also attached to each GitHub release if you'd rather not build.
Nix (flake)
The repo is a flake exposing a sentry-mcp package, app, and dev shell. Run it directly:
nix run github:stubbedev/sentry-mcp -- --config ~/.sentry-mcp.json
Or consume it from your own flake:
{ inputs.sentry-mcp.url = "github:stubbedev/sentry-mcp"; # ... then use inputs.sentry-mcp.packages.${system}.default }
For an MCP client, point the command at the built binary, e.g.:
claude mcp add sentry -- nix run github:stubbedev/sentry-mcp -- --config ~/.sentry-mcp.json
The package version is read from package.json (so it follows releases), and CI keeps vendorHash current automatically — see Releases.
Any other MCP-compatible tool
Most clients accept the Claude Desktop shape — an mcpServers object keyed by name, with
command, args and env:
{
"mcpServers": {
"sentry": {
"command": "npx",
"args": ["-y", "@stubbedev/sentry-mcp@latest"],
"env": {
"SENTRY_URL": "https://sentry.example.com",
"SENTRY_AUTH_TOKEN": "…",
"SENTRY_ORG_SLUG": "your-org-slug"
}
}
}
}
LM Studio uses exactly that shape in its own mcp.json (edit it from the app's plugin
panel); Cherry Studio, Witsy, Jan and 5ire have in-app MCP dialogs with the same fields.
Goose is the exception — its ~/.config/goose/config.yaml uses extensions: with cmd
rather than command:
extensions: sentry: enabled: true type: stdio cmd: npx args: ["-y", "@stubbedev/sentry-mcp@latest"] envs: SENTRY_URL: https://sentry.example.com SENTRY_AUTH_TOKEN: "…" SENTRY_ORG_SLUG: your-org-slug
Every GUI client brings the caveats from the Claude Desktop section:
absolute command path, minimal PATH, no usable cwd — so pass credentials as env (or
an absolute --config path) rather than relying on a .env file.
ChatGPT (desktop / web) — not supported
ChatGPT connectors accept remote HTTPS MCP servers only (streamable HTTP or SSE, with
OAuth or no auth); it cannot spawn a local stdio server. This server's --http mode speaks
the right protocol, but making it work would mean exposing an endpoint that reaches your
self-hosted Sentry to OpenAI's servers, with no place in the connector UI for
authentication this server would recognise. Use a client from the list above.
HTTP transport (behind a proxy)
By default the server speaks JSON-RPC over stdio. It can also serve the MCP Streamable HTTP transport on a single endpoint — useful when running behind a reverse proxy or sharing one process across clients.
Enable it with --http (or the SENTRY_MCP_HTTP_ADDR env var):
./sentry-mcp --http # listen on 127.0.0.1:8765/mcp ./sentry-mcp --http=0.0.0.0:8765 # bind all interfaces (put a proxy in front) ./sentry-mcp --http --http-path=/sentry # custom endpoint path
| Flag | Env | Default |
|---|---|---|
--http[=addr] |
SENTRY_MCP_HTTP_ADDR (or SENTRY_MCP_HTTP=1) |
127.0.0.1:8765 |
--http-path=<path> |
SENTRY_MCP_HTTP_PATH |
/mcp |
Behaviour:
- Clients POST a JSON-RPC request (or a batch array) and get the JSON-RPC response in the body.
- Sessions:
initializereturns anMcp-Session-Idheader; include it on every subsequent request.DELETEends a session; idle sessions expire after 30 minutes. - Notifications (no
id) return202 Acceptedwith no body. GETopens an SSE stream (text/event-stream) for server→client messages, scoped to the session — used to request workspace roots (see below).GET /healthzreturns{"status":"ok"}for liveness probes.- The default host is loopback so the server is not accidentally exposed; set an explicit host to listen wider, and front it with TLS/auth at the proxy.
Sentry credentials still come from the config file / env vars — the whole server uses one Sentry identity.
Workspace roots (cwd / repo root)
So the server can know which repo/working-tree it is acting on (e.g. for a future shell-calling tool), it accepts the working root two ways:
-
MCP roots — if the client advertises the
rootscapability atinitialize, the server requestsroots/listover the SSE stream and caches the result (refreshed onnotifications/roots/list_changed). -
Proxy header — a reverse proxy/harness can pin the root directly with a request header, avoiding the round-trip. Accepted headers (comma-separated for multiple,
file://URI or plain path):X-Mcp-Root: file:///srv/myrepo X-Mcp-Roots: /srv/a, /srv/bA header value takes precedence over
roots/list.
The resolved roots are shown in sentry_get_dev_context. (No tool shells out
today, so roots are currently informational — the plumbing is in place for when
one does.)
Quick check:
SID=$(curl -s -D - -o /dev/null -X POST http://127.0.0.1:8765/mcp \ -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}' \ | awk 'tolower($1)=="mcp-session-id:"{print $2}' | tr -d '\r') curl -s -X POST http://127.0.0.1:8765/mcp -H "Mcp-Session-Id: $SID" \ -d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'
Updating existing installs
If your MCP client is already configured and you want the newest package version:
npx clear-npx-cache # npx npm update -g @stubbedev/sentry-mcp # npm global composer global update stubbedev/sentry-mcp # Composer global composer update stubbedev/sentry-mcp # Composer per project
Then restart your MCP client. Each update replaces the package directory, so the launcher fetches the new release's binary on the next start.
Manual install (optional)
If you prefer to clone and build locally (requires Go 1.26+):
git clone git@github.com:stubbedev/sentry-mcp.git cd sentry-mcp go build -o sentry-mcp .
Then use /path/to/sentry-mcp/sentry-mcp instead of the npx or Composer command in the configs above.
Output format (TOON)
Structured responses are serialized as TOON (Token-Oriented Object Notation) by default — a compact, LLM-friendly format that cuts ~30-60% of the tokens of equivalent JSON, with the biggest savings on uniform tables (issue lists, stack frames, users). Example:
users[2]{email,name,role,username}:
alice@example.com,Alice,owner,alice
bob@example.com,Bob,member,bob
Every data tool accepts format (toon | json) to override per call, and the env var SENTRY_MCP_FORMAT=json flips the default process-wide. Plain-text tools (sentry_get_dev_context, project listings, mutation/comment confirmations) are unaffected.
Issue references
Every tool that takes an issue accepts whichever form you have, because these are the forms the server itself hands out:
| Form | Example |
|---|---|
| Numeric ID | 60741 |
| Short ID | KONTAINER-BACKEND-4JH — what sentry_get_dev_context prints and every issue row returns |
| Issue URL | https://sentry.example.com/organizations/org/issues/60741/ (short-ID and ?query= links too) |
Short IDs resolve through /organizations/{org}/shortids/{shortId}/; numeric IDs and URLs are parsed locally with no extra request.
Issue → trace in one call
sentry_get_event, sentry_stack_frames, and sentry_check_dsym take issueIdOrUrl on its own and read that issue's latest event — no project slug, no event ID:
sentry_stack_frames issueIdOrUrl=KONTAINER-BACKEND-4JH
The full resolution order is: an issue reference (with an optional eventId, where latest is the default) uses the issue-scoped endpoint; projectSlug + eventId reads that event; projectSlug alone falls through to the project's most recent issue. Failing that, the error names both routes and lists the org's project slugs.
sentry_search resource=issues searches the whole organization when no projectSlug is given, so listing projects first is no longer a prerequisite.
Assignment
sentry_mutate_issue assignedTo= accepts a username, an email, a real name, or team:slug, and resolves it against the org's members. An ambiguous name is an error listing the candidates. Sentry accepts an actor string it cannot resolve and silently leaves the issue unassigned, so the response is verified — an assignment that did not stick is reported as an error rather than a confirmation.
Compact responses
Sentry events can blow past LLM context limits — a single event with a long stack trace and many breadcrumbs is easily 100K+ tokens. TOON is only half the saving; the other half is projecting each payload before it is serialized. On a 25-issue list the two together come to ~26% of the raw nested shape (see TestIssueListStaysTabular).
TOON only collapses an array into a table when every row is a flat object with the same key set — one nested object, or one key missing from a single row, and the whole array falls back to expanded per-field blocks at ~2.5x the characters. So compact.go keeps rows flat and uniform:
- Nested objects become scalar columns.
project→ its slug,assignedTo→ a name,metadata.type/.value→metaType/metaValue. - Columns null in every row are dropped, so
sentry_stack_frameson a PHP project has noinstructionAddr/symbolAddr/modulecolumns at all. The decision is made per response, never per row: omitting nulls row-by-row leaves ragged keys, which de-tabularizes the array and costs more than emitting the nulls. - Columns identical in every row are hoisted into
_alland stated once instead of 25 times. - Derivable values are dropped.
permalinknever ships — it is a pure function ofid, and the link templates are in the server instructions. Timestamps come back at second precision, still absolute so they stay comparable across calls. AmetaType/metaValuepair that merely restatestitleis dropped, as is a projectnamethat only repeats itsslug. - Advice prose is stated once, in the server instructions, rather than re-sent with every response.
Identifiers always survive — id, shortId, eventID, next_cursor are never hoisted or dropped — so a row can always be cross-referenced against another call.
Getting the detail back
Every projected response carries _full: the sentry_raw_api endpoint that returns the complete, unprojected payload. Nothing is lost, it just does not sit in the context window until asked for.
sentry_raw_api endpoint=<_full> outline=true # sketch the shape
sentry_raw_api endpoint=<_full> path=entries.0.data.values.0 # pull one subtree
path walks the response with dot notation and numeric array indices, and a wrong path replies with the keys that were actually available. This is deliberately an endpoint string rather than a server-side handle: a handle would need session affinity to resolve, so it would die on restart and would not work across replicas or under stateless HTTP.
A response over ~20K tokens comes back as a shape sketch instead of its contents, so the follow-up is a path into the part that matters rather than a guessed grep pattern.
Per-call knobs
sentry_get_issue:maxStackFrames=5,excludeFields=["stats","annotations"],grepPattern="AttributeError|process_activity", orincludeFields=["id","title","latest_event.entries"]for the absolute minimum.sentry_get_event: defaults to 5 prioritised entries;entries_info.available_typeslists the rest. PassentryType="exception"for a stack trace, orlimit/offsetto page.sentry_stack_frames: a flat frame table plussource— the source-code window for the deepest in-app frame, rather than context lines repeated on every frame.sentry_raw_api:path,outline,grepPattern,maxChars/charOffset.
grepPattern filters the rendered response — the TOON or JSON text you would otherwise receive, not the raw Sentry JSON — so patterns are written against what the response actually looks like. It returns matching lines plus one line of context either side, always keeps the table header (without it the matched rows have no column names), and is case-insensitive.
Reliability
Tool annotations. Every tool declares whether it writes, via the MCP annotations field: the six read tools carry readOnlyHint, and sentry_mutate_issue, sentry_comment, and sentry_raw_api carry destructiveHint. Hosts use these to stop prompting for reads and gate the ones that mutate. sentry_raw_api is declared destructive because it accepts PUT/POST/DELETE, even though GET is the common case.
Transient failures are retried. A request is sent up to 3 times with exponential backoff from 250ms, honouring a Retry-After header when the server sends one. 429 is retried for any method, since the request was rejected before it did anything; 502/503/504 are retried only for GET/PUT/DELETE, because a POST that may already have been applied would double-post a comment. Request bodies are replayed per attempt. A wait that would outlast the call's deadline is declined rather than slept through, so a retry never eats the whole 60s tool-call budget just to fail at the end of it.
Startup does not block on Sentry. The identity and project-list lookups that decorate the server instructions run concurrently under a 5s budget, and the server starts with whatever came back. Previously they ran sequentially on an unbounded context inside main() before the transport started, so an unresponsive instance could leave a GUI client looking hung with no tools listed.
Releases (Maintainers)
Each release ships prebuilt Go binaries (attached to the GitHub release), the npm wrapper @stubbedev/sentry-mcp, and the Composer wrapper stubbedev/sentry-mcp. .github/workflows/publish.yml runs on a pushed v* tag and: cross-compiles binaries for 14 targets — linux (amd64, arm64, arm/v7, 386, ppc64le, s390x, riscv64), darwin (amd64, arm64), windows (amd64, arm64, 386), and freebsd (amd64, arm64) — packs six of them into .mcpb bundles for one-click Claude Desktop install (darwin/windows/linux × amd64/arm64), attaches everything to the GitHub release, then publishes the npm package.
Bundle sources live in packaging/mcpb/ (manifest.template.json, icon.png, pack.sh). CI packs and validates one bundle on every run, so a manifest typo fails on the PR. npm run bundle builds one for the host platform into dist/.
Use semantic versioning. Breaking tool-surface changes should bump the minor version while <1.0.0 (for example 0.0.x -> 0.1.0).
Release flow — use the helper scripts (they run go vet + go test + smoke via preversion, bump package.json, then commit, tag, and push):
npm run release:patch # or release:minor / release:major
This runs npm version <type> (which creates the version commit and vX.Y.Z tag) then git push --follow-tags, which triggers publish.yml. Equivalent by hand:
git commit -am "0.2.2" && git tag v0.2.2 && git push origin HEAD --follow-tags
The publish workflow verifies that package.json version matches the tag before publishing.
- The npm step uses npm Trusted Publisher (OIDC), so no
NPM_TOKENsecret is required - Composer needs no publish step: Packagist reads
composer.jsonand thevX.Y.Ztags straight from GitHub.composer.jsondeliberately has noversionfield — the tag is the version, and the launcher reads the binary version frompackage.jsonat that tag, whichpublish.ymlhas already checked against the tag - The release step uses the default
GITHUB_TOKEN - The Nix flake auto-tracks releases:
flake.nixreads itsversionfrompackage.json, so bumping the version updates the flake too. TheFlakeworkflow (.github/workflows/flake.yml) recomputesvendorHashon any change togo.mod/go.sum/sources and commits it back, sonix buildalways works againstmaster.
Required setup (one-time):
- npm: in the package settings, add this GitHub repo/workflow as a Trusted Publisher
- Packagist: submit
https://github.com/stubbedev/sentry-mcpat packagist.org/packages/submit, then enable the GitHub integration (or add the Packagist webhook to the repo) so new tags appear without a manual update
Creating a Sentry auth token
- Log in to your Sentry instance.
- Click your profile avatar → User settings → Auth tokens (or visit
/settings/account/api/auth-tokens/). - Click Create New Token.
- Give the token a name (e.g.
sentry-mcp) and grant scopes:org:readproject:readevent:readproject:write(only if you want to update issue status or assignee)event:admin(only if you want to comment on issues)
- Click Create Token and copy the value — it will only be shown once.
Paste the token as sentry.token in your config file.
Development
Requires Go 1.26+. The only third-party dependency is the TOON encoder (github.com/toon-format/toon-go); everything else is the standard library.
go build -o sentry-mcp . # build (or: npm run build) go vet ./... # vet go test ./... # unit tests (or: npm run test) npm run smoke # build + validate tools/list ./sentry-mcp # run directly # Inspect the tool list by hand echo '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' | ./sentry-mcp
To use a specific config file:
./sentry-mcp --config /path/to/config.json
Built on the official modelcontextprotocol/go-sdk, which provides the JSON-RPC protocol, the stdio and Streamable HTTP transports, and the client roots round-trip.
Layout:
main.go— server setup, transport selection, tool registration + dispatch, instructionshttp.go— Streamable HTTP transport wiring (SDK handler, sessions,--http)roots.go— workspace roots: header-pinned (proxy) or fetched via the clientroots/listsentry.go— Sentry API client, tool handlers, TOON/JSON rendering, helperscompact.go— response projection: flat uniform rows, hoisted constants, dropped derivablesdrill.go— shape sketching and dot-path walking forsentry_raw_apiresolve.go— issue/event/assignee resolution: short IDs, issue-scoped events, member lookupconfig.go— config resolution (--config/ env / file / XDG)tools.json— tool schemas, embedded into the binary viago:embedbin/cli.mjs+scripts/— npm wrapper (downloads + execs the binary)src/ComposerPlugin.php+src/Binary.php+bin/sentry-mcp+composer.json— Composer plugin that fetches the binary at install time, plus the PHP launcher (the twin ofbin/cli.mjs) that fetches it on first run when the plugin is not allowedflake.nix— Nix package / app / dev shell