- TypeScript 95.5%
- CSS 3.4%
- Shell 0.6%
- JavaScript 0.3%
- Dockerfile 0.1%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
Home Assistant's MCP integration authenticates with a client secret but never sends a code challenge, so every authorize came back invalid_request. A new oauth_clients.pkce_optional (default 0, never set by registration) lets such a client authorize with no challenge and redeem with its secret alone. It has no effect on a public client. A challenge the client does send is still enforced, and a verifier presented for a code issued without one is refused (RFC 9700 §2.1.1), so neither direction of a downgrade passes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
| .forgejo/workflows | ||
| docs | ||
| packages | ||
| scripts | ||
| tests | ||
| tools | ||
| .dockerignore | ||
| .gitignore | ||
| AGENTS.md | ||
| compose.yaml | ||
| Dockerfile | ||
| MOBILE.md | ||
| package-lock.json | ||
| package.json | ||
| playwright.config.ts | ||
| README.md | ||
| tsconfig.base.json | ||
| tsconfig.e2e.json | ||
| tsconfig.json | ||
| tsconfig.tools.json | ||
OpenLinear
OpenLinear is a self-hosted server that reimplements Linear's GraphQL API and
its MCP server, so that Linear's own clients work against it with only the
base URL changed: the official @linear/sdk
with apiUrl pointed at your server, and MCP clients set up for Linear's
hosted MCP server pointed at your /mcp. It also has its own web UI, login
with email and password, OAuth 2.1 for MCP clients and applications, webhooks,
and Linear's rate limits.
The served schema is not written by hand: it is pruned from Linear's published
SDL, so field names, arguments and nullability are Linear's. Operations are
allow-listed; one that is not served fails validation rather than returning an
invented answer. Every claim of compatibility is backed by a harness that
drives the real client against a running server (docs/COMPATIBILITY.md).
It runs on Node 24 and SQLite (node:sqlite), with no build step for the server.
Status
| What | Command | Result |
|---|---|---|
| Linear's root operations served | node tools/check-surface.ts |
335 of 554 (219 excluded, each with a reason) |
| Linear's MCP tools served | tools/list |
55 of 68 (13 excluded) |
@linear/sdk 97.0.0 documents rooted in a served operation, each validated as written |
npm run validate:sdk-ops |
517 of 671 (154 are for excluded operations) |
SDK harness: the real @linear/sdk against the server |
npm run validate:api |
711/711 checks |
| MCP harness: Linear's tool contract over the wire | npm run validate:mcp |
150/150 checks |
| Every test (kit, auth, operations, lanes, MCP, surface) | npm test |
3240 pass, 0 fail |
| Web UI, unit (Vitest) | npm run test:web |
2931 passed (2931) |
| Web UI, in a browser (Playwright) | npm run test:e2e |
77 passed |
| The container stack through a TLS proxy (Docker) | npm run test:integration |
9/9 scenarios |
What is left out, and why, is in docs/COMPATIBILITY.md §5.
Getting started
With Docker Compose, from a checkout:
docker compose up -d --buildstarts the server on port 3000 (data in theopenlinear-datavolume; setOPENLINEAR_BASE_URLif clients reach it at another address).- Open
http://localhost:3000: the first visit is the setup page, which creates the workspace and its owner. - In Settings, API keys, create a key; it is shown once.
- Use it from
@linear/sdkwith only the URL changed:new LinearClient({ apiKey, apiUrl: "http://localhost:3000/api/graphql" }). - Add
http://localhost:3000/mcpto an MCP client as a streamable-HTTP server; it registers itself, and you sign in and approve it in the browser.
@linear/sdk refuses plain http:// for any host but localhost, so anything
beyond local use needs a TLS-terminating proxy in front (and
OPENLINEAR_TRUSTED_PROXY_HOPS=1); docs/OPERATIONS.md lists the settings.
For an MCP client that takes a header instead of doing OAuth, send
Authorization: Bearer <api key> (docs/MCP.md).
Developing
npm ci
npm run typecheck # packages, web, tests, tools, scripts, e2e
npm test # every node test, including the surface manifest and coverage
npm run validate # DDL + migrations, SDL facts, SDK fragments and documents, surface, SDK and MCP harnesses
LANE=<lane> npm run test:lane # one lane held to the "done" standard
npm run docs:compat # regenerate the numbers in this file and docs/COMPATIBILITY.md
npm run dev serves on 127.0.0.1:3000 (npm run seed creates a demo
workspace and prints an API key). The web UI: npm run build:web,
npm run test:web, npm run test:e2e; the container stack:
npm run test:integration (Docker). AGENTS.md is the contract for adding to
the code.
Repository layout
packages/core/src/
graphql/ the schema pruned from Linear's SDL, the registry, one domain module per lane,
surface-manifest.json (every SDL root operation -> lane | excluded:<reason>)
services/ domain logic per lane, shared by GraphQL and MCP
kit/ paging, filters, mutate(), access rules, events, notifications, uploads, rate limits
migrations/ versioned DDL, one folder per lane
packages/server/ HTTP: /api/graphql, /mcp, /auth, OAuth 2.1, webhooks, static files
packages/mcp/ MCP transport and Linear's tools, run on the lanes' own resolvers
packages/web/ the web UI (React + Vite), served by the server
tests/ node tests per lane and for auth, ops, MCP and the surface; e2e/ (Playwright); integration/ (Docker)
tools/ the SDK and MCP harnesses, the surface and SDK checks, the docs generator, the lane tooling
docs/ the documents below
Documents
docs/COMPATIBILITY.md: what is served, how it was proved, Linear's quirks kept on purpose, and the known gaps.docs/AUTH.md: login, sessions, API keys, release access keys and OAuth 2.1, as a contract.docs/MCP.md: connecting an MCP client, the tools, and the MCP harness.docs/WEB.md: the web UI: developing, building, serving and its screens.docs/OPERATIONS.md: rate limits, webhook delivery and the environment variables.docs/DEPLOY.md: the production image, its environment behind a TLS proxy, first-run setup, the Forgejo build pipeline and upgrades.docs/reverse-engineering/: Linear's SDL, SDK facts, MCP captures, data model and invariants (reference), and underui/Linear's web UI screen by screen with 532 screenshots.docs/build-out/: the record of how this was built: the process, briefs, notes and reviews.
Scope
Served: Linear's issue tracking end to end (issues, comments, labels,
relations, teams, workflow states, cycles, projects, milestones, project and
initiative updates, initiatives, documents, templates, customers, releases,
the inbox, custom views, favorites), the people and workspace settings around
it, webhooks, OAuth applications and Linear's MCP tools on top of the same
resolvers. Not served, by design: integrations with third-party services, AI
features, billing and SLAs, Linear's agent runtime, SSO, importers, email, and
more than one workspace per deployment. The full list, with each excluded
operation and tool, is in docs/COMPATIBILITY.md.
Licence
AGPL-3.0-or-later.
The exception is the reverse-engineering artifacts: Linear's SDL and three of
its SDK's source files (client.ts, graphql-client.ts, error.ts) are
Linear's own, vendored under docs/reverse-engineering/artifacts/ for
reference and regenerable from upstream (npm run extract). The
reimplementation in packages/ is ours.