Now it's working!
This commit is contained in:
parent
2b7fcf4725
commit
5f189c23b3
1 changed files with 28 additions and 20 deletions
|
@ -97,6 +97,8 @@ class BrowserOAuthClientProvider implements OAuthClientProvider {
|
||||||
private clientName: string
|
private clientName: string
|
||||||
private clientUri: string
|
private clientUri: string
|
||||||
private callbackUrl: string
|
private callbackUrl: string
|
||||||
|
// Store additional options for popup windows
|
||||||
|
private popupFeatures: string
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
readonly serverUrl: string,
|
readonly serverUrl: string,
|
||||||
|
@ -105,6 +107,7 @@ class BrowserOAuthClientProvider implements OAuthClientProvider {
|
||||||
clientName?: string
|
clientName?: string
|
||||||
clientUri?: string
|
clientUri?: string
|
||||||
callbackUrl?: string
|
callbackUrl?: string
|
||||||
|
popupFeatures?: string
|
||||||
} = {},
|
} = {},
|
||||||
) {
|
) {
|
||||||
this.storageKeyPrefix = options.storageKeyPrefix || 'mcp:auth'
|
this.storageKeyPrefix = options.storageKeyPrefix || 'mcp:auth'
|
||||||
|
@ -112,6 +115,7 @@ class BrowserOAuthClientProvider implements OAuthClientProvider {
|
||||||
this.clientName = options.clientName || 'MCP Browser Client'
|
this.clientName = options.clientName || 'MCP Browser Client'
|
||||||
this.clientUri = options.clientUri || window.location.origin
|
this.clientUri = options.clientUri || window.location.origin
|
||||||
this.callbackUrl = options.callbackUrl || new URL('/oauth/callback', window.location.origin).toString()
|
this.callbackUrl = options.callbackUrl || new URL('/oauth/callback', window.location.origin).toString()
|
||||||
|
this.popupFeatures = options.popupFeatures || 'width=600,height=700,resizable=yes,scrollbars=yes'
|
||||||
}
|
}
|
||||||
|
|
||||||
get redirectUrl(): string {
|
get redirectUrl(): string {
|
||||||
|
@ -216,12 +220,21 @@ class BrowserOAuthClientProvider implements OAuthClientProvider {
|
||||||
localStorage.setItem(key, JSON.stringify(tokens))
|
localStorage.setItem(key, JSON.stringify(tokens))
|
||||||
}
|
}
|
||||||
|
|
||||||
async redirectToAuthorization(
|
/**
|
||||||
|
* Redirect method that matches the interface expected by OAuthClientProvider
|
||||||
|
*/
|
||||||
|
async redirectToAuthorization(authorizationUrl: URL): Promise<void> {
|
||||||
|
// Simply open the URL in the current window
|
||||||
|
console.log('WE WERE ABOUT TO REDIRECT BUT WE DONT DO THAT HERE')
|
||||||
|
// window.location.href = authorizationUrl.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extended popup-based authorization method specific to browser environments
|
||||||
|
*/
|
||||||
|
async openAuthorizationPopup(
|
||||||
authorizationUrl: URL,
|
authorizationUrl: URL,
|
||||||
metadata: OAuthMetadata,
|
metadata: OAuthMetadata,
|
||||||
options?: {
|
|
||||||
popupFeatures?: string
|
|
||||||
},
|
|
||||||
): Promise<{ success: boolean; popupBlocked?: boolean; url: string }> {
|
): Promise<{ success: boolean; popupBlocked?: boolean; url: string }> {
|
||||||
// Store the auth state for the popup flow
|
// Store the auth state for the popup flow
|
||||||
const state = Math.random().toString(36).substring(2)
|
const state = Math.random().toString(36).substring(2)
|
||||||
|
@ -238,14 +251,13 @@ class BrowserOAuthClientProvider implements OAuthClientProvider {
|
||||||
authorizationUrl.searchParams.set('state', state)
|
authorizationUrl.searchParams.set('state', state)
|
||||||
|
|
||||||
const authUrl = authorizationUrl.toString()
|
const authUrl = authorizationUrl.toString()
|
||||||
const popupFeatures = options?.popupFeatures || 'width=600,height=700,resizable=yes,scrollbars=yes'
|
|
||||||
|
|
||||||
// Store the auth URL in case we need it for manual authentication
|
// Store the auth URL in case we need it for manual authentication
|
||||||
localStorage.setItem(this.getKey('auth_url'), authUrl)
|
localStorage.setItem(this.getKey('auth_url'), authUrl)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Open the authorization URL in a popup window
|
// Open the authorization URL in a popup window
|
||||||
const popup = window.open(authUrl, 'mcp_auth', popupFeatures)
|
const popup = window.open(authUrl, 'mcp_auth', this.popupFeatures)
|
||||||
|
|
||||||
// Check if popup was blocked or closed immediately
|
// Check if popup was blocked or closed immediately
|
||||||
if (!popup || popup.closed || popup.closed === undefined) {
|
if (!popup || popup.closed || popup.closed === undefined) {
|
||||||
|
@ -735,10 +747,10 @@ class McpClient {
|
||||||
// Parse tokens to make sure they're valid
|
// Parse tokens to make sure they're valid
|
||||||
const tokens = JSON.parse(storedTokens)
|
const tokens = JSON.parse(storedTokens)
|
||||||
if (tokens.access_token) {
|
if (tokens.access_token) {
|
||||||
this.addLog('info', 'Found tokens in localStorage via polling')
|
console.log('Found tokens in localStorage via polling')
|
||||||
// Resolve with an object that indicates tokens are already available
|
// Resolve with an object that indicates tokens are already available
|
||||||
// This will signal to handleAuthCompletion that no token exchange is needed
|
// This will signal to handleAuthCompletion that no token exchange is needed
|
||||||
resolve({ tokensAlreadyExchanged: true })
|
resolve('TOKENS_ALREADY_EXCHANGED')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -759,9 +771,7 @@ class McpClient {
|
||||||
// Redirect to authorization
|
// Redirect to authorization
|
||||||
this.addLog('info', 'Opening authorization window...')
|
this.addLog('info', 'Opening authorization window...')
|
||||||
assert(this.metadata, 'Metadata not available')
|
assert(this.metadata, 'Metadata not available')
|
||||||
const redirectResult = await this.authProvider.redirectToAuthorization(this.authUrlRef, this.metadata, {
|
const redirectResult = await this.authProvider.openAuthorizationPopup(this.authUrlRef, this.metadata)
|
||||||
popupFeatures: this.options.popupFeatures,
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!redirectResult.success) {
|
if (!redirectResult.success) {
|
||||||
// Popup was blocked
|
// Popup was blocked
|
||||||
|
@ -782,25 +792,23 @@ class McpClient {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle authentication completion
|
* Handle authentication completion
|
||||||
* @param codeOrResult - Either the authorization code or an object indicating tokens are already available
|
* @param code - The authorization code or special token indicator
|
||||||
*/
|
*/
|
||||||
async handleAuthCompletion(codeOrResult: string | { tokensAlreadyExchanged: boolean }): Promise<void> {
|
async handleAuthCompletion(code: string): Promise<void> {
|
||||||
if (!this.authProvider || !this.transport) {
|
if (!this.authProvider || !this.transport) {
|
||||||
throw new Error('Authentication context not available')
|
throw new Error('Authentication context not available')
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Check if we received an object indicating tokens are already available
|
// Check if this is our special token indicator
|
||||||
if (typeof codeOrResult === 'object' && codeOrResult.tokensAlreadyExchanged) {
|
if (code === 'TOKENS_ALREADY_EXCHANGED') {
|
||||||
this.addLog('info', 'Using already exchanged tokens from localStorage')
|
this.addLog('info', 'Using already exchanged tokens from localStorage')
|
||||||
// No need to exchange tokens, they're already in localStorage
|
// No need to exchange tokens, they're already in localStorage
|
||||||
} else if (typeof codeOrResult === 'string') {
|
} else {
|
||||||
// We received an authorization code that needs to be exchanged
|
// We received an authorization code that needs to be exchanged
|
||||||
this.addLog('info', 'Finishing authorization with code exchange...')
|
this.addLog('info', 'Finishing authorization with code exchange...')
|
||||||
await this.transport.finishAuth(codeOrResult)
|
await this.transport.finishAuth(code)
|
||||||
this.addLog('info', 'Authorization code exchanged for tokens')
|
this.addLog('info', 'Authorization code exchanged for tokens')
|
||||||
} else {
|
|
||||||
throw new Error('Invalid authentication result')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.addLog('info', 'Authorization completed')
|
this.addLog('info', 'Authorization completed')
|
||||||
|
@ -986,7 +994,7 @@ export function useMcp(options: UseMcpOptions): UseMcpResult {
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// Tokens were already exchanged by the popup
|
// Tokens were already exchanged by the popup
|
||||||
client.handleAuthCompletion({ tokensAlreadyExchanged: true }).catch((err) => {
|
client.handleAuthCompletion('TOKENS_ALREADY_EXCHANGED').catch((err) => {
|
||||||
console.error('Auth callback error:', err)
|
console.error('Auth callback error:', err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue