MCP proxy to Linear with Keycard-brokered credentials. No static API keys — the user's Keycard bearer token is exchanged for a Linear-scoped OAuth token via RFC 8693 on every tool call.
Exposes two tools:
search— regex-search Linear's MCP tool catalogexecute— call any Linear tool by name with brokered credentials
- Python 3.10+
- uv
- Keycard CLI
- A Keycard zone with an STS provider and a vault provider
Follow SPEC.md to provision the required Keycard resources (Linear provider, applications, resources, dependency wiring, vault credentials), or use the keycard-template-app skill to do it automatically.
-
Install dependencies
uv sync
-
Configure
.envcp .env.example .env # Fill in KEYCARD_URL and PORT -
Configure
keycard.tomlFill in your
[org].idand[zone].id, then append the two[[credentials.default]]entries as described inSPEC.md §2.
keycard run -- uvicorn main:app --host 0.0.0.0 --port 8000keycard run brokers KEYCARD_CLIENT_ID and KEYCARD_CLIENT_SECRET from the zone vault into the process environment. Running uvicorn directly (without keycard run) will fail at startup because credentials cannot be discovered.
Claude → [Keycard bearer token] → proxy → [RFC 8693 token exchange] → [Linear token] → Linear MCP
- Claude authenticates to the proxy with a Keycard bearer token.
- When a tool is called,
@auth_provider.grant("https://mcp.linear.app/mcp")exchanges that token for a Linear-scoped OAuth token via the zone's STS. - The proxy opens a fresh MCP session to
https://mcp.linear.app/mcpwith the brokered token and forwards the call. - The brokered token is scoped to the call duration — no Linear tokens are cached or stored.
- Swap the upstream — change
LINEAR_MCP_URLinupstream.pyand register the new upstream's Keycard primitives (§1a–§1c ofSPEC.md). - Add direct tools — add a file to
tools/following the pattern intools/search.pyortools/execute.py, without@auth_provider.grant(). - Proxy multiple upstreams — pass a list of resource URLs to
@auth_provider.grant(["url1", "url2"])and callaccess_ctx.access(url)per resource.