@rui.branco/sentry-mcp 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +219 -0
- package/index.js +24 -97
- package/package.json +3 -2
package/README.md
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# Sentry MCP Server
|
|
2
|
+
|
|
3
|
+
A [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server that brings Sentry error tracking context directly into Claude Code. Fetch issues, inspect stack traces, manage issue status, and search errors without leaving your development environment.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
When debugging production errors, context switching between Sentry and your code editor breaks flow and wastes time. This MCP server solves that by:
|
|
8
|
+
|
|
9
|
+
- **Fetching full issue details** - Get error metadata, event counts, assignment, and status instantly
|
|
10
|
+
- **Inspecting stack traces** - View full stack traces with in-app frame markers, breadcrumbs, and HTTP request context
|
|
11
|
+
- **Managing issues** - Resolve, reopen, ignore, and assign issues directly from Claude Code
|
|
12
|
+
- **Searching errors** - Use Sentry's search syntax to find issues across your project
|
|
13
|
+
|
|
14
|
+
## Features
|
|
15
|
+
|
|
16
|
+
| Feature | Description |
|
|
17
|
+
|---------|-------------|
|
|
18
|
+
| Issue Details | Title, level, status, event count, user count, culprit, assignee |
|
|
19
|
+
| Stack Traces | Full exception chain with in-app frames highlighted |
|
|
20
|
+
| Breadcrumbs | Last N events leading up to the error |
|
|
21
|
+
| HTTP Request | Method, URL, headers from the request that triggered the error |
|
|
22
|
+
| Tags & Contexts | Device, browser, OS, user info, and custom tags |
|
|
23
|
+
| Search | Full Sentry search syntax support |
|
|
24
|
+
| Multi-Instance | Support for multiple Sentry organizations/instances |
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
### Prerequisites
|
|
29
|
+
|
|
30
|
+
- Node.js 18+
|
|
31
|
+
- [Claude Code](https://claude.ai/code) CLI
|
|
32
|
+
- Sentry account with API access
|
|
33
|
+
|
|
34
|
+
### Step 1: Add to Claude Code
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
claude mcp add --transport stdio sentry -- npx -y @rui.branco/sentry-mcp
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Step 2: Get Your Sentry API Token
|
|
41
|
+
|
|
42
|
+
1. Go to [Sentry Auth Tokens](https://sentry.io/settings/account/api/auth-tokens/) (or your self-hosted Sentry equivalent)
|
|
43
|
+
2. Click **"Create New Token"**
|
|
44
|
+
3. Select scopes: `event:read`, `project:read`, `org:read`, `member:read`
|
|
45
|
+
4. Click **"Create Token"**
|
|
46
|
+
5. Copy the token
|
|
47
|
+
|
|
48
|
+
### Step 3: Configure Credentials
|
|
49
|
+
|
|
50
|
+
Run the setup with your credentials:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npx @rui.branco/sentry-mcp setup "YOUR_AUTH_TOKEN" "https://sentry.io" "your-org-slug" "your-project-slug"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
| Parameter | Description | Example |
|
|
57
|
+
|-----------|-------------|---------|
|
|
58
|
+
| Auth Token | The token you created in Step 2 | `sntrys_eyJpYX...` |
|
|
59
|
+
| Base URL | Your Sentry instance URL | `https://sentry.io` or `https://my-org.sentry.io` |
|
|
60
|
+
| Org Slug | Your Sentry organization slug | `my-company` |
|
|
61
|
+
| Project Slug | Default project slug (optional) | `frontend` |
|
|
62
|
+
|
|
63
|
+
Or run interactively (will prompt for each value):
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
npx @rui.branco/sentry-mcp setup
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Step 4: Verify
|
|
70
|
+
|
|
71
|
+
Restart Claude Code and run `/mcp` to verify the server is connected.
|
|
72
|
+
|
|
73
|
+
### Alternative: Manual Installation
|
|
74
|
+
|
|
75
|
+
If you prefer to install manually:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
git clone https://github.com/rui-branco/sentry-mcp.git ~/.config/sentry-mcp
|
|
79
|
+
cd ~/.config/sentry-mcp && npm install
|
|
80
|
+
node setup.js
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Then add to Claude Code:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
claude mcp add --transport stdio sentry -- node $HOME/.config/sentry-mcp/index.js
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Usage
|
|
90
|
+
|
|
91
|
+
### List Issues
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
> Show me unresolved Sentry issues
|
|
95
|
+
|
|
96
|
+
# Returns all unresolved issues with metadata
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Inspect an Error
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
> Get the latest event for Sentry issue 12345
|
|
103
|
+
|
|
104
|
+
# Returns full stack trace, breadcrumbs, HTTP request, tags, and contexts
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Resolve After Fixing
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
> Resolve Sentry issue 12345
|
|
111
|
+
|
|
112
|
+
# Marks the issue as resolved in Sentry
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Search Issues
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
> Search Sentry for "TypeError" errors in the last 24 hours
|
|
119
|
+
|
|
120
|
+
# Uses Sentry search: is:unresolved TypeError lastSeen:-24h
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Example Output
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
## Stack Trace
|
|
127
|
+
|
|
128
|
+
**TypeError:** Cannot read properties of undefined (reading 'map')
|
|
129
|
+
|
|
130
|
+
```
|
|
131
|
+
-> renderItems (src/components/List.tsx:42:15)
|
|
132
|
+
-> List (src/components/List.tsx:28:3)
|
|
133
|
+
React.createElement (react-dom.js:1234)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Breadcrumbs (last 5)
|
|
137
|
+
|
|
138
|
+
- **navigation** [info] /dashboard -> /items
|
|
139
|
+
- **http** [info] GET /api/items (200)
|
|
140
|
+
- **ui.click** [info] button.load-more
|
|
141
|
+
- **http** [error] GET /api/items?page=2 (500)
|
|
142
|
+
- **console** [error] Unhandled error in component
|
|
143
|
+
|
|
144
|
+
## HTTP Request
|
|
145
|
+
|
|
146
|
+
`GET /api/items?page=2`
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Multi-Instance Setup
|
|
150
|
+
|
|
151
|
+
For multiple Sentry organizations, add instances via setup:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
npx @rui.branco/sentry-mcp setup add "work" "TOKEN_1" "https://sentry.io" "work-org" "work-project"
|
|
155
|
+
npx @rui.branco/sentry-mcp setup add "personal" "TOKEN_2" "https://sentry.io" "personal-org" "side-project"
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Or via MCP tool at runtime:
|
|
159
|
+
|
|
160
|
+
```
|
|
161
|
+
> Add a new Sentry instance called "work" with org "my-company"
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Use the `instance` parameter on any tool to target a specific instance, or omit it to use the default.
|
|
165
|
+
|
|
166
|
+
## API Reference
|
|
167
|
+
|
|
168
|
+
### Tools
|
|
169
|
+
|
|
170
|
+
| Tool | Description | Parameters |
|
|
171
|
+
|------|-------------|------------|
|
|
172
|
+
| `sentry_list_projects` | List all Sentry projects accessible by the token | `instance` |
|
|
173
|
+
| `sentry_get_issues` | Get unresolved issues for the configured project | `query`, `instance` |
|
|
174
|
+
| `sentry_get_issue` | Get full details for a specific issue | `issueId` (required), `instance` |
|
|
175
|
+
| `sentry_get_latest_event` | Get latest event with stack trace, breadcrumbs, HTTP request | `issueId` (required), `instance` |
|
|
176
|
+
| `sentry_resolve_issue` | Mark an issue as resolved | `issueId` (required), `instance` |
|
|
177
|
+
| `sentry_unresolve_issue` | Reopen a resolved issue | `issueId` (required), `instance` |
|
|
178
|
+
| `sentry_ignore_issue` | Ignore/snooze an issue | `issueId` (required), `instance` |
|
|
179
|
+
| `sentry_search_issues` | Search with custom Sentry query | `query` (required), `instance` |
|
|
180
|
+
| `sentry_assign_issue` | Assign an issue to a user or team | `issueId` (required), `assignee` (required), `instance` |
|
|
181
|
+
| `sentry_list_instances` | List all configured instances | none |
|
|
182
|
+
| `sentry_add_instance` | Add or update an instance at runtime | `name`, `token`, `baseUrl`, `orgSlug` (all required), `projectSlug`, `projects`, `setDefault` |
|
|
183
|
+
| `sentry_remove_instance` | Remove an instance | `name` (required) |
|
|
184
|
+
|
|
185
|
+
### Configuration
|
|
186
|
+
|
|
187
|
+
Config stored at `~/.config/sentry-mcp/config.json`:
|
|
188
|
+
|
|
189
|
+
```json
|
|
190
|
+
{
|
|
191
|
+
"instances": [
|
|
192
|
+
{
|
|
193
|
+
"name": "work",
|
|
194
|
+
"token": "YOUR_AUTH_TOKEN",
|
|
195
|
+
"baseUrl": "https://sentry.io",
|
|
196
|
+
"orgSlug": "my-company",
|
|
197
|
+
"projectSlug": "frontend",
|
|
198
|
+
"projects": ["frontend", "backend"]
|
|
199
|
+
}
|
|
200
|
+
],
|
|
201
|
+
"defaultInstance": "work"
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## Security
|
|
206
|
+
|
|
207
|
+
- API tokens are stored locally in `~/.config/sentry-mcp/config.json`
|
|
208
|
+
- Config files are excluded from git via `.gitignore`
|
|
209
|
+
- Tokens are never logged or transmitted except to the Sentry API
|
|
210
|
+
|
|
211
|
+
## License
|
|
212
|
+
|
|
213
|
+
MIT
|
|
214
|
+
|
|
215
|
+
## Related
|
|
216
|
+
|
|
217
|
+
- [jira-mcp](https://github.com/rui-branco/jira-mcp) - Jira MCP server for Claude Code
|
|
218
|
+
- [figma-mcp](https://github.com/rui-branco/figma-mcp) - Figma MCP server for Claude Code
|
|
219
|
+
- [Model Context Protocol](https://modelcontextprotocol.io/) - MCP specification
|
package/index.js
CHANGED
|
@@ -156,7 +156,26 @@ async function fetchSentry(apiPath, instance, options = {}) {
|
|
|
156
156
|
|
|
157
157
|
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
158
158
|
|
|
159
|
-
|
|
159
|
+
// Strip noisy/internal fields that waste context but keep everything useful
|
|
160
|
+
function cleanIssueData(issue) {
|
|
161
|
+
const skip = new Set(["pluginActions", "pluginContexts", "pluginIssues", "activity", "seenBy", "stats"]);
|
|
162
|
+
const cleaned = {};
|
|
163
|
+
for (const [k, v] of Object.entries(issue)) {
|
|
164
|
+
if (!skip.has(k) && v != null && v !== "") cleaned[k] = v;
|
|
165
|
+
}
|
|
166
|
+
return cleaned;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function cleanEventData(event) {
|
|
170
|
+
const skip = new Set(["_meta", "groupID", "eventID", "sdk", "dist", "fingerprints"]);
|
|
171
|
+
const cleaned = {};
|
|
172
|
+
for (const [k, v] of Object.entries(event)) {
|
|
173
|
+
if (!skip.has(k) && v != null && v !== "") cleaned[k] = v;
|
|
174
|
+
}
|
|
175
|
+
return cleaned;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function formatIssueSummary(issue) {
|
|
160
179
|
const meta = issue.metadata || {};
|
|
161
180
|
const lines = [];
|
|
162
181
|
lines.push(`**${issue.title}**`);
|
|
@@ -175,98 +194,6 @@ function formatIssue(issue) {
|
|
|
175
194
|
return lines.join("\n");
|
|
176
195
|
}
|
|
177
196
|
|
|
178
|
-
function formatEvent(event) {
|
|
179
|
-
const lines = [];
|
|
180
|
-
|
|
181
|
-
// Stack traces
|
|
182
|
-
const exceptions =
|
|
183
|
-
event.entries?.find((e) => e.type === "exception")?.data?.values || [];
|
|
184
|
-
if (exceptions.length) {
|
|
185
|
-
lines.push("## Stack Trace\n");
|
|
186
|
-
for (const ex of exceptions) {
|
|
187
|
-
if (ex.type || ex.value) lines.push(`**${ex.type || "Exception"}:** ${ex.value || ""}\n`);
|
|
188
|
-
const frames = ex.stacktrace?.frames;
|
|
189
|
-
if (frames?.length) {
|
|
190
|
-
lines.push("```");
|
|
191
|
-
for (const f of frames) {
|
|
192
|
-
const loc = f.lineNo ? `:${f.lineNo}${f.colNo ? ":" + f.colNo : ""}` : "";
|
|
193
|
-
const marker = f.inApp ? "-> " : " ";
|
|
194
|
-
lines.push(`${marker}${f.function || "?"} (${f.filename || "?"}${loc})`);
|
|
195
|
-
}
|
|
196
|
-
lines.push("```\n");
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// Breadcrumbs
|
|
202
|
-
const breadcrumbs =
|
|
203
|
-
event.entries?.find((e) => e.type === "breadcrumbs")?.data?.values || [];
|
|
204
|
-
if (breadcrumbs.length) {
|
|
205
|
-
const last = breadcrumbs.slice(-20);
|
|
206
|
-
lines.push(`## Breadcrumbs (last ${last.length})\n`);
|
|
207
|
-
for (const bc of last) {
|
|
208
|
-
const msg = bc.message || bc.data?.url || bc.data?.to || "";
|
|
209
|
-
lines.push(`- **${bc.category || "?"}** [${bc.level || "info"}] ${msg}`);
|
|
210
|
-
}
|
|
211
|
-
lines.push("");
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// HTTP Request
|
|
215
|
-
const httpReq =
|
|
216
|
-
event.entries?.find((e) => e.type === "request")?.data;
|
|
217
|
-
if (httpReq) {
|
|
218
|
-
lines.push("## HTTP Request\n");
|
|
219
|
-
lines.push(`\`${httpReq.method || "GET"} ${httpReq.url || ""}\``);
|
|
220
|
-
if (httpReq.headers) {
|
|
221
|
-
lines.push("\n```");
|
|
222
|
-
for (const [k, v] of httpReq.headers) {
|
|
223
|
-
lines.push(`${k}: ${v}`);
|
|
224
|
-
}
|
|
225
|
-
lines.push("```");
|
|
226
|
-
}
|
|
227
|
-
lines.push("");
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// Tags
|
|
231
|
-
if (event.tags?.length) {
|
|
232
|
-
lines.push("## Tags\n");
|
|
233
|
-
lines.push("| Key | Value |");
|
|
234
|
-
lines.push("|-----|-------|");
|
|
235
|
-
for (const t of event.tags) {
|
|
236
|
-
lines.push(`| ${t.key} | ${t.value} |`);
|
|
237
|
-
}
|
|
238
|
-
lines.push("");
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// Contexts
|
|
242
|
-
if (event.contexts) {
|
|
243
|
-
lines.push("## Contexts\n");
|
|
244
|
-
for (const [name, ctx] of Object.entries(event.contexts)) {
|
|
245
|
-
if (name === "trace" || name === "replay" || !ctx || typeof ctx !== "object") continue;
|
|
246
|
-
const entries = Object.entries(ctx).filter(([k, v]) => k !== "type" && v != null && v !== "");
|
|
247
|
-
if (!entries.length) continue;
|
|
248
|
-
const title = name.charAt(0).toUpperCase() + name.slice(1).replace(/_/g, " ");
|
|
249
|
-
lines.push(`**${title}:**`);
|
|
250
|
-
for (const [k, v] of entries.slice(0, 15)) {
|
|
251
|
-
const display = typeof v === "object" ? JSON.stringify(v) : String(v);
|
|
252
|
-
lines.push(`- ${k}: ${display}`);
|
|
253
|
-
}
|
|
254
|
-
lines.push("");
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
// User
|
|
259
|
-
if (event.user) {
|
|
260
|
-
const u = event.user;
|
|
261
|
-
lines.push("## User\n");
|
|
262
|
-
lines.push(`${u.username || u.name || u.email || u.id || "unknown"}${u.email ? " (" + u.email + ")" : ""}`);
|
|
263
|
-
if (u.ip_address) lines.push(`IP: ${u.ip_address}`);
|
|
264
|
-
lines.push("");
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
return lines.join("\n") || "No event data available.";
|
|
268
|
-
}
|
|
269
|
-
|
|
270
197
|
// ── MCP Server ──────────────────────────────────────────────────────────────
|
|
271
198
|
|
|
272
199
|
const server = new Server(
|
|
@@ -573,7 +500,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
573
500
|
content: [{ type: "text", text: `No issues found for query: ${query}` }],
|
|
574
501
|
};
|
|
575
502
|
}
|
|
576
|
-
const formatted = data.map(
|
|
503
|
+
const formatted = data.map(formatIssueSummary);
|
|
577
504
|
return {
|
|
578
505
|
content: [
|
|
579
506
|
{
|
|
@@ -591,7 +518,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
591
518
|
inst,
|
|
592
519
|
);
|
|
593
520
|
return {
|
|
594
|
-
content: [{ type: "text", text:
|
|
521
|
+
content: [{ type: "text", text: JSON.stringify(cleanIssueData(data), null, 2) }],
|
|
595
522
|
};
|
|
596
523
|
|
|
597
524
|
} else if (name === "sentry_get_latest_event") {
|
|
@@ -602,7 +529,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
602
529
|
inst,
|
|
603
530
|
);
|
|
604
531
|
return {
|
|
605
|
-
content: [{ type: "text", text:
|
|
532
|
+
content: [{ type: "text", text: JSON.stringify(cleanEventData(data), null, 2) }],
|
|
606
533
|
};
|
|
607
534
|
|
|
608
535
|
} else if (name === "sentry_resolve_issue") {
|
|
@@ -659,7 +586,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
659
586
|
content: [{ type: "text", text: `No issues found for query: ${args.query}` }],
|
|
660
587
|
};
|
|
661
588
|
}
|
|
662
|
-
const formatted = data.map(
|
|
589
|
+
const formatted = data.map(formatIssueSummary);
|
|
663
590
|
return {
|
|
664
591
|
content: [
|
|
665
592
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rui.branco/sentry-mcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Sentry MCP server for Claude Code - fetch issues, search errors, resolve/ignore issues, get stack traces and breadcrumbs",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"index.js",
|
|
11
|
-
"setup.js"
|
|
11
|
+
"setup.js",
|
|
12
|
+
"README.md"
|
|
12
13
|
],
|
|
13
14
|
"scripts": {
|
|
14
15
|
"setup": "node setup.js"
|