11"use client"
22
3- import { useState } from "react"
3+ import { useState , useEffect } from "react"
44import { Button } from "@/components/ui/button"
55
66interface McpConsentFormProps {
@@ -11,32 +11,29 @@ interface McpConsentFormProps {
1111 mcpCallbackUrl : string
1212}
1313
14- /**
15- * Client component for MCP OAuth consent approve/deny buttons.
16- *
17- * On approve: POSTs to MCP server /auth/callback with user identity.
18- * The MCP server then generates an auth code and redirects the browser
19- * back to the MCP client (Claude Desktop).
20- *
21- * On deny: Redirects back to MCP client with error=access_denied.
22- */
2314export function McpConsentForm ( {
2415 sessionId,
2516 userId,
2617 userRole,
2718 tenantId,
2819 mcpCallbackUrl,
2920} : McpConsentFormProps ) {
30- const [ loading , setLoading ] = useState < "approve " | "deny " | null > ( null )
21+ const [ state , setState ] = useState < "idle " | "redirecting " | "success" | "denied" | "error" > ( "idle" )
3122 const [ error , setError ] = useState < string | null > ( null )
3223
33- const handleApprove = async ( ) => {
34- setLoading ( "approve" )
24+ // After redirecting, show success state (the redirect may take a moment)
25+ useEffect ( ( ) => {
26+ if ( state === "redirecting" ) {
27+ const timer = setTimeout ( ( ) => setState ( "success" ) , 1500 )
28+ return ( ) => clearTimeout ( timer )
29+ }
30+ } , [ state ] )
31+
32+ const handleApprove = ( ) => {
33+ setState ( "redirecting" )
3534 setError ( null )
3635
3736 try {
38- // Redirect to MCP server callback via GET with query params
39- // The callback will generate an auth code and redirect back to Claude
4037 const url = new URL ( mcpCallbackUrl )
4138 url . searchParams . set ( "session_id" , sessionId )
4239 url . searchParams . set ( "user_id" , userId )
@@ -45,19 +42,49 @@ export function McpConsentForm({
4542 window . location . href = url . toString ( )
4643 } catch ( err ) {
4744 setError ( err instanceof Error ? err . message : "Failed to authorize" )
48- setLoading ( null )
45+ setState ( "error" )
4946 }
5047 }
5148
5249 const handleDeny = ( ) => {
53- setLoading ( "deny" )
54- // Close the window or show denied message
50+ setState ( "denied" )
5551 window . close ( )
56- // If window.close() doesn't work (not opened by script), show message
57- setTimeout ( ( ) => {
58- setLoading ( null )
59- setError ( "Authorization denied. You can close this window." )
60- } , 500 )
52+ }
53+
54+ if ( state === "success" ) {
55+ return (
56+ < div className = "space-y-4 text-center" >
57+ < div className = "mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-green-100 dark:bg-green-900/30" >
58+ < svg className = "h-6 w-6 text-green-600 dark:text-green-400" fill = "none" viewBox = "0 0 24 24" stroke = "currentColor" strokeWidth = { 2 } >
59+ < path strokeLinecap = "round" strokeLinejoin = "round" d = "M5 13l4 4L19 7" />
60+ </ svg >
61+ </ div >
62+ < div >
63+ < p className = "font-medium" > Authorization complete</ p >
64+ < p className = "mt-1 text-sm text-muted-foreground" >
65+ You can close this window and return to Claude Desktop.
66+ </ p >
67+ </ div >
68+ </ div >
69+ )
70+ }
71+
72+ if ( state === "denied" ) {
73+ return (
74+ < div className = "space-y-4 text-center" >
75+ < div className = "mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-muted" >
76+ < svg className = "h-6 w-6 text-muted-foreground" fill = "none" viewBox = "0 0 24 24" stroke = "currentColor" strokeWidth = { 2 } >
77+ < path strokeLinecap = "round" strokeLinejoin = "round" d = "M6 18L18 6M6 6l12 12" />
78+ </ svg >
79+ </ div >
80+ < div >
81+ < p className = "font-medium" > Authorization denied</ p >
82+ < p className = "mt-1 text-sm text-muted-foreground" >
83+ You can close this window.
84+ </ p >
85+ </ div >
86+ </ div >
87+ )
6188 }
6289
6390 return (
@@ -70,19 +97,19 @@ export function McpConsentForm({
7097
7198 < Button
7299 onClick = { handleApprove }
73- disabled = { loading !== null }
100+ disabled = { state === "redirecting" }
74101 className = "w-full"
75102 >
76- { loading === "approve " ? "Authorizing..." : "Approve" }
103+ { state === "redirecting " ? "Authorizing..." : "Approve" }
77104 </ Button >
78105
79106 < Button
80107 onClick = { handleDeny }
81- disabled = { loading !== null }
108+ disabled = { state === "redirecting" }
82109 variant = "outline"
83110 className = "w-full"
84111 >
85- { loading === "deny" ? "Denying..." : " Deny" }
112+ Deny
86113 </ Button >
87114 </ div >
88115 )
0 commit comments