Linear MCP Server (HTTP / OAuth / Remote)
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. In production, enforce least‑privilege credentials, audit logs, and approval workflows.
A streamable HTTP MCP server for Linear that lets you manage issues, projects, teams, users, comments, and cycles — locally or remotely.
Below is a comparison between the official Linear MCP (top) and this MCP (bottom).
Notice
This repo works in two ways:
- As a Node/Hono server for local workflows
- As a Cloudflare Worker for remote interactions
The HTTP/OAuth setup here is designed for convenience during development, not for production‑grade security. If you’re deploying to Cloudflare, see Remote Model Context Protocol servers (MCP) for details.
Motivation
I’m a big fan of Linear and use it daily — for both personal projects and professional workflows, including automations. At the time of writing, the official MCP server isn’t fully optimized for language models (this may change soon, as Linear is actively improving it).
This server is built with a few key goals in mind:
- Let LLMs easily find things like Team IDs, Project IDs, Status IDs, or User IDs in a single action (
workspace_metadata
) instead of calling multiple tools just to gather required data. - Include clear MCP instructions and schema descriptions that cut API jargon, making it more likely the model uses the right tool in the right order.
- Map API responses into human‑readable feedback — useful for both the LLM and the user.
- Provide hints and suggestions for next steps, plus tips on using available data or recovering from errors.
- Support batch actions (e.g.,
add_issues
instead ofadd_issue
) so the LLM can perform multiple steps in one go. - Prefetch related values — for example, return both a status ID and the actual status name for an issue.
- Hide tools not enabled in a given team’s settings (like
cycles_list
) to reduce noise. - Adjust schemas to match workspace preferences, such as issue priority formats.
In short, it’s not a direct mirror of Linear’s API — it’s tailored so AI agents and chat clients know exactly how to use it effectively.
Installation & development
Prerequisites: Bun, Node.js 24+, Linear account. For remote: a Cloudflare account and the Wrangler package.
You also need an MCP client such as:
Ways to run (pick one)
- Local (API key)
- Local + OAuth
- Cloudflare Worker — wrangler dev (local Worker)
- Cloudflare Worker — remote deploy
1) Quick start (local workflow with API key)
This is the easiest way to start. Run the server with your Linear Personal Access Token (Settings → Security).
https://linear.app/[your-account-name]/settings/account/security
git clone https://github.com/iceener/linear-streamable-mcp-server
cd linear
bun install
cp env.local-api.example .env
# Update your Linear API key in .env
bun dev
Now connect this server to Alice (Settings → MCP) and set it up as follows:
Or use Claude Desktop with the following settings:
{
"mcpServers": {
"remote-example": {
"command": "bunx",
"args": [
"mcp-remote",
"http://localhost:3040/mcp",
"--header",
"Authorization: ${LINEAR_API_KEY}"
]
}
}
}
2) Alternative: Local + OAuth
This is a more advanced workflow because it requires creating an OAuth application in Linear. Example:
git clone https://github.com/iceener/linear-streamable-mcp-server
cd linear
bun install
cp env.local-oauth.example .env
# Update OAUTH_CLIENT_ID and OAUTH_CLIENT_SECRET
# Add the following redirect URIs:
# alice://oauth/callback
# http://127.0.0.1:3041/linear/callback
# http://localhost:3041/linear/callback
# http://localhost:8788/linear/callback
# https://claude.ai/api/mcp/auth_callback
# https://claude.com/api/mcp/auth_callback
# https://<worker-name>.<account>.workers.dev/linear/callback
bun dev
Tip: the local Authorization Server (for OAuth) runs on PORT+1. If PORT=3040
, auth is on http://localhost:3041
.
When the server is up, connect to Alice:
Alternatively, connect with Claude Desktop:
{
"mcpServers": {
"linear": {
"command": "bunx",
"args": [
"mcp-remote",
"http://localhost:3040/mcp",
"--transport",
"http-only"
],
"env": { "NO_PROXY": "127.0.0.1,localhost" }
}
}
}
RS‑only mode (recommended for remote clients)
Enable these flags to require RS‑minted bearer tokens. When enabled, requests without Authorization
or with a non‑mapped Bearer <opaque>
will receive 401
with WWW-Authenticate
so OAuth can start (works with mcp-remote
).
# Challenge when Authorization missing or not one of our RS tokens
AUTH_REQUIRE_RS=true
# If you still want to allow Linear PATs as Bearer in RS‑only mode, set:
AUTH_ALLOW_LINEAR_BEARER=false
3) Cloudflare Worker — wrangler dev (local Worker)
Fast way to test the Worker locally.
cd linear
bun x wrangler dev --local | cat
If you want to pass a PAT directly in dev:
cd linear
bun x wrangler secret put LINEAR_API_KEY
bun x wrangler dev --local | cat
With OAuth, also set:
cd linear
bun x wrangler secret put OAUTH_CLIENT_ID
bun x wrangler secret put OAUTH_CLIENT_SECRET
# Ensure OAUTH_SCOPES and allowlist in wrangler.toml
bun x wrangler dev --local | cat
Endpoint (dev): http://127.0.0.1:8787/mcp
(Wrangler prints the exact port).
4) Cloudflare Worker — remote deploy
Wrangler reference (already included as linear/wrangler.toml
):
name = "linear-mcp-worker"
main = "src/worker.ts"
compatibility_date = "2025-06-18"
workers_dev = true
compatibility_flags = ["nodejs_compat"]
[vars]
MCP_PROTOCOL_VERSION = "2025-06-18"
AUTH_ENABLED = "true"
AUTH_REQUIRE_RS = "true"
AUTH_ALLOW_LINEAR_BEARER = "false"
OAUTH_AUTHORIZATION_URL = "https://linear.app/oauth/authorize"
OAUTH_TOKEN_URL = "https://api.linear.app/oauth/token"
OAUTH_SCOPES = "read write"
OAUTH_REDIRECT_ALLOW_ALL = "false" # dev helper; keep false in prod
OAUTH_REDIRECT_URI = "alice://oauth/callback"
OAUTH_REDIRECT_ALLOWLIST = "alice://oauth/callback,https://claude.ai/api/mcp/auth_callback,https://claude.com/api/mcp/auth_callback,https://<worker-name>.<account>.workers.dev/linear/callback"
NODE_ENV = "development" # allows localhost callback in dev
[[kv_namespaces]]
binding = "TOKENS"
id = "REDACTED"
Deploy with API key:
cd linear
bun x wrangler secret put LINEAR_API_KEY
bun x wrangler deploy
Endpoint: https://<worker-name>.<account>.workers.dev/mcp
.
Environment examples
- Local (API key):
env.local-api.example
- Local + OAuth:
env.local-oauth.example
- Generic defaults:
env.example
Remote (Cloudflare Worker) with OAuth
- Create KV for token mapping and add it to
wrangler.toml
:
bun x wrangler kv namespace create TOKENS
- Set secrets and vars:
cd linear
bun x wrangler secret put OAUTH_CLIENT_ID
bun x wrangler secret put OAUTH_CLIENT_SECRET
# Optionally set LINEAR_ACCESS_TOKEN or LINEAR_API_KEY if you prefer direct tokens
- Ensure
OAUTH_SCOPES = "read write"
and include your Worker callback in the Linear app and allowlist:
https://<worker-name>.<account>.workers.dev/linear/callback
- Deploy:
bun x wrangler deploy
The Worker advertises OAuth discovery and maps Resource‑Server tokens to Linear tokens using KV. It reuses the same tool handlers as the local server. In RS‑only mode, it will:
- 401‑challenge when Authorization is missing
- 401‑challenge when a non‑mapped Bearer is presented (unless
AUTH_ALLOW_LINEAR_BEARER=true
) - Rewrite a mapped RS Bearer to a Linear access token before invoking tools
Troubleshooting (Worker)
- If OAuth doesn’t start:
curl -i -X POST https://<worker>/mcp ...
should return401
withWWW-Authenticate
andMcp-Session-Id
. - If tools appear empty in Claude: ensure the Worker returns JSON Schema for
tools/list
(this repo does), and configure Claude withmcp-remote
(not Research connectors). - If redirect is blocked: set a valid
OAUTH_REDIRECT_URI
and allowlist; for dev you can setNODE_ENV=development
and keep loopback hosts.
Client configuration
MCP Inspector (quick test):
bunx @modelcontextprotocol/inspector
# Connect to: http://localhost:3040/mcp (local) or your Worker /mcp URL
Claude Desktop / Cursor via mcp‑remote:
{
"mcpServers": {
"linear": {
"command": "bunx",
"args": [
"mcp-remote",
"http://localhost:3040/mcp",
"--transport",
"http-only"
],
"env": { "NO_PROXY": "127.0.0.1,localhost" }
}
}
}
For Cloudflare, replace the URL with https://<worker-name>.<account>.workers.dev/mcp
.
Examples
1) List my issues due today
Request (get viewer timezone/id for context):
{
"name": "workspace_metadata",
"arguments": { "include": ["profile"] }
}
Request (issues assigned to me, due today):
{
"name": "list_my_issues",
"arguments": {
"filter": { "dueDate": { "eq": "2025-08-15" } },
"orderBy": "updatedAt",
"limit": 20
}
}
Response (example):
My issues: 1 (limit 20). Preview:
- [OVE-142 — Publish release notes](https://linear.app/example/issue/OVE-142/publish-release-notes) — state Done; project overment; due 2025-08-15; assignee Adam
2) Create an issue for Alice v3.8 and add it to the project
Request (discover team/project ids):
{
"name": "workspace_metadata",
"arguments": { "include": ["teams", "projects", "profile"] }
}
``;
Create (assigneeId omitted – this tool defaults to current viewer):
```json
{
"name": "create_issues",
"arguments": {
"items": [
{
"title": "Release Alice v3.8 app",
"teamId": "TEAM_ID",
"projectId": "ALICE_PROJECT_ID",
"dueDate": "2025-08-18",
"priority": 2,
"description": "Deploy and release Alice v3.8 to production"
}
]
}
}
Response (example):
Created issues: 1 / 1. OK: item[0]. Next: Use list_issues (by id or number+team.key/team.id, limit=1) to verify details.
3) Reschedule a release and mark a meeting as Done
Find the release issue:
{
"name": "list_issues",
"arguments": { "q": "Alice release", "limit": 5 }
}
Find the meeting issue:
{
"name": "list_issues",
"arguments": { "q": "team meeting", "limit": 20 }
}
Resolve workflow states (Done) for the team:
{
"name": "workspace_metadata",
"arguments": { "include": ["workflow_states"], "teamIds": ["TEAM_ID"] }
}
Update both:
{
"name": "update_issues",
"arguments": {
"items": [
{ "id": "RELEASE_UUID", "dueDate": "2025-08-16" },
{ "id": "MEETING_UUID", "stateId": "DONE_STATE_ID" }
]
}
}
Response (example):
Updated issues: 2 / 2. OK: RELEASE_UUID, MEETING_UUID
- [OVE-231 — Release Alice v3.8 app](https://linear.app/example/issue/OVE-231/release-alice-v38-app) (id RELEASE_UUID)
Due date: 2025-08-18 → 2025-08-16
- [OVE-224 — Team meeting](https://linear.app/example/issue/OVE-224/team-meeting) (id MEETING_UUID)
State: Current → Done
License
MIT