Linear integration
linear exposes account-specific MCP operations and registered endpoints through the installed @cotal-ai/linear extension.
linear
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:
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:
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 for this behavior.