Skip to content

Add interactive OAuth login flow#36

Open
itz4blitz wants to merge 1 commit into
tacticlaunch:mainfrom
itz4blitz:justin/oauth-login
Open

Add interactive OAuth login flow#36
itz4blitz wants to merge 1 commit into
tacticlaunch:mainfrom
itz4blitz:justin/oauth-login

Conversation

@itz4blitz

@itz4blitz itz4blitz commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds an interactive OAuth login so the server can authenticate to Linear without anyone pasting a long-lived API key. This is a standalone change off main.

mcp-linear auth login --client-id ... --client-secret ...   # browser-based sign-in
mcp-linear auth status                                      # scopes, expiry, masked token suffix
mcp-linear auth logout                                      # best-effort revoke + delete
mcp-linear                                                  # server uses the stored credentials automatically

Auth flow

  • auth login runs an OAuth 2.0 authorization-code flow against https://linear.app/oauth/authorize with PKCE (S256) and a random state, using a loopback callback server on 127.0.0.1 (default port 8734, overridable with --redirect-port; must match a registered redirect URI). The client id/secret come from --client-id/--client-secret or LINEAR_OAUTH_CLIENT_ID/LINEAR_OAUTH_CLIENT_SECRET.
  • The callback server accepts exactly one redirect, rejects state mismatches and error responses, then shuts down (also on timeout or cancellation).
  • The server refreshes the stored access token automatically when it is within 60s of expiry — checked at startup and before requests, with refreshes serialized so concurrent requests never race a rotation. Linear rotates refresh tokens; each rotated token is persisted before use. The Linear client is rebuilt only when the access token actually changes.

Storage and security

  • Credentials are stored in ~/.config/mcp-linear/credentials.json ($XDG_CONFIG_HOME and MCP_LINEAR_CONFIG_DIR honored), file mode 0600 inside a 0700 directory, written atomically (temp file + rename).
  • A corrupt or partially written store is tolerated: it reads as "not logged in" instead of crashing the server.
  • Token and secret values are never printed or logged; auth status shows only a masked suffix, and OAuth HTTP errors are sanitized to the HTTP status.
  • auth logout revokes the access token best-effort and deletes the local file even when revocation fails (e.g. offline).

Precedence

  1. Explicit --token CLI flag
  2. LINEAR_API_TOKEN / LINEAR_API_KEY environment variables
  3. Credentials stored by mcp-linear auth login (used as LinearClient({ accessToken }))

Explicit credentials always win over the store, so existing setups are completely unaffected. This also composes cleanly with #34: if #34 merges, its explicit OAuth-token flags simply take precedence above the stored credentials, and a trivial rebase slots the store fallback below them.

Verification

  • npx jest: 33 suites, 228 tests passing (58 cover the auth modules and config precedence: PKCE, state mismatch, store permissions, corrupt-store tolerance, refresh rotation persistence, no secrets in output, credential precedence). Tests perform no network I/O and write only under temp dirs via MCP_LINEAR_CONFIG_DIR.
  • npm run build, npm run test:mcp-smoke (185 tools, 6 resources, 4 prompts), npm pack --dry-run, and git diff --check all pass.
  • Manually exercised the built CLI (auth, auth status, auth logout, missing-credential server startup) and booted the MCP server end-to-end from a seeded credential store with no token in the environment.

No tool definitions or handlers are touched, and there is no package version bump.

@itz4blitz
itz4blitz marked this pull request as ready for review July 12, 2026 15:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant