Linear MCP Server

Linear MCP Server

Enables AI agents to manage Linear workspace resources such as issues, projects, teams, cycles, and comments via Streamable HTTP MCP, with LLM-optimized tools and batch operations.

Category
访问服务器

README

Linear MCP Server

Streamable HTTP MCP server for Linear — manage issues, projects, teams, cycles, and comments.

Author: overment

[!WARNING] You connect this server to your MCP client at your own responsibility. Language models can make mistakes, misinterpret instructions, or perform unintended actions. Review tool outputs, verify changes (e.g., with list_issues), and prefer small, incremental writes.

The HTTP/OAuth layer is designed for convenience during development, not production-grade security. If deploying remotely, harden it: proper token validation, secure storage, TLS termination, strict CORS/origin checks, rate limiting, audit logging, and compliance with Linear's terms.

Comparison

Below is a comparison between the official Linear MCP (top) and this MCP (bottom).

<img src="docs/comparison-hd.gif" width="800" />

Notice

This repo works in two ways:

  • As a Node/Hono server for local workflows
  • As a Cloudflare Worker for remote interactions

For production Cloudflare deployments, see Remote Model Context Protocol servers (MCP).

Motivation

I'm a big fan of Linear and use it daily. At the time of writing, the official MCP server isn't fully optimized for language models. This server is built with key goals in mind:

  • Let LLMs find Team IDs, Project IDs, Status IDs, or User IDs in a single action (workspace_metadata) instead of multiple tool calls
  • Include clear MCP instructions and schema descriptions that cut API jargon
  • Map API responses into human-readable feedback — useful for both the LLM and user
  • Provide hints and suggestions for next steps, plus tips on recovering from errors
  • Support batch actions (e.g., create_issues instead of create_issue) so the LLM can perform multiple steps in one go
  • Prefetch related values — return both a status ID and actual status name for an issue
  • Hide tools not enabled in a given team's settings (like list_cycles) to reduce noise

In short, it's not a direct mirror of Linear's API — it's tailored so AI agents know exactly how to use it effectively.

Features

  • Issues — List, search, create, update (state, assignee, labels, priority, etc.)
  • Projects — List, create, update projects
  • Teams & Users — Discover workspace structure
  • Cycles — Browse sprint/cycle planning
  • Comments — List and add comments on issues
  • OAuth 2.1 — Secure PKCE flow with RS token mapping
  • Dual Runtime — Node.js/Bun or Cloudflare Workers
  • Production Ready — Encrypted token storage, rate limiting, multi-user support

Design Principles

  • LLM-friendly: Tools are simplified and unified, not 1:1 API mirrors
  • Batch-first: Create/update operations accept arrays to minimize tool calls
  • Discovery-first: workspace_metadata returns all IDs needed for subsequent calls
  • Clear feedback: Every response includes human-readable summaries with diffs

Installation

Prerequisites: Bun, Node.js 24+, Linear account. For remote: a Cloudflare account.

Ways to Run (Pick One)

  1. Local (API key) — Fastest start
  2. Local + OAuth — For multi-user or token refresh
  3. Cloudflare Worker (wrangler dev) — Local Worker testing
  4. Cloudflare Worker (deploy) — Remote production

1. Local (API Key) — Quick Start

Run the server with your Linear Personal Access Token from Settings → Security.

git clone <repo>
cd linear-mcp
bun install
cp env.example .env

Edit .env:

PORT=3000
AUTH_STRATEGY=bearer
BEARER_TOKEN=lin_api_xxxx  # Your Linear API key
bun dev
# MCP: http://127.0.0.1:3000/mcp

Connect to your MCP client:

Claude Desktop / Cursor:

{
  "mcpServers": {
    "linear": {
      "command": "bunx",
      "args": [
        "mcp-remote",
        "http://localhost:3000/mcp",
        "--header",
        "Authorization: Bearer ${LINEAR_API_KEY}"
      ]
    }
  }
}

2. Local + OAuth

More advanced — requires creating an OAuth application in Linear.

  1. Create an OAuth app at Linear Settings → API → OAuth Applications
  2. Set redirect URIs:
    http://127.0.0.1:3001/oauth/callback
    alice://oauth/callback
    
  3. Copy Client ID and Secret
cp env.example .env

Edit .env:

PORT=3000
AUTH_ENABLED=true

PROVIDER_CLIENT_ID=your_client_id
PROVIDER_CLIENT_SECRET=your_client_secret

OAUTH_SCOPES=read write
OAUTH_REDIRECT_URI=alice://oauth/callback
OAUTH_REDIRECT_ALLOWLIST=alice://oauth/callback,http://127.0.0.1:3001/oauth/callback
bun dev
# MCP: http://127.0.0.1:3000/mcp
# OAuth: http://127.0.0.1:3001

Tip: The Authorization Server runs on PORT+1.

Claude Desktop:

{
  "mcpServers": {
    "linear": {
      "command": "bunx",
      "args": ["mcp-remote", "http://localhost:3000/mcp", "--transport", "http-only"],
      "env": { "NO_PROXY": "127.0.0.1,localhost" }
    }
  }
}

RS-Only Mode (Recommended for Remote)

Enable these flags to require RS-minted bearer tokens:

AUTH_REQUIRE_RS=true
AUTH_ALLOW_DIRECT_BEARER=false

When enabled, requests without Authorization or with non-mapped tokens receive 401 with WWW-Authenticate so OAuth can start.


3. Cloudflare Worker (Local Dev)

bun x wrangler dev --local | cat

With OAuth:

bun x wrangler secret put PROVIDER_CLIENT_ID
bun x wrangler secret put PROVIDER_CLIENT_SECRET
bun x wrangler dev --local | cat

Endpoint: http://127.0.0.1:8787/mcp


4. Cloudflare Worker (Deploy)

  1. Create KV namespace:
bun x wrangler kv:namespace create TOKENS
  1. Update wrangler.toml with KV namespace ID

  2. Set secrets:

bun x wrangler secret put PROVIDER_CLIENT_ID
bun x wrangler secret put PROVIDER_CLIENT_SECRET

# Generate encryption key (32-byte base64url):
openssl rand -base64 32 | tr -d '=' | tr '+/' '-_'
bun x wrangler secret put RS_TOKENS_ENC_KEY

Note: RS_TOKENS_ENC_KEY encrypts OAuth tokens stored in KV (AES-256-GCM).

  1. Update redirect URI and allowlist in wrangler.toml

  2. Add Workers URL to your Linear OAuth app's redirect URIs

  3. Deploy:

bun x wrangler deploy

Endpoint: https://<worker-name>.<account>.workers.dev/mcp


Client Configuration

MCP Inspector (quick test):

bunx @modelcontextprotocol/inspector
# Connect to: http://localhost:3000/mcp

Claude Desktop / Cursor:

{
  "mcpServers": {
    "linear": {
      "command": "bunx",
      "args": ["mcp-remote", "http://127.0.0.1:3000/mcp", "--transport", "http-only"],
      "env": { "NO_PROXY": "127.0.0.1,localhost" }
    }
  }
}

For Cloudflare, replace URL with https://<worker-name>.<account>.workers.dev/mcp.


Tools

workspace_metadata

Discover workspace entities and IDs. Call this first when you don't know IDs.

// Input
{
  include?: ("profile"|"teams"|"workflow_states"|"labels"|"projects"|"favorites")[];
  teamIds?: string[];
  project_limit?: number;
  label_limit?: number;
}

// Output
{
  viewer: { id, name, email, displayName, timezone };
  teams: Array<{ id, key, name, cyclesEnabled, defaultIssueEstimate }>;
  workflowStatesByTeam: Record<teamId, Array<{ id, name, type }>>;
  labelsByTeam: Record<teamId, Array<{ id, name, color }>>;
  projects: Array<{ id, name, state, teamId, leadId, targetDate }>;
}

list_issues

Search and filter issues with powerful GraphQL filtering.

// Input
{
  teamId?: string;
  projectId?: string;
  filter?: IssueFilter;        // GraphQL-style: { state: { type: { eq: "started" } } }
  q?: string;                  // Title search tokens
  keywords?: string[];         // Alternative to q
  includeArchived?: boolean;
  orderBy?: "updatedAt" | "createdAt" | "priority";
  limit?: number;              // 1-100
  cursor?: string;             // Pagination
  fullDescriptions?: boolean;
}

// Output
{
  items: Array<{
    id, identifier, title, description?,
    stateId, stateName, projectId?, projectName?,
    assigneeId?, assigneeName?, labels[], dueDate?, url
  }>;
  cursor?: string;
  nextCursor?: string;
  limit: number;
}

create_issues

Create multiple issues in one call.

{
  items: Array<{
    teamId: string;
    title: string;
    description?: string;
    stateId?: string;
    labelIds?: string[];
    assigneeId?: string;       // Defaults to current viewer
    projectId?: string;
    priority?: number;         // 0-4
    estimate?: number;
    dueDate?: string;          // YYYY-MM-DD
    parentId?: string;
  }>;
  parallel?: boolean;
}

update_issues

Update issues in batch (state, labels, assignee, metadata).

{
  items: Array<{
    id: string;
    title?: string;
    description?: string;
    stateId?: string;
    labelIds?: string[];
    addLabelIds?: string[];     // Incremental add
    removeLabelIds?: string[];  // Incremental remove
    assigneeId?: string;
    projectId?: string;
    priority?: number;
    estimate?: number;
    dueDate?: string;
    archived?: boolean;
  }>;
  parallel?: boolean;
}

Other Tools

  • get_issues — Fetch issues by ID (batch)
  • list_projects / create_projects / update_projects — Manage projects
  • list_teams / list_users — Discover workspace structure
  • list_cycles — Browse team cycles (if enabled)
  • list_comments / add_comments — Issue comments

Examples

1. List my issues due today

// First, get viewer info
{ "name": "workspace_metadata", "arguments": { "include": ["profile"] } }

// Then list issues assigned to me
{
  "name": "list_issues",
  "arguments": {
    "assignedToMe": true,
    "filter": { "dueDate": { "eq": "2025-08-15" } },
    "orderBy": "updatedAt",
    "limit": 20
  }
}

Response:

Issues: 1 (limit 20). Preview:
- [OVE-142 — Publish release notes](https://linear.app/.../OVE-142) — state Done; due 2025-08-15

2. Create an issue and add it to a project

// Discover IDs first
{ "name": "workspace_metadata", "arguments": { "include": ["teams", "projects"] } }

// Create (assigneeId defaults to current viewer)
{
  "name": "create_issues",
  "arguments": {
    "items": [{
      "title": "Release Alice v3.8",
      "teamId": "TEAM_ID",
      "projectId": "PROJECT_ID",
      "dueDate": "2025-08-18",
      "priority": 2
    }]
  }
}

Response:

Created issues: 1 / 1. OK: item[0].
Next: Use list_issues to verify details.

3. Batch update: reschedule + mark as Done

// Resolve workflow states first
{ "name": "workspace_metadata", "arguments": { "include": ["workflow_states"], "teamIds": ["TEAM_ID"] } }

// Update both issues
{
  "name": "update_issues",
  "arguments": {
    "items": [
      { "id": "RELEASE_UUID", "dueDate": "2025-08-16" },
      { "id": "MEETING_UUID", "stateId": "DONE_STATE_ID" }
    ]
  }
}

Response:

Updated issues: 2 / 2. OK: RELEASE_UUID, MEETING_UUID
- [OVE-231 — Release Alice v3.8] Due date: 2025-08-18 → 2025-08-16
- [OVE-224 — Team meeting] State: Current → Done

HTTP Endpoints

Endpoint Method Purpose
/mcp POST MCP JSON-RPC 2.0
/mcp GET SSE stream (Node.js only)
/health GET Health check
/.well-known/oauth-authorization-server GET OAuth AS metadata
/.well-known/oauth-protected-resource GET OAuth RS metadata

OAuth (PORT+1):

  • GET /authorize — Start OAuth flow
  • GET /oauth/callback — Provider callback
  • POST /token — Token exchange
  • POST /revoke — Revoke tokens

Development

bun dev           # Start with hot reload
bun run typecheck # TypeScript check
bun run lint      # Lint code
bun run build     # Production build
bun start         # Run production

Testing

The project uses a two-layer testing strategy:

Unit Tests (Mock)

Fast tests using mocked Linear API responses. Tests all logic, validation, and edge cases without network calls.

bun test              # Run all unit tests (~4 seconds)
bun run test:watch    # Watch mode
bun run test:coverage # With coverage report

Integration Tests (Live API)

Real API tests that verify the actual Linear connection works. Creates issues in a "Tests" team and cleans up after.

Setup:

  1. Create a team named "Tests" in your Linear workspace
  2. Add your Linear API key to .env:
    PROVIDER_API_KEY=lin_api_xxxx
    

Run:

bun run test:integration  # ~45 seconds

What it tests:

Category Tests Purpose
CRUD 5 Create, Read, Update, List operations
Filtering 3 Priority, title search, workflow state filters
Pagination 2 Limit and cursor behavior
Errors 3 Non-existent issues, invalid filters
Rate Limiting 2 Rapid requests, batch operations

Testing Strategy

Layer Speed Purpose
Unit/Mock ⚡️ Fast Logic correctness, validation, edge cases
Integration 🐢 Slow API contract, real data mapping
TypeScript 🛡️ Build SDK type alignment

Run unit tests on every change. Run integration tests before releases or after SDK upgrades.


Architecture

src/
├── shared/
│   ├── tools/
│   │   └── linear/         # Tool definitions (work in Node + Workers)
│   │       ├── workspace-metadata.ts
│   │       ├── list-issues.ts
│   │       ├── create-issues.ts
│   │       ├── update-issues.ts
│   │       ├── projects.ts
│   │       ├── comments.ts
│   │       ├── cycles.ts
│   │       └── shared/     # Formatting, validation, snapshots
│   ├── oauth/              # OAuth flow (PKCE, discovery)
│   └── storage/            # Token storage (file, KV, memory)
├── services/
│   └── linear/
│       └── client.ts       # LinearClient wrapper with auth
├── schemas/
│   ├── inputs.ts           # Zod input schemas
│   └── outputs.ts          # Zod output schemas
├── config/
│   └── metadata.ts         # Server & tool descriptions
├── index.ts                # Node.js entry
└── worker.ts               # Workers entry

Troubleshooting

Issue Solution
"Workspace does not exist" Verify your OAuth app is in the correct Linear workspace. Check PROVIDER_CLIENT_ID.
"Unauthorized" Complete OAuth flow. Tokens may have expired.
"State not found" Use workspace_metadata to get valid stateIds for the team.
"Rate limited" Linear has strict rate limits. Wait and retry.
OAuth doesn't start (Worker) curl -i -X POST https://<worker>/mcp should return 401 with WWW-Authenticate.
Tools empty in Claude Ensure Worker returns JSON Schema for tools/list; use mcp-remote.

License

MIT

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选