Linear MCP Server
raw JSON → 0.1.0 verified Sat Apr 25 auth: no javascript
A Model Context Protocol (MCP) server for the Linear API, enabling LLMs to interact with Linear issues, teams, and users. Version 0.1.0 provides tools for creating, updating, searching issues, adding comments, and accessing resources via MCP. It requires a Linear API key and is installed as a client configuration for Claude Desktop. Key differentiators: direct MCP integration for AI agents, supports markdown descriptions, flexible issue search with filters, and resource URIs for context access.
Common errors
error Error: LINEAR_API_KEY is not set ↓
cause The required environment variable LINEAR_API_KEY is missing when the server starts.
fix
Set the environment variable before running the server (e.g., export LINEAR_API_KEY=your_key or add to Claude config).
error Error: Team not found ↓
cause The teamId provided to linear_create_issue does not exist or is invalid.
fix
Verify the team ID from Linear's settings (https://linear.app/YOUR-TEAM/settings/api) and ensure it's a valid UUID.
error Error: Cannot read properties of undefined (reading 'map') ↓
cause The result of a Linear API call might be undefined when no data is returned.
fix
Add null/undefined checks before accessing properties like .nodes on search results.
error Error: Unsupported transport type ↓
cause The MCP SDK's StdioServerTransport requires Node.js 16+; older versions may not support it fully.
fix
Ensure Node.js 16+ is installed (use node --version to check).
Warnings
breaking The package is at v0.1.0; breaking changes can occur at any time without major version bump. ↓
fix Pin to exact version in package.json and monitor releases for breaking changes.
deprecated Import paths may change as the MCP SDK evolves. Current paths are specific to version 0.1.0. ↓
fix Check SDK documentation for updated import paths.
gotcha The LINEAR_API_KEY must be set in environment variables; using a placeholder will cause authentication failures. ↓
fix Ensure LINEAR_API_KEY is set in Claude Desktop config or process environment.
gotcha Tool names use snake_case (e.g., 'linear_create_issue') but the Linear SDK may use camelCase. Inconsistent naming can cause mismatches. ↓
fix Map MCP tool names to Linear SDK method names carefully.
Install
npm install linear-mcp-server yarn add linear-mcp-server pnpm add linear-mcp-server Imports
- server wrong
import { Server } from '@modelcontextprotocol/sdk'correctimport { Server } from '@modelcontextprotocol/sdk/server/index.js' - LinearClient wrong
const LinearClient = require('linear-client')correctimport { LinearClient } from 'linear-client' - CallToolResult wrong
import { CallToolResult } from '@modelcontextprotocol/sdk'correctimport { CallToolResult } from '@modelcontextprotocol/sdk/types.js'
Quickstart
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { LinearClient } from 'linear-client';
import { CallToolRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema } from '@modelcontextprotocol/sdk/types.js';
const server = new Server({
name: 'linear-mcp-server',
version: '0.1.0',
});
const linearClient = new LinearClient(process.env.LINEAR_API_KEY ?? '');
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case 'linear_create_issue': {
const issue = await linearClient.issue.create({
teamId: args.teamId,
title: args.title,
description: args.description,
priority: args.priority ?? 0,
});
return { content: [{ type: 'text', text: JSON.stringify(issue) }] };
}
case 'linear_search_issues': {
const issues = await linearClient.issues.search(args.query ?? '', { limit: args.limit ?? 10 });
return { content: [{ type: 'text', text: JSON.stringify(issues.nodes) }] };
}
default:
throw new Error(`Unknown tool: ${name}`);
}
} catch (error) {
return { isError: true, content: [{ type: 'text', text: error.message }] };
}
});
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
resources: [
{ uri: 'linear-issue:///{issueId}', name: 'Linear Issue' },
{ uri: 'linear-team:///{teamId}/issues', name: 'Team Issues' },
],
}));
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const uri = request.params.uri;
const match = uri.match(/linear-(issue|team):\/\/(.+)/);
if (!match) throw new Error(`Invalid resource URI: ${uri}`);
const [, type, id] = match;
if (type === 'issue') {
const issue = await linearClient.issue(id);
return { contents: [{ uri, mimeType: 'application/json', text: JSON.stringify(issue) }] };
}
throw new Error('Resource not found');
});
const transport = new StdioServerTransport();
await server.connect(transport);