@rui.branco/sentry-mcp 1.0.2 → 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.
Files changed (2) hide show
  1. package/index.js +404 -1
  2. 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
- return;
6
+ process.exit(0);
7
7
  }
8
8
 
9
9
  const { Server } = require("@modelcontextprotocol/sdk/server/index.js");
@@ -383,6 +383,232 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
383
383
  required: ["issueId", "assignee"],
384
384
  },
385
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
+ },
386
612
  {
387
613
  name: "sentry_list_instances",
388
614
  description:
@@ -610,6 +836,183 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
610
836
  ],
611
837
  };
612
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
+
613
1016
  } else if (name === "sentry_list_instances") {
614
1017
  if (!instances.length) {
615
1018
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rui.branco/sentry-mcp",
3
- "version": "1.0.2",
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
  }