# Linear

Lightweight, dependency-free, in-memory fake of the **Linear GraphQL API** for testing code that uses the real `@linear/sdk` client or raw GraphQL requests.

Default port: `4788`

The fake includes a **minimal but real GraphQL parser**: the query string is tokenised, the operation type (`query`/`mutation`) and top-level fields + arguments are extracted, `$variables` are substituted from the request `variables` map, and each field is dispatched to an in-memory resolver.

## Quick start

```js
import { LinearServer } from "./services/linear/src/server.js";

const server = new LinearServer(4788);
await server.start();
// ... run your app/tests ...
await server.stop();
```

```js
const res = await fetch("http://127.0.0.1:4788/graphql", {
  method: "POST",
  headers: { Authorization: "lin_api_xxx", "Content-Type": "application/json" },
  body: JSON.stringify({
    query: `mutation { issueCreate(input: { title: "Hello" }) { success issue { id identifier title } } }`,
  }),
});
// => 200 { data: { issueCreate: { success: true, issue: { id, identifier: "PAR-1", title } } } }
```

## Access via MCP / preview URL

Point your MCP server / agent tooling at the preview URL printed by the parlel
pool (defaults to `http://127.0.0.1:4788`). Set `LINEAR_BASE_URL` to that URL
and provide any non-empty `LINEAR_API_KEY` — the fake accepts a raw API key or a
`Bearer` token. Only `POST /graphql` is served.

## Implemented operations

`POST /graphql` is the only API endpoint and requires a non-empty `Authorization` header.

### Queries
- `viewer { id name email displayName }` — the authenticated user.
- `issues { nodes { ... } pageInfo }` — all issues (connection shape).
- `issue(id: ID) { ... }` — a single issue by id.
- `teams { nodes { id name key } pageInfo }` — all teams.

### Mutations
- `issueCreate(input: { title, description?, priority?, teamId? }) { success issue { id identifier title ... } }` — create an issue. Requires `title`.
- `issueUpdate(id, input) { success issue }` — update fields.
- `issueDelete(id) { success }` — delete an issue.

### Service & inspection
- `GET /` — service metadata.
- `GET /health` — `{ status: "ok" }`.
- `POST /__parlel/reset` — reset all in-memory state.

## Surface coverage

This emulator faithfully replicates the API surface most application code and agents exercise. Anything below the supported lines is either an intentional design choice for a fast, zero-cost local emulator (✓ By design) or a candidate for a future release (⟳ Roadmap) — never a silent inaccuracy.

Legend: ✅ fully supported · ◐ accepted (stored, not strictly enforced) · ✓ by design · ⟳ on the roadmap.

| Feature | Status |
| --- | --- |
| `viewer`, `teams`, `issues`, `issue` queries | ✅ Supported |
| `issueCreate` / `issueUpdate` / `issueDelete` | ✅ Supported |
| GraphQL parsing (variables, args, aliases, nested selections) | ✅ Supported (minimal real parser) |
| Field-level selection pruning (returning only requested fields) | ◐ Returns full resolver objects |
| Fragments, directives, introspection | ⟳ Roadmap |
| Comments, projects, cycles, labels, workflow states | ◐ State enum only |
| API key validity / scope enforcement | ✓ By design — Any non-empty credential is accepted — no real secrets needed |

## Error shapes

GraphQL errors are returned with HTTP `200` and an `errors: [{ message, path }]` array (per the GraphQL spec). A missing `Authorization` header returns HTTP `401` with `errors`.

## Manifest

See `services/linear/manifest.json`:

- name: `linear`, image: `parlel/linear:1`
- port: `4788`, protocol: `http`, healthcheck: `/health`, startup ≈ 100ms
- env: `LINEAR_API_KEY`, `LINEAR_BASE_URL`
