(
serverName: string,
serverConfig: McpSSEServerConfig | McpHTTPServerConfig,
{ preserveStepUpState = false }: { preserveStepUpState?: boolean } = {},
)
| 465 | * access tokens and many servers implicitly invalidate associated access tokens. |
| 466 | */ |
| 467 | export async function revokeServerTokens( |
| 468 | serverName: string, |
| 469 | serverConfig: McpSSEServerConfig | McpHTTPServerConfig, |
| 470 | { preserveStepUpState = false }: { preserveStepUpState?: boolean } = {}, |
| 471 | ): Promise<void> { |
| 472 | const storage = getSecureStorage() |
| 473 | const existingData = storage.read() |
| 474 | if (!existingData?.mcpOAuth) return |
| 475 | |
| 476 | const serverKey = getServerKey(serverName, serverConfig) |
| 477 | const tokenData = existingData.mcpOAuth[serverKey] |
| 478 | |
| 479 | // Attempt server-side revocation if there are tokens to revoke (best-effort) |
| 480 | if (tokenData?.accessToken || tokenData?.refreshToken) { |
| 481 | try { |
| 482 | // For XAA (and any PRM-discovered auth), the AS is at a different host |
| 483 | // than the MCP URL — use the persisted discoveryState if we have it. |
| 484 | const asUrl = |
| 485 | tokenData.discoveryState?.authorizationServerUrl ?? serverConfig.url |
| 486 | const metadata = await fetchAuthServerMetadata( |
| 487 | serverName, |
| 488 | asUrl, |
| 489 | serverConfig.oauth?.authServerMetadataUrl, |
| 490 | ) |
| 491 | |
| 492 | if (!metadata) { |
| 493 | logMCPDebug(serverName, 'No OAuth metadata found') |
| 494 | } else { |
| 495 | const revocationEndpoint = |
| 496 | 'revocation_endpoint' in metadata |
| 497 | ? metadata.revocation_endpoint |
| 498 | : null |
| 499 | if (!revocationEndpoint) { |
| 500 | logMCPDebug(serverName, 'Server does not support token revocation') |
| 501 | } else { |
| 502 | const revocationEndpointStr = String(revocationEndpoint) |
| 503 | // RFC 7009 defines revocation_endpoint_auth_methods_supported |
| 504 | // separately from the token endpoint's list; prefer it if present. |
| 505 | const authMethods = |
| 506 | ('revocation_endpoint_auth_methods_supported' in metadata |
| 507 | ? metadata.revocation_endpoint_auth_methods_supported |
| 508 | : undefined) ?? |
| 509 | ('token_endpoint_auth_methods_supported' in metadata |
| 510 | ? metadata.token_endpoint_auth_methods_supported |
| 511 | : undefined) |
| 512 | const authMethod: 'client_secret_basic' | 'client_secret_post' = |
| 513 | authMethods && |
| 514 | !authMethods.includes('client_secret_basic') && |
| 515 | authMethods.includes('client_secret_post') |
| 516 | ? 'client_secret_post' |
| 517 | : 'client_secret_basic' |
| 518 | logMCPDebug( |
| 519 | serverName, |
| 520 | `Revoking tokens via ${revocationEndpointStr} (${authMethod})`, |
| 521 | ) |
| 522 | |
| 523 | // Revoke refresh token first (more important - prevents future access token generation) |
| 524 | if (tokenData.refreshToken) { |
no test coverage detected