Skip to content

Commit 3bdc0bc

Browse files
betegoncodex
andcommitted
fix(tools): preserve suspect commits in all issue detail outputs
Render the suspect commit in Markdown as well as structured responses. Report unexpected commit lookup failures while keeping issue details available, and cover output modes and failure handling with regression tests. Co-Authored-By: GPT-6 <noreply@openai.com>
1 parent 6202943 commit 3bdc0bc

8 files changed

Lines changed: 295 additions & 93 deletions

File tree

‎docs/contributing/tool-responses.md‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,16 @@ When changing Sentry API endpoint usage, validate the upstream behavior in
115115
should model what Sentry returns, but tool responses should model what users
116116
need.
117117

118+
### Issue Details
119+
120+
`get_issue_details` includes the issue's suspect commit when available, with
121+
its SHA, message, author, and source. Structured responses expose
122+
`suspectCommit`; Markdown responses include a `Suspect Commit` section with
123+
the same data. Without a commit, the structured field is `null` and Markdown
124+
omits the section. Commit lookup is optional: failures do not prevent issue
125+
details from loading, but unexpected server or response-validation failures
126+
are reported to Sentry.
127+
118128
## Structured Content
119129

120130
MCP tools may expose `structuredContent` alongside generated text `content`.

‎packages/mcp-core/src/api-client/schema.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -794,7 +794,7 @@ export const CommitSchema = z
794794
message: z.string().nullable().optional(),
795795
dateCreated: z.string().datetime().nullable().optional(),
796796
pullRequest: z.record(z.string(), z.unknown()).nullable().optional(),
797-
// only the event committers endpoint actually populates this; release commits never do
797+
// The event committers endpoint populates this; release commits usually return an empty string.
798798
suspectCommitType: z.string().optional(),
799799
author: ApiActorSchema.nullable().optional(),
800800
repository: z

‎packages/mcp-core/src/internal/formatting.ts‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import type {
2121
import { ThreadsEntrySchema } from "../api-client";
2222
import type {
2323
AutofixRunState,
24+
CommitterList,
2425
Event,
2526
ExternalIssueList,
2627
GenericEvent,
@@ -1984,6 +1985,22 @@ function formatSeerSummary(autofixState: AutofixRunState | undefined): string {
19841985
return `${parts.join("\n")}\n\n`;
19851986
}
19861987

1988+
/** Projects the suspect commit consistently for structured and markdown issue details. */
1989+
export function getSuspectCommit(committers: CommitterList | undefined) {
1990+
// The endpoint currently returns the issue's latest suspect commit, grouped by author.
1991+
const committer = committers?.[0];
1992+
const commit = committer?.commits[0];
1993+
if (!commit) {
1994+
return null;
1995+
}
1996+
return {
1997+
id: String(commit.id),
1998+
message: commit.message,
1999+
author: committer.author?.name ?? committer.author?.email,
2000+
suspectCommitType: commit.suspectCommitType,
2001+
};
2002+
}
2003+
19872004
/**
19882005
* Formats a Sentry issue with its latest event into comprehensive markdown output.
19892006
* Includes issue metadata, event details, and usage instructions.
@@ -2002,6 +2019,7 @@ export function formatIssueOutput({
20022019
relatedReplayIds,
20032020
aiConversations,
20042021
codeLocation,
2022+
committers,
20052023
experimentalMode,
20062024
availableToolNames,
20072025
directToolNames,
@@ -2016,6 +2034,7 @@ export function formatIssueOutput({
20162034
relatedReplayIds?: string[];
20172035
aiConversations?: AIConversationReference[];
20182036
codeLocation?: CodeLocation;
2037+
committers?: CommitterList;
20192038
experimentalMode?: boolean;
20202039
availableToolNames?: ReadonlySet<string>;
20212040
directToolNames?: ReadonlySet<string>;
@@ -2091,6 +2110,22 @@ export function formatIssueOutput({
20912110
output += formatCodeLocation(codeLocation);
20922111
}
20932112

2113+
const suspectCommit = getSuspectCommit(committers);
2114+
if (suspectCommit) {
2115+
output += "## Suspect Commit\n\n";
2116+
output += `**SHA**: \`${suspectCommit.id}\`\n`;
2117+
if (suspectCommit.message) {
2118+
output += `**Message**: ${suspectCommit.message}\n`;
2119+
}
2120+
if (suspectCommit.author) {
2121+
output += `**Author**: ${suspectCommit.author}\n`;
2122+
}
2123+
if (suspectCommit.suspectCommitType) {
2124+
output += `**Source**: ${suspectCommit.suspectCommitType}\n`;
2125+
}
2126+
output += "\n";
2127+
}
2128+
20942129
output += "## Event Details\n\n";
20952130

20962131
// Check if this is an unsupported event type

‎packages/mcp-core/src/skillDefinitions.json‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@
9494
},
9595
{
9696
"name": "get_issue_details",
97-
"description": "Get detailed information about a specific Sentry issue by ID.\n\nUSE THIS TOOL WHEN USERS:\n- Provide a specific issue ID (e.g., 'CLOUDFLARE-MCP-41', 'PROJECT-123')\n- Ask to 'explain [ISSUE-ID]', 'tell me about [ISSUE-ID]'\n- Want details/stacktrace/analysis for a known issue\n- Provide a Sentry issue URL\n\nDO NOT USE for:\n- General searching or listing issues (use search_issues)\n\nTRIGGER PATTERNS:\n- 'Explain ISSUE-123' → use get_issue_details\n- 'Tell me about PROJECT-456' → use get_issue_details\n- 'What happened in [issue URL]' → use get_issue_details\n\n<examples>\n### With Sentry URL (recommended - simplest approach)\n```\nget_issue_details(issueUrl='https://sentry.sentry.io/issues/6916805731/?project=4509062593708032&query=is%3Aunresolved')\n```\n\n### With issue ID and organization\n```\nget_issue_details(organizationSlug='my-organization', issueId='CLOUDFLARE-MCP-41')\n```\n\n### With event ID and organization\n```\nget_issue_details(organizationSlug='my-organization', eventId='c49541c747cb4d8aa3efb70ca5aba243')\n```\n</examples>\n\n<hints>\n- **IMPORTANT**: If user provides a Sentry URL, pass the ENTIRE URL to issueUrl parameter unchanged\n- When using issueUrl, all other parameters are automatically extracted - don't provide them separately\n- If using issueId (not URL), then organizationSlug is required\n</hints>",
97+
"description": "Get detailed information about a specific Sentry issue by ID.\n\nUSE THIS TOOL WHEN USERS:\n- Provide a specific issue ID (e.g., 'CLOUDFLARE-MCP-41', 'PROJECT-123')\n- Ask to 'explain [ISSUE-ID]', 'tell me about [ISSUE-ID]'\n- Want details/stacktrace/analysis for a known issue\n- Want the suspect commit's SHA, message, author, and source when available\n- Provide a Sentry issue URL\n\nDO NOT USE for:\n- General searching or listing issues (use search_issues)\n\nTRIGGER PATTERNS:\n- 'Explain ISSUE-123' → use get_issue_details\n- 'Tell me about PROJECT-456' → use get_issue_details\n- 'What happened in [issue URL]' → use get_issue_details\n\n<examples>\n### With Sentry URL (recommended - simplest approach)\n```\nget_issue_details(issueUrl='https://sentry.sentry.io/issues/6916805731/?project=4509062593708032&query=is%3Aunresolved')\n```\n\n### With issue ID and organization\n```\nget_issue_details(organizationSlug='my-organization', issueId='CLOUDFLARE-MCP-41')\n```\n\n### With event ID and organization\n```\nget_issue_details(organizationSlug='my-organization', eventId='c49541c747cb4d8aa3efb70ca5aba243')\n```\n</examples>\n\n<hints>\n- **IMPORTANT**: If user provides a Sentry URL, pass the ENTIRE URL to issueUrl parameter unchanged\n- When using issueUrl, all other parameters are automatically extracted - don't provide them separately\n- If using issueId (not URL), then organizationSlug is required\n</hints>",
9898
"requiredScopes": ["event:read"]
9999
},
100100
{
@@ -239,7 +239,7 @@
239239
},
240240
{
241241
"name": "get_issue_details",
242-
"description": "Get detailed information about a specific Sentry issue by ID.\n\nUSE THIS TOOL WHEN USERS:\n- Provide a specific issue ID (e.g., 'CLOUDFLARE-MCP-41', 'PROJECT-123')\n- Ask to 'explain [ISSUE-ID]', 'tell me about [ISSUE-ID]'\n- Want details/stacktrace/analysis for a known issue\n- Provide a Sentry issue URL\n\nDO NOT USE for:\n- General searching or listing issues (use search_issues)\n\nTRIGGER PATTERNS:\n- 'Explain ISSUE-123' → use get_issue_details\n- 'Tell me about PROJECT-456' → use get_issue_details\n- 'What happened in [issue URL]' → use get_issue_details\n\n<examples>\n### With Sentry URL (recommended - simplest approach)\n```\nget_issue_details(issueUrl='https://sentry.sentry.io/issues/6916805731/?project=4509062593708032&query=is%3Aunresolved')\n```\n\n### With issue ID and organization\n```\nget_issue_details(organizationSlug='my-organization', issueId='CLOUDFLARE-MCP-41')\n```\n\n### With event ID and organization\n```\nget_issue_details(organizationSlug='my-organization', eventId='c49541c747cb4d8aa3efb70ca5aba243')\n```\n</examples>\n\n<hints>\n- **IMPORTANT**: If user provides a Sentry URL, pass the ENTIRE URL to issueUrl parameter unchanged\n- When using issueUrl, all other parameters are automatically extracted - don't provide them separately\n- If using issueId (not URL), then organizationSlug is required\n</hints>",
242+
"description": "Get detailed information about a specific Sentry issue by ID.\n\nUSE THIS TOOL WHEN USERS:\n- Provide a specific issue ID (e.g., 'CLOUDFLARE-MCP-41', 'PROJECT-123')\n- Ask to 'explain [ISSUE-ID]', 'tell me about [ISSUE-ID]'\n- Want details/stacktrace/analysis for a known issue\n- Want the suspect commit's SHA, message, author, and source when available\n- Provide a Sentry issue URL\n\nDO NOT USE for:\n- General searching or listing issues (use search_issues)\n\nTRIGGER PATTERNS:\n- 'Explain ISSUE-123' → use get_issue_details\n- 'Tell me about PROJECT-456' → use get_issue_details\n- 'What happened in [issue URL]' → use get_issue_details\n\n<examples>\n### With Sentry URL (recommended - simplest approach)\n```\nget_issue_details(issueUrl='https://sentry.sentry.io/issues/6916805731/?project=4509062593708032&query=is%3Aunresolved')\n```\n\n### With issue ID and organization\n```\nget_issue_details(organizationSlug='my-organization', issueId='CLOUDFLARE-MCP-41')\n```\n\n### With event ID and organization\n```\nget_issue_details(organizationSlug='my-organization', eventId='c49541c747cb4d8aa3efb70ca5aba243')\n```\n</examples>\n\n<hints>\n- **IMPORTANT**: If user provides a Sentry URL, pass the ENTIRE URL to issueUrl parameter unchanged\n- When using issueUrl, all other parameters are automatically extracted - don't provide them separately\n- If using issueId (not URL), then organizationSlug is required\n</hints>",
243243
"requiredScopes": ["event:read"]
244244
},
245245
{
@@ -365,7 +365,7 @@
365365
},
366366
{
367367
"name": "get_issue_details",
368-
"description": "Get detailed information about a specific Sentry issue by ID.\n\nUSE THIS TOOL WHEN USERS:\n- Provide a specific issue ID (e.g., 'CLOUDFLARE-MCP-41', 'PROJECT-123')\n- Ask to 'explain [ISSUE-ID]', 'tell me about [ISSUE-ID]'\n- Want details/stacktrace/analysis for a known issue\n- Provide a Sentry issue URL\n\nDO NOT USE for:\n- General searching or listing issues (use search_issues)\n\nTRIGGER PATTERNS:\n- 'Explain ISSUE-123' → use get_issue_details\n- 'Tell me about PROJECT-456' → use get_issue_details\n- 'What happened in [issue URL]' → use get_issue_details\n\n<examples>\n### With Sentry URL (recommended - simplest approach)\n```\nget_issue_details(issueUrl='https://sentry.sentry.io/issues/6916805731/?project=4509062593708032&query=is%3Aunresolved')\n```\n\n### With issue ID and organization\n```\nget_issue_details(organizationSlug='my-organization', issueId='CLOUDFLARE-MCP-41')\n```\n\n### With event ID and organization\n```\nget_issue_details(organizationSlug='my-organization', eventId='c49541c747cb4d8aa3efb70ca5aba243')\n```\n</examples>\n\n<hints>\n- **IMPORTANT**: If user provides a Sentry URL, pass the ENTIRE URL to issueUrl parameter unchanged\n- When using issueUrl, all other parameters are automatically extracted - don't provide them separately\n- If using issueId (not URL), then organizationSlug is required\n</hints>",
368+
"description": "Get detailed information about a specific Sentry issue by ID.\n\nUSE THIS TOOL WHEN USERS:\n- Provide a specific issue ID (e.g., 'CLOUDFLARE-MCP-41', 'PROJECT-123')\n- Ask to 'explain [ISSUE-ID]', 'tell me about [ISSUE-ID]'\n- Want details/stacktrace/analysis for a known issue\n- Want the suspect commit's SHA, message, author, and source when available\n- Provide a Sentry issue URL\n\nDO NOT USE for:\n- General searching or listing issues (use search_issues)\n\nTRIGGER PATTERNS:\n- 'Explain ISSUE-123' → use get_issue_details\n- 'Tell me about PROJECT-456' → use get_issue_details\n- 'What happened in [issue URL]' → use get_issue_details\n\n<examples>\n### With Sentry URL (recommended - simplest approach)\n```\nget_issue_details(issueUrl='https://sentry.sentry.io/issues/6916805731/?project=4509062593708032&query=is%3Aunresolved')\n```\n\n### With issue ID and organization\n```\nget_issue_details(organizationSlug='my-organization', issueId='CLOUDFLARE-MCP-41')\n```\n\n### With event ID and organization\n```\nget_issue_details(organizationSlug='my-organization', eventId='c49541c747cb4d8aa3efb70ca5aba243')\n```\n</examples>\n\n<hints>\n- **IMPORTANT**: If user provides a Sentry URL, pass the ENTIRE URL to issueUrl parameter unchanged\n- When using issueUrl, all other parameters are automatically extracted - don't provide them separately\n- If using issueId (not URL), then organizationSlug is required\n</hints>",
369369
"requiredScopes": ["event:read"]
370370
},
371371
{

‎packages/mcp-core/src/toolDefinitions.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3753,7 +3753,7 @@
37533753
},
37543754
{
37553755
"name": "get_issue_details",
3756-
"description": "Get detailed information about a specific Sentry issue by ID.\n\nUSE THIS TOOL WHEN USERS:\n- Provide a specific issue ID (e.g., 'CLOUDFLARE-MCP-41', 'PROJECT-123')\n- Ask to 'explain [ISSUE-ID]', 'tell me about [ISSUE-ID]'\n- Want details/stacktrace/analysis for a known issue\n- Provide a Sentry issue URL\n\nDO NOT USE for:\n- General searching or listing issues (use search_issues)\n\nTRIGGER PATTERNS:\n- 'Explain ISSUE-123' → use get_issue_details\n- 'Tell me about PROJECT-456' → use get_issue_details\n- 'What happened in [issue URL]' → use get_issue_details\n\n<examples>\n### With Sentry URL (recommended - simplest approach)\n```\nget_issue_details(issueUrl='https://sentry.sentry.io/issues/6916805731/?project=4509062593708032&query=is%3Aunresolved')\n```\n\n### With issue ID and organization\n```\nget_issue_details(organizationSlug='my-organization', issueId='CLOUDFLARE-MCP-41')\n```\n\n### With event ID and organization\n```\nget_issue_details(organizationSlug='my-organization', eventId='c49541c747cb4d8aa3efb70ca5aba243')\n```\n</examples>\n\n<hints>\n- **IMPORTANT**: If user provides a Sentry URL, pass the ENTIRE URL to issueUrl parameter unchanged\n- When using issueUrl, all other parameters are automatically extracted - don't provide them separately\n- If using issueId (not URL), then organizationSlug is required\n</hints>",
3756+
"description": "Get detailed information about a specific Sentry issue by ID.\n\nUSE THIS TOOL WHEN USERS:\n- Provide a specific issue ID (e.g., 'CLOUDFLARE-MCP-41', 'PROJECT-123')\n- Ask to 'explain [ISSUE-ID]', 'tell me about [ISSUE-ID]'\n- Want details/stacktrace/analysis for a known issue\n- Want the suspect commit's SHA, message, author, and source when available\n- Provide a Sentry issue URL\n\nDO NOT USE for:\n- General searching or listing issues (use search_issues)\n\nTRIGGER PATTERNS:\n- 'Explain ISSUE-123' → use get_issue_details\n- 'Tell me about PROJECT-456' → use get_issue_details\n- 'What happened in [issue URL]' → use get_issue_details\n\n<examples>\n### With Sentry URL (recommended - simplest approach)\n```\nget_issue_details(issueUrl='https://sentry.sentry.io/issues/6916805731/?project=4509062593708032&query=is%3Aunresolved')\n```\n\n### With issue ID and organization\n```\nget_issue_details(organizationSlug='my-organization', issueId='CLOUDFLARE-MCP-41')\n```\n\n### With event ID and organization\n```\nget_issue_details(organizationSlug='my-organization', eventId='c49541c747cb4d8aa3efb70ca5aba243')\n```\n</examples>\n\n<hints>\n- **IMPORTANT**: If user provides a Sentry URL, pass the ENTIRE URL to issueUrl parameter unchanged\n- When using issueUrl, all other parameters are automatically extracted - don't provide them separately\n- If using issueId (not URL), then organizationSlug is required\n</hints>",
37573757
"inputSchema": {
37583758
"type": "object",
37593759
"properties": {

0 commit comments

Comments
 (0)