(
refreshToken: string,
)
| 2088 | } |
| 2089 | |
| 2090 | async refreshAuthorization( |
| 2091 | refreshToken: string, |
| 2092 | ): Promise<OAuthTokens | undefined> { |
| 2093 | const serverKey = getServerKey(this.serverName, this.serverConfig) |
| 2094 | const claudeDir = getClaudeConfigHomeDir() |
| 2095 | await mkdir(claudeDir, { recursive: true }) |
| 2096 | const sanitizedKey = serverKey.replace(/[^a-zA-Z0-9]/g, '_') |
| 2097 | const lockfilePath = join(claudeDir, `mcp-refresh-${sanitizedKey}.lock`) |
| 2098 | |
| 2099 | let release: (() => Promise<void>) | undefined |
| 2100 | for (let retry = 0; retry < MAX_LOCK_RETRIES; retry++) { |
| 2101 | try { |
| 2102 | logMCPDebug( |
| 2103 | this.serverName, |
| 2104 | `Acquiring refresh lock (attempt ${retry + 1})`, |
| 2105 | ) |
| 2106 | release = await lockfile.lock(lockfilePath, { |
| 2107 | realpath: false, |
| 2108 | onCompromised: () => { |
| 2109 | logMCPDebug(this.serverName, `Refresh lock was compromised`) |
| 2110 | }, |
| 2111 | }) |
| 2112 | logMCPDebug(this.serverName, `Acquired refresh lock`) |
| 2113 | break |
| 2114 | } catch (e: unknown) { |
| 2115 | const code = getErrnoCode(e) |
| 2116 | if (code === 'ELOCKED') { |
| 2117 | logMCPDebug( |
| 2118 | this.serverName, |
| 2119 | `Refresh lock held by another process, waiting (attempt ${retry + 1}/${MAX_LOCK_RETRIES})`, |
| 2120 | ) |
| 2121 | await sleep(1000 + Math.random() * 1000) |
| 2122 | continue |
| 2123 | } |
| 2124 | logMCPDebug( |
| 2125 | this.serverName, |
| 2126 | `Failed to acquire refresh lock: ${code}, proceeding without lock`, |
| 2127 | ) |
| 2128 | break |
| 2129 | } |
| 2130 | } |
| 2131 | if (!release) { |
| 2132 | logMCPDebug( |
| 2133 | this.serverName, |
| 2134 | `Could not acquire refresh lock after ${MAX_LOCK_RETRIES} retries, proceeding without lock`, |
| 2135 | ) |
| 2136 | } |
| 2137 | |
| 2138 | try { |
| 2139 | // Re-read tokens after acquiring lock — another process may have refreshed |
| 2140 | clearKeychainCache() |
| 2141 | const storage = getSecureStorage() |
| 2142 | const data = storage.read() |
| 2143 | const tokenData = data?.mcpOAuth?.[serverKey] |
| 2144 | if (tokenData) { |
| 2145 | const expiresIn = (tokenData.expiresAt - Date.now()) / 1000 |
| 2146 | if (expiresIn > 300) { |
| 2147 | logMCPDebug( |
no test coverage detected