Prettified
This commit is contained in:
parent
c1aef99817
commit
551b545090
3 changed files with 236 additions and 236 deletions
|
|
@ -1,41 +1,43 @@
|
|||
// browser-provider.ts
|
||||
import { OAuthClientInformation, OAuthMetadata, OAuthTokens, OAuthClientMetadata } from '@modelcontextprotocol/sdk/shared/auth.js';
|
||||
import { OAuthClientProvider } from '@modelcontextprotocol/sdk/client/auth.js';
|
||||
import { OAuthClientInformation, OAuthMetadata, OAuthTokens, OAuthClientMetadata } from '@modelcontextprotocol/sdk/shared/auth.js'
|
||||
import { OAuthClientProvider } from '@modelcontextprotocol/sdk/client/auth.js'
|
||||
// Assuming StoredState is defined in ./types.js and includes fields for provider options
|
||||
import { StoredState } from './types.js'; // Adjust path if necessary
|
||||
import { StoredState } from './types.js' // Adjust path if necessary
|
||||
|
||||
/**
|
||||
* Browser-compatible OAuth client provider for MCP using localStorage.
|
||||
*/
|
||||
export class BrowserOAuthClientProvider implements OAuthClientProvider {
|
||||
readonly serverUrl: string;
|
||||
readonly storageKeyPrefix: string;
|
||||
readonly serverUrlHash: string;
|
||||
readonly clientName: string;
|
||||
readonly clientUri: string;
|
||||
readonly callbackUrl: string;
|
||||
readonly serverUrl: string
|
||||
readonly storageKeyPrefix: string
|
||||
readonly serverUrlHash: string
|
||||
readonly clientName: string
|
||||
readonly clientUri: string
|
||||
readonly callbackUrl: string
|
||||
|
||||
constructor(
|
||||
serverUrl: string,
|
||||
options: {
|
||||
storageKeyPrefix?: string;
|
||||
clientName?: string;
|
||||
clientUri?: string;
|
||||
callbackUrl?: string;
|
||||
storageKeyPrefix?: string
|
||||
clientName?: string
|
||||
clientUri?: string
|
||||
callbackUrl?: string
|
||||
} = {},
|
||||
) {
|
||||
this.serverUrl = serverUrl;
|
||||
this.storageKeyPrefix = options.storageKeyPrefix || 'mcp:auth';
|
||||
this.serverUrlHash = this.hashString(serverUrl);
|
||||
this.clientName = options.clientName || 'MCP Browser Client';
|
||||
this.clientUri = options.clientUri || (typeof window !== 'undefined' ? window.location.origin : '');
|
||||
this.callbackUrl = options.callbackUrl || (typeof window !== 'undefined' ? new URL('/oauth/callback', window.location.origin).toString() : '/oauth/callback');
|
||||
this.serverUrl = serverUrl
|
||||
this.storageKeyPrefix = options.storageKeyPrefix || 'mcp:auth'
|
||||
this.serverUrlHash = this.hashString(serverUrl)
|
||||
this.clientName = options.clientName || 'MCP Browser Client'
|
||||
this.clientUri = options.clientUri || (typeof window !== 'undefined' ? window.location.origin : '')
|
||||
this.callbackUrl =
|
||||
options.callbackUrl ||
|
||||
(typeof window !== 'undefined' ? new URL('/oauth/callback', window.location.origin).toString() : '/oauth/callback')
|
||||
}
|
||||
|
||||
// --- SDK Interface Methods ---
|
||||
|
||||
get redirectUrl(): string {
|
||||
return this.callbackUrl;
|
||||
return this.callbackUrl
|
||||
}
|
||||
|
||||
get clientMetadata(): OAuthClientMetadata {
|
||||
|
|
@ -47,68 +49,69 @@ export class BrowserOAuthClientProvider implements OAuthClientProvider {
|
|||
client_name: this.clientName,
|
||||
client_uri: this.clientUri,
|
||||
// scope: 'openid profile email mcp', // Example scopes, adjust as needed
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async clientInformation(): Promise<OAuthClientInformation | undefined> {
|
||||
const key = this.getKey('client_info');
|
||||
const data = localStorage.getItem(key);
|
||||
if (!data) return undefined;
|
||||
const key = this.getKey('client_info')
|
||||
const data = localStorage.getItem(key)
|
||||
if (!data) return undefined
|
||||
try {
|
||||
// TODO: Add validation using a schema
|
||||
return JSON.parse(data) as OAuthClientInformation;
|
||||
return JSON.parse(data) as OAuthClientInformation
|
||||
} catch (e) {
|
||||
console.warn(`[${this.storageKeyPrefix}] Failed to parse client information:`, e);
|
||||
localStorage.removeItem(key);
|
||||
return undefined;
|
||||
console.warn(`[${this.storageKeyPrefix}] Failed to parse client information:`, e)
|
||||
localStorage.removeItem(key)
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: The SDK's auth() function uses this if dynamic registration is needed.
|
||||
// Ensure your OAuthClientInformationFull matches the expected structure if DCR is used.
|
||||
async saveClientInformation(clientInformation: OAuthClientInformation /* | OAuthClientInformationFull */): Promise<void> {
|
||||
const key = this.getKey('client_info');
|
||||
const key = this.getKey('client_info')
|
||||
// Cast needed if handling OAuthClientInformationFull specifically
|
||||
localStorage.setItem(key, JSON.stringify(clientInformation));
|
||||
localStorage.setItem(key, JSON.stringify(clientInformation))
|
||||
}
|
||||
|
||||
|
||||
async tokens(): Promise<OAuthTokens | undefined> {
|
||||
const key = this.getKey('tokens');
|
||||
const data = localStorage.getItem(key);
|
||||
if (!data) return undefined;
|
||||
const key = this.getKey('tokens')
|
||||
const data = localStorage.getItem(key)
|
||||
if (!data) return undefined
|
||||
try {
|
||||
// TODO: Add validation
|
||||
return JSON.parse(data) as OAuthTokens;
|
||||
return JSON.parse(data) as OAuthTokens
|
||||
} catch (e) {
|
||||
console.warn(`[${this.storageKeyPrefix}] Failed to parse tokens:`, e);
|
||||
localStorage.removeItem(key);
|
||||
return undefined;
|
||||
console.warn(`[${this.storageKeyPrefix}] Failed to parse tokens:`, e)
|
||||
localStorage.removeItem(key)
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
async saveTokens(tokens: OAuthTokens): Promise<void> {
|
||||
const key = this.getKey('tokens');
|
||||
localStorage.setItem(key, JSON.stringify(tokens));
|
||||
const key = this.getKey('tokens')
|
||||
localStorage.setItem(key, JSON.stringify(tokens))
|
||||
// Clean up code verifier and last auth URL after successful token save
|
||||
localStorage.removeItem(this.getKey('code_verifier'));
|
||||
localStorage.removeItem(this.getKey('last_auth_url'));
|
||||
localStorage.removeItem(this.getKey('code_verifier'))
|
||||
localStorage.removeItem(this.getKey('last_auth_url'))
|
||||
}
|
||||
|
||||
async saveCodeVerifier(codeVerifier: string): Promise<void> {
|
||||
const key = this.getKey('code_verifier');
|
||||
localStorage.setItem(key, codeVerifier);
|
||||
const key = this.getKey('code_verifier')
|
||||
localStorage.setItem(key, codeVerifier)
|
||||
}
|
||||
|
||||
async codeVerifier(): Promise<string> {
|
||||
const key = this.getKey('code_verifier');
|
||||
const verifier = localStorage.getItem(key);
|
||||
const key = this.getKey('code_verifier')
|
||||
const verifier = localStorage.getItem(key)
|
||||
if (!verifier) {
|
||||
throw new Error(`[${this.storageKeyPrefix}] Code verifier not found in storage for key ${key}. Auth flow likely corrupted or timed out.`);
|
||||
throw new Error(
|
||||
`[${this.storageKeyPrefix}] Code verifier not found in storage for key ${key}. Auth flow likely corrupted or timed out.`,
|
||||
)
|
||||
}
|
||||
// SDK's auth() retrieves this BEFORE exchanging code. Don't remove it here.
|
||||
// It will be removed in saveTokens on success.
|
||||
return verifier;
|
||||
return verifier
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -118,8 +121,8 @@ export class BrowserOAuthClientProvider implements OAuthClientProvider {
|
|||
*/
|
||||
async redirectToAuthorization(authorizationUrl: URL): Promise<void> {
|
||||
// Generate a unique state parameter for this authorization request
|
||||
const state = crypto.randomUUID();
|
||||
const stateKey = `${this.storageKeyPrefix}:state_${state}`;
|
||||
const state = crypto.randomUUID()
|
||||
const stateKey = `${this.storageKeyPrefix}:state_${state}`
|
||||
|
||||
// Store context needed by the callback handler, associated with the state param
|
||||
const stateData: StoredState = {
|
||||
|
|
@ -132,32 +135,34 @@ export class BrowserOAuthClientProvider implements OAuthClientProvider {
|
|||
clientName: this.clientName,
|
||||
clientUri: this.clientUri,
|
||||
callbackUrl: this.callbackUrl,
|
||||
}
|
||||
};
|
||||
localStorage.setItem(stateKey, JSON.stringify(stateData));
|
||||
},
|
||||
}
|
||||
localStorage.setItem(stateKey, JSON.stringify(stateData))
|
||||
|
||||
// Add the state parameter to the URL
|
||||
authorizationUrl.searchParams.set('state', state);
|
||||
const authUrlString = authorizationUrl.toString();
|
||||
authorizationUrl.searchParams.set('state', state)
|
||||
const authUrlString = authorizationUrl.toString()
|
||||
|
||||
// Persist the exact auth URL in case the popup fails and manual navigation is needed
|
||||
localStorage.setItem(this.getKey('last_auth_url'), authUrlString);
|
||||
localStorage.setItem(this.getKey('last_auth_url'), authUrlString)
|
||||
|
||||
// Attempt to open the popup
|
||||
const popupFeatures = 'width=600,height=700,resizable=yes,scrollbars=yes,status=yes'; // Make configurable if needed
|
||||
const popupFeatures = 'width=600,height=700,resizable=yes,scrollbars=yes,status=yes' // Make configurable if needed
|
||||
try {
|
||||
const popup = window.open(authUrlString, `mcp_auth_${this.serverUrlHash}`, popupFeatures);
|
||||
const popup = window.open(authUrlString, `mcp_auth_${this.serverUrlHash}`, popupFeatures)
|
||||
|
||||
if (!popup || popup.closed || typeof popup.closed === 'undefined') {
|
||||
console.warn(`[${this.storageKeyPrefix}] Popup likely blocked by browser. Manual navigation might be required using the stored URL.`);
|
||||
console.warn(
|
||||
`[${this.storageKeyPrefix}] Popup likely blocked by browser. Manual navigation might be required using the stored URL.`,
|
||||
)
|
||||
// Cannot signal failure back via SDK auth() directly.
|
||||
// useMcp will need to rely on timeout or manual trigger if stuck.
|
||||
} else {
|
||||
popup.focus();
|
||||
console.info(`[${this.storageKeyPrefix}] Redirecting to authorization URL in popup.`);
|
||||
popup.focus()
|
||||
console.info(`[${this.storageKeyPrefix}] Redirecting to authorization URL in popup.`)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`[${this.storageKeyPrefix}] Error opening popup window:`, e);
|
||||
console.error(`[${this.storageKeyPrefix}] Error opening popup window:`, e)
|
||||
// Cannot signal failure back via SDK auth() directly.
|
||||
}
|
||||
// Regardless of popup success, the interface expects this method to initiate the redirect.
|
||||
|
|
@ -170,60 +175,59 @@ export class BrowserOAuthClientProvider implements OAuthClientProvider {
|
|||
* Retrieves the last URL passed to `redirectToAuthorization`. Useful for manual fallback.
|
||||
*/
|
||||
getLastAttemptedAuthUrl(): string | null {
|
||||
return localStorage.getItem(this.getKey('last_auth_url'));
|
||||
return localStorage.getItem(this.getKey('last_auth_url'))
|
||||
}
|
||||
|
||||
|
||||
clearStorage(): number {
|
||||
const prefixPattern = `${this.storageKeyPrefix}_${this.serverUrlHash}_`;
|
||||
const statePattern = `${this.storageKeyPrefix}:state_`;
|
||||
const keysToRemove: string[] = [];
|
||||
let count = 0;
|
||||
const prefixPattern = `${this.storageKeyPrefix}_${this.serverUrlHash}_`
|
||||
const statePattern = `${this.storageKeyPrefix}:state_`
|
||||
const keysToRemove: string[] = []
|
||||
let count = 0
|
||||
|
||||
for (let i = 0; i < localStorage.length; i++) {
|
||||
const key = localStorage.key(i);
|
||||
if (!key) continue;
|
||||
const key = localStorage.key(i)
|
||||
if (!key) continue
|
||||
|
||||
if (key.startsWith(prefixPattern)) {
|
||||
keysToRemove.push(key);
|
||||
keysToRemove.push(key)
|
||||
} else if (key.startsWith(statePattern)) {
|
||||
try {
|
||||
const item = localStorage.getItem(key);
|
||||
const item = localStorage.getItem(key)
|
||||
if (item) {
|
||||
// Check if state belongs to this provider instance based on serverUrlHash
|
||||
// We need to parse cautiously as the structure isn't guaranteed.
|
||||
const state = JSON.parse(item) as Partial<StoredState>;
|
||||
const state = JSON.parse(item) as Partial<StoredState>
|
||||
if (state.serverUrlHash === this.serverUrlHash) {
|
||||
keysToRemove.push(key);
|
||||
keysToRemove.push(key)
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn(`[${this.storageKeyPrefix}] Error parsing state key ${key} during clearStorage:`, e);
|
||||
console.warn(`[${this.storageKeyPrefix}] Error parsing state key ${key} during clearStorage:`, e)
|
||||
// Optionally remove malformed keys
|
||||
// keysToRemove.push(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const uniqueKeysToRemove = [...new Set(keysToRemove)];
|
||||
uniqueKeysToRemove.forEach(key => {
|
||||
localStorage.removeItem(key);
|
||||
count++;
|
||||
});
|
||||
return count;
|
||||
const uniqueKeysToRemove = [...new Set(keysToRemove)]
|
||||
uniqueKeysToRemove.forEach((key) => {
|
||||
localStorage.removeItem(key)
|
||||
count++
|
||||
})
|
||||
return count
|
||||
}
|
||||
|
||||
private hashString(str: string): string {
|
||||
let hash = 0;
|
||||
let hash = 0
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const char = str.charCodeAt(i);
|
||||
hash = (hash << 5) - hash + char;
|
||||
hash = hash & hash;
|
||||
const char = str.charCodeAt(i)
|
||||
hash = (hash << 5) - hash + char
|
||||
hash = hash & hash
|
||||
}
|
||||
return Math.abs(hash).toString(16);
|
||||
return Math.abs(hash).toString(16)
|
||||
}
|
||||
|
||||
getKey(keySuffix: string): string {
|
||||
return `${this.storageKeyPrefix}_${this.serverUrlHash}_${keySuffix}`;
|
||||
return `${this.storageKeyPrefix}_${this.serverUrlHash}_${keySuffix}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,100 +8,91 @@ import { StoredState } from './types.js' // Adjust path, ensure definition inclu
|
|||
* Assumes it's running on the page specified as the callbackUrl.
|
||||
*/
|
||||
export async function onMcpAuthorization() {
|
||||
const queryParams = new URLSearchParams(window.location.search);
|
||||
const code = queryParams.get('code');
|
||||
const state = queryParams.get('state');
|
||||
const error = queryParams.get('error');
|
||||
const errorDescription = queryParams.get('error_description');
|
||||
const queryParams = new URLSearchParams(window.location.search)
|
||||
const code = queryParams.get('code')
|
||||
const state = queryParams.get('state')
|
||||
const error = queryParams.get('error')
|
||||
const errorDescription = queryParams.get('error_description')
|
||||
|
||||
const logPrefix = '[mcp-callback]'; // Generic prefix, or derive from stored state later
|
||||
console.log(`${logPrefix} Handling callback...`, { code, state, error, errorDescription });
|
||||
const logPrefix = '[mcp-callback]' // Generic prefix, or derive from stored state later
|
||||
console.log(`${logPrefix} Handling callback...`, { code, state, error, errorDescription })
|
||||
|
||||
let provider: BrowserOAuthClientProvider | null = null;
|
||||
let storedStateData: StoredState | null = null;
|
||||
const stateKey = state ? `mcp:auth:state_${state}` : null; // Reconstruct state key prefix assumption
|
||||
let provider: BrowserOAuthClientProvider | null = null
|
||||
let storedStateData: StoredState | null = null
|
||||
const stateKey = state ? `mcp:auth:state_${state}` : null // Reconstruct state key prefix assumption
|
||||
|
||||
try {
|
||||
// --- Basic Error Handling ---
|
||||
if (error) {
|
||||
throw new Error(`OAuth error: ${error} - ${errorDescription || 'No description provided.'}`);
|
||||
throw new Error(`OAuth error: ${error} - ${errorDescription || 'No description provided.'}`)
|
||||
}
|
||||
if (!code) {
|
||||
throw new Error('Authorization code not found in callback query parameters.');
|
||||
throw new Error('Authorization code not found in callback query parameters.')
|
||||
}
|
||||
if (!state || !stateKey) {
|
||||
throw new Error('State parameter not found or invalid in callback query parameters.');
|
||||
throw new Error('State parameter not found or invalid in callback query parameters.')
|
||||
}
|
||||
|
||||
// --- Retrieve Stored State & Provider Options ---
|
||||
const storedStateJSON = localStorage.getItem(stateKey);
|
||||
const storedStateJSON = localStorage.getItem(stateKey)
|
||||
if (!storedStateJSON) {
|
||||
throw new Error(`Invalid or expired state parameter "${state}". No matching state found in storage.`);
|
||||
throw new Error(`Invalid or expired state parameter "${state}". No matching state found in storage.`)
|
||||
}
|
||||
try {
|
||||
storedStateData = JSON.parse(storedStateJSON) as StoredState;
|
||||
storedStateData = JSON.parse(storedStateJSON) as StoredState
|
||||
} catch (e) {
|
||||
throw new Error('Failed to parse stored OAuth state.');
|
||||
throw new Error('Failed to parse stored OAuth state.')
|
||||
}
|
||||
|
||||
// Validate expiry
|
||||
if (!storedStateData.expiry || storedStateData.expiry < Date.now()) {
|
||||
localStorage.removeItem(stateKey); // Clean up expired state
|
||||
throw new Error('OAuth state has expired. Please try initiating authentication again.');
|
||||
localStorage.removeItem(stateKey) // Clean up expired state
|
||||
throw new Error('OAuth state has expired. Please try initiating authentication again.')
|
||||
}
|
||||
|
||||
// Ensure provider options are present
|
||||
if (!storedStateData.providerOptions) {
|
||||
throw new Error('Stored state is missing required provider options.');
|
||||
throw new Error('Stored state is missing required provider options.')
|
||||
}
|
||||
const { serverUrl, ...providerOptions } = storedStateData.providerOptions;
|
||||
|
||||
const { serverUrl, ...providerOptions } = storedStateData.providerOptions
|
||||
|
||||
// --- Instantiate Provider ---
|
||||
console.log(`${logPrefix} Re-instantiating provider for server: ${serverUrl}`);
|
||||
provider = new BrowserOAuthClientProvider(serverUrl, providerOptions);
|
||||
console.log(`${logPrefix} Re-instantiating provider for server: ${serverUrl}`)
|
||||
provider = new BrowserOAuthClientProvider(serverUrl, providerOptions)
|
||||
|
||||
// --- Call SDK Auth Function ---
|
||||
console.log(`${logPrefix} Calling SDK auth() to exchange code...`);
|
||||
console.log(`${logPrefix} Calling SDK auth() to exchange code...`)
|
||||
// The SDK auth() function will internally:
|
||||
// 1. Use provider.clientInformation()
|
||||
// 2. Use provider.codeVerifier()
|
||||
// 3. Call exchangeAuthorization()
|
||||
// 4. Use provider.saveTokens() on success
|
||||
const authResult = await auth(provider, { serverUrl, authorizationCode: code });
|
||||
const authResult = await auth(provider, { serverUrl, authorizationCode: code })
|
||||
|
||||
if (authResult === 'AUTHORIZED') {
|
||||
console.log(`${logPrefix} Authorization successful via SDK auth(). Notifying opener...`);
|
||||
console.log(`${logPrefix} Authorization successful via SDK auth(). Notifying opener...`)
|
||||
// --- Notify Opener and Close (Success) ---
|
||||
if (window.opener && !window.opener.closed) {
|
||||
window.opener.postMessage(
|
||||
{ type: 'mcp_auth_callback', success: true },
|
||||
window.location.origin,
|
||||
);
|
||||
window.close();
|
||||
window.opener.postMessage({ type: 'mcp_auth_callback', success: true }, window.location.origin)
|
||||
window.close()
|
||||
} else {
|
||||
console.warn(`${logPrefix} No opener window detected. Redirecting to root.`);
|
||||
window.location.href = '/'; // Or a configured post-auth destination
|
||||
console.warn(`${logPrefix} No opener window detected. Redirecting to root.`)
|
||||
window.location.href = '/' // Or a configured post-auth destination
|
||||
}
|
||||
// Clean up state ONLY on success and after notifying opener
|
||||
localStorage.removeItem(stateKey);
|
||||
|
||||
localStorage.removeItem(stateKey)
|
||||
} else {
|
||||
// This case shouldn't happen if `authorizationCode` is provided to `auth()`
|
||||
console.warn(`${logPrefix} SDK auth() returned unexpected status: ${authResult}`);
|
||||
throw new Error(`Unexpected result from authentication library: ${authResult}`);
|
||||
console.warn(`${logPrefix} SDK auth() returned unexpected status: ${authResult}`)
|
||||
throw new Error(`Unexpected result from authentication library: ${authResult}`)
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
console.error(`${logPrefix} Error during OAuth callback handling:`, err);
|
||||
const errorMessage = err instanceof Error ? err.message : String(err);
|
||||
console.error(`${logPrefix} Error during OAuth callback handling:`, err)
|
||||
const errorMessage = err instanceof Error ? err.message : String(err)
|
||||
|
||||
// --- Notify Opener and Display Error (Failure) ---
|
||||
if (window.opener && !window.opener.closed) {
|
||||
window.opener.postMessage(
|
||||
{ type: 'mcp_auth_callback', success: false, error: errorMessage },
|
||||
window.location.origin,
|
||||
);
|
||||
window.opener.postMessage({ type: 'mcp_auth_callback', success: false, error: errorMessage }, window.location.origin)
|
||||
// Optionally close even on error, depending on UX preference
|
||||
// window.close();
|
||||
}
|
||||
|
|
@ -116,22 +107,22 @@ export async function onMcpAuthorization() {
|
|||
</p>
|
||||
<p>You can close this window or <a href="#" onclick="window.close(); return false;">click here to close</a>.</p>
|
||||
<pre style="font-size: 0.8em; color: #555; margin-top: 20px; white-space: pre-wrap;">${
|
||||
err instanceof Error ? err.stack : ''
|
||||
}</pre>
|
||||
err instanceof Error ? err.stack : ''
|
||||
}</pre>
|
||||
</div>
|
||||
`;
|
||||
`
|
||||
} catch (displayError) {
|
||||
console.error(`${logPrefix} Could not display error in callback window:`, displayError);
|
||||
console.error(`${logPrefix} Could not display error in callback window:`, displayError)
|
||||
}
|
||||
// Clean up potentially invalid state on error
|
||||
if (stateKey) {
|
||||
localStorage.removeItem(stateKey);
|
||||
localStorage.removeItem(stateKey)
|
||||
}
|
||||
// Clean up potentially dangling verifier or last_auth_url if auth failed badly
|
||||
// Note: saveTokens should clean these on success
|
||||
if (provider) {
|
||||
localStorage.removeItem(provider.getKey('code_verifier'));
|
||||
localStorage.removeItem(provider.getKey('last_auth_url'));
|
||||
localStorage.removeItem(provider.getKey('code_verifier'))
|
||||
localStorage.removeItem(provider.getKey('last_auth_url'))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
import { OAuthMetadata } from '@modelcontextprotocol/sdk/shared/auth.js';
|
||||
import { OAuthMetadata } from '@modelcontextprotocol/sdk/shared/auth.js'
|
||||
|
||||
/**
|
||||
* Internal type for storing OAuth state in localStorage during the popup flow.
|
||||
* @internal
|
||||
*/
|
||||
export interface StoredState {
|
||||
expiry: number;
|
||||
metadata?: OAuthMetadata; // Optional: might not be needed if auth() rediscovers
|
||||
serverUrlHash: string;
|
||||
expiry: number
|
||||
metadata?: OAuthMetadata // Optional: might not be needed if auth() rediscovers
|
||||
serverUrlHash: string
|
||||
// Add provider options needed on callback:
|
||||
providerOptions: {
|
||||
serverUrl: string;
|
||||
storageKeyPrefix: string;
|
||||
clientName: string;
|
||||
clientUri: string;
|
||||
callbackUrl: string;
|
||||
};
|
||||
}
|
||||
serverUrl: string
|
||||
storageKeyPrefix: string
|
||||
clientName: string
|
||||
clientUri: string
|
||||
callbackUrl: string
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue