# Linear integration

`linear` exposes account-specific MCP operations and registered endpoints through the installed `@cotal-ai/linear` extension.


## linear

```bash
cotal ext add @cotal-ai/linear
cotal linear account add <name> --mode <write|readonly> (--token-stdin | --token-file <path>)
cotal linear account login <name> --mode <write|readonly>
cotal linear account show <name>
cotal linear inventory <account> [--json|--plain]
cotal linear call <account> <tool> [--args '<json>'] [--inventory <digest>] [--timeout <ms>]
cotal linear resource <account> <uri> [--inventory <digest>] [--timeout <ms>]
cotal linear prompt <account> <name> [--args '<json>'] [--inventory <digest>] [--timeout <ms>]
cotal linear serve <account> --endpoint <reverse-dns-name> [--space <s>] [--server <url>]
cotal linear caller <name> --endpoint <reverse-dns-name> --out <path> [--channels <a,b>] [--expires-in <s>] [--json|--plain]
```

Serves one Linear account as a registered Cotal endpoint that agents call with `cotal_describe` and
`cotal_invoke`, and gives the operator a direct client for the same server. Until the extension is
added, `cotal linear` and `cotal help linear` name the `cotal ext add` that adds it. It only talks to `https://mcp.linear.app/mcp`
(mode `write`) or `https://mcp.linear.app/mcp/readonly` (mode `readonly`); there is no URL or header
option. Use a separate account per Linear workspace, and prefer a `readonly` account with a
restricted read key wherever writes are not needed.


### Account credentials

An account's credential is sent as `Authorization: Bearer` and is never taken from argv. `account add`
takes a Linear API key: `--token-stdin` stores it under the cotal home as a 0600 file, and
`--token-file` points at an existing file that must not be readable by other users. The file is read
at each use, so rotating it needs no restart. `account login` runs Linear's OAuth flow instead: it
registers a client, prints the authorize URL, and waits on a loopback redirect. It asks for scope
`read` in `readonly` mode and `read write` in `write` mode, keeps the tokens in a 0600 file, and
refreshes them before a request is sent. OAuth requests only go to `https://mcp.linear.app`.
Redirects are refused so the credential is never forwarded.


### Native inventory

`inventory` reads the server's capabilities and every page of its tools, plus resources, resource
templates and prompts when the server advertises them. Names and schemas are printed as the server
sent them. The digest covers the whole inventory; pass it to `--inventory` so a call is refused
before dispatch if the inventory changed. Capabilities this command does not represent, such as
resource subscriptions or logging, are listed under `unsupported`.


### Finite output

Bare `linear` and `linear account` show their scoped operations without opening an account or
contacting the upstream. Account add/show, inventory and the native call/resource/prompt forms
support shared rich/plain reports and version-1 JSON envelopes. Scoped help and completion use
the same operation/flag declarations as validation, and each operation's help shows its own usage
line. Extra operands and wrong-mode flags refuse before account access; a flag the operation does
not take is named, with the flags it does take and its usage. Positional values are bounded to 8192 characters without terminal controls.
Piped tokens are bounded to 1 MiB of strict UTF-8; their contents and argument parse errors are
not echoed. Account reports describe recorded configuration or a native save acknowledgement,
not current access to Linear. An account name with no record refuses with `TARGET_NOT_FOUND` and
the `account add` route. A record that exists but cannot be read refuses with
`DEPENDENCY_UNAVAILABLE`.

`inventory --json` retains the full native inventory inside the result envelope. Human reports
show at most 60 whole tool rows. Read-only hints remain upstream declarations, not enforcement or
work progress. `call`, `resource` and `prompt` retain the complete native reply under
`result.reply` with `--json`; rich/plain output shows at most 60 whole result lines. The bearer
is scrubbed by the native client; other upstream content is not claimed to be secret-redacted.


### Native outcomes

A tool result with `isError: true` remains an MCP result (exit 0), with a visible warning. This does
not assert that the requested work succeeded. A native refusal with outcome `not-executed` returns
exit 2; that includes a deadline that expires while queued, discovering or connecting. An unknown
protocol/transport outcome returns exit 3 without replay, including for read-only requests. The
native outcome and reason remain in the result. Errors carry a status or class, not HTTP response
bodies. An interrupted account save or OAuth flow can return unknown because private state or an
external authorization may already have changed.


### Other output modes

OAuth login keeps the native human authorization flow, so it offers plain output but not JSON
and refuses when input is disabled. The existing foreground `serve` route keeps its native authority and human output; it does not
accept finite-output flags, and `--json` or `--plain` there refuses as a usage error (exit 1) that
says so. Its refusals carry their identifiers in the text report instead, such as the proposal to
approve on a user-auth mesh. The existing `caller` route
keeps its native static issuer but supports shared JSON/plain private-file receipts. Its output
must be outside managed state in an existing protected physical parent. An existing destination
refuses before provisioning. Publication is create-only under the cooperative output lock, and
uncertain provisioning/publication is not retried. It prints neither credentials nor environment-
export launch recipes. The preferred supported standalone path is now `auth mint --endpoint`
after a live digest-verified surface is available. No caller or managed launch is inferred from an
account, inventory or reply.


## Serving the endpoint

`serve` registers the account as an endpoint named by `--endpoint` and serves it until Ctrl-C. The
name must be a reverse-DNS name in a namespace you own, such as `com.example.linear`; it is
authorized for a static-auth mesh's local owner because you configured it. `serve` reads the inventory
first, so an account that cannot be reached is never registered. On a static-auth mesh, it publishes
the contract to the contract store, opens the issuance gate,
registers a fresh instance, writes its ready status, and mints a scoped serve credential through
the gate. Every step runs on short-lived executor credentials scoped to this one endpoint instance.
The space signer stays in the `serve` process for renewal at 75% of the credential's life and is
never written out, printed, or handed to the serving connection. Ctrl-C stops serving, closes the
Linear session and removes the instance's service record, each within 10 seconds. A start that
fails after registration removes the record before it exits and says whether that removal
completed; a registration that fails part-way rolls its own record back and reports when it could not.


### Endpoint command contract

The endpoint serves five fixed commands: `inventory`, `call-tool`, `read-resource`, `get-prompt` and
`complete`. Each needs the one `linear.mcp` capability. That capability is permission to call the
endpoint; which tools a call can reach is decided by the Linear account and its mode. `inventory`
replies in pages that fit a broker message: the first page carries the server, its capabilities,
its instructions and per-section counts, and every page carries entries verbatim with an opaque
cursor bound to the inventory digest. An entry is never cut: discovery refuses an inventory whose
first-page head (server, instructions, capabilities) or any entry prints over 48 KiB the way an
agent tool prints it (indented JSON, which grows with nesting), naming the entry, so the first page
and every entry it accepts fit one reply and the tool an agent reads it with. A cursor or `inventoryDigest` from an older inventory is refused before anything is sent to
Linear. `call-tool`, `read-resource`, `get-prompt` and `complete` require the digest the caller read.


## Callers

`caller` provisions an isolated caller on a static-auth mesh: an `agent` credential with its own
mailboxes and request rows for only the five Linear commands on that endpoint, plus the channels
in `--channels`. It carries no spawn, run, admin or provisioner capability. The credential is
written 0600 to a create-only `--out` outside managed state and never printed. Its physical parent
must already exist and be protected. The shared receipt contains the native identity, lifecycle,
expiry and acknowledged footprint, not credential bytes. An existing output refuses before
provisioning. A failure after native issuance starts stays unknown with confirmed identity/file
receipts retained, and is not retried. The command does not print environment-export launch recipes.

This remains the native standalone caller exporter, not a connector setup or generic caller bundle.
A supported bootstrap must carry its actual credential and minted lifecycle together; a file alone
is not a native caller identity. No seat is started, and managed static `cotal spawn` agents still
refuse endpoint capabilities. `auth mint --endpoint` now covers supported untargeted synchronous
surfaces through live native resolution. Targeted/journaled/action issuance and caller/launch
adapters are not supported.

On a user-auth mesh, `serve` uses the registered auth provider's service authority. The owner must
approve the original service proposal and the operator must bind its endpoint name. Until then
`serve` refuses with `PERMISSION_DENIED` (exit 2), and its Next line names the proposal and the
command that approves it:

```bash
cotal service-endpoint approve <proposalId> --caller-ceiling linear.mcp --space <space>
```

The `linear.mcp` ceiling covers the five Linear commands, so callers can be granted them later; an
approval without it has an empty ceiling and no caller can be granted. Grant an agent, for example:

```bash
cotal service-endpoint grant <proposalId> --to <owner.actor> --lifecycle <uid> --commands inventory --space <space>
```

Repeat the same `serve` command after approval. Its instance, registration body and request identities are retained;
a changed account, target or embedded contract refuses instead of replacing them. The shared
renewal source activates and renews the access bearer. The service verifies the signed bearer and
embedded cluster before serving, and reports ready only after the broker flush and host acknowledgement.
No local signer or ordinary enrolled agent is substituted.

Stopping a user-auth service closes its serving connection, renewal source and Linear session.
It retains the approved registration for a later start; stopping does not claim remote revocation.
Use the space's service approval and exact caller-grant controls to revoke access. Callers need
explicit instance-scoped grants through that remote authority. An account or visible registration
does not grant invocation rights. The standalone `caller` credential exporter remains static-auth
only. Open meshes refuse because they have no credential system to scope.

Not represented yet, and listed under `unsupported` when the server advertises them: resource
subscriptions, logging, progress notifications and experimental capabilities. The server cannot
call back for sampling, elicitation or roots. Every call acts as the account's one Linear identity.


## Native Linear service

The `linear` service preset uses an existing native `account`, a reverse-DNS `endpoint` in a
namespace you own, and the selected registered static-auth or user-auth `root`. An optional `server` only
repeats its broker. Account login/token setup remains a separate explicit operation. A start whose
account has no record refuses with `TARGET_NOT_FOUND` and the `linear account add` route before a
child is launched. That check reads the Cotal home of the process that runs `service start`. While
`COTAL_HOME` is unset, a definition whose `envRefs` set `HOME` (on Windows, `LOCALAPPDATA` or
`USERPROFILE`) can give the child another home, so that start refuses with `UNSUPPORTED` before a
child is launched. Set `COTAL_HOME` for the start, or remove that reference.
`service add` checks only the declared option types and lengths. The account
and the reverse-DNS endpoint name are checked when the service starts, and an invalid endpoint
name refuses that start before a child is launched, leaving a failed instance record. The native
static issuer, inventory, registration, credential renewal, request handling and deregistration
stay in the Linear implementation. No generic caller authority, new account or broker dependency
is supplied by the preset. User-auth spaces use their remote service authority and retain the
original approved registration on restart. Open spaces refuse instead of using another issuer.


### Linear service readiness

On static-auth roots, an explicit start can allocate another instance through the native registration path. The preset
declares foreground control through the shared terminal-bound lifetime; OS-supervised service
control is not supported. The child keeps the selected
root and registered TLS requirement and does not print upstream bodies or credentials into its
startup report. Its private phase records initialized native serve handlers and installed shutdown
handlers. It does not claim a broker-flushed subscription, a fresh authenticated serving response
or upstream business-work completion. Reports label it process/adapter initialization, not
endpoint-serving proof.

Stop closes the native serving connection and Linear session. Static-auth roots also attempt bounded
deregistration; user-auth roots retain the original approved registration. Original process exit
still does not establish that deregistration, revocation or other cleanup completed. The legacy
`linear serve` foreground entry remains available for installed callers; the independent
service route does not call that parser or its printed launch output.


### Linear service stop

See the [current command contract](/api-reference/cli-integrations/#native-linear-service) for this behavior.
