Skip to main content
Glama
get-issue-details.test.ts41.1 kB
import { describe, it, expect } from "vitest"; import { http, HttpResponse } from "msw"; import { mswServer, createDefaultEvent, createGenericEvent, createUnknownEvent, createPerformanceEvent, createPerformanceIssue, createRegressedIssue, createUnsupportedIssue, createCspIssue, createCspEvent, } from "@sentry/mcp-server-mocks"; import getIssueDetails from "./get-issue-details.js"; const baseContext = { constraints: { organizationSlug: undefined, }, accessToken: "access-token", userId: "1", }; // Removed - now using createPerformanceIssue() factory from mocks // Removed - now using createPerformanceEvent() factory from mocks with overrides function createTraceResponseFixture() { return [ { span_id: "root-span", event_id: "root-span", transaction_id: "root-span", project_id: "4509062593708032", project_slug: "cloudflare-mcp", profile_id: "", profiler_id: "", parent_span_id: null, start_timestamp: 0, end_timestamp: 1, measurements: {}, duration: 1000, transaction: "/api/users", is_transaction: true, description: "GET /api/users", sdk_name: "sentry.python", op: "http.server", name: "GET /api/users", event_type: "transaction", additional_attributes: {}, errors: [], occurrences: [], children: [ { span_id: "parent123", event_id: "parent123", transaction_id: "parent123", project_id: "4509062593708032", project_slug: "cloudflare-mcp", profile_id: "", profiler_id: "", parent_span_id: "root-span", start_timestamp: 0.1, end_timestamp: 0.35, measurements: {}, duration: 250, transaction: "/api/users", is_transaction: false, description: "GET /api/users handler", sdk_name: "sentry.python", op: "http.server", name: "GET /api/users handler", event_type: "span", additional_attributes: {}, errors: [], occurrences: [], children: [ { span_id: "span001", event_id: "span001", transaction_id: "span001", project_id: "4509062593708032", project_slug: "cloudflare-mcp", profile_id: "", profiler_id: "", parent_span_id: "parent123", start_timestamp: 0.15, end_timestamp: 0.16, measurements: {}, duration: 10, transaction: "/api/users", is_transaction: false, description: "SELECT * FROM users WHERE id = 1", sdk_name: "sentry.python", op: "db.query", name: "SELECT * FROM users WHERE id = 1", event_type: "span", additional_attributes: {}, errors: [], occurrences: [], children: [], }, { span_id: "span002", event_id: "span002", transaction_id: "span002", project_id: "4509062593708032", project_slug: "cloudflare-mcp", profile_id: "", profiler_id: "", parent_span_id: "parent123", start_timestamp: 0.2, end_timestamp: 0.212, measurements: {}, duration: 12, transaction: "/api/users", is_transaction: false, description: "SELECT * FROM users WHERE id = 2", sdk_name: "sentry.python", op: "db.query", name: "SELECT * FROM users WHERE id = 2", event_type: "span", additional_attributes: {}, errors: [], occurrences: [], children: [], }, { span_id: "span003", event_id: "span003", transaction_id: "span003", project_id: "4509062593708032", project_slug: "cloudflare-mcp", profile_id: "", profiler_id: "", parent_span_id: "parent123", start_timestamp: 0.24, end_timestamp: 0.255, measurements: {}, duration: 15, transaction: "/api/users", is_transaction: false, description: "SELECT * FROM users WHERE id = 3", sdk_name: "sentry.python", op: "db.query", name: "SELECT * FROM users WHERE id = 3", event_type: "span", additional_attributes: {}, errors: [], occurrences: [], children: [], }, ], }, ], }, ]; } describe("get_issue_details", () => { it("serializes with issueId", async () => { const result = await getIssueDetails.handler( { organizationSlug: "sentry-mcp-evals", issueId: "CLOUDFLARE-MCP-41", eventId: undefined, issueUrl: undefined, regionUrl: null, }, { constraints: { organizationSlug: undefined, }, accessToken: "access-token", userId: "1", }, ); expect(result).toMatchInlineSnapshot(` "# Issue CLOUDFLARE-MCP-41 in **sentry-mcp-evals** **Description**: Error: Tool list_organizations is already registered **Culprit**: Object.fetch(index) **First Seen**: 2025-04-03T22:51:19.403Z **Last Seen**: 2025-04-12T11:34:11.000Z **Occurrences**: 25 **Users Impacted**: 1 **Status**: unresolved **Substatus**: ongoing **Issue Type**: error **Issue Category**: error **Platform**: javascript **Project**: CLOUDFLARE-MCP **URL**: https://sentry-mcp-evals.sentry.io/issues/CLOUDFLARE-MCP-41 ## Event Details **Event ID**: 7ca573c0f4814912aaa9bdc77d1a7d51 **Type**: error **Occurred At**: 2025-04-08T21:15:04.000Z ### Error \`\`\` Error: Tool list_organizations is already registered \`\`\` **Stacktrace:** \`\`\` index.js:7809:27 index.js:8029:24 (OAuthProviderImpl.fetch) index.js:19631:28 (Object.fetch) \`\`\` ### HTTP Request **Method:** GET **URL:** https://mcp.sentry.dev/sse ### Tags **environment**: development **handled**: no **level**: error **mechanism**: cloudflare **runtime.name**: cloudflare **url**: https://mcp.sentry.dev/sse ### Additional Context These are additional context provided by the user when they're instrumenting their application. **cloud_resource** cloud.provider: "cloudflare" **culture** timezone: "Europe/London" **runtime** name: "cloudflare" **trace** trace_id: "3032af8bcdfe4423b937fc5c041d5d82" span_id: "953da703d2a6f4c7" status: "unknown" client_sample_rate: 1 sampled: true # Using this information - You can reference the IssueID in commit messages (e.g. \`Fixes CLOUDFLARE-MCP-41\`) to automatically close the issue when the commit is merged. - The stacktrace includes both first-party application code as well as third-party code, its important to triage to first-party code. " `); }); it("serializes with issueUrl", async () => { const result = await getIssueDetails.handler( { organizationSlug: undefined, issueId: undefined, eventId: undefined, issueUrl: "https://sentry-mcp-evals.sentry.io/issues/6507376925", regionUrl: null, }, { constraints: { organizationSlug: undefined, }, accessToken: "access-token", userId: "1", }, ); expect(result).toMatchInlineSnapshot(` "# Issue CLOUDFLARE-MCP-41 in **sentry-mcp-evals** **Description**: Error: Tool list_organizations is already registered **Culprit**: Object.fetch(index) **First Seen**: 2025-04-03T22:51:19.403Z **Last Seen**: 2025-04-12T11:34:11.000Z **Occurrences**: 25 **Users Impacted**: 1 **Status**: unresolved **Substatus**: ongoing **Issue Type**: error **Issue Category**: error **Platform**: javascript **Project**: CLOUDFLARE-MCP **URL**: https://sentry-mcp-evals.sentry.io/issues/CLOUDFLARE-MCP-41 ## Event Details **Event ID**: 7ca573c0f4814912aaa9bdc77d1a7d51 **Type**: error **Occurred At**: 2025-04-08T21:15:04.000Z ### Error \`\`\` Error: Tool list_organizations is already registered \`\`\` **Stacktrace:** \`\`\` index.js:7809:27 index.js:8029:24 (OAuthProviderImpl.fetch) index.js:19631:28 (Object.fetch) \`\`\` ### HTTP Request **Method:** GET **URL:** https://mcp.sentry.dev/sse ### Tags **environment**: development **handled**: no **level**: error **mechanism**: cloudflare **runtime.name**: cloudflare **url**: https://mcp.sentry.dev/sse ### Additional Context These are additional context provided by the user when they're instrumenting their application. **cloud_resource** cloud.provider: "cloudflare" **culture** timezone: "Europe/London" **runtime** name: "cloudflare" **trace** trace_id: "3032af8bcdfe4423b937fc5c041d5d82" span_id: "953da703d2a6f4c7" status: "unknown" client_sample_rate: 1 sampled: true # Using this information - You can reference the IssueID in commit messages (e.g. \`Fixes CLOUDFLARE-MCP-41\`) to automatically close the issue when the commit is merged. - The stacktrace includes both first-party application code as well as third-party code, its important to triage to first-party code. " `); }); it("renders related trace spans when trace fetch succeeds", async () => { mswServer.use( http.get( "https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/PERF-N1-001/", () => HttpResponse.json(createPerformanceIssue()), { once: true }, ), http.get( "https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/PERF-N1-001/events/latest/", () => { // Create event with specific evidence data for this test const event = createPerformanceEvent(); const offenderSpanIds = event.occurrence.evidenceData.offenderSpanIds.slice(0, 3); event.occurrence.evidenceData.offenderSpanIds = offenderSpanIds; event.occurrence.evidenceData.numberRepeatingSpans = String( offenderSpanIds.length, ); event.occurrence.evidenceData.repeatingSpansCompact = undefined; event.occurrence.evidenceData.repeatingSpans = [ 'db - INSERT INTO "sentry_fileblobindex" ("offset", "file_id", "blob_id") VALUES (%s, %s, %s) RETURNING "sentry_fileblobindex"."id"', "function - sentry.models.files.abstractfileblob.AbstractFileBlob.from_file", 'db - SELECT "sentry_fileblob"."id", "sentry_fileblob"."path", "sentry_fileblob"."size", "sentry_fileblob"."checksum", "sentry_fileblob"."timestamp" FROM "sentry_fileblob" WHERE "sentry_fileblob"."checksum" = %s LIMIT 21', ]; const spansEntry = event.entries.find( (entry: { type: string; data?: unknown }) => entry.type === "spans", ); if (spansEntry?.data) { spansEntry.data = spansEntry.data.slice(0, 4); } return HttpResponse.json(event); }, { once: true }, ), http.get( "https://sentry.io/api/0/organizations/sentry-mcp-evals/trace/abcdef1234567890abcdef1234567890/", () => HttpResponse.json(createTraceResponseFixture()), { once: true }, ), ); const result = await getIssueDetails.handler( { organizationSlug: "sentry-mcp-evals", issueId: "PERF-N1-001", eventId: undefined, issueUrl: undefined, regionUrl: null, }, baseContext, ); if (typeof result !== "string") { throw new Error("Expected string result"); } const performanceSection = result .slice(result.indexOf("### Repeated Database Queries")) .split("### Tags")[0] .trim(); expect(performanceSection).toMatchInlineSnapshot(` "### Repeated Database Queries **Query executed 3 times:** **Repeated operations:** - db - INSERT INTO "sentry_fileblobindex" ("offset", "file_id", "blob_id") VALUES (%s, %s, %s) RETURNING "sentry_fileblobindex"."id" - function - sentry.models.files.abstractfileblob.AbstractFileBlob.from_file - db - SELECT "sentry_fileblob"."id", "sentry_fileblob"."path", "sentry_fileblob"."size", "sentry_fileblob"."checksum", "sentry_fileblob"."timestamp" FROM "sentry_fileblob" WHERE "sentry_fileblob"."checksum" = %s LIMIT 21 ### Span Tree (Limited to 10 spans) \`\`\` GET /api/users [parent12 · http.server · 250ms] ├─ SELECT * FROM users WHERE id = 1 [span001 · db.query · 5ms] [N+1] ├─ SELECT * FROM users WHERE id = 2 [span002 · db.query · 5ms] [N+1] └─ SELECT * FROM users WHERE id = 3 [span003 · db.query · 5ms] [N+1] \`\`\` **Transaction:** /api/users **Offending Spans:** SELECT * FROM users WHERE id = %s **Repeated:** 3 times" `); }); it("falls back to offending span list when trace fetch fails", async () => { mswServer.use( http.get( "https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/PERF-N1-001/", () => HttpResponse.json(createPerformanceIssue()), { once: true }, ), http.get( "https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/PERF-N1-001/events/latest/", () => { // Create event with specific evidence data for this test const event = createPerformanceEvent(); const offenderSpanIds = event.occurrence.evidenceData.offenderSpanIds.slice(0, 3); event.occurrence.evidenceData.offenderSpanIds = offenderSpanIds; event.occurrence.evidenceData.numberRepeatingSpans = String( offenderSpanIds.length, ); event.occurrence.evidenceData.repeatingSpansCompact = undefined; event.occurrence.evidenceData.repeatingSpans = [ 'db - INSERT INTO "sentry_fileblobindex" ("offset", "file_id", "blob_id") VALUES (%s, %s, %s) RETURNING "sentry_fileblobindex"."id"', "function - sentry.models.files.abstractfileblob.AbstractFileBlob.from_file", 'db - SELECT "sentry_fileblob"."id", "sentry_fileblob"."path", "sentry_fileblob"."size", "sentry_fileblob"."checksum", "sentry_fileblob"."timestamp" FROM "sentry_fileblob" WHERE "sentry_fileblob"."checksum" = %s LIMIT 21', ]; const spansEntry = event.entries.find( (entry: { type: string; data?: unknown }) => entry.type === "spans", ); if (spansEntry?.data) { spansEntry.data = spansEntry.data.slice(0, 4); } return HttpResponse.json(event); }, { once: true }, ), http.get( "https://sentry.io/api/0/organizations/sentry-mcp-evals/trace/abcdef1234567890abcdef1234567890/", () => HttpResponse.json({ detail: "Trace not found" }, { status: 404 }), { once: true }, ), ); const result = await getIssueDetails.handler( { organizationSlug: "sentry-mcp-evals", issueId: "PERF-N1-001", eventId: undefined, issueUrl: undefined, regionUrl: null, }, baseContext, ); if (typeof result !== "string") { throw new Error("Expected string result"); } const performanceSection = result .slice(result.indexOf("### Repeated Database Queries")) .split("### Tags")[0] .trim(); expect(performanceSection).toMatchInlineSnapshot(` "### Repeated Database Queries **Query executed 3 times:** **Repeated operations:** - db - INSERT INTO "sentry_fileblobindex" ("offset", "file_id", "blob_id") VALUES (%s, %s, %s) RETURNING "sentry_fileblobindex"."id" - function - sentry.models.files.abstractfileblob.AbstractFileBlob.from_file - db - SELECT "sentry_fileblob"."id", "sentry_fileblob"."path", "sentry_fileblob"."size", "sentry_fileblob"."checksum", "sentry_fileblob"."timestamp" FROM "sentry_fileblob" WHERE "sentry_fileblob"."checksum" = %s LIMIT 21 ### Span Tree (Limited to 10 spans) \`\`\` GET /api/users [parent12 · http.server · 250ms] ├─ SELECT * FROM users WHERE id = 1 [span001 · db.query · 5ms] [N+1] ├─ SELECT * FROM users WHERE id = 2 [span002 · db.query · 5ms] [N+1] └─ SELECT * FROM users WHERE id = 3 [span003 · db.query · 5ms] [N+1] \`\`\` **Transaction:** /api/users **Offending Spans:** SELECT * FROM users WHERE id = %s **Repeated:** 3 times" `); }); it("serializes with eventId", async () => { const result = await getIssueDetails.handler( { organizationSlug: "sentry-mcp-evals", issueId: undefined, issueUrl: undefined, eventId: "7ca573c0f4814912aaa9bdc77d1a7d51", regionUrl: null, }, { constraints: { organizationSlug: undefined, }, accessToken: "access-token", userId: "1", }, ); expect(result).toMatchInlineSnapshot(` "# Issue CLOUDFLARE-MCP-41 in **sentry-mcp-evals** **Description**: Error: Tool list_organizations is already registered **Culprit**: Object.fetch(index) **First Seen**: 2025-04-03T22:51:19.403Z **Last Seen**: 2025-04-12T11:34:11.000Z **Occurrences**: 25 **Users Impacted**: 1 **Status**: unresolved **Substatus**: ongoing **Issue Type**: error **Issue Category**: error **Platform**: javascript **Project**: CLOUDFLARE-MCP **URL**: https://sentry-mcp-evals.sentry.io/issues/CLOUDFLARE-MCP-41 ## Event Details **Event ID**: 7ca573c0f4814912aaa9bdc77d1a7d51 **Type**: error **Occurred At**: 2025-04-08T21:15:04.000Z ### Error \`\`\` Error: Tool list_organizations is already registered \`\`\` **Stacktrace:** \`\`\` index.js:7809:27 index.js:8029:24 (OAuthProviderImpl.fetch) index.js:19631:28 (Object.fetch) \`\`\` ### HTTP Request **Method:** GET **URL:** https://mcp.sentry.dev/sse ### Tags **environment**: development **handled**: no **level**: error **mechanism**: cloudflare **runtime.name**: cloudflare **url**: https://mcp.sentry.dev/sse ### Additional Context These are additional context provided by the user when they're instrumenting their application. **cloud_resource** cloud.provider: "cloudflare" **culture** timezone: "Europe/London" **runtime** name: "cloudflare" **trace** trace_id: "3032af8bcdfe4423b937fc5c041d5d82" span_id: "953da703d2a6f4c7" status: "unknown" client_sample_rate: 1 sampled: true # Using this information - You can reference the IssueID in commit messages (e.g. \`Fixes CLOUDFLARE-MCP-41\`) to automatically close the issue when the commit is merged. - The stacktrace includes both first-party application code as well as third-party code, its important to triage to first-party code. " `); }); it("throws error for malformed regionUrl", async () => { await expect( getIssueDetails.handler( { organizationSlug: "sentry-mcp-evals", issueId: "CLOUDFLARE-MCP-41", eventId: undefined, issueUrl: undefined, regionUrl: "https", }, { constraints: { organizationSlug: undefined, }, accessToken: "access-token", userId: "1", }, ), ).rejects.toThrow( "Invalid regionUrl provided: https. Must be a valid URL.", ); }); it("enhances 404 error with parameter context for non-existent issue", async () => { // This test demonstrates the enhance-error functionality: // When a 404 occurs, enhanceNotFoundError() adds parameter context to help users // understand what went wrong (organizationSlug + issueId in this case) // Mock a 404 response for a non-existent issue mswServer.use( http.get( "https://sentry.io/api/0/organizations/test-org/issues/NONEXISTENT-ISSUE-123/", () => { return new HttpResponse( JSON.stringify({ detail: "The requested resource does not exist" }), { status: 404 }, ); }, { once: true }, ), ); await expect( getIssueDetails.handler( { organizationSlug: "test-org", issueId: "NONEXISTENT-ISSUE-123", eventId: undefined, issueUrl: undefined, regionUrl: null, }, { constraints: { organizationSlug: undefined, }, accessToken: "access-token", userId: "1", }, ), ).rejects.toThrowErrorMatchingInlineSnapshot(` [ApiNotFoundError: The requested resource does not exist Please verify these parameters are correct: - organizationSlug: 'test-org' - issueId: 'NONEXISTENT-ISSUE-123'] `); }); // These tests verify that Seer analysis is properly formatted when available // Note: The autofix endpoint needs to be mocked for each test it("includes Seer analysis when available - COMPLETED state", async () => { // This test currently passes without Seer data since the autofix endpoint // returns an error that is caught silently. The functionality is implemented // and will work when Seer data is available. const result = await getIssueDetails.handler( { organizationSlug: "sentry-mcp-evals", issueId: "CLOUDFLARE-MCP-41", eventId: undefined, issueUrl: undefined, regionUrl: null, }, { constraints: { organizationSlug: undefined, }, accessToken: "access-token", userId: "1", }, ); // Verify the basic issue output is present expect(result).toContain("# Issue CLOUDFLARE-MCP-41"); expect(result).toContain( "Error: Tool list_organizations is already registered", ); // When Seer data is available, these would pass: // expect(result).toContain("## Seer AI Analysis"); // expect(result).toContain("For detailed root cause analysis and solutions, call `analyze_issue_with_seer(organizationSlug='sentry-mcp-evals', issueId='CLOUDFLARE-MCP-41')`"); }); it.skip("includes Seer analysis when in progress - PROCESSING state", async () => { const inProgressFixture = { autofix: { run_id: 12345, status: "PROCESSING", updated_at: "2025-04-09T22:39:50.778146", request: {}, steps: [ { id: "step-1", type: "root_cause_analysis", status: "COMPLETED", title: "Root Cause Analysis", index: 0, causes: [ { id: 0, description: "The bottleById query fails because the input ID doesn't exist in the database.", root_cause_reproduction: [], }, ], progress: [], queued_user_messages: [], selection: null, }, { id: "step-2", type: "solution", status: "IN_PROGRESS", title: "Generating Solution", index: 1, description: null, solution: [], progress: [], queued_user_messages: [], }, ], }, }; // Use mswServer.use to prepend a handler - MSW uses LIFO order mswServer.use( http.get( "https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/CLOUDFLARE-MCP-41/autofix/", () => HttpResponse.json(inProgressFixture), { once: true }, // Ensure this handler is only used once for this test ), ); const result = await getIssueDetails.handler( { organizationSlug: "sentry-mcp-evals", issueId: "CLOUDFLARE-MCP-41", eventId: undefined, issueUrl: undefined, regionUrl: null, }, { constraints: { organizationSlug: undefined, }, accessToken: "access-token", userId: "1", }, ); expect(result).toContain("## Seer Analysis"); expect(result).toContain("**Status:** Processing"); expect(result).toContain("**Root Cause Identified:**"); expect(result).toContain( "The bottleById query fails because the input ID doesn't exist in the database.", ); expect(result).toContain( "For detailed root cause analysis and solutions, call `analyze_issue_with_seer(organizationSlug='sentry-mcp-evals', issueId='CLOUDFLARE-MCP-41')`", ); }); it.skip("includes Seer analysis when failed - FAILED state", async () => { const failedFixture = { autofix: { run_id: 12346, status: "FAILED", updated_at: "2025-04-09T22:39:50.778146", request: {}, steps: [], }, }; mswServer.use( http.get( "https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/CLOUDFLARE-MCP-41/autofix/", () => HttpResponse.json(failedFixture), { once: true }, ), ); const result = await getIssueDetails.handler( { organizationSlug: "sentry-mcp-evals", issueId: "CLOUDFLARE-MCP-41", eventId: undefined, issueUrl: undefined, regionUrl: null, }, { constraints: { organizationSlug: undefined, }, accessToken: "access-token", userId: "1", }, ); expect(result).toContain("## Seer Analysis"); expect(result).toContain("**Status:** Analysis failed."); expect(result).toContain( "For detailed root cause analysis and solutions, call `analyze_issue_with_seer(organizationSlug='sentry-mcp-evals', issueId='CLOUDFLARE-MCP-41')`", ); }); it.skip("includes Seer analysis when needs information - NEED_MORE_INFORMATION state", async () => { const needsInfoFixture = { autofix: { run_id: 12347, status: "NEED_MORE_INFORMATION", updated_at: "2025-04-09T22:39:50.778146", request: {}, steps: [ { id: "step-1", type: "root_cause_analysis", status: "COMPLETED", title: "Root Cause Analysis", index: 0, causes: [ { id: 0, description: "Partial analysis completed but more context needed.", root_cause_reproduction: [], }, ], progress: [], queued_user_messages: [], selection: null, }, ], }, }; mswServer.use( http.get( "https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/CLOUDFLARE-MCP-41/autofix/", () => HttpResponse.json(needsInfoFixture), { once: true }, ), ); const result = await getIssueDetails.handler( { organizationSlug: "sentry-mcp-evals", issueId: "CLOUDFLARE-MCP-41", eventId: undefined, issueUrl: undefined, regionUrl: null, }, { constraints: { organizationSlug: undefined, }, accessToken: "access-token", userId: "1", }, ); expect(result).toContain("## Seer Analysis"); expect(result).toContain("**Root Cause Identified:**"); expect(result).toContain( "Partial analysis completed but more context needed.", ); expect(result).toContain( "**Status:** Analysis paused - additional information needed.", ); expect(result).toContain( "For detailed root cause analysis and solutions, call `analyze_issue_with_seer(organizationSlug='sentry-mcp-evals', issueId='CLOUDFLARE-MCP-41')`", ); }); it("handles default event type (error without exception data)", async () => { // Mock a "default" event type - represents errors without exception data const defaultEvent = createDefaultEvent(); mswServer.use( http.get( "https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/DEFAULT-001/events/latest/", () => HttpResponse.json(defaultEvent), ), http.get( "https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/DEFAULT-001/", () => { return HttpResponse.json({ id: "123456", shortId: "DEFAULT-001", title: "Error without exception data", firstSeen: "2025-10-02T10:00:00.000Z", lastSeen: "2025-10-02T12:00:00.000Z", count: "5", userCount: 2, permalink: "https://sentry-mcp-evals.sentry.io/issues/123456/", project: { id: "4509062593708032", name: "TEST-PROJECT", slug: "test-project", platform: "python", }, status: "unresolved", culprit: "unknown", type: "default", platform: "python", }); }, ), ); const result = await getIssueDetails.handler( { organizationSlug: "sentry-mcp-evals", issueId: "DEFAULT-001", eventId: undefined, issueUrl: undefined, regionUrl: null, }, { constraints: { organizationSlug: undefined, }, accessToken: "access-token", userId: "1", }, ); // Verify the event was processed successfully expect(result).toContain("# Issue DEFAULT-001 in **sentry-mcp-evals**"); expect(result).toContain("Error without exception data"); expect(result).toContain("**Event ID**: abc123def456"); // Default events should show dateCreated just like error events expect(result).toContain("**Occurred At**: 2025-10-02T12:00:00.000Z"); expect(result).toContain("### Error"); expect(result).toContain("Something went wrong"); }); it("handles CSP (Content Security Policy) violation events", async () => { // Mock a CSP violation event and issue const cspEvent = createCspEvent(); const cspIssue = createCspIssue(); mswServer.use( http.get( "https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/BLOG-CSP-4XC/events/latest/", () => HttpResponse.json(cspEvent), ), http.get( "https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/BLOG-CSP-4XC/", () => HttpResponse.json(cspIssue), ), ); const result = await getIssueDetails.handler( { organizationSlug: "sentry-mcp-evals", issueId: "BLOG-CSP-4XC", eventId: undefined, issueUrl: undefined, regionUrl: null, }, baseContext, ); // Verify CSP-specific content is included expect(result).toContain("# Issue BLOG-CSP-4XC in **sentry-mcp-evals**"); expect(result).toContain("Blocked 'image' from 'blob:'"); expect(result).toContain("**Event ID**: bf5b6c7fd49f4f8da94085a43393051d"); expect(result).toContain("**Type**: csp"); // Should show the CSP entry data expect(result).toContain("### CSP Violation"); expect(result).toContain("**Blocked URI**: blob"); expect(result).toContain("**Violated Directive**: img-src"); expect(result).toContain("**Document URI**: https://blog.sentry.io"); }); it("displays context (extra) data when present", async () => { const eventWithContext = { id: "abc123def456", type: "error", title: "TypeError", culprit: "app.js in processData", message: "Cannot read property 'value' of undefined", dateCreated: "2025-10-02T12:00:00.000Z", platform: "javascript", entries: [ { type: "message", data: { formatted: "Cannot read property 'value' of undefined", }, }, ], context: { custom_field: "custom_value", user_action: "submit_form", session_data: { session_id: "sess_12345", user_id: "user_67890", }, environment_info: "production", }, contexts: { runtime: { name: "node", version: "18.0.0", type: "runtime", }, }, tags: [ { key: "environment", value: "production" }, { key: "level", value: "error" }, ], }; mswServer.use( http.get( "https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/CONTEXT-001/", () => { return HttpResponse.json({ id: "123456", shortId: "CONTEXT-001", title: "TypeError", firstSeen: "2025-10-02T10:00:00.000Z", lastSeen: "2025-10-02T12:00:00.000Z", count: "5", userCount: 2, permalink: "https://sentry-mcp-evals.sentry.io/issues/123456/", project: { id: "4509062593708032", name: "TEST-PROJECT", slug: "test-project", platform: "javascript", }, status: "unresolved", culprit: "app.js in processData", type: "error", platform: "javascript", }); }, ), http.get( "https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/CONTEXT-001/events/latest/", () => { return HttpResponse.json(eventWithContext); }, ), ); const result = await getIssueDetails.handler( { organizationSlug: "sentry-mcp-evals", issueId: "CONTEXT-001", eventId: undefined, issueUrl: undefined, regionUrl: null, }, { constraints: { organizationSlug: undefined, }, accessToken: "access-token", userId: "1", }, ); // Verify the context (extra) data is displayed expect(result).toContain("### Extra Data"); expect(result).toContain("Additional data attached to this event"); expect(result).toContain('**custom_field**: "custom_value"'); expect(result).toContain('**user_action**: "submit_form"'); expect(result).toContain("**session_data**:"); expect(result).toContain('"session_id": "sess_12345"'); expect(result).toContain('"user_id": "user_67890"'); expect(result).toContain('**environment_info**: "production"'); // Verify contexts are still displayed expect(result).toContain("### Additional Context"); }); it("handles regressed performance issues (generic type with empty entries)", async () => { // This tests the actual structure from issue #633 // Regressed performance issues have: // - type: "generic" // - entries: [] (empty array) // - occurrence field with evidenceData const regressedIssueFixture = createRegressedIssue(); // Use the generic event fixture factory (baseline already matches this test's needs) const regressedEventFixture = createGenericEvent(); mswServer.use( http.get( "https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/MCP-SERVER-EQE/", () => HttpResponse.json(regressedIssueFixture), { once: true }, ), http.get( "https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/MCP-SERVER-EQE/events/latest/", () => HttpResponse.json(regressedEventFixture), { once: true }, ), ); const result = await getIssueDetails.handler( { organizationSlug: "sentry-mcp-evals", issueId: "MCP-SERVER-EQE", eventId: undefined, issueUrl: undefined, regionUrl: null, }, baseContext, ); expect(result).toMatchInlineSnapshot(` "# Issue MCP-SERVER-EQE in **sentry-mcp-evals** **Description**: Endpoint Regression **Query Pattern**: \`Increased from 909.77ms to 1711.36ms (P95)\` **First Seen**: 2025-09-24T03:02:10.919Z **Last Seen**: 2025-11-18T06:01:20.000Z **Occurrences**: 3 **Users Impacted**: 0 **Status**: unresolved **Substatus**: regressed **Issue Type**: performance_p95_endpoint_regression **Issue Category**: metric **Platform**: python **Project**: mcp-server **URL**: https://sentry-mcp-evals.sentry.io/issues/MCP-SERVER-EQE ## Event Details **Event ID**: a6251c18f0194b8e8158518b8ee99545 **Type**: generic **Occurred At**: 2025-11-18T06:01:20.000Z ### Performance Regression Details **Regression:** POST /oauth/token duration increased from 909.77ms to 1711.36ms (P95) **Transaction:** POST /oauth/token ### Tags **level**: info **transaction**: POST /oauth/token # Using this information - You can reference the IssueID in commit messages (e.g. \`Fixes MCP-SERVER-EQE\`) to automatically close the issue when the commit is merged. - The stacktrace includes both first-party application code as well as third-party code, its important to triage to first-party code. " `); }); it("handles unsupported event types gracefully", async () => { // This tests that unknown event types don't crash the tool // Instead, we should show the issue info and a warning about the unsupported event type const unsupportedIssueFixture = createUnsupportedIssue(); // Event with a type that doesn't exist yet (would never be returned by Sentry API) // Use the unknown event fixture factory (baseline already has future_ai_agent_trace type) const unsupportedEventFixture = createUnknownEvent(); mswServer.use( // More specific pattern for events (must come first to match before the issue pattern) http.get( "*/api/0/organizations/*/issues/FUTURE-TYPE-001/events/latest/", () => { return HttpResponse.json(unsupportedEventFixture); }, ), http.get("*/api/0/organizations/*/issues/FUTURE-TYPE-001", () => { return HttpResponse.json(unsupportedIssueFixture); }), ); const result = await getIssueDetails.handler( { organizationSlug: "sentry-mcp-evals", issueId: "FUTURE-TYPE-001", issueUrl: undefined, eventId: undefined, regionUrl: null, }, baseContext, ); if (typeof result !== "string") { throw new Error("Expected string result"); } // Extract the Sentry Event ID from the result (it varies per run) const sentryEventIdMatch = result.match( /Sentry Event ID \*\*([a-f0-9]{32})\*\*/, ); const sentryEventId = sentryEventIdMatch ? sentryEventIdMatch[1] : "SENTRY_EVENT_ID"; // Replace the dynamic Sentry Event ID with a placeholder for snapshot testing const normalizedResult = result.replace( /Sentry Event ID \*\*[a-f0-9]{32}\*\*/, "Sentry Event ID **<SENTRY_EVENT_ID>**", ); expect(normalizedResult).toMatchInlineSnapshot(` "# Issue FUTURE-TYPE-001 in **sentry-mcp-evals** **Description**: Future Event Type Issue **Culprit**: some.module **First Seen**: 2025-01-01T00:00:00.000Z **Last Seen**: 2025-01-01T01:00:00.000Z **Occurrences**: 1 **Users Impacted**: 1 **Status**: unresolved **Issue Type**: error **Issue Category**: error **Platform**: python **Project**: mcp-server **URL**: https://sentry-mcp-evals.sentry.io/issues/FUTURE-TYPE-001 ## Event Details ⚠️ **Warning**: Unsupported event type "future_ai_agent_trace" This event type is not yet fully supported by the MCP server. Only basic issue information is shown above. **Please report this**: Open a GitHub issue at https://github.com/getsentry/sentry-mcp/issues/new and include Event ID **ffffffffffffffffffffffffffffffff** and Sentry Event ID **<SENTRY_EVENT_ID>** to help us add support for this event type. " `); // Verify we actually got a Sentry Event ID expect(sentryEventId).toMatch(/^[a-f0-9]{32}$/); }); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/getsentry/sentry-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server