@rui.branco/sentry-mcp 1.0.1 → 1.0.3
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/index.js +428 -98
- package/package.json +6 -2
package/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Handle setup command
|
|
4
4
|
if (process.argv[2] === "setup") {
|
|
5
5
|
require("./setup.js");
|
|
6
|
-
|
|
6
|
+
process.exit(0);
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
const { Server } = require("@modelcontextprotocol/sdk/server/index.js");
|
|
@@ -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(
|
|
@@ -456,6 +383,232 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
456
383
|
required: ["issueId", "assignee"],
|
|
457
384
|
},
|
|
458
385
|
},
|
|
386
|
+
{
|
|
387
|
+
name: "sentry_list_releases",
|
|
388
|
+
description:
|
|
389
|
+
"List releases for a Sentry project. Returns release versions, dates, and artifact counts.",
|
|
390
|
+
inputSchema: {
|
|
391
|
+
type: "object",
|
|
392
|
+
properties: {
|
|
393
|
+
query: {
|
|
394
|
+
type: "string",
|
|
395
|
+
description:
|
|
396
|
+
"Optional search query to filter releases by version (e.g. '26.05').",
|
|
397
|
+
},
|
|
398
|
+
instance: {
|
|
399
|
+
type: "string",
|
|
400
|
+
description:
|
|
401
|
+
"Instance name (for multi-instance setups). Uses default instance if omitted.",
|
|
402
|
+
},
|
|
403
|
+
},
|
|
404
|
+
required: [],
|
|
405
|
+
},
|
|
406
|
+
},
|
|
407
|
+
{
|
|
408
|
+
name: "sentry_get_release_artifacts",
|
|
409
|
+
description:
|
|
410
|
+
"List artifacts (source maps, JS bundles) uploaded for a specific release. Use to verify source maps were uploaded correctly.",
|
|
411
|
+
inputSchema: {
|
|
412
|
+
type: "object",
|
|
413
|
+
properties: {
|
|
414
|
+
version: {
|
|
415
|
+
type: "string",
|
|
416
|
+
description: "Release version string (e.g. '26.05.4b185-TRAIN').",
|
|
417
|
+
},
|
|
418
|
+
instance: {
|
|
419
|
+
type: "string",
|
|
420
|
+
description:
|
|
421
|
+
"Instance name (for multi-instance setups). Uses default instance if omitted.",
|
|
422
|
+
},
|
|
423
|
+
},
|
|
424
|
+
required: ["version"],
|
|
425
|
+
},
|
|
426
|
+
},
|
|
427
|
+
{
|
|
428
|
+
name: "sentry_create_release",
|
|
429
|
+
description:
|
|
430
|
+
"Create a new release in Sentry. Optionally associate commits and set a ref.",
|
|
431
|
+
inputSchema: {
|
|
432
|
+
type: "object",
|
|
433
|
+
properties: {
|
|
434
|
+
version: {
|
|
435
|
+
type: "string",
|
|
436
|
+
description: "Release version string (e.g. '1.0.0', '26.05.4b185-TRAIN').",
|
|
437
|
+
},
|
|
438
|
+
ref: {
|
|
439
|
+
type: "string",
|
|
440
|
+
description: "Optional git ref (commit SHA or tag).",
|
|
441
|
+
},
|
|
442
|
+
projects: {
|
|
443
|
+
type: "array",
|
|
444
|
+
items: { type: "string" },
|
|
445
|
+
description: "Project slugs to associate with this release. Defaults to the instance's projectSlug.",
|
|
446
|
+
},
|
|
447
|
+
instance: {
|
|
448
|
+
type: "string",
|
|
449
|
+
description: "Instance name. Uses default if omitted.",
|
|
450
|
+
},
|
|
451
|
+
},
|
|
452
|
+
required: ["version"],
|
|
453
|
+
},
|
|
454
|
+
},
|
|
455
|
+
{
|
|
456
|
+
name: "sentry_finalize_release",
|
|
457
|
+
description:
|
|
458
|
+
"Finalize a release (mark it as deployed/complete). Sets dateReleased to now.",
|
|
459
|
+
inputSchema: {
|
|
460
|
+
type: "object",
|
|
461
|
+
properties: {
|
|
462
|
+
version: {
|
|
463
|
+
type: "string",
|
|
464
|
+
description: "Release version string.",
|
|
465
|
+
},
|
|
466
|
+
instance: {
|
|
467
|
+
type: "string",
|
|
468
|
+
description: "Instance name. Uses default if omitted.",
|
|
469
|
+
},
|
|
470
|
+
},
|
|
471
|
+
required: ["version"],
|
|
472
|
+
},
|
|
473
|
+
},
|
|
474
|
+
{
|
|
475
|
+
name: "sentry_delete_release",
|
|
476
|
+
description:
|
|
477
|
+
"Delete a release and all its artifacts from Sentry.",
|
|
478
|
+
inputSchema: {
|
|
479
|
+
type: "object",
|
|
480
|
+
properties: {
|
|
481
|
+
version: {
|
|
482
|
+
type: "string",
|
|
483
|
+
description: "Release version string to delete.",
|
|
484
|
+
},
|
|
485
|
+
instance: {
|
|
486
|
+
type: "string",
|
|
487
|
+
description: "Instance name. Uses default if omitted.",
|
|
488
|
+
},
|
|
489
|
+
},
|
|
490
|
+
required: ["version"],
|
|
491
|
+
},
|
|
492
|
+
},
|
|
493
|
+
{
|
|
494
|
+
name: "sentry_create_deploy",
|
|
495
|
+
description:
|
|
496
|
+
"Create a deploy for a release. Records when and where a release was deployed.",
|
|
497
|
+
inputSchema: {
|
|
498
|
+
type: "object",
|
|
499
|
+
properties: {
|
|
500
|
+
version: {
|
|
501
|
+
type: "string",
|
|
502
|
+
description: "Release version string.",
|
|
503
|
+
},
|
|
504
|
+
environment: {
|
|
505
|
+
type: "string",
|
|
506
|
+
description: "Environment name (e.g. 'production', 'staging', 'QA', 'TRAIN').",
|
|
507
|
+
},
|
|
508
|
+
name: {
|
|
509
|
+
type: "string",
|
|
510
|
+
description: "Optional deploy name.",
|
|
511
|
+
},
|
|
512
|
+
instance: {
|
|
513
|
+
type: "string",
|
|
514
|
+
description: "Instance name. Uses default if omitted.",
|
|
515
|
+
},
|
|
516
|
+
},
|
|
517
|
+
required: ["version", "environment"],
|
|
518
|
+
},
|
|
519
|
+
},
|
|
520
|
+
{
|
|
521
|
+
name: "sentry_list_deploys",
|
|
522
|
+
description:
|
|
523
|
+
"List deploys for a release.",
|
|
524
|
+
inputSchema: {
|
|
525
|
+
type: "object",
|
|
526
|
+
properties: {
|
|
527
|
+
version: {
|
|
528
|
+
type: "string",
|
|
529
|
+
description: "Release version string.",
|
|
530
|
+
},
|
|
531
|
+
instance: {
|
|
532
|
+
type: "string",
|
|
533
|
+
description: "Instance name. Uses default if omitted.",
|
|
534
|
+
},
|
|
535
|
+
},
|
|
536
|
+
required: ["version"],
|
|
537
|
+
},
|
|
538
|
+
},
|
|
539
|
+
{
|
|
540
|
+
name: "sentry_delete_artifact",
|
|
541
|
+
description:
|
|
542
|
+
"Delete a specific artifact from a release by its ID.",
|
|
543
|
+
inputSchema: {
|
|
544
|
+
type: "object",
|
|
545
|
+
properties: {
|
|
546
|
+
version: {
|
|
547
|
+
type: "string",
|
|
548
|
+
description: "Release version string.",
|
|
549
|
+
},
|
|
550
|
+
artifactId: {
|
|
551
|
+
type: "string",
|
|
552
|
+
description: "Artifact ID to delete (from sentry_get_release_artifacts).",
|
|
553
|
+
},
|
|
554
|
+
instance: {
|
|
555
|
+
type: "string",
|
|
556
|
+
description: "Instance name. Uses default if omitted.",
|
|
557
|
+
},
|
|
558
|
+
},
|
|
559
|
+
required: ["version", "artifactId"],
|
|
560
|
+
},
|
|
561
|
+
},
|
|
562
|
+
{
|
|
563
|
+
name: "sentry_set_commits",
|
|
564
|
+
description:
|
|
565
|
+
"Associate commits with a release. Provide a repository and commit range.",
|
|
566
|
+
inputSchema: {
|
|
567
|
+
type: "object",
|
|
568
|
+
properties: {
|
|
569
|
+
version: {
|
|
570
|
+
type: "string",
|
|
571
|
+
description: "Release version string.",
|
|
572
|
+
},
|
|
573
|
+
repository: {
|
|
574
|
+
type: "string",
|
|
575
|
+
description: "Repository name (e.g. 'org/repo').",
|
|
576
|
+
},
|
|
577
|
+
commit: {
|
|
578
|
+
type: "string",
|
|
579
|
+
description: "Current commit SHA (HEAD of the release).",
|
|
580
|
+
},
|
|
581
|
+
previousCommit: {
|
|
582
|
+
type: "string",
|
|
583
|
+
description: "Previous commit SHA (start of range). If omitted, uses the previous release's commit.",
|
|
584
|
+
},
|
|
585
|
+
instance: {
|
|
586
|
+
type: "string",
|
|
587
|
+
description: "Instance name. Uses default if omitted.",
|
|
588
|
+
},
|
|
589
|
+
},
|
|
590
|
+
required: ["version", "repository", "commit"],
|
|
591
|
+
},
|
|
592
|
+
},
|
|
593
|
+
{
|
|
594
|
+
name: "sentry_get_release",
|
|
595
|
+
description:
|
|
596
|
+
"Get full details for a specific release including projects, commits, authors, and deploy info.",
|
|
597
|
+
inputSchema: {
|
|
598
|
+
type: "object",
|
|
599
|
+
properties: {
|
|
600
|
+
version: {
|
|
601
|
+
type: "string",
|
|
602
|
+
description: "Release version string.",
|
|
603
|
+
},
|
|
604
|
+
instance: {
|
|
605
|
+
type: "string",
|
|
606
|
+
description: "Instance name. Uses default if omitted.",
|
|
607
|
+
},
|
|
608
|
+
},
|
|
609
|
+
required: ["version"],
|
|
610
|
+
},
|
|
611
|
+
},
|
|
459
612
|
{
|
|
460
613
|
name: "sentry_list_instances",
|
|
461
614
|
description:
|
|
@@ -573,7 +726,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
573
726
|
content: [{ type: "text", text: `No issues found for query: ${query}` }],
|
|
574
727
|
};
|
|
575
728
|
}
|
|
576
|
-
const formatted = data.map(
|
|
729
|
+
const formatted = data.map(formatIssueSummary);
|
|
577
730
|
return {
|
|
578
731
|
content: [
|
|
579
732
|
{
|
|
@@ -591,7 +744,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
591
744
|
inst,
|
|
592
745
|
);
|
|
593
746
|
return {
|
|
594
|
-
content: [{ type: "text", text:
|
|
747
|
+
content: [{ type: "text", text: JSON.stringify(cleanIssueData(data), null, 2) }],
|
|
595
748
|
};
|
|
596
749
|
|
|
597
750
|
} else if (name === "sentry_get_latest_event") {
|
|
@@ -602,7 +755,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
602
755
|
inst,
|
|
603
756
|
);
|
|
604
757
|
return {
|
|
605
|
-
content: [{ type: "text", text:
|
|
758
|
+
content: [{ type: "text", text: JSON.stringify(cleanEventData(data), null, 2) }],
|
|
606
759
|
};
|
|
607
760
|
|
|
608
761
|
} else if (name === "sentry_resolve_issue") {
|
|
@@ -659,7 +812,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
659
812
|
content: [{ type: "text", text: `No issues found for query: ${args.query}` }],
|
|
660
813
|
};
|
|
661
814
|
}
|
|
662
|
-
const formatted = data.map(
|
|
815
|
+
const formatted = data.map(formatIssueSummary);
|
|
663
816
|
return {
|
|
664
817
|
content: [
|
|
665
818
|
{
|
|
@@ -683,6 +836,183 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
683
836
|
],
|
|
684
837
|
};
|
|
685
838
|
|
|
839
|
+
} else if (name === "sentry_list_releases") {
|
|
840
|
+
const inst = getInstanceByName(args.instance);
|
|
841
|
+
if (!inst) throw new Error("No Sentry instance configured. Run: sentry-mcp setup");
|
|
842
|
+
if (!inst.projectSlug) throw new Error("No projectSlug configured for this instance.");
|
|
843
|
+
const params = new URLSearchParams();
|
|
844
|
+
if (args.query) params.set("query", args.query);
|
|
845
|
+
const data = await fetchSentry(
|
|
846
|
+
`/api/0/organizations/${encodeURIComponent(inst.orgSlug)}/releases/?project=${encodeURIComponent(inst.projectSlug)}&${params}`,
|
|
847
|
+
inst,
|
|
848
|
+
);
|
|
849
|
+
if (!data || !data.length) {
|
|
850
|
+
return {
|
|
851
|
+
content: [{ type: "text", text: `No releases found.${args.query ? ` Query: ${args.query}` : ""}` }],
|
|
852
|
+
};
|
|
853
|
+
}
|
|
854
|
+
const lines = data.slice(0, 25).map((r) => {
|
|
855
|
+
const artifacts = r.newGroups != null ? ` | New issues: ${r.newGroups}` : "";
|
|
856
|
+
return `- **${r.version}** | Created: ${r.dateCreated || "N/A"}${artifacts}${r.lastEvent ? ` | Last event: ${r.lastEvent}` : ""}`;
|
|
857
|
+
});
|
|
858
|
+
return {
|
|
859
|
+
content: [
|
|
860
|
+
{
|
|
861
|
+
type: "text",
|
|
862
|
+
text: `Found ${data.length} release(s):\n\n${lines.join("\n")}`,
|
|
863
|
+
},
|
|
864
|
+
],
|
|
865
|
+
};
|
|
866
|
+
|
|
867
|
+
} else if (name === "sentry_get_release_artifacts") {
|
|
868
|
+
const inst = getInstanceByName(args.instance);
|
|
869
|
+
if (!inst) throw new Error("No Sentry instance configured. Run: sentry-mcp setup");
|
|
870
|
+
const data = await fetchSentry(
|
|
871
|
+
`/api/0/organizations/${encodeURIComponent(inst.orgSlug)}/releases/${encodeURIComponent(args.version)}/files/`,
|
|
872
|
+
inst,
|
|
873
|
+
);
|
|
874
|
+
if (!data || !data.length) {
|
|
875
|
+
return {
|
|
876
|
+
content: [{ type: "text", text: `No artifacts found for release "${args.version}". Source maps may not have been uploaded.` }],
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
const lines = data.map((a) => {
|
|
880
|
+
const size = a.size ? ` (${(a.size / 1024).toFixed(1)} KB)` : "";
|
|
881
|
+
return `- ${a.name}${size} [id: ${a.id}]`;
|
|
882
|
+
});
|
|
883
|
+
return {
|
|
884
|
+
content: [
|
|
885
|
+
{
|
|
886
|
+
type: "text",
|
|
887
|
+
text: `Found ${data.length} artifact(s) for release "${args.version}":\n\n${lines.join("\n")}`,
|
|
888
|
+
},
|
|
889
|
+
],
|
|
890
|
+
};
|
|
891
|
+
|
|
892
|
+
} else if (name === "sentry_create_release") {
|
|
893
|
+
const inst = getInstanceByName(args.instance);
|
|
894
|
+
if (!inst) throw new Error("No Sentry instance configured. Run: sentry-mcp setup");
|
|
895
|
+
const projects = args.projects || (inst.projectSlug ? [inst.projectSlug] : []);
|
|
896
|
+
if (!projects.length) throw new Error("No projects specified and no projectSlug configured.");
|
|
897
|
+
const body = { version: args.version, projects };
|
|
898
|
+
if (args.ref) body.ref = args.ref;
|
|
899
|
+
const data = await fetchSentry(
|
|
900
|
+
`/api/0/organizations/${encodeURIComponent(inst.orgSlug)}/releases/`,
|
|
901
|
+
inst,
|
|
902
|
+
{ method: "POST", body },
|
|
903
|
+
);
|
|
904
|
+
return {
|
|
905
|
+
content: [{ type: "text", text: `Release "${data.version}" created for projects: ${projects.join(", ")}` }],
|
|
906
|
+
};
|
|
907
|
+
|
|
908
|
+
} else if (name === "sentry_finalize_release") {
|
|
909
|
+
const inst = getInstanceByName(args.instance);
|
|
910
|
+
if (!inst) throw new Error("No Sentry instance configured. Run: sentry-mcp setup");
|
|
911
|
+
await fetchSentry(
|
|
912
|
+
`/api/0/organizations/${encodeURIComponent(inst.orgSlug)}/releases/${encodeURIComponent(args.version)}/`,
|
|
913
|
+
inst,
|
|
914
|
+
{ method: "PUT", body: { dateReleased: new Date().toISOString() } },
|
|
915
|
+
);
|
|
916
|
+
return {
|
|
917
|
+
content: [{ type: "text", text: `Release "${args.version}" finalized.` }],
|
|
918
|
+
};
|
|
919
|
+
|
|
920
|
+
} else if (name === "sentry_delete_release") {
|
|
921
|
+
const inst = getInstanceByName(args.instance);
|
|
922
|
+
if (!inst) throw new Error("No Sentry instance configured. Run: sentry-mcp setup");
|
|
923
|
+
await fetchSentry(
|
|
924
|
+
`/api/0/organizations/${encodeURIComponent(inst.orgSlug)}/releases/${encodeURIComponent(args.version)}/`,
|
|
925
|
+
inst,
|
|
926
|
+
{ method: "DELETE" },
|
|
927
|
+
);
|
|
928
|
+
return {
|
|
929
|
+
content: [{ type: "text", text: `Release "${args.version}" deleted.` }],
|
|
930
|
+
};
|
|
931
|
+
|
|
932
|
+
} else if (name === "sentry_create_deploy") {
|
|
933
|
+
const inst = getInstanceByName(args.instance);
|
|
934
|
+
if (!inst) throw new Error("No Sentry instance configured. Run: sentry-mcp setup");
|
|
935
|
+
const body = { environment: args.environment };
|
|
936
|
+
if (args.name) body.name = args.name;
|
|
937
|
+
const data = await fetchSentry(
|
|
938
|
+
`/api/0/organizations/${encodeURIComponent(inst.orgSlug)}/releases/${encodeURIComponent(args.version)}/deploys/`,
|
|
939
|
+
inst,
|
|
940
|
+
{ method: "POST", body },
|
|
941
|
+
);
|
|
942
|
+
return {
|
|
943
|
+
content: [{ type: "text", text: `Deploy created for release "${args.version}" to environment "${args.environment}".` }],
|
|
944
|
+
};
|
|
945
|
+
|
|
946
|
+
} else if (name === "sentry_list_deploys") {
|
|
947
|
+
const inst = getInstanceByName(args.instance);
|
|
948
|
+
if (!inst) throw new Error("No Sentry instance configured. Run: sentry-mcp setup");
|
|
949
|
+
const data = await fetchSentry(
|
|
950
|
+
`/api/0/organizations/${encodeURIComponent(inst.orgSlug)}/releases/${encodeURIComponent(args.version)}/deploys/`,
|
|
951
|
+
inst,
|
|
952
|
+
);
|
|
953
|
+
if (!data || !data.length) {
|
|
954
|
+
return {
|
|
955
|
+
content: [{ type: "text", text: `No deploys found for release "${args.version}".` }],
|
|
956
|
+
};
|
|
957
|
+
}
|
|
958
|
+
const lines = data.map((d) => `- **${d.environment}** | ${d.dateFinished || d.dateStarted || "N/A"}${d.name ? ` | ${d.name}` : ""}`);
|
|
959
|
+
return {
|
|
960
|
+
content: [{ type: "text", text: `${data.length} deploy(s) for "${args.version}":\n\n${lines.join("\n")}` }],
|
|
961
|
+
};
|
|
962
|
+
|
|
963
|
+
} else if (name === "sentry_delete_artifact") {
|
|
964
|
+
const inst = getInstanceByName(args.instance);
|
|
965
|
+
if (!inst) throw new Error("No Sentry instance configured. Run: sentry-mcp setup");
|
|
966
|
+
await fetchSentry(
|
|
967
|
+
`/api/0/organizations/${encodeURIComponent(inst.orgSlug)}/releases/${encodeURIComponent(args.version)}/files/${encodeURIComponent(args.artifactId)}/`,
|
|
968
|
+
inst,
|
|
969
|
+
{ method: "DELETE" },
|
|
970
|
+
);
|
|
971
|
+
return {
|
|
972
|
+
content: [{ type: "text", text: `Artifact ${args.artifactId} deleted from release "${args.version}".` }],
|
|
973
|
+
};
|
|
974
|
+
|
|
975
|
+
} else if (name === "sentry_set_commits") {
|
|
976
|
+
const inst = getInstanceByName(args.instance);
|
|
977
|
+
if (!inst) throw new Error("No Sentry instance configured. Run: sentry-mcp setup");
|
|
978
|
+
const commitSpec = { repository: args.repository, currentId: args.commit };
|
|
979
|
+
if (args.previousCommit) commitSpec.previousId = args.previousCommit;
|
|
980
|
+
await fetchSentry(
|
|
981
|
+
`/api/0/organizations/${encodeURIComponent(inst.orgSlug)}/releases/${encodeURIComponent(args.version)}/commits/`,
|
|
982
|
+
inst,
|
|
983
|
+
{ method: "PUT", body: { commits: [commitSpec] } },
|
|
984
|
+
);
|
|
985
|
+
return {
|
|
986
|
+
content: [{ type: "text", text: `Commits associated with release "${args.version}" (repo: ${args.repository}, HEAD: ${args.commit}).` }],
|
|
987
|
+
};
|
|
988
|
+
|
|
989
|
+
} else if (name === "sentry_get_release") {
|
|
990
|
+
const inst = getInstanceByName(args.instance);
|
|
991
|
+
if (!inst) throw new Error("No Sentry instance configured. Run: sentry-mcp setup");
|
|
992
|
+
const data = await fetchSentry(
|
|
993
|
+
`/api/0/organizations/${encodeURIComponent(inst.orgSlug)}/releases/${encodeURIComponent(args.version)}/`,
|
|
994
|
+
inst,
|
|
995
|
+
);
|
|
996
|
+
const lines = [];
|
|
997
|
+
lines.push(`**${data.version}**`);
|
|
998
|
+
lines.push(`- Status: ${data.status || "open"}`);
|
|
999
|
+
lines.push(`- Created: ${data.dateCreated || "N/A"}`);
|
|
1000
|
+
if (data.dateReleased) lines.push(`- Released: ${data.dateReleased}`);
|
|
1001
|
+
if (data.firstEvent) lines.push(`- First event: ${data.firstEvent}`);
|
|
1002
|
+
if (data.lastEvent) lines.push(`- Last event: ${data.lastEvent}`);
|
|
1003
|
+
lines.push(`- New issues: ${data.newGroups || 0}`);
|
|
1004
|
+
lines.push(`- Commits: ${data.commitCount || 0}`);
|
|
1005
|
+
lines.push(`- Deploys: ${data.deployCount || 0}`);
|
|
1006
|
+
if (data.projects?.length) {
|
|
1007
|
+
lines.push(`- Projects: ${data.projects.map((p) => p.slug || p.name).join(", ")}`);
|
|
1008
|
+
}
|
|
1009
|
+
if (data.authors?.length) {
|
|
1010
|
+
lines.push(`- Authors: ${data.authors.map((a) => a.name || a.email).join(", ")}`);
|
|
1011
|
+
}
|
|
1012
|
+
return {
|
|
1013
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
1014
|
+
};
|
|
1015
|
+
|
|
686
1016
|
} else if (name === "sentry_list_instances") {
|
|
687
1017
|
if (!instances.length) {
|
|
688
1018
|
return {
|
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.3",
|
|
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": {
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
"README.md"
|
|
13
13
|
],
|
|
14
14
|
"scripts": {
|
|
15
|
-
"setup": "node setup.js"
|
|
15
|
+
"setup": "node setup.js",
|
|
16
|
+
"test": "vitest run"
|
|
16
17
|
},
|
|
17
18
|
"keywords": [
|
|
18
19
|
"mcp",
|
|
@@ -27,5 +28,8 @@
|
|
|
27
28
|
"dependencies": {
|
|
28
29
|
"@modelcontextprotocol/sdk": "^1.25.3",
|
|
29
30
|
"node-fetch": "^2.7.0"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"vitest": "^4.1.6"
|
|
30
34
|
}
|
|
31
35
|
}
|