Search by

stubbedev / sentry-mcp

stubbedev

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

Statistics

Installs: 149

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v0.2.9 2026-10-05 06:58 UTC

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_search with resource=projects
  • "find user alice" → sentry_search with resource=users, query=alice
  • "show unresolved issues in my-web-app" → sentry_search with projectSlug, status=unresolved
  • "what's issue 5217" → sentry_get_issue with issueIdOrUrl=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_issue with status=resolved, comment=...
  • "list releases for my org" → sentry_raw_api with endpoint=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-online can 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: initialize returns an Mcp-Session-Id header; include it on every subsequent request. DELETE ends a session; idle sessions expire after 30 minutes.
  • Notifications (no id) return 202 Accepted with no body.
  • GET opens an SSE stream (text/event-stream) for server→client messages, scoped to the session — used to request workspace roots (see below).
  • GET /healthz returns {"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:

  1. MCP roots — if the client advertises the roots capability at initialize, the server requests roots/list over the SSE stream and caches the result (refreshed on notifications/roots/list_changed).

  2. 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/b
    

    A 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_frames on a PHP project has no instructionAddr/symbolAddr/module columns 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 _all and stated once instead of 25 times.
  • Derivable values are dropped. permalink never ships — it is a pure function of id, and the link templates are in the server instructions. Timestamps come back at second precision, still absolute so they stay comparable across calls. A metaType/metaValue pair that merely restates title is dropped, as is a project name that only repeats its slug.
  • 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", or includeFields=["id","title","latest_event.entries"] for the absolute minimum.
  • sentry_get_event: defaults to 5 prioritised entries; entries_info.available_types lists the rest. Pass entryType="exception" for a stack trace, or limit/offset to page.
  • sentry_stack_frames: a flat frame table plus source — 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_TOKEN secret is required
  • Composer needs no publish step: Packagist reads composer.json and the vX.Y.Z tags straight from GitHub. composer.json deliberately has no version field — the tag is the version, and the launcher reads the binary version from package.json at that tag, which publish.yml has already checked against the tag
  • The release step uses the default GITHUB_TOKEN
  • The Nix flake auto-tracks releases: flake.nix reads its version from package.json, so bumping the version updates the flake too. The Flake workflow (.github/workflows/flake.yml) recomputes vendorHash on any change to go.mod/go.sum/sources and commits it back, so nix build always works against master.

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-mcp at 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

  1. Log in to your Sentry instance.
  2. Click your profile avatar → User settings → Auth tokens (or visit /settings/account/api/auth-tokens/).
  3. Click Create New Token.
  4. Give the token a name (e.g. sentry-mcp) and grant scopes:
    • org:read
    • project:read
    • event:read
    • project:write (only if you want to update issue status or assignee)
    • event:admin (only if you want to comment on issues)
  5. 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, instructions
  • http.go — Streamable HTTP transport wiring (SDK handler, sessions, --http)
  • roots.go — workspace roots: header-pinned (proxy) or fetched via the client roots/list
  • sentry.go — Sentry API client, tool handlers, TOON/JSON rendering, helpers
  • compact.go — response projection: flat uniform rows, hoisted constants, dropped derivables
  • drill.go — shape sketching and dot-path walking for sentry_raw_api
  • resolve.go — issue/event/assignee resolution: short IDs, issue-scoped events, member lookup
  • config.go — config resolution (--config / env / file / XDG)
  • tools.json — tool schemas, embedded into the binary via go:embed
  • bin/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 of bin/cli.mjs) that fetches it on first run when the plugin is not allowed
  • flake.nix — Nix package / app / dev shell