No description
  • TypeScript 95.5%
  • CSS 3.4%
  • Shell 0.6%
  • JavaScript 0.3%
  • Dockerfile 0.1%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Luiz Felipe F M Costa 0433f0ce11
All checks were successful
Build image / test (push) Successful in 12m0s
Build image / build (push) Successful in 1m18s
feat(oauth): an operator may exempt a confidential client from PKCE
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>
2026-10-26 04:42:55 -03:00
.forgejo/workflows fix(deploy): the deploy host and its pinned key come from Forgejo secrets 2026-10-07 16:57:51 -03:00
docs feat(oauth): an operator may exempt a confidential client from PKCE 2026-10-26 04:42:55 -03:00
packages feat(oauth): an operator may exempt a confidential client from PKCE 2026-10-26 04:42:55 -03:00
scripts fix(migrate): review round: plan under the write lock, roll back on a failed verification, keep OpenLinear's edits on a re-run 2026-10-05 16:12:23 -03:00
tests feat(oauth): an operator may exempt a confidential client from PKCE 2026-10-26 04:42:55 -03:00
tools fix(ui-gate): a lane's own report is owned; a mobile lane's red may be its mobile specs alone 2026-10-06 01:03:48 -03:00
.dockerignore build: .dev/ never enters the Docker build context 2026-10-05 15:55:55 -03:00
.gitignore chore(repo): Linear workspace captures stay local and out of git 2026-10-07 17:03:00 -03:00
AGENTS.md docs(ui): reverse-engineer Linear's web UI screen by screen, from the live product 2026-10-03 23:15:36 -03:00
compose.yaml fix(auth): credentials cannot outlive or exceed what granted them 2026-10-02 07:38:12 -03:00
Dockerfile feat(deploy): a push to main tests, publishes and deploys to tasks.lab.thenets.org 2026-10-07 01:51:30 -03:00
MOBILE.md chore(ui-mobile): split m6 into m6-rest (settings, auth) and m6b-planning to run in parallel 2026-10-06 11:18:54 -03:00
package-lock.json docs(ui): plans for rebuilding the web UI as a faithful re-creation of Linear's, and the central F0 step 2026-10-04 00:50:11 -03:00
package.json feat(migrate): export a Linear workspace and import it into OpenLinear 2026-10-05 16:04:51 -03:00
playwright.config.ts feat(ui-m0-mobile-infra): iPhone device for fidelity and Playwright, quiet gate with gaming guard, viewport hooks, Linear's breakpoints, mobile lanes 2026-10-06 00:08:00 -03:00
README.md docs(deploy): DEPLOY.md: the image, production env behind a TLS proxy, first run, pipeline, upgrades 2026-10-05 12:56:51 -03:00
tsconfig.base.json feat: reimplement Linear's GraphQL API and MCP server 2026-10-02 04:03:16 -03:00
tsconfig.e2e.json test(web): the app end to end, and the server rules a browser found 2026-10-02 18:09:20 -03:00
tsconfig.json feat: reimplement Linear's GraphQL API and MCP server 2026-10-02 04:03:16 -03:00
tsconfig.tools.json test: add a shared test kit and one workspace seed 2026-10-02 04:42:28 -03:00

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:

  1. docker compose up -d --build starts the server on port 3000 (data in the openlinear-data volume; set OPENLINEAR_BASE_URL if clients reach it at another address).
  2. Open http://localhost:3000: the first visit is the setup page, which creates the workspace and its owner.
  3. In Settings, API keys, create a key; it is shown once.
  4. Use it from @linear/sdk with only the URL changed: new LinearClient({ apiKey, apiUrl: "http://localhost:3000/api/graphql" }).
  5. Add http://localhost:3000/mcp to 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 under ui/ 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.