Skip to content
Open
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
60 changes: 43 additions & 17 deletions packages/docs/src/content/docs/extend/linear-plugin.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ related:
- /operate/security-hardening/
---

Use the Linear plugin to find, create, update, comment on, and triage Linear issues from Slack. Each user connects their own Linear account through Linear's hosted MCP server.
Use the Linear plugin to find, create, update, comment on, and triage Linear issues from Slack. A workspace admin installs one Linear OAuth app. Junior uses that app connection instead of asking each user to connect Linear.

Optional webhooks let Junior publish `issue.created` resource events for subscriptions and event tasks. User MCP OAuth and webhook ingress stay separate.
Optional webhooks let Junior publish `issue.created` resource events for watches and event tasks. OAuth and webhooks use separate secrets.

## Install

Expand All @@ -28,16 +28,19 @@ import { linearPlugin } from "@sentry/junior-linear";
export const plugins = defineJuniorPlugins([linearPlugin()]);
```

Register `linearPlugin()` so Junior loads the webhook route.
Register `linearPlugin()` so Junior loads the Linear tools and webhook route.

## Auth model
## Connect Linear

- No `LINEAR_API_KEY`, shared workspace token, or custom OAuth app is required for the default setup.
- Each user completes Linear's MCP OAuth flow the first time Junior calls a Linear MCP tool on their behalf.
- Junior sends the authorization link privately, then resumes the same thread automatically after the user authorizes.
- Webhooks use a separate Linear webhook secret. They do not use the user's MCP OAuth grant.
1. Create an OAuth app in Linear.
2. Set its callback URL to `https://<junior-host>/api/oauth/callback/linear`.
3. Give it the `read,write` scopes.
4. Set `LINEAR_CLIENT_ID` and `LINEAR_CLIENT_SECRET` in Junior.
5. Ask Junior to use Linear. A workspace admin must complete the install once.

Junior uses Linear's hosted MCP tools for reads and writes. When an issue is created through that path, Junior links it to the current conversation.
Junior requests `actor=app`. Linear records changes as made by the Junior app. The app can access every team available to it in the connected workspace. Junior uses the same app connection for requests from conversations, scheduled tasks, and event tasks.

When Junior creates an issue, it links the issue to the current conversation. If Linear rejects the refresh token, an admin must install the app again.

## Config

Expand Down Expand Up @@ -71,6 +74,28 @@ Default project for issue creation when a request does not name one. Use it only

### Environment variables

<details class="plugin-config">
<summary><code>LINEAR_CLIENT_ID</code></summary>

Client ID for the Linear OAuth app.

- **Define:** Set `LINEAR_CLIENT_ID` in the deployment environment
- **Required:** Yes
- **Environment override:** `LINEAR_CLIENT_ID`

</details>

<details class="plugin-config">
<summary><code>LINEAR_CLIENT_SECRET</code></summary>

Client secret for the Linear OAuth app.

- **Define:** Set `LINEAR_CLIENT_SECRET` in the deployment environment
- **Required:** Yes
- **Environment override:** `LINEAR_CLIENT_SECRET`

</details>

<details class="plugin-config">
<summary><code>LINEAR_WEBHOOK_SECRET</code></summary>

Expand Down Expand Up @@ -143,22 +168,23 @@ Optional `match` values come from the resource type. For Linear issue and team e

## Verify

**OAuth:** Ask Junior to create or update a real Linear issue, complete the private authorization flow, and confirm the issue key or URL returns in the same thread.
**OAuth:** Ask Junior to create or update a real Linear issue. If Linear is not connected, have a workspace admin complete the install. Confirm that Junior returns the issue key or URL in the same thread.

**Webhooks:** Create an event task for a team key, then create a test issue in that team. You can also create an issue-scoped task with `match.teamKey` set to the same team key and confirm non-matching teams do not fire.

## Security

- Junior stores user MCP grants and does not include them in model input.
- Webhooks use the Linear webhook signing secret, not user MCP OAuth.
- Issue title, description, and other payload text are untrusted event content.
- Junior stores the app tokens outside the model and sandbox.
- Webhooks use `LINEAR_WEBHOOK_SECRET`, not the OAuth app tokens.
- Issue titles, descriptions, and other webhook text are untrusted input.

## Failure modes

- **No auth prompt or no resume:** Retry the Linear request and complete the private authorization flow when prompted.
- **Wrong team or project target:** Include the team name, project name, or existing Linear issue key explicitly in the Slack request.
- **Duplicate or low-signal tickets:** Give Junior the core problem, impact, and any supporting URLs from the thread so it can create a grounded issue instead of a vague summary.
- **Permission failures after connect:** The user's Linear account may not have access to that team, project, or issue. Retry with a resource the user can access.
- **Linear is not connected:** Check `LINEAR_CLIENT_ID` and `LINEAR_CLIENT_SECRET`, then have a workspace admin install the app.
- **The connection expired:** Have a workspace admin install the app again.
- **Wrong team or project:** Name the team, project, or issue key in the Slack request.
- **Duplicate or vague tickets:** Give Junior the core problem, impact, and useful links from the thread.
- **Permission failure:** Confirm that the installed app can access the team, project, or issue.
- **Webhooks are ignored:** Check `LINEAR_WEBHOOK_SECRET`, confirm the webhook points at `/api/webhooks/linear`, and confirm a matching subscription or event task exists.
- **Event task stays unavailable:** Resource events stay disabled until `LINEAR_WEBHOOK_SECRET` is set and Junior is redeployed.

Expand Down
40 changes: 15 additions & 25 deletions packages/junior-linear/README.md
Original file line number Diff line number Diff line change
@@ -1,41 +1,31 @@
# @sentry/junior-linear

`@sentry/junior-linear` adds Linear issue workflows to Junior through Linear's hosted MCP server.
`@sentry/junior-linear` lets Junior read and update Linear through its GraphQL API. It also supports issue webhooks.

Install it alongside `@sentry/junior`:
Install it alongside `@sentry/junior`, then register `linearPlugin()` in `plugins.ts`.

```bash
pnpm add @sentry/junior @sentry/junior-linear
```
## OAuth app

Then add the plugin to the set exported from `plugins.ts`:
Create a Linear OAuth app with:

```ts title="plugins.ts"
import { defineJuniorPlugins } from "@sentry/junior";
import { linearPlugin } from "@sentry/junior-linear";
- Callback: `https://<junior-host>/api/oauth/callback/linear`
- Scopes: `read,write`
- Environment variables: `LINEAR_CLIENT_ID` and `LINEAR_CLIENT_SECRET`

export const plugins = defineJuniorPlugins([linearPlugin()]);
```
Junior requests `actor=app`. A workspace admin installs the app once. Junior then uses that app connection for requests from conversations, scheduled tasks, and event tasks. It does not ask each user to connect Linear.

This package does not require a shared `LINEAR_API_KEY` or a custom OAuth app for the default setup. Each user connects their own Linear account the first time Junior calls a Linear MCP tool. Junior sends the authorization link privately and resumes the same Slack thread automatically after the user authorizes.
Linear records changes as made by the Junior app. The app can access every team available to it in the connected workspace. If Linear rejects the refresh token, an admin must install the app again.

Linear operations use Linear's hosted MCP tools directly. When an issue is created through that path, Junior links it to the current conversation.
The tools can read and search issues, create and update issues, add comments, and list teams, projects, and workflow states. Junior links created issues to the current conversation.

## Webhooks

To run watches or event tasks when Linear issues are created:

1. Set `LINEAR_WEBHOOK_SECRET` to the Linear webhook signing secret.
1. Set `LINEAR_WEBHOOK_SECRET`.
2. Create a Linear webhook for the `Issue` resource at `https://<junior-host>/api/webhooks/linear`.
3. Redeploy Junior.

The plugin verifies the `Linear-Signature` header and publishes `issue.created` for the issue identifier and the team key. Team event tasks use the Linear team key, such as `SRE`. Issue and team watches also accept an optional `match.teamKey` filter on trusted event data.

Optional: set channel defaults when a Slack thread usually routes work to the same Linear destination:

```bash
jr-rpc config set linear.team Platform
jr-rpc config set linear.project "Cross-team reliability"
```

These defaults are only fallbacks. If the user names a different team or project in the request, Junior should follow the explicit request instead.
The plugin verifies `Linear-Signature` and publishes `issue.created` for the issue identifier and team key.

Full setup guide: https://junior.sentry.dev/extend/linear-plugin/
You can set conversation defaults with `linear.team` and `linear.project`. An explicit team or project in the request always wins.
12 changes: 6 additions & 6 deletions packages/junior-linear/skills/linear/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: linear
description: Manage Linear issues through Linear's hosted MCP server. Use when users ask to create a Linear ticket, update a Linear issue, add a Linear comment, move work between states, assign work, or look up Linear issue, team, or project details from Slack context.
description: Manage Linear issues through Junior's Linear tools. Use when users ask to create a Linear ticket, update a Linear issue, add a Linear comment, move work between states, assign work, or look up Linear issue, team, or project details from Slack context.
---

# Linear Operations
Expand Down Expand Up @@ -32,7 +32,7 @@ Load references conditionally based on the request:

2. Prepare the Linear operation:

- Prefer a short read/search step before mutating when you need to confirm the existing issue, team, project, or workflow state.
- Read or search first when you need to confirm the issue, team, project, or workflow state.

3. Draft issue content (create or substantial rewrite):

Expand All @@ -57,7 +57,7 @@ Attribute the reporter by name when clear from the thread (e.g. "Raised by Alice
- Use only Linear's standard priority levels: `low`, `medium`, `high`, `urgent`.
- Set project, labels, cycle, estimate, or assignee only when the user asked for them or the thread makes them clear.

5. Verify draft before mutating:
5. Check the draft before writing:

- Title length ≤ 60 characters.
- Delegated-action footer is the last line when applicable, using the action actor's real name, not the reporter's name unless they are the same person.
Expand All @@ -69,13 +69,13 @@ If any gate fails, revise and re-check before calling the Linear create/update t

6. Execute:

- Create or update issues with Linear's live hosted MCP tools. Discover the current create/update tool and copy only fields justified by its live schema.
- For updates, prefer partial changes over full rewrites. Fetch current issue state first if the mutation could overwrite structured fields or duplicate an existing comment.
- Use the `linear_*` tools. Look up team, project, workflow state, and issue IDs before writes.
- For updates, change only the requested fields. Fetch the issue first if an update could overwrite fields or repeat a comment.
- Check for duplicates silently before creating a new issue when the request appears related to existing work.

7. Report the result:

- Return the canonical Linear issue URL or key and what changed.
- Return the Linear issue URL or key and what changed.
- Report issue type when you created a new issue and it materially clarifies the outcome.

## Guardrails
Expand Down
53 changes: 28 additions & 25 deletions packages/junior-linear/skills/linear/references/api-surface.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,39 @@

Use this reference for any Linear operation.

## Provider capabilities
## Tools

Linear's hosted MCP server is intended for authenticated remote MCP access to Linear data.
The current public docs describe support for finding, creating, and updating objects such as issues, projects, and comments.
- `linear_getIssue`: get one issue by UUID or identifier.
- `linear_searchIssues`: find issues before a create or update.
- `linear_createIssue`: create an issue as the installed Junior app.
- `linear_updateIssue`: update selected issue fields as the installed Junior app.
- `linear_createComment`: add a comment as the installed Junior app.
- `linear_listTeams`: resolve a team UUID.
- `linear_listProjects`: resolve an active project UUID.
- `linear_listWorkflowStates`: resolve one team's workflow state UUID.

The tools call Linear's GraphQL API with the installed Junior app. They do not use the requesting user's Linear account.

## Linear issue model constraints

- Every issue belongs to exactly one team.
- A new issue requires a title and a status; all other properties are optional.
- Workflow states are team-specific. The common default order is `Backlog > Todo > In Progress > Done > Canceled`, but teams can customize names and ordering.
- Priority is optional and limited to `low`, `medium`, `high`, or `urgent`.
- Labels can be workspace-scoped or team-scoped.
- Estimates are optional and team-configured.
- A new issue requires a title and team UUID.
- Workflow states are team-specific. Never infer a state UUID from a name.
- Linear priorities use numeric API values: `0` no priority, `1` urgent, `2` high, `3` medium, `4` low.
- Resolve project, state, team, and issue identifiers before a write.

## Operation patterns

| Intent | Minimum tool pattern |
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| Inspect an issue | Resolve the issue by key, URL, or search query, then fetch current state before answering. |
| Create an issue | Confirm the team first, then create via the live hosted MCP tools with grounded title/body content and only fields justified by the current schema. |
| Update fields | Fetch current issue state first, then mutate via the live hosted MCP tools with only the requested fields. |
| Add a comment | Resolve the exact issue first, then add a concise comment with durable links and next steps. |
| Move state or assign | Read the current issue and team workflow first when state, workflow, or assignee ambiguity could cause the wrong mutation. |
| Check for duplicates | Search for an existing matching issue before opening a new one when the request appears related to ongoing work. |

## Content expectations

- Translate Slack-thread wording into stable product or engineering language.
- Preserve material links already present in the conversation, such as Sentry, GitHub, docs, repro, or dashboard URLs.
- Keep provenance concise. Mention Slack origin only when it helps future readers understand why the issue exists.
- Treat team, status, labels, estimate, cycle, and project as structured properties, not prose-only body content, when those fields are available and the values are actually known.
- Prefer partial updates over full rewrites.
- Label assumptions clearly when the thread leaves important details uncertain.
- Inspect: resolve the issue, then fetch current state.
- Create: search for duplicates, resolve the team, then call `linear_createIssue`.
- Update: fetch current state, then send only requested fields to `linear_updateIssue`.
- Comment: resolve the exact issue before `linear_createComment`.
- Move state: list the team's states before updating `stateId`.

## OAuth

- One workspace admin installs the OAuth app with `actor=app`.
- Junior stores and refreshes the app tokens.
- Requests from conversations, scheduled tasks, and event tasks use the same app connection.
- Linear records changes as made by the Junior app, not the Slack user.
- If Linear rejects the refresh token, an admin must install the app again.
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Use these patterns to shape concrete Linear requests.
## 6. Reassign work or change ownership

- Resolve the issue and confirm the target assignee when names are ambiguous.
- Keep the mutation small. Do not rewrite unrelated fields.
- Change only the requested fields.
- Preserve the current project, labels, and workflow state unless the user asked to change them too.

## 7. Tighten an existing issue description
Expand All @@ -60,9 +60,9 @@ Use these patterns to shape concrete Linear requests.

## 10. Mark work as a duplicate

- Search for the canonical destination issue first.
- If the MCP tool supports duplicate relationships directly, use that instead of only posting a comment.
- If the workflow exposes a dedicated duplicate status, prefer it; otherwise expect duplicate handling to land in the team's canceled category.
- Search for the issue that should remain open.
- Add a comment that links the duplicate to that issue.
- Use the team's duplicate state if one exists. Otherwise use its canceled state.

## 11. When a user asks to set channel defaults for a Linear-heavy Slack thread

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,11 @@ Default to `task` when the request does not clearly describe a defect or a net-n
## Linear-specific field guidance

- Every new issue must belong to a single team. Resolve that before creating the issue.
- If the request maps to a known team template and the active MCP tools expose template-based creation, prefer the template so the team's default properties are applied consistently.
- Do not invent a custom status name. Read the team's actual workflow states first when a non-default status is needed.
- Priority stays within Linear's standard levels: `low`, `medium`, `high`, `urgent`.
- Estimates are team-configured. Set one only when the thread provides a clear value or the team context makes the scale unambiguous.
- Labels may be workspace- or team-scoped. Reuse an existing matching label instead of introducing near-duplicates.
- If the tool exposes structured link attachments, attach important URLs there and keep the prose body focused on interpretation.
- Put important URLs in the issue body near the text they support.

## Duplicate handling

Expand Down
Loading
Loading