Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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
2 changes: 2 additions & 0 deletions openspec/changes/issue-linking/.openspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-05-29
121 changes: 121 additions & 0 deletions openspec/changes/issue-linking/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
## Context

`update_issue` currently updates status, ignore state, assignment, and optional reason comments. `get_issue_details` already reads platform external links through `GET /organizations/{org}/issues/{issue}/external-issues/`, but there is no write path.

Sentry has two separate external issue systems:

- Native integrations store `ExternalIssue` plus `GroupLink` and are driven by `GroupIntegrationDetailsEndpoint`. The UI links existing issues with `PUT /organizations/{org}/issues/{issue}/integrations/{integration_id}/`. The required internal fields are provider-specific, but the real providers Sentry supports can be derived from canonical issue URLs in the common case.
- Sentry Apps store `PlatformExternalIssue` and are driven by `SentryAppInstallationExternalIssuesEndpoint`. The endpoint is `POST /sentry-app-installations/{uuid}/external-issues/` with `issueId`, `webUrl`, `project`, and `identifier`. The UUID is an installation UUID, not a user-facing provider id.

The implementation must not use the Sentry App endpoint as a universal Jira/GitHub/Linear solution. It only covers installed Sentry Apps. Native Jira/GitHub-style integrations need the native integration endpoint so provider hooks, comments, sync behavior, and activity are preserved.

## Goals / Non-Goals

**Goals:**

- Add external issue linking to `update_issue` without increasing MCP tool count.
- Support link-only and combined update-plus-link calls.
- Prefer native integration linking when the target provider is a native issue-tracking integration.
- Support Sentry App/platform links when the target is an installed Sentry App, including Linear and Shortcut-style URLs.
- Keep the tool API minimal: link by URL only.
- Validate all link preconditions before mutating the Sentry issue.
- Keep user-facing errors actionable and avoid exposing native integration ids, installation UUIDs, or provider form fields unless needed for diagnostics.

**Non-Goals:**

- Creating new external issues or tickets.
- Unlinking external issues.
- Replacing provider-specific Sentry integration configuration.
- Supporting arbitrary custom Sentry App link schemas.
- Exposing provider-specific form fields as a public MCP API.
- Changing upstream Sentry APIs.

## Decisions

### Use A Minimal Link API

Add only one external linking parameter to `update_issue`:

- `externalIssueUrl`: full URL of the external issue to link.

Rationale: the expected user workflow is "link this Sentry issue to this external issue URL." Native providers need internal fields, but those fields are implementation details that can be parsed from canonical URLs and validated against Sentry's link config.

Alternative considered: exposing `externalIssueIntegrationId`, `externalIssueIdentifier`, `externalIssueProject`, `externalIssueFields`, and `externalIssueKind`. This is more flexible, but it leaks Sentry internals into the MCP API and makes common linking harder for agents. The implementation can still use these concepts internally.

### Resolve Native Integrations Through Sentry's Group Integration List

For native linking, call `GET /organizations/{org}/issues/{issue}/integrations/` to list issue-capable integrations and existing links. Resolve to one integration by:

1. URL parser result for known native providers.
2. Provider host metadata where available, such as GitHub Enterprise, GitLab self-managed, Jira Server, or Azure DevOps instance URLs.
3. Sentry link config validation. For source-control integrations, fetch candidate link configs and choose the integration whose repository choices contain the parsed repository.

If resolution yields zero or multiple integrations, throw `UserInputError` listing candidate provider/name values and ask the user to use a URL that maps to one installed integration or adjust duplicate integration access in Sentry. Do not mutate.

Rationale: users should not need to know Sentry integration ids. Canonical URLs include enough provider-specific context for the supported native providers:

- Jira/Jira Server: `/browse/PROJ-123` -> `externalIssue=PROJ-123`.
- GitHub/GitHub Enterprise: `/{owner}/{repo}/issues/{number}` -> `repo=owner/repo`, `externalIssue=number`.
- GitLab: `/{group}/{project}/-/issues/{iid}` -> `externalIssue=group/project#iid`.
- Bitbucket: `/{workspace}/{repo}/issues/{id}` -> `repo=workspace/repo`, `externalIssue=id`.
- Azure DevOps/VSTS: `/_workitems/edit/{id}` -> `externalIssue=id`.

Alternative considered: search organization integrations globally. The group integration endpoint is better because it already filters to issue-capable integrations and includes existing issue links for that group.

### Build Native Link Payload Internally

After resolving a native integration, fetch link config with `GET /organizations/{org}/issues/{issue}/integrations/{integration_id}/?action=link`.

Build the `PUT` body from the provider parser and config defaults:

1. Start with default values from returned config fields so Sentry's existing backlink/comment defaults are preserved.
2. Override the link target fields parsed from the URL.
3. Validate all config fields marked `required` have non-empty values.
4. For repo/project select fields, verify the parsed repo/project appears in config choices when choices are available.

If required internal fields remain missing, throw `UserInputError` explaining that the URL shape is unsupported for that provider. Do not ask users to provide raw form fields in the first version.

Rationale: this mirrors Sentry's UI behavior while keeping provider-specific forms out of the public MCP API.

Alternative considered: expose an `externalIssueFields` escape hatch. That would support more edge cases, but it is not minimal and effectively asks the LLM/user to understand Sentry's private integration form contract.

### Resolve Sentry App Installations Without Exposing UUIDs

When the URL matches a known Sentry App provider such as `linear` or `shortcut`, call `GET /organizations/{org}/sentry-app-installations/`. Match installations by exact URL-derived app slug. The installation UUID remains internal.

Build the Sentry App payload internally from URL parsers:

- Linear: `linear.app/.../issue/ENG-123/...` -> `project=ENG`, `identifier=ENG-123`.
- Shortcut: `app.shortcut.com/.../story/123/...` -> `project=shortcut`, `identifier=123`.

Rationale: the installation UUID is an implementation detail, and the endpoint only creates platform external links.

Alternative considered: require `externalIssueProject` and `externalIssueIdentifier`. That mirrors the upstream endpoint but makes the MCP API worse; those fields can be inferred well enough for platform links because Sentry does not validate them against the remote provider.

### Mutation Order And Partial Failure

Order handler execution as:

1. Parse issue parameters and fetch current issue.
2. Validate status/assignment/ignore/link inputs.
3. Resolve external link target and build the internal link payload if linking is requested.
4. If no Sentry issue update is needed and only a reason comment is requested, preserve current no-change behavior.
5. Apply Sentry issue status/assignment/ignore update if needed.
6. Apply external link.
7. Post reason comment if requested.
8. Return a combined result.

Resolution and payload validation happen before any mutation. If the issue update succeeds but the external link write fails, return a partial-success message that names the completed Sentry update and the failed link operation.

Rationale: ambiguous or invalid linking must not accidentally change issue state. Once a combined request starts mutating, partial failure needs explicit reporting.

Alternative considered: link first, then update. Existing `update_issue` semantics center on issue updates, and linking may depend on a valid fetched issue id; update first also keeps existing status output anchored on the updated issue.

## Risks / Trade-offs

- URL inference can be wrong for self-hosted or customized integrations -> only infer for recognized URL shapes and exact single matches; otherwise return an unsupported or ambiguous URL error.
- Dynamic provider config can require fields not derivable from a URL -> fail with an actionable unsupported-shape error rather than exposing raw form fields.
- Sentry App and native integration links may both match a provider token -> prefer native integrations for native provider URLs; use Sentry App matching for app-only providers like Linear and Shortcut.
- Combined update-plus-link can partially succeed -> validate before mutation and report link write failures as partial success.
- Adding parameters increases `update_issue` token footprint -> keep descriptions concise and regenerate definitions, then measure token cost.
- The upstream group integration endpoint is deprecated in Sentry source but still powers the UI path -> use it because it is the current behavior source; revisit if Sentry introduces a replacement public endpoint.
37 changes: 37 additions & 0 deletions openspec/changes/issue-linking/proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
## Why

Users can inspect linked external issues through MCP today, but they cannot link a Sentry issue to Jira, GitHub, Linear, or other issue trackers without leaving the agent workflow. This is now a visible source of friction in issue #228, and Sentry already exposes the needed UI-backed APIs with provider-specific constraints.

## What Changes

- Extend `update_issue` so it can link a Sentry issue to an existing external issue by URL, in addition to status, ignore, assignment, and reason-comment updates.
- Support native Sentry issue-tracking integrations such as Jira, GitHub, GitLab, Bitbucket, and Azure DevOps through Sentry's group integration endpoint.
- Support Sentry App/platform external links, including Linear and Shortcut-style app links, through the Sentry App external issue endpoint.
- Keep the user-facing API minimal for linking: require only `externalIssueUrl`.
- Resolve the target integration from URL shape, installed native integrations, Sentry App installations, and Sentry's own link configuration. Do not expose native integration ids, Sentry App installation UUIDs, provider hints, or provider form fields as normal user-facing parameters.
- Fail before mutating the Sentry issue when the requested external link target is ambiguous, unavailable, or missing required fields.
- Report partial success clearly when a Sentry status/assignment update succeeds but the subsequent external link operation fails.
- Keep this in `update_issue`; do not add a new MCP tool.

## Capabilities

### New Capabilities

- `issue-linking`: Tool behavior for linking Sentry issues to existing external issue trackers through native integrations and Sentry Apps.

### Modified Capabilities

- None.

## Impact

- `packages/mcp-core/src/tools/update-issue.ts`: new minimal link input parameters, provider URL parsing, validation, execution ordering, and response formatting.
- `packages/mcp-core/src/api-client/client.ts`, `schema.ts`, and `types.ts`: client methods and schemas for integration issue config/linking and Sentry App installation/linking APIs.
- `packages/mcp-core/src/tools/update-issue.test.ts` and API client tests: coverage for link-only, combined update-and-link, ambiguity, validation, and partial-failure behavior.
- `packages/mcp-server-mocks/src/index.ts`: mock responses for native integration linking and Sentry App external issue links.
- Generated tool definitions after schema/description changes.
- Upstream Sentry APIs used by this change:
- `GET /api/0/organizations/{org}/issues/{issue}/integrations/{integration_id}/?action=link`
- `PUT /api/0/organizations/{org}/issues/{issue}/integrations/{integration_id}/`
- `GET /api/0/organizations/{org}/sentry-app-installations/`
- `POST /api/0/sentry-app-installations/{uuid}/external-issues/`
109 changes: 109 additions & 0 deletions openspec/changes/issue-linking/specs/issue-linking/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
## ADDED Requirements

### Requirement: Link Existing External Issues
The `update_issue` tool SHALL allow linking an existing external issue to a Sentry issue when `externalIssueUrl` is provided.

#### Scenario: Link-only request succeeds
- **WHEN** `update_issue` is called with a valid Sentry issue and `externalIssueUrl`, without `status` or `assignedTo`
- **THEN** the tool links the external issue and returns a response that includes the linked issue identifier and URL

#### Scenario: Combined update and link succeeds
- **WHEN** `update_issue` is called with both issue update parameters and `externalIssueUrl`
- **THEN** the tool updates the Sentry issue, links the external issue, and reports both changes in the response

#### Scenario: Creation parameters are not accepted for linking
- **WHEN** `update_issue` is called with link parameters
- **THEN** the tool treats `externalIssueUrl` as an existing issue link target and does not create a new external ticket

### Requirement: Native Integration Linking
The `update_issue` tool SHALL link native Sentry issue-tracking integrations through Sentry's group integration link endpoint.

#### Scenario: Native integration resolved by URL
- **WHEN** `externalIssueUrl` has a recognized native provider URL shape and maps unambiguously to one issue-capable native integration
- **THEN** the tool uses that integration for the link request

#### Scenario: Native integration resolution is ambiguous
- **WHEN** provider or URL inference matches multiple native integrations
- **THEN** the tool raises a user input error listing the candidate integration names and providers

### Requirement: Native Provider URL Parsing
The `update_issue` tool SHALL parse canonical external issue URLs for supported native providers into Sentry's internal native integration link payload.

#### Scenario: Jira URL is parsed
- **WHEN** `externalIssueUrl` is a Jira or Jira Server issue URL containing `/browse/PROJ-123`
- **THEN** the tool sends `externalIssue=PROJ-123` to the native integration link endpoint

#### Scenario: GitHub URL is parsed
- **WHEN** `externalIssueUrl` is a GitHub or GitHub Enterprise issue URL containing `owner/repo/issues/123`
- **THEN** the tool sends `repo=owner/repo` and `externalIssue=123` to the native integration link endpoint

#### Scenario: GitLab URL is parsed
- **WHEN** `externalIssueUrl` is a GitLab issue URL containing `group/project/-/issues/123`
- **THEN** the tool sends the GitLab project and issue identifier in the format expected by Sentry's GitLab integration

#### Scenario: Bitbucket URL is parsed
- **WHEN** `externalIssueUrl` is a Bitbucket issue URL containing `workspace/repo/issues/123`
- **THEN** the tool sends `repo=workspace/repo` and `externalIssue=123` to the native integration link endpoint

#### Scenario: Azure DevOps URL is parsed
- **WHEN** `externalIssueUrl` is an Azure DevOps or VSTS work item URL containing `/_workitems/edit/123`
- **THEN** the tool sends `externalIssue=123` to the native integration link endpoint

### Requirement: Native Link Config Validation
The `update_issue` tool SHALL use Sentry's native integration link configuration to validate internally constructed link payloads.

#### Scenario: Parsed fields satisfy link config
- **WHEN** Sentry's link config required fields are satisfied by the parsed URL and config defaults
- **THEN** the tool sends the constructed payload to the native integration link endpoint

#### Scenario: Required fields are missing
- **WHEN** Sentry's link config requires fields that cannot be derived from the URL or config defaults
- **THEN** the tool raises a user input error explaining that the URL shape is unsupported for that provider

### Requirement: Sentry App External Issue Linking
The `update_issue` tool SHALL support platform external issue links through installed Sentry Apps without exposing installation UUIDs as user-facing inputs.

#### Scenario: Sentry App link succeeds
- **WHEN** `externalIssueUrl` targets an installed Sentry App provider and the URL contains an inferable identifier
- **THEN** the tool resolves the Sentry App installation internally and creates the external issue link

#### Scenario: Linear URL is parsed
- **WHEN** `externalIssueUrl` is a Linear issue URL containing `/issue/ENG-123`
- **THEN** the tool creates a Sentry App platform external issue with `webUrl` set to the URL, `identifier=ENG-123`, and `project=ENG`

#### Scenario: Shortcut URL is parsed
- **WHEN** `externalIssueUrl` is a Shortcut story URL containing `/story/123`
- **THEN** the tool creates a Sentry App platform external issue with `webUrl` set to the URL, an identifier derived from the story id, and a stable project value

#### Scenario: Sentry App resolution is ambiguous
- **WHEN** the request matches multiple Sentry App installations
- **THEN** the tool raises a user input error listing candidate app names and slugs without listing installation UUIDs

### Requirement: Link Validation Before Mutation
The `update_issue` tool SHALL validate external link target resolution and required link payload fields before applying Sentry issue status, ignore, or assignment changes.

#### Scenario: Link validation fails before issue update
- **WHEN** a combined update-and-link request has an invalid, unsupported, or ambiguous `externalIssueUrl`
- **THEN** the tool raises a user input error and does not call the Sentry issue update endpoint

#### Scenario: No action provided
- **WHEN** `update_issue` is called without status, assignment, ignore, reason-only no-op, or `externalIssueUrl`
- **THEN** the tool raises a user input error explaining the accepted actions

### Requirement: Partial Success Reporting
The `update_issue` tool SHALL clearly report partial success when an issue update succeeds but external issue linking fails afterward.

#### Scenario: Link write fails after issue update
- **WHEN** a combined update-and-link request successfully updates the Sentry issue but the external link write fails
- **THEN** the tool response states that the issue update succeeded and the external link failed, including the link failure message

### Requirement: Existing Link Visibility
The `update_issue` tool SHALL report linked external issue changes consistently with existing issue detail external link formatting.

#### Scenario: Link result has display fields
- **WHEN** the external link API returns display name, provider/service type, and URL
- **THEN** the response includes those fields in the changes made section

#### Scenario: Link result lacks display fields
- **WHEN** the external link API omits optional display fields
- **THEN** the response falls back to the requested external issue identifier and URL
Loading
Loading