Skip to main content
Glama
wobondar

linear-codemode-mcp

by wobondar

linear-codemode-mcp

A local MCP server for Linear in the code mode pattern. The agent writes JavaScript, the server runs it in a throwaway worker, and only the return value comes back. Two tools instead of ninety in linear-mcp, under 1K tokens of tool descriptions instead of tens of thousands.

Read-only by default. Mutations are refused on the host before any request leaves the process, whatever the API key can do.

Tools

Tool

What the agent's code gets

Network

schema

schema.queries, schema.types (and schema.mutations when enabled): the Linear SDL projected to plain objects, type references as SDL strings

none

execute

linear.request({ query, variables, allowPartial }), which resolves to GraphQL data and throws on any error. With the file flags set, files.read(path) and files.write(path, text) too

Linear only, via the host

Results over about 6,000 tokens are cut structurally so they stay valid JSON (Cloudflare's truncate.ts, Apache-2.0, see LICENSE-cloudflare-mcp).

Related MCP server: Read-only Analytics MCP Server

Run

Requires Bun.

bun install            # also fetches schema/linear.graphql
cp .env.example .env.local   # then put your key in it
bun test
bun start              # speaks MCP on stdio

The MCP host spawns the server from its own working directory, so Bun needs an absolute path to the env file. For Claude Code, user scope:

claude mcp add --scope user linear-code -- bun --env-file=/ABS/PATH/linear-codemode-mcp/.env.local /ABS/PATH/linear-codemode-mcp/src/index.ts

For a project-level .mcp.json, or any other host that takes a JSON server block, copy .mcp.json.example and fix the paths.

Configuration

Variable

Default

Meaning

LINEAR_API_KEY

required

Personal API key, sent as a bare Authorization header

LINEAR_MCP_ALLOW_MUTATIONS

unset

true lets mutation operations through and adds schema.mutations. Subscriptions are always refused

LINEAR_MCP_TRUNCATE

true

false returns whole results

LINEAR_MCP_TIMEOUT_MS

30000

Wall-clock budget per call; the worker is terminated when it expires

LINEAR_MCP_FS_READ

unset

true adds files.read(path) for UTF-8 text under the server's working directory and the Claude Code scratchpad

LINEAR_MCP_FS_WRITE

unset

true adds files.write(path, text) in the same places, resolving to the bytes written. It never overwrites, not even through a symlink, and never creates directories

LINEAR_MCP_FS_ALLOW_READ_OUTSIDE_CWD

unset

true lifts the directory limit on reads. Needs LINEAR_MCP_FS_READ

LINEAR_MCP_FS_ALLOW_WRITE_OUTSIDE_CWD

unset

true lifts the directory limit on writes. Needs LINEAR_MCP_FS_WRITE

LINEAR_API_URL

https://api.linear.app/graphql

Files

The point of code mode is that big payloads never pass through the model. A comment body that lives in a file, or a thousand issues the model wants to grep later, should go straight between disk and Linear:

async () => {
  const body = await files.read("/abs/path/docs/ENG-123-comment.md");
  const { commentCreate } = await linear.request({
    query: `mutation($issueId: String!, $body: String!) { commentCreate(input: { issueId: $issueId, body: $body }) { success } }`,
    variables: { issueId: "...", body },
  });
  return commentCreate.success;
}

Paths are absolute. Symlinks are resolved before the directory check, so a link inside the project pointing elsewhere counts as elsewhere. .env* files are refused on both sides whatever the flags say. The host does every read and write; the worker only ever sees the text.

How a call runs

  1. The tool handler spawns a fresh Bun Worker from a blob URL with env: {} and smol: true.

  2. The agent's code is compiled with new Function whose parameters shadow fetch, process, Bun, require, postMessage and the other globals that reach outside the worker, then awaited.

  3. linear.request() posts to the host thread. The host parses the document with graphql, refuses anything that is not a query (or mutation when enabled), then fetches with the key. The key never enters the worker. files.read() and files.write() take the same route; the worker has no file handles.

  4. The return value is JSON-serialised in the worker, truncated on the host, and sent back as text.

  5. The worker is terminated. A loop that never yields dies with the timer.

This is a guardrail, not a security boundary against a hostile author. The author owns the key.

Updating the schema

bun run update-schema pulls packages/sdk/src/schema.graphql from linear/linear. The file is gitignored; the server refuses to start without it.

Credits

License

MIT

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    A
    maintenance
    Provides a sandboxed environment for AI coding agents to execute TypeScript AWS SDK queries securely, with cross-account aggregation and no host credential exposure.
    33 npm
    7
    Apache 2.0
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables analytics agents to run validated read-only SQL against a warehouse API with enforced row limits, required JSON responses, and secret-safe audit metadata.
    MIT
  • F
    license
    A
    quality
    C
    maintenance
    Enables running read-only SQL queries against Increff Webget database replicas using an authenticated browser session, with mandatory human approval for every query.
    2
    -
  • A
    license
    A
    quality
    C
    maintenance
    Enables querying PostHog with HogQL, retrieving event and property definitions, session replays, and project lists, plus optional REST API access with safeguards against accidental writes and API key leakage.
    6
    7 npm
    MIT