Problem
Junior currently sends users directly to provider OAuth authorization URLs when a provider requires authorization. This works for normal cases, but it leaves two reliability problems:
- For generic plugin OAuth, the provider authorization URL and Junior
oauth-state:* record are created when the agent pauses. The callback state is valid for only 10 minutes.
- For MCP OAuth, the MCP SDK creates the upstream authorization URL during the auth challenge. Junior can store its local MCP auth session longer, but the provider authorization URL has already been minted and may expire or become invalid before the user clicks it.
Recent MCP auth debugging also showed that provider-side redirect URI validation can fail when dynamic OAuth client information is stale or was registered for a different Junior callback URL. That specific stale-client bug should be fixed separately, but a first-party start URL would make the OAuth initiation boundary clearer and more controllable.
Proposal
Send a Junior-owned authorization start URL in Slack instead of sending provider authorization URLs directly.
Suggested routes:
GET /api/oauth/start/:provider for generic plugin OAuth
GET /api/oauth/start/mcp/:provider for MCP OAuth
The Slack message would contain only the Junior URL. When the user clicks it, Junior should:
- Validate a signed or server-stored auth intent with a longer TTL than provider callback state.
- Verify the intent still belongs to the requesting user/provider/session.
- Create the short-lived provider OAuth state at click time.
- For MCP, initiate the MCP SDK authorization flow at click time so PKCE, state, client registration, and provider authorize URL are fresh.
- Redirect the browser to the upstream provider authorization endpoint.
The callback routes can stay largely the same: provider redirects still land on the existing Junior callback paths after the user approves.
Rationale
This avoids minting provider authorization URLs before the user is ready to use them. It gives Junior control over auth-link lifetime and error pages, reduces Slack-visible provider URL exposure, and creates a single place to handle stale intents, superseded requests, and provider-specific retry messaging.
This should apply to both MCP and non-MCP OAuth. A simple redirect proxy to the already-stored provider URL is not enough for MCP, because it would not refresh provider-side state or PKCE; the upstream OAuth authorization flow needs to start when the user opens Junior's link.
Acceptance Criteria
- Slack auth messages for both plugin OAuth and MCP OAuth contain Junior-owned start URLs, not provider authorize URLs.
- Generic plugin OAuth creates
oauth-state:* only when the start URL is opened.
- MCP OAuth creates or refreshes the upstream provider authorize URL only when the start URL is opened.
- Existing callback resume behavior remains unchanged for successful authorization.
- Expired or stale Junior start links render a safe, actionable HTML response and do not resume superseded Slack work.
Problem
Junior currently sends users directly to provider OAuth authorization URLs when a provider requires authorization. This works for normal cases, but it leaves two reliability problems:
oauth-state:*record are created when the agent pauses. The callback state is valid for only 10 minutes.Recent MCP auth debugging also showed that provider-side redirect URI validation can fail when dynamic OAuth client information is stale or was registered for a different Junior callback URL. That specific stale-client bug should be fixed separately, but a first-party start URL would make the OAuth initiation boundary clearer and more controllable.
Proposal
Send a Junior-owned authorization start URL in Slack instead of sending provider authorization URLs directly.
Suggested routes:
GET /api/oauth/start/:providerfor generic plugin OAuthGET /api/oauth/start/mcp/:providerfor MCP OAuthThe Slack message would contain only the Junior URL. When the user clicks it, Junior should:
The callback routes can stay largely the same: provider redirects still land on the existing Junior callback paths after the user approves.
Rationale
This avoids minting provider authorization URLs before the user is ready to use them. It gives Junior control over auth-link lifetime and error pages, reduces Slack-visible provider URL exposure, and creates a single place to handle stale intents, superseded requests, and provider-specific retry messaging.
This should apply to both MCP and non-MCP OAuth. A simple redirect proxy to the already-stored provider URL is not enough for MCP, because it would not refresh provider-side state or PKCE; the upstream OAuth authorization flow needs to start when the user opens Junior's link.
Acceptance Criteria
oauth-state:*only when the start URL is opened.