Credentials & Connecting Providers
How you give Axtary access to GitHub / Slack / Linear without pasting long-lived tokens around — and where those secrets live.
The governing rule: the credential broker is local. Provider secrets stay on your machine and never reach Axtary's hosted control plane (see Core concepts → the two planes). There are three supported ways to supply a credential, in order of preference.
Which one should you use? Quick guide:
- Just trying it / one provider? Use
axtary connect <provider>(option 1) — a browser click, token stored locally. (GitHub is the exception; it uses the App install flow.) - Already run a secret manager (1Password, Vault, Doppler, AWS Secrets Manager)? Use env-name indirection (option 2) — Axtary reads the name of the env var and your tooling injects the value.
- Quick local bootstrap? Paste a token into an env var (option 3) — fine for a trial, weakest for anything ongoing.
In every case the secret stays on your machine; Axtary only ever holds the name of where to find it, never the value.
1. axtary connect — browser OAuth, token stored locally (recommended)
Instead of hand-copying a bot token, run a browser OAuth flow. The token is written to the local credential broker (OS keychain when available, else a 0600 file at ~/.axtary/credentials.json) and is never printed.
axtary connect linear # opens the browser, you click "Allow" (http loopback)
axtary connections # list connected providers (metadata only, never secrets)
axtary disconnect linear # remove a stored credential
This is the gh auth login model: a loopback redirect to 127.0.0.1 captures the authorization code locally and exchanges it locally. Axtary never sees your OAuth client secret or the resulting token — both stay on your machine.
One-time setup: bring your own OAuth app
Because Axtary is not a credential custodian, you register your own OAuth app and provide its client id/secret via local env (read by name, never stored by Axtary):
| Provider | Register | Redirect URI | Env |
|---|---|---|---|
| Linear ✅ loopback | linear.app → Settings → API → OAuth application (scopes read,write) | http://127.0.0.1:7878/callback | AXTARY_LINEAR_CLIENT_ID, AXTARY_LINEAR_CLIENT_SECRET |
| Slack ⚠️ needs HTTPS | api.slack.com/apps → OAuth & Permissions (scope chat:write) | https://<tunnel>/callback (Slack rejects http) | AXTARY_SLACK_CLIENT_ID, AXTARY_SLACK_CLIENT_SECRET |
| Jira Cloud ⚠️ needs HTTPS | developer.atlassian.com → OAuth 2.0 integration (read:jira-work, write:jira-work, read:me, offline_access) | https://<tunnel>/callback | AXTARY_JIRA_CLIENT_ID, AXTARY_JIRA_CLIENT_SECRET |
| Google Drive ⚠️ Picker HTTPS | Google Cloud Console → Drive API + Picker API → Web application OAuth client (drive.file only) | https://<tunnel>/callback | AXTARY_DRIVE_CLIENT_ID, AXTARY_DRIVE_CLIENT_SECRET |
# Linear — works with a plain http loopback:
export AXTARY_LINEAR_CLIENT_ID=...
export AXTARY_LINEAR_CLIENT_SECRET=...
axtary connect linear
Slack requires an HTTPS redirect URL — it rejects
http://127.0.0.1/http://localhost. The local loopback alone can't satisfy this, soaxtary connect slackfails fast unless you pass an HTTPS redirect (--redirect-uri https://<tunnel>/callback) that a tunnel (e.g. ngrok) forwards to your local port. For most teams it's simpler to keep Slack on the bot token in env (below) until the hosted OAuth broker ships (roadmap), which gives Slack a proper HTTPS callback with no tunnel.
Jira Cloud uses the same HTTPS callback pattern. axtary connect jira requests
offline access, discovers the account's accessible Jira sites, and stores the
selected cloud ID/site URL as non-secret metadata. See the
Jira Cloud connector guide.
Google Drive also requires a public HTTPS callback for its desktop Picker flow.
axtary connect drive requests only drive.file, opens one-file Picker, and
stores the selected file id as non-secret context beside the local OAuth token.
See the Google Drive connector guide.
# Slack via a tunnel: register the https URL in your Slack app, then:
axtary connect slack --redirect-uri https://your-subdomain.ngrok.io/callback --redirect-port 7878
Flags: --client-id <id> / --client-id-env NAME / --client-secret-env NAME override the defaults; --redirect-port is the local listen port; --redirect-uri is the URL advertised to the provider (defaults to the http loopback; use an HTTPS tunnel URL for Slack, Jira, and Google Drive Picker); --json for scripting.
Once connected, doctor, smoke, proxy, and run workflow all resolve the token from the broker automatically — a connected provider reads as ready even with no env var set. A broker token takes precedence over the env var; an expired OAuth credential falls back to the env var.
GitHub uses a different path: the GitHub App install flow with short-lived installation tokens (github.mode: app). There is no axtary connect github.
2. Bring your own secret manager (env-name indirection)
Every connector references its credential by env-var name, never by value (tokenEnv, privateKeyEnv, appIdEnv, accessKeyIdEnv, …). That env var can be populated by whatever secret manager you already run — Axtary reads the name and lets your tooling inject the value:
# 1Password
op run --env-file=.env.axtary -- axtary run workflow github-pr-review --real ...
# Doppler
doppler run -- axtary proxy --config axtary.yml
# Vault (agent/template renders the env), AWS Secrets Manager, SOPS, etc.
Nothing about the secret is committed, logged, or stored by Axtary. This is the zero-extra-setup path for teams that already centralize secrets, and it composes with the broker (broker first, then env).
3. Manual token (local bootstrap)
The simplest path still works: set the connector's tokenEnv to a token you paste into your shell/secret store. Fine for a quick local trial; prefer (1) or (2) for anything ongoing because long-lived pasted tokens are the weakest option.
Where secrets live (and don't)
- Broker file:
~/.axtary/credentials.json,0600. Holds connection metadata (provider, scopes, expiry, source) plus — only when the keychain is unavailable — the token. On macOS the token is kept in the login keychain and the file holds metadata only. axtary connectionsreads metadata only, so it can never print a secret.- Tokens are never written to the ledger, hosted sync payloads, dashboard, or logs.
- The hosted dashboard cannot see local credentials by design; its connectors page documents
axtary connectrather than showing connection state.
Remote MCP servers over OAuth (axtary mcp login)
OAuth-protected remote MCP servers use a discovery-driven flow rather than a pre-registered provider app. axtary mcp login --wrap-url <https url> discovers the server's authorization server (RFC 9728 Protected Resource Metadata → RFC 8414 Authorization Server Metadata), registers a public PKCE client automatically when the server supports Dynamic Client Registration (RFC 7591), and completes a loopback browser consent — the gh auth login model again.
- The token is audience-bound to that exact server by the RFC 8707
resourceparameter; a server advertising a mismatched resource or issuer is rejected before any token is requested. - The bearer and refresh token live only in the same local broker (keychain /
0600file) and are injected only into the upstream MCP request. They are never written to pins, the ledger, attestations, CLI status, or the dashboard. - Use it:
axtary mcp serve --wrap-url <url> --oauth(and--oauthonmcp review/mcp conformance). Inspect sessions withaxtary mcp sessions(resource/issuer/scopes/expiry only); disconnect withaxtary mcp logout --wrap-url <url>.
This is verified live (2026-06-24) against the real Sentry remote MCP server (https://mcp.sentry.dev/mcp): a dynamically-registered client and resource-bound PKCE consent governed a real read call end to end, with an independently-verified signed ledger export and the bearer never leaving the local plane. See MCP wrapper and spec/mcp-remote-oauth-v0.md.
Roadmap (Coming Soon)
- AWS/GCP keyless via Workload Identity Federation / OIDC — no static keys at all; exchange a short-lived OIDC token for a short-lived provider token. The gold standard for cloud, implemented when a cloud design partner needs guarded cloud actions.
- Hosted OAuth broker (planned) — a future optional mode where the hosted plane would hold only the OAuth client secret, perform the code exchange, and hand the token straight to the local plane without persisting it. This is not available today.
- Device authorization grant (RFC 8628) for headless/CI where no browser is available on the same machine.
- Provider OAuth refresh expansion — remote-MCP, Jira, and Google Drive refresh locally today; the remaining work is equivalent refresh handling for every other first-party provider flow that issues short-lived access tokens.
Next: Integrate your agent to route Claude Code or an MCP client through Axtary.