Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions packages/cli/src/lib/api/issues.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import type {
GetOrganizationIssueData,
ListOrganizationIssuesData,
UpdateOrganizationIssueData,
} from "@sentry/api";
import {
getOrganizationIssue,
Expand Down Expand Up @@ -469,6 +470,10 @@ export async function tryGetIssueByShortId(
}
}

type IssueUpdateStatusDetails = NonNullable<
NonNullable<UpdateOrganizationIssueData["body"]>["statusDetails"]
>;

/**
* Resolution-release tracking for {@link updateIssueStatus}. Maps to the
* `statusDetails` shape expected by Sentry's bulk mutate endpoint:
Expand All @@ -481,11 +486,14 @@ export async function tryGetIssueByShortId(
* Sentry resolves when a release containing the commit is created. Both
* `commit` (SHA) and `repository` (Sentry-registered repo name) are
* required by the API's InCommitValidator.
*
* Narrowed from the `@sentry/api` issue update body so each variant is
* exclusive.
*/
export type ResolveStatusDetails =
| { inRelease: string }
| Required<Pick<IssueUpdateStatusDetails, "inRelease">>
| { inNextRelease: true }
| { inCommit: { commit: string; repository: string } };
| Required<Pick<IssueUpdateStatusDetails, "inCommit">>;

/**
* Sentinel string meaning "resolve in the next release (tied to HEAD)".
Expand Down
5 changes: 5 additions & 0 deletions packages/mcp-core/src/api-client/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ import type {
IssueCommentList,
IssueIntegrationList,
IssueList,
IssueResolutionDetails,
IssueTagKeyList,
IssueTagValues,
MetricAlertRule,
Expand Down Expand Up @@ -4122,6 +4123,7 @@ export class SentryApiService {
ignoreWindow,
ignoreUserCount,
ignoreUserWindow,
statusDetails,
}: {
organizationSlug: string;
issueId: string;
Expand All @@ -4133,6 +4135,7 @@ export class SentryApiService {
ignoreWindow?: number;
ignoreUserCount?: number;
ignoreUserWindow?: number;
statusDetails?: IssueResolutionDetails;
},
opts?: RequestOptions,
): Promise<Issue> {
Expand All @@ -4145,8 +4148,10 @@ export class SentryApiService {
ignoreWindow?: number;
ignoreUserCount?: number;
ignoreUserWindow?: number;
statusDetails?: IssueResolutionDetails;
} = {};
if (status !== undefined) updateData.status = status;
if (statusDetails !== undefined) updateData.statusDetails = statusDetails;
if (assignedTo !== undefined) updateData.assignedTo = assignedTo ?? "";
if (substatus !== undefined) updateData.substatus = substatus;
if (ignoreDuration !== undefined)
Expand Down
14 changes: 14 additions & 0 deletions packages/mcp-core/src/api-client/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
* }
* ```
*/
import type { UpdateOrganizationIssueData } from "@sentry/api";
import type { z } from "zod";
import type {
AgenticOnboardingRunSchema,
Expand Down Expand Up @@ -214,6 +215,19 @@ export type Commit = z.infer<typeof CommitSchema>;
export type Issue = z.infer<typeof IssueSchema>;
export type IssueActivity = z.infer<typeof IssueActivitySchema>;
export type IssueComment = z.infer<typeof IssueCommentSchema>;

type IssueUpdateStatusDetails = NonNullable<
NonNullable<UpdateOrganizationIssueData["body"]>["statusDetails"]
>;

/**
* `statusDetails` that resolve an issue in a specific release or commit,
* narrowed from the `@sentry/api` issue update body. Sentry validates both
* against its own release and commit records.
*/
export type IssueResolutionDetails =
| Required<Pick<IssueUpdateStatusDetails, "inRelease">>
| Required<Pick<IssueUpdateStatusDetails, "inCommit">>;
export type Monitor = z.infer<typeof MonitorSchema>;
export type MonitorCheckIn = z.infer<typeof MonitorCheckInSchema>;
export type MonitorStat = z.infer<typeof MonitorStatSchema>;
Expand Down
24 changes: 24 additions & 0 deletions packages/mcp-core/src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,30 @@ export const ParamIgnoreUserWindowMinutes = z
"Optional time window in minutes for ignoreUserCount. If omitted, Sentry counts all future affected users.",
);

export const ParamInRelease = z
.string()
.trim()
.min(1)
.describe(
"Release version to resolve the issue in, such as a version returned by find_releases. Requires status='resolved'. Events from newer releases reopen the issue as a regression; events from older releases do not.",
);

export const ParamInCommit = z
.string()
.trim()
.min(1)
.describe(
"SHA of the commit that fixes the issue. Requires status='resolved' and inCommitRepository. The commit must already be known to Sentry.",
);

export const ParamInCommitRepository = z
.string()
.trim()
.min(1)
.describe(
"Name of the repository that contains inCommit, as it appears in Sentry (for example 'getsentry/sentry').",
);

export const ParamReason = z
.string()
.overwrite((s) => s.replace(/\0/g, ""))
Expand Down
2 changes: 1 addition & 1 deletion packages/mcp-core/src/skillDefinitions.json
Original file line number Diff line number Diff line change
Expand Up @@ -525,7 +525,7 @@
},
{
"name": "update_issue",
"description": "Update a Sentry issue's status or assignment.\n\nUse this to resolve, reopen, assign, unassign, or ignore an issue.\n\n<examples>\n```\nupdate_issue(organizationSlug='my-org', issueId='PROJECT-123', status='resolved')\nupdate_issue(organizationSlug='my-org', issueId='PROJECT-123', assignedTo='user:123456')\nupdate_issue(organizationSlug='my-org', issueId='PROJECT-123', assignedTo=null)\nupdate_issue(organizationSlug='my-org', issueId='PROJECT-123', status='ignored')\nupdate_issue(organizationSlug='my-org', issueId='PROJECT-123', status='ignored', ignoreMode='forever')\nupdate_issue(organizationSlug='my-org', issueId='PROJECT-123', status='ignored', ignoreMode='untilOccurrenceCount', ignoreCount=100, ignoreWindowMinutes=60)\nupdate_issue(organizationSlug='my-org', issueId='PROJECT-123', status='ignored', reason='Ignoring because this is expected noise from the staging deploy')\n```\n</examples>\n\n<hints>\n- Provide `issueUrl` or `organizationSlug` + `issueId`.\n- At least one of `status` or `assignedTo` is required.\n- Omit `assignedTo` to leave assignment unchanged. Pass `null` to unassign; otherwise use `user:ID` or `team:ID_OR_SLUG`.\n- Use the `whoami` tool to find your user ID for self-assignment.\n- Status values: `resolved`, `resolvedInNextRelease`, `unresolved`, `ignored`.\n- `status='ignored'` defaults to `ignoreMode='untilEscalating'`.\n- Ignore modes: `untilEscalating`, `forever`, `forDuration`, `untilOccurrenceCount`, `untilUserCount`.\n- Matching ignore inputs are `ignoreDurationMinutes`, `ignoreCount` + optional `ignoreWindowMinutes`, or `ignoreUserCount` + optional `ignoreUserWindowMinutes`.\n- To switch an already ignored issue between `untilEscalating`, `forever`, and condition-based ignore modes, first set `status='unresolved'`, then ignore it again with the new rule.\n- `reason` is optional. When provided, it will be posted as a comment on the issue's activity feed explaining why the action was taken.\n</hints>",
"description": "Update a Sentry issue's status or assignment.\n\nUse this to resolve, reopen, assign, unassign, or ignore an issue.\n\n<examples>\n```\nupdate_issue(organizationSlug='my-org', issueId='PROJECT-123', status='resolved')\nupdate_issue(organizationSlug='my-org', issueId='PROJECT-123', assignedTo='user:123456')\nupdate_issue(organizationSlug='my-org', issueId='PROJECT-123', assignedTo=null)\nupdate_issue(organizationSlug='my-org', issueId='PROJECT-123', status='ignored')\nupdate_issue(organizationSlug='my-org', issueId='PROJECT-123', status='ignored', ignoreMode='forever')\nupdate_issue(organizationSlug='my-org', issueId='PROJECT-123', status='ignored', ignoreMode='untilOccurrenceCount', ignoreCount=100, ignoreWindowMinutes=60)\nupdate_issue(organizationSlug='my-org', issueId='PROJECT-123', status='ignored', reason='Ignoring because this is expected noise from the staging deploy')\n```\n</examples>\n\n<hints>\n- Provide `issueUrl` or `organizationSlug` + `issueId`.\n- At least one of `status` or `assignedTo` is required.\n- Omit `assignedTo` to leave assignment unchanged. Pass `null` to unassign; otherwise use `user:ID` or `team:ID_OR_SLUG`.\n- Use the `whoami` tool to find your user ID for self-assignment.\n- Status values: `resolved`, `resolvedInNextRelease`, `unresolved`, `ignored`.\n- `status='resolved'` takes optional `inRelease`, or `inCommit` + `inCommitRepository`.\n- `status='ignored'` defaults to `ignoreMode='untilEscalating'`.\n- Ignore modes: `untilEscalating`, `forever`, `forDuration`, `untilOccurrenceCount`, `untilUserCount`.\n- Matching ignore inputs are `ignoreDurationMinutes`, `ignoreCount` + optional `ignoreWindowMinutes`, or `ignoreUserCount` + optional `ignoreUserWindowMinutes`.\n- To switch an already ignored issue between `untilEscalating`, `forever`, and condition-based ignore modes, first set `status='unresolved'`, then ignore it again with the new rule.\n- `reason` is optional. When provided, it will be posted as a comment on the issue's activity feed explaining why the action was taken.\n</hints>",
"requiredScopes": ["event:write"]
},
{
Expand Down
17 changes: 16 additions & 1 deletion packages/mcp-core/src/toolDefinitions.json
Original file line number Diff line number Diff line change
Expand Up @@ -14405,7 +14405,7 @@
{
"name": "update_issue",
"title": "Update Issue",
"description": "Update a Sentry issue's status or assignment.\n\nUse this to resolve, reopen, assign, unassign, or ignore an issue.\n\n<examples>\n```\nupdate_issue(organizationSlug='my-org', issueId='PROJECT-123', status='resolved')\nupdate_issue(organizationSlug='my-org', issueId='PROJECT-123', assignedTo='user:123456')\nupdate_issue(organizationSlug='my-org', issueId='PROJECT-123', assignedTo=null)\nupdate_issue(organizationSlug='my-org', issueId='PROJECT-123', status='ignored')\nupdate_issue(organizationSlug='my-org', issueId='PROJECT-123', status='ignored', ignoreMode='forever')\nupdate_issue(organizationSlug='my-org', issueId='PROJECT-123', status='ignored', ignoreMode='untilOccurrenceCount', ignoreCount=100, ignoreWindowMinutes=60)\nupdate_issue(organizationSlug='my-org', issueId='PROJECT-123', status='ignored', reason='Ignoring because this is expected noise from the staging deploy')\n```\n</examples>\n\n<hints>\n- Provide `issueUrl` or `organizationSlug` + `issueId`.\n- At least one of `status` or `assignedTo` is required.\n- Omit `assignedTo` to leave assignment unchanged. Pass `null` to unassign; otherwise use `user:ID` or `team:ID_OR_SLUG`.\n- Use the `whoami` tool to find your user ID for self-assignment.\n- Status values: `resolved`, `resolvedInNextRelease`, `unresolved`, `ignored`.\n- `status='ignored'` defaults to `ignoreMode='untilEscalating'`.\n- Ignore modes: `untilEscalating`, `forever`, `forDuration`, `untilOccurrenceCount`, `untilUserCount`.\n- Matching ignore inputs are `ignoreDurationMinutes`, `ignoreCount` + optional `ignoreWindowMinutes`, or `ignoreUserCount` + optional `ignoreUserWindowMinutes`.\n- To switch an already ignored issue between `untilEscalating`, `forever`, and condition-based ignore modes, first set `status='unresolved'`, then ignore it again with the new rule.\n- `reason` is optional. When provided, it will be posted as a comment on the issue's activity feed explaining why the action was taken.\n</hints>",
"description": "Update a Sentry issue's status or assignment.\n\nUse this to resolve, reopen, assign, unassign, or ignore an issue.\n\n<examples>\n```\nupdate_issue(organizationSlug='my-org', issueId='PROJECT-123', status='resolved')\nupdate_issue(organizationSlug='my-org', issueId='PROJECT-123', assignedTo='user:123456')\nupdate_issue(organizationSlug='my-org', issueId='PROJECT-123', assignedTo=null)\nupdate_issue(organizationSlug='my-org', issueId='PROJECT-123', status='ignored')\nupdate_issue(organizationSlug='my-org', issueId='PROJECT-123', status='ignored', ignoreMode='forever')\nupdate_issue(organizationSlug='my-org', issueId='PROJECT-123', status='ignored', ignoreMode='untilOccurrenceCount', ignoreCount=100, ignoreWindowMinutes=60)\nupdate_issue(organizationSlug='my-org', issueId='PROJECT-123', status='ignored', reason='Ignoring because this is expected noise from the staging deploy')\n```\n</examples>\n\n<hints>\n- Provide `issueUrl` or `organizationSlug` + `issueId`.\n- At least one of `status` or `assignedTo` is required.\n- Omit `assignedTo` to leave assignment unchanged. Pass `null` to unassign; otherwise use `user:ID` or `team:ID_OR_SLUG`.\n- Use the `whoami` tool to find your user ID for self-assignment.\n- Status values: `resolved`, `resolvedInNextRelease`, `unresolved`, `ignored`.\n- `status='resolved'` takes optional `inRelease`, or `inCommit` + `inCommitRepository`.\n- `status='ignored'` defaults to `ignoreMode='untilEscalating'`.\n- Ignore modes: `untilEscalating`, `forever`, `forDuration`, `untilOccurrenceCount`, `untilUserCount`.\n- Matching ignore inputs are `ignoreDurationMinutes`, `ignoreCount` + optional `ignoreWindowMinutes`, or `ignoreUserCount` + optional `ignoreUserWindowMinutes`.\n- To switch an already ignored issue between `untilEscalating`, `forever`, and condition-based ignore modes, first set `status='unresolved'`, then ignore it again with the new rule.\n- `reason` is optional. When provided, it will be posted as a comment on the issue's activity feed explaining why the action was taken.\n</hints>",
"inputSchema": {
"type": "object",
"properties": {
Expand Down Expand Up @@ -14497,6 +14497,21 @@
"maximum": 10080,
"description": "Optional time window in minutes for ignoreUserCount. If omitted, Sentry counts all future affected users."
},
"inRelease": {
"type": "string",
"minLength": 1,
"description": "Release version to resolve the issue in, such as a version returned by find_releases. Requires status='resolved'. Events from newer releases reopen the issue as a regression; events from older releases do not."
},
"inCommit": {
"type": "string",
"minLength": 1,
"description": "SHA of the commit that fixes the issue. Requires status='resolved' and inCommitRepository. The commit must already be known to Sentry."
},
"inCommitRepository": {
"type": "string",
"minLength": 1,
"description": "Name of the repository that contains inCommit, as it appears in Sentry (for example 'getsentry/sentry')."
},
"reason": {
"type": "string",
"minLength": 1,
Expand Down
Loading
Loading