mirror of
https://github.com/davidkaya/aryx.git
synced 2026-08-07 04:08:45 +02:00
feat: implement MCP OAuth 2.1 flow, token storage, and injection
- Create mcpTokenStore: in-memory token storage keyed by normalized server URL with automatic expiry checking - Create mcpOAuthService: full OAuth 2.1 + PKCE flow implementation - Protected Resource Metadata discovery (RFC 9728) - Authorization Server Metadata fetch (RFC 8414) - Dynamic Client Registration (RFC 7591) when no static client ID - PKCE S256 code challenge generation - Local HTTP callback server for auth code receipt - Browser-based consent via Electron shell.openExternal - Authorization code to token exchange - Add startSessionMcpAuth IPC channel and handler to trigger OAuth flow - Inject stored OAuth tokens as Authorization headers in buildRunTurnToolingConfig - Update McpAuthBanner with 'Authenticate in browser' button and loading state - Add tests for token store (7 tests) and token injection (3 tests) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -44,6 +44,7 @@ interface ChatPaneProps {
|
||||
onSetInteractionMode?: (mode: InteractionMode) => void;
|
||||
onDismissPlanReview?: () => void;
|
||||
onDismissMcpAuth?: () => void;
|
||||
onAuthenticateMcp?: () => void;
|
||||
onUpdateSessionModelConfig?: (config: {
|
||||
model: string;
|
||||
reasoningEffort?: ReasoningEffort;
|
||||
@@ -66,6 +67,7 @@ export function ChatPane({
|
||||
onSetInteractionMode,
|
||||
onDismissPlanReview,
|
||||
onDismissMcpAuth,
|
||||
onAuthenticateMcp,
|
||||
onUpdateSessionModelConfig,
|
||||
onUpdateSessionTooling,
|
||||
onUpdateSessionApprovalSettings,
|
||||
@@ -145,6 +147,10 @@ export function ChatPane({
|
||||
onDismissMcpAuth?.();
|
||||
}
|
||||
|
||||
function handleAuthenticateMcp() {
|
||||
onAuthenticateMcp?.();
|
||||
}
|
||||
|
||||
async function handleSessionModelConfigChange(config: {
|
||||
model: string;
|
||||
reasoningEffort?: ReasoningEffort;
|
||||
@@ -414,6 +420,7 @@ export function ChatPane({
|
||||
<div className="mb-3">
|
||||
<McpAuthBanner
|
||||
mcpAuth={pendingMcpAuth}
|
||||
onAuthenticate={handleAuthenticateMcp}
|
||||
onDismiss={handleDismissMcpAuth}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
import { useCallback } from 'react';
|
||||
import { KeyRound, X } from 'lucide-react';
|
||||
import { KeyRound, Loader2, X } from 'lucide-react';
|
||||
|
||||
import type { PendingMcpAuthRecord } from '@shared/domain/mcpAuth';
|
||||
|
||||
export function McpAuthBanner({
|
||||
mcpAuth,
|
||||
onAuthenticate,
|
||||
onDismiss,
|
||||
}: {
|
||||
mcpAuth: PendingMcpAuthRecord;
|
||||
onAuthenticate: () => void;
|
||||
onDismiss: () => void;
|
||||
}) {
|
||||
const handleAuthenticate = useCallback(() => {
|
||||
onAuthenticate();
|
||||
}, [onAuthenticate]);
|
||||
|
||||
const handleDismiss = useCallback(() => {
|
||||
onDismiss();
|
||||
}, [onDismiss]);
|
||||
@@ -51,11 +57,30 @@ export function McpAuthBanner({
|
||||
<p className="mt-2 text-[12px] text-red-400">{mcpAuth.errorMessage}</p>
|
||||
)}
|
||||
|
||||
<p className="mt-3 text-[12px] leading-relaxed text-zinc-400">
|
||||
{isAuthenticating
|
||||
? 'Waiting for authentication to complete in the browser…'
|
||||
: 'Authentication support for HTTP MCP servers is not yet available. Configure a static access token in the MCP server headers instead.'}
|
||||
</p>
|
||||
<div className="mt-3 flex items-center gap-3">
|
||||
<button
|
||||
className="inline-flex items-center gap-1.5 rounded-lg bg-amber-500/20 px-3 py-1.5 text-[12px] font-medium text-amber-200 transition hover:bg-amber-500/30 disabled:opacity-50"
|
||||
disabled={isAuthenticating}
|
||||
onClick={handleAuthenticate}
|
||||
type="button"
|
||||
>
|
||||
{isAuthenticating ? (
|
||||
<>
|
||||
<Loader2 className="size-3.5 animate-spin" />
|
||||
Authenticating…
|
||||
</>
|
||||
) : hasFailed ? (
|
||||
'Retry authentication'
|
||||
) : (
|
||||
'Authenticate in browser'
|
||||
)}
|
||||
</button>
|
||||
<span className="text-[11px] text-zinc-500">
|
||||
{isAuthenticating
|
||||
? 'Waiting for consent in the browser…'
|
||||
: 'Opens your browser for OAuth consent. Token is stored for this session only.'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user