# linear-mcp
A [Linear](https://linear.app) MCP server for Dedalus.
## Prerequisites
- Python 3.11+
- [`uv`](https://docs.astral.sh/uv/)
- A Linear workspace
## Setup
```bash
cd linear-mcp
cp .env.example .env # then fill in your values
uv sync
```
### Environment variables
Linear uses OAuth2 for authentication. The Dedalus platform (DAuth) handles the
OAuth flow. The MCP server declares the secret name it needs (e.g. `LINEAR_ACCESS_TOKEN`).
OAuth provider configuration (consumed by the Dedalus platform):
| Variable | Description |
| --- | --- |
| `OAUTH_ENABLED` | `true` |
| `OAUTH_AUTHORIZE_URL` | `https://linear.app/oauth/authorize` |
| `OAUTH_TOKEN_URL` | `https://api.linear.app/oauth/token` |
| `OAUTH_CLIENT_ID` | Your Linear OAuth app client ID |
| `OAUTH_CLIENT_SECRET` | Your Linear OAuth app client secret |
| `OAUTH_SCOPES_AVAILABLE` | `read,write,issues:create,comments:create` |
| `OAUTH_BASE_URL` | `https://api.linear.app` |
Dedalus client configuration (for `_client.py` testing):
| Variable | Description |
| --- | --- |
| `DEDALUS_API_KEY` | Your Dedalus API key (`dsk_*`) |
| `DEDALUS_API_URL` | Defaults to `https://api.dedaluslabs.ai` |
| `DEDALUS_AS_URL` | Defaults to `https://as.dedaluslabs.ai` |
## Run the server
```bash
uv run src/main.py
```
## Test locally
```bash
uv run src/_client.py
```
This opens an interactive agent loop. On first use, DAuth will prompt you to
complete the Linear OAuth flow in your browser.
## Lint and typecheck
```bash
uv run --group lint ruff format src/
uv run --group lint ruff check src/ --fix
uv run --group lint ty check src/
```
## Available tools
| Tool | R/W | Description |
| ------------------------- | --- | ------------------------------------------ |
| `linear_get_issue` | R | Get issue by ID or identifier (ENG-123) |
| `linear_list_issues` | R | List issues with filters |
| `linear_create_issue` | W | Create a new issue |
| `linear_update_issue` | W | Update an existing issue |
| `linear_list_comments` | R | List comments on an issue |
| `linear_create_comment` | W | Add a comment to an issue |
| `linear_get_project` | R | Get project by ID |
| `linear_list_projects` | R | List projects |
| `linear_create_project` | W | Create a new project |
| `linear_update_project` | W | Update an existing project |
| `linear_list_cycles` | R | List cycles for a team |
| `linear_get_cycle` | R | Get a cycle by ID |
| `linear_active_cycle` | R | Get the current active cycle for a team |
| `linear_list_teams` | R | List all teams |
| `linear_get_team` | R | Get a team by ID |
| `linear_list_team_states` | R | List workflow states (statuses) for a team |
| `linear_whoami` | R | Get authenticated user profile |
| `linear_list_users` | R | List workspace members |
| `linear_list_labels` | R | List labels |
| `linear_create_label` | W | Create a new label |
| `linear_search_issues` | R | Full-text search across issues |
**21 tools** (14 read, 7 write)
## Architecture
Linear uses a single GraphQL endpoint (`POST /graphql`) for all operations. The
request layer in `src/linear/request.py` dispatches queries through Dedalus's HTTP
enclave, which injects OAuth credentials transparently.
```plaintext
src/
├── linear/
│ ├── config.py # Connection definition (OAuth)
│ ├── request.py # GraphQL dispatch + coercion helpers
│ └── types.py # Typed dataclass models
├── tools/
│ ├── issues.py # Issue CRUD
│ ├── comments.py # Comment operations
│ ├── projects.py # Project CRUD
│ ├── cycles.py # Cycle queries
│ ├── teams.py # Team + workflow states
│ ├── users.py # User queries
│ ├── labels.py # Label operations
│ └── search.py # Issue search
├── server.py # MCPServer setup
├── main.py # Entry point
└── _client.py # Interactive agent loop (DAuth)
```
## Notes
- Linear's API is **GraphQL-only**. Every tool dispatches a GraphQL query or mutation.
- Workflow states (statuses) vary per team. Use `linear_list_team_states` to discover
valid state IDs before creating or transitioning issues.
- Issue identifiers (e.g. `ENG-123`) can be used interchangeably with UUIDs in most tools.
- Priority values: 0 = No priority, 1 = Urgent, 2 = High, 3 = Medium, 4 = Low.
- Archived resources are hidden by default. Some tools accept `include_archived` to surface them.
- Authentication uses OAuth2 via DAuth. Personal API keys are **not** supported in this server.