(state: OAuthDiscoveryState)
| 1995 | } |
| 1996 | |
| 1997 | async saveDiscoveryState(state: OAuthDiscoveryState): Promise<void> { |
| 1998 | const storage = getSecureStorage() |
| 1999 | const existingData = storage.read() || {} |
| 2000 | const serverKey = getServerKey(this.serverName, this.serverConfig) |
| 2001 | |
| 2002 | logMCPDebug( |
| 2003 | this.serverName, |
| 2004 | `Saving discovery state (authServer: ${state.authorizationServerUrl})`, |
| 2005 | ) |
| 2006 | |
| 2007 | // Persist only the URLs, NOT the full metadata blobs. |
| 2008 | // authorizationServerMetadata alone is ~1.5-2KB per MCP server (every |
| 2009 | // grant type, PKCE method, endpoint the IdP supports). On macOS the |
| 2010 | // keychain write goes through `security -i` which has a 4096-byte stdin |
| 2011 | // line limit — with hex encoding that's ~2013 bytes of JSON total. Two |
| 2012 | // OAuth MCP servers persisting full metadata overflows it, corrupting |
| 2013 | // the credential store (#30337). The SDK re-fetches missing metadata |
| 2014 | // with one HTTP GET on the next auth — see node_modules/.../auth.js |
| 2015 | // `cachedState.authorizationServerMetadata ?? await discover...`. |
| 2016 | const updatedData: SecureStorageData = { |
| 2017 | ...existingData, |
| 2018 | mcpOAuth: { |
| 2019 | ...existingData.mcpOAuth, |
| 2020 | [serverKey]: { |
| 2021 | ...existingData.mcpOAuth?.[serverKey], |
| 2022 | serverName: this.serverName, |
| 2023 | serverUrl: this.serverConfig.url, |
| 2024 | accessToken: existingData.mcpOAuth?.[serverKey]?.accessToken || '', |
| 2025 | expiresAt: existingData.mcpOAuth?.[serverKey]?.expiresAt || 0, |
| 2026 | discoveryState: { |
| 2027 | authorizationServerUrl: state.authorizationServerUrl, |
| 2028 | resourceMetadataUrl: state.resourceMetadataUrl, |
| 2029 | }, |
| 2030 | }, |
| 2031 | }, |
| 2032 | } |
| 2033 | |
| 2034 | storage.update(updatedData) |
| 2035 | } |
| 2036 | |
| 2037 | async discoveryState(): Promise<OAuthDiscoveryState | undefined> { |
| 2038 | const storage = getSecureStorage() |
nothing calls this directly
no test coverage detected