Linear API Client โ MCP Server Template
Overview
This template provides a production-ready Model Context Protocol (MCP) server that exposes the Linear project management API to AI assistants like Claude. Built with TypeScript and Express, it acts as a structured bridge between MCP clients and Linear's GraphQL API, handling authentication, retries, rate limiting, and response normalization so your AI workflows can query issues, projects, teams, and users without boilerplate.
The server is designed for teams that want to give Claude or other MCP-compatible assistants direct, safe access to their Linear workspace. Every tool call is validated, logged with a correlation ID, and executed through a resilient HTTP client that respects Linear's rate limits. The architecture keeps business logic cleanly separated from transport concerns, making it straightforward to extend with new Linear endpoints or swap out the underlying HTTP transport.
This template is ideal for engineering teams building internal AI tooling, developers prototyping Linear-powered automation, and anyone who wants a battle-tested starting point for an MCP server that talks to a third-party GraphQL API.
What You'll Learn
- How to scaffold a fully typed MCP server using the
@modelcontextprotocol/sdkpackage with HTTP/SSE transport - How to implement API key authentication middleware that protects every MCP endpoint
- How to build a reusable GraphQL client with automatic retries and exponential back-off
- How to enforce rate limiting per client using
express-rate-limitto protect upstream APIs - How to set up Pino for structured JSON logging with per-request correlation IDs
- How to register and validate MCP tools using Zod schemas for runtime input safety
- How to write a
/healthendpoint that checks Linear API reachability and reports structured JSON status - How to create a multi-stage Dockerfile that produces a minimal, secure production image
- How to wire up environment-based configuration with typed defaults and validation at startup
- How to write integration-style tests with Jest and Supertest covering auth, health, and tool execution
- How to handle Linear GraphQL errors, network timeouts, and upstream failures gracefully
- How to normalise Linear's nested GraphQL responses into flat, LLM-friendly structures
Architecture
MCP Client (Claude Desktop / Cursor)
โ
โ HTTP POST /mcp (MCP JSON-RPC)
โผ
Express Server
โ
โโโบ API Key Auth Middleware โโโโ 401 if invalid
โโโบ Rate Limit Middleware โโโโ 429 if exceeded
โโโบ Pino Request Logger โโโโ correlation ID injected
โ
โผ
MCP SDK Router (StreamableHTTPServerTransport)
โ
โโโบ Tool: list_issues
โโโบ Tool: get_issue
โโโบ Tool: create_issue
โโโบ Tool: list_projects
โโโบ Tool: list_teams
โ
โผ
Linear GraphQL Client
โ (retries + back-off)
โผ
Linear API (api.linear.app/graphql)
Project Structure
linear-mcp-server/
โโโ src/
โ โโโ __tests__/
โ โ โโโ server.test.ts # Jest + Supertest integration tests
โ โ โโโ setup.ts # Test environment variables
โ โโโ linear/
โ โ โโโ client.ts # Reusable GraphQL client with retries
โ โ โโโ queries.ts # All Linear GraphQL query strings
โ โ โโโ types.ts # Full TypeScript types for Linear responses
โ โโโ tools/
โ โ โโโ issues.ts # list_issues, get_issue, create_issue tools
โ โ โโโ projects.ts # list_projects tool
โ โ โโโ teams.ts # list_teams tool
โ โโโ auth.ts # API key authentication middleware
โ โโโ config.ts # Typed config loaded from environment
โ โโโ health.ts # /health endpoint with dependency checks
โ โโโ logger.ts # Pino structured logger
โ โโโ server.ts # Express app + MCP server entrypoint
โโโ .env.example # All environment variables documented
โโโ .gitignore
โโโ babel.config.js # Babel config for ESM node-fetch in Jest
โโโ docker-compose.yml
โโโ Dockerfile
โโโ package.json
โโโ tsconfig.json
Prerequisites
- Node.js 20 or later
- npm 9 or later (or pnpm/yarn)
- A Linear account with an API key (Settings โ API โ Personal API keys)
- Docker (optional, for containerised deployment)
- Claude Desktop or another MCP client for end-to-end testing
Quick Start
# 1. Clone or download the template
git clone https://github.com/mcpforge/linear-mcp-server.git
cd linear-mcp-server
# 2. Install dependencies
npm install
# 3. Configure environment
cp .env.example .env
# Edit .env โ at minimum set LINEAR_API_KEY and MCP_API_KEY
# Generate a secure MCP_API_KEY: openssl rand -hex 32
# 4. Start in development mode (hot reload)
npm run dev
# 5. Verify the server is healthy (no API key required)
curl http://localhost:3000/health
# 6. Test the MCP endpoint (API key and Accept header required)
curl -s -X POST http://localhost:3000/mcp \
-H "X-API-Key: <your-MCP_API_KEY>" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
# 7. Run the test suite
npm test
# 8. Build and run in production
npm run build
npm start
To connect Claude Desktop, add this to your claude_desktop_config.json:
{
"mcpServers": {
"linear": {
"command": "npx",
"args": ["mcp-remote", "http://localhost:3000/mcp"],
"env": { "MCP_API_KEY": "your-mcp-api-key" }
}
}
}
Features
- 5 Linear tools โ list issues, get a single issue, create an issue, list projects, list teams
- GraphQL client with configurable retries, exponential back-off, and timeout
- API key authentication protecting every MCP and health endpoint
- Rate limiting โ configurable requests-per-minute per client IP
- Pino structured logging with JSON output and per-request correlation IDs
- Health endpoint that pings the Linear API and reports latency
- Zod input validation on every tool โ bad inputs are rejected before they reach Linear
- Response normalisation โ nested GraphQL edges/nodes flattened to clean arrays
- Multi-stage Dockerfile producing a ~120 MB production image
- docker-compose.yml for instant local development
- Jest + Supertest test suite covering auth, health, and tool execution
- Typed configuration โ startup fails fast if required env vars are missing
Works With
- Claude Desktop โ add as an MCP server in
claude_desktop_config.json - Claude Code โ use
mcp addwith the HTTP endpoint - Cursor โ configure under Settings โ MCP Servers
- Windsurf โ add to
windsurf_mcp_config.json - Continue โ add as an MCP server in
config.json
Configuration
| Variable | Description | Required |
|---|---|---|
LINEAR_API_KEY | Linear personal or OAuth API key | โ Required |
MCP_API_KEY | Secret key MCP clients must send in x-api-key header | โ Required |
PORT | HTTP port the server listens on | Optional (default: 3000) |
LOG_LEVEL | Pino log level: trace debug info warn error | Optional (default: info) |
LINEAR_API_URL | Linear GraphQL endpoint override | Optional (default: https://api.linear.app/graphql) |
LINEAR_MAX_RETRIES | Max retry attempts for failed Linear requests | Optional (default: 3) |
LINEAR_RETRY_DELAY_MS | Initial retry delay in milliseconds | Optional (default: 500) |
LINEAR_REQUEST_TIMEOUT_MS | Per-request timeout for Linear API calls | Optional (default: 10000) |
RATE_LIMIT_WINDOW_MS | Rate limit window in milliseconds | Optional (default: 60000) |
RATE_LIMIT_MAX_REQUESTS | Max requests per window per IP | Optional (default: 100) |
NODE_ENV | Runtime environment (development / production) | Optional (default: development) |
Deployment
Docker (recommended)
# Build the production image
docker build -t linear-mcp-server .
# Run with environment variables
docker run -d \
-p 3000:3000 \
-e LINEAR_API_KEY=lin_api_xxxx \
-e MCP_API_KEY=your-secret-key \
--name linear-mcp \
linear-mcp-server
docker-compose (local dev)
docker-compose up --build
Railway
npm install -g @railway/cli
railway login
railway init
railway up
# Set env vars in the Railway dashboard
Fly.io
npm install -g flyctl
fly auth login
fly launch --name linear-mcp-server
fly secrets set LINEAR_API_KEY=lin_api_xxxx MCP_API_KEY=your-secret-key
fly deploy
Production Checklist
-
LINEAR_API_KEYis a dedicated service key, not a personal developer key -
MCP_API_KEYis at least 32 random characters (useopenssl rand -hex 32) -
NODE_ENV=productionis set in the deployment environment -
LOG_LEVEL=infoorwarnโ nevertraceordebugin production - Rate limiting values reviewed and tuned for your expected traffic
- Docker image built from the
productionstage (notbuilder) - Health endpoint
/healthis monitored by your uptime service - Container restarts automatically on failure (
--restart unless-stopped) - Secrets are injected via environment variables โ never baked into the image
-
LINEAR_REQUEST_TIMEOUT_MSis set to a value your clients can tolerate - Log output is collected by a log aggregator (Datadog, Logtail, etc.)
- Network egress to
api.linear.appis allowed from your hosting environment - Image is scanned for vulnerabilities before deployment (
docker scoutor Snyk) - Resource limits (CPU/memory) are set on the container
Testing
Automated tests
# Run all tests
npm test
# Watch mode during development
npm run test:watch
# With coverage report
npm run test:coverage
The test suite covers:
GET /healthreturns HTTP 200 with valid JSON- Auth middleware rejects missing or incorrect
x-api-keywith HTTP 401 POST /mcpwith a validtools/listcall returns the registered toolslist_issuestool returns normalised issue objectscreate_issuetool validates required inputs and rejects bad data
Manual testing with curl
# Health check
curl -H "x-api-key: your-mcp-api-key" http://localhost:3000/health
# List MCP tools
curl -X POST http://localhost:3000/mcp \
-H "Content-Type: application/json" \
-H "x-api-key: your-mcp-api-key" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
# Call list_teams tool
curl -X POST http://localhost:3000/mcp \
-H "Content-Type: application/json" \
-H "x-api-key: your-mcp-api-key" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"list_teams","arguments":{}}}'