Skip to main content
Glama

Sentry MCP

Official
by getsentry
get-issue-details.ts8.08 kB
import { z } from "zod"; import { setTag } from "@sentry/core"; import { defineTool } from "../internal/tool-helpers/define"; import { apiServiceFromContext } from "../internal/tool-helpers/api"; import { parseIssueParams, formatIssueOutput, } from "../internal/tool-helpers/issue"; import { enhanceNotFoundError } from "../internal/tool-helpers/enhance-error"; import { ApiNotFoundError } from "../api-client"; import type { SentryApiService } from "../api-client"; import type { Event, ErrorEvent, DefaultEvent, TransactionEvent, Trace, } from "../api-client/types"; import { UserInputError } from "../errors"; import type { ServerContext } from "../types"; import { logError } from "../telem/logging"; import { ParamOrganizationSlug, ParamRegionUrl, ParamIssueShortId, ParamIssueUrl, } from "../schema"; export default defineTool({ name: "get_issue_details", requiredScopes: ["event:read"], description: [ "Get detailed information about a specific Sentry issue by ID.", "", "🔍 USE THIS TOOL WHEN USERS:", "- Provide a specific issue ID (e.g., 'CLOUDFLARE-MCP-41', 'PROJECT-123')", "- Ask to 'explain [ISSUE-ID]', 'tell me about [ISSUE-ID]'", "- Want details/stacktrace/analysis for a known issue", "- Provide a Sentry issue URL", "", "❌ DO NOT USE for:", "- General searching or listing issues (use search_issues)", "- Root cause analysis (use analyze_issue_with_seer)", "", "TRIGGER PATTERNS:", "- 'Explain ISSUE-123' → use get_issue_details", "- 'Tell me about PROJECT-456' → use get_issue_details", "- 'What happened in [issue URL]' → use get_issue_details", "", "<examples>", "### Explain specific issue", "```", "get_issue_details(organizationSlug='my-organization', issueId='CLOUDFLARE-MCP-41')", "```", "", "### Get details for event ID", "```", "get_issue_details(organizationSlug='my-organization', eventId='c49541c747cb4d8aa3efb70ca5aba243')", "```", "</examples>", "", "<hints>", "- If the user provides the `issueUrl`, you can ignore the other parameters.", "- If the user provides `issueId` or `eventId` (only one is needed), `organizationSlug` is required.", "</hints>", ].join("\n"), inputSchema: { organizationSlug: ParamOrganizationSlug.optional(), regionUrl: ParamRegionUrl.optional(), issueId: ParamIssueShortId.optional(), eventId: z.string().trim().describe("The ID of the event.").optional(), issueUrl: ParamIssueUrl.optional(), }, async handler(params, context: ServerContext) { const apiService = apiServiceFromContext(context, { regionUrl: params.regionUrl, }); if (params.eventId) { const orgSlug = params.organizationSlug; const eventId = params.eventId; // Capture eventId for type safety if (!orgSlug) { throw new UserInputError( "`organizationSlug` is required when providing `eventId`", ); } setTag("organization.slug", orgSlug); // API client will throw ApiClientError/ApiServerError which the MCP server wrapper handles const [issue] = await apiService.listIssues({ organizationSlug: orgSlug, query: eventId, }); if (!issue) { return `# Event Not Found\n\nNo issue found for Event ID: ${eventId}`; } // For this call, we might want to provide context if it fails let event: Awaited<ReturnType<typeof apiService.getEventForIssue>>; try { event = await apiService.getEventForIssue({ organizationSlug: orgSlug, issueId: issue.shortId, eventId, }); } catch (error) { // Optionally enhance 404 errors with parameter context if (error instanceof ApiNotFoundError) { throw enhanceNotFoundError(error, { organizationSlug: orgSlug, issueId: issue.shortId, eventId, }); } throw error; } // Try to fetch Seer analysis context (non-blocking) let autofixState: | Awaited<ReturnType<typeof apiService.getAutofixState>> | undefined; try { autofixState = await apiService.getAutofixState({ organizationSlug: orgSlug, issueId: issue.shortId, }); } catch (error) { // Silently continue if Seer analysis is not available // This ensures the tool works even if Seer is not enabled } const performanceTrace = await maybeFetchPerformanceTrace({ apiService, organizationSlug: orgSlug, event, }); return formatIssueOutput({ organizationSlug: orgSlug, issue, event, apiService, autofixState, performanceTrace, }); } // Validate that we have the minimum required parameters if (!params.issueUrl && !params.issueId) { throw new UserInputError( "Either `issueId` or `issueUrl` must be provided", ); } if (!params.issueUrl && !params.organizationSlug) { throw new UserInputError( "`organizationSlug` is required when providing `issueId`", ); } const { organizationSlug: orgSlug, issueId: parsedIssueId } = parseIssueParams({ organizationSlug: params.organizationSlug, issueId: params.issueId, issueUrl: params.issueUrl, }); setTag("organization.slug", orgSlug); // For the main issue lookup, provide parameter context on 404 let issue: Awaited<ReturnType<typeof apiService.getIssue>>; try { issue = await apiService.getIssue({ organizationSlug: orgSlug, issueId: parsedIssueId!, }); } catch (error) { if (error instanceof ApiNotFoundError) { throw enhanceNotFoundError(error, { organizationSlug: orgSlug, issueId: parsedIssueId, }); } throw error; } const event = await apiService.getLatestEventForIssue({ organizationSlug: orgSlug, issueId: issue.shortId, }); // Try to fetch Seer analysis context (non-blocking) let autofixState: | Awaited<ReturnType<typeof apiService.getAutofixState>> | undefined; try { autofixState = await apiService.getAutofixState({ organizationSlug: orgSlug, issueId: issue.shortId, }); } catch (error) { // Silently continue if Seer analysis is not available // This ensures the tool works even if Seer is not enabled } const performanceTrace = await maybeFetchPerformanceTrace({ apiService, organizationSlug: orgSlug, event, }); return formatIssueOutput({ organizationSlug: orgSlug, issue, event, apiService, autofixState, performanceTrace, }); }, }); async function maybeFetchPerformanceTrace({ apiService, organizationSlug, event, }: { apiService: SentryApiService; organizationSlug: string; event: Event; }): Promise<Trace | undefined> { const context = shouldFetchTraceForEvent(event); if (!context) { return undefined; } try { return await apiService.getTrace({ organizationSlug, traceId: context.traceId, limit: 10000, }); } catch (error) { logError(error); return undefined; } } function isErrorEvent(event: Event): event is ErrorEvent | DefaultEvent { // "default" type represents error events without exception data return event.type === "error" || event.type === "default"; } function isTransactionEvent(event: Event): event is TransactionEvent { return event.type === "transaction"; } function shouldFetchTraceForEvent(event: Event): { traceId: string } | null { // Only fetch traces for non-error events (transactions, profiling, etc.) if (isErrorEvent(event)) { return null; } // Check if we have a trace ID const traceId = event.contexts?.trace?.trace_id; if (typeof traceId !== "string" || traceId.length === 0) { return null; } return { traceId }; }

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